Skip to content

Release 0.15.0a1 - #445

Open
github-actions[bot] wants to merge 105 commits into
masterfrom
release-0.15.0a1
Open

Release 0.15.0a1#445
github-actions[bot] wants to merge 105 commits into
masterfrom
release-0.15.0a1

Conversation

@github-actions

@github-actions github-actions Bot commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

Human review requested! This supersedes any earlier open release proposal for this repository.

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 August 14, 2026 15:35
* perf: skip disabled debug call-site resolution

* fix: mirror stdlib logging suppression

* perf: gate all disabled log levels
…error type (#429)

mpris_metadata used Variant('d', length) for mpris:length, but MPRIS2
requires signature 'x' (int64 microseconds); a non-finite length also
crashed at Variant construction. update() set attributes via raw setattr
with no validation, letting a non-numeric length/position/match_confidence
poison later consumers such as Playlist.length's sum(). dict2entry raised
AttributeError on non-dict input instead of a consistent ValueError.
)

log_deprecation() called inspect.stack() (a full O(n) frame walk) before
checking whether the deprecation had already been logged, so every
deduplicated repeat call paid the same walk as the first. A caller
invoking a deprecated helper once per file in a batch (e.g. once per
.desktop file at boot) paid this cost on every repeat instead of just
the first, measured at ~8 minutes of added boot time on a real install.

log_deprecation now consults a cheap, inspect.stack()-free pre-check
keyed on the immediate caller's code object/line (via sys._getframe(1))
plus the message and any explicit func_name/func_module before doing
anything else. Only a call from a not-yet-seen call site falls through
to the existing stack walk that resolves the full (message, origin)
dedup key; repeats from the same site return immediately. Logged
messages and dedup semantics (same message, different callers still
each log once; same site, same message logs once) are unchanged.

