From c229e9e9e33f0ef26e12140c49985c70b5e9ac97 Mon Sep 17 00:00:00 2001 From: JarbasAi Date: Wed, 9 Sep 2026 04:08:31 +0100 Subject: [PATCH] spec(intent-4): the cross-skill deregistration needs a transport that does not mirror onto detach_skill Co-Authored-By: Claude Fable 5.1 Co-Authored-By: Claude Opus 5 --- CHANGELOG.md | 10 ++++++++++ appendix/divergences.md | 2 +- intent-4.md | 26 ++++++++++++++++++++++++++ 3 files changed, 37 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 988f834..6cbdbd1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -326,6 +326,16 @@ version 2: its `{{ … }}` sequences become substitution points, and its ### 2 +- §8.4 — the cross-skill deregistration (source ≠ target) is carried + only by a transport that delivers `ovos.skill.deregister` on its own; + a producer **MUST NOT** emit it through a client that mirrors the + topic onto `detach_skill`, whose predecessor handlers act on the + source. Self-deregistration is unaffected. A malformed emission + (payload without `skill_id`) has no target, so spec handlers remove + nothing. The mirrored predecessor acts on it as a + self-deregistration. The mirror + runs both ways, so emitting `detach_skill` directly does not avoid + it. Divergence row for `detach_skill` records both shapes. - §8.6 (new) — `ovos.skill.loaded`, the session-keyed load announcement with a registered `capabilities` vocabulary (`fallback`, `common_query`, `converse`); withdrawn by `ovos.skill.deregister`. §10.3 (new) — diff --git a/appendix/divergences.md b/appendix/divergences.md index 08520c2..70efdb3 100644 --- a/appendix/divergences.md +++ b/appendix/divergences.md @@ -518,7 +518,7 @@ a number of predecessor names. The mapping: | `padatious:register_intent` | `ovos.intent.register.template` | Same content, structured payload. | | `padatious:register_entity` | `ovos.entity.register` | Entities are not Padatious-specific. | | `detach_intent` | `ovos.intent.deregister` | Identity now expressed as the structured triple, not the munged `skill_id:intent_name` string. | -| `detach_skill` | `ovos.skill.deregister` | | +| `detach_skill` | `ovos.skill.deregister` | Predecessor handlers resolve the skill from `context`. A client that mirrors `ovos.skill.deregister` onto `detach_skill` therefore turns two shapes into a self-deregistration on the mirrored copy: a cross-skill deregistration (payload names another skill), which removes the source together with the target, and a malformed one (payload omits `skill_id`), which the spec handlers reject and the predecessor handlers act on. The cross-skill form of INTENT-4 §8.4 needs the mirror off (`emit_legacy=false` in ovos-bus-client); the malformed form is unsafe on a mirroring client rather than merely ignored. The mirror runs both ways through two flags: `emit_legacy` copies `ovos.skill.deregister` onto `detach_skill`, and `modernize` copies `detach_skill` onto `ovos.skill.deregister`. So a producer emitting `detach_skill` directly reaches the same consumers, and the source is removed whichever wire it uses. | | `mycroft.skill.enable_intent` / `mycroft.skill.disable_intent` | `ovos.intent.enable` / `ovos.intent.disable` | First-class topics under v1, with the prefix dropped. | | `mycroft.skills.loaded` | `ovos.skill.loaded` | Session-keyed, carries `capabilities` instead of a filesystem `path` and display `name`. `mycroft.skills.loading_failure` has no successor: a skill that did not load announces nothing. | | `skillmanager.list` / `mycroft.skills.list` | `ovos.skills.list` / `.list.response` | A `response` derivation with a `skills` array instead of a map keyed by skill id; entries carry `session_id`, `capabilities` and an intent count instead of `active`. | diff --git a/intent-4.md b/intent-4.md index 2540fd8..3cd85ad 100644 --- a/intent-4.md +++ b/intent-4.md @@ -607,6 +607,32 @@ SHOULD emit `ovos.skill.deregister` carrying the satellite's session in `context` for every skill the satellite registered when the satellite disconnects (OVOS-BRIDGE-1 §3). +The payload `skill_id` is the target and `context.skill_id` the +source (§3.2), so a source may deregister a skill other than itself. +That **cross-skill form** is carried only by a transport that +delivers `ovos.skill.deregister` on its own. A client that mirrors +the topic onto its predecessor `detach_skill` (appendix, divergences) +hands the mirrored copy to every consumer whose predecessor handler +resolves the skill from `context`, and the source's own +registrations are removed together with the target's. A producer +**MUST NOT** emit a cross-skill deregistration through a client that +mirrors registration topics onto their predecessors. A skill +deregistering itself is unaffected, because source and target +coincide. The same transport property reaches a **malformed** +emission: a Message whose payload omits `skill_id`, a required +field of the §3.2 table, has no target, so a consumer of this +section removes nothing, but its mirrored copy carries +the emitter in `context`, and a predecessor handler acts on it as a +self-deregistration. Through a mirroring client, then, a producer +that omits the target loses its own registrations rather than +nothing. The mirror runs in both directions: a client that mirrors +the spec topic onto `detach_skill` also delivers a `detach_skill` +emission under `ovos.skill.deregister`, so emitting the predecessor +directly reaches the same consumers and the source is removed +whichever wire the producer uses. The mirror is a setting of the +migration tooling, not a property of any handler; the cross-skill +form is available once the deployment's clients stop mirroring. + Deregistering an intent, entity, or skill that is not currently registered is a **no-op**: registrations are fire-and-forget, every plugin processes the message independently, and any plugin without a