Files
Assistant/modules/work/domain/task_evaluation_graph.go
m 863a379048
CI / test (push) Successful in 57s
CI / test (pull_request) Successful in 1m2s
feat(work): link task evaluation worker execution
2026-08-23 12:56:01 +03:00

89 lines
4.1 KiB
Go

package domain
import (
"errors"
"fmt"
"git.cin.su/m/Assistant/contracts/capability"
"git.cin.su/m/Assistant/contracts/work"
)
var ErrInvalidTaskEvaluationGraph = errors.New("invalid task evaluation graph request")
// BuildTaskEvaluationGraph turns one bounded profile into durable specialist
// nodes. It deliberately has no provider, endpoint or hardware inputs: those
// are resolved later by the cognitive worker for each binding.
func BuildTaskEvaluationGraph(request work.TaskEvaluationGraphRequest) (work.WorkGraph, error) {
if request.GraphID == "" || request.IdempotencyPrefix == "" || len(request.BindingIDs) == 0 || len(request.BindingIDs) != len(request.DispatchIDs) || request.Budget.MaxParallel < 1 || request.Budget.MaxAttempts < 1 {
return work.WorkGraph{}, ErrInvalidTaskEvaluationGraph
}
if err := request.Profile.Validate(); err != nil {
return work.WorkGraph{}, ErrInvalidTaskEvaluationGraph
}
if len(request.Cases) != len(request.Profile.RequiredCaseIDs) {
return work.WorkGraph{}, ErrInvalidTaskEvaluationGraph
}
seenCases := make(map[string]struct{}, len(request.Cases))
for _, item := range request.Cases {
if item.CaseID == "" || item.Prompt == "" || item.EvidenceArtifactRef == "" {
return work.WorkGraph{}, ErrInvalidTaskEvaluationGraph
}
if _, exists := seenCases[item.CaseID]; exists {
return work.WorkGraph{}, ErrInvalidTaskEvaluationGraph
}
seenCases[item.CaseID] = struct{}{}
}
for _, requiredID := range request.Profile.RequiredCaseIDs {
if _, exists := seenCases[requiredID]; !exists {
return work.WorkGraph{}, ErrInvalidTaskEvaluationGraph
}
}
if request.Profile.Stage == capability.TaskEvaluationGeneral {
if request.ParentGraphID != "" || request.ParentGraphRevision != 0 || request.ParentGraphState != "" || request.TriggerEvidenceRef != "" {
return work.WorkGraph{}, ErrInvalidTaskEvaluationGraph
}
} else {
if request.ParentGraphID == "" || request.ParentGraphRevision < 1 || request.TriggerEvidenceRef == "" || (request.ParentGraphState != work.GraphCompleted && request.ParentGraphState != work.GraphPartial) {
return work.WorkGraph{}, ErrInvalidTaskEvaluationGraph
}
if request.Profile.ParentProfileID == "" {
return work.WorkGraph{}, ErrInvalidTaskEvaluationGraph
}
}
seenBindings := make(map[capability.CapabilityBindingID]struct{}, len(request.BindingIDs))
seenDispatches := make(map[string]struct{}, len(request.DispatchIDs))
nodes := make([]work.GraphNode, 0, len(request.BindingIDs))
for i, bindingID := range request.BindingIDs {
if bindingID == "" || request.DispatchIDs[i] == "" {
return work.WorkGraph{}, ErrInvalidTaskEvaluationGraph
}
if _, ok := seenBindings[bindingID]; ok {
return work.WorkGraph{}, ErrInvalidTaskEvaluationGraph
}
if _, ok := seenDispatches[request.DispatchIDs[i]]; ok {
return work.WorkGraph{}, ErrInvalidTaskEvaluationGraph
}
seenBindings[bindingID] = struct{}{}
seenDispatches[request.DispatchIDs[i]] = struct{}{}
nodes = append(nodes, work.GraphNode{
ID: work.GraphNodeID(fmt.Sprintf("%s-node-%d", request.GraphID, i+1)),
DispatchID: request.DispatchIDs[i],
IdempotencyKey: fmt.Sprintf("%s:%s:%d", request.IdempotencyPrefix, bindingID, request.Profile.Revision),
State: work.GraphNodeBlocked,
Purpose: work.GraphNodePurposeTaskEvaluation,
TaskProfileID: request.Profile.ID,
BindingID: bindingID,
CaseIDs: append([]string(nil), request.Profile.RequiredCaseIDs...),
TaskProfile: cloneTaskEvaluationProfile(request.Profile),
TaskCases: append([]capability.TaskEvaluationCase(nil), request.Cases...),
})
}
return work.WorkGraph{ID: request.GraphID, Revision: 1, State: work.GraphDraft, Nodes: nodes, Budget: request.Budget, ParentGraphID: request.ParentGraphID, ParentGraphRevision: request.ParentGraphRevision, TriggerEvidenceRef: request.TriggerEvidenceRef}, nil
}
func cloneTaskEvaluationProfile(value capability.TaskEvaluationProfile) *capability.TaskEvaluationProfile {
copyValue := value
copyValue.RequiredCaseIDs = append([]string(nil), value.RequiredCaseIDs...)
return &copyValue
}