feat(server): api.v2_enabled hot-reload via middleware gate (ADR-0023 Phase 4)

Closes ADR-0023 — all 4 phases now shipped. Final field: api.v2_enabled.

Approach: always-register-with-middleware-gate.

- /api/v2/* routes are now registered UNCONDITIONALLY at startup
- new Server.v2EnabledGate middleware reads the live config on every
  request and returns 404 + {"error":"not_found","message":"v2 API is
  currently disabled"} when api.v2_enabled is false
- the existing Config.WatchAndApply hot-reload pipeline already keeps
  the config struct fresh — no extra plumbing needed
- flag flip takes effect on the NEXT request (not in-flight ones)
- no router rebuild, no restart

Tested via 3 unit tests in pkg/server/v2_gate_test.go:
- blocked-when-disabled: 404 + correct error message + JSON content-type
- passes-when-enabled: NOT 404, gate message ABSENT (handler executed)
- hot-reload-mid-life: same Server, same router, config flipped between
  two requests → 404 then 200, proves the gate reads live config

Race detector clean. Full BDD suite green. ADR-0023 status promoted to
"Implemented" (no more parenthetical phase tracking).

The original "deferred" rationale in ADR-0023 listed router refactor as
the cost. Turns out the cost was minimal: ~25 lines of middleware + 3
tests + an `if` block deletion.
This commit is contained in:
2026-05-05 10:34:43 +02:00
parent 9895c159fe
commit 7fef564ba9
4 changed files with 114 additions and 9 deletions

View File

@@ -1,6 +1,6 @@
# Config Hot Reloading Strategy
**Status:** Phase 1+2+3 Implemented (2026-05-05). Hot-reloadable fields: `logging.level`, `auth.jwt.ttl`, `telemetry.sampler.type`, `telemetry.sampler.ratio`. Plumbing: `Config.WatchAndApply` in `pkg/config/config.go`, `ReconfigureTracerProvider` in `pkg/telemetry/telemetry.go`, sampler reconfigure callback wired in `pkg/server/server.go Run`. Phase 2 also fixed a pre-existing bug where the hardcoded 24h TTL ignored `auth.jwt.ttl` from config. Remaining field `api.v2_enabled` is **deferred**: hot-reloading routing requires either an always-register-with-middleware-gate refactor of the chi router or an atomic router swap — different complexity class, separate ADR if reopened.
**Status:** Implemented — all 4 phases shipped (2026-05-05). Hot-reloadable fields: `logging.level` (Phase 1), `auth.jwt.ttl` (Phase 2), `telemetry.sampler.type` + `telemetry.sampler.ratio` (Phase 3), `api.v2_enabled` (Phase 4). Plumbing: `Config.WatchAndApply` in `pkg/config/config.go` is the single entry point. Phase 2 fixed a pre-existing bug where hardcoded 24h TTL ignored `auth.jwt.ttl`. Phase 4 chose the **always-register-with-middleware-gate** approach: v2 routes are now ALWAYS registered, and `Server.v2EnabledGate` middleware reads the live config on every request (returns 404 + JSON body when disabled). No router rebuild needed for the flag flip. 3 unit tests in `pkg/server/v2_gate_test.go` cover blocked-when-disabled / passes-when-enabled / hot-reload-mid-life-of-same-Server.
**Authors:** Gabriel Radureau, AI Agent
**Date:** 2026-04-05
**Last Updated:** 2026-05-05