Skip to content

feat!: drop legacy wire compat (kill-switch — merge when fleet upgraded) - #837

Closed
JarbasAl wants to merge 3 commits into
devfrom
feat/drop-legacy-wire-compat
Closed

JarbasAl wants to merge 3 commits into
devfrom
feat/drop-legacy-wire-compat

Conversation

@JarbasAl

@JarbasAl JarbasAl commented Aug 1, 2026

Copy link
Copy Markdown
Member

The kill-switch

This is a prepared trigger, not a change to merge today. It makes ovos-core
spec-pure on the wire, so the stack-wide legacy-compat drop can be tested at
any time and pulled the moment the fleet upgrades.

Do not merge until then, and merge it together with its sibling drop PRs.

The surprise: ovos-core emitted no legacy twins

Worth stating plainly, because it changes what this PR is. ovos-core never
emitted a legacy twin of anything. send_complete_intent_failure emits
ovos.intent.unmatched alone; the legacy complete_intent_failure was
re-delivered by the bus-client bridge, not by this repo. Same for the rest
of the intent layer.

So the work here is not deletion. It is the handful of places where ovos-core
still writes a legacy topic literal and relied on the bridge to reach a
spec-side consumer — or would strand a spec-side consumer once the bridge goes.

What changed

File Was Now
intent_services/service.py 'mycroft.audio.play_sound' (×2: cancel + no-match error sound) SpecMessage.AUDIO_PLAY_SOUND
intent_services/stop_service.py "mycroft.stop" SpecMessage.STOP (STOP-1 §5.3)
intent_services/stop_service.py "skill.stop.pong" SpecMessage.STOP_PONG (STOP-1 §4.2)
intent_services/stop_service.py "mycroft.audio.speech.stop" SpecMessage.AUDIO_STOP
skill_installer.py "mycroft.audio.play_sound" (×2) SpecMessage.AUDIO_PLAY_SOUND

Plus the docstring at send_complete_intent_failure, which promised the
bridge would re-deliver the legacy topic. It no longer does, and the docstring
now says so.

What is deliberately left

  • {skill_id}.stop.ping / {skill_id}.stop — runtime-assembled per-skill
    placeholders, DELIBERATE EXCLUSION 2 in MIGRATION_MAP. Replacing them with
    the STOP-1 broadcast is engine adoption, not a bridge dependency.
  • mycroft.skill.handler.complete / .error subscriptions in
    dispatcher.py — the PIPELINE-1 §8 trio is orchestrator-owned and
    intentionally NOT migrated. The framework keeps these as a private
    done-signal.
  • mycroft.skills.abort_question — not in MIGRATION_MAP, never bridged.
  • "complete_intent_failure" at service.py:463 — a metrics label in
    the opendata upload payload, not a bus topic.
  • Padatious legacy blacklist ids — engine-side compat inside
    ovos-padatious (_canonicalize_blacklist), not bus compat. Still works,
    still tested.

Downstream this now requires

Flipping the producer means the consumer must have adopted the spec topic.
This is the "fleet upgraded" precondition, spelled out:

  • ovos-audio must consume ovos.audio.play_sound and ovos.audio.stop
    (AUDIO-1 §4.2 / §6);
  • skills must listen on ovos.stop and answer on ovos.stop.pong — that half
    ships in the sibling ovos-workshop drop PR.

Tests

Unit tests that asserted the legacy topic are flipped to assert the spec one,
and test_handle_global_stop additionally asserts mycroft.stop is not
emitted.

The end-to-end suite used to run every scenario twice, once per namespace,
because the bus bridged them. The "legacy" entry is removed from every
NAMESPACE_PATHS, and the other half is pinned in one new file:

  • test/end2end/test_no_legacy_wire_compat.py — injecting on
    recognizer_loop:utterance starts no pipeline; a no-match emits
    ovos.intent.unmatched with no complete_intent_failure twin.

test/end2end/test_intent_alias_backcompat.py is half-inverted:
test_legacy_dispatch_topic_fires_handler becomes
test_legacy_dispatch_topic_reaches_nothing, since both mechanisms that made
it work are gone (workshop's dual-bind, dropped by ovos-workshop#500, and the
bus twin, dropped here). The blacklist half stays green — it is engine-side.

Also flipped: the stop end-to-end expectations (mycroft.stop ->
ovos.stop), and both spellings of recognizer_loop:audio_output_* are now
filtered in the ignore lists — ovoscope's TTS mock still emits the legacy one
and nothing bridges it. Follow-up for ovoscope: adopt AUDIO-1 §5.1/§5.2,
and stop passing emit_legacy / modernize to FakeBus.

Results on this branch, with the sibling drop branches stacked:

Suite Result
test/unittests/ 306 passed
test/end2end/test_stop.py + test_stop_refactor.py 12 passed
test/end2end/test_no_legacy_wire_compat.py 3 passed
test/end2end/test_no_skills.py 2 passed
test/end2end/test_intent_alias_backcompat.py errors in setUp — see below

test_intent_alias_backcompat.py cannot run in this environment at all: its
setUp touches ovos_padatious.opm._warned_legacy_blacklist_entries, which
the installed ovos-padatious does not have. That is environment drift on
dev, not something this PR causes — the failure happens before any changed
code runs.

To test the post-compat world, stack the branches:

export PYTHONPATH=/path/to/ovos-spec-tools:\
/path/to/ovos-bus-client@feat/drop-legacy-wire-compat:\
/path/to/ovos-utils@feat/drop-fakebus-legacy-compat:\
/path/to/ovos-workshop@feat/drop-legacy-compat
pytest test/unittests/
pytest test/end2end/test_no_legacy_wire_compat.py test/end2end/test_intent_alias_backcompat.py

Order

  1. ovos-spec-tools#88 — helpers (merged)
  2. ovos-bus-client#271, ovos-utils#411, ovos-workshop#500 — the compat train
  3. the four drop PRs, together

Merging any drop PR alone breaks the stack. They go in as one flip.


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

🤖 Generated with Claude Code

The four drop PRs

They flip together, or the stack breaks.

