package pipeline import ( "strconv" "strings" "time" ) const ( // RevisionRetentionKey is the global_settings KV for revision TTL (minutes). RevisionRetentionKey = "revision_retention_minutes" RevisionMinTTLMin = 15 RevisionMaxTTLMin = 30 * 24 * 60 RevisionDefaultTTL = 30 * 24 * time.Hour ) // RevisionCutoffFromMinutes returns created_at cutoff for revision prune (UTC now minus clamped TTL). func RevisionCutoffFromMinutes(minutes int) time.Time { ttl := RevisionDefaultTTL if minutes > 0 { m := minutes if m < RevisionMinTTLMin { m = RevisionMinTTLMin } if m > RevisionMaxTTLMin { m = RevisionMaxTTLMin } ttl = time.Duration(m) * time.Minute } return time.Now().UTC().Add(-ttl) } // RevisionRetentionMinutesFromSettings reads revision_retention_minutes from tenant KV (0 if unset). func RevisionRetentionMinutesFromSettings(settings map[string]any) int { if settings == nil { return 0 } v, ok := settings[RevisionRetentionKey] if !ok || v == nil { return 0 } switch x := v.(type) { case float64: return int(x) case int: return x case int64: return int(x) case string: n, err := strconv.Atoi(strings.TrimSpace(x)) if err == nil { return n } } return 0 } // RevisionRetentionCutoff resolves tenant revision_retention_minutes from settings (default 30d). func RevisionRetentionCutoff(settings map[string]any) time.Time { return RevisionCutoffFromMinutes(RevisionRetentionMinutesFromSettings(settings)) } // ClampRevisionRetentionMinutes normalizes user input to the allowed range. func ClampRevisionRetentionMinutes(minutes int) int { if minutes <= 0 { return int(RevisionDefaultTTL / time.Minute) } if minutes < RevisionMinTTLMin { return RevisionMinTTLMin } if minutes > RevisionMaxTTLMin { return RevisionMaxTTLMin } return minutes }