feat(datastore): migration runner app.Component (S04 Step 2)

Summary

Step 2 of the database composition-root plan: ships the migration runner as an app.Component so the composition root can register it alongside the rest of LabKit's lifecycle. The runner opens the LabKit migration client, runs migrations.Up through client.DB() under a 5-minute timeout that caps the goose advisory-lock retry budget, then closes the pool before returning.

Sibling-parallel with merged Step 1 (!310 (merged)); Step 3 will wire both into cmd/artifact-registry/main.go.

References

What ships

  • internal/datastore/migrations/runner.go: Runner (Start, Shutdown, Check, Name), NewRunner, the migrationClient test seam, and the upFunc injection seam. Start is strictly exactly-once per instance; Shutdown is a no-op because Start already released the pool.
  • internal/datastore/migrations/runner_test.go: 17 unit tests covering every plan acceptance criterion plus defensive hardening (re-entry guard, bounded Shutdown deadline, nil-arg panics, ctx propagation, structured log emission on Shutdown error and *goose.PartialError).
  • internal/datastore/migrations/runner_integration_test.go: 3 tests under //go:build integration covering migration application, idempotency on second Start, and OTel tracing through client.DB().

MR-review improvements

Additive hardenings from the local PR-review pass; none remove planned behavior. The plan's Step 2 entry records these (commit `9d91f9f0`):

  • Deferred `client.Shutdown` runs under `context.WithTimeout(context.WithoutCancel(ctx), 30s)` instead of `context.Background()` so a stuck teardown cannot wedge boot.
  • `Start` carries a `started atomic.Bool` re-entry guard.
  • `NewRunner` panics on nil `client` or `logger` (mirrors `server.NewShutdownDelay`).
  • Shutdown errors and `*goose.PartialError` failures emit structured slog records under `fields.ErrorMessage`.

Test plan

  • `go test -race ./internal/datastore/migrations/` (17 unit tests pass under -race)
  • `go test -tags=integration -race ./internal/datastore/migrations/` (3 integration tests pass against testcontainers Postgres)
  • `go vet ./...` clean
  • `golangci-lint run ./internal/datastore/migrations/...` 0 issues
  • CI pipeline green

Follow-ups

Edited by João Pereira

Merge request reports

Loading
Loading