Skip to content

feat(runtime): add topic_router to DispatchResultApplier for per-event-type topic routing - #810

Merged
jonahgabriel merged 1 commit into
mainfrom
jonahgabriel/omn-4881-task-1-tdd-add-topic_router-param-to-dispatchresultapplier
Mar 13, 2026
Merged

jonahgabriel merged 1 commit into
mainfrom
jonahgabriel/omn-4881-task-1-tdd-add-topic_router-param-to-dispatchresultapplier

Conversation

@jonahgabriel

@jonahgabriel jonahgabriel commented Mar 13, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

  • Adds topic_router: dict[str, str] | None param to DispatchResultApplier.__init__
  • Each output event is published to its per-type declared Kafka topic instead of the single output_topic fallback
  • Events not in the router map fall back to output_topic (backward-compatible — no existing callers broken)
  • Router is built from contract.yaml published_events via build_topic_router_from_contract() (wired in OMN-4883)

Test plan

  • 3 new unit tests: routes known type, falls back for unknown type, no router uses output_topic
  • All 15 tests pass (12 existing + 3 new)
  • mypy --strict clean
  • All pre-commit hooks pass

Tickets

  • Fixes: OMN-4881
  • Epic: OMN-4880

Summary by CodeRabbit

Release Notes

  • New Features

    • Enabled per-event-type Kafka topic routing, allowing different event types to be published to distinct topics with automatic fallback to the default output topic for unmapped types.
  • Tests

    • Added comprehensive test coverage for topic routing functionality, including proper routing behavior, fallback handling, and default behavior when no custom routing is configured.

…t-type topic routing

Adds `topic_router: dict[str, str] | None` param to DispatchResultApplier.__init__.
When provided, each output event is published to its per-type declared Kafka topic
instead of the single `output_topic` fallback. Events not in the map fall back to
`output_topic` (backward-compatible).

The router is built from contract.yaml published_events via
build_topic_router_from_contract() (wired in OMN-4883).

Fixes: OMN-4881
Epic: OMN-4880
@jonahgabriel
jonahgabriel enabled auto-merge March 13, 2026 15:09
@coderabbitai

coderabbitai Bot commented Mar 13, 2026 •

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

The DispatchResultApplier class gains optional per-event-type Kafka topic routing. A new topic_router parameter accepts event class name to topic mappings, enabling selective topic assignment during event publishing with automatic fallback to the default output topic.

Changes

Cohort / File(s) Summary
Core Feature Implementation
src/omnibase_infra/runtime/service_dispatch_result_applier.py
Added optional topic_router parameter to constructor to map event type names to Kafka topics. Resolves publish topic per event during distribution with fallback to default output_topic. Updated logging and docstrings accordingly.
Test Coverage
tests/unit/runtime/test_service_dispatch_result_applier.py
Added helper builders _make_accepted_event and _make_result_with to construct test fixtures. Introduced three new tests validating topic router behavior: known event routing, unknown event fallback, and default behavior without router.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Poem

🐰 A rabbit hops through event streams so fine,
With topic routers that align,
Each event type now finds its place,
On Kafka topics at perfect grace!
🐇✨

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title accurately describes the main change: adding a topic_router parameter to DispatchResultApplier for per-event-type topic routing, which aligns directly with the primary modifications shown in both the implementation and test files.
Docstring Coverage ✅ Passed Docstring coverage is 80.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch jonahgabriel/omn-4881-task-1-tdd-add-topic_router-param-to-dispatchresultapplier
📝 Coding Plan
  • Generate coding plan for human review comments

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (3)
src/omnibase_infra/runtime/service_dispatch_result_applier.py (1)

136-166: Defensively copy topic_router in __init__.

Storing the passed dict directly allows external mutation after construction, which can change routing behavior at runtime unexpectedly.

Suggested patch
-        self._topic_router: dict[str, str] = topic_router or {}
+        self._topic_router: dict[str, str] = (
+            dict(topic_router) if topic_router is not None else {}
+        )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/omnibase_infra/runtime/service_dispatch_result_applier.py` around lines
136 - 166, The constructor stores the passed topic_router reference directly
(topic_router -> self._topic_router), which allows external mutation to affect
routing; defensively copy the mapping in __init__ so self._topic_router is an
independent dict (e.g., use a shallow copy when topic_router is not None) and
keep the existing fallback to an empty dict when topic_router is None; update
the assignment that sets self._topic_router in
ServiceDispatchResultApplier.__init__ to create that copy.
tests/unit/runtime/test_service_dispatch_result_applier.py (2)

423-468: Strengthen topic-router tests with single-call assertions.

Each test currently inspects only the last publish call; asserting exactly one publish call will catch accidental duplicate publishes.

Suggested patch
 async def test_topic_router_routes_known_event_to_declared_topic() -> None:
@@
     result = _make_result_with([_make_accepted_event()])
     await applier.apply(result)
+    bus.publish_envelope.assert_awaited_once()
     published_topic = bus.publish_envelope.call_args.kwargs["topic"]
     assert published_topic == "onex.evt.platform.node-registration-accepted.v1"
@@
 async def test_topic_router_falls_back_for_unknown_event_type() -> None:
@@
     result = _make_result_with([_make_accepted_event()])
     await applier.apply(result)
+    bus.publish_envelope.assert_awaited_once()
     published_topic = bus.publish_envelope.call_args.kwargs["topic"]
     assert published_topic == "responses"
@@
 async def test_no_topic_router_uses_output_topic() -> None:
@@
     result = _make_result_with([_make_accepted_event()])
     await applier.apply(result)
+    bus.publish_envelope.assert_awaited_once()
     published_topic = bus.publish_envelope.call_args.kwargs["topic"]
     assert published_topic == "responses"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/unit/runtime/test_service_dispatch_result_applier.py` around lines 423