Added a regression test pinning existing dedup behavior on top of the
existing test_log_deprecation_dedupe (adding a fast-cache clear it
didn't need to know about before), and a perf regression guard that
patches inspect.stack with a counting wrapper: on unfixed dev code the
wrapper is called 3/3 times (and further fails at
`_logged_deprecations_fast` not existing before that); after the fix
only the first of three repeat calls invokes inspect.stack.

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
…it (#411)

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

The FakeBus/AsyncFakeBus intent-topic bridge itself (RULE 1 canonical
twin, RULE 2 legacy modernize) already landed on dev via #417 with a
slightly different design (separate modernize/emit_legacy flags,
per-topic-pair mirror-guard dedup, no wire-firehose replay for the
twin) than this branch originally shipped, so the regression suite
added here targets the current, merged behaviour instead of
duplicating the bridge.

Rebased onto dev (was 7 commits atop a stale base with heavy conflicts
in fakebus.py from the intervening #417 refactor; squashed to one
commit here). Fail-before: the two error-isolation tests raised
RuntimeError out of emit() without the try/except; full suite went
983 (dev) -> 1004 passed with this branch, no regressions.

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
TestEventScheduler in test_event_scheduler.py imported EventScheduler
straight from ovos_bus_client.util.scheduler and patched
builtins.open/json.load/json.dump/threading.Thread to assert on that
class's pre-rework internals (es.events, tuple entries, a bare persisted
dict). ovos-bus-client 2.11.1a1 replaced that store with a versioned
envelope (ovos_bus_client/util/scheduled_events/store.py); load() now
does content.get("version") which raises AttributeError against the
mocked json.load returning a string, so all four tests fail on current
ovos-bus-client while the real (unmocked) code path works fine.

The scheduler class and its persistence format are owned by
ovos-bus-client, which carries its own scheduler test suite. ovos-utils
has no business pinning another package's internals; drop the stale
tests rather than rewriting them here. TestEventSchedulerInterface,
which exercises ovos_utils' own deprecated shim, is unaffected and
stays.

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

OVOS-SESSION-2 §5.1's arrival merge is a once-per-utterance orchestrator-intake
fold (core#915), not something every bus consumer repeats on every message it
observes. FakeBus.on_message (sync and async) called SessionManager.update on
every inbound message, including default-session traffic; for a default
session that resolves through spec-tools' full-replace update() using spec
defaults for OMITTED fields, so a later message whose carrier simply doesn't
restate a field wipes it out of the live store -- the same §2.6 violation
fixed on the real client in ovos-bus-client#317. A FakeBus models one bus
connection for a test, and a test drives far more default-session traffic
through it than one fold per utterance.

on_message now only calls update() for a carrier that names a real (non-
default) session id, checked via resolve_session_id(carrier) ==
DEFAULT_SESSION_ID, matching the real MessageBusClient's _take_inbound_
session (ovos-bus-client#317, rebased onto ovos-bus-client's #324 which
renamed the resolver from names_the_default to the now-public
resolve_session_id). A test that wants the orchestrator's own intake
semantics calls SessionManager.fold_inbound explicitly, the same shape
core's real intake now uses. No existing test in this repo asserted the old
per-message fold behavior, so nothing needed rewriting for meaning; the new
tests in test_fakebus_session_fold.py pin the corrected contract directly
(fail before this fix: stale/omitted-field observed traffic overwrites the
store; pass after) plus a positive pin that explicit fold_inbound still
merges field-by-field through the same FakeBus/Message plumbing.

resolve_session_id and session_carrier only exist from ovos-bus-client
2.11.4a1 (session_carrier since fold_inbound landed, resolve_session_id
since #324 made the resolver public), but pyproject.toml's `extras` still
floored ovos_bus_client at >=2.6.2a2. On any install between that floor and
2.11.4a1 the new import raised ImportError, and the bare `except ImportError:
pass` this block already had (for the case ovos-bus-client is not installed
at all, an optional dependency) silently swallowed it too, disabling ALL
named-session tracking in FakeBus rather than failing loudly or working
correctly. Fixed two ways: the floor is bumped to >=2.11.4a1 (floor pin
only, no upper bound), and the try/except is narrowed to an if/else so only
the import statement itself is covered by the "not installed" ImportError
guard -- a real error in the block that follows is no longer misread as
"not installed".

Rebased onto origin/dev's #438, which dropped the stale
test_event_scheduler.py failures this branch previously reported as
pre-existing and unrelated -- the full suite is fully green with no
failures left to carry forward.

Full suite: clean origin/dev (6b67265) is 1000 passed / 1 skipped; this
branch is 1003 passed / 1 skipped -- the +3 is the new
test_fakebus_session_fold.py file in full, nothing else changed status.

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
…er (#441)

OVOS-SESSION-1 §2.5, as merged in architecture dev 198a3c9, requires that on
a malformed carrier the messagebus SERVER -- the first consumer on ingress,
before fan-out -- MUST NOT crash: it drops the whole message and emits one
ovos.session.rejected notice naming the dropped type and reason. A regular
client only drops and logs; it does not emit the rejection itself, since it
is not the ingress point. ovos-bus-client 2.11.9a1's MessageBusClient already
does the client-side drop-and-log half in
ovos_bus_client/client/client.py (_take_inbound_session wrapped in
try/except MalformedSession, returning before dispatch). A FakeBus is server
and client in one process, so it is the correct place for the
ovos.session.rejected emit -- there is no separate server process to do it
instead. Both FakeBus.on_message and AsyncFakeBus.on_message in
ovos_utils/fakebus.py called session_carrier()/Session.from_message()
completely unguarded, so any handler that re-emits a Message carrying a
non-object context.session (e.g. a skill that drops an utterance but still
emits its own ack) crashed every FakeBus-based test and ovoscope's MiniCroft
with an uncaught MalformedSession.

This implements the full merged §2.5 behaviour (drop + one
ovos.session.rejected) in FakeBus/AsyncFakeBus, playing the server's role in
the single process a FakeBus models. Both on_message implementations now
catch MalformedSession, log a WARNING, and report the drop back to their
caller (emit()) instead of raising, so emit() skips delivering the malformed
message to any listener. on_message then emits SpecMessage.SESSION_REJECTED
(ovos-spec-tools>=1.10.7a2) with data={"msg_type": <dropped type>,
"reason": "malformed_carrier"}, carrying the dropped message's utterance_id
when present and no session key -- routed through a new _deliver() helper
split out of emit() so the rejection is not itself stamped with a default
session. pyproject.toml's ovos-spec-tools floor is bumped to >=1.10.7a2 for
the SESSION_REJECTED constant.

Fail-before: reverting only the ovos_utils/fakebus.py + pyproject.toml hunks
(tests kept) makes 3 of 5 new regression tests fail -- the malformed message
was still delivered to the "x" listener and no ovos.session.rejected was
emitted. Restoring the fix makes all 5 pass. Full suite: 1008 passed, 1
skipped (pre-existing, unrelated), before (aside from the 3 failing new
tests) and after.

Co-authored-by: Claude Sonnet 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.

2 participants