Skip to content

Release 0.13.12a1 - #424

Open
github-actions[bot] wants to merge 74 commits into
masterfrom
release-0.13.12a1
Open

Release 0.13.12a1#424
github-actions[bot] wants to merge 74 commits into
masterfrom
release-0.13.12a1

Conversation

@github-actions

Copy link
Copy Markdown
Contributor

Human review requested!

JarbasAl and others added 30 commits March 11, 2026 04:28
* feat: AsyncFakeBus alongside FakeBus

Adds an asyncio-native sibling to FakeBus that mirrors the surface of
ovos_bus_client.client.AsyncMessageBusClient (the [async] extra
shipped in ovos-bus-client 2.0).

ovos_utils.fakebus.AsyncFakeBus
- connect/close/emit/wait_for_message/wait_for_response: coroutines
- on/once/remove/remove_all_listeners: synchronous (matches the real
  AsyncMessageBusClient's handler-registration contract — pyee, no
  awaitable callbacks)
- Same session-context injection side effects as FakeBus, so multi-turn
  flows behave identically
- Lazy-imports from ovos_bus_client.session when present; gracefully
  degrades without it (same pattern as FakeBus)
- Backwards-compat shims (create_client, run_forever, run_in_thread) so
  it is a drop-in replacement anywhere FakeBus is currently used

Where this pays off
- Tests of code that calls 'await bus.emit(...)' or
  'await bus.wait_for_response(...)' (test_fakebus's sync equivalents
  cover that surface for the old client; this covers the new one).
- Runtime: in-process bus stand-in for asyncio-native components that
  do not need a real ovos-core (mirrors how FakeBus is used today by
  HiveMessageBusClient.connect(bus=FakeBus())).

Tests (test/unittests/test_async_fakebus.py, 19 tests)
- Lifecycle (construct/connect/close, session_id from kwarg)
- Handler registration (on/once/remove/remove_all_listeners + dispatch)
- emit side effects (session injection, raw 'message' event)
- wait_for_message (matched concurrently, timeout)
- wait_for_response (default <type>.response, explicit reply_type, timeout)
- Backwards-compat shims

No changes to existing FakeBus; all 23 existing FakeBus tests still pass.

* docs: add AsyncFakeBus to fakebus documentation

Documents the new AsyncFakeBus class alongside FakeBus in
docs/fakebus.md: coroutine/sync split table, key-methods table
with file:line citations, session-handling note, and a minimal
usage snippet. Updated docs/index.md module table and Contents
list to include AsyncFakeBus.

AI-Generated Change:
- Model: claude-sonnet-4-6
- Intent: keep docs accurate after feat: AsyncFakeBus alongside FakeBus (14ccc3d)
- Impact: updated docs/fakebus.md, docs/index.md; added citations; no stale content removed
- Verified via: manual review against ovos_utils/fakebus.py
* feat: expand_template delegates to ovos-spec-tools

ovos-utils' bracket expander is one of ~7 copies of the same logic
across the OVOS ecosystem. It now delegates to the OVOS-INTENT-1
reference expander, ovos_spec_tools.expand — the single conformant
implementation — instead of carrying its own.

- expand_template() is a thin wrapper over ovos_spec_tools.expand.
- adds the ovos-spec-tools dependency.

Behaviour change (conformance with OVOS-INTENT-1):
- a malformed template — a single-branch group, an empty sample, a
  slot-only template — now raises MalformedTemplate instead of silently
  producing a degenerate result. The old lenient behaviour was a bug.
- whitespace in a sample is normalized to single spaces; an emptied
  [optional] no longer leaves a double space.

Tests updated to the conformant output; a test added for the
malformed-template raise.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat: migrate lang and dialog onto ovos-spec-tools; deprecate the shims

Completes the ovos-utils migration onto ovos-spec-tools.

- lang.get_language_dir() delegates to ovos_spec_tools.closest_lang.
- dialog.py and file_utils.py use ovos_spec_tools.expand directly.
- expand_template, get_language_dir, MustacheDialogRenderer,
  load_dialogs and get_dialog are deprecated: each both emits a
  DeprecationWarning (visible to IDEs and tooling) and logs via the
  @deprecated helper, pointing callers at the ovos-spec-tools
  equivalent. The removal version is derived from version.py — the
  next major release (VERSION_MAJOR + 1).

