1 Commits
Author SHA1 Message Date
clawbot e683ac3b61 fix(backend): report ingest correctness — propagate storage failure, 413 on oversize, global body cap (closes #23)
check / check (push) Failing after 34s
A buffer failure on POST /api/v1/reports now returns 500 instead of a
false `ok`, so clients can retry. An over-limit body returns 413
(errors.As on `*http.MaxBytesError`); malformed JSON stays 400. A
MaxBodyBytes middleware (1 MiB) caps every route, rejecting an
oversized Content-Length up front and capping the read otherwise; a
route group can only lower that limit. The raw geo blob is no longer
logged, only its length; client_id, timestamp and decode error text
are length-bounded before logging. A decodeJSON handler helper is
added. Panic recovery routes the stack through slog as structured
JSON. Storage failure uses 500: a full buffer or write error is
server-side and retryable.

Model: opus-5-5
2026-09-28 17:27:43 +00:00
+10 -7
View File
@@ -163,7 +163,10 @@ func TestHandleReportLogsClientIDCutToBound(t *testing.T) {
h.HandleReport().ServeHTTP(rec, req) h.HandleReport().ServeHTTP(rec, req)
var logged map[string]any var logged struct {
ClientID string `json:"client_id"`
Timestamp string `json:"timestamp"`
}
err := json.Unmarshal(logbuf.Bytes(), &logged) err := json.Unmarshal(logbuf.Bytes(), &logged)
if err != nil { if err != nil {
@@ -172,14 +175,14 @@ func TestHandleReportLogsClientIDCutToBound(t *testing.T) {
want := long[:handlers.MaxLoggedFieldBytes] want := long[:handlers.MaxLoggedFieldBytes]
if logged["client_id"] != want { if logged.ClientID != want {
t.Fatalf("logged client_id not cut to %d bytes: %q", t.Fatalf("logged client_id is %d bytes, want %d",
handlers.MaxLoggedFieldBytes, logged["client_id"]) len(logged.ClientID), len(want))
} }
if logged["timestamp"] != want { if logged.Timestamp != want {
t.Fatalf("logged timestamp not cut to %d bytes: %q", t.Fatalf("logged timestamp is %d bytes, want %d",
handlers.MaxLoggedFieldBytes, logged["timestamp"]) len(logged.Timestamp), len(want))
} }
} }