Make delivery and integration failures explicit
Persist reminder presentations and retry state, atomically complete collapsed deliveries, fall back across away reaches, and block permanent failures visibly (V-715, V-678). Fail closed when enabled integrations lack credentials and keep remote arms explicitly dark (V-691). Give mavweb one sanitized, request-correlated error contract (V-689). Owner explicitly requested direct commits to master.
This commit is contained in:
+19
-13
@@ -46,24 +46,28 @@ func pushToTalk(pcm []byte) voice.Request {
|
||||
|
||||
func handlePTT(w http.ResponseWriter, r *http.Request, voiceAddr string, session *webauthn.PasskeySession, requireStepUp bool) {
|
||||
if r.Method != http.MethodPost {
|
||||
http.Error(w, "POST only", http.StatusMethodNotAllowed)
|
||||
writeProblem(w, r, http.StatusMethodNotAllowed, problemMethodNotAllowed,
|
||||
"POST only", nil)
|
||||
return
|
||||
}
|
||||
if !stepUpGate(w, session, requireStepUp) {
|
||||
if !stepUpGate(w, r, session, requireStepUp) {
|
||||
return
|
||||
}
|
||||
body, err := io.ReadAll(http.MaxBytesReader(w, r.Body, maxPTTAudioBytes))
|
||||
if err != nil {
|
||||
var tooLarge *http.MaxBytesError
|
||||
if errors.As(err, &tooLarge) {
|
||||
http.Error(w, "audio exceeds the ten-minute PTT limit", http.StatusRequestEntityTooLarge)
|
||||
writeProblem(w, r, http.StatusRequestEntityTooLarge, problemPayloadTooLarge,
|
||||
"audio exceeds the ten-minute PTT limit", err)
|
||||
return
|
||||
}
|
||||
http.Error(w, "read audio", http.StatusBadRequest)
|
||||
writeProblem(w, r, http.StatusBadRequest, problemInvalidRequest,
|
||||
"read audio", fmt.Errorf("read PTT audio: %w", err))
|
||||
return
|
||||
}
|
||||
if len(body) < 4 {
|
||||
http.Error(w, "too short", 400)
|
||||
writeProblem(w, r, http.StatusBadRequest, problemInvalidRequest,
|
||||
"audio too short", nil)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -72,36 +76,38 @@ func handlePTT(w http.ResponseWriter, r *http.Request, voiceAddr string, session
|
||||
var d net.Dialer
|
||||
tc, err := d.DialContext(r.Context(), "tcp", voiceAddr)
|
||||
if err != nil {
|
||||
log.Printf("ptt dial voice: %v", err)
|
||||
http.Error(w, "voice unavailable", http.StatusServiceUnavailable)
|
||||
writeProblem(w, r, http.StatusServiceUnavailable, problemVoiceUnavailable,
|
||||
"voice unavailable", fmt.Errorf("dial voice service: %w", err))
|
||||
return
|
||||
}
|
||||
defer tc.Close()
|
||||
|
||||
req := pushToTalk(body)
|
||||
if err := writeFrame(tc, &req); err != nil {
|
||||
log.Printf("ptt write: %v", err)
|
||||
http.Error(w, err.Error(), 500)
|
||||
writeProblem(w, r, http.StatusBadGateway, problemVoiceTransport,
|
||||
"voice request failed", fmt.Errorf("write voice request: %w", err))
|
||||
return
|
||||
}
|
||||
|
||||
for {
|
||||
resp, push, err := readOneFrame(tc)
|
||||
if err != nil {
|
||||
log.Printf("ptt read: %v", err)
|
||||
http.Error(w, err.Error(), 500)
|
||||
writeProblem(w, r, http.StatusBadGateway, problemVoiceTransport,
|
||||
"voice response failed", fmt.Errorf("read voice response: %w", err))
|
||||
return
|
||||
}
|
||||
if push != nil {
|
||||
continue
|
||||
}
|
||||
if resp.Error != nil {
|
||||
http.Error(w, resp.Error.Message, 500)
|
||||
writeProblem(w, r, http.StatusBadGateway, problemVoiceResponse,
|
||||
"voice turn failed", fmt.Errorf("voice RPC error: %s", resp.Error.Message))
|
||||
return
|
||||
}
|
||||
var pttResp voice.PushToTalkResp
|
||||
if err := json.Unmarshal(resp.Result, &pttResp); err != nil {
|
||||
http.Error(w, err.Error(), 500)
|
||||
writeProblem(w, r, http.StatusBadGateway, problemVoiceResponse,
|
||||
"voice response failed", fmt.Errorf("decode voice response: %w", err))
|
||||
return
|
||||
}
|
||||
w.Header().Set("Content-Type", "audio/l16;rate=16000;channels=1")
|
||||
|
||||
Reference in New Issue
Block a user