standardize_lang_tag is left unchanged: its `macro` parameter has no
ovos-spec-tools equivalent and documented, tested behaviour.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat: migrate standardize_lang_tag to ovos-spec-tools

standardize_lang_tag now delegates to ovos_spec_tools.standardize_lang
and is deprecated (DeprecationWarning + the @deprecated log helper).

The `macro` parameter is kept for backward compatibility but no longer
affects the result: its region-stripping only ever happened on the
no-langcodes fallback path — inconsistent with the langcodes path,
which never stripped the region — so it was a latent bug.

geolocation.py now uses ovos_spec_tools.standardize_lang directly.
test_lang.py updated to the conformant behaviour.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix: standardize_lang_tag macro=True returns the bare language

Keep the `macro` parameter functional — when set, drop the region with
a plain .split("-")[0] on the standardized tag, so
standardize_lang_tag(x, macro=True) returns the bare primary language
subtag as before.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ak (#375)

* feat: fakebus Message is the ovos-spec-tools class directly; publish attached as a method

OVOS-MSG-1 lives in ovos-spec-tools 0.5.0a1+. The 165-line FakeMessage
class (with its _MutableMessage metaclass + dynamic __new__) is gone;
fakebus now re-exports the spec-tools Message directly and attaches the
one legacy convenience method downstream still uses:

    from ovos_spec_tools.message import Message as FakeMessage
    FakeMessage.publish = _publish_function

The MutableMessage metaclass / runtime indirection that tried to return
an ovos_bus_client.Message when bus-client was installed is no longer
needed: spec-tools is a hard dependency, the canonical class is always
present, and ovos-bus-client.Message is the **same** class.

What's gone:

* The historical reply() quirk that promoted data['destination'] into
  context['destination'] was always a bug.
* as_dict is now on the spec-tools Message itself; no need to define
  it here.

What stays:

* publish() — attached at module import; relay under a new topic, drop
  'target', no swap, no deep-copy of data;
* The deprecated ovos_utils.fakebus.Message alias for downstream
  callers still doing from ovos_utils.fakebus import Message.

pyproject pin: ovos-spec-tools>=0.5.0a1.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* chore: deprecate fakebus.FakeMessage.publish — slated for removal in next major

publish is a bus-client tradition outside OVOS-MSG-1 (the spec defines
forward / reply / response as the only normative derivations). Every
call now fires a DeprecationWarning via both warnings.warn and the
@deprecated decorator from ovos_utils.log, naming the next major
(computed f'{VERSION_MAJOR + 1}.0.0' from version.py) as the removal
target.

Migration: switch publish() callers to forward() or reply().

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix: bump ovos-spec-tools to >=0.5.1a1 to pick up the empty-msg_type accept

0.5.0a1 still rejected empty `msg_type` at construction, which broke
the `Message("").forward(real_type, data)` scaffold pattern used by
the scheduler/event tests. 0.5.1a1 accepts empty at construction and
gates only on serialize/wire output. Align both pins.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* ci: align coverage workflow with build-tests (install extras, scope to test/unittests)

The coverage job was installing the base package only — test modules
that import `ovos_bus_client` / `ovos_config` failed at collection.
Match the build-tests config so coverage actually runs the suite.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* ci: pass install_extras as a pip arg, not an extras name

The gh-automations coverage workflow installs literally what install_extras
contains: `pip install ${install_extras}`. `extras` alone is meaningless;
`.[extras]` is what installs the optional-dependencies group.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…des semantics) (#377)

The spec-tools migration of `standardize_lang_tag` changed the
`macro` argument from its historic meaning (langcodes-defined
**macrolanguage substitution** — `cmn` -> `zh`, `nb` -> `no`)
to **"strip the region"** (`en-US` -> `en`) via
`tag.split('-')[0]`. Every caller that passes the default
`macro=True` (`ovos_bus_client.session`, many transformer
plugins, …) now gets region-stripped output — including OVOS's
`SessionManager`, which silently rewrites `session.lang` from
`en-US` to `en` on every message and breaks downstream consumers
that key on region (locale resource lookups, regional dialog,
ovoscope final-session assertions).

Restore the historic semantics by delegating to
`langcodes.standardize_tag(lang_code, macro=macro)` — which is what
the pre-migration body did (commit 9baa615). When langcodes is
unavailable, fall back to spec-tools' `standardize_lang` (also
region-preserving) and treat `macro` as a no-op.