Repo Kill-switch PR Based on
ovos-bus-client OpenVoiceOS/ovos-bus-client#272 #271
ovos-utils OpenVoiceOS/ovos-utils#412 #411
ovos-core #837 dev
ovos-workshop OpenVoiceOS/ovos-workshop#501 #500

The compat train they undo: OpenVoiceOS/ovos-spec-tools#88 (merged),
OpenVoiceOS/ovos-bus-client#271, OpenVoiceOS/ovos-utils#411,
OpenVoiceOS/ovos-workshop#500.

ovos-spec-tools keeps its helpers. MIGRATION_MAP, SPEC_TO_LEGACY,
migration_counterpart and ovos_spec_tools.intent_topics are pure functions
the spec linter and migration tooling use. Nothing is dropped there.


Red CI diagnosis (ovoscope check). The single failing check is pre-existing on dev, independent of this branch: dev's own Ovoscope End-to-End Tests workflow fails the same way on unrelated recent pushes (runs 30673793131, 30672760305, 30094248519 — all failure), with the same signature — test_activate.py::TestDeactivate::test_deactivate/test_activate timing out waiting for ovos.utterance.handled, plus test_intent_alias_backcompat.py subfailures. This branch touches neither activation/deactivation dispatch nor the intent-alias backcompat path. Not fixed in-branch; tracked as a pre-existing dev issue.

JarbasAl and others added 2 commits August 1, 2026 14:35
ovos-core never emitted a legacy twin of anything: the intent layer already
speaks the spec topics, and the legacy copies were re-delivered by the
bus-client bridge. With the bridge removed, what is left is the handful of
places that still wrote a legacy topic literal.

Flipped to the SpecMessage constant:

* mycroft.audio.play_sound  -> ovos.audio.play_sound  (cancel + no-match
  error sound in the intent service, install/error sounds in the skill
  installer)
* mycroft.stop              -> ovos.stop              (STOP-1 §5.3)
* skill.stop.pong           -> ovos.stop.pong         (STOP-1 §4.2)
* mycroft.audio.speech.stop -> ovos.audio.stop        (AUDIO-1 §6)

Left alone, because none of them ever rode the bridge: the per-skill
{skill_id}.stop.ping / {skill_id}.stop placeholders, the PIPELINE-1 §8
mycroft.skill.handler.* done-signals, mycroft.skills.abort_question, and the
"complete_intent_failure" metrics label in the opendata payload.

The send_complete_intent_failure docstring promised the bridge would
re-deliver the legacy topic to old consumers. It no longer does, and the
docstring says so.

Unit tests that asserted the legacy spelling now assert the spec one;
test_handle_global_stop additionally asserts mycroft.stop is absent. The
end-to-end suite ran every scenario twice, once per namespace, because the
bus bridged them - the "legacy" path is removed from every NAMESPACE_PATHS
and pinned instead by test/end2end/test_no_legacy_wire_compat.py, which
proves a legacy injection starts no pipeline and a no-match emits no
complete_intent_failure twin. test_intent_alias_backcompat.py keeps its
engine-side blacklist half and inverts its dispatch half: the legacy
suffixed topic now reaches nobody.

BREAKING CHANGE: ovos-core emits and consumes OVOS-MSG-1 spec topics only.
ovos-audio must consume ovos.audio.play_sound and ovos.audio.stop, and
skills must listen on ovos.stop and answer on ovos.stop.pong.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The stop end-to-end suites expected the legacy mycroft.stop broadcast; the
orchestrator now emits ovos.stop (STOP-1 §5.3), so the expectations follow.

ovoscope's TTS mock still emits the legacy recognizer_loop:audio_output_*
and nothing bridges it any more, so both spellings are filtered in the
ignore lists until ovoscope adopts AUDIO-1 §5.1/§5.2.

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

coderabbitai Bot commented Aug 1, 2026

Copy link
Copy Markdown

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 317ad4a1-ba1b-4e03-b363-23db888d1c91

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

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

Hello! The automated checks have been performed. 👋

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

🏷️ Release Preview

The release tag is ready to be minted. 🏷️

Caution

Breaking change — this PR will bump the MAJOR version (2.5.8a33.0.0a1).
Downstream dependents may break. Double-check compatibility before merging.

Current: 2.5.8a3Next: 3.0.0a1

Signal Value
Label (none)
PR title feat!: drop legacy wire compat (kill-switch — merge when fleet upgraded)
Bump major

✅ PR title follows conventional commit format.


🚀 Release Channel Compatibility

Predicted next version: 3.0.0a1

Channel Status Note Current Constraint
Stable Too new (must be <1.4.0) ovos-core>=1.3.1,<1.4.0
Testing Too new (must be <3.0.0) ovos-core>=2.1.1,<3.0.0
Alpha Compatible ovos-core>=2.2.4a1

🔎 Type Check

I've performed a routine sweep of your changes. 🧹

mypy: 280 error(s) found

ovos_core/main.py:24:1: error: Skipping analyzing "ovos_utils.log": module is installed, but missing library stubs or py.typed marker [import-untyped]

Errors (showing first 10/280)
test/unittests/test_skill/__init__.py:15:1: error: Skipping analyzing "ovos_workshop.skills.ovos": module is installed, but missing library stubs or py.typed marker  [import-untyped]
test/end2end/conftest.py:10:1: error: Cannot find implementation or library stub for module named "pytest"  [import-not-found]
ovos_core/transformers.py:3:1: error: Skipping analyzing "ovos_config": module is installed, but missing library stubs or py.typed marker  [import-untyped]
ovos_core/transformers.py:4:1: error: Skipping analyzing "ovos_plugin_manager.intent_transformers": module is installed, but missing library stubs or py.typed marker  [import-untyped]
ovos_core/transformers.py:5:1: error: Skipping analyzing "ovos_plugin_manager.metadata_transformers": module is installed, but missing library stubs or py.typed marker  [import-untyped]
ovos_core/transformers.py:6:1: error: Skipping analyzing "ovos_plugin_manager.text_transformers": module is installed, but missing library stubs or py.typed marker  [import-untyped]
ovos_core/transformers.py:7:1: error: Skipping analyzing "ovos_plugin_manager.transformer_services": module is installed, but missing library stubs or py.typed marker  [import-untyped]
ovos_core/intent_services/manifest.py:15:1: error: Skipping analyzing "ovos_bus_client.message": module is installed, but missing library stubs or py.typed marker  [import-untyped]
ovos_core/intent_services/manifest.py:16:1: error: Skipping analyzing "ovos_spec_tools": module is installed, but missing library stubs or py.typed marker  [import-untyped]
ovos_core/intent_services/manifest.py:17:1: error: Skipping analyzing "ovos_utils.log": module is installed, but missing library stubs or py.typed marker  [import-untyped]

