package ports import ( "context" "fmt" "galaxy/user/internal/domain/common" "galaxy/user/internal/domain/entitlement" ) // EntitlementHistoryStore persists immutable entitlement period records and // later close-state updates. type EntitlementHistoryStore interface { // Create stores one new entitlement period history record. Implementations // must wrap ErrConflict when record.RecordID already exists. Create(ctx context.Context, record entitlement.PeriodRecord) error // GetByRecordID returns the entitlement period history record identified by // recordID. GetByRecordID(ctx context.Context, recordID entitlement.EntitlementRecordID) (entitlement.PeriodRecord, error) // ListByUserID returns every entitlement period history record owned by // userID. ListByUserID(ctx context.Context, userID common.UserID) ([]entitlement.PeriodRecord, error) // Update replaces one stored entitlement period history record. Update(ctx context.Context, record entitlement.PeriodRecord) error } // EntitlementSnapshotStore persists the read-optimized current entitlement // snapshot. type EntitlementSnapshotStore interface { // GetByUserID returns the current entitlement snapshot for userID. GetByUserID(ctx context.Context, userID common.UserID) (entitlement.CurrentSnapshot, error) // Put stores the current entitlement snapshot for record.UserID. Put(ctx context.Context, record entitlement.CurrentSnapshot) error } // GrantEntitlementInput stores one atomic transition from a current free // entitlement state to a current paid state. type GrantEntitlementInput struct { // ExpectedCurrentSnapshot stores the exact snapshot that must still be // current before the mutation commits. ExpectedCurrentSnapshot entitlement.CurrentSnapshot // ExpectedCurrentRecord stores the current effective free period that must // still be current before the mutation commits. ExpectedCurrentRecord entitlement.PeriodRecord // UpdatedCurrentRecord stores ExpectedCurrentRecord after the close metadata // is applied. UpdatedCurrentRecord entitlement.PeriodRecord // NewRecord stores the new paid entitlement history segment. NewRecord entitlement.PeriodRecord // NewSnapshot stores the new current effective entitlement snapshot. NewSnapshot entitlement.CurrentSnapshot } // Validate reports whether GrantEntitlementInput is structurally complete. func (input GrantEntitlementInput) Validate() error { if err := input.ExpectedCurrentSnapshot.Validate(); err != nil { return fmt.Errorf("grant entitlement input expected current snapshot: %w", err) } if err := input.ExpectedCurrentRecord.Validate(); err != nil { return fmt.Errorf("grant entitlement input expected current record: %w", err) } if err := input.UpdatedCurrentRecord.Validate(); err != nil { return fmt.Errorf("grant entitlement input updated current record: %w", err) } if err := input.NewRecord.Validate(); err != nil { return fmt.Errorf("grant entitlement input new record: %w", err) } if err := input.NewSnapshot.Validate(); err != nil { return fmt.Errorf("grant entitlement input new snapshot: %w", err) } if input.ExpectedCurrentSnapshot.UserID != input.ExpectedCurrentRecord.UserID || input.ExpectedCurrentSnapshot.UserID != input.UpdatedCurrentRecord.UserID || input.ExpectedCurrentSnapshot.UserID != input.NewRecord.UserID || input.ExpectedCurrentSnapshot.UserID != input.NewSnapshot.UserID { return fmt.Errorf("grant entitlement input all records must belong to the same user id") } if input.ExpectedCurrentRecord.RecordID != input.UpdatedCurrentRecord.RecordID { return fmt.Errorf("grant entitlement input updated current record must preserve record id") } return nil } // ExtendEntitlementInput stores one atomic extension of a current finite paid // entitlement state. type ExtendEntitlementInput struct { // ExpectedCurrentSnapshot stores the exact snapshot that must still be // current before the mutation commits. ExpectedCurrentSnapshot entitlement.CurrentSnapshot // NewRecord stores the appended entitlement history segment that extends the // current paid state. NewRecord entitlement.PeriodRecord // NewSnapshot stores the replacement current effective entitlement snapshot. NewSnapshot entitlement.CurrentSnapshot } // Validate reports whether ExtendEntitlementInput is structurally complete. func (input ExtendEntitlementInput) Validate() error { if err := input.ExpectedCurrentSnapshot.Validate(); err != nil { return fmt.Errorf("extend entitlement input expected current snapshot: %w", err) } if err := input.NewRecord.Validate(); err != nil { return fmt.Errorf("extend entitlement input new record: %w", err) } if err := input.NewSnapshot.Validate(); err != nil { return fmt.Errorf("extend entitlement input new snapshot: %w", err) } if input.ExpectedCurrentSnapshot.UserID != input.NewRecord.UserID || input.ExpectedCurrentSnapshot.UserID != input.NewSnapshot.UserID { return fmt.Errorf("extend entitlement input all records must belong to the same user id") } return nil } // RevokeEntitlementInput stores one atomic transition from a current paid // entitlement state to a new free state. type RevokeEntitlementInput struct { // ExpectedCurrentSnapshot stores the exact snapshot that must still be // current before the mutation commits. ExpectedCurrentSnapshot entitlement.CurrentSnapshot // ExpectedCurrentRecord stores the current effective paid period that must // still be current before the mutation commits. ExpectedCurrentRecord entitlement.PeriodRecord // UpdatedCurrentRecord stores ExpectedCurrentRecord after the close metadata // is applied. UpdatedCurrentRecord entitlement.PeriodRecord // NewRecord stores the newly created free entitlement period. NewRecord entitlement.PeriodRecord // NewSnapshot stores the replacement current effective free snapshot. NewSnapshot entitlement.CurrentSnapshot } // Validate reports whether RevokeEntitlementInput is structurally complete. func (input RevokeEntitlementInput) Validate() error { if err := input.ExpectedCurrentSnapshot.Validate(); err != nil { return fmt.Errorf("revoke entitlement input expected current snapshot: %w", err) } if err := input.ExpectedCurrentRecord.Validate(); err != nil { return fmt.Errorf("revoke entitlement input expected current record: %w", err) } if err := input.UpdatedCurrentRecord.Validate(); err != nil { return fmt.Errorf("revoke entitlement input updated current record: %w", err) } if err := input.NewRecord.Validate(); err != nil { return fmt.Errorf("revoke entitlement input new record: %w", err) } if err := input.NewSnapshot.Validate(); err != nil { return fmt.Errorf("revoke entitlement input new snapshot: %w", err) } if input.ExpectedCurrentSnapshot.UserID != input.ExpectedCurrentRecord.UserID || input.ExpectedCurrentSnapshot.UserID != input.UpdatedCurrentRecord.UserID || input.ExpectedCurrentSnapshot.UserID != input.NewRecord.UserID || input.ExpectedCurrentSnapshot.UserID != input.NewSnapshot.UserID { return fmt.Errorf("revoke entitlement input all records must belong to the same user id") } if input.ExpectedCurrentRecord.RecordID != input.UpdatedCurrentRecord.RecordID { return fmt.Errorf("revoke entitlement input updated current record must preserve record id") } return nil } // RepairExpiredEntitlementInput stores one atomic lazy-repair transition from // an expired finite paid snapshot to a materialized free state. type RepairExpiredEntitlementInput struct { // ExpectedExpiredSnapshot stores the exact expired snapshot that must still // be current before the repair commits. ExpectedExpiredSnapshot entitlement.CurrentSnapshot // NewRecord stores the newly created free entitlement period. NewRecord entitlement.PeriodRecord // NewSnapshot stores the replacement current effective free snapshot. NewSnapshot entitlement.CurrentSnapshot } // Validate reports whether RepairExpiredEntitlementInput is structurally // complete. func (input RepairExpiredEntitlementInput) Validate() error { if err := input.ExpectedExpiredSnapshot.Validate(); err != nil { return fmt.Errorf("repair expired entitlement input expected expired snapshot: %w", err) } if err := input.NewRecord.Validate(); err != nil { return fmt.Errorf("repair expired entitlement input new record: %w", err) } if err := input.NewSnapshot.Validate(); err != nil { return fmt.Errorf("repair expired entitlement input new snapshot: %w", err) } if input.ExpectedExpiredSnapshot.UserID != input.NewRecord.UserID || input.ExpectedExpiredSnapshot.UserID != input.NewSnapshot.UserID { return fmt.Errorf("repair expired entitlement input all records must belong to the same user id") } return nil } // EntitlementLifecycleStore persists atomic entitlement timeline transitions // that must keep history and current snapshot consistent. type EntitlementLifecycleStore interface { // Grant atomically closes the current free period, creates a new paid // period, and replaces the current snapshot. Grant(ctx context.Context, input GrantEntitlementInput) error // Extend atomically appends one paid-history segment and replaces the // current snapshot. Extend(ctx context.Context, input ExtendEntitlementInput) error // Revoke atomically closes the current paid period, creates a new free // period, and replaces the current snapshot. Revoke(ctx context.Context, input RevokeEntitlementInput) error // RepairExpired atomically replaces one expired finite paid snapshot with a // materialized free state. RepairExpired(ctx context.Context, input RepairExpiredEntitlementInput) error }