diff --git a/cmd/server/main.go b/cmd/server/main.go index 0f3656c6..aea2c305 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -174,6 +174,12 @@ func main() { database.hasResolvedPath = true // detectSchema ran before column was added; fix the flag } + // Ensure observers.inactive column exists (PR #954 filters on it; ingestor migration + // adds it but server may run against DBs ingestor never touched, e.g. e2e fixture). + if err := ensureObserverInactiveColumn(dbPath); err != nil { + log.Printf("[store] warning: could not add observers.inactive column: %v", err) + } + // WaitGroup for background init steps that gate /api/healthz readiness. var initWg sync.WaitGroup diff --git a/cmd/server/neighbor_persist.go b/cmd/server/neighbor_persist.go index 53f83437..feb77efb 100644 --- a/cmd/server/neighbor_persist.go +++ b/cmd/server/neighbor_persist.go @@ -281,6 +281,45 @@ func ensureResolvedPathColumn(dbPath string) error { return nil } +// ensureObserverInactiveColumn adds the inactive column to observers if missing. +// The column was originally added by ingestor migration (cmd/ingestor/db.go:344) to +// support soft-delete via RemoveStaleObservers + filtered reads (PR #954). When the +// server starts against a DB that was never touched by the ingestor (e.g. the e2e +// fixture), the column is missing and read queries that filter on it (GetObservers, +// GetStats) silently fail with "no such column: inactive" — leaving /api/observers +// returning empty. +func ensureObserverInactiveColumn(dbPath string) error { + rw, err := openRW(dbPath) + if err != nil { + return err + } + defer rw.Close() + + rows, err := rw.Query("PRAGMA table_info(observers)") + if err != nil { + return err + } + defer rows.Close() + + for rows.Next() { + var cid int + var colName string + var colType sql.NullString + var notNull, pk int + var dflt sql.NullString + if rows.Scan(&cid, &colName, &colType, ¬Null, &dflt, &pk) == nil && colName == "inactive" { + return nil // already exists + } + } + + _, err = rw.Exec("ALTER TABLE observers ADD COLUMN inactive INTEGER DEFAULT 0") + if err != nil { + return fmt.Errorf("add inactive column: %w", err) + } + log.Println("[store] Added inactive column to observers") + return nil +} + // resolvePathForObs resolves hop prefixes to full pubkeys for an observation. // Returns nil if path is empty. func resolvePathForObs(pathJSON, observerID string, tx *StoreTx, pm *prefixMap, graph *NeighborGraph) []*string {