📋 Repo Health

Ensuring the codebase stays lean and mean. 💪

✅ All required files present.

Latest Version: 2.5.8a3

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

📚 Docs

I've distilled the results into this summary. 🧪

✅ All required documentation files present.

README.md

📊 Coverage

Quantifying the quality of our test suite. 🧪

⚠️ 64.3% total coverage

Files below 80% coverage (9 files)
File Coverage Missing lines
ovos_core/__init__.py 0.0% 7
ovos_core/__main__.py 0.0% 26
ovos_core/intent_services/__init__.py 0.0% 1
ovos_core/version.py 0.0% 18
ovos_core/transformers.py 33.3% 18
ovos_core/skill_installer.py 45.5% 133
ovos_core/intent_services/service.py 51.2% 186
ovos_core/skill_manager.py 62.0% 156
ovos_core/intent_services/dispatcher.py 73.4% 29

Full report: download the coverage-report artifact.

🔌 Plugin Detection

The OPM check is now finished. 🏁

Plugin Status: ERRORS (1)

Plugin Info:

  • Name: ovos-core
  • Description: The spiritual successor to Mycroft AI, OVOS is flexible voice assistant software that can be run almost anywhere!

OPM Detection:

Plugin Type Wheel Editable
pipeline

Entry Point Validation:

Entry Point Type Import Interface
ovos-converse-pipeline-plugin pipeline ✅ 927ms
ovos-fallback-pipeline-plugin pipeline ✅ 1ms
ovos-stop-pipeline-plugin pipeline ✅ 36ms

⊘ No settingsmeta.json
requires-python >=3.10 — running Python 3.11

Issues:

  • ❌ Import time for ovos-converse-pipeline-plugin exceeds 500ms (927ms)
  • ⚠️ No settingsmeta.json found
  • ⚠️ No settingsmeta.json found

🔨 Build Tests

The build pipeline has reached its destination. 📍

✅ All versions pass

Python Build Install Tests
3.10
3.11
3.12
3.13
3.14

🔌 Skill Tests (ovoscope)

A detailed report on the skill's intent-matching accuracy. 📝

3/43 passed, 5 failed

❌ **TestAdaptIntent** — 0/4
Test Result
test_padatious_no_match ❌ subtests passed
test_skill_blacklist ❌ subtests passed
test_adapt_match ❌ subtests passed
test_intent_blacklist ❌ subtests passed

test_padatious_no_match failure:

[gw0] linux -- Python 3.11.15 /opt/hostedtoolcache/Python/3.11.15/x64/bin/python

test_skill_blacklist failure:

[gw3] linux -- Python 3.11.15 /opt/hostedtoolcache/Python/3.11.15/x64/bin/python

test_adapt_match failure:

[gw0] linux -- Python 3.11.15 /opt/hostedtoolcache/Python/3.11.15/x64/bin/python
❌ **TestCancelIntentMidSentence** — 0/1
Test Result
test_cancel_match ❌ subtests passed

test_cancel_match failure:

[gw3] linux -- Python 3.11.15 /opt/hostedtoolcache/Python/3.11.15/x64/bin/python
❌ **TestConverse** — 0/1
Test Result
test_parrot_mode ❌ subtests passed

test_parrot_mode failure:

[gw2] linux -- Python 3.11.15 /opt/hostedtoolcache/Python/3.11.15/x64/bin/python
❌ **TestCountSkills** — 0/4
Test Result
test_count_infinity_stop_low ❌ subtests passed
test_count_infinity_active ❌ subtests passed
test_count ❌ subtests passed
test_count_infinity_global ❌ subtests passed

test_count_infinity_stop_low failure:

[gw1] linux -- Python 3.11.15 /opt/hostedtoolcache/Python/3.11.15/x64/bin/python

test_count_infinity_active failure:

[gw3] linux -- Python 3.11.15 /opt/hostedtoolcache/Python/3.11.15/x64/bin/python

test_count failure:

[gw1] linux -- Python 3.11.15 /opt/hostedtoolcache/Python/3.11.15/x64/bin/python
❌ **TestDeactivate** — 0/3
Test Result
test_deactivate_inside_converse ❌ subtests passed
test_deactivate ❌ failed
test_activate ❌ failed

test_deactivate_inside_converse failure:

[gw2] linux -- Python 3.11.15 /opt/hostedtoolcache/Python/3.11.15/x64/bin/python

test_deactivate failure:

Time', 'dstOffset': 3600000, 'offset': -21600000}}, 'system_unit': 'metric', 'time_format': 'half', 'date_format': 'MDY', 'is_speaking': False, 'is_recording': False}, 'source': 'A', 'destination': 'B', 'skill_id': 'test_activation.openvoiceos'})], async_responses=[], eof_msgs=['ovos.utterance.handled'], eof_count=1, ignore_messages=['ovos.skills.settings_changed', 'gui.clear.namespace', 'gui.value.set', 'mycroft.gui.screen.close', 'gui.page.show'], async_messages=[], done=<threading.Event at 0x7f2dcbe99290: set>, _eof_lock=<unlocked _thread.lock object at 0x7f2dcbe9b340>, _eof_seen=0, _armed=False, _generation=1, _done_generation=-1, timed_out=True, timeout_seconds=10).timed_out

/opt/hostedtoolcache/Python/3.11.15/x64/lib/python3.11/site-packages/ovoscope/__init__.py:1163: AssertionError

