Skip to content

feat: scaffold Fiber API with probes, metrics, and middleware stubs#7

Merged
haydercyber merged 3 commits into
mainfrom
feat/scaffold-api
May 28, 2026
Merged

feat: scaffold Fiber API with probes, metrics, and middleware stubs#7
haydercyber merged 3 commits into
mainfrom
feat/scaffold-api

Conversation

@haydercyber

Copy link
Copy Markdown
Contributor

Summary

Close #1 — bring api up to "compiles, starts, answers probes." No business logic yet; that arrives with #2 (storage), #3 (runtime), #4 / #5 (agent), and #6 (workflow).

Layout

cmd/api/                fiber app + env-driven config
internal/handlers/      thin HTTP layer — probes + /metrics
internal/middleware/    requestID, logger, recover; auth/RBAC/audit stubs
internal/observability/ slog JSON logger
internal/services/      placeholder for business logic
pkg/storage/            placeholder — postgres lands in #2
pkg/runtime/            placeholder — redis lands in #3
pkg/workflow/           placeholder — approval engine lands in #6

pkg/* is the import surface that worker reuses (per REFACTOR_PLAN.md §4). internal/* is closed.

Middleware order (fixed by this PR so downstream issues don't refactor route registration)

RequestID → Logger → Recover → /api/v1 group → Auth → RBAC → Audit

Auth / RBAC / Audit are explicit stubs today. The audit stub logs "TODO: emit immutable audit event with correlation_id" on every authenticated request so missing coverage stays visible during development.

Endpoints

Endpoint Behavior
GET /healthz Unconditional 200 once the process answers HTTP. Liveness probes failing trigger pod restarts, which is the wrong response to a dependency outage.
GET /readyz Gated by atomic.Bool. Defaults to ready; flips to 503 when SetReady(false). Real dep checks register here as #2 / #3 land.
GET /metrics Prometheus exposition via expfmt.Encoder writing directly to the response writer — no net/http↔Fiber adapter.

Tests (all pass under -race)

  • Healthz unconditional 200 even when SetReady(false)
  • Readyz returns 503 when not ready and 200 when ready
  • /metrics produces real go_* and process_* series
  • RequestID generates when absent, echoes inbound, rejects overlong (>128 char) inbound headers
  • Auth stub sets "anonymous" actor on the context

Local verification

$ go build ./... && go vet ./... && go test -race -count=1 ./...
ok  github.com/secrets-bridge/api/internal/handlers   1.024s
ok  github.com/secrets-bridge/api/internal/middleware 1.018s

$ API_ADDR=:18080 ./api &
$ curl -s :18080/healthz  →  {"status":"ok"}            (200)
$ curl -s :18080/readyz   →  {"status":"ready"}         (200)
$ curl -s :18080/metrics  →  # HELP go_gc_duration...   (200, exposition format)

Decisions worth flagging in review

Decision Why
Go 1.25, not 1.24 gofiber/fiber/v3 v3.3.0 requires Go 1.25. core will be bumped to match when it next changes.
Fiber v3 (not v2) New module — no upgrade tax later. v3 dropped DisableStartupMessage; banner is just stdout noise.
expfmt.Encoder for /metrics Cleaner than bridging net/http to Fiber. Removes an entire dependency surface.
Probes are unauthenticated Kubelet scrapes without credentials. NetworkPolicy in charts/api (#14) restricts external reachability.
Request ID = 128-bit hex, not UUID v4 Avoids implicit semantic claims the format doesn't carry.
pkg/* exported, internal/* closed pkg/storage, pkg/runtime, pkg/workflow are the surface worker will import.

Hard rules respected (BRD / CLAUDE.md)

  • No secret values anywhere in this PR (handlers don't touch them; placeholders document the rule in package comments)
  • Stateless (no in-process state that wouldn't survive a pod restart)
  • Audit stub already fires on every authenticated request — wiring is correct even though the policy isn't

Test plan

  • CI green on this PR (4 required checks: build / test / lint / tidy)
  • Local: build / vet / test / live smoke test all green
  • Reviewer reads the middleware order in cmd/api/main.go and confirms it matches the contract (RequestID → Logger → Recover → group → Auth → RBAC → Audit)

Closes #1

Bring the api repo up to "compiles, starts, answers probes" per
#1. No business logic yet — that arrives with #2
(storage), #3 (runtime), #4-#5 (agent), and #6 (workflow).

Structure mirrors the layout sibling Go services will adopt:

  cmd/api/                fiber app + env-driven config
  internal/handlers/      thin HTTP layer — probes + /metrics
  internal/middleware/    requestID, logger, recover; auth/RBAC/audit stubs
  internal/observability/ slog JSON logger
  internal/services/      placeholder for business logic
  pkg/storage/            placeholder; reserved for postgres (#2)
  pkg/runtime/            placeholder; reserved for redis (#3)
  pkg/workflow/           placeholder; reserved for approval engine (#6)

The pkg/* split is intentional: those packages are the import surface
that worker reuses (per REFACTOR_PLAN.md §4). internal/* is closed.

Middleware order is fixed in this PR so downstream issues don't have
to refactor route registration:

  RequestID → Logger → Recover → /api/v1 group → Auth → RBAC → Audit

Auth/RBAC/Audit are explicit stubs today. The audit stub logs a
"TODO: emit immutable audit event with correlation_id" line on every
authenticated request so missing coverage stays visible during dev.

Endpoints:
  GET /healthz    unconditional 200 once the process answers HTTP
  GET /readyz     gated by atomic.Bool (default true; SetReady flips)
  GET /metrics    Prometheus exposition via expfmt.Encoder directly

Tests cover: probe semantics (Healthz unconditional, Readyz gated),
/metrics produces real go_* / process_* series, requestID middleware
generates/echoes/rejects-overlong inbound IDs, Auth stub sets the
anonymous actor on the context. All pass under -race.

CI: same 4-job workflow shape as secrets-bridge/core — build (+ vet),
test (-race), golangci-lint, tidy check.

Dockerfile: multi-stage golang:1.25-alpine → distroless/static:nonroot.

Note: Go 1.25 (not 1.24 as written in REFACTOR_PLAN) because
gofiber/fiber/v3 v3.3.0 requires it. core will be bumped to match
when it next changes.
v6 ships golangci-lint v1.x built with Go 1.24, which refuses to lint a
module declaring 'go 1.25'. v7 uses golangci-lint v2.x built with Go
1.25+.
golangci-lint v2 (in CI via golangci-lint-action@v7) flags expfmt.FmtText
under staticcheck SA1019. NewFormat(TypeTextPlain) is the replacement
since prometheus/common v0.49.
@haydercyber
haydercyber merged commit 925e520 into main May 28, 2026
4 checks passed
@haydercyber
haydercyber deleted the feat/scaffold-api branch May 28, 2026 10:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Step 4] Scaffold Fiber API + health/ready/metrics + middleware placeholders

1 participant