0.9.0

Итерация 4 плана аудита: шина сообщений и доставка cron/heartbeat.

Added

  • Миграции 020_actor_pending_msg (SQLite + PG): таблица spillover для inbound-сообщений, не уместившихся в буфер актора. FIFO-порядок по id, индекс idx_actor_pending_msg_session_key
  • Тип store.ActorPendingMsg и методы SaveActorPendingMsg/ListActorPendingMsg/DeleteActorPendingMsg в SQLite/PG/Dual store
  • Метрики шины и актора: Metrics.BusInboundDropped, Metrics.BusOutboundDropped, Metrics.ActorChFull, Metrics.ActorPendingSpilled + новый блок bus в /api/v1/metrics snapshot
  • Singleton-доступ metrics.Default() для использования из пакетов, не получающих *Metrics явно
  • WS-событие system_message (поля source_channel, session_key, chat_id, text, session_title) — рассылается при любом outbound из cron/heartbeat с непустым Content. UI (web/src/routes/chat/+page.svelte) обрабатывает наряду с session_updated
  • Метод WebSocketChannel.SendSystemMessage(sourceChannel, sessionKey, text, sessionTitle)
  • Тесты: internal/bus/bus_test.go (success / inbound full / outbound full / recovers when reader / SessionKey override), internal/channels/manager_test.go (coalesce без re-publish, AllOurs, NoWebSocket), internal/store/actor_pending_test.go (FIFO, limit, empty key)

Changed

  • bus.PublishInbound/PublishOutbound больше не делают silent-drop через select-default. Используется select с time.NewTimer(PublishTimeout) (5s по умолчанию). При таймауте — slog.Error, метрика BusInboundDropped/BusOutboundDropped, возврат bus.ErrBusFull. Сигнатура изменилась с func(msg) на func(msg) error
  • Все callers PublishInbound/PublishOutbound адаптированы: chat handler возвращает 503, email poller логирует и пропускает, base channel логирует, MessageTool возвращает «bus overloaded» в LLM, ProcessDirect пробрасывает ошибку наружу
  • actorChanCap увеличен с 32 до 128. При действительном переполнении routeToActor сериализует bus.InboundMessage в JSON и сохраняет в actor_pending_msg (вместо slog.Warn("dropping"))
  • SessionActor.run подгружает spillover-сообщения при старте (после возможного рестарта процесса) и после каждой обработки (drainPending). Реинжект пакетами по pendingDrainBatch=32, FIFO по id
  • ChannelManager.coalesceStreamDeltas больше не публикует «не наше» сообщение обратно в bus.Outbound — теперь сохраняет в m.pendingMsg под pendingMu. dispatchOutbound сначала проверяет pendingMsg, потом канал. Это устраняет потенциальный double-drop под нагрузкой
  • ChannelManager.forwardSystemNotification помимо session_updated теперь рассылает system_message с Content для cron/heartbeat outbound. UI получает текст в WS-уведомлении даже при отсутствии открытого чата с этой сессией

Fixed

  • Work §1.2/§3.1/§4.1 P0.1: silent-drop сообщений в шине под нагрузкой. Теперь caller знает, что сообщение не доставлено
  • Work P0.3 / Bug B2.1: cron/heartbeat ответ агента не доходил до пользователя — теперь forwardSystemNotification ретранслирует Content в WS как system_message
  • Work §1.2: dropping message at actor.ch full — заменено на persistent spillover в БД с автоматическим drain
  • Work §1.2 (хвост): coalesceStreamDeltas могла re-publish-нуть в полную шину и потерять сообщение