package agent_execution_test import ( "testing" "git.cin.su/m/Assistant/contracts/work" workdomain "git.cin.su/m/Assistant/modules/work/domain" ) func TestWorkGraphRejectsCycleAndOrdersReadyNodes(t *testing.T) { graph := work.WorkGraph{ID: "graph-1", Revision: 1, State: work.GraphAdmitted, Budget: work.GraphBudget{MaxParallel: 2, MaxAttempts: 2}, Nodes: []work.GraphNode{ {ID: "node-b", IdempotencyKey: "key-b", State: work.GraphNodeReady}, {ID: "node-a", IdempotencyKey: "key-a", State: work.GraphNodeReady}, {ID: "node-c", IdempotencyKey: "key-c", State: work.GraphNodeBlocked}, }, Edges: []work.GraphEdge{{From: "node-a", To: "node-c"}}} ready, err := workdomain.ReadyGraphNodes(graph) if err != nil || len(ready) != 2 || ready[0] != "node-a" || ready[1] != "node-b" { t.Fatalf("unexpected ready nodes: %#v %v", ready, err) } graph.Edges = append(graph.Edges, work.GraphEdge{From: "node-c", To: "node-a"}) if err := workdomain.ValidateWorkGraph(graph); err != workdomain.ErrGraphCycle { t.Fatalf("expected cycle rejection, got %v", err) } } func TestWorkGraphKeepsDependentNodeBlocked(t *testing.T) { graph := work.WorkGraph{ID: "graph-1", Revision: 1, State: work.GraphRunning, Budget: work.GraphBudget{MaxParallel: 1, MaxAttempts: 1}, Nodes: []work.GraphNode{ {ID: "source", IdempotencyKey: "source-key", State: work.GraphNodeRunning}, {ID: "dependent", IdempotencyKey: "dependent-key", State: work.GraphNodeBlocked}, }, Edges: []work.GraphEdge{{From: "source", To: "dependent"}}} ready, err := workdomain.ReadyGraphNodes(graph) if err != nil || len(ready) != 0 { t.Fatalf("dependent node became ready early: %#v %v", ready, err) } } func TestGraphSchedulerEnforcesParallelBudgetAndExplicitConflict(t *testing.T) { graph := work.WorkGraph{ID: "graph-1", Revision: 1, State: work.GraphDraft, Budget: work.GraphBudget{MaxParallel: 2, MaxAttempts: 2}, Nodes: []work.GraphNode{ {ID: "b", IdempotencyKey: "b-key", State: work.GraphNodeReady}, {ID: "a", IdempotencyKey: "a-key", State: work.GraphNodeReady}, }} admitted, err := workdomain.AdmitGraph(graph) if err != nil || admitted.State != work.GraphAdmitted { t.Fatalf("graph was not admitted: %#v %v", admitted, err) } ready, err := workdomain.ScheduleReadyNodes(admitted, 1) if err != nil || len(ready) != 1 || ready[0] != "a" { t.Fatalf("scheduler ignored parallel budget/order: %#v %v", ready, err) } conflict := work.ConflictSet{ID: "conflict-1", GraphID: "graph-1", GraphRevision: 1, NodeID: "a", CandidateIDs: []string{"candidate-b", "candidate-a"}} resolved, err := workdomain.ResolveGraphConflict(conflict, "candidate-a") if err != nil || !resolved.Resolved || resolved.CandidateIDs[0] != "candidate-a" { t.Fatalf("conflict was not explicitly resolved: %#v %v", resolved, err) } if _, err := workdomain.ResolveGraphConflict(conflict, "unknown"); err != workdomain.ErrConflictUnresolved { t.Fatalf("unknown candidate was accepted: %v", err) } } func TestGraphSynthesisRequiresIndependentVerifierAndRetainsDissent(t *testing.T) { conflict := work.ConflictSet{ID: "conflict-1", GraphID: "graph-1", GraphRevision: 1, NodeID: "node-1", CandidateIDs: []string{"candidate-a", "candidate-b"}} candidates := []work.ResultCandidate{{ID: "candidate-a", GraphID: "graph-1", GraphRevision: 1, NodeID: "node-1", AuthorRunID: "producer-a", EvidenceSummary: "a"}, {ID: "candidate-b", GraphID: "graph-1", GraphRevision: 1, NodeID: "node-1", AuthorRunID: "producer-b", EvidenceSummary: "b"}} resolved, synthesis, err := workdomain.BuildVerifiedGraphSynthesis(conflict, candidates, "candidate-a", "verifier-1", "independent-verifier", []work.EvidenceRef{"tests:1"}, "candidate a is supported", true) if err != nil || !resolved.Resolved || synthesis.SelectedCandidateID != "candidate-a" || len(synthesis.DissentCandidateIDs) != 1 || synthesis.VerifierRunID == candidates[0].AuthorRunID { t.Fatalf("unexpected synthesis: conflict=%+v synthesis=%+v err=%v", resolved, synthesis, err) } if _, _, err := workdomain.BuildVerifiedGraphSynthesis(conflict, candidates, "candidate-a", "producer-a", "producer", []work.EvidenceRef{"tests:1"}, "forged", true); err != workdomain.ErrGraphSynthesisRejected { t.Fatalf("producer must not verify its own result, got %v", err) } if _, _, err := workdomain.BuildVerifiedGraphSynthesis(conflict, candidates, "candidate-a", "verifier-1", "independent-verifier", []work.EvidenceRef{"tests:1"}, "failed", false); err != workdomain.ErrGraphSynthesisRejected { t.Fatalf("failed verification must not resolve conflict, got %v", err) } }