lint: drive baseline to zero, drop only-new-issues gate

Cleanup pass over the repo so CI can enforce lint going forward
without the only-new-issues escape hatch:

* gofumpt -w across the tree (31 hits, all formatting)
* misspell --fix (25 hits, US-locale spelling) — but reverted on
  api.JobCancelled = "cancelled" since that literal is the wire +
  DB CHECK constraint value, plus matched the case in store/fleet.go
  back to "cancelled" and added //nolint:misspell on both for the
  next time someone reaches for the auto-fix
* Wrap every `defer rows.Close()` / `defer stmt.Close()` /
  `defer res.Body.Close()` in `defer func() { _ = .Close() }()`
  to satisfy errcheck without losing the close itself
* websocket.Dial callers (1 prod, 4 tests) now capture + close the
  upgrade response Body — coder/websocket can return res with a nil
  Body on success, so the test deferred-closes guard against that
* Annotate the two genuine-by-design nilerr cases with //nolint
  comments explaining why nil-on-error is the contract (cookie
  missing = no session; ctx cancelled mid-backoff = clean shutdown)
* Add brief godoc on the 10 exported const groups + types that
  revive flagged (api.HostOS/HostArch/JobKind/JobStatus/LogStream/
  ErrorCode, restic.EventKind, store.Role, web.FS)
* Drop the unused (*Server).userByID method
* Inline the unparam baseView(active) — every UI page is under
  the dashboard primary nav today

