Release 0.18.0a1#62
Open
github-actions[bot] wants to merge 110 commits into
Open
Conversation
Adds the remaining two intent primitives.
- resources.py — `LocaleResources`, the OVOS-INTENT-2 loader: the §3
common reader (UTF-8/BOM/CRLF, comments, blanks), recursive
`locale/<lang>/` search with case-insensitive tags, the user→skill→core
override precedence (§2.1), and the five resource roles (§4). The
user-data path is a parameter — no configuration import. Duplicate
resources and empty files raise `MalformedResource`.
- dialog.py — `render()`, the OVOS-INTENT-2 §4.2 dialog renderer: select
a phrase, expand its variety, fill `{name}` slots from caller values.
Expansion precedes fill so a slot value is never parsed as grammar.
An unfilled slot raises `UnfilledSlot`.
20 more conformance tests (58 total).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The package is the reference implementation of the OVOS formal specifications generally, not only the intent specs — the formal-specifications repo will grow beyond OVOS-INTENT-1/2/3. Renamed from ovos-intent-primitives: repo, distribution, and the import package (ovos_intent_primitives -> ovos_spec_tools). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
A CLI and library function that validates every resource file under a locale directory and reports every problem found. - syntax (OVOS-INTENT-1): each template expanded; malformed forms, empty files, and named slots in slot-free roles are errors - naming/layout (OVOS-INTENT-2): base-name charset, .entity slot-name rule, duplicate (role, base name), language-tag form, files outside a language directory, legacy file types - `ovos-spec-lint <locale>` — accepts a locale/ or a <lang>/ directory; exit code non-zero on errors, or on warnings with --strict (CI-ready) 15 linter tests (73 total). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
examples/dirty-locale is a deliberately broken skill locale — every file trips at least one ovos-spec-lint check (two valid files produce no findings). examples/README.md shows the locale, the command, the verbatim linter output (10 errors, 4 warnings), and a table mapping each finding to its spec rule. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
An OO alternative to render(). Beyond holding the phrases, vocabularies and rng, it tracks the last phrase chosen and avoids repeating it on the next call — repetition avoidance, which a stateless function cannot do and which OVOS-INTENT-2 §4.2 explicitly allows. - DialogRenderer(phrases, vocabularies=, rng=) with .render(slots=) - DialogRenderer.from_resources(resources, name) builds one from a LocaleResources - render() refactored to share _render_phrase with the class 6 more tests (79 total). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…a Protocol - rng parameters are now typed `Chooser`, a Protocol (an object with a `choice` method) instead of the meaningless `object`. `random` and a `random.Random` instance both satisfy it. - DialogRenderer holds default slot values (set once, reused every render) and `.entity` value sets. A slot resolves in order: per-call value, default, a random `.entity` value, then UnfilledSlot. - DialogRenderer.from_resources now also loads `.entity` value sets and accepts default `slots`. - LocaleResources.entities() — every `.entity` for the language, as a name -> expanded value set map (mirrors vocabularies()). 6 more tests (85 total). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
One self-contained .py per feature worth demonstrating: - expand.py — the sentence template expander - load_resources.py — the locale resource loader - render_dialog.py — render() and DialogRenderer (repetition avoidance, default slots, .entity fallback) - lint.py — the linter used as a library Adds examples/skill-locale/, a small valid skill locale the loader and dialog scripts read. examples/README.md indexes the scripts. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
When the requested language has no directory, LocaleResources resolves to the nearest available language (OVOS-INTENT-2 §2.2, non-normative) — e.g. en-AU finds en-US. - nearness is decided by a LanguageMatcher Protocol (an object with tag_distance(desired, supported)); the langcodes module satisfies it structurally and is the default - langcodes moved from a hard dependency to the [langcodes] extra; the package core now has zero dependencies. Without langcodes, resolution is exact-match only - max_language_distance caps the fallback (default 10, per §2.2); set 0 to disable 5 more tests (90 total). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Language is dynamic — a locale folder is a skill's multilingual unit — so it must not be pinned at construction. - LocaleResources: drops the `lang` constructor argument; every load method takes `lang`. One instance serves every language, and the smart fallback is resolved afresh per call. - DialogRenderer: now resource-backed and multilingual — built from a LocaleResources + a dialog name, with `render(lang, slots=)`. Repetition avoidance is tracked per language. The `render()` function stays as the stateless, language-agnostic primitive. - New ovos_spec_tools/language.py — `standardize_lang` and `closest_lang`, the single home for the language-tag logic OVOS reimplements across locale loading, TTS voices and STT models (ovos_utils.lang.get_language_dir, phoonnx.match_lang, ...). Mirrors their proven behaviour: tag standardization, distance-below-10 match, the Tagalog `tl`/`fil` quirk. langcodes stays optional. - LocaleResources uses closest_lang; a custom `lang_resolver` may be injected. The LanguageMatcher protocol is replaced by this. 9 more tests (99 total). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…s one langcodes resolves a bare tag to its most-populous region, so "pt" matches "pt-BR" closer than "pt-PT". The unmarked form of a language should resolve to its reference variety: Portuguese is "from Portugal", and every Lusophone country except Brazil follows the pt-PT norm. closest_lang() now, for a bare language tag with a norm region (_NORM_REGION, seeded with pt -> PT), prefers a candidate in that region over the langcodes distance result. So a request for "pt" against a locale offering pt-PT and pt-BR resolves to pt-PT. An explicit pt-BR request is still respected; a bare tag still falls back by distance when the norm region is absent. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…bsent Without langcodes no tag distance can be computed, so closest_lang previously resolved exact matches only. It now adds a final fallback: a candidate sharing the primary subtag — so a request for en-AU still accepts en, en-GB, en-US, ... It prefers the bare language tag, then the norm region, then the first match. This fallback also covers the rare case where langcodes is installed but computes no in-threshold distance. 3 more tests (105 total). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The previous closest_lang was a cascade of special cases — exact, then norm region, then langcodes distance, then a primary-subtag fallback. That logic now lives in a single distance function. - lang_distance(a, b) — the one place policy lives: standardizes tags, measures a bare tag from its norm region (so pt -> pt-PT is 0, not a langcodes population guess), uses langcodes when present, and falls back to a coarse same-language measure (shared primary subtag is near, the generic form nearer than a sibling region) when it is not. - closest_lang(target, available, max_distance) — now branch-free: the candidate with the smallest lang_distance, accepted if below the cap. - lang_distance is exported as a public primitive. 108 tests pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
New docs/ — an ordered guide that builds from a first install to the full API: - getting-started — install and a taste of every tool - templates — the OVOS-INTENT-1 grammar - locale-resources — the locale/ folder and the five file roles - dialog — render() and DialogRenderer - language-matching — standardize_lang, lang_distance, closest_lang - linting — ovos-spec-lint, on the CLI and in CI - api-reference — every public name The README drops its long per-tool sections in favour of a quick taste and links into docs/. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
34 new tests (142 total): - expansion: whitespace/tab normalization, optional vocabularies arg, bare <name> templates, multi-word vocabulary members, empty vocabulary, no cross-template slot-consistency check, optional around a slot, unicode literals, duplicate-branch de-duplication - resources: core fallback and skill-overrides-core precedence, empty .dialog/.voc, vocabularies()/entities() directly, indented and mid-line "#", nonexistent skill_locale, undefined <voc> reference - dialog: single-phrase renderer, no-slot phrases, a slot value containing braces stays literal, numeric values, missing dialog - language: empty available list, empty-string tag, regional vs different-language distance, regioned-request sibling fallback - lint: nonexistent path, empty locale, unknown extension ignored, a single-language directory argument Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
New checks:
- §5.5 slot consistency: every template in one .intent/.dialog must
declare the same slot set — flagged when they differ (previously
unchecked)
- a non-UTF-8 file is now reported as an error instead of crashing the
linter (UnicodeDecodeError is not an OSError)
- a .blacklist with no matching .intent — a warning (orphan suppression)
- a language directory with no resource files — a warning
New --spec-version {0,1,2} flag — a forward-compatibility check that
flags features newer than a target runtime:
- spec-version 0: a .blacklist file warns (a v0 runtime ignores it)
- spec-version < 2: a <name> reference errors (an older runtime cannot
expand the template)
- default 2: nothing extra flagged
11 more linter tests (153 total). The dirty-locale example output is
unchanged.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…cifications) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Part of a documentation and interoperability effort for OpenVoiceOS, funded by NLnet under grant agreement 101135429. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
AI-Generated Change: - Model: claude-sonnet-4-6 - Intent: add missing CI/CD workflows - Impact: added build-tests.yml, coverage.yml, lint.yml, license_check.yml, pip_audit.yml, release_workflow.yml, publish_stable.yml, release-preview.yml, repo-health.yml, conventional-label.yml - Verified via: reviewed apply_workflows.py output
Implements the `.prompt` resource role: a localized, whole-file
plain-text prompt for a language model.
- prompt.py — render_prompt() and PromptRenderer. {name} substitution is
conservative: only a well-formed name, only names the caller supplied,
never inside a ``` fenced code block. Unfilled slots and any other
braces are left literal.
- resources.py — read_prompt_file() (whole/verbatim read) and
LocaleResources.load_prompt(); PROMPT_ROLE constant.
- 22 tests (175 total); docs and an examples/render_prompt.py added.
The linter's .prompt awareness (and --spec-version 3) remains a
follow-up.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The linter now recognizes `.prompt` as a resource role instead of silently ignoring it as an unknown extension. - `.prompt` is a known role: collected, deduplicated, and naming-checked like the others; no longer mistaken for a legacy/unknown file. - A `.prompt` is checked as a whole-file document, not a template — only non-emptiness and a valid UTF-8 read; never template syntax, slot-free or §5.5 checks. - A non-UTF-8 `.prompt` is reported, not crashed. - --spec-version gains 3 (the .prompt role); default is now 3. Below it, a `.prompt` file is a warning — an older runtime ignores the role. 6 tests (181 total); docs/linting.md updated. dirty-locale output unchanged. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…g policy (#4) * feat: lang_matches and iter_locale_dirs — pin the cross-component lang policy Every place that crossed a component boundary (resource loaders, adapt/padatious/padacioso engine bucketing, TTS/STT plugin routing, `_get_closest_lang` boilerplate) reimplemented the same three steps — standardize, distance, threshold-check — with subtle drift between the copies. Bug surface: a skill stores resources under a full tag but an engine buckets under a macroed one (or vice versa), and the two never reconcile. This adds the two missing primitives so callers stop reinventing them: * `lang_matches(a, b, max_distance=10) -> bool` — the `if lang_distance(...) < threshold` check, named and shared. Pass `max_distance=0` for exact-match. * `iter_locale_dirs(root, native_langs=None) -> Iterator[(lang, path)]` — walks `<root>/locale/` and yields each subdir as a canonical `(standardize_lang(name), Path)` pair, optionally filtered against the skill's native langs via `closest_lang`. Resource loaders for `.rx`, `.dialog`, `.voc`, `.intent`, locale `.json` reinvent this walk by hand — switch them to this and the macro/full-tag disagreement disappears. The implicit policy: **full normalized lang tag everywhere**. Macro-stripping is no longer a knob; if an engine buckets under `en` and a resource is `en-US`, the engine reconciles at match time via `closest_lang`, not at registration time via macro flags. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix: remove unused MalformedTemplate import, rename ambiguous loop var, sync docstring - Drop MalformedTemplate from resources.py import (F401 — unused) - Rename loop variable l -> lang in iter_locale_dirs comprehension (E741) - Add lang_matches and iter_locale_dirs to the top-level module docstring Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * ci: pass install_extras as pip args and expose license classifier - install_extras was 'test' (bare) → reusable workflow ran 'pip install test', no such PyPI package. Pass '.[test]' instead. - pip-licenses categorised the package itself as 'Error' (unknown license) because pyproject had no License classifier. Add the SPDX Apache-2.0 classifier and matching Python-version classifiers. - Tighten requires-python to >=3.10 to match the tested matrix. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * ci: exclude ovos-spec-tools from auditing itself pilosus/action-pip-license-checker queries PyPI metadata for each installed package. The PyPI release predates the SPDX License classifier added in this PR, so the package is audited as 'Error' (unknown license) against itself. Exclude it from the audit until the next alpha republishes. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * ci: build-tests install_extras takes the bare extras name The build-tests reusable workflow wraps it as `${WHEEL}[${EXTRAS}]`, so it expects `test` not `.[test]`. The coverage workflow expects raw pip args, so that one keeps `.[test]`. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * ci: warn_only license_check until alpha republishes The pilosus 'exclude' regex did not suppress self-audit; switch to warn_only so the job no longer blocks. Transitive dependencies still get audited and reported in the PR comment. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ng) (#6) Every callsite outside this module that needed to locate a resource file ended up reinventing the override-precedence + closest-lang walk the loader already encapsulates. Promote the previously private _locate method to a public find — same body, fuller docstring, covered by six new tests. The internal load_intent / load_dialog / load_prompt callers move to the new name. The duplicate-resource check is preserved, so a LocaleResources.find caller that violates the §2 uniqueness rule gets the same MalformedResource it would have got via load_*. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…_samples (#8) * feat: keyword_form / vocabulary_keywords / utterance_contains / strip_samples Every consumer that registered slot-free keywords or matched a vocab against an utterance reimplemented the same handful of operations. Promote them to ovos-spec-tools so the OVOS-INTENT-2 §4.3 keyword convention has exactly one implementation. New free functions in ovos_spec_tools.resources: - keyword_form(template_line, vocabularies=None) -> (entity, aliases) expands one slot-free template, lowercases, dedupes and sorts; the first item is the canonical entity, the rest are aliases that canonicalize to it. Malformed input yields ('', []) rather than poisoning a batch. - normalize_for_match(text, ensure_ascii=True) -> str lowercases, strips, and optionally folds accents and ASCII punctuation; the comparison normalization shared by the two below. - utterance_contains(utterance, samples, exact=False, ensure_ascii=True) true iff utterance matches any sample — whole-word substring by default, exact when requested, accent/punct-folded by default. - strip_samples(utterance, samples) -> str remove every whole-word occurrence of any sample, longest first so composite phrases consume their parts before fallback matches. New methods on LocaleResources: - vocabulary_keywords(lang) -> Iterator[(voc_name, entity, aliases)] - entity_keywords(lang) -> Iterator[(entity_name, entity, aliases)] yield one triple per template line, with the entity/alias split applied via keyword_form. Suit any consumer that registers keyword sets with a primary/alias distinction. Tests: 52 passed (16 new). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat: granular normalization flags + permissive whole-word match - normalize_for_match now takes two keyword-only flags: strip_diacritics (NFD + drop combining marks) and strip_punct (drop ASCII punctuation, keep slot braces). The previous ensure_ascii knob conflated the two — French ou/où or technical names like c++ need separate control. Both default True; either flag can be flipped independently. - utterance_contains and strip_samples forward both flags and the whole-word match anchor moves from \b...\b to (?<!\w)...(?!\w), which preserves the \b semantics on word-char boundaries while also matching samples that begin or end in non-word characters (c++, yes!). Without this, a sample ending in punctuation could never match in non-stripped mode. Coverage on the new helpers expanded from 16 to 41 tests covering: each flag independently and in combination, exact-mode normalization, slot marker preservation, regex metachar escaping, unicode samples, malformed lines, blank-sample filtering, override precedence walking, voc-reference resolution at expansion time, and the missing-language branch in _keywords_for. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…n) (#41) * fix: locale/template/lint/language spec-conformance (audit remediation) Remediate the spec-conformance flags from the adversarial audit, each tied to the cited INTENT-1/2/3 clause: - expansion.py inline_keywords: drop the silent max_values=10 truncation (data loss). Default now inlines ALL values; an explicit max_values bound REFUSES (raises MalformedTemplate) when exceeded per INTENT-1 §4.3 (refuse-and-document, never silently drop). - expansion.py inline_keywords: replace the `for _ in range(8)` depth cap with proper recursion + cycle detection (INTENT-1 §4.1) — unbounded resolution that raises on a reference cycle, no magic depth limit. - dialog.py: implement the INTENT-1 §7 / §5.5 MUST — verify_slot_consistency checks all phrases of a dialog declare the same slot set; both render paths call it before rendering and raise on divergence. - lint.py: a divergent .intent slot set is now an ERROR (was WARNING), matching .dialog. INTENT-1 §5.5, INTENT-2 §4.1/§4.2, INTENT-3 §5.1 all mandate rejection; removed the misread INTENT-3 §5.3 (a worked example that itself violates §5.5) justification. - language.py: relabel the pt-PT norm-region and tl/tgl Tagalog overrides as NON-NORMATIVE implementation policy (they deliberately diverge from langcodes, which §2.2 endorses); annotate _coarse_distance 3/5/100 as arbitrary ordering-only values with no spec basis (§2.2 silent on the no-langcodes case). - resources.py keyword_form: comment clarifying its deliberate leniency is confined to the best-effort keyword extractors; the conformant loaders (_load_expanded etc.) call expand() directly and raise. Docs (linting.md, dialog.md traceability, spec-traceability.md) updated to match. Tests updated for the new ERROR severity, no-truncation, cycle rejection, and §5.5 dialog checks. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix: .intent MAY declare different slot sets (union); slot-consistency ERROR is .dialog-only Corrects the audit-remediation overreach: INTENT-2 §4.1 / INTENT-3 §5.1 (per #56/#67) allow .intent templates to declare different slot sets (union); only .dialog (§4.2) requires identical slots. * feat: validate required_slots are declared by a template (OVOS-INTENT-3 §5.3) A required slot MUST be declared by at least one template in the intent; declaring a required slot no template mentions is malformed and a tool MUST reject the definition at registration time (OVOS-INTENT-3 §5.3). required_slots is an intent-definition field above the raw .intent file, so the locale linter (which sees only files) cannot enforce it. Expose the check as public functions for the registration/loading path that has both in hand: - declared_slots(templates): the union of named slots across templates, folding {{name}} to {name} (§3.4). - validate_required_slots(required_slots, templates): raises MalformedTemplate if any required slot is declared by no template. - lint_required_slots(path, required_slots, templates): a Finding-returning wrapper for linting callers. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
…istered field) (#45) SESSION-1 §3 registers fallback_handlers (array of string, owner OVOS-FALLBACK-1 §4). It was falling through to opaque extras instead of being a first-class registered field. Add it to _LIST_OVERRIDE_FIELDS (same array-of-string, other-spec bucket as the blacklists / transformer chains): constructor param, _as_str_list attribute, generic to_dict / from_dict round-trip with empty-list-as- omission (§3.4 / §2.1), and SESSION1_REGISTERED_FIELDS membership via the _LIST_OVERRIDE_FIELDS union. Like converse_handlers, OVOS-FALLBACK-1 is a forward reference (unmerged); the field is carried on the strength of the SESSION-1 §3 registration. Refs SESSION-1 §3, OVOS-FALLBACK-1 §4. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
…ord model, adapt-free) (#46) Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
…ad translation (#42) * fix: message-domain conformance + NamespaceTranslator per-topic payload translation Part 1 — citation/docstring conformance fixes: - messages.py: AUDIO_OUTPUT_STARTED/ENDED + MIC_LISTEN were mis-cited to AUDIO-IN-1; they are owned by OVOS-AUDIO-1 (audio-out.md, open PR #38, unmerged) §5.1/§5.2/§4.4 -> re-cited and marked genuinely PROVISIONAL. - messages.py: LISTENER_RECORD_STARTED/ENDED, LISTENER_SLEEP, LISTENER_AWOKEN are AUDIO-IN-1 §6.1-§6.4 (MERGED) -> mandated, stale 'provisional' removed. - messages.py: relabel NamespaceTranslator/new_mirror_guard dual-emit + mirror-window dedup as non-normative IMPLEMENTATION POLICY (MSG-1 §5.4 disavows host-side correlation). - message.py: serialize() now refuses an empty 'type' (MSG-1 §2.1/§7 gate). - message.py: deserialize() rejects present-but-non-object data/context ([], 0, false, ...) instead of silently coercing to {} (MSG-1 §6/§7). Part 2 — per-topic payload translation (maintainer-directed feature): - Add MIGRATION_PAYLOAD_TRANSFORMS (legacy topic -> (legacy_to_spec, spec_to_legacy)) with transforms for the 5 shape-changing renames (handler trio, detach_intent, enable/disable). Payload-compatible renames default to identity. - Add NamespaceTranslator.translate_payload(from_topic, to_topic, data) that selects direction and applies the transform (identity if none). Bus wiring (ovos-bus-client / ovos-utils FakeBus) is a follow-up. - Update MIGRATION_MAP / module docstrings: drop the false 'legacy consumers keep working without code changes' blanket claim; document lossy cases. Tests: +25 (serialize empty-type, deserialize wrong-type, transform round-trips, identity, direction selection). 410 pass. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix: AUDIO-1 merged — drop provisional labels + add §7 bus-surface topics AUDIO-1 (audio-out.md) is merged, so its mandated topics are no longer provisional. Strip the unmerged/PR#38/PROVISIONAL qualifiers from the audio output signals (ovos.audio.output.{started,ended} §5.1/§5.2, ovos.mic.listen §4.4) across the module docstring, the SpecMessage block, and the MIGRATION_MAP comments. Add the 6 AUDIO-1 §7 bus-surface topics the enum was missing (enum-only, not legacy renames): SPEAK_B64 (§3.4), AUDIO_SPEECH (§4.3), AUDIO_QUEUE (§4.1), AUDIO_PLAY_SOUND (§4.2), AUDIO_STOP (§6), AUDIO_IS_SPEAKING (§5.3). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * docs: make message-domain docstrings timeless and standalone Drop development-process framing (merged/now-mandated/no-longer/historically) from the message.py and messages.py docstrings; state the current spec-defined behaviour directly. Runtime-state phrasing (a 'previously disabled' intent, a 'previously-seen' mirror Message) is unchanged. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* docs: make intent + bus-namespace docs timeless and standalone Restate the intent primitives and the namespace-migration docs in terms of current behaviour rather than development history: drop 'historically provided by ovos-workshop', 'reimplementation/replacement', 'OVOS is moving ... off the historical names', and 'still landing'. Describes what the code and the migration map ARE, readable without knowing the project's history. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * docs: state AUDIO-IN-1/AUDIO-1 bus topics as defined, not provisional The listener-lifecycle (AUDIO-IN-1 §6) and mic/audio-output (AUDIO-1 §4.4/§5) specs are merged; split the conflated table row by owning spec and drop the 'provisional / prose not yet finalized' caveat. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
The six OVOS-AUDIO-1 §7 output topics are present in the SpecMessage enum but were enum-only — they had no MIGRATION_MAP entry, so the bus could not bridge the legacy Mycroft-era handler names during the migration window. Add the legacy->spec renames (legacy names verified against ovos-audio register_handlers); all are payload-compatible 1:1 renames, so they bridge with the identity payload transform (no MIGRATION_PAYLOAD_TRANSFORMS entry): - speak:b64_audio -> ovos.utterance.speak.b64 (§3.4) - speak:b64_audio.response -> ovos.audio.speech (§4.3) - mycroft.audio.queue -> ovos.audio.queue (§4.1) - mycroft.audio.play_sound -> ovos.audio.play_sound (§4.2) - mycroft.audio.speak.status -> ovos.audio.is_speaking (§5.3) - mycroft.audio.speech.stop -> ovos.audio.stop (§6) Tests: enum membership + legacy->spec mapping + round-trip; replace the now-stale assertion that the AUDIO-1 members carry no migration counterpart. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
serialize() already gates the §2.1 non-emptiness rule but not the §2.1
character/whitespace rule: a topic may contain only ASCII letters, digits,
'.', ':', '_', '-' and no whitespace. Message("a b").serialize() emitted an
embedded space instead of rejecting it.
Add the §2.1 charset/whitespace validation at the serialize() wire gate
(after the non-empty check) so a malformed topic never reaches the wire,
matching the §7 producer MUST. The constructor still tolerates an empty
scaffold type for the construct-then-forward pattern.
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
) The INTENT-3 §4.2 well-formedness MUSTs were enforced only in lint_locale, not in the IntentBuilder/Intent data model, so an invalid intent could be built and registered on the bus. Add Intent.validate() enforcing both §4.2 MUSTs and call it from the construction path (IntentBuilder.build) and the emission path (Intent.to_keyword_payload): - (a) a keyword intent MUST declare at least one required or one-of constraint — an optional/excluded-only intent has nothing that must be present and is malformed; - (b) a vocabulary MUST appear under at most one role — the same vocab under two roles (e.g. required and excluded) is contradictory and malformed. Raises the new MalformedIntent. Raw Intent(...) construction stays permissive (the scaffold / wire round-trip path via open_intent_envelope), so validation fires only at build/emit and a subclass overriding build() is unaffected. INTENT-1 §5.5 (DialogRenderer rejecting non-identical slot sets) is already enforced on dev via verify_slot_consistency in both render paths. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
…3 §4.2) (#59) IntentBuilder.build() and Intent.to_keyword_payload() validated the intent and RAISED MalformedIntent. Because ovos-workshop re-exports this IntentBuilder, build()-raises broke skills that build a constraint-less intent — a backward-compat regression on consumers' dev (e.g. workshop's test_build_preserves_name). Build/emit now LOG a warning instead; the raising enforcement stays available via the explicit Intent.validate() method and the locale linter, so §4.2 is still enforced where rejection is wanted, without breaking construction. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Both classes defined __eq__ but no __hash__, so Python set __hash__ to
None and they were unhashable -- forcing downstream lru_cache callers
(padacioso/linha-fina/nebulento) to pass frozenset workarounds instead
of the objects themselves.
Add __hash__ to both, derived from the SAME fields each __eq__ compares
so equal objects always hash equal (the hash/eq contract):
- Session.__hash__ from to_dict()
- Message.__hash__ from (msg_type, data, context)
A shared _freeze() helper (in message.py, imported by session.py)
recursively converts nested dict/list/set payloads into a deterministic
hashable form. It preserves value-equality: {"x": 1} and {"x": 1.0}
freeze equal (unlike a json.dumps digest, which would diverge "1" vs
"1.0" and violate the contract). The hash is a point-in-time snapshot of
these mutable objects -- safe for lru_cache keys and short-lived dict/set
membership, documented in the method docstrings.
Not a spec change: no message/session semantics or spec docs touched.
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Human review requested!