- 468, Add an assertion that the mock publish method was called exactly once in
each of the three tests (test_topic_router_routes_known_event_to_declared_topic,
test_topic_router_falls_back_for_unknown_event_type,
test_no_topic_router_uses_output_topic) to catch unexpected duplicate publishes:
after awaiting applier.apply(result) assert the AsyncMock for
event_bus.publish_envelope was called exactly once (use
bus.publish_envelope.assert_awaited_once() or
bus.publish_envelope.assert_called_once() depending on whether publish_envelope
is awaited) before inspecting call_args.kwargs["topic"].

412-413: Remove type: ignore[type-arg] by typing the events parameter as list[BaseModel].

The events parameter can be precisely typed to match the ModelDispatchResult.output_events field annotation (list[BaseModel]), eliminating the need for the ignore comment. BaseModel is already imported in this file.

Change:

def _make_result_with(events: list) -> ModelDispatchResult:  # type: ignore[type-arg]

to:

def _make_result_with(events: list[BaseModel]) -> ModelDispatchResult:
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/unit/runtime/test_service_dispatch_result_applier.py` around lines 412
- 413, The helper function _make_result_with currently uses a broad events
parameter and silences a typing error with "type: ignore[type-arg]"; change the
signature to type events as list[BaseModel] to match
ModelDispatchResult.output_events and remove the ignore. Update the function
definition for _make_result_with(events: list[BaseModel]) -> ModelDispatchResult
so callers and the ModelDispatchResult construction use the correct typed list
of BaseModel.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/omnibase_infra/runtime/service_dispatch_result_applier.py`:
- Around line 136-166: The constructor stores the passed topic_router reference
directly (topic_router -> self._topic_router), which allows external mutation to
affect routing; defensively copy the mapping in __init__ so self._topic_router
is an independent dict (e.g., use a shallow copy when topic_router is not None)
and keep the existing fallback to an empty dict when topic_router is None;
update the assignment that sets self._topic_router in
ServiceDispatchResultApplier.__init__ to create that copy.

In `@tests/unit/runtime/test_service_dispatch_result_applier.py`:
- Around line 423-468: Add an assertion that the mock publish method was called
exactly once in each of the three tests
(test_topic_router_routes_known_event_to_declared_topic,
test_topic_router_falls_back_for_unknown_event_type,
test_no_topic_router_uses_output_topic) to catch unexpected duplicate publishes:
after awaiting applier.apply(result) assert the AsyncMock for
event_bus.publish_envelope was called exactly once (use
bus.publish_envelope.assert_awaited_once() or
bus.publish_envelope.assert_called_once() depending on whether publish_envelope
is awaited) before inspecting call_args.kwargs["topic"].
- Around line 412-413: The helper function _make_result_with currently uses a
broad events parameter and silences a typing error with "type:
ignore[type-arg]"; change the signature to type events as list[BaseModel] to
match ModelDispatchResult.output_events and remove the ignore. Update the
function definition for _make_result_with(events: list[BaseModel]) ->
ModelDispatchResult so callers and the ModelDispatchResult construction use the
correct typed list of BaseModel.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 66ad1cfe-d8ae-4b5b-be84-b6e3f57ceff9

📥 Commits

Reviewing files that changed from the base of the PR and between e8fc8c3 and 5e9e8fc.

📒 Files selected for processing (2)
  • src/omnibase_infra/runtime/service_dispatch_result_applier.py
  • tests/unit/runtime/test_service_dispatch_result_applier.py

@jonahgabriel
jonahgabriel added this pull request to the merge queue Mar 13, 2026
Merged via the queue into main with commit 07bde3a Mar 13, 2026
35 checks passed
@jonahgabriel
jonahgabriel deleted the jonahgabriel/omn-4881-task-1-tdd-add-topic_router-param-to-dispatchresultapplier branch March 13, 2026 15:33
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