Tests pin all three:
- macro=True preserves the region (`en-US` round-trips)
- macro=True substitutes macrolanguages (`cmn` -> `zh`)
- macro=False keeps both the region and the sublanguage

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
json_database~=0.10 resolves to >=0.10,<1.0, excluding json_database 1.x (now published, 1.0.2a1, which dropped the duplicate hivemind-json-db-plugin entry point). As ovos-utils is a foundational dependency, this cap blocks json_database 1.x from resolving anywhere in the OVOS alpha set. Widen to >=0.10,<2.0.0.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
FakeBus now uses ovos_spec_tools.NamespaceTranslator (the same logic as
MessageBusClient) so the test/satellite double bridges legacy<->ovos.* topics
and dedupes dual-listeners identically: emit() also dispatches the counterpart
topic(s); on() wraps migrated-topic handlers with the shared mirror-guard.
Both flags default on; override per-instance with modernize=/emit_legacy=.
Bumps ovos-spec-tools>=0.9.0a1 (NamespaceTranslator).
…383)

fakebus.py imports NamespaceTranslator from ovos_spec_tools at module top
level (unconditional), and that symbol first ships in ovos-spec-tools
0.10.0a1. The previous >=0.9.0a1 floor could resolve to a version without
it, making `import ovos_utils.fakebus` fail at install time.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* fix: translate mirrored payload onto counterpart topic in FakeBus

FakeBus mirrors MessageBusClient's namespace-migration bridge; it now
calls NamespaceTranslator.translate_payload() at its counterpart-emit
point so the mirrored Message carries the payload in the COUNTERPART
topic's shape instead of a verbatim copy. This keeps the test double
faithful to the real bus.

Identity transform for payload-compatible renames (behaviour unchanged);
reshaped per direction for the shape-changing renames (handler trio,
detach_intent, enable/disable_intent).

