ADR-019: Scoped Name Uniqueness¶
Status¶
Accepted Date: 2026-03-04
Context¶
Cosalette enforced globally unique names across all registration types — devices,
telemetry, and commands. A single _validate_name_unique() check collected names from
all three registries into one set[str] and rejected duplicates regardless of
registration type.
This prevented telemetry and command registrations from sharing the same name, even though ADR-002 explicitly defines a topic layout where both concerns share a single device segment:
The @app.device archetype already used a single name for both state publishing and
command handling, proving that the MQTT topic structure was designed for shared
namespaces. But using @app.telemetry and @app.command separately — which is
necessary for accessing telemetry-specific features like PublishStrategy,
PersistPolicy, and coalescing groups — forced workarounds like cmd_ prefixes
that violated ADR-002's topic conventions.
In the vito2mqtt reference project, 4 out of 7 domain groups (57%) required both
telemetry and commands. The cmd_ prefix workaround produced topics like
vito2mqtt/.../cmd_hot_water/set instead of the expected
vito2mqtt/.../hot_water/set.
Decision¶
Scope name uniqueness per registration type instead of globally. The collision matrix:
| Pair | Result | Rationale |
|---|---|---|
| device ↔ device | REJECT | Same MQTT topics would conflict |
| device ↔ telemetry | REJECT | Device archetype already publishes state |
| device ↔ command | REJECT | Device archetype already handles commands |
| telemetry ↔ telemetry | REJECT | Both would publish to the same /state topic |
| command ↔ command | REJECT | Both would subscribe to the same /set topic |
| telemetry ↔ command | ALLOW | Different MQTT suffixes (/state vs /set) — this is the ADR-002 design intent |
When a telemetry and command registration share the same name, the framework:
- Creates a shared
DeviceContextso both handlers publish availability and errors under the same device identity. - Publishes one availability message per shared name instead of duplicates.
- Maintains separate handler execution — the telemetry handler runs in its polling loop and the command handler responds to incoming MQTT messages independently.
Device registrations remain globally unique — the @app.device archetype already
manages both state and commands under a single name, so collisions with any other
registration type are always rejected.
Decision Drivers¶
- ADR-002 compliance — the MQTT topic layout was designed for telemetry and commands to share a device segment. The global uniqueness constraint contradicted this.
- 57% domain overlap — more than half of vito2mqtt's domains needed both telemetry and commands, making this a mainstream use case, not an edge case.
- Principle of least surprise —
@app.devicealready uses a single name for both concerns. Users would expect@app.telemetry("x")+@app.command("x")to work the same way when the archetype features require separate registrations. - Zero API surface change — the solution required no new parameters, no new decorators, and no changes to existing user code.
Considered Options¶
Option A: topic= Parameter to Decouple Name from Topic¶
Add an optional topic= parameter to add_telemetry() and add_command() so that
the internal name (used for logging, keying, health) and the MQTT topic segment can
differ:
app.add_telemetry(name="hot_water_telemetry", topic="hot_water", ...)
app.add_command(name="hot_water_command", topic="hot_water", ...)
- Advantages: Preserves global name uniqueness. Introduces an explicit topic override. Flexible for advanced use cases.
- Disadvantages: Adds API surface (new parameter). Creates a name/topic duality
that complicates logging and debugging. Requires changes to
DeviceContext,TopicRouter, and validation logic. Users must remember two identifiers per registration.
Option B: Per-Type Uniqueness (Chosen)¶
Validate name uniqueness within each registration type rather than globally. Telemetry and command registrations can share the same name; same-type duplicates and device collisions are still rejected.
- Advantages: Zero API surface change. ADR-002 compliant. Name and topic remain
identical — no duality. Shared
DeviceContextenables clean availability and error semantics. - Disadvantages: Shared
DeviceContextsemantics must be understood (mitigated: consistent with@app.devicebehavior). Root device constraints remain global (acceptable: root devices are a special case).
Option C: @app.domain() Composite Registration¶
Introduce a new decorator that registers both telemetry and command handlers under a single name with a unified configuration:
@app.domain("hot_water", interval=30)
async def hot_water(ctx):
# handles both polling and commands
...
- Advantages: Expressive API for the common case. Single registration point.
- Disadvantages: New API surface and new archetype concept. Unclear how to combine telemetry-specific features (publish strategy, coalescing groups) with command semantics. Significant design and implementation effort. Does not help with the majority of existing registrations that use the current three-archetype model.
Decision Matrix¶
| Criterion | A: topic= Parameter |
B: Per-Type Uniqueness | C: @app.domain() |
|---|---|---|---|
| ADR-002 compliance | 5 | 5 | 5 |
| Zero API surface change | 2 | 5 | 1 |
| Implementation complexity | 3 | 4 | 2 |
| Name/topic clarity | 3 | 5 | 4 |
| Backward compatibility | 4 | 5 | 3 |
| Shared DeviceContext semantics | 3 | 4 | 5 |
| Total | 20 | 28 | 20 |
Scale: 1 (poor) to 5 (excellent)
Option B scores highest because it achieves ADR-002 compliance with zero API surface change and minimal implementation complexity. The name remains the topic segment, so there is no duality to manage.
Consequences¶
Positive¶
- Telemetry and command registrations can share the same name, enabling ADR-002 compliant MQTT topic namespaces without workarounds
- Zero API surface change — no new parameters, decorators, or concepts
- Shared
DeviceContextmeans availability and error topics are unified per device, matching the natural MQTT device model - Existing applications with globally unique names continue to work identically
- The
cmd_prefix workaround in vito2mqtt can be removed
Negative¶
- Shared
DeviceContextsemantics require understanding — developers must know that a telemetry and command handler sharing the same name also share device availability and error context (mitigated: consistent with@app.devicebehavior) - Root device (unnamed) constraints remain global — only one unnamed registration of any type is permitted (acceptable: root devices are a distinct, rare use case)
- Device archetype collisions are still globally rejected —
@app.device("x")cannot coexist with@app.telemetry("x")or@app.command("x")(by design: the device archetype already handles both concerns)
2026-03-04