Skip to content

feat: FakeBus mirrors the .intent-suffixed twin for aliased intents - #411

Open
JarbasAl wants to merge 7 commits into
devfrom
feat/fakebus-intent-legacy-reemit
Open

feat: FakeBus mirrors the .intent-suffixed twin for aliased intents#411
JarbasAl wants to merge 7 commits into
devfrom
feat/fakebus-intent-legacy-reemit

Conversation

@JarbasAl

@JarbasAl JarbasAl commented Aug 1, 2026

Copy link
Copy Markdown
Member

Why

OVOS-MSG-1 §2.1.1 assembles the per-intent dispatch topic at runtime as <skill_id>:<intent_name>. Old ovos-workshop releases built it from the padatious resource filename, so a skill with food.order.intent registered and listened on <skill_id>:food.order.intent. Current workshop is spec-pure and registers <skill_id>:food.order.

ovos-bus-client#271 makes the real client bridge the two spellings when emit_legacy is on. This PR gives the test doubles the same behavior. Without it every harness built on FakeBus — ovoscope, the skill test suites, the satellite fakes — hides the compat path, and a skill that passes its tests still misses the dispatch on a real bus.

The version skew this covers is unchanged from the outside:

old core (dispatches X:Y.intent) new core (dispatches X:Y)
old skill (listens X:Y.intent) works today, untouched twin (rule 1)
new skill (listens X:Y) modernize (rule 2) works today, untouched

Design: two stateless rules

Per the maintainer-approved simplification, the compat layer is two if blocks and nothing else:

RULE 1. A canonical intent dispatch also fires its legacy_intent_topic twin, carrying an _intent_compat_twin: True context marker.

RULE 2. A suffixed intent dispatch that is not already such a twin also fires its canonical_intent_topic form.

The real client splits these over a wire send and a wire receive; a fake bus is one process, so both land in emit(), right after the existing counterpart_topics() dispatch. The two cases are mutually exclusive and neither cascades, so one emit reaches each handler exactly once — provable by inspection, with no bookkeeping to audit.

Kill-switch: delete the two if blocks. Until then the bridge rides the existing emit_legacy flag, the same one the namespace bridge uses. There is no second flag.

What the previous revision had, and why it is gone

IntentAliasRegistry, the per-bus alias table, the on() / once() / remove() / remove_all_listeners() bookkeeping that maintained it, and the intent_reemit_blanket flag (blanket is the behavior now) are all removed. The bridge does not need to know which handlers exist: a twin nobody listens to is a few ignored bytes.

_LegacyIntentBridge stays as the mixin shared by FakeBus and AsyncFakeBus, so the two doubles cannot drift, but it now holds one stateless method.

Dependency order

  1. ovos-spec-tools#88 — the pure helpers. Released as 1.6.0a1; the floor here is raised to it.
  2. ovos-spec-tools#92 — removes IntentAliasRegistry and legacy_reemit_targets. No follow-up floor bump is needed here: after this rewrite fakebus.py imports only canonical_intent_topic, legacy_intent_topic, and is_intent_topic, all present in 1.6.0a1.
  3. ovos-workshop#500 — registration becomes canonical-only.
  4. ovos-bus-client#271 + this PR — the two rules execute in both bus layers.
  5. ovoscope#127 / the ovos-core guards — the strict-xfail guards flip once the double behaves like the real bus.

Non-normative

No specification mandates the suffixed topic or the twin. This is transitional tooling scoped to the migration period. New code must produce and consume canonical topics only.

Tests

test/unittests/test_fakebus_intent_legacy_reemit.py, 15 tests (was 26): rule 1 (twin fired, marked, payload and context kept, canonical handler still once, a handler on both spellings hears both frames once each, nothing when compat is off); rule 2 (canonical handler hears an old-style dispatch, suffixed handler still gets the original, a marked twin is not modernized again, no cascade, nothing when compat is off); non-intent topics untouched; AsyncFakeBus parity on both rules and the off switch.

Full suite: 951 passed, 1 skipped, 1 pre-existing test_events.py::TestEventSchedulerInterface::test_00_init failure also present on dev.


Implemented by Claude (opus), orchestrated by Claude Fable.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Improved legacy intent-topic compatibility for synchronous and asynchronous FakeBus messaging.
    • Intent events now support both canonical and legacy topic formats while preventing duplicate or recursive dispatch.
    • Compatibility markers are preserved for firehose consumers but hidden from local handlers.
  • Bug Fixes

    • Prevented legacy intent events from cascading through descendant handlers or being delivered multiple times.
  • Tests

    • Added comprehensive coverage for topic bridging, deduplication, marker handling, non-intent topics, and asynchronous dispatch.

