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:
2026-08-13 02:50:59 +04:00
parent da9114b623
commit 35c6ff5a71
67 changed files with 3174 additions and 477 deletions
+19 -11
View File
@@ -4,6 +4,7 @@ import (
"crypto/subtle"
"encoding/json"
"errors"
"fmt"
"io"
"log"
"net/http"
@@ -51,34 +52,41 @@ type ambientResp struct {
// never registered, so it is treated as a hard failure here too.
func handleAmbient(w http.ResponseWriter, r *http.Request, core ipc.CoreAPI, token string) {
if r.Method != http.MethodPost {
http.Error(w, "POST only", http.StatusMethodNotAllowed)
writeProblem(w, r, http.StatusMethodNotAllowed, problemMethodNotAllowed,
"POST only", nil)
return
}
if token == "" {
http.Error(w, "ambient ingest disabled (no -ambient-token)", http.StatusServiceUnavailable)
writeProblem(w, r, http.StatusServiceUnavailable, problemIntegrationOff,
"ambient ingest disabled (no -ambient-token)", nil)
return
}
if !ambientAuthorized(r, token) {
http.Error(w, "unauthorized", http.StatusUnauthorized)
writeProblem(w, r, http.StatusUnauthorized, problemUnauthorized,
"unauthorized", nil)
return
}
if core == nil {
http.Error(w, "ambient ingest disabled (no -core)", http.StatusServiceUnavailable)
writeProblem(w, r, http.StatusServiceUnavailable, problemCoreUnavailable,
"ambient ingest disabled (no -core)", nil)
return
}
var n calendar.Notification
body, err := io.ReadAll(io.LimitReader(r.Body, ambientMaxBody))
if err != nil {
http.Error(w, "read failed", http.StatusBadRequest)
writeProblem(w, r, http.StatusBadRequest, problemInvalidRequest,
"read failed", fmt.Errorf("read ambient request: %w", err))
return
}
if err := json.Unmarshal(body, &n); err != nil {
http.Error(w, "bad json", http.StatusBadRequest)
writeProblem(w, r, http.StatusBadRequest, problemInvalidRequest,
"bad json", fmt.Errorf("decode ambient request: %w", err))
return
}
if n.Posted.IsZero() {
writeAmbient(w, http.StatusBadRequest, ambientResp{Reason: "posted_at is required"})
writeProblem(w, r, http.StatusBadRequest, problemInvalidRequest,
"posted_at is required", nil)
return
}
@@ -99,8 +107,8 @@ func handleAmbient(w http.ResponseWriter, r *http.Request, core ipc.CoreAPI, tok
writeAmbient(w, http.StatusOK, ambientResp{Stored: false, Key: key, Reason: "unchanged"})
return
} else if err != nil && !errors.Is(err, ipc.ErrNoFact) {
log.Printf("ambient: read %s: %v", key, err)
http.Error(w, "read failed", http.StatusBadGateway)
writeProblem(w, r, http.StatusBadGateway, problemCoreReadFailed,
"read failed", fmt.Errorf("read ambient fact %q: %w", key, err))
return
}
@@ -116,8 +124,8 @@ func handleAmbient(w http.ResponseWriter, r *http.Request, core ipc.CoreAPI, tok
Source: calendar.SourceAmbient,
Confidence: calendar.AmbientConfidence,
}); err != nil {
log.Printf("ambient: write %s: %v", key, err)
http.Error(w, "write failed", http.StatusBadGateway)
writeProblem(w, r, http.StatusBadGateway, problemCoreWriteFailed,
"write failed", fmt.Errorf("write ambient fact %q: %w", key, err))
return
}
log.Printf("ambient: %s=%s (%s, pkg=%s)", key, val, calendar.SourceAmbient, n.Package)