package service import ( "context" "fmt" "github.com/orchard9/rdev/internal/domain" "github.com/orchard9/rdev/internal/logging" "github.com/orchard9/rdev/internal/sdlc" ) // SDLCCallbackService handles build completion callbacks to update SDLC artifact status. type SDLCCallbackService struct { sdlcService *SDLCService } // NewSDLCCallbackService creates a new SDLC callback service. func NewSDLCCallbackService(sdlcService *SDLCService) *SDLCCallbackService { return &SDLCCallbackService{ sdlcService: sdlcService, } } // HandleCallback processes a build completion and updates SDLC artifact status. func (s *SDLCCallbackService) HandleCallback(ctx context.Context, payload *domain.SDLCCallbackPayload) error { // Validate required fields if payload.ProjectID == "" { return fmt.Errorf("project_id is required") } if payload.Feature == "" { return fmt.Errorf("feature is required") } if payload.ArtifactType == "" { return fmt.Errorf("artifact_type is required") } // Map generate artifact type to SDLC artifact type artType, err := s.mapArtifactType(payload.ArtifactType) if err != nil { return fmt.Errorf("invalid artifact_type: %w", err) } log := logging.FromContext(ctx).WithService("sdlc_callback") log.Info("handling SDLC callback", "task_id", payload.TaskID, logging.FieldProjectID, payload.ProjectID, "feature", payload.Feature, "artifact_type", payload.ArtifactType, "success", payload.Success, ) if payload.Success { // QA results get "passed" status, others get "draft" (awaiting human approval) if artType == sdlc.ArtifactQAResults { if err := s.sdlcService.PassArtifact(ctx, payload.ProjectID, payload.Feature, artType); err != nil { log.Error("failed to pass artifact", logging.FieldError, err, logging.FieldProjectID, payload.ProjectID, "feature", payload.Feature, "artifact_type", artType, ) return fmt.Errorf("pass artifact: %w", err) } log.Info("artifact marked as passed", logging.FieldProjectID, payload.ProjectID, "feature", payload.Feature, "artifact_type", artType, ) } else { // Mark as draft - awaiting human approval // The SDLC service doesn't have a direct setDraft method, but we can // achieve this by first ensuring the artifact exists, then the file // generation creates the draft status automatically. // For now, we'll log that the artifact was generated. log.Info("artifact generated successfully (draft status)", logging.FieldProjectID, payload.ProjectID, "feature", payload.Feature, "artifact_type", artType, "commit_sha", payload.CommitSHA, ) } } else { // Mark as needs_fix on failure if err := s.sdlcService.NeedsFixArtifact(ctx, payload.ProjectID, payload.Feature, artType); err != nil { log.Error("failed to mark artifact as needs_fix", logging.FieldError, err, logging.FieldProjectID, payload.ProjectID, "feature", payload.Feature, "artifact_type", artType, ) return fmt.Errorf("needs_fix artifact: %w", err) } log.Info("artifact marked as needs_fix", logging.FieldProjectID, payload.ProjectID, "feature", payload.Feature, "artifact_type", artType, logging.FieldError, payload.Error, ) } return nil } // mapArtifactType converts generate artifact types to SDLC artifact types. func (s *SDLCCallbackService) mapArtifactType(genType string) (sdlc.ArtifactType, error) { switch genType { case "spec": return sdlc.ArtifactSpec, nil case "design": return sdlc.ArtifactDesign, nil case "tasks": return sdlc.ArtifactTasks, nil case "code": // Code generation doesn't map to a single artifact type // The code is committed directly, no artifact status change needed return "", fmt.Errorf("code artifact type does not have a corresponding SDLC artifact") case "qa": return sdlc.ArtifactQAResults, nil default: return "", fmt.Errorf("unknown artifact type: %s", genType) } }