✨ feat(auth): JWT secret retention policy + automatic cleanup loop (ADR-0021) #41
Reference in New Issue
Block a user
Delete Branch "feat/adr-0021-jwt-secret-cleanup"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Sprint 3 of autonomous trainer day 2026-05-05
Implements the cleanup half of ADR-0021 — the config infrastructure (TTL, retention factor, max retention, cleanup interval) had been merged earlier without the actual cleanup logic. This PR closes that gap.
What ships
Core mechanism
RemoveExpiredSecrets() intonJWTSecretManager— drops every non-primary secret whoseExpiresAtis non-nil and in the past. Returns count of removed. Primary secret is never removed regardless of expiration (ADR-0021 invariant).StartCleanupLoop(ctx, interval)— spawns a goroutine runningRemoveExpiredSecretsat the configured interval. Stops cleanly whenctxis cancelled.sync.Mutex). Race detector clean.Wiring
pkg/server/server.goRun()callsStartJWTSecretCleanupLoop(rootCtx, ...)after the signal context is set up — the loop stops on graceful shutdown.AuthServiceinterface extended withStartJWTSecretCleanupLoopandRemoveExpiredJWTSecrets.Status update
Implemented. Heading also corrected from10.to21.(was a doc error from earlier commit).Tests
pkg/user/jwt_manager_test.go:TestRemoveExpiredSecrets_ExpiredNonPrimaryRemovedTestRemoveExpiredSecrets_PrimaryNeverRemovedTestRemoveExpiredSecrets_NonExpiredKeptTestStartCleanupLoop_FiresAndStops(drives the goroutine end-to-end with low TTL)go test -race ./pkg/user/...passes.@todo/@skipinfeatures/jwt/jwt_secret_retention.featureremain so — they reference an admin endpoint/api/v1/admin/jwt/secretsthat is explicitly out of scope for this PR.Verifier verdict (skill-driven)
APPROVE_WITH_NITS
StartCleanupLoopis 34 lines (just over the 30-line guideline; could be split intosetupLoopCtx+runTickerLoophelpers but not critical).10., now21.); status accurately reflects what landed; out-of-scope items explicitly documented.time.Sleepuses are justified by the goroutine-timing nature ofTestStartCleanupLoop_FiresAndStops(low TTL keeps the test < 100ms).Out of scope (deferred)
/api/v1/admin/jwt/secrets— referenced by@todoBDD scenarios but no concrete need yet. Reopen when ops require introspection.log.Info().Int("removed", n)).RemoveExpiredJWTSecrets()is exposed on the service but not as an HTTP endpoint.Test plan
go test -race ./pkg/user/...passesgo vet ./...clean