# AGENTS.go.md — Go Service Conventions Applies to: `ai-compliance-sdk/`. ## Layered architecture (Gin) Follows [Standard Go Project Layout](https://github.com/golang-standards/project-layout) + hexagonal/clean-arch. ``` ai-compliance-sdk/ ├── cmd/server/main.go # Thin: parse flags → app.New → app.Run. <50 LOC. ├── internal/ │ ├── app/ # Wiring: config + DI graph + lifecycle. │ ├── domain/ # Pure types, interfaces, errors. No I/O imports. │ │ └── / │ ├── service/ # Business logic. Depends on domain interfaces only. │ │ └── / │ ├── repository/postgres/ # Concrete repo implementations. │ │ └── / │ ├── transport/http/ # Gin handlers. Thin. One handler per file group. │ │ ├── handler// │ │ ├── middleware/ │ │ └── router.go │ └── platform/ # DB pool, logger, config, tracing. └── pkg/ # Importable by other repos. Empty unless needed. ``` **Dependency direction:** `transport → service → domain ← repository`. `domain` imports nothing from siblings. ## Handlers - One handler = one Gin function. ≤40 LOC. - Bind → call service → map domain error to HTTP via `httperr.Write(c, err)` → respond. - Return early on errors. No business logic, no SQL. ```go func (h *IACEHandler) Create(c *gin.Context) { var req CreateIACERequest if err := c.ShouldBindJSON(&req); err != nil { httperr.Write(c, httperr.BadRequest(err)) return } out, err := h.svc.Create(c.Request.Context(), req.ToInput()) if err != nil { httperr.Write(c, err) return } c.JSON(http.StatusCreated, out) } ``` ## Services - Struct + constructor + interface methods. No package-level state. - Take `context.Context` as first arg always. Propagate to repos. - Return `(value, error)`. Wrap with `fmt.Errorf("create iace: %w", err)`. - Domain errors implemented as sentinel vars or typed errors; matched with `errors.Is` / `errors.As`. ## Repositories - Interface lives in `domain//repository.go`. Implementation in `repository/postgres//`. - One file per query group; no file >500 LOC. - Use `pgx`/`sqlc` over hand-rolled string SQL when feasible. No ORM globals. - All queries take `ctx`. No background goroutines without explicit lifecycle. ## Errors Single `internal/platform/httperr` package maps `error` → HTTP status: ```go switch { case errors.Is(err, domain.ErrNotFound): return 404 case errors.Is(err, domain.ErrConflict): return 409 case errors.As(err, &validationErr): return 422 default: return 500 } ``` Never `panic` in request handling. `recover` middleware logs and returns 500. ## Tests - Co-located `*_test.go`. - **Table-driven** tests for service logic; use `t.Run(tt.name, ...)`. - Handlers tested with `httptest.NewRecorder`. - Repos tested with `testcontainers-go` (or the existing compose Postgres) — never mocks at the SQL boundary. - Coverage target: 80% on `service/`. CI fails on regression. ```go func TestIACEService_Create(t *testing.T) { tests := []struct { name string input service.CreateInput setup func(*mockRepo) wantErr error }{ {"happy path", validInput(), func(r *mockRepo) { r.createReturns(nil) }, nil}, {"conflict", validInput(), func(r *mockRepo) { r.createReturns(domain.ErrConflict) }, domain.ErrConflict}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { /* ... */ }) } } ``` ## Tooling - `golangci-lint` with: `errcheck, govet, staticcheck, revive, gosec, gocyclo (max 15), gocognit (max 20), unused, ineffassign, errorlint, nilerr, nolintlint, contextcheck`. - `gofumpt` formatting. - `go vet ./...` clean. - `go mod tidy` clean — no unused deps. ## Concurrency - Goroutines must have a clear lifecycle owner (struct method that started them must stop them). - Pass `ctx` everywhere. Cancellation respected. - No global mutexes for request data. Use per-request context. ## What you may NOT do - Touch DB schema/migrations. - Add a new top-level package directly under `internal/` without architectural review. - `import "C"`, unsafe, reflection-heavy code. - Use `init()` for non-trivial setup. Wire it in `internal/app`. - Create a file >500 lines. - Change a public route's contract without updating consumers.