a73x

.golangci.yml

Ref:   Size: 5.7 KiB   History

# golangci-lint v2 configuration for Eitri.
#
# Two tiers (see docs/architecture.md and the CI workflow):
#   - correctness + boundary linters: BLOCK merge (run via `make lint`)
#   - complexity/style linters:       WARN only at first, promoted to block
#                                     one at a time as the baseline stays clean
#
# depguard here is the fast, in-editor half of the boundary enforcement; the
# transitive-closure half lives in internal/arch/arch_test.go (R1–R6).
version: "2"

run:
  timeout: 5m

linters:
  default: none
  # BLOCK TIER — these are clean today and `make lint` fails CI on any finding.
  # The complexity/style WARN tier (errcheck, revive, gocyclo, funlen, gocritic,
  # misspell, unconvert, nakedret) is run informationally by `make lint-extra`
  # (exit code 0). Their settings live below so they apply when enabled via CLI.
  # Promote a warn linter into this list once its baseline is clean.
  enable:
    # --- correctness ---
    - govet
    - staticcheck # also covers the old gosimple + stylecheck
    - ineffassign
    - unused
    - bodyclose # http bodies in cloudhv.socketClient / imagecache (prod only)
    - rowserrcheck # database/sql in server/store
    - sqlclosecheck # *sql.Rows in store.scanVM / ListVMs
    # --- context discipline (matches the ctx-first-arg convention) ---
    - contextcheck
    - containedctx # forbid context.Context stored in structs; Engine takes ctx per-call
    # --- decoupling / boundaries ---
    - depguard # encodes R1/R2/R4 as import bans (below)
    - ireturn # nudge toward concrete returns; allow at the documented seams

  settings:
    staticcheck:
      # Quietly drop the purely-stylistic quickfix nags (redundant type in decl,
      # embedded-field selector) — keep the correctness checks blocking.
      checks:
        - all
        - -QF1008
        - -QF1011
    gocyclo:
      min-complexity: 20 # reconcile.create is the one legit outlier; excluded below
    funlen:
      lines: 90
      statements: 60
    ireturn:
      allow:
        - error
        - empty
        - stdlib
        # ssh.Signer is the idiomatic return across the x/crypto/ssh API (keys and
        # CAs ARE signers); returning the concrete key type would be worse. A
        # documented seam, per this tier's policy.
        - golang.org/x/crypto/ssh.Signer
        # ssh.PublicKey is likewise the idiomatic x/crypto/ssh type for a fetched
        # CA / host key; the concrete key types are unexported. Same seam.
        - golang.org/x/crypto/ssh.PublicKey
    depguard:
      rules:
        # R1 cross-plane bans apply to production code only; integration tests
        # (e.g. agent/syncclient/client_test.go) legitimately wire up both planes.
        # The transitive-closure check in internal/arch also covers production only.
        server-no-agent: # R1: control plane must not import data plane
          files:
            - "**/internal/server/**"
            - "!**/*_test.go"
          deny:
            - pkg: github.com/a73x/eitri/internal/agent
              desc: control plane (server) must not import data plane (agent)
        server-no-exec: # R2: the server is a control plane and never shells out
          files:
            - "**/internal/server/**"
          deny:
            - pkg: os/exec
              desc: the server expresses desired state and must never shell out
        agent-no-server: # R1: data plane must not import control plane
          files:
            - "**/internal/agent/**"
            - "!**/*_test.go"
          deny:
            - pkg: github.com/a73x/eitri/internal/server
              desc: data plane (agent) must not import control plane (server)
        no-protojson: # R15: protobuf names are not a contract
          files:
            - "$all"
          deny:
            - pkg: google.golang.org/protobuf/encoding/protojson
              desc: protobuf serialises field numbers, not names; renaming a wire message must stay free — serialise through internal/server/api/types
        domain-no-transport: # R4: pure domain stays serialization-agnostic
          files:
            - "**/internal/agent/state/**"
            - "**/internal/agent/seed/**"
            - "**/internal/agent/ipalloc/**"
            - "**/internal/server/registry/**"
          deny:
            - pkg: net/http
              desc: domain logic must not depend on the HTTP stack
            - pkg: github.com/quic-go/quic-go
              desc: domain logic must not depend on QUIC
            - pkg: github.com/a73x/eitri/internal/transport
              desc: domain logic must not depend on the transport layer

  exclusions:
    # Auto-exclude files carrying a "DO NOT EDIT" generated header (the .pb.go).
    generated: lax
    rules:
      # reconcile.create is an intentionally linear, well-commented state
      # machine: every step is a create side effect with its own failure path.
      - path: internal/agent/reconcile/reconcile.go
        linters:
          - funlen
          - gocyclo
        source: "func \\(e \\*Engine\\) create"
      # Generated protobuf — belt-and-suspenders alongside `generated: lax`.
      - path: 'internal/pb/.*\.pb\.go'
        linters:
          - govet
          - gocritic
          - revive
          - unused
          - funlen
          - gocyclo
          - staticcheck
      # Tests: fakes and table tests run long; ctx-in-struct is fine in fixtures;
      # an unclosed httptest response body in a test leaks nothing meaningful.
      - path: _test\.go
        linters:
          - funlen
          - containedctx
          - bodyclose
      # contextcheck adds value in the core libraries; the CLI entrypoints use
      # context.Background in shutdown paths by design.
      - path: (^|/)cmd/
        linters:
          - contextcheck