Old ovos-workshop built the per-intent dispatch topic from the padatious
resource filename, so the `.intent` extension leaked onto the wire. The real
MessageBusClient now mirrors an intent dispatch onto that suffixed twin when
emit_legacy is on. A test double that skipped the mirror would hide the compat
path from every harness built on it.

FakeBus and AsyncFakeBus now share a `_LegacyIntentBridge` that calls
`ovos_spec_tools.intent_topics.legacy_reemit_targets` after the namespace
counterpart dispatch, with an `IntentAliasRegistry` filled from the bus's own
on() / once() calls. Blanket mode (`intent_reemit_blanket`, off by default)
mirrors every intent dispatch for pure-bus listeners that never register.

The spec-tools import is guarded: against a release without the intent-topic
helpers both fake buses behave exactly as before.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@JarbasAl, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 46 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

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.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 59334797-4619-4c53-8897-45e95560a74e

📥 Commits

Reviewing files that changed from the base of the PR and between 82e5d84 and a345cef.

📒 Files selected for processing (2)
  • ovos_utils/fakebus.py
  • test/unittests/test_fakebus_intent_legacy_reemit.py
📝 Walkthrough

Walkthrough

FakeBus and AsyncFakeBus now bridge canonical and .intent-suffixed intent topics. Wire markers remain on firehose frames but are removed before local delivery. New tests cover dispatch, isolation, disabled compatibility, and asynchronous behavior.

Changes

Legacy intent-topic bridge

Layer / File(s) Summary
Bridge contract and counterpart dispatch
ovos_utils/fakebus.py, pyproject.toml
Adds shared counterpart dispatch logic and updates the ovos-spec-tools requirement.
Synchronous and asynchronous emission integration
ovos_utils/fakebus.py
Integrates the bridge with both bus classes. Markers remain on firehose frames and are removed before local handlers receive events.
Compatibility behavior validation
test/unittests/test_fakebus_intent_legacy_reemit.py, test/unittests/log_test/rotate.log.1
Adds coverage for both bridge directions, marker handling, disabled compatibility, unrelated topics, cascading prevention, and async dispatch. Updates the recorded log timestamp.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant FakeBus
  participant LegacyIntentBridge
  participant Firehose
  participant LocalHandlers
  FakeBus->>LegacyIntentBridge: emit intent counterpart
  LegacyIntentBridge->>Firehose: publish marked wire frame
  LegacyIntentBridge->>LocalHandlers: dispatch unmarked local event
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 11.11% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: FakeBus mirrors aliased intent topics to their .intent-suffixed twins.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/fakebus-intent-legacy-reemit

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@github-actions

github-actions Bot commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

Hello there! I've processed your latest changes. 🌊

I've aggregated the results of the automated checks for this PR below.

🔍 Lint

The automated sentinel is back with news. 💂‍♂️

ruff: issues found — see job log

⚖️ License Check

Reading the fine print with a magnifying glass. 🔍

✅ No license violations found.

Policy: Apache 2.0 (universal donor). StrongCopyleft / NetworkCopyleft / WeakCopyleft / Other / Error categories fail. MPL allowed.

🏷️ Release Preview

Get ready for the next big release! 🚀

Current: 0.13.9a2Next: 0.14.0a1

Signal Value
Label feature
PR title feat: FakeBus mirrors the .intent-suffixed twin for aliased intents
Bump minor

✅ PR title follows conventional commit format.


🚀 Release Channel Compatibility

Predicted next version: 0.14.0a1

Channel Status Note Current Constraint
Stable Too new (must be <0.9.0) ovos-utils>=0.8.1,<0.9.0
Testing Too new (must be <0.8.5) ovos-utils>=0.8.4,<0.8.5
Alpha Compatible ovos-utils>=0.13.9a1

🔒 Security (pip-audit)

I've checked the digital signatures of our packages. ✍️

✅ No known vulnerabilities found (47 packages scanned).

📊 Coverage

How much of the logic is under the microscope? 🔬

85.3% total coverage

Files below 80% coverage (5 files)
File Coverage Missing lines
ovos_utils/log_parser.py 48.4% 225
ovos_utils/__init__.py 63.6% 16
ovos_utils/file_utils.py 74.4% 56
ovos_utils/thread_utils.py 76.9% 12
ovos_utils/geolocation.py 78.4% 22

Full report: download the coverage-report artifact.

📋 Repo Health

I've checked the repo's posture (aka architectural alignment). 🧘

✅ All required files present.

Latest Version: 0.13.9a2

ovos_utils/version.py — Version file
README.md — README
LICENSE — License file
pyproject.toml — pyproject.toml
⚠️ setup.py — setup.py
CHANGELOG.md — Changelog
ovos_utils/version.py has valid version block markers

