From f923b1ef544fd764f49dd063764f028645fadb89 Mon Sep 17 00:00:00 2001 From: drjones Date: Sun, 5 Jul 2026 07:06:59 +0000 Subject: [PATCH] fix: 6 bugs (timeline path value, seer SSE cursor, erasure checksum, subtle compare, build status code, dead WorkerName branch) --- internal/api/handlers/forge.go | 2 +- internal/api/handlers/seer.go | 14 ++++++++++-- internal/court/seer.go | 2 +- internal/erasure/service.go | 8 +++---- internal/fleet/atlas.go | 41 ++++++++++++++++++++++++++++++++++ internal/fleet/hub.go | 10 ++------- 6 files changed, 61 insertions(+), 16 deletions(-) diff --git a/internal/api/handlers/forge.go b/internal/api/handlers/forge.go index 1a03230..dee821d 100644 --- a/internal/api/handlers/forge.go +++ b/internal/api/handlers/forge.go @@ -70,7 +70,7 @@ func (h *ForgeHandler) TriggerBuild(w http.ResponseWriter, r *http.Request) { builds, err := h.Pipeline.BuildAll(req.Public) if err != nil { - auth.JSON(w, http.StatusOK, map[string]any{ + auth.JSON(w, http.StatusInternalServerError, map[string]any{ "ok": false, "message": err.Error(), }) diff --git a/internal/api/handlers/seer.go b/internal/api/handlers/seer.go index 2c225b7..030866d 100644 --- a/internal/api/handlers/seer.go +++ b/internal/api/handlers/seer.go @@ -36,10 +36,15 @@ func (h *SeerHandler) Stream(w http.ResponseWriter, r *http.Request) { w.Header().Set("Cache-Control", "no-cache") w.Header().Set("Connection", "keep-alive") + // Send last 20 events as backlog, track the most recent timestamp as cursor. + var cursor string events, _ := h.Store.ListSeerEvents(20) for _, ev := range events { payload, _ := json.Marshal(ev) fmt.Fprintf(w, "data: %s\n\n", payload) + if ev.CreatedAt > cursor { + cursor = ev.CreatedAt + } } flusher.Flush() @@ -51,13 +56,18 @@ func (h *SeerHandler) Stream(w http.ResponseWriter, r *http.Request) { case <-r.Context().Done(): return case <-ticker.C: - events, err := h.Store.ListSeerEvents(5) + newEvents, err := h.Store.ListSeerEventsSince(cursor, 50) if err != nil { continue } - for _, ev := range events { + for _, ev := range newEvents { payload, _ := json.Marshal(ev) fmt.Fprintf(w, "data: %s\n\n", payload) + if ev.CreatedAt > cursor { + cursor = ev.CreatedAt + } + } + if len(newEvents) > 0 { flusher.Flush() } } diff --git a/internal/court/seer.go b/internal/court/seer.go index b20fa9d..5913fce 100644 --- a/internal/court/seer.go +++ b/internal/court/seer.go @@ -98,7 +98,7 @@ func (h *SeerHub) fetchEvents(ctx context.Context, since string) ([]SeerEvent, e // TimelineHandler serves LOTL timeline merged with court events for a host. func TimelineHandler(c *Court) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - hostID := r.PathValue("host_id") + hostID := r.PathValue("id") if hostID == "" { http.Error(w, "host_id required", http.StatusBadRequest) return diff --git a/internal/erasure/service.go b/internal/erasure/service.go index 4f8166d..887395b 100644 --- a/internal/erasure/service.go +++ b/internal/erasure/service.go @@ -176,12 +176,12 @@ func (s *Service) Reconstruct(ctx context.Context, bundleID string, indices []in for i := 0; i < bundle.DataShards; i++ { out = append(out, shards[i]...) } - // Trim padding — find actual length via checksum match - sum := sha256.Sum256(out) + trimmed := trimNullPadding(out) + sum := sha256.Sum256(trimmed) if hex.EncodeToString(sum[:]) != bundle.Checksum { - // Return best-effort; caller validates + return nil, fmt.Errorf("reconstructed data checksum mismatch: bundle may be corrupt") } - return trimNullPadding(out), nil + return trimmed, nil } func trimNullPadding(b []byte) []byte { diff --git a/internal/fleet/atlas.go b/internal/fleet/atlas.go index da2a5e9..0eed049 100644 --- a/internal/fleet/atlas.go +++ b/internal/fleet/atlas.go @@ -51,6 +51,47 @@ type SeerEvent struct { CreatedAt string `json:"created_at"` } +// ListSeerEventsSince returns events newer than the given SQLite timestamp string (exclusive). +func (s *Store) ListSeerEventsSince(since string, limit int) ([]SeerEvent, error) { + if limit <= 0 { + limit = 50 + } + var rows interface { + Close() error + Next() bool + Scan(dest ...any) error + Err() error + } + var err error + if since == "" { + rows, err = s.db.Query(` + SELECT id, host_id, event_type, payload_json, created_at + FROM seer_events ORDER BY created_at ASC LIMIT ?`, limit) + } else { + rows, err = s.db.Query(` + SELECT id, host_id, event_type, payload_json, created_at + FROM seer_events WHERE created_at > ? ORDER BY created_at ASC LIMIT ?`, since, limit) + } + if err != nil { + return nil, err + } + defer rows.Close() + + var events []SeerEvent + for rows.Next() { + var e SeerEvent + var hostID sql.NullString + if err := rows.Scan(&e.ID, &hostID, &e.EventType, &e.PayloadJSON, &e.CreatedAt); err != nil { + return nil, err + } + if hostID.Valid { + e.HostID = hostID.String + } + events = append(events, e) + } + return events, rows.Err() +} + func nullString(s string) sql.NullString { if s == "" { return sql.NullString{} diff --git a/internal/fleet/hub.go b/internal/fleet/hub.go index 3676f44..dfb587c 100644 --- a/internal/fleet/hub.go +++ b/internal/fleet/hub.go @@ -1,6 +1,7 @@ package fleet import ( + "crypto/subtle" "encoding/json" "log" "net/http" @@ -329,14 +330,7 @@ func (h *Hub) HandleBeacon(store *Store) http.HandlerFunc { } func constantTimeEqual(a, b string) bool { - if len(a) != len(b) { - return false - } - var v byte - for i := 0; i < len(a); i++ { - v |= a[i] ^ b[i] - } - return v == 0 + return subtle.ConstantTimeCompare([]byte(a), []byte(b)) == 1 } func coalesce(values ...string) string {