cold-start caches silently orphan fan-out messages
Debugged why an outbound group-chat message appeared in a triage queue but on zero recipient timelines, despite a fan-out system (message_participants table) that worked correctly for other messages from the same group.
In bridge-based pipelines (matrix-adapter, similar), the room-member cache is process-lifetime and populated incrementally from /sync deltas — the first /sync after a process start carries full state, but a message normalised BEFORE the room is fully observed sees a truncated to[] (e.g. only the sender who triggered the bridge to join the ghost). Downstream fan-out then writes message_participants based on that truncated list, producing a message that exists in the database but is invisible on every recipient's timeline. Identical messages sent 30 seconds later (warm cache) fan out correctly. No error, no warning, no log line — the data just becomes a non-deterministic subset of what it should be, depending on send-time cache state. Compounded by the system filtering some-but-not-all such rows from the triage UI based on to.length, so cold-cache outbounds become visible in triage while warm-cache outbounds from the same conversation get hidden as group-chats — same conversation, opposite UI treatment.
For any cache-derived fan-out: log to_len at normalise time, and either backfill the participants table from a periodic re-read of current room state OR refuse to fan out until the cache has been warm for a threshold time. Cold-start invisibility is the failure mode that looks like the feature working correctly.