2d73a3d1db
* fix: keep completed office comments editable Keep the office task detail composer editable after completed sessions load, including projectless office tasks. Update verification tests for SHA-pinned GitHub actions. Extract the dockview main area to satisfy the existing max-lines lint rule. * fix: preserve upload artifact version guard * fix: clarify upload artifact pin check * fix: wake completed office sessions on comments * fix: queue completed office comment runs * fix: preserve dispatcher session lookup errors * fix: resolve office comment wake review issues * fix: stabilize comment wake idempotency * fix: salt custom comment wake keys * fix: address comment wake review cleanup * fix: avoid comment payload allocation overflow pattern * fix: salt comment wake action variants * fix: resolve cross-task primary by target step * fix: resolve cross-task participant roles * fix: surface cross-task comment run status * fix: use target steps for cross-task queue runs * fix: avoid comment run lookup scan regression * fix: cover cross-task queue run edge cases * fix: index comment payload run lookup * fix: support postgres comment run index * fix: tighten queue run target test coverage * fix: route source comment run updates * fix: preserve cross-task comment wakes * fix: tighten comment wake identity * fix: align comment wake routing * fix: remove stale primary resolver * fix: dedupe comment run status * fix: tighten legacy comment wake dedupe * fix: remove unreachable comment wake guard * fix: skip duplicate comment reactivity wakes * fix: route comment wakes synchronously * fix: share comment engine dispatch contracts * fix: preserve comment wake fallbacks * fix: preserve done comment runner * fix: guard empty task runner fallback * fix: handle empty task runner fallback * fix: stabilize completed comment wakes * fix: index comment runner fallback --------- Co-authored-by: root <root@kandev.ts.net>
164 lines
5.9 KiB
Go
164 lines
5.9 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)
|
|
ResolveCurrentRunner(ctx context.Context, stepID, taskID string) (string, error)
|
|
GetTaskWorkflowStepID(ctx context.Context, taskID string) (string, 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
|
|
}
|
|
|
|
// WorkflowStepIDForTask satisfies engine.TargetTaskStepResolver.
|
|
func (a *ParticipantAdapter) WorkflowStepIDForTask(ctx context.Context, taskID string) (string, error) {
|
|
return a.Repo.GetTaskWorkflowStepID(ctx, taskID)
|
|
}
|
|
|
|
// 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 task is its current runner participant, falling back to the
|
|
// workflow step's agent_profile_id when no task-specific runner exists.
|
|
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, taskID string,
|
|
) (string, error) {
|
|
agentID, err := a.Repo.ResolveCurrentRunner(ctx, stepID, taskID)
|
|
if err != nil {
|
|
return "", fmt.Errorf("resolve current runner for step %s task %s: %w", stepID, taskID, err)
|
|
}
|
|
return agentID, nil
|
|
}
|
|
|
|
// WorkflowStepIDForTask satisfies engine.TargetTaskStepResolver.
|
|
func (a *PrimaryAgentAdapter) WorkflowStepIDForTask(ctx context.Context, taskID string) (string, error) {
|
|
return a.Repo.GetTaskWorkflowStepID(ctx, taskID)
|
|
}
|
|
|
|
// Compile-time interface assertions.
|
|
var (
|
|
_ engine.ParticipantStore = (*ParticipantAdapter)(nil)
|
|
_ engine.TargetTaskStepResolver = (*ParticipantAdapter)(nil)
|
|
_ engine.DecisionStore = (*DecisionAdapter)(nil)
|
|
_ engine.PrimaryAgentResolver = (*PrimaryAgentAdapter)(nil)
|
|
_ engine.TargetTaskStepResolver = (*PrimaryAgentAdapter)(nil)
|
|
)
|