package securityevents import ( "context" "time" "github.com/easyai/easyai-ai-gateway/apps/api/internal/store" ) const ( defaultSecurityEventRetirementDuration = 6 * time.Minute securityEventRetirementWorkerInterval = time.Minute ) type SecurityEventRetirementRepository interface { SecurityEventConnection(context.Context) (store.SecurityEventConnection, error) FinalizeRetiringSecurityEventConnection(context.Context, string, int64) error } // RunSecurityEventRetirementWorker owns the local end of SSF retirement at the // server lifecycle. It intentionally does not share an Active identity // Runtime's context, because that Runtime is closed before the RFC 8935 overlap // period expires. Store-level generation CAS makes one worker per replica safe. func RunSecurityEventRetirementWorker(ctx context.Context, repository SecurityEventRetirementRepository) { runSecurityEventRetirementWorker( ctx, repository, defaultSecurityEventRetirementDuration, securityEventRetirementWorkerInterval, ) } func runSecurityEventRetirementWorker( ctx context.Context, repository SecurityEventRetirementRepository, retirementDuration time.Duration, interval time.Duration, ) { if repository == nil { return } if retirementDuration <= 0 { retirementDuration = defaultSecurityEventRetirementDuration } if interval <= 0 { interval = securityEventRetirementWorkerInterval } ticker := time.NewTicker(interval) defer ticker.Stop() for { finalizeDueSecurityEventRetirement(ctx, repository, retirementDuration) select { case <-ctx.Done(): return case <-ticker.C: } } } func finalizeDueSecurityEventRetirement( ctx context.Context, repository SecurityEventRetirementRepository, retirementDuration time.Duration, ) { connection, err := repository.SecurityEventConnection(ctx) if err != nil || connection.LifecycleStatus != "retiring" { return } if retirementDuration <= 0 { retirementDuration = defaultSecurityEventRetirementDuration } if time.Since(connection.UpdatedAt) < retirementDuration { return } // Finalization atomically queues every Secret reference and removes only the // exact retiring generation. A concurrent worker or changed generation is a // benign CAS miss and will be observed on the next loop. _ = repository.FinalizeRetiringSecurityEventConnection(ctx, connection.ConnectionID, connection.Version) }