Skip to content

Commit a170059

Browse files
dfredmota999claude
andcommitted
audit(round2): check all error returns — zero silent failures across system
- websocket.go: sendMessage() checks Marshal+WriteMessage errors - websocket.go: audioGeminiToClient() breaks after 10 consecutive errors, notifies client - websocket.go: SendAudio error now notifies client with error message - voice/handler.go: SendText/SendAudio errors checked and logged - browser_voice_handler.go: SendAudio/SendImage/SendText errors checked - eva_handler.go: SendText error checked, notifies browser on failure - crisis/notifier.go: NotifyPsychologist returns error when ALL channels fail - crisis/notifier.go: json.Marshal errors checked in email/SMS fallbacks - whatsapp/service.go: json.Marshal and http.NewRequest errors checked Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 61d08d7 commit a170059

6 files changed

Lines changed: 103 additions & 23 deletions

File tree

browser_voice_handler.go

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -637,22 +637,28 @@ func (s *SignalingServer) handleBrowserVoice(w http.ResponseWriter, r *http.Requ
637637
case "audio":
638638
pcmData, err := base64.StdEncoding.DecodeString(msg.Data)
639639
if err == nil {
640-
client.SendAudio(pcmData)
640+
if err := client.SendAudio(pcmData); err != nil {
641+
log.Error().Err(err).Str("session", sessionID).Msg("[Browser] Erro ao enviar audio para Gemini")
642+
}
641643
if s.speakerSvc != nil {
642644
go s.speakerSvc.ProcessAudioChunk(sessionID, clientCPF, pcmData)
643645
}
644646
}
645647
case "video":
646648
jpegData, err := base64.StdEncoding.DecodeString(msg.Data)
647649
if err == nil {
648-
client.SendImage(jpegData)
650+
if err := client.SendImage(jpegData); err != nil {
651+
log.Error().Err(err).Str("session", sessionID).Msg("[Browser] Erro ao enviar imagem para Gemini")
652+
}
649653
}
650654
case "text":
651655
if msg.Text != "" {
652656
if s.evaMemory != nil {
653657
go s.evaMemory.StoreTurn(ctx, sessionID, "user", msg.Text)
654658
}
655-
client.SendText(msg.Text)
659+
if err := client.SendText(msg.Text); err != nil {
660+
log.Error().Err(err).Str("session", sessionID).Msg("[Browser] Erro ao enviar texto para Gemini")
661+
}
656662
}
657663
case "config":
658664
log.Info().Str("session", sessionID).Msg("Browser sent config update")

eva_handler.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -267,7 +267,12 @@ func (s *SignalingServer) handleEvaChat(w http.ResponseWriter, r *http.Request)
267267
transcriptMu.Lock()
268268
transcriptAccum.WriteString("Usuario: " + msg.Text + "\n")
269269
transcriptMu.Unlock()
270-
geminiClient.SendText(msg.Text)
270+
if err := geminiClient.SendText(msg.Text); err != nil {
271+
log.Error().Err(err).Str("session", sessionID).Msg("[EVA] Erro ao enviar texto para Gemini")
272+
writeMu.Lock()
273+
conn.WriteJSON(evaMessage{Type: "status", Text: "error: falha ao enviar mensagem"})
274+
writeMu.Unlock()
275+
}
271276
}
272277
}
273278
}

internal/clinical/crisis/notifier.go

Lines changed: 34 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -93,30 +93,48 @@ func (n *Notifier) NotifyPsychologist(ctx context.Context, event *CrisisEvent) e
9393
RequiresAction: event.ResponseActions["require_acknowledgment"],
9494
}
9595