test_activate failure:

Time', 'dstOffset': 3600000, 'offset': -21600000}}, 'system_unit': 'metric', 'time_format': 'half', 'date_format': 'MDY', 'is_speaking': False, 'is_recording': False}, 'source': 'A', 'destination': 'B', 'skill_id': 'test_activation.openvoiceos'})], async_responses=[], eof_msgs=['ovos.utterance.handled'], eof_count=1, ignore_messages=['ovos.skills.settings_changed', 'gui.clear.namespace', 'gui.value.set', 'mycroft.gui.screen.close', 'gui.page.show'], async_messages=[], done=<threading.Event at 0x7f2dca0c3d10: set>, _eof_lock=<unlocked _thread.lock object at 0x7f2dca0c2080>, _eof_seen=0, _armed=False, _generation=1, _done_generation=-1, timed_out=True, timeout_seconds=10).timed_out

/opt/hostedtoolcache/Python/3.11.15/x64/lib/python3.11/site-packages/ovoscope/__init__.py:1163: AssertionError
❌ **TestFallback** — 0/1
Test Result
test_fallback_match ❌ subtests passed

test_fallback_match failure:

[gw1] linux -- Python 3.11.15 /opt/hostedtoolcache/Python/3.11.15/x64/bin/python
✅ **TestGGWaveSkillInstall** — 2/2
❌ **TestGlobalStopVocWithActiveSkill** — 0/1
Test Result
test_global_stop_voc_with_active_skill ❌ subtests passed

test_global_stop_voc_with_active_skill failure:

[gw1] linux -- Python 3.11.15 /opt/hostedtoolcache/Python/3.11.15/x64/bin/python
❌ **TestGlobalStopVocabulary** — 0/2
Test Result
test_stop_voc_exact_still_works ❌ subtests passed
test_global_stop_voc_no_active_skills ❌ subtests passed

test_stop_voc_exact_still_works failure:

[gw3] linux -- Python 3.11.15 /opt/hostedtoolcache/Python/3.11.15/x64/bin/python

test_global_stop_voc_no_active_skills failure:

[gw3] linux -- Python 3.11.15 /opt/hostedtoolcache/Python/3.11.15/x64/bin/python
❌ **TestIntentPipelineRouting** — 0/4
Test Result
test_blacklisted_skill_falls_through_to_failure ❌ subtests passed
test_high_priority_stage_handles_before_low ❌ subtests passed
test_padatious_intent_matched ❌ subtests passed
test_no_match_produces_intent_failure ❌ subtests passed

test_blacklisted_skill_falls_through_to_failure failure:

[gw3] linux -- Python 3.11.15 /opt/hostedtoolcache/Python/3.11.15/x64/bin/python

test_high_priority_stage_handles_before_low failure:

[gw1] linux -- Python 3.11.15 /opt/hostedtoolcache/Python/3.11.15/x64/bin/python

test_padatious_intent_matched failure:

[gw3] linux -- Python 3.11.15 /opt/hostedtoolcache/Python/3.11.15/x64/bin/python
❌ **TestLangDisambiguation** — 0/4
Test Result
test_stt_lang ❌ subtests passed
test_metadata_preferred_over_text_detection ❌ subtests passed
test_invalid_lang_detection ❌ subtests passed
test_lang_text_detection ❌ subtests passed

test_stt_lang failure:

[gw2] linux -- Python 3.11.15 /opt/hostedtoolcache/Python/3.11.15/x64/bin/python

test_metadata_preferred_over_text_detection failure:

[gw2] linux -- Python 3.11.15 /opt/hostedtoolcache/Python/3.11.15/x64/bin/python

test_invalid_lang_detection failure:

[gw2] linux -- Python 3.11.15 /opt/hostedtoolcache/Python/3.11.15/x64/bin/python
❌ **TestLegacyIntentIdBackCompat** — 0/2
Test Result
test_legacy_blacklist_id_suppresses ❌ subtests passed
test_legacy_dispatch_topic_reaches_nothing ❌ failed

test_legacy_blacklist_id_suppresses failure:

[gw0] linux -- Python 3.11.15 /opt/hostedtoolcache/Python/3.11.15/x64/bin/python

test_legacy_dispatch_topic_reaches_nothing failure:

[gw0] linux -- Python 3.11.15 /opt/hostedtoolcache/Python/3.11.15/x64/bin/python
❌ **TestNoLegacyWireCompat** — 1/3
Test Result
test_legacy_utterance_topic_starts_no_pipeline ❌ failed
test_no_match_emits_no_legacy_twin ❌ failed
test_spec_utterance_topic_still_runs_the_pipeline ✅ passed

test_legacy_utterance_topic_starts_no_pipeline failure:

def test_legacy_utterance_topic_starts_no_pipeline(self):
        """The orchestrator listens on the spec topic only."""
        seen = self._capture(INTENT_UNMATCHED, UTTERANCE_HANDLED)
        self.minicroft.bus.emit(self._session_message(LEGACY_UTTERANCE))
        time.sleep(2)
>       self.assertEqual(seen, [])
E       AssertionError: Lists differ: [<SpecMessage.INTENT_UNMATCHED: 'ovos.inte[69 chars]ed'>] != []
E       
E       First list contains 2 additional elements.
E       First extra element 0:
E       <SpecMessage.INTENT_UNMATCHED: 'ovos.intent.unmatched'>
E       
E       + []
E       - [<SpecMessage.INTENT_UNMATCHED: 'ovos.intent.unmatched'>,
E       -  <SpecMessage.UTTERANCE_HANDLED: 'ovos.utterance.handled'>]

test/end2end/test_no_legacy_wire_compat.py:62: AssertionError

test_no_match_emits_no_legacy_twin failure:

