rdev/internal/service/sdlc_callback.go
jordan d69da6d627 feat: add structured logging infrastructure and SDLC extensions
Major changes:
- Add internal/logging package with field constants, context propagation,
  sensitive data auto-redaction, and per-component log levels
- Add worker timeout constants (TimeoutQuickOp, TimeoutHealthCheck, etc.)
- Extend SDLC with callback handlers, generate endpoints, and executor
- Add new cookbook trees for aeries and slackpath progression
- Add skeleton templates for queue, realtime, and microservices
- Add worker component template with async job processing
- Refactor services and handlers to use new logging infrastructure
- Split component.go into component_infra.go and component_listing.go

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-04 22:56:04 -07:00

123 lines
3.8 KiB
Go

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)
}
}