Architecture

Layout

cmd/
  api/        composition root: load config, wire dependencies, serve
  migrate/    apply migrations as an explicit deploy step
  forge/      day-2 generator: go run ./cmd/forge add resource <Name>
  dev/        pinned tool bootstrap and verification commands
internal/
  config/         typed, validated environment configuration
  observability/  slog (trace correlation + redaction), OpenTelemetry, Prometheus
  platform/
    database/   pgx pool and transaction helper
    problem/    RFC 9457 problem+json responses
  server/         http.Server, middleware, router, health, graceful drain
  auth/           JWKS/RSA verification, RBAC, OpenAPI authenticator
  modules/        one package per resource (SQL, store, service, handler)
  gen/            generated code (sqlc + oapi-codegen), committed
  testutil/       isolated Postgres databases, Docker or an external test server
api/openapi.yaml  the API contract, source of truth
migrations/       versioned SQL, embedded into the binaries
deployments/      Dockerfile and Docker Compose configuration

forge init removes unselected components and its own installer.

Request flow

Client → middleware → OpenAPI validation and authentication → handler → service authorization and rules → repository → PostgreSQL.

A request passes global middleware, then the OpenAPI validator checks it against api/openapi.yaml and runs the bearer authentication function for protected operations. The handler maps transport types to the service, which enforces authorization before calling the repository. Writes that emit events persist the event to outbox_messages in the same transaction; a relay publishes them at-least-once and, while batches come back full, keeps draining within a tick. A message the publisher rejects records the attempt and error and backs off (up to an hour between tries) without holding up the rest, so delivery order is not guaranteed and consumers must be idempotent.

The default outbox.LogPublisher logs event metadata and treats delivery as successful. Replace it in cmd/api/main.go with a broker or webhook Publisher when the application needs external delivery.

Boundaries

Observability

Every log line and problem response carries the trace ID of the active span, so a log backend can link a line to its trace. Metrics are exposed at /metrics for Prometheus. Spans are exported when FORGE_OTEL_OTLP_ENDPOINT is set; task observe runs a Collector, Tempo, Prometheus and Grafana to receive them.

/metrics, /livez, and /readyz are unauthenticated on the public listener, which is the usual Prometheus and Kubernetes-probe convention; restrict them at the network layer in production. pprof and expvar are kept off the public listener entirely, on a separate token-gated admin server.