package domain import ( "errors" "fmt" "strconv" "strings" "time" "unicode/utf8" ) type TaskStatus string const ( TaskStatusPending TaskStatus = "PENDING" TaskStatusClaimed TaskStatus = "CLAIMED" TaskStatusRunning TaskStatus = "RUNNING" TaskStatusWaitingConfirmation TaskStatus = "WAITING_CONFIRMATION" TaskStatusSucceeded TaskStatus = "SUCCEEDED" TaskStatusFailed TaskStatus = "FAILED" TaskStatusCanceled TaskStatus = "CANCELED" CurrencyCNY = "CNY" ) const ( MaxTitleRunes = 120 MaxTitleBytes = 2048 MaxSKUBytes = 512 MaxDescriptionBytes = 8192 MaxSourceRefBytes = 256 MaxCancelReasonBytes = 500 ) type PurchaseTask struct { ID string CreatorSubject string CreatedByUserID *string SourceRef *string Title string Description string SKU string ImageAssetID string Quantity int MaxBudgetCents *int64 Currency string Status TaskStatus Version int64 ClaimedByUserID *string ClaimedByDeviceID *string ClaimGeneration int64 ClaimTokenHash *string ClaimIssuedAt *time.Time ClaimExpiresAt *time.Time CancelReason *string CancelRequestedAt *time.Time CancelRequestedByUserID *string CanceledAt *time.Time CreatedAt time.Time UpdatedAt time.Time } type TaskEvent struct { ID string TaskID string ActorUserID *string ActorDeviceID *string Type string Message string OccurredAt time.Time } type TaskExecution struct { ID string TaskID string AttemptNo int64 ClaimGeneration int64 UserID string DeviceID string CurrentStep string OrderSubmitted bool StartedAt time.Time LastHeartbeatAt *time.Time FinishedAt *time.Time } type ExecutionEvent struct { ID string TaskID string ExecutionID string Step string Type string Message string OccurredAt time.Time ReceivedAt time.Time ReceivedAfterExecutionExpiry bool } type ExecutionEvidenceAsset struct { ID string TaskID string ExecutionID string MediaType string SizeBytes int64 SHA256 string StorageKey string CreatedAt time.Time ReceivedAfterExecutionExpiry bool } type ExecutionCandidateBatch struct { TaskID string ExecutionID string TaskContentSHA256 string ExecutionMode string SearchQuery string ProvenanceJSON *string CandidatesJSON string RecommendationJSON *string ReadyEvent *TaskEvent ReceivedAt time.Time ReceivedAfterExecutionExpiry bool } type ExecutionOutcome struct { TaskID string ExecutionID string ResultType string ExecutionMode *string TaskContentSHA256 *string Outcome *string OperatorReason *string SelectedCandidateJSON *string EvidenceAssetIDsJSON *string ErrorCode *string ErrorMessage *string ErrorStep *string Retryable *bool OrderSubmitted bool ReceivedAt time.Time ReceivedAfterExecutionExpiry bool } type CandidateSearchRun struct { TaskID string ExecutionID string TaskContentSHA256 string ExecutionMode string SearchQuery string AppVersion *string AndroidVersion *string PDDVersion *string StartedAt time.Time ReceivedAt time.Time ObservationCount int CollectionComplete bool ReceivedAfterExecutionExpiry bool } type CandidateObservation struct { TaskID string ExecutionID string Ordinal int Title string SKUText string PriceText string ProductURL string ImageURL string EvidenceAssetIDs []string CollectionStatus string ObservedAt time.Time Identity *CandidateObservationIdentity } type CandidateObservationIdentity struct { CandidateKey string ExecutionID string CandidateOrdinal int CardSignature string DetailSignature string DetailEvidenceSHA256 string SpecificationEvidenceSHA256 string IdentityVersion int CreatedAt time.Time } type CandidateModelRun struct { ExecutionID string ProviderID string Model string PromptVersion string SchemaVersion int RecommendationThreshold float64 RequestSHA256 string ResultSHA256 string CreatedAt time.Time } type CandidateEvaluationRecord struct { ExecutionID string CandidateOrdinal int Decision string Score float64 Confidence float64 MatchedJSON string MissingOrUncertainJSON string RejectionReasonsJSON string HardConstraintsJSON string CreatedAt time.Time } type CandidateRecommendationRecord struct { ExecutionID string CandidateOrdinal int Conclusion string PolicyVersion string ReasonsJSON string CreatedAt time.Time } type CandidateHumanReview struct { ID string TaskID string ExecutionID string TaskContentSHA256 string Version int ReasonSchemaVersion int Outcome string SelectedCandidateOrdinal *int PrimaryReasonCode string Note string SupersedesReviewID *string ActorUserID string ActorDeviceID *string CreatedAt time.Time ReceivedAfterExecutionExpiry bool Items []CandidateHumanReviewItem } type CandidateHumanReviewItem struct { CandidateOrdinal int Label string PrimaryReasonCode string ReasonCodes []string Note string } type CandidateDecisionDataset struct { SearchRun *CandidateSearchRun Observations []CandidateObservation ModelRun *CandidateModelRun Evaluations []CandidateEvaluationRecord Recommendation *CandidateRecommendationRecord HumanReviews []CandidateHumanReview } type OrderAuthorizationStatus string const ( OrderAuthorizationPendingDelivery OrderAuthorizationStatus = "PENDING_DELIVERY" OrderAuthorizationDelivered OrderAuthorizationStatus = "DELIVERED" OrderAuthorizationAcknowledged OrderAuthorizationStatus = "ACKNOWLEDGED" OrderAuthorizationExecuting OrderAuthorizationStatus = "EXECUTING" OrderAuthorizationConsumed OrderAuthorizationStatus = "CONSUMED" OrderAuthorizationFailed OrderAuthorizationStatus = "FAILED" OrderAuthorizationRevoked OrderAuthorizationStatus = "REVOKED" OrderAuthorizationSuperseded OrderAuthorizationStatus = "SUPERSEDED" ) type OrderAuthorization struct { ID string TaskID string ExecutionID string AuthorizationVersion int CandidateKey string TaskContentSHA256 string TaskVersion int64 ReviewID string ReviewVersion int UserID string DeviceID string ClaimGeneration int64 OriginalSKU string Quantity int CandidateSKUText string CandidatePriceText string CardSignature string DetailSignature string DetailEvidenceSHA256 string SpecificationEvidenceSHA256 string Status OrderAuthorizationStatus SupersedesAuthorizationID *string CreatedByUserID string CreatedAt time.Time DeliveredAt *time.Time AcknowledgedAt *time.Time ExecutionStartedAt *time.Time ConsumedAt *time.Time FailedAt *time.Time RevokedAt *time.Time FailureCode *string FailureMessage *string CommandSHA256 *string DeliveryAttemptCount int LastDeliveredAt *time.Time } type DeviceOrderCommand struct { ID string SchemaVersion int Type string AuthorizationVersion int TaskID string ExecutionID string TaskContentSHA256 string OriginalSKU string Quantity int CandidateKey string ObservedOrdinal int CandidateTitle string CandidateSKUText string CandidatePriceText string CardSignature string DetailSignature string DetailEvidenceSHA256 string SpecificationEvidenceSHA256 string CommandSHA256 string AuthorizationStatus OrderAuthorizationStatus } type OrderDryRunStatus string const ( OrderDryRunPreparing OrderDryRunStatus = "PREPARING" OrderDryRunReady OrderDryRunStatus = "READY" ) type OrderDryRun struct { ID string AuthorizationID string TaskID string ExecutionID string CommandSHA256 string Status OrderDryRunStatus CardSignature *string DetailSignature *string ObservedTitle *string SelectedSKU *string Quantity *int UnitPriceCents *int64 TotalPriceCents *int64 EvidenceAssetID *string EvidenceSHA256 *string StartedAt time.Time ReadyAt *time.Time } type OrderSubmissionStatus string const ( OrderSubmissionFenced OrderSubmissionStatus = "FENCED" OrderSubmissionReconciled OrderSubmissionStatus = "RECONCILED" OrderSubmissionManualReview OrderSubmissionStatus = "MANUAL_REVIEW" ) type OrderSubmission struct { ID string AuthorizationID string DryRunID string TaskID string ExecutionID string CommandSHA256 string DryRunEvidenceSHA256 string Status OrderSubmissionStatus ExpectedTitle string ExpectedSKU string ExpectedQuantity int ExpectedUnitPriceCents int64 ExpectedTotalPriceCents int64 PlatformOrderNo *string PlatformOrderedAt *time.Time PlatformOrderStatus *string ReconciliationEvidenceAssetID *string ReconciliationEvidenceSHA256 *string ManualReasonCode *string FencedAt time.Time ReconciledAt *time.Time ManualReviewAt *time.Time } type ExecutionReport struct { Events []ExecutionEvent EvidenceAssets []ExecutionEvidenceAsset CandidateBatch *ExecutionCandidateBatch DecisionDataset *CandidateDecisionDataset Outcome *ExecutionOutcome } type TaskDetail struct { Task PurchaseTask Asset Asset Execution *TaskExecution Events []TaskEvent Report *ExecutionReport OrderAuthorizations []OrderAuthorization OrderSubmissions []OrderSubmission } type TaskValidationError struct { Fields map[string]string } func (e *TaskValidationError) Error() string { return "purchase task validation failed" } func ValidateTaskInput( creatorSubject string, sourceRef *string, title string, description string, sku string, imageAssetID string, quantity int, ) error { fields := make(map[string]string) if strings.TrimSpace(creatorSubject) == "" { fields["creator_subject"] = "required" } if sourceRef != nil { if strings.TrimSpace(*sourceRef) == "" { fields["source_ref"] = "must not be blank" } else if len([]byte(*sourceRef)) > MaxSourceRefBytes { fields["source_ref"] = fmt.Sprintf( "must not exceed %d UTF-8 bytes", MaxSourceRefBytes, ) } } if strings.TrimSpace(title) == "" { fields["title"] = "required" } else { if utf8.RuneCountInString(title) > MaxTitleRunes { fields["title"] = fmt.Sprintf( "must not exceed %d characters", MaxTitleRunes, ) } if len([]byte(title)) > MaxTitleBytes { fields["title"] = fmt.Sprintf( "must not exceed %d UTF-8 bytes", MaxTitleBytes, ) } } if len([]byte(description)) > MaxDescriptionBytes { fields["description"] = fmt.Sprintf( "must not exceed %d UTF-8 bytes", MaxDescriptionBytes, ) } if strings.TrimSpace(sku) == "" { fields["sku"] = "required" } else if len([]byte(sku)) > MaxSKUBytes { fields["sku"] = fmt.Sprintf( "must not exceed %d UTF-8 bytes", MaxSKUBytes, ) } if strings.TrimSpace(imageAssetID) == "" { fields["image_asset_id"] = "required" } if quantity <= 0 { fields["quantity"] = "must be a positive integer" } if len(fields) > 0 { return &TaskValidationError{Fields: fields} } return nil } func ParseOptionalCNY(value *string) (*int64, error) { if value == nil { return nil, nil } trimmed := strings.TrimSpace(*value) if trimmed == "" { return nil, errors.New("budget must be omitted or positive") } if strings.HasPrefix(trimmed, "+") || strings.HasPrefix(trimmed, "-") { return nil, errors.New("budget must be positive") } parts := strings.Split(trimmed, ".") if len(parts) > 2 || parts[0] == "" || len(parts[0]) > 16 { return nil, errors.New("budget format is invalid") } if !allDigits(parts[0]) { return nil, errors.New("budget format is invalid") } fraction := "" if len(parts) == 2 { fraction = parts[1] if fraction == "" || len(fraction) > 2 || !allDigits(fraction) { return nil, errors.New("budget format is invalid") } } for len(fraction) < 2 { fraction += "0" } whole, err := strconv.ParseInt(parts[0], 10, 64) if err != nil || whole > (int64(^uint64(0)>>1)-99)/100 { return nil, errors.New("budget is too large") } centsPart, err := strconv.ParseInt(fraction, 10, 64) if err != nil { return nil, errors.New("budget format is invalid") } cents := whole*100 + centsPart if cents <= 0 { return nil, errors.New("budget must be positive") } return ¢s, nil } func FormatOptionalCNY(cents *int64) *string { if cents == nil { return nil } value := fmt.Sprintf("%d.%02d", *cents/100, *cents%100) return &value } func CanCancel(status TaskStatus) bool { return CanAdminCancelImmediately(status) || CanRequestCancel(status) } func IsActiveTaskStatus(status TaskStatus) bool { switch status { case TaskStatusClaimed, TaskStatusRunning, TaskStatusWaitingConfirmation: return true default: return false } } func CanStart(status TaskStatus) bool { return status == TaskStatusClaimed } func CanHeartbeat(status TaskStatus) bool { return status == TaskStatusRunning || status == TaskStatusWaitingConfirmation } func CanRelease(status TaskStatus) bool { return status == TaskStatusClaimed } func CanAdminCancelImmediately(status TaskStatus) bool { return status == TaskStatusPending || status == TaskStatusClaimed } func CanRequestCancel(status TaskStatus) bool { return status == TaskStatusRunning || status == TaskStatusWaitingConfirmation } func CanAcknowledgeCancel(status TaskStatus) bool { return CanRequestCancel(status) } func IsValidTaskStatus(status TaskStatus) bool { switch status { case TaskStatusPending, TaskStatusClaimed, TaskStatusRunning, TaskStatusWaitingConfirmation, TaskStatusSucceeded, TaskStatusFailed, TaskStatusCanceled: return true default: return false } } func allDigits(value string) bool { if value == "" { return false } for _, char := range value { if char < '0' || char > '9' { return false } } return true }