π Found during T73's live smoke (2026-07-22)
A persona_taste.context of {} deserializes TasteContext.DaysOfWeek to null (STJ missing-property default), and TasteMatcher.MatchesDay (TasteMatcher.cs:42) throws NRE on it β on every pick evaluation. The exception is caught upstream, so the symptom is invisible: picks continue, the rule just never fires, nothing logs above the swallowed stack trace.
Reachable in production via any external write whose context omits the array β persona card import (F79) being the obvious door; hand-edited rows and future operator-rule authoring (#8-adjacent) likewise.
β
Proposed (either/both)
- Fail-safe matcher: treat null
DaysOfWeek as "no day gate" (same semantics as []) β one null-coalesce, converts garbage into the least-astonishing meaning.
- Normalize at the read seam:
PersonaTasteRepository coalesces null collections after deserialize, so the domain type's non-null contract actually holds everywhere downstream.
Plus one WARN when a taste rule's evaluation throws β a silently disabled rule contradicts the F82.6 "why did it play that?" observability contract.
π Notes
- Pre-existing Q4 code (T63/T64), not introduced by V24 work; found because a hand-seeded smoke row hit exactly this shape.
π Found during T73's live smoke (2026-07-22)
A
persona_taste.contextof{}deserializesTasteContext.DaysOfWeekto null (STJ missing-property default), andTasteMatcher.MatchesDay(TasteMatcher.cs:42) throws NRE on it β on every pick evaluation. The exception is caught upstream, so the symptom is invisible: picks continue, the rule just never fires, nothing logs above the swallowed stack trace.Reachable in production via any external write whose context omits the array β persona card import (F79) being the obvious door; hand-edited rows and future operator-rule authoring (#8-adjacent) likewise.
β Proposed (either/both)
DaysOfWeekas "no day gate" (same semantics as[]) β one null-coalesce, converts garbage into the least-astonishing meaning.PersonaTasteRepositorycoalesces null collections after deserialize, so the domain type's non-null contract actually holds everywhere downstream.Plus one WARN when a taste rule's evaluation throws β a silently disabled rule contradicts the F82.6 "why did it play that?" observability contract.
π Notes