package ports import ( "context" "fmt" "galaxy/user/internal/domain/common" "galaxy/user/internal/domain/policy" ) // SanctionStore persists sanction history records and later remove-state // updates. type SanctionStore interface { // Create stores one new sanction history record. Implementations must wrap // ErrConflict when record.RecordID already exists. Create(ctx context.Context, record policy.SanctionRecord) error // GetByRecordID returns the sanction history record identified by recordID. GetByRecordID(ctx context.Context, recordID policy.SanctionRecordID) (policy.SanctionRecord, error) // ListByUserID returns every sanction history record owned by userID. ListByUserID(ctx context.Context, userID common.UserID) ([]policy.SanctionRecord, error) // Update replaces one stored sanction history record. Update(ctx context.Context, record policy.SanctionRecord) error } // LimitStore persists user-specific limit history records and later // remove-state updates. type LimitStore interface { // Create stores one new limit history record. Implementations must wrap // ErrConflict when record.RecordID already exists. Create(ctx context.Context, record policy.LimitRecord) error // GetByRecordID returns the limit history record identified by recordID. GetByRecordID(ctx context.Context, recordID policy.LimitRecordID) (policy.LimitRecord, error) // ListByUserID returns every limit history record owned by userID. ListByUserID(ctx context.Context, userID common.UserID) ([]policy.LimitRecord, error) // Update replaces one stored limit history record. Update(ctx context.Context, record policy.LimitRecord) error } // ApplySanctionInput stores one atomic creation of a new active sanction. type ApplySanctionInput struct { // NewRecord stores the sanction history record that must become active. NewRecord policy.SanctionRecord } // Validate reports whether ApplySanctionInput is structurally complete. func (input ApplySanctionInput) Validate() error { if err := input.NewRecord.Validate(); err != nil { return fmt.Errorf("apply sanction input new record: %w", err) } return nil } // RemoveSanctionInput stores one atomic removal of the current active // sanction for one `user_id + sanction_code`. type RemoveSanctionInput struct { // ExpectedActiveRecord stores the exact sanction record that must still be // active before the mutation commits. ExpectedActiveRecord policy.SanctionRecord // UpdatedRecord stores ExpectedActiveRecord after remove metadata is // applied. UpdatedRecord policy.SanctionRecord } // Validate reports whether RemoveSanctionInput is structurally complete. func (input RemoveSanctionInput) Validate() error { if err := input.ExpectedActiveRecord.Validate(); err != nil { return fmt.Errorf("remove sanction input expected active record: %w", err) } if err := input.UpdatedRecord.Validate(); err != nil { return fmt.Errorf("remove sanction input updated record: %w", err) } if input.ExpectedActiveRecord.RecordID != input.UpdatedRecord.RecordID { return fmt.Errorf("remove sanction input updated record must preserve record id") } if input.ExpectedActiveRecord.UserID != input.UpdatedRecord.UserID { return fmt.Errorf("remove sanction input records must belong to the same user id") } if input.ExpectedActiveRecord.SanctionCode != input.UpdatedRecord.SanctionCode { return fmt.Errorf("remove sanction input records must preserve sanction code") } return nil } // SetLimitInput stores one atomic creation or replacement of the current // active limit for one `user_id + limit_code`. type SetLimitInput struct { // ExpectedActiveRecord stores the currently active limit that must still be // active before replacement commits. It stays nil when no active limit // exists yet. ExpectedActiveRecord *policy.LimitRecord // UpdatedActiveRecord stores ExpectedActiveRecord after remove metadata is // applied. It stays nil when no active limit exists yet. UpdatedActiveRecord *policy.LimitRecord // NewRecord stores the limit history record that must become active. NewRecord policy.LimitRecord } // Validate reports whether SetLimitInput is structurally complete. func (input SetLimitInput) Validate() error { if err := input.NewRecord.Validate(); err != nil { return fmt.Errorf("set limit input new record: %w", err) } switch { case input.ExpectedActiveRecord == nil && input.UpdatedActiveRecord == nil: return nil case input.ExpectedActiveRecord == nil || input.UpdatedActiveRecord == nil: return fmt.Errorf("set limit input active replacement records must both be present or absent") } if err := input.ExpectedActiveRecord.Validate(); err != nil { return fmt.Errorf("set limit input expected active record: %w", err) } if err := input.UpdatedActiveRecord.Validate(); err != nil { return fmt.Errorf("set limit input updated active record: %w", err) } if input.ExpectedActiveRecord.RecordID != input.UpdatedActiveRecord.RecordID { return fmt.Errorf("set limit input updated active record must preserve record id") } if input.ExpectedActiveRecord.UserID != input.UpdatedActiveRecord.UserID || input.ExpectedActiveRecord.UserID != input.NewRecord.UserID { return fmt.Errorf("set limit input records must belong to the same user id") } if input.ExpectedActiveRecord.LimitCode != input.UpdatedActiveRecord.LimitCode || input.ExpectedActiveRecord.LimitCode != input.NewRecord.LimitCode { return fmt.Errorf("set limit input records must preserve limit code") } return nil } // RemoveLimitInput stores one atomic removal of the current active limit for // one `user_id + limit_code`. type RemoveLimitInput struct { // ExpectedActiveRecord stores the exact limit record that must still be // active before the mutation commits. ExpectedActiveRecord policy.LimitRecord // UpdatedRecord stores ExpectedActiveRecord after remove metadata is // applied. UpdatedRecord policy.LimitRecord } // Validate reports whether RemoveLimitInput is structurally complete. func (input RemoveLimitInput) Validate() error { if err := input.ExpectedActiveRecord.Validate(); err != nil { return fmt.Errorf("remove limit input expected active record: %w", err) } if err := input.UpdatedRecord.Validate(); err != nil { return fmt.Errorf("remove limit input updated record: %w", err) } if input.ExpectedActiveRecord.RecordID != input.UpdatedRecord.RecordID { return fmt.Errorf("remove limit input updated record must preserve record id") } if input.ExpectedActiveRecord.UserID != input.UpdatedRecord.UserID { return fmt.Errorf("remove limit input records must belong to the same user id") } if input.ExpectedActiveRecord.LimitCode != input.UpdatedRecord.LimitCode { return fmt.Errorf("remove limit input records must preserve limit code") } return nil } // PolicyLifecycleStore persists atomic sanction and limit transitions that // must keep history and active-slot state consistent. type PolicyLifecycleStore interface { // ApplySanction atomically creates one new active sanction record. ApplySanction(ctx context.Context, input ApplySanctionInput) error // RemoveSanction atomically removes one active sanction record. RemoveSanction(ctx context.Context, input RemoveSanctionInput) error // SetLimit atomically creates or replaces one active limit record. SetLimit(ctx context.Context, input SetLimitInput) error // RemoveLimit atomically removes one active limit record. RemoveLimit(ctx context.Context, input RemoveLimitInput) error }