Codex R15 on the local transport:
- [P2] A light command set brightness/rgb but the normalizer stored only `on`,
so the poll dropped them and the UI/automations went stale. The light state
now carries brightness + rgb when the device reports them.
- [P2] ShellyLocalOnboarder matched an existing row by IP BEFORE the Shelly id,
so a DHCP-reassigned IP could let one Shelly overwrite an unrelated device.
Now matches by stable Shelly id first and reuses an IP row only when it's the
same (or an unidentified) device.
+3 tests (light attrs, housekeeping dropped, IP-reuse no hijack). Suite 71 green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Multi-agent review (5 confirmed) + Codex found real issues in the rework:
- [P1 security] Shared `shelly` ACL had readwrite +/rpc, so a compromised
device could inject Switch.Set into ANY other device's <prefix>/rpc. Now
read-only on +/rpc (receive own commands) + write only homeos/rpc (reply).
Broker-verified: a shelly publish to victim/rpc is denied; laravel's is not.
- [P2 security] `+` wildcard reaches reserved homeos/ring namespaces → bogus
device. Ingest now rejects RESERVED_PREFIXES (homeos/ring/$SYS).
- [P2 security] Unbounded auto-onboarding = DB-exhaustion DoS. Added a device
cap (homeos.mqtt.max_devices, default 250).
- [P2 correctness] Every Shelly `input` became a phantom window contact (wall
switches shown as windows, possibly inverted). `input` is now a generic
sensor; the user PROMOTES specific inputs to window/door contacts on the
device page (invert-aware), stored in config->input_roles and applied in the
ingest — this is the "assign contacts" flow the user asked for.
- [P3 ux] Motion pill read `active`; producer writes `on`. Now reads both.
- [P1 migrations] Dedup computed survivor keys once; 3+ duplicates could
collide on unique(device_id,key). Re-query per duplicate (ring + mqtt_prefix).
+9 tests (reserved prefix, cap, input generic/promoted/inverted, demo echo).
Live-verified: input published → onboarded as input → assigned window contact
via UI → appears on Fenster page, persists across messages. Suite 57 green,
12/12 tabs clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Addresses "no way to read window/door contacts": ShellyNormalizer never emitted
contact entities, so the Fenster page was empty for real hardware.
- Map the Shelly `input` component (digital reed on a Shelly input) → contact
entity {open, position}; analog/count inputs are skipped.
- Dedicated `contact`/`window` components → 3-state {open, position} with
closed/open/**tilted** support (`gekippt`) when the sensor reports it.
- entity-state renders the 3-state pill (closed=neutral, tilted=warning,
open=offline); DE/EN labels. `contact` is a primary auto-onboarding type, so a
window sensor on an input onboards itself.
ShellyNormalizerTest (5 cases). Live-verified: input state true/false →
contact open/closed on an auto-onboarded device. Suite 51 green, 12/12 clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>