package domain import ( "fmt" "git.cin.su/m/Assistant/contracts/work" "testing" ) func TestSolutionSearchDoesNotDeadEndAfterBudgetedWave(t *testing.T) { decision, err := DecideSolutionSearch(work.SolutionSearchInput{GoalID: "goal-1", GoalState: work.GoalInProgress, SearchRound: 3, MaxRound: 3, Unresolved: true}) if err != nil { t.Fatal(err) } if !decision.ContinueSearching || decision.State != work.OrchestrationWaiting || decision.NextSearchRound != 4 { t.Fatalf("unexpected exhausted-wave decision: %#v", decision) } if len(decision.Options) != 5 || decision.Options[4] != work.SearchOptionHuman { t.Fatalf("all search avenues were not retained: %#v", decision.Options) } } func TestSolutionSearchStopsOnlyForCancelOrVerifiedCompletion(t *testing.T) { completed, err := DecideSolutionSearch(work.SolutionSearchInput{GoalID: "goal-1", GoalState: work.GoalCompleted, VerificationReady: true, MaxRound: 1}) if err != nil || completed.ContinueSearching || completed.State != work.OrchestrationCompleted { t.Fatalf("verified completion was not terminal: %#v %v", completed, err) } cancelled, err := DecideSolutionSearch(work.SolutionSearchInput{GoalID: "goal-1", GoalState: work.GoalInProgress, CancelRequested: true, MaxRound: 1}) if err != nil || cancelled.ContinueSearching || cancelled.State != work.OrchestrationCancelled { t.Fatalf("cancel was not terminal: %#v %v", cancelled, err) } } func TestBuildSolutionSearchWaveMaterializesParallelAgentDirections(t *testing.T) { decision, err := DecideSolutionSearch(work.SolutionSearchInput{GoalID: "goal-1", GoalState: work.GoalInProgress, MaxRound: 1}) if err != nil { t.Fatal(err) } options := []work.SearchOptionKind{work.SearchOptionAgents, work.SearchOptionCapability, work.SearchOptionResearch, work.SearchOptionReflection} dispatches := make([]work.SolutionSearchDispatch, 0, len(options)) for i, option := range options { dispatches = append(dispatches, work.SolutionSearchDispatch{Option: option, Intent: work.AgentDispatchIntent{DispatchID: workDispatchID(i), IdempotencyKey: workDispatchID(i) + ":idem"}}) } graph, intents, err := BuildSolutionSearchWave(decision, work.SolutionSearchWaveRequest{GraphID: "graph-1", IdempotencyPrefix: "goal-1", Dispatches: dispatches, Budget: work.GraphBudget{MaxParallel: 4, MaxAttempts: 2}}) if err != nil { t.Fatal(err) } if len(graph.Nodes) != 4 || len(intents) != 4 || graph.State != work.GraphDraft { t.Fatalf("unexpected materialized wave: %#v %#v", graph, intents) } for i, node := range graph.Nodes { if node.State != work.GraphNodeBlocked || intents[i].SpecialistPurpose != string(options[i]) { t.Fatalf("wave direction not preserved: node=%#v intent=%#v", node, intents[i]) } } } func workDispatchID(index int) string { return fmt.Sprintf("dispatch-%d", index) }