Python: add missing 'import enum' to compliance/db/models.py shim. TypeScript: remove duplicate export of useVendorCompliance from vendor-compliance/context.tsx (already exported from ./hooks). Docs: add mandatory pre-push checklist (lint + test + build) to AGENTS.python.md and AGENTS.go.md. [guardrail-change] Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
6.5 KiB
AGENTS.go.md — Go Service Conventions
Applies to: ai-compliance-sdk/.
Layered architecture (Gin)
Follows Standard Go 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.
│ │ └── <aggregate>/
│ ├── service/ # Business logic. Depends on domain interfaces only.
│ │ └── <aggregate>/
│ ├── repository/postgres/ # Concrete repo implementations.
│ │ └── <aggregate>/
│ ├── transport/http/ # Gin handlers. Thin. One handler per file group.
│ │ ├── handler/<aggregate>/
│ │ ├── 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.
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.Contextas first arg always. Propagate to repos. - Return
(value, error). Wrap withfmt.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/<aggregate>/repository.go. Implementation inrepository/postgres/<aggregate>/. - One file per query group; no file >500 LOC.
- Use
pgx/sqlcover 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:
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.
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
Run lint before pushing:
golangci-lint run --timeout 5m ./...
The .golangci.yml at the service root (ai-compliance-sdk/.golangci.yml) enables: errcheck, govet, staticcheck, gosec, gocyclo (≤20), gocritic, revive, goimports, unused, ineffassign. Fix lint violations in new code; legacy violations are tracked but not required to fix immediately.
gofumptformatting.go vet ./...clean.go mod tidyclean — no unused deps.
File splitting pattern
When a Go file exceeds the 500-line hard cap, split it in place — no new packages needed:
- All split files stay in the same package directory with the same
package <name>declaration. - No import changes are needed anywhere because Go packages are directory-scoped.
- Naming:
store_projects.go,store_components.go(noun + underscore + sub-resource). - For handlers:
iace_handler_projects.go,iace_handler_hazards.go, etc. - Before splitting, add a characterization test that pins current behaviour.
Error handling
Domain errors are defined in internal/domain/<aggregate>/errors.go as sentinel vars or typed errors. The mapping from domain error to HTTP status lives exclusively in internal/platform/httperr/httperr.go via errors.Is / errors.As. Handlers call httperr.Write(c, err) — never directly call c.JSON with a status code derived from business logic.
Context propagation
- Always pass
ctx context.Contextas the first parameter in every service and repository method. - Never store a context in a struct field — pass it per call.
- Cancellation must be respected: check
ctx.Err()in loops; propagate to all I/O calls.
Concurrency
- Goroutines must have a clear lifecycle owner (struct method that started them must stop them).
- Pass
ctxeverywhere. Cancellation respected. - No global mutexes for request data. Use per-request context.
Before every push — MANDATORY
Run all steps for ai-compliance-sdk/ before pushing. CI runs the same checks and will fail if you skip this.
cd ai-compliance-sdk
# 1. Vet + lint
go vet ./...
golangci-lint run --timeout 5m ./...
# 2. Tests
go test ./...
# 3. Build
go build ./...
All steps must exit 0. Do not push if any step fails.
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 ininternal/app. - Use
interface{}/anyin new code without an explicit comment justifying it. - Call
log.Fataloutside ofmain.go; panicking in request handling is also forbidden. - Shadow
errwith:=inside anif-block when the outer scope already declareserr— use=or rename. - Create a file >500 lines.
- Change a public route's contract without updating consumers.