🔨 Build Tests

Assembling the puzzle pieces of your PR. 🧩

✅ All versions pass

Python Build Install Tests
3.10
3.11
3.12
3.13
3.14

Closing the loop on this automated check ♻️

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The fake buses bridge the canonical `<skill_id>:<intent_name>` dispatch topic
and the legacy `.intent`-suffixed spelling with the same two rules the real
`MessageBusClient` now uses. The client splits them over a wire send and a
wire receive; a fake bus is one process, so both land in `emit`:

* a canonical dispatch also fires its suffixed twin, marked in `context` with
  `_intent_compat_twin`;
* a suffixed dispatch that is not already such a twin also fires its canonical
  spelling.

The two cases are mutually exclusive and neither cascades, so one emit reaches
each handler exactly once. The alias registry, the per-bus alias table, the
`on()` / `remove()` bookkeeping, and the blanket flag are all gone: which
handlers exist is not something the bridge needs to know.

Only the two pure helpers are imported from ovos-spec-tools, so the existing
>=1.6.0a1 floor stays sufficient.
JarbasAl and others added 3 commits August 1, 2026 17:14
spec-tools#92 (registry removal) merged and released as 1.6.0a2;
pin to the simplified intent_topics surface.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Message.forward()/reply() deep-copy the whole context, so the twin marker in
message.context rode onto every descendant frame. A handler that forwarded a
received twin's context to emit an unrelated suffixed intent branded that frame
a twin, and _bridge_intent_topics skipped its canonical spelling -- silent loss
for the old-emitter -> new-core population the rule serves.

emit now pops the marker into a local decision before local dispatch, so
descendants start clean, and _bridge_intent_topics trusts that decision instead
of reading context. RULE 1's twin still carries the marker on the wire (its
serialized form goes on the "message" firehose), while local handlers receive
an unmarked copy -- mirroring the real client, whose local delivery happens on
the receive side after the pop. Regression tests build the follow-up via
forward()/reply() off a received twin and assert the unrelated canonical topic
is still modernized; wire-survival keeps the second-receiver skip property.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@JarbasAl
JarbasAl marked this pull request as ready for review August 1, 2026 18:13
@github-actions github-actions Bot added feature and removed feature labels Aug 1, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
ovos_utils/fakebus.py (1)

119-123: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Optional: avoid constructing the local twin message twice.

message.forward(legacy, message.data) runs twice (Line 120 for wire_twin, Line 123 for the local dispatch). Line 123 duplicates the same object construction Line 120 already did. Not a bug, since is_twin semantics stay correct, but a single forward() call plus a context copy would reduce duplicated work.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ovos_utils/fakebus.py` around lines 119 - 123, Update the message forwarding
logic around wire_twin to call message.forward(legacy, message.data) only once,
then reuse the resulting message for local dispatch while preserving the
separate context mutation and existing is_twin semantics.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@ovos_utils/fakebus.py`:
- Around line 217-219: The intent-topic bridge can propagate exceptions from
both synchronous and asynchronous emit paths; wrap the _bridge_intent_topics
call in FakeBus.emit at ovos_utils/fakebus.py:217-219 and AsyncFakeBus.emit at
ovos_utils/fakebus.py:577-579 with try/except and LOG.exception, matching the
existing counterpart-topic isolation pattern while preserving successful local
handler execution.

---

Nitpick comments:
In `@ovos_utils/fakebus.py`:
- Around line 119-123: Update the message forwarding logic around wire_twin to
call message.forward(legacy, message.data) only once, then reuse the resulting
message for local dispatch while preserving the separate context mutation and
existing is_twin semantics.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 6dc76a56-f66c-4bf9-b96a-e0c013ff4cdd

📥 Commits

Reviewing files that changed from the base of the PR and between b742ad3 and 82e5d84.

⛔ Files ignored due to path filters (2)
  • test/unittests/log_test/configured.log is excluded by !**/*.log
  • test/unittests/log_test/rotate.log is excluded by !**/*.log
📒 Files selected for processing (4)
  • ovos_utils/fakebus.py
  • pyproject.toml
  • test/unittests/log_test/rotate.log.1
  • test/unittests/test_fakebus_intent_legacy_reemit.py

Comment thread ovos_utils/fakebus.py Outdated
_bridge_intent_topics() ran unguarded in both FakeBus.emit and
AsyncFakeBus.emit, unlike the counterpart-topics loop right above it,
which isolates per-topic errors with try/except + LOG.exception. A
raising bridge helper would propagate out of emit() and take down local
handler dispatch with it. Wrap both call sites in the same guard.

Addresses CodeRabbit review on ovos-utils#411.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant