Skip to content

test: intent-topic back-compat guards — in-process, plus a mixed-version two-venv matrix - #836

Open
JarbasAl wants to merge 6 commits into
devfrom
test/intent-legacy-reemit-guard
Open

test: intent-topic back-compat guards — in-process, plus a mixed-version two-venv matrix#836
JarbasAl wants to merge 6 commits into
devfrom
test/intent-legacy-reemit-guard

Conversation

@JarbasAl

@JarbasAl JarbasAl commented Aug 1, 2026

Copy link
Copy Markdown
Member

Summary

Two layers of guard for the intent-topic migration, in one branch:

  1. in-processtest/end2end/test_intent_legacy_reemit_guard.py, relocated from ovoscope#127 per maintainer decision (ovoscope is the harness library, not the stack, so a stack contract does not belong pinned there);
  2. cross-versiontest/backcompat/, a four-cell matrix that runs two venvs against one real ovos-messagebus.

The second layer is new in this update and is the one that reproduces the actual field scenario.


Layer 1 — in-process guard (unchanged)

Paired positive/negative guard for the send-side half of the migration; the registration half is already covered by test/end2end/test_intent_alias_backcompat.py:

  • with emit_legacy enabled, a canonical intent dispatch must re-emit the .intent-suffixed twin exactly once, with identical data/context, to a bus-only legacy listener that never registered through this core;
  • with compat disabled, only the canonical dispatch is observed (paired negative control);
  • FakeBus must expose the same re-emit hook so this can run without a live MQ.

None of that wiring exists yet, so those assertions are xfail(strict=True) pending the compat train.


Layer 2 — mixed-version back-compat matrix

Why the in-process guards are not enough

Every guard above shares one interpreter, so it can only ever observe one set of package versions. The breakage is definitionally cross-version: an outdated standalone skill process — frozen ovos-workshop, as in a container built months ago — connects to a real ovos-messagebus and the current stack dispatches at it. Two package sets have to be alive at once, which one process cannot do.

Hence two venvs and a real websocket on localhost, no docker. A docker flavour can follow later as a manual job; venv isolation is the same experiment with a far cheaper CI story.

The matrix

Four combinations, one CI matrix entry each, all sharing one parameterized script that takes the two venv paths as inputs:

Combo Skill binds Core dispatches Handler runs
old skill / old core suffixed only suffixed yes
old skill / new core suffixed only canonical noxfail(strict=True)
new skill / old core both suffixed yes
new skill / new core both canonical yes, exactly once

Only one cell is broken. The other three are passing controls: they prove the harness can see a handler fire at all, so the red cell is a real finding and not a broken fixture. new skill / new core doubles as a double-fire guard — a skill bound to both spellings must not answer twice once the mirror is live.

What actually moved

OVOS-MSG-1 §2.1.1 builds the dispatch topic as <skill_id>:<intent_name>. Old workshop built it from the padatious resource filename, leaking .intent onto the wire. Then two independent changes landed:

  • ovos-padatious >= 2.0.1a1 folds the suffixed id onto the canonical form at registration time, so every match — and every dispatch — is canonical;
  • ovos-workshop >= 9.3.2a1 binds the handler to both spellings (improve common_qa/OCP conflicts #497), which survives either dispatch.

Worth stating plainly: ovos-core is not the deciding component. IntentService._dispatch_match forwards match.match_type verbatim, so the spelling on the wire is decided by the pipeline plugin's registry. That is why the "old core" pin below is really a pin on padatious.

The pins, and the evidence

Both pins were chosen by behaviour, verified against source and tags, and are asserted at runtime by test_pins_are_the_intended_vintage. A release that quietly changes either fails as a wrong-vintage error rather than turning the red cell green.

ovos-workshop==9.3.1a2 — newest release binding the suffixed topic only. The canonical binding arrived in 27c8728 (2026-07-26, #497), first released in 9.3.2a1; 9.3.1a2 is the last tag without it. Verified by running the real skill against a real bus in each venv:

9.3.1a2 → BOUND_TOPICS ["…:food.order.intent"]
dev     → BOUND_TOPICS ["…:food.order", "…:food.order.intent"]

A newer pin would hide the breakage outright.

ovos-padatious==2.0.0a1 + ovos-core==2.5.5a2_dealias_intent_name landed in padatious 2.0.1a1; 2.0.0a1 is the last tag without it, with core pinned to its contemporary release. The suite probes for the real symbol rather than a version string, so it keeps telling the truth if the fold ever moves.

Where the fix has to land

The frozen container does not ship a frozen ovos-bus-client. A workshop pin is a dependency floor, not a ceiling, so a rebuilt container resolves today's client — uv resolving ovos-workshop==9.3.1a2 picks up ovos-bus-client==2.7.2a1. The suite asserts this rather than assuming it, because the whole repair strategy depends on it.

That is also why bus-client#271 puts its mirror on the receive side and keeps it off the wire: the fix must execute in the process that owns the stale binding, and only that process knows what it bound. The old skill's own bus.on("…​.intent") fills an IntentAliasRegistry; when the canonical dispatch arrives, its client mirrors it locally onto the suffixed twin, and the handler runs with neither the skill nor the wire changing.

Kill-switch gate

These cells gate the compat train in both directions:

  • the day default VAD to "ovos-vad-plugin-webrtcvad" + setup/requirements cleanup #271 releases, old skill/new core starts passing, strict=True turns that into a loud XPASS failure, and that failure is the signal to drop the marker and keep the guard permanently;
  • ovos-workshop#500 removes the suffixed binding, which turns new skill/old core red. That cell must not be deleted to make fr-fr/translate #500 green — it is the signal that the inbound direction (suffixed wire topic → canonical local listener) is still missing from the client;
  • an explicit inverse control runs the broken cell with emit_legacy off and asserts the handler stays silent. It passes today for the trivial reason that no mirror exists; once default VAD to "ovos-vad-plugin-webrtcvad" + setup/requirements cleanup #271 ships it becomes the real negative half of the pair. Any PR dropping the compat must flip these deliberately.

CI

.github/workflows/backcompat_matrix.yml, separate from build_tests.yml because that workflow installs a single environment and runs pytest in it. fail-fast is off so one broken combination cannot hide the other three, and each job uploads the versions its venvs actually resolved.

Without BACKCOMPAT_COMBO the suite skips cleanly, so existing runs are unaffected.


Channel cells — pinned by the live fleet, not just a boundary

Maintainer-requested extension: the boundary-pin cells above answer "where is
the line", but not "which side of the line is the fleet actually on". Four
more cells answer that, using the OVOS distro's own constraint files —
fetched fresh in build_venvs.sh at build time, never vendored, so the gate
tracks the fleet:

Combo Skill binds Core dispatches Handler runs
stable-skill/dev-core suffixed only (ovos-workshop>=3.4.0,<3.5.0) canonical noxfail(strict=True)
dev-skill/stable-core both suffixed (ovos-padatious>=1.4.2,<1.5.0) yes
testing-skill/dev-core suffixed only (ovos-workshop>=7.0.6,<8.0.0) canonical noxfail(strict=True)
dev-skill/testing-core both suffixed (ovos-padatious>=1.4.3,<2.0.0) yes

As pinned today (2026-08-01), both channels float their ovos-workshop
and ovos-padatious floors well below the 9.3.2a1 / 2.0.1a1 boundaries the
boundary-pin cells mark, so stable-skill/dev-core and
testing-skill/dev-core hit the identical known gap as old-skill/new-core
and carry the same xfail(strict=True) for the same reason. The other two
are passing controls, like new-skill/old-core.

This is the point of pinning against a live URL instead of vendoring a
copy
: the day either channel's constraints file bumps ovos-workshop past
9.3.2a1 (or ovos-padatious past 2.0.1a1) without the receiving side also
moving, the corresponding cell goes red as a loud XPASS at that exact
moment
— the fleet moved, and this is the trace. Each job uploads the
fetched constraints file as an artifact alongside the resolved venv freeze,
so a red channel cell is traceable to exactly what was pinned that day.

One real finding surfaced building these: on the channel-constrained side,
ovos-bus-client is capped by the same constraints file (testing pins it
<2.0.0, so the skill venv resolves 1.5.0), unlike the boundary cells where
only ovos-workshop is pinned and its dependency floor is left to resolve
whatever ovos-bus-client is current. test_old_container_resolves_a_current_bus_client
now treats that as a fleet-inventory fact for channel combos rather than
asserting it — a real stable/testing-channel container will need a
ovos-bus-client bump on top of a workshop bump to receive the #271 fix,
not just the workshop bump the boundary cells describe.

Both layers — boundary pins and channel pins — stay in the matrix
deliberately: boundary pins document where the behavior line is and fail as
wrong-vintage if a release moves it; channel pins verify what the fleet is
actually running today against that line.

Compat train

Two copies, on purpose

ovos-test-harness#16 carries the same matrix. Every relevant repo carries its own back-compat suite so a breakage is traceable to the repo that caused it, and the harness duplicates in parallel as the cross-repo home.

Verified locally

All four combinations, real venvs, real ovos-messagebus, no mocks:

old-skill/old-core         5 passed, 1 skipped
old-skill/new-core         4 passed, 2 xfailed     ← the broken cell
new-skill/old-core         5 passed, 1 skipped
new-skill/new-core         5 passed, 1 skipped
stable-skill/dev-core      4 passed, 2 xfailed     ← same gap, live stable pin
dev-skill/stable-core      5 passed, 1 skipped
testing-skill/dev-core     4 passed, 2 xfailed     ← same gap, live testing pin
dev-skill/testing-core     5 passed, 1 skipped

Maintainer-approved. Layer 1 implemented by Claude (sonnet); layer 2 by Claude (opus). Orchestrated by Claude Fable.

🤖 Generated with Claude Code


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.

Relocated from ovoscope#127 at the maintainer's request: ovoscope is the
harness library, not the stack, and a stack contract must be pinned where
the stack lives.

Pins the send-side half of the intent-topic migration that
test_intent_alias_backcompat.py does not cover: when emit_legacy compat is
enabled, a canonical intent dispatch must re-emit the .intent-suffixed
twin exactly once, with identical data/context, to a bus-only legacy
listener that never registered through this core (paired positive/negative
control); with compat disabled, only the canonical dispatch is observed.

The re-emit and FakeBus wiring do not exist yet, so those assertions are
marked xfail(strict=True) pending the compat train: ovos-spec-tools#88
(IntentAliasRegistry / legacy_reemit_targets, vendored unreleased),
bus-client#271, and ovos-utils#411. strict=True turns the day the train
lands into a loud XPASS failure, the signal to drop the marker and promote
the guard permanently.

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

coderabbitai Bot commented Aug 1, 2026

Copy link
Copy Markdown

Warning

Review limit reached

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

Next review available in: 37 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: 8d2d1407-d6ea-4849-909c-80e0f99f3d69

📥 Commits

Reviewing files that changed from the base of the PR and between 9799e22 and 4d5e2e3.

📒 Files selected for processing (7)
  • .github/workflows/backcompat_matrix.yml
  • test/backcompat/__init__.py
  • test/backcompat/build_venvs.sh
  • test/backcompat/driver.py
  • test/backcompat/skill_process.py
  • test/backcompat/test_mixed_version_matrix.py
  • test/end2end/test_intent_legacy_reemit_guard.py

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

The results of your automated verification are here! 📜

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

📋 Repo Health

Scanning for any signs of code rot or decay. 🍄

✅ All required files present.

Latest Version: 2.5.9a1

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 checked the pulse of your pull request. 💓

✅ All required documentation files present.

README.md

🔎 Type Check

Checking the status... yep, it's done! ✅

mypy: 292 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/292)
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]

🏷️ Release Preview

I've checked the 'Documentation Updates' link. 📖

Current: 2.5.9a1Next: 2.5.9a2

Signal Value
Label (none)
PR title test: intent-topic back-compat guards — in-process, plus a mixed-version two-venv matrix
Bump alpha

✅ PR title follows conventional commit format.


🚀 Release Channel Compatibility

Predicted next version: 2.5.9a2

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

🌍 Locale Build

Just a quick heads-up on the latest check. 🛎️

✅ Locale properly configured (64 files, 17 languages)

Locale directories found:

  • ovos_core/intent_services/locale

Localization coverage:

  • ovos_core/intent_services/locale: 64 files in 17 languages (pt-pt, en-us, it-it, de-de, ca-es...)

pyproject.toml:[tool.setuptools.package-data.ovos_core] includes locale

  • intent_services/locale/*/*.voc

Build manifest: ✅ 31 locale files included in package

📊 Coverage

I've been crunching the numbers! Here's how the test coverage changed. 📈

⚠️ 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.7% 132
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.

🔒 Security (pip-audit)

I've checked the vulnerability database for hits. 🎯

✅ No known vulnerabilities found (113 packages scanned).

⚖️ License Check

I've verified the license compliance for your changes. ✅

✅ No license violations found.

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

🔨 Build Tests

The build is complete. No hard hats required. 👷‍♂️

✅ All versions pass

Python Build Install Tests
3.10
3.11
3.12
3.13
3.14

🔌 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 ✅ 1730ms
ovos-fallback-pipeline-plugin pipeline ✅ 1ms
ovos-stop-pipeline-plugin pipeline ✅ 38ms

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

Issues:

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

🔌 Skill Tests (ovoscope)

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

6/44 passed

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

test_padatious_no_match failure:

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

test_adapt_match failure:

[gw2] 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
❌ **TestCancelIntentMidSentence** — 0/1
Test Result
test_cancel_match ❌ subtests passed

test_cancel_match failure:

[gw0] 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:

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

test_count_infinity_active failure:

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

test_count_infinity_global failure:

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

test_count failure:

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

test_deactivate_inside_converse failure:

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

test_fallback_match failure:

[gw2] 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:

[gw2] linux -- Python 3.11.15 /opt/hostedtoolcache/Python/3.11.15/x64/bin/python
❌ **TestIntentLegacyReemitGuard** — 2/4
Test Result
test_fakebus_supports_emit_legacy_wiring ❌ xfailed
test_direct_legacy_dispatch_still_fires_the_handler ✅ passed
test_no_twin_when_compat_disabled ✅ passed
test_legacy_twin_reemitted_when_compat_enabled ❌ xfailed

test_fakebus_supports_emit_legacy_wiring failure:

estMethod=test_fakebus_supports_emit_legacy_wiring>

    @pytest.mark.xfail(strict=True, reason=_XFAIL_REASON)
    def test_fakebus_supports_emit_legacy_wiring(self):
        """``FakeBus`` (ovos-utils#411) must expose the same re-emit hook the
        real bus client gets from bus-client#271, so end-to-end intent-compat
        tests can run against FakeBus without a live MQ."""
        bus = FakeBus()
        twin_hits = []
        bus.on(LEGACY_TOPIC, twin_hits.append)
        try:
>           bus.emit(Message(CANONICAL_TOPIC, {"x": 1}),
                    intent_topic_compat={"emit_legacy": True})
E                   TypeError: _patch_fakebus.<locals>.patched_emit() got an unexpected keyword argument 'intent_topic_compat'

test/end2end/test_intent_legacy_reemit_guard.py:180: TypeError

test_legacy_twin_reemitted_when_compat_enabled failure:

JSON-decoded dict per §2.
    
        Tolerant of the §2.1 deferral surface: an explicit ``null`` on
        any registered field is logged and treated as omitted, not as a
        rejection. Unknown fields are preserved verbatim in
        :attr:`extras` (§2.4). Passing ``None`` (no ``session`` key in
        ``context``) yields the same well-formed empty session as ``{}``
        (§2.1)."""
        if payload is None:
            return cls()
        if not isinstance(payload, dict):
>           raise MalformedSession(
                "session must be a JSON object (§2 / §5)")
E           ovos_spec_tools.session.MalformedSession: session must be a JSON object (§2 / §5)

/opt/hostedtoolcache/Python/3.11.15/x64/lib/python3.11/site-packages/ovos_spec_tools/session.py:598: MalformedSession
❌ **TestIntentPipelineRouting** — 0/4
Test Result
test_padatious_intent_matched ❌ subtests passed
test_high_priority_stage_handles_before_low ❌ subtests passed
test_blacklisted_skill_falls_through_to_failure ❌ subtests passed
test_no_match_produces_intent_failure ❌ subtests passed

test_padatious_intent_matched failure:

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

test_high_priority_stage_handles_before_low failure:

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

test_blacklisted_skill_falls_through_to_failure failure:

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

test_invalid_lang_detection failure:

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

test_stt_lang failure:

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

test_metadata_preferred_over_text_detection failure:

[gw3] 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_fires_handler ❌ subtests passed

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_fires_handler failure:

[gw1] linux -- Python 3.11.15 /opt/hostedtoolcache/Python/3.11.15/x64/bin/python
❌ **TestNoSkills** — 0/2
Test Result
test_complete_failure ❌ subtests passed
test_routing ❌ subtests passed

test_complete_failure failure:

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

test_routing failure:

[gw0] 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_intent_blacklist ❌ subtests passed
test_adapt_no_match ❌ subtests passed
test_skill_blacklist ❌ subtests passed

test_padatious_match failure:

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

test_intent_blacklist failure:

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

test_adapt_no_match failure:

[gw1] 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_exact ❌ subtests passed
test_not_exact_high ❌ subtests passed

test_not_exact_med failure:

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

test_exact failure:

[gw1] 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
❌ **TestStopServiceNotASkill** — 0/1
Test Result
test_stop_service_is_not_a_skill ❌ subtests passed

test_stop_service_is_not_a_skill failure:

[gw3] 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:

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

🚌 Bus Coverage

Measuring the reach of our test cases across the message bus. 📏

🔴 Coverage Summary

Metric Status Coverage
Listeners ░░░░░░░░░░ 3.7% 7/187 handlers
Emitters ██████████ 100% 20/20 observed
Assertions ██████████ 100% 20/20 asserted

📊 Per-Skill Breakdown

Skill Listeners Observed Asserted
AdaptPipeline 0/14 (0.0%) 0/0 0/0
ConverseService 0/4 (0.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 1/4 (25.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 1/2 (50.0%) 0/0 0/0
__core__ 2/18 (11.1%) 5/5 5/5
ovos-skill-count.openvoiceos 0/21 (0.0%) 0/0 0/0
ovos-skill-hello-world.openvoiceos 1/26 (3.8%) 9/9 9/9
stop.openvoiceos 0/0 (0.0%) 6/6 6/6
type 1/4 (25.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:

  • converse:skill (Intent)
  • intent.service.active_skills.get
  • intent.service.skills.activate
  • intent.service.skills.deactivate

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 (6x)

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
  • intent.service.skills.deactivate
    ✅ Covered Listeners:
  • ovos.utterance.handle (9x)

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

⚠️ Uncovered Listeners:

  • stop:skill (Intent)
    ✅ Covered Listeners:
  • stop:global (4x)

__core__

⚠️ Uncovered Listeners:

  • add_context
  • clear_context
  • message
  • mycroft.ovos-skill-count.openvoiceos.all_loaded
  • mycroft.ovos-skill-count.openvoiceos.is_alive
  • mycroft.ovos-skill-count.openvoiceos.is_ready
  • mycroft.ovos-skill-hello-world.openvoiceos.all_loaded
  • mycroft.ovos-skill-hello-world.openvoiceos.is_alive
  • mycroft.ovos-skill-hello-world.openvoiceos.is_ready
  • ovos-skill-count.openvoiceos.set
  • ovos-skill-hello-world.openvoiceos.set
  • ovos.session.sync
  • ovos.session.update_default
  • remove_context
  • skill.converse.get_response.disable
  • skill.converse.get_response.enable
    ✅ Covered Listeners:
  • ovos.utterance.handled (17x)
  • ovos.utterance.speak (2x)

📤 Emitters:

  • mycroft.audio.play_sound (Asserted ✅)
  • ovos.intent.unmatched (Asserted ✅)
  • ovos.utterance.handle (Asserted ✅)
  • ovos.utterance.handled (Asserted ✅)
  • recognizer_loop:utterance (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-skill-count.openvoiceos.stop
  • ovos-skill-count.openvoiceos.stop.ping
  • ovos.common_query.ping
  • ovos.skills.settings_changed

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 (2x)

📤 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 ✅)

stop.openvoiceos

📤 Emitters:

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

type

⚠️ Uncovered Listeners:

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


Automatically generated, personally appreciated 💖

…bus)

The in-process guards in this repo share one interpreter, so they can only
observe one set of package versions. The breakage they pin is cross-version by
definition: a skill container frozen months ago talking to a stack that moved
on. Seeing that needs two package sets alive at once, which means two venvs and
a real websocket between them.

Adds `test/backcompat/`: a real `ovos-messagebus` on a private port, a
standalone skill launched from a second venv, and one parameterized script that
takes the two venvs as inputs. `.github/workflows/backcompat_matrix.yml` runs
the four skill/core combinations as four matrix entries.

| combo              | skill binds   | core dispatches | handler runs |
|--------------------|---------------|-----------------|--------------|
| old skill/old core | suffixed only | suffixed        | yes          |
| old skill/new core | suffixed only | canonical       | **no**       |
| new skill/old core | both          | suffixed        | yes          |
| new skill/new core | both          | canonical       | yes (once)   |

Only `old skill/new core` is broken; it is xfail(strict=True) pending
ovos-bus-client#271. The other three are passing controls that prove the
harness can see a handler fire at all.

Pins, chosen by behaviour and asserted at runtime so a release cannot silently
turn a red cell green:

* `ovos-workshop==9.3.1a2` — newest release binding the suffixed topic ONLY
  (9.3.2a1 added the canonical binding in #497 and hides the breakage);
* `ovos-padatious==2.0.0a1` — newest release BEFORE registration-time
  canonicalization (`_dealias_intent_name`, 2.0.1a1). ovos-core forwards
  `match.match_type` verbatim, so the pipeline plugin decides the spelling.

`ovos-bus-client` is deliberately unpinned on the skill side: a workshop pin is
a floor, not a ceiling, so a rebuilt container resolves a current client. The
suite asserts this, because #271's repair is receive-side and can only run in
the process holding the stale binding.

Keeps the existing in-process guards untouched.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@JarbasAl JarbasAl changed the title test: loud guards for the .intent legacy re-emit contract test: intent-topic back-compat guards — in-process, plus a mixed-version two-venv matrix Aug 1, 2026
JarbasAl and others added 4 commits August 1, 2026 15:10
The alias registry was removed (spec-tools#92); the bridge is two
stateless rules now.

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

Adds four channel-pinned cells alongside the existing boundary-pin cells:
stable-skill/dev-core, dev-skill/stable-core, testing-skill/dev-core, and
dev-skill/testing-core. Each is built from the OVOS distro's own
constraints-stable.txt / constraints-testing.txt, fetched fresh at build
time rather than vendored, so the gate tracks the fleet: a distro pin bump
past a behavior boundary flips the affected cell red at that moment.

As pinned today both channels float below the 9.3.2a1/2.0.1a1 boundaries,
so the two skill-side channel cells hit the same known gap as
old-skill/new-core and share its xfail(strict=True); the other two are
passing controls. Also fixes a real setuptools<81 pkg_resources gap the
channel venvs surfaced, and exempts channel combos from the
"frozen container resolves a modern bus-client" assertion, since a distro
constraints file caps bus-client too — a genuine fleet-inventory finding,
not a broken assumption.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Keeps the empirical pin-resolution finding, but describes the current
design: emitter wire twin (reaches frozen images) + local inbound
canonicalization (serves rebuilt containers), each cell noting which
rule it exercises.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@JarbasAl
JarbasAl marked this pull request as ready for review August 1, 2026 18:03
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