Files
kandev/apps/backend/internal/workflow/adapters/adapters.go
T
2026-05-16 20:07:57 +01:00

150 lines
5.1 KiB
Go

// Package adapters implements the workflow engine's participant, decision,
// and primary-agent adapter interfaces against the workflow repository.
//
// The engine declares the interfaces in internal/workflow/engine/adapters.go
// and internal/workflow/engine/phase2_callbacks.go; this package wires them
// to the workflow_step_participants, workflow_step_decisions, and
// workflow_steps tables so callers (cmd/kandev/main.go) can compose an
// office-grade engine without leaking model imports into the engine package.
package adapters
import (
"context"
"fmt"
"github.com/kandev/kandev/internal/workflow/engine"
"github.com/kandev/kandev/internal/workflow/models"
"github.com/kandev/kandev/internal/workflow/repository"
)
// WorkflowRepo captures the subset of *repository.Repository methods the
// adapters need. Defined as an interface so tests can swap in fakes.
type WorkflowRepo interface {
ListStepParticipantsForTask(ctx context.Context, stepID, taskID string) ([]*models.WorkflowStepParticipant, error)
ListStepDecisions(ctx context.Context, taskID, stepID string) ([]*models.WorkflowStepDecision, error)
RecordStepDecision(ctx context.Context, d *models.WorkflowStepDecision) error
ClearStepDecisions(ctx context.Context, taskID, stepID string) (int64, error)
GetStep(ctx context.Context, id string) (*models.WorkflowStep, error)
}
// Compile-time check that *repository.Repository satisfies WorkflowRepo.
var _ WorkflowRepo = (*repository.Repository)(nil)
// ParticipantAdapter implements engine.ParticipantStore.
type ParticipantAdapter struct {
Repo WorkflowRepo
}
// NewParticipantAdapter builds a ParticipantAdapter wrapping the workflow repo.
func NewParticipantAdapter(repo WorkflowRepo) *ParticipantAdapter {
return &ParticipantAdapter{Repo: repo}
}
// ListStepParticipants satisfies engine.ParticipantStore. The Wave 8 (ADR-0004)
// dual-scoped table — template-level rows (task_id=”) merged with per-task
// overrides — is resolved by the repository's ListStepParticipantsForTask.
func (a *ParticipantAdapter) ListStepParticipants(
ctx context.Context, stepID, taskID string,
) ([]engine.ParticipantInfo, error) {
rows, err := a.Repo.ListStepParticipantsForTask(ctx, stepID, taskID)
if err != nil {
return nil, fmt.Errorf("list step participants: %w", err)
}
out := make([]engine.ParticipantInfo, 0, len(rows))
for _, p := range rows {
out = append(out, engine.ParticipantInfo{
ID: p.ID,
StepID: p.StepID,
TaskID: p.TaskID,
Role: string(p.Role),
AgentProfileID: p.AgentProfileID,
DecisionRequired: p.DecisionRequired,
Position: p.Position,
})
}
return out, nil
}
// DecisionAdapter implements engine.DecisionStore.
type DecisionAdapter struct {
Repo WorkflowRepo
}
// NewDecisionAdapter builds a DecisionAdapter wrapping the workflow repo.
func NewDecisionAdapter(repo WorkflowRepo) *DecisionAdapter {
return &DecisionAdapter{Repo: repo}
}
// ListStepDecisions satisfies engine.DecisionStore.
func (a *DecisionAdapter) ListStepDecisions(
ctx context.Context, taskID, stepID string,
) ([]engine.DecisionInfo, error) {
rows, err := a.Repo.ListStepDecisions(ctx, taskID, stepID)
if err != nil {
return nil, fmt.Errorf("list step decisions: %w", err)
}
out := make([]engine.DecisionInfo, 0, len(rows))
for _, d := range rows {
out = append(out, engine.DecisionInfo{
ID: d.ID,
TaskID: d.TaskID,
StepID: d.StepID,
ParticipantID: d.ParticipantID,
Decision: d.Decision,
Note: d.Note,
})
}
return out, nil
}
// RecordStepDecision satisfies engine.DecisionStore.
func (a *DecisionAdapter) RecordStepDecision(
ctx context.Context, d engine.DecisionInfo,
) error {
row := &models.WorkflowStepDecision{
ID: d.ID,
TaskID: d.TaskID,
StepID: d.StepID,
ParticipantID: d.ParticipantID,
Decision: d.Decision,
Note: d.Note,
}
return a.Repo.RecordStepDecision(ctx, row)
}
// ClearStepDecisions satisfies engine.DecisionStore.
func (a *DecisionAdapter) ClearStepDecisions(
ctx context.Context, taskID, stepID string,
) (int64, error) {
return a.Repo.ClearStepDecisions(ctx, taskID, stepID)
}
// PrimaryAgentAdapter implements engine.PrimaryAgentResolver. The "primary"
// agent for a workflow step is the one stored on workflow_steps.agent_profile_id.
type PrimaryAgentAdapter struct {
Repo WorkflowRepo
}
// NewPrimaryAgentAdapter builds a PrimaryAgentAdapter wrapping the workflow repo.
func NewPrimaryAgentAdapter(repo WorkflowRepo) *PrimaryAgentAdapter {
return &PrimaryAgentAdapter{Repo: repo}
}
// PrimaryAgentProfileID satisfies engine.PrimaryAgentResolver.
func (a *PrimaryAgentAdapter) PrimaryAgentProfileID(
ctx context.Context, stepID string,
) (string, error) {
step, err := a.Repo.GetStep(ctx, stepID)
if err != nil {
return "", fmt.Errorf("get step %s: %w", stepID, err)
}
return step.AgentProfileID, nil
}
// Compile-time interface assertions.
var (
_ engine.ParticipantStore = (*ParticipantAdapter)(nil)
_ engine.DecisionStore = (*DecisionAdapter)(nil)
_ engine.PrimaryAgentResolver = (*PrimaryAgentAdapter)(nil)
)