Files
orbit/internal/retention/policy.go

193 lines
8.2 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Package retention 定义 Orbit 历史数据保留策略的通用模型。
//
// 策略只描述候选数据的最短保留窗口,不直接删除任务、事件或审计记录。
// 归档执行器必须在独立流程中先完成核查、备份和归档证明,再按策略处理。
package retention
import (
"context"
"errors"
"fmt"
"strconv"
"strings"
"time"
"unicode/utf8"
core "git.yuntuo.hk/k2safe/orbit/model"
"git.yuntuo.hk/k2safe/orbit/platform/auth"
)
const (
// DefaultIdempotencyDays 是请求幂等记录的默认保留天数。
DefaultIdempotencyDays uint32 = 90
// DefaultTaskHistoryDays 是终态任务历史的默认保留天数。
DefaultTaskHistoryDays uint32 = 365
// DefaultWorkflowHistoryDays 是终态流程历史的默认保留天数。
DefaultWorkflowHistoryDays uint32 = 365
// DefaultEventLogDays 是状态事件和结果事件的默认保留天数。
DefaultEventLogDays uint32 = 365
// DefaultDeliveryHistoryDays 是已确认交付历史的默认保留天数。
DefaultDeliveryHistoryDays uint32 = 90
// DefaultInboxHistoryDays 是已处理流程 Inbox 历史的默认保留天数。
DefaultInboxHistoryDays uint32 = 90
// DefaultAuditLogDays 是管理审计的默认保留天数,约七年。
DefaultAuditLogDays uint32 = 2555
minRetentionDays uint32 = 1
maxRetentionDays uint32 = 3650
)
// Policy 是按命名空间保存的历史数据保留策略。
// Enabled 默认关闭;即使打开,也允许只读归档导出使用满足窗口且已核验的数据。
// 当前版本不会因为配置变更自动删除任何数据。
type Policy struct {
// NamespaceID 是策略所属命名空间,不能由控制台请求替换。
NamespaceID core.ID `json:"namespace_id"`
// Revision 是乐观并发版本;默认策略未落库时为0,首次保存后从1开始。
Revision uint64 `json:"revision,string"`
// Enabled 表示是否允许归档执行器使用该策略;默认关闭以避免误清理。
Enabled bool `json:"enabled"`
// IdempotencyDays 是请求幂等记录至少保留的天数。
IdempotencyDays uint32 `json:"idempotency_days"`
// TaskHistoryDays 是终态任务及执行证据至少保留的天数。
TaskHistoryDays uint32 `json:"task_history_days"`
// WorkflowHistoryDays 是终态流程、步骤、等待和流程事件至少保留的天数。
WorkflowHistoryDays uint32 `json:"workflow_history_days"`
// EventLogDays 是状态事件和结果事件至少保留的天数。
EventLogDays uint32 `json:"event_log_days"`
// DeliveryHistoryDays 是已确认结果交付至少保留的天数;未确认交付不得按窗口清理。
DeliveryHistoryDays uint32 `json:"delivery_history_days"`
// InboxHistoryDays 是已处理流程 Inbox 事件至少保留的天数;未处理记录不得清理。
InboxHistoryDays uint32 `json:"inbox_history_days"`
// AuditLogDays 是管理审计至少保留的天数;审计记录不参与普通清理。
AuditLogDays uint32 `json:"audit_log_days"`
// UpdatedAt 是策略最后修改时间,统一使用 UTC。
UpdatedAt time.Time `json:"updated_at"`
// Source 表示策略来自默认值还是数据库配置,便于控制台明确当前状态。
Source string `json:"source"`
}
// UpdateInput 是控制台保存策略所需的完整替换输入。
// 所有窗口必须显式提交,避免遗漏字段被解释成缩短保留期。
type UpdateInput struct {
// ExpectedRevision 是页面读取到的版本;首次保存必须为0。
ExpectedRevision uint64 `json:"expected_revision,string" required:"true"`
// Enabled 是否允许归档执行器读取本策略。
Enabled bool `json:"enabled" required:"true"`
// IdempotencyDays 请求幂等记录保留天数。
IdempotencyDays uint32 `json:"idempotency_days" required:"true"`
// TaskHistoryDays 终态任务历史保留天数。
TaskHistoryDays uint32 `json:"task_history_days" required:"true"`
// WorkflowHistoryDays 终态流程历史保留天数。
WorkflowHistoryDays uint32 `json:"workflow_history_days" required:"true"`
// EventLogDays 状态和结果事件保留天数。
EventLogDays uint32 `json:"event_log_days" required:"true"`
// DeliveryHistoryDays 已确认交付历史保留天数。
DeliveryHistoryDays uint32 `json:"delivery_history_days" required:"true"`
// InboxHistoryDays 已处理 Inbox 历史保留天数。
InboxHistoryDays uint32 `json:"inbox_history_days" required:"true"`
// AuditLogDays 管理审计保留天数。
AuditLogDays uint32 `json:"audit_log_days" required:"true"`
// Reason 是本次策略变更原因,会进入管理审计。
Reason string `json:"reason" required:"true"`
}
// Store 是控制台和归档执行器共享的最小策略存储边界。
// 实现必须保证 Update 的版本核对、幂等和审计与策略写入处于同一事务。
type Store interface {
GetRetentionPolicy(context.Context, core.ID) (Policy, error)
UpdateRetentionPolicy(context.Context, auth.Token, string, UpdateInput) (Policy, error)
}
// Defaults 返回不会触发任何归档执行的安全默认策略。
func Defaults(namespace core.ID) Policy {
return Policy{
NamespaceID: namespace,
Enabled: false,
IdempotencyDays: DefaultIdempotencyDays,
TaskHistoryDays: DefaultTaskHistoryDays,
WorkflowHistoryDays: DefaultWorkflowHistoryDays,
EventLogDays: DefaultEventLogDays,
DeliveryHistoryDays: DefaultDeliveryHistoryDays,
InboxHistoryDays: DefaultInboxHistoryDays,
AuditLogDays: DefaultAuditLogDays,
Source: "default",
}
}
// Validate 检查策略身份、窗口和审计原因。
func (p Policy) Validate() error {
if p.NamespaceID.Validate() != nil {
return errors.New("retention namespace is invalid")
}
for name, days := range map[string]uint32{
"idempotency_days": p.IdempotencyDays,
"task_history_days": p.TaskHistoryDays,
"workflow_history_days": p.WorkflowHistoryDays,
"event_log_days": p.EventLogDays,
"delivery_history_days": p.DeliveryHistoryDays,
"inbox_history_days": p.InboxHistoryDays,
"audit_log_days": p.AuditLogDays,
} {
if days < minRetentionDays || days > maxRetentionDays {
return fmt.Errorf("%s must be between %d and %d days", name, minRetentionDays, maxRetentionDays)
}
}
if p.IdempotencyDays < DefaultIdempotencyDays {
return errors.New("idempotency_days cannot be less than 90 days")
}
return nil
}
// ToPolicy 将完整输入转为待保存策略,版本由事务内分配。
func (in UpdateInput) ToPolicy(namespace core.ID) (Policy, error) {
if strings.TrimSpace(in.Reason) == "" || !utf8.ValidString(in.Reason) || len(in.Reason) > 1024 || strings.ContainsAny(in.Reason, "\r\n") {
return Policy{}, errors.New("retention reason must be 1-1024 UTF-8 bytes without newlines")
}
p := Policy{
NamespaceID: namespace,
Enabled: in.Enabled,
IdempotencyDays: in.IdempotencyDays,
TaskHistoryDays: in.TaskHistoryDays,
WorkflowHistoryDays: in.WorkflowHistoryDays,
EventLogDays: in.EventLogDays,
DeliveryHistoryDays: in.DeliveryHistoryDays,
InboxHistoryDays: in.InboxHistoryDays,
AuditLogDays: in.AuditLogDays,
}
if err := p.Validate(); err != nil {
return Policy{}, err
}
return p, nil
}
// FromEnv 读取部署默认值;数据库策略保存后优先于部署默认值。
// 所有节点应注入同一组默认值,保证尚未保存策略的展示一致。
func FromEnv(get func(string) string) (Policy, error) {
p := Defaults("")
for _, f := range []struct {
name string
dst *uint32
}{
{"ORBIT_RETENTION_IDEMPOTENCY_DAYS", &p.IdempotencyDays},
{"ORBIT_RETENTION_TASK_DAYS", &p.TaskHistoryDays},
{"ORBIT_RETENTION_WORKFLOW_DAYS", &p.WorkflowHistoryDays},
{"ORBIT_RETENTION_EVENT_DAYS", &p.EventLogDays},
{"ORBIT_RETENTION_DELIVERY_DAYS", &p.DeliveryHistoryDays},
{"ORBIT_RETENTION_INBOX_DAYS", &p.InboxHistoryDays},
{"ORBIT_RETENTION_AUDIT_DAYS", &p.AuditLogDays},
} {
if raw := get(f.name); raw != "" {
n, err := strconv.ParseUint(raw, 10, 32)
if err != nil || n < 1 || n > 3650 {
return Policy{}, fmt.Errorf("%s must be 1-3650 days", f.name)
}
*f.dst = uint32(n)
}
}
if p.IdempotencyDays < 90 {
return Policy{}, errors.New("ORBIT_RETENTION_IDEMPOTENCY_DAYS cannot be less than 90")
}
return p, nil
}