Pins ovos-spec-tools floor to >=0.14.1a1 (the version that will publish
the translate_payload API, ovos-spec-tools#42).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* ci: install in-flight spec-tools cross-dep from git

* ci: pre-install spec-tools conformance-message for coverage job

* fix: lower spec-tools floor to 0.14.0a1 (translate_payload branch version)

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* feat: mirror namespace migration in AsyncFakeBus + env/config flag parity

AsyncFakeBus now mirrors FakeBus / MessageBusClient namespace migration:
builds a NamespaceTranslator, wraps migrated-topic handlers with a
mirror-guard for dedup in on()/remove(), and dispatches counterpart
topics with translate_payload on emit().

Both FakeBus and AsyncFakeBus resolve modernize/emit_legacy the way the
real client's _bus_flag does: explicit kwarg wins, else env var ->
websocket.* config -> default True. A local _bus_flag helper mirrors the
semantics without importing from ovos-bus-client (layering). A _UNSET
sentinel distinguishes "kwarg omitted" from "kwarg passed True/False".

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix: drop obsolete spec-tools git-ref in CI; floor to published translate_payload

The build/coverage workflows pinned ovos-spec-tools@fix/conformance-message
via pre_install_pip because translate_payload was unpublished. It has shipped
since 0.16.1a1, so the git-ref is obsolete (and masked an inadequate
>=0.14.0a1 floor that cannot satisfy the FakeBus namespace-migration code).
Floor bumped to >=0.16.1a2 (the published min carrying the NamespaceTranslator
payload-transform API) and the CI git-ref removed — versions belong in
pyproject, not CI.

* chore: remove stray agent scratch files (AGENTS.md, TODO.md, console-script artifact)

* chore: drop requirements/*.txt — pyproject.toml is the single source of truth

The requirements/{requirements,extras}.txt files duplicated the inline
[project.dependencies] / [project.optional-dependencies] (pyproject was already
a superset — it additionally carried python-dateutil and packaging). Nothing
reads them (no setup.py/MANIFEST; CI installs .[extras]). Removed per the
pyproject-only packaging rule.

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
…390)

The FakeBus shape-changing reshape tests asserted a
mycroft.skill.handler.* <-> ovos.intent.handler.* migration that does not
(and should not) exist — the handler-lifecycle trio is orchestrator-owned
spec topics, not a rename of the legacy per-skill handler events, so it was
correctly absent from ovos-spec-tools' MIGRATION_MAP. The tests therefore
failed (the spec listener was never reached).

Retarget them at an actual shape-changing pair from
MIGRATION_PAYLOAD_TRANSFORMS: detach_intent <-> ovos.intent.deregister,
which splits the compound "skill:intent" name into skill_id + intent_name
(and rejoins it in reverse). Same reshape coverage, real mapping.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
JarbasAl and others added 30 commits July 23, 2026 19:35
LOGLOCK was instantiated at import time as
ComboLock("ovos_logs_console_script"), a bare relative path. ComboLock
unconditionally creates the lock file if it doesn't exist relative to
the process's current working directory, so simply running
`ovos-logs --help` (or importing ovos_utils.log_parser at all) leaves
behind an empty file named `ovos_logs_console_script` wherever the
command was invoked from.

Switch to combo_lock.NamedLock, which resolves the lock file to a
proper runtime directory (RAM disk or tempdir) instead of the cwd.

Added a regression test that runs the CLI (and a bare import) via
subprocess in a temporary cwd and asserts no stray files appear.

Authored with AI assistance (Claude).
…#402)

distutils was removed from the standard library in Python 3.12, so
ovos_utils.sound and ovos_utils.device_input fail to import with
ModuleNotFoundError: No module named 'distutils'. shutil.which is a
drop-in stdlib replacement for distutils.spawn.find_executable.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
create_self_signed_cert() generates a 1024-bit RSA / SHA-1 certificate
that modern OpenSSL (SECLEVEL=2, the default on Debian/Ubuntu/Fedora)
refuses to load (EE_KEY_TOO_SMALL), making any cert it produces unusable
for TLS. Reproduced with:

    openssl req -x509 -newkey rsa:1024 -sha1 -nodes -keyout w.key -out w.crt -days 1 -subj "/CN=test"
    python3 -c "import ssl; c=ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER); c.load_cert_chain('w.crt','w.key')"
    # -> ssl.SSLError: [SSL: EE_KEY_TOO_SMALL] ee key too small

Rather than fix the crypto parameters here, mark the helper deprecated
using the repo's existing @deprecated decorator and dynamic
VERSION_MAJOR + 1 removal-version convention (see bracket_expansion.py,
dialog.py, system.py). The function keeps working unchanged (same
signature, same file naming, same reuse-if-present behaviour, same
return value) so nothing breaks for existing callers; only the log
message steers callers away from it.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
FileEventHandler stored file_path but never compared it against
event.src_path, so a FileWatcher asked to watch a single file fired
its callback for every file changing in that file's directory, and
two single-file watchers sharing a directory each fired for both
files (duplicate callbacks). FileWatcher now tells the handler which
file to filter on (None in directory-watch mode), and the handler
compares realpath(event.src_path) against it before dispatching, in
both the closed and created/modified branches. Directory-watch mode
is unchanged.
FileWatcher dispatched file-vs-directory mode with os.path.isfile(),
so a path that doesn't exist yet fell into directory mode: watchdog
was asked to schedule an observer on the nonexistent path (which
fails) and the handler ran unfiltered. Callers watching a config file
that is created later (eg. ovos-config watching mycroft.conf before it
exists) never got the 'created' event.

Dispatch on os.path.isdir() instead: an existing directory keeps
directory mode unchanged; anything else (an existing file, or a path
that doesn't exist yet) is file mode, watching the parent directory
and filtering to that single path - safe because of the per-file
filtering added in #406.

If the parent directory also doesn't exist, watchdog can't schedule an
observer on it either; skip that entry with a LOG.warning instead of
letting an opaque watchdog exception propagate.
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
…MessageBusClient) (#417)

* fix: give FakeBus the intent-topic bridge (RULE 1/RULE 2 parity with MessageBusClient)

FakeBus already mirrored MessageBusClient's fixed legacy<->ovos.* namespace
migration (NamespaceTranslator), but not the per-intent dispatch-topic
bridge the real client applies on every emit: the send-side legacy twin
and the receive-side canonical modernize. In-process tests that raw-emit
a legacy <skill_id>:IntentName.intent topic never reached a canonical-only
listener (ovos-workshop >= 9.3.11a2 dropped its own dual-bind), while a
real websocket deployment dealiased fine. Port the same two rules into
FakeBus.emit()/AsyncFakeBus.emit(), gated by the existing modernize/
emit_legacy flags and guarded against recursion with the same
INTENT_COMPAT_TWIN_KEY marker the real client uses.

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

* fix: intent-pair subscribe guard + pop-before-dispatch for FakeBus intent bridge

Adversarial review found two defects in the FakeBus RULE1/RULE2 intent-topic
bridge (#417):

- on() had no subscribe-side dedup for the intent bridge at all, so a handler
  bound to both the canonical and legacy-suffixed spelling of an intent (the
  common ovos-workshop dual-bind, which uses a fresh wrapper closure per
  registration) fired twice for one dispatch. Ported MessageBusClient's
  per-topic-pair mirror guard (shared by every registration on either
  spelling, not per-handler) into FakeBus.on()/AsyncFakeBus.on().

- emit() popped the RULE2 twin marker too late (inside _bridge_intent_topic,
  after the main handler dispatch), and the internally-generated RULE1 twin
  carried the marker into local handler context. A handler that forwarded
  that twin's context onto an unrelated topic would incorrectly brand the
  new frame a twin and silently suppress its own modernization. Marker is
  now popped at the top of emit()/AsyncFakeBus.emit() before any dispatch,
  and the RULE1 twin no longer carries the marker into local delivery at
  all (mirroring the real client, whose receiving process pops it before
  any local handler runs) -- this also makes the pair guard's payload+
  context fingerprint match the canonical dispatch's, which the dedup
  above depends on.

Rewrote test_no_double_fire_dual_listener to assert ONE call (matching the
real client) and test_canonical_emit_twin_marked to assert the marker is
NOT visible locally; added starvation and marker-leak regression tests.
Every new/changed assertion verified to fail against the pre-fix source via
patch-revert before the fix, and pass after.

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…wire (#419)

Clarify that FakeBus's intent-pair dedup guard mirrors a single real
MessageBusClient connection, not multiple bus connections; test
observers sharing a skill's FakeBus should subscribe to the canonical
intent topic.
…421)

Registering the same handler twice on one guarded topic used to be
harmless because pyee keys its listener OrderedDict by the handler
object, so an equal bound method collapsed onto one slot. Both the
intent-topic bridge and the namespace-migration (is_migrated) branch
of the mirror guard started wrapping every registration in a fresh
closure, and pyee saw a new object each time, so the same registration
fired twice instead of once.

FakeBus.on/AsyncFakeBus.on already reused the existing wrapper for a
repeat (msg_type, handler) pair; once() bypassed the mirror guard
entirely, so a handler bound via once() to both spellings of a
mirrored dispatch fired twice, and a later on() of the same handler
stacked a second, independent listener because once() never recorded
itself in _dedup_registrations. once() now goes through the same
guard-selection and wrapper-reuse machinery as on(), self-cleaning its
bookkeeping once pyee auto-removes the fired listener.

Adds regression coverage for once() on both intent spellings, once()
followed by on() of the same handler, duplicate on() registration on
the legacy intent spelling, and duplicate on() registration on a
migrated-namespace topic (recognizer_loop:utterance) -- the last pins
the chosen 1-fire behaviour rather than baseline's 2.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…#422)

is_connected_dns() opened a raw socket for the reachability probe and never
closed it, leaking a file descriptor on every check (and on every failed
probe, which is the common case on a flaky network). It's now closed in a
finally block with the return semantics unchanged.

The rest of the ~90 warnings were tests deliberately covering deprecated
shims (standardize_lang_tag/get_language_dir, fakebus.Message,
EventSchedulerInterface, the dialog module, expand_template) plus a couple
of unrelated stdlib/test-fixture ResourceWarnings — now filtered per-test
with specific message patterns, coverage kept. One warning remains, sourced
from the still-installed (unpatched) ovos-bus-client dependency's own
session.py, not from this repo; it clears once that fix ships.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
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