Result: `golangci-lint run ./...` reports 0 issues. CI lint job
no longer needs only-new-issues: true; X-06 follow-up entry in
tasks.md removed.
This commit is contained in:
2026-05-03 16:15:17 +01:00
parent 41c3ec7c6f
commit b6f8de1dcc
54 changed files with 317 additions and 260 deletions
+1 -1
View File
@@ -26,7 +26,7 @@ func (s *Store) AppendAudit(ctx context.Context, e AuditEntry) error {
}
// nullable returns nil for nil/empty *string so SQLite stores NULL.
// SQLite's driver treats Go nil as NULL but treats *string("") as ''.
// SQLite's driver treats Go nil as NULL but treats *string("") as .
// We want NULL semantics for "absent."
func nullable(p *string) any {
if p == nil || *p == "" {
-1
View File
@@ -172,4 +172,3 @@ func (s *Store) PurgeExpiredEnrollmentTokens(ctx context.Context) (int64, error)
n, _ := res.RowsAffected()
return n, nil
}
+2 -2
View File
@@ -57,7 +57,7 @@ func (s *Store) FleetSummary(ctx context.Context) (FleetSummary, error) {
if err != nil {
return FleetSummary{}, fmt.Errorf("store: fleet summary jobs: %w", err)
}
defer rows.Close()
defer func() { _ = rows.Close() }()
for rows.Next() {
var status string
var n int
@@ -70,7 +70,7 @@ func (s *Store) FleetSummary(ctx context.Context) (FleetSummary, error) {
fs.JobsLast24hSucceeded = n
case "failed":
fs.JobsLast24hFailed = n
case "cancelled":
case "cancelled": //nolint:misspell // matches the DB CHECK constraint and api.JobCancelled wire value
fs.JobsLast24hCancelled = n
}
}
+6 -6
View File
@@ -121,7 +121,7 @@ func (s *Store) ListHosts(ctx context.Context) ([]Host, error) {
if err != nil {
return nil, fmt.Errorf("store: list hosts: %w", err)
}
defer rows.Close()
defer func() { _ = rows.Close() }()
var out []Host
for rows.Next() {
h, err := scanHostRow(rows)
@@ -150,11 +150,11 @@ func scanHost(row *sql.Row) (*Host, error) {
func scanHostRow(s hostScanner) (*Host, error) {
var h Host
var (
lastSeen, lastBackupAt sql.NullString
repoID, currentJob, lastBkSt sql.NullString
enrolled string
tags string
bwUp, bwDown sql.NullInt64
lastSeen, lastBackupAt sql.NullString
repoID, currentJob, lastBkSt sql.NullString
enrolled string
tags string
bwUp, bwDown sql.NullInt64
)
err := s.Scan(&h.ID, &h.Name, &h.OS, &h.Arch,
&h.AgentVersion, &h.ResticVersion, &h.ProtocolVersion,
+10 -10
View File
@@ -118,7 +118,7 @@ func (s *Store) ListJobLogs(ctx context.Context, jobID string, afterSeq int64, l
if err != nil {
return nil, fmt.Errorf("store: list job logs: %w", err)
}
defer rows.Close()
defer func() { _ = rows.Close() }()
var out []JobLogLine
for rows.Next() {
var l JobLogLine
@@ -143,15 +143,15 @@ func (s *Store) GetJob(ctx context.Context, id string) (*Job, error) {
started_at, finished_at, exit_code, stats, error, created_at
FROM jobs WHERE id = ?`, id)
var (
j Job
schedID sql.NullString
actorID sql.NullString
startedAt sql.NullString
finishedAt sql.NullString
exitCode sql.NullInt64
stats sql.NullString
errMsg sql.NullString
createdAt string
j Job
schedID sql.NullString
actorID sql.NullString
startedAt sql.NullString
finishedAt sql.NullString
exitCode sql.NullInt64
stats sql.NullString
errMsg sql.NullString
createdAt string
)
if err := row.Scan(&j.ID, &j.HostID, &j.Kind, &j.Status, &schedID,
&j.ActorKind, &actorID, &startedAt, &finishedAt,
+1 -1
View File
@@ -31,7 +31,7 @@ func (st *Store) GetRepoMaintenance(ctx context.Context, hostID string) (*HostRe
check_cron, check_enabled, check_subset_pct
FROM host_repo_maintenance WHERE host_id = ?`, hostID)
var (
m HostRepoMaintenance
m HostRepoMaintenance
forgetEnabled, pruneEnabled, checkEnabled int
)
err := row.Scan(&m.HostID,
+3 -3
View File
@@ -15,9 +15,9 @@ var migrationsFS embed.FS
// migration is one ordered SQL file from migrations/.
type migration struct {
version int // parsed from filename prefix (0001, 0002, …)
name string // full filename, for error messages
sql string
version int // parsed from filename prefix (0001, 0002, …)
name string // full filename, for error messages
sql string
}
// loadMigrations reads every migrations/*.sql file in lexical order
+1 -1
View File
@@ -52,7 +52,7 @@ func (st *Store) DuePendingRuns(ctx context.Context, now time.Time, limit int) (
if err != nil {
return nil, fmt.Errorf("store: due pending runs: %w", err)
}
defer rows.Close()
defer func() { _ = rows.Close() }()
out := []PendingRun{}
for rows.Next() {
var p PendingRun
+3 -3
View File
@@ -144,7 +144,7 @@ func (st *Store) ListSchedulesByHost(ctx context.Context, hostID string) ([]Sche
if err != nil {
return nil, fmt.Errorf("store: list schedules: %w", err)
}
defer rows.Close()
defer func() { _ = rows.Close() }()
out := []Schedule{}
for rows.Next() {
s, err := scanScheduleRow(rows)
@@ -247,7 +247,7 @@ func (st *Store) scheduleGroupIDs(ctx context.Context, scheduleID string) ([]str
if err != nil {
return nil, fmt.Errorf("store: read schedule junction: %w", err)
}
defer rows.Close()
defer func() { _ = rows.Close() }()
out := []string{}
for rows.Next() {
var id string
@@ -269,7 +269,7 @@ func (st *Store) SchedulesUsingGroup(ctx context.Context, groupID string) ([]str
if err != nil {
return nil, fmt.Errorf("store: schedules using group: %w", err)
}
defer rows.Close()
defer func() { _ = rows.Close() }()
out := []string{}
for rows.Next() {
var id string
+2 -2
View File
@@ -51,7 +51,7 @@ func (s *Store) ReplaceHostSnapshots(ctx context.Context, hostID string, snaps [
if err != nil {
return fmt.Errorf("store: prepare snapshot insert: %w", err)
}
defer stmt.Close()
defer func() { _ = stmt.Close() }()
refreshed := when.UTC().Format(time.RFC3339Nano)
for _, snap := range snaps {
@@ -92,7 +92,7 @@ func (s *Store) ListSnapshotsByHost(ctx context.Context, hostID string) ([]Snaps
if err != nil {
return nil, fmt.Errorf("store: list snapshots: %w", err)
}
defer rows.Close()
defer func() { _ = rows.Close() }()
var out []Snapshot
for rows.Next() {
+15 -13
View File
@@ -30,20 +30,20 @@ func TestReplaceHostSnapshotsRoundTrip(t *testing.T) {
now := time.Now().UTC().Truncate(time.Second)
in := []Snapshot{
{
ID: "deadbeef" + "00000000000000000000000000000000000000000000000000000000",
ShortID: "deadbeef",
Time: now.Add(-2 * time.Hour),
Hostname: "snap-host",
Paths: []string{"/etc", "/home"},
Tags: []string{"daily"},
ID: "deadbeef" + "00000000000000000000000000000000000000000000000000000000",
ShortID: "deadbeef",
Time: now.Add(-2 * time.Hour),
Hostname: "snap-host",
Paths: []string{"/etc", "/home"},
Tags: []string{"daily"},
SizeBytes: 4096, FileCount: 12,
},
{
ID: "cafef00d" + "00000000000000000000000000000000000000000000000000000000",
ShortID: "cafef00d",
Time: now.Add(-1 * time.Hour),
Hostname: "snap-host",
Paths: []string{"/etc"},
ID: "cafef00d" + "00000000000000000000000000000000000000000000000000000000",
ShortID: "cafef00d",
Time: now.Add(-1 * time.Hour),
Hostname: "snap-host",
Paths: []string{"/etc"},
SizeBytes: 8192, FileCount: 24,
},
}
@@ -129,9 +129,11 @@ func TestReplaceHostSnapshotsEmpty(t *testing.T) {
// First a non-empty replace.
if err := s.ReplaceHostSnapshots(ctx, hostID, []Snapshot{
{ID: "1111111111111111111111111111111111111111111111111111111111111111",
{
ID: "1111111111111111111111111111111111111111111111111111111111111111",
ShortID: "11111111", Time: time.Now().UTC(), Hostname: "snap-host",
Paths: []string{"/x"}},
Paths: []string{"/x"},
},
}, time.Now().UTC()); err != nil {
t.Fatalf("replace 1: %v", err)
}
+5 -5
View File
@@ -183,7 +183,7 @@ func (st *Store) ListSourceGroupsByHost(ctx context.Context, hostID string) ([]S
if err != nil {
return nil, fmt.Errorf("store: list source groups: %w", err)
}
defer rows.Close()
defer func() { _ = rows.Close() }()
out := []SourceGroup{}
for rows.Next() {
g, err := scanSourceGroupRow(rows)
@@ -220,10 +220,10 @@ type sourceGroupScanner interface {
func scanSourceGroupRow(s sourceGroupScanner) (*SourceGroup, error) {
var (
out SourceGroup
includes, excludes, retention string
conflict sql.NullString
createdAt, updatedAt string
out SourceGroup
includes, excludes, retention string
conflict sql.NullString
createdAt, updatedAt string
)
err := s.Scan(&out.ID, &out.HostID, &out.Name,
&includes, &excludes, &retention,
+1 -1
View File
@@ -177,7 +177,7 @@ func TestPendingRunQueue(t *testing.T) {
now := time.Now().UTC()
if err := s.EnqueuePendingRun(ctx, &PendingRun{
ID: "01HPEND00000000000000001",
ID: "01HPEND00000000000000001",
ScheduleID: schedID, SourceGroupID: gid, HostID: hostID,
NextAttemptAt: now.Add(-time.Second), // already due
ScheduledAt: now.Add(-time.Minute),
+4 -2
View File
@@ -34,10 +34,12 @@ func TestOpenAppliesMigrations(t *testing.T) {
}
// Spot-check a few tables exist with expected columns.
tables := []string{"users", "sessions", "hosts", "repos",
tables := []string{
"users", "sessions", "hosts", "repos",
"credentials", "schedules", "jobs", "job_logs",
"snapshots", "alerts", "audit_log",
"enrollment_tokens", "host_schedule_version"}
"enrollment_tokens", "host_schedule_version",
}
for _, tbl := range tables {
row := s.DB().QueryRow(
`SELECT name FROM sqlite_master WHERE type='table' AND name = ?`, tbl)
+15 -14
View File
@@ -20,6 +20,7 @@ type User struct {
// Role enumerates the access tiers from spec.md §7.2.
type Role string
// Defined Role values, in descending order of privilege.
const (
RoleAdmin Role = "admin"
RoleOperator Role = "operator"
@@ -73,12 +74,12 @@ type Host struct {
// only. forget/prune/check are repo-level cadences on
// HostRepoMaintenance, not schedule kinds.
type Schedule struct {
ID string
HostID string
CronExpr string
Enabled bool
CreatedAt time.Time
UpdatedAt time.Time
ID string
HostID string
CronExpr string
Enabled bool
CreatedAt time.Time
UpdatedAt time.Time
// SourceGroupIDs is populated by ListSchedulesByHost (joins
// schedule_source_groups) and accepted on Create / Update so the
// caller passes the desired junction state in one shape.
@@ -160,14 +161,14 @@ type HostRepoMaintenance struct {
// PendingRun queues a missed cron tick (agent was offline) for the
// server-side retry ticker to dispatch later.
type PendingRun struct {
ID string
ScheduleID string
SourceGroupID string
HostID string
Attempt int
NextAttemptAt time.Time
ScheduledAt time.Time // original cron tick — forensic / audit
LastError string
ID string
ScheduleID string
SourceGroupID string
HostID string
Attempt int
NextAttemptAt time.Time
ScheduledAt time.Time // original cron tick — forensic / audit
LastError string
}
// EnrollmentToken is the issuer's view of a one-time token.