package domain import ( "errors" "sort" "git.cin.su/m/Assistant/contracts/work" ) var ( ErrGraphBudgetExceeded = errors.New("work graph budget exceeded") ErrConflictUnresolved = errors.New("conflict resolution is not explicit") ErrGraphSynthesisRejected = errors.New("graph synthesis rejected") ) // BuildVerifiedGraphSynthesis resolves a conflict only after an independent // verifier has evaluated all candidates. The verifier may not be any result // producer, and every candidate remains in the returned lineage. func BuildVerifiedGraphSynthesis(conflict work.ConflictSet, candidates []work.ResultCandidate, selectedCandidate string, verifierRunID work.AgentRunID, verifierIdentity string, evidence []work.EvidenceRef, summary string, verified bool) (work.ConflictSet, work.GraphSynthesisCandidate, error) { if conflict.ID == "" || conflict.GraphID == "" || conflict.GraphRevision < 1 || conflict.NodeID == "" || conflict.Resolved || len(conflict.CandidateIDs) < 2 || selectedCandidate == "" || verifierRunID == "" || verifierIdentity == "" || len(evidence) == 0 || summary == "" || !verified { return work.ConflictSet{}, work.GraphSynthesisCandidate{}, ErrGraphSynthesisRejected } candidateByID := make(map[string]work.ResultCandidate, len(candidates)) for _, candidate := range candidates { if candidate.ID == "" || candidate.GraphID != conflict.GraphID || candidate.GraphRevision != conflict.GraphRevision || candidate.NodeID != conflict.NodeID || candidate.AuthorRunID == "" { return work.ConflictSet{}, work.GraphSynthesisCandidate{}, ErrGraphSynthesisRejected } candidateByID[candidate.ID] = candidate if candidate.AuthorRunID == verifierRunID { return work.ConflictSet{}, work.GraphSynthesisCandidate{}, ErrGraphSynthesisRejected } } ordered := append([]string(nil), conflict.CandidateIDs...) for _, id := range ordered { if _, ok := candidateByID[id]; !ok { return work.ConflictSet{}, work.GraphSynthesisCandidate{}, ErrGraphSynthesisRejected } } if !containsString(ordered, selectedCandidate) { return work.ConflictSet{}, work.GraphSynthesisCandidate{}, ErrGraphSynthesisRejected } dissent := make([]string, 0, len(ordered)-1) for _, id := range ordered { if id != selectedCandidate { dissent = append(dissent, id) } } conflict.Resolved = true synthesis := work.GraphSynthesisCandidate{ID: conflict.ID + ":synthesis", GraphID: conflict.GraphID, GraphRevision: conflict.GraphRevision, NodeID: conflict.NodeID, CandidateIDs: ordered, SelectedCandidateID: selectedCandidate, DissentCandidateIDs: dissent, VerifierRunID: verifierRunID, VerifierIdentity: verifierIdentity, EvidenceRefs: append([]work.EvidenceRef(nil), evidence...), Summary: summary, State: work.GraphSynthesisVerified} return conflict, synthesis, nil } func containsString(values []string, target string) bool { for _, value := range values { if value == target { return true } } return false } func ScheduleReadyNodes(graph work.WorkGraph, active int) ([]work.GraphNodeID, error) { if err := ValidateWorkGraph(graph); err != nil { return nil, err } if active < 0 || active > graph.Budget.MaxParallel { return nil, ErrGraphBudgetExceeded } ready, err := ReadyGraphNodes(graph) if err != nil { return nil, err } available := graph.Budget.MaxParallel - active if len(ready) > available { ready = ready[:available] } return ready, nil } func AdmitGraph(graph work.WorkGraph) (work.WorkGraph, error) { if err := ValidateWorkGraph(graph); err != nil { return work.WorkGraph{}, err } for _, node := range graph.Nodes { if node.Attempts > graph.Budget.MaxAttempts { return work.WorkGraph{}, ErrGraphBudgetExceeded } } graph.State = work.GraphAdmitted return graph, nil } func ResolveGraphConflict(conflict work.ConflictSet, selectedCandidate string) (work.ConflictSet, error) { if conflict.ID == "" || conflict.GraphID == "" || conflict.GraphRevision < 1 || conflict.NodeID == "" || conflict.Resolved || selectedCandidate == "" { return work.ConflictSet{}, ErrConflictUnresolved } candidates := append([]string(nil), conflict.CandidateIDs...) sort.Strings(candidates) for _, candidate := range candidates { if candidate == selectedCandidate { conflict.CandidateIDs = candidates conflict.Resolved = true return conflict, nil } } return work.ConflictSet{}, ErrConflictUnresolved }