96+
var errors []string
97+
9698
// 1. Send webhook (real-time)
9799
if n.webhookURL != "" {
98-
err := n.sendWebhook(ctx, payload)
99-
if err != nil {
100+
if err := n.sendWebhook(ctx, payload); err != nil {
100101
log.Error().Err(err).Msg("Failed to send webhook")
102+
errors = append(errors, "webhook: "+err.Error())
101103
}
102104
}
103105

104106
// 2. Send email (backup)
105-
if n.emailAPI != "" {
106-
err := n.sendEmail(ctx, payload)
107-
if err != nil {
107+
if n.emailAPI != "" || n.emailSvc != nil {
108+
if err := n.sendEmail(ctx, payload); err != nil {
108109
log.Error().Err(err).Msg("Failed to send email")
110+
errors = append(errors, "email: "+err.Error())
109111
}
110112
}
111113

112114
// 3. Send SMS (critical only)
113-
if event.Severity == "CRITICAL" && n.smsAPI != "" {
114-
err := n.sendSMS(ctx, payload)
115-
if err != nil {
115+
if event.Severity == "CRITICAL" && (n.smsAPI != "" || n.smsSvc != nil) {
116+
if err := n.sendSMS(ctx, payload); err != nil {
116117
log.Error().Err(err).Msg("Failed to send SMS")
118+
errors = append(errors, "sms: "+err.Error())
117119
}
118120
}
119121

122+
// Se NENHUM canal conseguiu enviar, retorna erro
123+
channelsAttempted := 0
124+
if n.webhookURL != "" {
125+
channelsAttempted++
126+
}
127+
if n.emailAPI != "" || n.emailSvc != nil {
128+
channelsAttempted++
129+
}
130+
if event.Severity == "CRITICAL" && (n.smsAPI != "" || n.smsSvc != nil) {
131+
channelsAttempted++
132+
}
133+
134+
if channelsAttempted > 0 && len(errors) == channelsAttempted {
135+
return fmt.Errorf("CRITICO: todos os %d canais de notificacao falharam para evento %d: %v", channelsAttempted, event.ID, errors)
136+
}
137+
120138
return nil
121139
}
122140

@@ -179,7 +197,10 @@ func (n *Notifier) sendEmail(ctx context.Context, payload NotificationPayload) e
179197
"subject": subject,
180198
"payload": payload,
181199
}
182-
jsonData, _ := json.Marshal(emailPayload)
200+
jsonData, err := json.Marshal(emailPayload)
201+
if err != nil {
202+
return fmt.Errorf("marshal email payload: %w", err)
203+
}
183204
req, err := http.NewRequestWithContext(ctx, "POST", n.emailAPI, bytes.NewBuffer(jsonData))
184205
if err != nil {
185206
return err
@@ -222,7 +243,10 @@ func (n *Notifier) sendSMS(ctx context.Context, payload NotificationPayload) err
222243
"message": reason,
223244
"payload": payload,
224245
}
225-
jsonData, _ := json.Marshal(smsPayload)
246+
jsonData, err := json.Marshal(smsPayload)
247+
if err != nil {
248+
return fmt.Errorf("marshal sms payload: %w", err)
249+
}
226250
req, err := http.NewRequestWithContext(ctx, "POST", n.smsAPI, bytes.NewBuffer(jsonData))
227251
if err != nil {
228252
return err

internal/motor/whatsapp/service.go

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,14 @@ func (s *Service) SendMessage(to, message string) error {
3838
},
3939
}
4040

41-
body, _ := json.Marshal(payload)
42-
req, _ := http.NewRequest("POST", url, bytes.NewBuffer(body))
41+
body, err := json.Marshal(payload)
42+
if err != nil {
43+
return fmt.Errorf("marshal payload: %w", err)
44+
}
45+
req, err := http.NewRequest("POST", url, bytes.NewBuffer(body))
46+
if err != nil {
47+
return fmt.Errorf("create request: %w", err)
48+
}
4349
req.Header.Set("Authorization", "Bearer "+s.accessToken)
4450
req.Header.Set("Content-Type", "application/json")
4551

@@ -85,8 +91,14 @@ func (s *Service) SendTemplateMessage(to, templateName string, params []string)
8591
},
8692
}
8793

88-
body, _ := json.Marshal(payload)
89-
req, _ := http.NewRequest("POST", url, bytes.NewBuffer(body))
94+
body, err := json.Marshal(payload)
95+
if err != nil {
96+
return fmt.Errorf("marshal template payload: %w", err)
97+
}
98+
req, err := http.NewRequest("POST", url, bytes.NewBuffer(body))
99+
if err != nil {
100+
return fmt.Errorf("create template request: %w", err)
101+
}
90102
req.Header.Set("Authorization", "Bearer "+s.accessToken)
91103
req.Header.Set("Content-Type", "application/json")
92104

internal/senses/signaling/websocket.go

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -743,7 +743,11 @@ Use isso para guiar sua resposta ao próximo áudio.
743743
}
744744

