Skip to content

fix(OMN-8724): coerce dict payload into typed handler model at dispatch boundary + SkillRoutingError envelope - #1983

Merged
jonahgabriel merged 1 commit into
devfrom
jonah/omn-8724-runtime-adapter-coerce-typed-model
Jun 14, 2026
Merged

jonahgabriel merged 1 commit into
devfrom
jonah/omn-8724-runtime-adapter-coerce-typed-model

Conversation

@jonahgabriel

@jonahgabriel jonahgabriel commented Jun 14, 2026 •

Copy link
Copy Markdown
Collaborator

OMN-8724 — coerce dict payload into typed handler model at the dispatch boundary

Fixes the confirmed node_golden_chain_sweep crash via the canonical onex node path (BUG A in the integration-tooling triage): AttributeError: 'dict' object has no attribute 'chains' at handler_golden_chain_sweep.py because runtime_local_adapter._invoke_handle_method handed the raw decoded dict to handle(self, request: GoldenChainSweepRequest) instead of the typed model.

Layer decision — A1 (systemic), not A2 (narrow)

Chose A1: coerce the dict into the handler's declared input type inside omnibase_infra.runtime.runtime_local_adapter, fixing the whole dict-not-typed dispatch-boundary class (same family as OMN-13141 / the savings_estimation .get() bug) — not a per-node model_validate band-aid. Blast radius is bounded and regression-free: coercion fires only when (a) the payload is a raw dict AND (b) the handler's single positional parameter is annotated with a concrete BaseModel subclass. That combination previously always crashed, so the only behavior change is crash → success. All pre-existing pass-through paths (already-a-model, name-heuristic, kwargs-fanout, zero/var-keyword params) are unchanged.

Root cause completion — PEP 563 string annotations

Every node handler uses from __future__ import annotations, so inspect.signature(...).parameters[0].annotation was the literal string "GoldenChainSweepRequest", and the issubclass(annotation, BaseModel) check never matched. Fixed by resolving forward refs with inspect.signature(handle_method, eval_str=True) (graceful fallback to unevaluated on NameError).

Step 3 — SkillRoutingError envelope

cli_node default (non-receipt) mode now emits the documented SkillRoutingError JSON envelope to stdout on non-zero exit instead of leaving only a stderr traceback. The golden_chain_sweep SKILL.md documents this contract ("Non-zero exit emits a SkillRoutingError JSON envelope — surface it verbatim"). Envelope shape matches omnibase_core.cli.cli_run_node._emit_error. RuntimeLocal gains a public last_error property to populate message.

CI gate — real dispatch path

tests/unit/runtime/test_runtime_local_operation_match_typed_handler.py boots a typed single-model-parameter handler through RuntimeLocal.run_async() (the real operation_match dispatch path) and asserts the run COMPLETES. This is the test that "would have caught this" — handler-isolation tests pass while the live path crashed. Plus cli_node test asserting the SkillRoutingError envelope on a failed default-mode run.

Verification (local, in worktree)

  • uv run ruff format / ruff check --fix on changed files — clean
  • uv run mypy src/omnibase_infra/runtime/runtime_local_adapter.py src/omnibase_infra/runtime/runtime_local.py src/omnibase_infra/cli/cli_node.py --strict — Success, 3 files
  • New test: FAILS (AttributeError: 'dict' object has no attribute 'correlation_id') on origin/dev, PASSES with fix
  • uv run pytest tests/unit/runtime/ tests/unit/cli/ — 5157 passed (the only failures are an env-artifact skip-test that requires omnimarket installed, and a load-sensitive perf stress test that passes in isolation — both pre-existing, unrelated)
  • pre-commit run on changed files — all hooks pass (incl. union budget gate)
  • End-to-end proof: uv run onex node node_golden_chain_sweep → result=completed (was result=failed + traceback on origin/dev). .onex_state/workflow_result.json terminal_payload status: pass.

Companion PR

omnimarket jonah/omn-8724-golden-chain-sweep-downgrade-claim adds the runtime-injected correlation_id field to GoldenChainSweepRequest and downgrades the node/skill evidence claim (the node is pure compute over caller-supplied rows — never live evidence). Both land independently on dev.

dod_evidence / OCC pairing

Paired OCC receipt PR in onex_change_control carries contracts/OMN-8724.yaml + PASS receipts under drift/dod_receipts/OMN-8724/.

Evidence-Source: OCC#2626
Evidence-Ticket: OMN-8724

Closes OMN-8724 (crash + SkillRoutingError halves). Parent: OMN-8718.

…ch boundary + emit SkillRoutingError envelope

Systemic fix for the dict-not-typed dispatch-boundary class (same family as
OMN-13141). _invoke_handle_method now validates a raw decoded dict into the
handler's single annotated BaseModel parameter before calling it. Root cause:
PEP 563 string annotations (every node handler uses from __future__ import
annotations) meant the parameter annotation was the literal string, so the
BaseModel-subclass check never matched — fixed via inspect.signature(eval_str=True).

A typed single-parameter handler reached via operation_match (e.g.
NodeGoldenChainSweep.handle(self, request: GoldenChainSweepRequest)) crashed
with AttributeError on the first attribute access via onex node, while the
module __main__ entrypoint (which builds the model itself) worked.

Also: cli_node default mode now emits the documented SkillRoutingError JSON
envelope on non-zero exit instead of leaving only a stderr traceback (the
golden_chain_sweep skill documents this contract). RuntimeLocal gains a public
last_error property to populate the envelope.

Real-dispatch-path test boots a typed operation_match contract through
RuntimeLocal.run_async() — reproduces the crash on origin/dev, passes with fix.

Evidence-Source: OCC#PENDING
Evidence-Ticket: OMN-8724
@coderabbitai

coderabbitai Bot commented Jun 14, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@jonahgabriel, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 2 hours, 13 minutes, and 21 seconds. Learn how PR review limits work.

Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file).

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 4b5ee97f-4f0d-4011-920c-692d23efb74f

📥 Commits

Reviewing files that changed from the base of the PR and between b56a3ff and 03712fb.

📒 Files selected for processing (5)
  • src/omnibase_infra/cli/cli_node.py
  • src/omnibase_infra/runtime/runtime_local.py
  • src/omnibase_infra/runtime/runtime_local_adapter.py
  • tests/unit/cli/test_cli_node.py
  • tests/unit/runtime/test_runtime_local_operation_match_typed_handler.py
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch jonah/omn-8724-runtime-adapter-coerce-typed-model

Comment @coderabbitai help to get the list of available commands and usage tips.

@jonahgabriel
jonahgabriel added this pull request to the merge queue Jun 14, 2026
Merged via the queue into dev with commit 0159438 Jun 14, 2026
81 of 84 checks passed
@jonahgabriel
jonahgabriel deleted the jonah/omn-8724-runtime-adapter-coerce-typed-model branch June 14, 2026 18:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant