package domain import ( "strings" "time" ) // OperationType represents the type of operation. type OperationType string const ( OperationTypeProjectCreate OperationType = "project.create" OperationTypeComponentAdd OperationType = "component.add" OperationTypeBuild OperationType = "build" OperationTypeResourceProvision OperationType = "resource.provision" ) // IsValid returns true if the operation type is known. func (t OperationType) IsValid() bool { switch t { case OperationTypeProjectCreate, OperationTypeComponentAdd, OperationTypeBuild, OperationTypeResourceProvision: return true } return false } // OperationStatus represents the status of an operation. type OperationStatus string const ( OperationStatusPending OperationStatus = "pending" OperationStatusRunning OperationStatus = "running" OperationStatusCompleted OperationStatus = "completed" OperationStatusFailed OperationStatus = "failed" ) // IsValid returns true if the status is known. func (s OperationStatus) IsValid() bool { switch s { case OperationStatusPending, OperationStatusRunning, OperationStatusCompleted, OperationStatusFailed: return true } return false } // IsTerminal returns true if the status is a final state. func (s OperationStatus) IsTerminal() bool { return s == OperationStatusCompleted || s == OperationStatusFailed } // OperationStep represents a single step within an operation. type OperationStep struct { // Name is the step identifier (e.g., "git", "build-api", "deploy-web"). Name string `json:"name"` // Status is the step status. Status OperationStatus `json:"status"` // StartedAt is when the step started. StartedAt time.Time `json:"started_at"` // DurationMs is the step duration in milliseconds. DurationMs int64 `json:"duration_ms,omitempty"` // Output contains step-specific output data. Output map[string]any `json:"output,omitempty"` // Error is a one-line error summary. Error string `json:"error,omitempty"` // ErrorDetail is the full error detail. ErrorDetail string `json:"error_detail,omitempty"` } // Operation represents a tracked project operation. type Operation struct { // ID is the unique operation identifier. ID string `json:"id"` // ProjectID is the project this operation belongs to. ProjectID string `json:"project_id"` // Type is the operation type. Type OperationType `json:"type"` // Status is the current operation status. Status OperationStatus `json:"status"` // RequestID is the HTTP request that initiated this operation. RequestID string `json:"request_id,omitempty"` // TriggeredBy is the ID of the parent operation that triggered this one. TriggeredBy string `json:"triggered_by,omitempty"` // CommitSHA is the git commit this operation created or was triggered by. CommitSHA string `json:"commit_sha,omitempty"` // ExternalRef is an external reference (e.g., "build#42"). ExternalRef string `json:"external_ref,omitempty"` // StartedAt is when the operation started. StartedAt time.Time `json:"started_at"` // CompletedAt is when the operation finished. CompletedAt *time.Time `json:"completed_at,omitempty"` // DurationMs is the total operation duration in milliseconds. DurationMs int64 `json:"duration_ms,omitempty"` // Input contains the operation input parameters. Input map[string]any `json:"input,omitempty"` // Output contains the operation output/result. Output map[string]any `json:"output,omitempty"` // Error is a one-line error summary. Error string `json:"error,omitempty"` // ErrorDetail is the full error detail (truncated to 10KB). ErrorDetail string `json:"error_detail,omitempty"` // Steps contains the operation steps. Steps []OperationStep `json:"steps,omitempty"` // CreatedAt is when the record was created. CreatedAt time.Time `json:"created_at"` } // StepsSummary returns a human-readable summary of step statuses. // Example: "git ✓ → build-web ✓ → build-api ✗" func (o *Operation) StepsSummary() string { if len(o.Steps) == 0 { return "" } var parts []string for _, step := range o.Steps { symbol := "?" switch step.Status { case OperationStatusCompleted: symbol = "✓" case OperationStatusFailed: symbol = "✗" case OperationStatusRunning: symbol = "…" case OperationStatusPending: symbol = "○" } parts = append(parts, step.Name+" "+symbol) } return strings.Join(parts, " → ") } // FailedStep returns the first failed step, or nil if none failed. func (o *Operation) FailedStep() *OperationStep { for i := range o.Steps { if o.Steps[i].Status == OperationStatusFailed { return &o.Steps[i] } } return nil } // OperationFilters specifies criteria for listing operations. type OperationFilters struct { // ProjectID filters by project (required for List). ProjectID string // Type filters by operation type. Type OperationType // Status filters by operation status. Status OperationStatus // CommitSHA filters by commit SHA. CommitSHA string // Since filters operations started after this time. Since time.Time // Limit is the maximum number of operations to return. Limit int } // DefaultOperationFilters returns filters with default values. func DefaultOperationFilters() OperationFilters { return OperationFilters{ Limit: 50, } } // Normalize applies defaults and limits to the filters. func (f *OperationFilters) Normalize() { if f.Limit <= 0 { f.Limit = 50 } if f.Limit > 200 { f.Limit = 200 } } // MaxErrorDetailSize is the maximum size of error_detail (10KB). const MaxErrorDetailSize = 10 * 1024 // TruncateErrorDetail truncates error detail to the maximum allowed size. func TruncateErrorDetail(detail string) string { if len(detail) <= MaxErrorDetailSize { return detail } return detail[:MaxErrorDetailSize-3] + "..." }