745745
if err := session.GeminiClient.SendAudio(pcmData); err != nil {
746-
log.Printf("❌ Erro ao enviar áudio para Gemini")
746+
log.Printf("❌ [SendAudio] Erro ao enviar audio para Gemini (session=%s): %v", session.ID, err)
747+
s.sendMessage(session.WSConn, ControlMessage{
748+
Type: "error",
749+
Error: "Falha ao enviar audio para IA: " + err.Error(),
750+
})
747751
}
748752
}
749753

@@ -752,17 +756,33 @@ func (s *SignalingServer) audioClientToGemini(session *WebSocketSession) {
752756
}
753757

754758
func (s *SignalingServer) audioGeminiToClient(session *WebSocketSession) {
759+
consecutiveErrors := 0
760+
const maxConsecutiveErrors = 10
761+
755762
for {
756763
select {
757764
case <-session.ctx.Done():
758765
return
759766
default:
760767
response, err := session.GeminiClient.ReadResponse()
761768
if err != nil {
769+
consecutiveErrors++
770+
log.Printf("⚠️ [audioGeminiToClient] Erro Gemini (%d/%d): %v", consecutiveErrors, maxConsecutiveErrors, err)
771+
772+
if consecutiveErrors >= maxConsecutiveErrors {
773+
log.Printf("❌ [audioGeminiToClient] %d erros consecutivos — encerrando sessao %s", maxConsecutiveErrors, session.ID)
774+
s.sendMessage(session.WSConn, ControlMessage{
775+
Type: "error",
776+
Error: fmt.Sprintf("Conexao com IA perdida apos %d tentativas", maxConsecutiveErrors),
777+
})
778+
return
779+
}
780+
762781
time.Sleep(100 * time.Millisecond)
763782
continue
764783
}
765784

785+
consecutiveErrors = 0
766786
s.handleGeminiResponse(session, response)
767787
}
768788
}
@@ -1683,8 +1703,14 @@ func (s *SignalingServer) getIdosoByCPF(cpf string) (*Idoso, error) {
16831703
}
16841704

16851705
func (s *SignalingServer) sendMessage(conn *websocket.Conn, msg ControlMessage) {
1686-
data, _ := json.Marshal(msg)
1687-
conn.WriteMessage(websocket.TextMessage, data)
1706+
data, err := json.Marshal(msg)
1707+
if err != nil {
1708+
log.Printf("❌ [sendMessage] Erro ao serializar mensagem type=%s: %v", msg.Type, err)
1709+
return
1710+
}
1711+
if err := conn.WriteMessage(websocket.TextMessage, data); err != nil {
1712+
log.Printf("❌ [sendMessage] Erro ao enviar mensagem type=%s: %v", msg.Type, err)
1713+
}
16881714
}
16891715

16901716
func (s *SignalingServer) sendError(conn *websocket.Conn, errMsg string) {

internal/voice/handler.go

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"context"
88
"encoding/base64"
99
"encoding/binary"
10+
"fmt"
1011
"eva-mind/internal/brainstem/config"
1112
"eva-mind/internal/brainstem/database"
1213
"eva-mind/internal/cortex/gemini"
@@ -235,7 +236,11 @@ func (h *Handler) HandleMediaStream(w http.ResponseWriter, r *http.Request) {
235236
if sid, ok := start["streamSid"].(string); ok {
236237
streamSidChan <- sid
237238
// Inicia a conversa
238-
geminiSession.Client.SendText("O usuário atendeu. Diga 'Olá'.")
239+
if err := geminiSession.Client.SendText("O usuário atendeu. Diga 'Olá'."); err != nil {
240+
h.logger.Error().Err(err).Msg("Erro ao enviar texto inicial para Gemini")
241+
errChan <- fmt.Errorf("gemini SendText: %w", err)
242+
return
243+
}
239244
}
240245
}
241246
case "media":
@@ -255,7 +260,9 @@ func (h *Handler) HandleMediaStream(w http.ResponseWriter, r *http.Request) {
255260

256261
// Flush se buffer cheio
257262
if len(audioSess.AudioBuffer) >= MIN_BUFFER_SIZE {
258-
geminiSession.Client.SendAudio(audioSess.AudioBuffer)
263+
if err := geminiSession.Client.SendAudio(audioSess.AudioBuffer); err != nil {
264+
h.logger.Error().Err(err).Msg("Erro ao enviar audio para Gemini")
265+
}
259266
// Reset buffer mantendo capacidade
260267
audioSess.AudioBuffer = audioSess.AudioBuffer[:0]
261268
}

0 commit comments

Comments
 (0)