Skip to content

feat(rotation): Phase 4 — retired-key cleanup scheduler (#72) - #131

Closed
sarg3nt wants to merge 1 commit into
feature/issue-72-phase-3-rotate-uifrom
feature/issue-72-phase-4-cleanup-scheduler
Closed

feat(rotation): Phase 4 — retired-key cleanup scheduler (#72)#131
sarg3nt wants to merge 1 commit into
feature/issue-72-phase-3-rotate-uifrom
feature/issue-72-phase-4-cleanup-scheduler

Conversation

@sarg3nt

@sarg3nt sarg3nt commented May 17, 2026

Copy link
Copy Markdown
Owner

Summary

Stacked on #130 (Phase 3). Adds a background sweeper that walks every enabled box on a tick and removes any keys whose retired_at + overlap window has passed — completing the install→use→remove three-phase rotation cycle without operator intervention.

Scope note: Phase 4 in the original plan also covered auto-rotation on a schedule (off by default, configurable cadence). That needs a new global-settings surface in the dashboard (no app-level config table exists today; only user_preferences), which is its own design pass. Deferring the auto-rotate scheduler to a follow-up so this PR stays focused on the piece that's actually needed regardless of whether auto-rotation is enabled. With this PR shipped, an operator clicking the Phase 3 Rotate button gets the old key cleaned up automatically a day later.

Implementation

  • services/agent_keyring/cleaner.goRetiredKeyCleaner runs as a goroutine off the dashboard's process-lifetime context
  • Hourly tick (CleanerInterval) — short enough that a 24h-overlap rotation cleans up within a few hours of its target, long enough that the sweep is cheap
  • Immediate sweep on start so a freshly-deployed dashboard catches up on retired keys left from manual rotations while the prior instance was down
  • Per-box failures logged but don't halt the sweep
  • Wired into cmd/server/main.go startup alongside the existing alert evaluator

Test plan

  • Both cleaner tests pass: removes a retired key on tick / no-op when nothing is retired
  • Manual: rotate a box; wait an hour (or set CleanerInterval low for testing); confirm the old kid is removed from both the agent's /api/v1/system/keyring and the dashboard's DB
  • On dashboard restart, immediate sweep fires (log line "retired-key cleaner started")
  • No regressions in existing tests

Stack

Phase 4 of 5. Base: feature/issue-72-phase-3-rotate-ui (#130).

Closes part of #72.

🤖 Generated with Claude Code

Adds a background sweeper that walks every enabled box on a tick and
removes any keys whose retired_at + overlap window has passed —
completing the install→use→remove three-phase rotation cycle without
operator intervention.

Originally Phase 4 in the issue plan also covered auto-rotation on a
schedule (off by default, configurable cadence). That needs a new
global-settings surface in the dashboard (no app-level config table
exists today; only user_preferences), which is its own design pass.
Deferring the auto-rotate scheduler to a follow-up so this PR stays
focused on the piece that's actually needed regardless of whether
auto-rotation is enabled.

What's here
-----------

`services/agent_keyring/cleaner.go`

- `RetiredKeyCleaner` runs as a goroutine off the dashboard's
  process-lifetime context.
- Hourly tick (`CleanerInterval`) — short enough that a 24h
  rotation cleans up within a few hours of its target, long enough
  that the sweep is cheap.
- Immediate sweep on start so a freshly-deployed dashboard catches
  up on any retired keys left from manual rotations done while the
  prior instance was down.
- Per-box failures are logged but don't halt the sweep; one
  unreachable agent shouldn't block cleanup on the others.

`cmd/server/main.go`

- Wires the cleaner into startup alongside the existing alert
  evaluator. Cancelled when main returns.

Tests
-----

- `TestCleaner_RemovesRetiredKeyOnTick` — rotates a box, then runs
  the cleaner with a 1ms overlap and 20ms interval; verifies the
  retired key is removed from both the mock agent and the DB.
- `TestCleaner_NoopWhenNothingRetired` — no rotation happens, so
  the cleaner finds nothing to do; verifies the seeded entry
  survives a sweep.

Refs: Phase 4 (lite) of the implementation plan posted to #72.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@sarg3nt
sarg3nt force-pushed the feature/issue-72-phase-3-rotate-ui branch from c66f37a to 139e7b7 Compare May 17, 2026 19:03
@sarg3nt
sarg3nt force-pushed the feature/issue-72-phase-4-cleanup-scheduler branch from 71f92b0 to 7645c9b Compare May 17, 2026 19:03
@sarg3nt
sarg3nt deleted the branch feature/issue-72-phase-3-rotate-ui May 17, 2026 19:04
@sarg3nt sarg3nt closed this May 17, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant