package application import ( "context" "testing" "git.cin.su/m/Assistant/contracts/capability" "git.cin.su/m/Assistant/contracts/work" ) type taskGraphStore struct { graph work.WorkGraph intents []work.AgentDispatchIntent } func (s *taskGraphStore) SaveWorkGraph(_ context.Context, graph work.WorkGraph) error { s.graph = graph return nil } func (s *taskGraphStore) LoadWorkGraph(context.Context, string, int64) (work.WorkGraph, error) { return s.graph, nil } func (s *taskGraphStore) SaveDispatchIntent(_ context.Context, intent work.AgentDispatchIntent) error { s.intents = append(s.intents, intent) return nil } func (s *taskGraphStore) EnqueueDispatch(context.Context, work.AgentDispatchQueueItem) error { return nil } func TestAdmitAndScheduleTaskEvaluationCarriesSpecialistContext(t *testing.T) { store := &taskGraphStore{} request := TaskEvaluationScheduleRequest{ Plan: work.TaskEvaluationGraphRequest{ GraphID: "graph-task", Profile: capability.TaskEvaluationProfile{ID: "profile-general", Revision: 1, CapabilityID: "capability-chat", Stage: capability.TaskEvaluationGeneral, BenchmarkVersion: "general-v1", RequiredCaseIDs: []string{"case-a", "case-b"}, QualityWeightMillis: 600, ReliabilityWeightMillis: 300, LatencyWeightMillis: 100}, Cases: []capability.TaskEvaluationCase{{CaseID: "case-a", Prompt: "one", EvidenceArtifactRef: "artifact-a"}, {CaseID: "case-b", Prompt: "two", EvidenceArtifactRef: "artifact-b"}}, BindingIDs: []capability.CapabilityBindingID{"binding-a"}, DispatchIDs: []string{"dispatch-a"}, IdempotencyPrefix: "eval", Budget: work.GraphBudget{MaxParallel: 1, MaxAttempts: 2}, }, Intents: []work.AgentDispatchIntent{{DispatchID: "dispatch-a", IdempotencyKey: "idem-a", DelegationID: "delegation-a", RunID: "run-a", ImplementationID: "impl", ImplementationRevision: 1, SourceRevision: "source", WorktreeID: "tree", VerificationPlanRef: "verify", CreatedAtUnixMillis: 1}}, } if _, _, err := AdmitAndScheduleTaskEvaluation(context.Background(), store, request); err != nil { t.Fatal(err) } if len(store.intents) != 1 || store.intents[0].TaskEvaluationProfileID != "profile-general" || store.intents[0].TaskEvaluationBindingID != "binding-a" || len(store.intents[0].TaskEvaluationCaseIDs) != 2 || store.intents[0].SpecialistPurpose != string(work.GraphNodePurposeTaskEvaluation) || store.intents[0].TaskEvaluationExecution == nil || store.intents[0].TaskEvaluationExecution.GraphRevision != 2 { t.Fatalf("specialist context was not carried into dispatch: %+v", store.intents) } } func TestExpandAndScheduleTaskEvaluationCreatesAdditionalDurableGraph(t *testing.T) { store := &taskGraphStore{} additional := capability.TaskEvaluationProfile{ID: "profile-additional", Revision: 1, CapabilityID: "capability-chat", Stage: capability.TaskEvaluationAdditional, ParentProfileID: "profile-general", TriggerEvidenceRef: "artifact-unresolved", BenchmarkVersion: "additional-v1", RequiredCaseIDs: []string{"case-a:reproduce", "case-a:address-failure"}, QualityWeightMillis: 600, ReliabilityWeightMillis: 300, LatencyWeightMillis: 100} request := TaskEvaluationExpansionScheduleRequest{ Profile: additional, Cases: []capability.TaskEvaluationCase{{CaseID: "case-a:reproduce", Prompt: "проверь доступность", EvidenceArtifactRef: "artifact-unresolved"}, {CaseID: "case-a:address-failure", Prompt: "проверь доступность; обработай неизвестный результат", EvidenceArtifactRef: "artifact-unresolved"}}, ParentGraphState: work.GraphCompleted, TriggerEvidenceRef: "artifact-unresolved", Plan: work.TaskEvaluationGraphRequest{ GraphID: "graph-additional", BindingIDs: []capability.CapabilityBindingID{"binding-a"}, DispatchIDs: []string{"dispatch-additional"}, IdempotencyPrefix: "additional", Budget: work.GraphBudget{MaxParallel: 1, MaxAttempts: 2}, ParentGraphID: "graph-general", ParentGraphRevision: 2, }, Intents: []work.AgentDispatchIntent{{DispatchID: "dispatch-additional", IdempotencyKey: "idem-additional", DelegationID: "delegation-a", RunID: "run-a", ImplementationID: "impl", ImplementationRevision: 1, SourceRevision: "source", WorktreeID: "tree", VerificationPlanRef: "verify", CreatedAtUnixMillis: 1}}, Now: 20, } graph, ready, err := ScheduleAdditionalTaskEvaluation(context.Background(), store, request) if err != nil || len(ready) != 1 || graph.State != work.GraphRunning { t.Fatalf("graph=%+v ready=%v err=%v", graph, ready, err) } if len(graph.Nodes) != 1 || graph.Nodes[0].TaskProfileID != "profile-additional" || len(graph.Nodes[0].CaseIDs) != 2 || graph.TriggerEvidenceRef != "artifact-unresolved" { t.Fatalf("additional graph did not preserve bounded expansion lineage: %+v", graph) } if len(store.intents) != 1 || store.intents[0].TaskEvaluationProfileID != "profile-additional" { t.Fatalf("additional dispatch context missing: %+v", store.intents) } } func TestScheduleAdditionalTaskEvaluationRejectsIncompleteLineage(t *testing.T) { store := &taskGraphStore{} _, _, err := ScheduleAdditionalTaskEvaluation(context.Background(), store, TaskEvaluationExpansionScheduleRequest{Profile: capability.TaskEvaluationProfile{Stage: capability.TaskEvaluationGeneral}}) if err == nil || len(store.intents) != 0 { t.Fatalf("incomplete expansion lineage was accepted: intents=%v err=%v", store.intents, err) } }