ovos.intent.unmatched alone.
    
        ``complete_intent_failure`` used to ride along on the emit_legacy
        bridge. A consumer still subscribed to it now hears nothing.
        """
        legacy_seen = self._capture(LEGACY_UNMATCHED)
        spec_seen = self._capture(INTENT_UNMATCHED)
        self.minicroft.bus.emit(self._session_message(SPEC_UTTERANCE))
        time.sleep(2)
        self.assertIn(INTENT_UNMATCHED, spec_seen)
>       self.assertEqual(legacy_seen, [])
E       AssertionError: Lists differ: ['complete_intent_failure'] != []
E       
E       First list contains 1 additional elements.
E       First extra element 0:
E       'complete_intent_failure'
E       
E       - ['complete_intent_failure']
E       + []

test/end2end/test_no_legacy_wire_compat.py:83: AssertionError
❌ **TestNoSkills** — 0/2
Test Result
test_routing ❌ subtests passed
test_complete_failure ❌ subtests passed

test_routing failure:

[gw1] linux -- Python 3.11.15 /opt/hostedtoolcache/Python/3.11.15/x64/bin/python

test_complete_failure failure:

[gw3] linux -- Python 3.11.15 /opt/hostedtoolcache/Python/3.11.15/x64/bin/python
❌ **TestPadatiousIntent** — 0/4
Test Result
test_padatious_match ❌ subtests passed
test_skill_blacklist ❌ subtests passed
test_adapt_no_match ❌ subtests passed
test_intent_blacklist ❌ subtests passed

test_padatious_match failure:

[gw2] linux -- Python 3.11.15 /opt/hostedtoolcache/Python/3.11.15/x64/bin/python

test_skill_blacklist failure:

[gw0] linux -- Python 3.11.15 /opt/hostedtoolcache/Python/3.11.15/x64/bin/python

test_adapt_no_match failure:

[gw2] linux -- Python 3.11.15 /opt/hostedtoolcache/Python/3.11.15/x64/bin/python
❌ **TestStopNoSkills** — 0/3
Test Result
test_not_exact_med ❌ subtests passed
test_not_exact_high ❌ subtests passed
test_exact ❌ subtests passed

test_not_exact_med failure:

[gw3] linux -- Python 3.11.15 /opt/hostedtoolcache/Python/3.11.15/x64/bin/python

test_not_exact_high failure:

[gw2] linux -- Python 3.11.15 /opt/hostedtoolcache/Python/3.11.15/x64/bin/python

test_exact failure:

[gw2] linux -- Python 3.11.15 /opt/hostedtoolcache/Python/3.11.15/x64/bin/python
❌ **TestStopServiceNotASkill** — 0/1
Test Result
test_stop_service_is_not_a_skill ❌ subtests passed

test_stop_service_is_not_a_skill failure:

[gw1] linux -- Python 3.11.15 /opt/hostedtoolcache/Python/3.11.15/x64/bin/python
❌ **TestStopSkillCanHandleFalse** — 0/1
Test Result
test_stop_with_active_skill_ping_pong ❌ subtests passed

test_stop_with_active_skill_ping_pong failure:

[gw2] linux -- Python 3.11.15 /opt/hostedtoolcache/Python/3.11.15/x64/bin/python

🚌 Bus Coverage

A comprehensive review of the skill's bus-level interactions. 🚌

🔴 Coverage Summary

Metric Status Coverage
Listeners █░░░░░░░░░ 10.3% 27/262 handlers
Emitters ██████████ 100% 48/48 observed
Assertions ██████████ 100% 48/48 asserted

📊 Per-Skill Breakdown

Skill Listeners Observed Asserted
AdaptPipeline 0/14 (0.0%) 0/0 0/0
ConverseService 2/4 (50.0%) 0/0 0/0
DomainAdaptPipeline 0/14 (0.0%) 0/0 0/0
FallbackService 0/2 (0.0%) 0/0 0/0
HierarchicalAdaptPipeline 0/14 (0.0%) 0/0 0/0
IntentDispatcher 1/2 (50.0%) 0/0 0/0
IntentManifest 0/8 (0.0%) 0/0 0/0
IntentService 2/4 (50.0%) 0/0 0/0
Model2VecIntentPipeline 0/12 (0.0%) 0/0 0/0
Model2VecPrototypePipeline 0/12 (0.0%) 0/0 0/0
PadaciosoPipeline 0/11 (0.0%) 0/0 0/0
PadatiousPipeline 0/15 (0.0%) 0/0 0/0
SkillManager 0/4 (0.0%) 0/0 0/0
StopService 2/2 (100.0%) 0/0 0/0
__core__ 6/27 (22.2%) 6/6 6/6
ovos-skill-count.openvoiceos 2/21 (9.5%) 0/0 0/0
ovos-skill-hello-world.openvoiceos 1/26 (3.8%) 9/9 9/9
ovos-skill-parrot.openvoiceos 4/39 (10.3%) 11/11 11/11
stop.openvoiceos 0/0 (0.0%) 8/8 8/8
test_activation.openvoiceos 5/27 (18.5%) 14/14 14/14
type 2/4 (50.0%) 0/0 0/0
🔍 Detailed Message Type Breakdown

AdaptPipeline

⚠️ Uncovered Listeners:

  • ovos.intent.deregister (Intent)
  • ovos.intent.disable (Intent)
  • ovos.intent.enable (Intent)
  • ovos.intent.register.keyword (Intent)
  • detach_intent
  • detach_skill
  • intent.service.adapt.get
  • intent.service.adapt.manifest.get
  • intent.service.adapt.vocab.manifest.get
  • ovos.entity.deregister
  • ovos.entity.register
  • ovos.skill.deregister
  • register_intent
  • register_vocab

ConverseService

⚠️ Uncovered Listeners:

  • intent.service.active_skills.get
  • intent.service.skills.activate
    ✅ Covered Listeners:
  • converse:skill (3x)
  • intent.service.skills.deactivate (1x)

DomainAdaptPipeline

⚠️ Uncovered Listeners:

  • ovos.intent.deregister (Intent)
  • ovos.intent.disable (Intent)
  • ovos.intent.enable (Intent)
  • ovos.intent.register.keyword (Intent)
  • detach_intent
  • detach_skill
  • intent.service.adapt.get
  • intent.service.adapt.manifest.get
  • intent.service.adapt.vocab.manifest.get
  • ovos.entity.deregister
  • ovos.entity.register
  • ovos.skill.deregister
  • register_intent
  • register_vocab

FallbackService

⚠️ Uncovered Listeners:

  • ovos.skills.fallback.deregister
  • ovos.skills.fallback.register

HierarchicalAdaptPipeline

⚠️ Uncovered Listeners:

  • ovos.intent.deregister (Intent)
  • ovos.intent.disable (Intent)
  • ovos.intent.enable (Intent)
  • ovos.intent.register.keyword (Intent)
  • detach_intent
  • detach_skill
  • intent.service.adapt.get
  • intent.service.adapt.manifest.get
  • intent.service.adapt.vocab.manifest.get
  • ovos.entity.deregister
  • ovos.entity.register
  • ovos.skill.deregister
  • register_intent
  • register_vocab

IntentDispatcher

⚠️ Uncovered Listeners:

  • mycroft.skill.handler.error
    ✅ Covered Listeners:
  • mycroft.skill.handler.complete (8x)

IntentManifest

⚠️ Uncovered Listeners:

  • ovos.intent.deregister (Intent)
  • ovos.intent.describe (Intent)
  • ovos.intent.disable (Intent)
  • ovos.intent.enable (Intent)
  • ovos.intent.list (Intent)
  • ovos.intent.register.keyword (Intent)
  • ovos.intent.register.template (Intent)
  • ovos.skill.deregister

IntentService

⚠️ Uncovered Listeners:

  • intent.service.intent.get (Intent)
  • intent.service.pipelines.reload
    ✅ Covered Listeners:
  • intent.service.skills.deactivate (1x)
  • ovos.utterance.handle (14x)

Model2VecIntentPipeline

⚠️ Uncovered Listeners:

  • ovos.intent.deregister (Intent)
  • ovos.intent.disable (Intent)
  • ovos.intent.enable (Intent)
  • ovos.intent.register.template (Intent)
  • padatious:register_intent (Intent)
  • detach_intent
  • detach_skill
  • mycroft.ready
  • ovos.entity.deregister
  • ovos.entity.register
  • ovos.skill.deregister
  • register_intent

Model2VecPrototypePipeline

⚠️ Uncovered Listeners:

  • ovos.intent.deregister (Intent)
  • ovos.intent.disable (Intent)
  • ovos.intent.enable (Intent)
  • ovos.intent.register.template (Intent)
  • padatious:register_intent (Intent)
  • detach_intent
  • detach_skill
  • mycroft.ready
  • ovos.entity.deregister
  • ovos.entity.register
  • ovos.skill.deregister
  • register_intent

PadaciosoPipeline

⚠️ Uncovered Listeners:

  • ovos.intent.deregister (Intent)
  • ovos.intent.disable (Intent)
  • ovos.intent.enable (Intent)
  • ovos.intent.register.template (Intent)
  • padatious:register_entity (Intent)
  • padatious:register_intent (Intent)
  • detach_intent
  • detach_skill
  • ovos.entity.deregister
  • ovos.entity.register
  • ovos.skill.deregister

PadatiousPipeline

⚠️ Uncovered Listeners:

  • ovos.intent.deregister (Intent)
  • ovos.intent.disable (Intent)
  • ovos.intent.enable (Intent)
  • ovos.intent.register.template (Intent)
  • padatious:register_entity (Intent)
  • padatious:register_intent (Intent)
  • detach_intent
  • detach_skill
  • intent.service.padatious.entities.manifest.get
  • intent.service.padatious.get
  • intent.service.padatious.manifest.get
  • mycroft.skills.train
  • ovos.entity.deregister
  • ovos.entity.register
  • ovos.skill.deregister

SkillManager

⚠️ Uncovered Listeners:

  • skillmanager.activate
  • skillmanager.deactivate
  • skillmanager.keep
  • skillmanager.list

StopService

✅ Covered Listeners:

  • stop:global (1x)
  • stop:skill (1x)

__core__

⚠️ Uncovered Listeners:

  • add_context
  • clear_context
  • complete_intent_failure
  • message
  • mycroft.ovos-skill-hello-world.openvoiceos.all_loaded
  • mycroft.ovos-skill-hello-world.openvoiceos.is_alive
  • mycroft.ovos-skill-hello-world.openvoiceos.is_ready
  • mycroft.ovos-skill-parrot.openvoiceos.all_loaded
  • mycroft.ovos-skill-parrot.openvoiceos.is_alive
  • mycroft.ovos-skill-parrot.openvoiceos.is_ready
  • mycroft.test_activation.openvoiceos.all_loaded
  • mycroft.test_activation.openvoiceos.is_alive
  • mycroft.test_activation.openvoiceos.is_ready
  • ovos-skill-hello-world.openvoiceos.set
  • ovos-skill-parrot.openvoiceos.set
  • ovos.session.sync
  • ovos.session.update_default
  • remove_context
  • skill.converse.get_response.disable
  • skill.converse.get_response.enable
  • test_activation.openvoiceos.set
    ✅ Covered Listeners:
  • ovos.intent.unmatched (2x)
  • ovos.skills.converse.force_timeout (1x)
  • ovos.utterance.handled (14x)
  • ovos.utterance.speak (5x)
  • skill.converse.pong (3x)
  • skill.converse.response (2x)

📤 Emitters:

  • ovos-skill-parrot.openvoiceos.converse.ping (Asserted ✅)
  • ovos.audio.play_sound (Asserted ✅)
  • ovos.intent.unmatched (Asserted ✅)
  • ovos.utterance.handle (Asserted ✅)
  • ovos.utterance.handled (Asserted ✅)
  • test_activation.openvoiceos.converse.ping (Asserted ✅)

ovos-skill-count.openvoiceos

⚠️ Uncovered Listeners:

  • ovos-skill-count.openvoiceos:count_to_n (Intent)
  • ovos-skill-count.openvoiceos:count_to_n.intent (Intent)
  • question:action (Intent)
  • question:action.ovos-skill-count.openvoiceos (Intent)
  • question:query (Intent)
  • homescreen.metadata.get
  • mycroft.ovos-skill-count.openvoiceos.all_loaded
  • mycroft.ovos-skill-count.openvoiceos.is_alive
  • mycroft.ovos-skill-count.openvoiceos.is_ready
  • mycroft.skill.disable_intent
  • mycroft.skill.enable_intent
  • mycroft.skill.remove_cross_context
  • mycroft.skill.set_cross_context
  • mycroft.skills.settings.changed
  • mycroft.stop
  • ovos-skill-count.openvoiceos.converse.get_response
  • ovos-skill-count.openvoiceos.set
  • ovos.common_query.ping
  • ovos.skills.settings_changed
    ✅ Covered Listeners:
  • ovos-skill-count.openvoiceos.stop (1x)
  • ovos-skill-count.openvoiceos.stop.ping (1x)

ovos-skill-hello-world.openvoiceos

⚠️ Uncovered Listeners:

  • ovos-skill-hello-world.openvoiceos:Greetings.intent (Intent)
  • ovos-skill-hello-world.openvoiceos:HelloWorldIntent (Intent)
  • ovos-skill-hello-world.openvoiceos:HowAreYou (Intent)
  • ovos-skill-hello-world.openvoiceos:HowAreYou.intent (Intent)
  • ovos-skill-hello-world.openvoiceos:ThankYouIntent (Intent)
  • question:action (Intent)
  • question:action.ovos-skill-hello-world.openvoiceos (Intent)
  • question:query (Intent)
  • hello.world
  • homescreen.metadata.get
  • mycroft.ovos-skill-hello-world.openvoiceos.all_loaded
  • mycroft.ovos-skill-hello-world.openvoiceos.is_alive
  • mycroft.ovos-skill-hello-world.openvoiceos.is_ready
  • mycroft.skill.disable_intent
  • mycroft.skill.enable_intent
  • mycroft.skill.remove_cross_context
  • mycroft.skill.set_cross_context
  • mycroft.skills.settings.changed
  • mycroft.stop
  • ovos-skill-hello-world.openvoiceos.converse.get_response
  • ovos-skill-hello-world.openvoiceos.set
  • ovos-skill-hello-world.openvoiceos.stop
  • ovos-skill-hello-world.openvoiceos.stop.ping
  • ovos.common_query.ping
  • ovos.skills.settings_changed
    ✅ Covered Listeners:
  • ovos-skill-hello-world.openvoiceos:Greetings (1x)

📤 Emitters:

  • mycroft.skill.handler.complete (Asserted ✅)
  • mycroft.skill.handler.start (Asserted ✅)
  • ovos-skill-hello-world.openvoiceos.activate (Asserted ✅)
  • ovos-skill-hello-world.openvoiceos:Greetings (Asserted ✅)
  • ovos.intent.handler.complete (Asserted ✅)
  • ovos.intent.handler.start (Asserted ✅)
  • ovos.intent.matched (Asserted ✅)
  • ovos.utterance.handled (Asserted ✅)
  • ovos.utterance.speak (Asserted ✅)

ovos-skill-parrot.openvoiceos

⚠️ Uncovered Listeners:

  • ovos-skill-parrot.openvoiceos:did_you_hear_me (Intent)
  • ovos-skill-parrot.openvoiceos:did_you_hear_me.intent (Intent)
  • ovos-skill-parrot.openvoiceos:repeat_stt (Intent)
  • ovos-skill-parrot.openvoiceos:repeat_stt.intent (Intent)
  • ovos-skill-parrot.openvoiceos:repeat_tts (Intent)
  • ovos-skill-parrot.openvoiceos:repeat_tts.intent (Intent)
  • ovos-skill-parrot.openvoiceos:speak (Intent)
  • ovos-skill-parrot.openvoiceos:speak.intent (Intent)
  • ovos-skill-parrot.openvoiceos:start_parrot.intent (Intent)
  • ovos-skill-parrot.openvoiceos:stop_parrot (Intent)
  • ovos-skill-parrot.openvoiceos:stop_parrot.intent (Intent)
  • question:action (Intent)
  • question:action.ovos-skill-parrot.openvoiceos (Intent)
  • question:query (Intent)
  • recognizer_loop:utterance (Intent)
  • homescreen.metadata.get
  • intent.service.skills.activated
  • intent.service.skills.deactivated
  • mycroft.ovos-skill-parrot.openvoiceos.all_loaded
  • mycroft.ovos-skill-parrot.openvoiceos.is_alive
  • mycroft.ovos-skill-parrot.openvoiceos.is_ready
  • mycroft.skill.disable_intent
  • mycroft.skill.enable_intent
  • mycroft.skill.remove_cross_context
  • mycroft.skill.set_cross_context
  • mycroft.skills.settings.changed
  • mycroft.stop
  • ovos-skill-parrot.openvoiceos.converse.get_response
  • ovos-skill-parrot.openvoiceos.deactivate
  • ovos-skill-parrot.openvoiceos.set
  • ovos-skill-parrot.openvoiceos.stop
  • ovos-skill-parrot.openvoiceos.stop.ping
  • ovos.common_query.ping
  • ovos.skills.settings_changed
  • speak
    ✅ Covered Listeners:
  • ovos-skill-parrot.openvoiceos.activate (3x)
  • ovos-skill-parrot.openvoiceos.converse.ping (3x)
  • ovos-skill-parrot.openvoiceos.converse.request (2x)
  • ovos-skill-parrot.openvoiceos:start_parrot (1x)

📤 Emitters:

  • converse:skill (Asserted ✅)
  • mycroft.skill.handler.complete (Asserted ✅)
  • mycroft.skill.handler.start (Asserted ✅)
  • ovos-skill-parrot.openvoiceos.activate (Asserted ✅)
  • ovos-skill-parrot.openvoiceos.converse.request (Asserted ✅)
  • ovos-skill-parrot.openvoiceos:start_parrot (Asserted ✅)
  • ovos.intent.matched (Asserted ✅)
  • ovos.utterance.handled (Asserted ✅)
  • ovos.utterance.speak (Asserted ✅)
  • skill.converse.pong (Asserted ✅)
  • skill.converse.response (Asserted ✅)

stop.openvoiceos

📤 Emitters:

  • mycroft.skill.handler.complete (Asserted ✅)
  • mycroft.skill.handler.start (Asserted ✅)
  • ovos-skill-count.openvoiceos.stop (Asserted ✅)
  • ovos.stop (Asserted ✅)
  • ovos.utterance.handled (Asserted ✅)
  • stop.openvoiceos.activate (Asserted ✅)
  • stop:global (Asserted ✅)
  • stop:skill (Asserted ✅)

test_activation.openvoiceos

⚠️ Uncovered Listeners:

  • question:action (Intent)
  • question:action.test_activation.openvoiceos (Intent)
  • question:query (Intent)
  • homescreen.metadata.get
  • intent.service.skills.activated
  • mycroft.skill.disable_intent
  • mycroft.skill.enable_intent
  • mycroft.skill.remove_cross_context
  • mycroft.skill.set_cross_context
  • mycroft.skills.settings.changed
  • mycroft.stop
  • mycroft.test_activation.openvoiceos.all_loaded
  • mycroft.test_activation.openvoiceos.is_alive
  • mycroft.test_activation.openvoiceos.is_ready
  • ovos.common_query.ping
  • ovos.skills.settings_changed
  • test_activate
  • test_activation.openvoiceos.converse.get_response
  • test_activation.openvoiceos.set
  • test_activation.openvoiceos.stop
  • test_activation.openvoiceos.stop.ping
  • test_deactivate
    ✅ Covered Listeners:
  • intent.service.skills.deactivated (1x)
  • test_activation.openvoiceos.activate (1x)
  • test_activation.openvoiceos.converse.ping (1x)
  • test_activation.openvoiceos.converse.request (1x)
  • test_activation.openvoiceos.deactivate (1x)

📤 Emitters:

  • converse:skill (Asserted ✅)
  • intent.service.skills.deactivate (Asserted ✅)
  • intent.service.skills.deactivated (Asserted ✅)
  • mycroft.skill.handler.complete (Asserted ✅)
  • mycroft.skill.handler.start (Asserted ✅)
  • ovos.intent.handler.complete (Asserted ✅)
  • ovos.intent.handler.start (Asserted ✅)
  • ovos.intent.matched (Asserted ✅)
  • ovos.utterance.handled (Asserted ✅)
  • skill.converse.pong (Asserted ✅)
  • skill.converse.response (Asserted ✅)
  • test_activation.openvoiceos.activate (Asserted ✅)
  • test_activation.openvoiceos.converse.request (Asserted ✅)
  • test_activation.openvoiceos.deactivate (Asserted ✅)

type

⚠️ Uncovered Listeners:

  • recognizer_loop:record_begin (Intent)
  • recognizer_loop:record_end (Intent)
    ✅ Covered Listeners:
  • recognizer_loop:audio_output_end (4x)
  • recognizer_loop:audio_output_start (5x)

⚖️ License Check

Ensuring our CLA requirements are met. 🖋️

✅ No license violations found.

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


Stay curious and keep coding! 🚀

@github-actions github-actions Bot added breaking breaks backwards compatibility (kinda) feature and removed feature breaking breaks backwards compatibility (kinda) labels Aug 1, 2026
The dropped legacy-wire-compat guard tests (test_no_legacy_wire_compat.py,
test_activate.py, test_intent_alias_backcompat.py) fail against the
released ovos-bus-client/ovos-utils/ovos-workshop because those still
bridge the legacy bus namespace. The bridge removal is split across
sibling PRs (ovos-bus-client#272, ovos-utils#412, ovos-workshop#501)
that haven't merged/released yet.

Use the ovoscope reusable workflow's post_install_pip hook to re-pin
those three packages from their companion dev branches after the normal
test-extras install, so CI exercises the full post-kill-switch stack
this PR assumes. Remove once the companions merge and release.

AI-Generated Change: Model: claude-sonnet-4-6
JarbasAl added a commit to goldyfruit/ovos-core that referenced this pull request Aug 12, 2026
… for the migration window

The skill-addressed fallback ping/pong machinery landed with no
compatibility window: any released ovos-workshop (pre-OpenVoiceOS#465, broadcast-only)
paired with this branch never gets its fallback ping answered, since the
broadcast ping/pong collector was dropped outright. FALLBACK-1 §6.1 makes
the addressed topics normative but explicitly sanctions the broadcast poll
as an observably-equivalent optimisation, so restore it for one
deprecation window (kill-switch OpenVoiceOS#837 conventions):

- emit the general `ovos.skills.fallback.ping` broadcast once per poll
  round alongside the addressed pings, and keep a `ovos.skills.fallback.pong`
  collector with the same session filter as the addressed collectors.
- dedup pongs by skill_id: a skill running fixed ovos-workshop (OpenVoiceOS#465)
  answers BOTH ping families during the window, and must only count once
  (first answer wins).
- delete the end2end `_wire_skill_addressed_probe` fake and let the real
  (released) ovos-workshop installed by the test run answer the ping
  honestly; expected_messages updated to reflect the addressed ping being
  emitted-but-unanswered against a pre-OpenVoiceOS#465 workshop.
- fix the CodeRabbit-flagged flaky session-lock unit test's docstring
  (it exercises the session-id filter, not the lock) and add a real
  same-session lock serialization test.
- revert the unrelated pipeline-blacklist normalization change to
  intent_services/service.py (and its test) that had leaked into this
  branch; it is being split into its own PR.

Executed matrix (probe-free harness, real ovoscope + real workshop):
fixed-core+fixed-ws, fixed-core+ws-dev, core-dev+fixed-ws, and
fixed-core+PyPI ovos-workshop==9.3.9a1 are all GREEN (ping answered,
dispatched exactly once, skill spoke exactly once).

Narrows OpenVoiceOS#807 (same-session stale-pong residue remains; needs a round
nonce -- pre-existing on dev).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@JarbasAl JarbasAl closed this Sep 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

breaking breaks backwards compatibility (kinda) feature

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant