fix(canary): replace //nolint pragmas with explicit error handling
The standing "no //nolint anywhere" rule (handoff anti-relitigation set) was honored by Phases 2-4 but inherited template code in core/database.go, middleware/ratelimit.go, and health/handler.go carried 6 pragmas from the original template import. Replacing each with proper error handling rather than silencing the linter: core/database.go NewDatabase ping-failure path: propagates db.Close() error via multi-%w fmt.Errorf rather than discarding it via _ = db.Close(). Pattern matches the existing rollback error wrap on line 102. InTx + InTxWithOptions panic-recovery defer: rollback failure now logs via slog.Error before re-panicking, rather than discarding the error via _ = tx.Rollback(). The original error context is preserved by the panic; the rollback failure is observable. jitteredDuration: switches from math/rand/v2 to crypto/rand + math/big. The function runs once per pool init at startup so the per-call cost (~microseconds) is irrelevant. Eliminates the gosec G404 trigger without a global exclude (handoff anti-relitigation: "don't add new excludes for one-off issues"). Adds the missing maxJitter <= 0 guard that the rand.Int64N call would have panicked on with a base smaller than 7ns. middleware/ratelimit.go writeRateLimitExceeded: json.NewEncoder().Encode error is now logged via slog.Error rather than discarded. slog is already imported and used elsewhere in the file (line 60). health/handler.go writeStatus: same pattern; adds log/slog import. Brings the file in line with the rest of the codebase's slog-everywhere discipline. After: zero //nolint pragmas anywhere in backend Go code (grep -rn "//nolint" returns nothing). golangci-lint run ./... reports 0 issues. All unit tests pass under -race; all integration tests pass under -race -tags=integration with the testcontainer Postgres. Surfaced by the pre-Phase-5 cross-phase alignment audit. Cleared as part of finishing the Phase 0 lint-discipline alignment so Phases 5+ inherit a fully consistent codebase.
This commit is contained in:
parent
fdc4144cbc
commit
13b2604f18
|
|
@ -5,9 +5,11 @@ package core
|
|||
|
||||
import (
|
||||
"context"
|
||||
crand "crypto/rand"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"math/rand/v2"
|
||||
"log/slog"
|
||||
"math/big"
|
||||
"time"
|
||||
|
||||
_ "github.com/jackc/pgx/v5/stdlib"
|
||||
|
|
@ -41,9 +43,15 @@ func NewDatabase(
|
|||
pingCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
if err := db.PingContext(pingCtx); err != nil {
|
||||
_ = db.Close() //nolint:errcheck // cleanup on connection failure
|
||||
return nil, fmt.Errorf("ping database: %w", err)
|
||||
if pingErr := db.PingContext(pingCtx); pingErr != nil {
|
||||
if closeErr := db.Close(); closeErr != nil {
|
||||
return nil, fmt.Errorf(
|
||||
"ping database: %w (close also failed: %w)",
|
||||
pingErr,
|
||||
closeErr,
|
||||
)
|
||||
}
|
||||
return nil, fmt.Errorf("ping database: %w", pingErr)
|
||||
}
|
||||
|
||||
return &Database{DB: db}, nil
|
||||
|
|
@ -91,7 +99,12 @@ func InTx(ctx context.Context, db *sqlx.DB, fn func(tx *sqlx.Tx) error) error {
|
|||
|
||||
defer func() {
|
||||
if p := recover(); p != nil {
|
||||
_ = tx.Rollback() //nolint:errcheck // best-effort rollback on panic
|
||||
if rbErr := tx.Rollback(); rbErr != nil {
|
||||
slog.Error(
|
||||
"rollback failed during panic recovery",
|
||||
"error", rbErr,
|
||||
)
|
||||
}
|
||||
panic(p)
|
||||
}
|
||||
}()
|
||||
|
|
@ -123,7 +136,12 @@ func InTxWithOptions(
|
|||
|
||||
defer func() {
|
||||
if p := recover(); p != nil {
|
||||
_ = tx.Rollback() //nolint:errcheck // best-effort rollback on panic
|
||||
if rbErr := tx.Rollback(); rbErr != nil {
|
||||
slog.Error(
|
||||
"rollback failed during panic recovery",
|
||||
"error", rbErr,
|
||||
)
|
||||
}
|
||||
panic(p)
|
||||
}
|
||||
}()
|
||||
|
|
@ -143,7 +161,13 @@ func InTxWithOptions(
|
|||
}
|
||||
|
||||
func jitteredDuration(base time.Duration) time.Duration {
|
||||
//nolint:gosec // G404: non-security-sensitive jitter for connection pool
|
||||
jitter := time.Duration(rand.Int64N(int64(base / 7)))
|
||||
return base + jitter
|
||||
maxJitter := int64(base / 7)
|
||||
if maxJitter <= 0 {
|
||||
return base
|
||||
}
|
||||
jitter, err := crand.Int(crand.Reader, big.NewInt(maxJitter))
|
||||
if err != nil {
|
||||
return base
|
||||
}
|
||||
return base + time.Duration(jitter.Int64())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ package health
|
|||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
|
@ -174,8 +175,9 @@ func (h *Handler) writeStatus(w http.ResponseWriter, status int, data any) {
|
|||
w.Header().Set("Content-Type", "application/json")
|
||||
w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate")
|
||||
w.WriteHeader(status)
|
||||
//nolint:errcheck // best-effort response
|
||||
_ = json.NewEncoder(w).Encode(data)
|
||||
if err := json.NewEncoder(w).Encode(data); err != nil {
|
||||
slog.Error("failed to encode health response", "error", err)
|
||||
}
|
||||
}
|
||||
|
||||
type StatusResponse struct {
|
||||
|
|
|
|||
|
|
@ -154,8 +154,9 @@ func writeRateLimitExceeded(w http.ResponseWriter, res *redis_rate.Result) {
|
|||
},
|
||||
}
|
||||
|
||||
//nolint:errcheck // best-effort response write
|
||||
_ = json.NewEncoder(w).Encode(response)
|
||||
if err := json.NewEncoder(w).Encode(response); err != nil {
|
||||
slog.Error("failed to encode rate-limit response", "error", err)
|
||||
}
|
||||
}
|
||||
|
||||
type limiterEntry struct {
|
||||
|
|
|
|||
Loading…
Reference in New Issue