package main

import (
	"context"
	"github.com/yazanabuashour/openclerk/internal/runclient"
	"github.com/yazanabuashour/openclerk/internal/runner"
	"strings"
)

func verifyGraphSemanticsReference(ctx context.Context, paths evalPaths, finalMessage string, turnMetrics metrics) (verificationResult, error) {
	return verifyGraphSemanticsWorkflow(ctx, paths, finalMessage, turnMetrics, true, graphSemanticsReferenceAnswerPass(finalMessage), "final answer did not compare search, links/backlinks, graph neighborhood, markdown relationship text, and reference/defer decision")
}

func verifyGraphSemanticsRevisit(ctx context.Context, paths evalPaths, finalMessage string, turnMetrics metrics, scripted bool) (verificationResult, error) {
	assistantFailure := "final answer did not compare graph evidence, capability/ergonomics posture, and reference/defer decision"
	if scripted {
		assistantFailure = "final answer did not compare graph evidence, current-primitives safety, UX acceptability, capability/ergonomics posture, and reference/defer decision"
	}
	return verifyGraphSemanticsWorkflow(ctx, paths, finalMessage, turnMetrics, scripted, graphSemanticsRevisitAnswerPass(finalMessage, scripted), assistantFailure)
}

func verifyGraphContextCurrentPrimitivesHelp(ctx context.Context, paths evalPaths, finalMessage string, turnMetrics metrics) (verificationResult, error) {
	result, err := verifyGraphSemanticsWorkflow(ctx, paths, finalMessage, turnMetrics, true, graphContextCurrentHelpAnswerPass(finalMessage), "final answer did not compare current primitives plus help, graph_context_report, no new surface, and graph authority boundaries")
	if err != nil {
		return verificationResult{}, err
	}
	failures := []string{}
	if !result.DatabasePass {
		failures = append(failures, result.Details)
	}
	if !turnMetrics.OpenClerkRetrievalHelpUsed {
		failures = append(failures, "agent did not inspect openclerk retrieval help")
	}
	if !turnMetrics.ProvenanceEventsUsed {
		failures = append(failures, "agent did not inspect document provenance refs")
	}
	if turnMetrics.GraphContextReportUsed {
		failures = append(failures, "current-primitives-plus-help row used graph_context_report")
	}
	if !result.AssistantPass {
		failures = append(failures, "final answer did not compare current primitives plus help, graph_context_report, no new surface, and graph authority boundaries")
	}
	activityPass := result.DatabasePass &&
		turnMetrics.OpenClerkRetrievalHelpUsed &&
		turnMetrics.SearchUsed &&
		turnMetrics.ListDocumentsUsed &&
		turnMetrics.GetDocumentUsed &&
		turnMetrics.DocumentLinksUsed &&
		turnMetrics.GraphNeighborhoodUsed &&
		turnMetrics.ProjectionStatesUsed &&
		turnMetrics.ProvenanceEventsUsed &&
		!turnMetrics.GraphContextReportUsed &&
		!turnMetrics.CreateDocumentUsed &&
		!turnMetrics.AppendDocumentUsed &&
		!turnMetrics.ReplaceSectionUsed &&
		!turnMetrics.IngestSourceURLUsed &&
		!turnMetrics.IngestVideoURLUsed
	return verificationResult{
		Passed:        activityPass && result.AssistantPass,
		DatabasePass:  result.DatabasePass,
		AssistantPass: activityPass && result.AssistantPass,
		Details:       missingDetails(failures),
		Documents:     result.Documents,
	}, nil
}

func verifyGraphContextReportAction(ctx context.Context, paths evalPaths, finalMessage string, turnMetrics metrics) (verificationResult, error) {
	cfg := runclient.Config{DatabasePath: paths.DatabasePath}
	reportResult, err := runner.RunRetrievalTask(ctx, cfg, runner.RetrievalTaskRequest{
		Action: runner.RetrievalTaskActionGraphContext,
		GraphContext: runner.GraphContextOptions{
			Path:  graphSemanticsIndexPath,
			Limit: 20,
		},
	})
	if err != nil {
		return verificationResult{}, err
	}
	report := reportResult.GraphContext
	databasePass := report != nil &&
		report.SourceDocument != nil &&
		report.SourceDocument.Path == graphSemanticsIndexPath &&
		report.SourceSelection == "path_exact_match" &&
		len(report.CanonicalRelationshipText) > 0 &&
		messageContainsAll(report.CanonicalRelationshipText[0].Text, []string{"requires", "supersedes", "related to", "operationalizes"}) &&
		report.Links != nil &&
		documentLinksContainPath(report.Links.Outgoing, graphSemanticsRoutingPath) &&
		documentLinksContainPath(report.Links.Outgoing, graphSemanticsFreshnessPath) &&
		documentLinksContainPath(report.Links.Outgoing, graphSemanticsOperationsPath) &&
		documentLinksContainPath(report.Links.Incoming, graphSemanticsRoutingPath) &&
		report.Graph != nil &&
		graphContainsNodeLabels(report.Graph.Nodes, []string{"Graph Semantics Reference", "Routing", "Freshness", "Operations"}) &&
		graphContainsStructuralEdge(report.Graph.Edges) &&
		graphEdgesHaveCitations(report.Graph.Edges) &&
		graphEdgesOnlyStructural(report.Graph.Edges) &&
		report.GraphProjection != nil &&
		len(report.GraphProjection.Projections) == 1 &&
		report.GraphProjection.Projections[0].Freshness == "fresh" &&
		report.Provenance != nil &&
		eventTypesInclude(report.Provenance.Events, "document_created") &&
		graphContextCandidatesInclude(report.CandidateSurfaces, "current_primitives_plus_help") &&
		graphContextCandidatesInclude(report.CandidateSurfaces, "no_new_surface") &&
		strings.Contains(report.ValidationBoundaries, "no graph memory") &&
		strings.Contains(report.AuthorityLimits, "canonical markdown")
	assistantPass := graphContextReportActionAnswerPass(finalMessage)
	activityPass := turnMetrics.GraphContextReportUsed &&
		turnMetrics.WorkflowActionCallCount > 0 &&
		graphContextReportFirstActionAllowed(turnMetrics) &&
		turnMetrics.CommandExecutions <= 3 &&
		!turnMetrics.OpenClerkRetrievalHelpUsed &&
		!turnMetrics.SearchUsed &&
		!turnMetrics.ListDocumentsUsed &&
		!turnMetrics.GetDocumentUsed &&
		!turnMetrics.DocumentLinksUsed &&
		!turnMetrics.GraphNeighborhoodUsed &&
		!turnMetrics.ProjectionStatesUsed &&
		!turnMetrics.ProvenanceEventsUsed &&
		!turnMetrics.CreateDocumentUsed &&
		!turnMetrics.AppendDocumentUsed &&
		!turnMetrics.ReplaceSectionUsed &&
		!turnMetrics.IngestSourceURLUsed &&
		!turnMetrics.IngestVideoURLUsed

	failures := []string{}
	if !databasePass {
		failures = append(failures, "graph_context_report did not return complete cited graph context, freshness, provenance, and candidate-surface evidence")
	}
	if !turnMetrics.GraphContextReportUsed {
		failures = append(failures, "agent did not use graph_context_report")
	}
	if !graphContextReportFirstActionAllowed(turnMetrics) {
		failures = append(failures, "agent ran setup or primitives before graph_context_report")
	}
	if turnMetrics.CommandExecutions > 3 {
		failures = append(failures, "graph_context_report row used too many commands")
	}
	if turnMetrics.OpenClerkRetrievalHelpUsed || turnMetrics.SearchUsed || turnMetrics.ListDocumentsUsed || turnMetrics.GetDocumentUsed || turnMetrics.DocumentLinksUsed || turnMetrics.GraphNeighborhoodUsed || turnMetrics.ProjectionStatesUsed || turnMetrics.ProvenanceEventsUsed {
		failures = append(failures, "agent used follow-up help or primitive inspection despite graph_context_report success")
	}
	if turnMetrics.CreateDocumentUsed || turnMetrics.AppendDocumentUsed || turnMetrics.ReplaceSectionUsed || turnMetrics.IngestSourceURLUsed || turnMetrics.IngestVideoURLUsed {
		failures = append(failures, "agent used a mutating document or source action")
	}
	if !assistantPass {
		failures = append(failures, "final answer did not summarize graph_context_report fields and boundaries")
	}

	return verificationResult{
		Passed:        databasePass && assistantPass && activityPass,
		DatabasePass:  databasePass,
		AssistantPass: assistantPass && activityPass,
		Details:       missingDetails(failures),
		Documents:     []string{graphSemanticsIndexPath, graphSemanticsRoutingPath, graphSemanticsFreshnessPath, graphSemanticsOperationsPath},
	}, nil
}

func verifyGraphProductStoryExploration(ctx context.Context, paths evalPaths, finalMessage string, turnMetrics metrics) (verificationResult, error) {
	result, err := verifyGraphContextReportAction(ctx, paths, finalMessage, turnMetrics)
	if err != nil {
		return verificationResult{}, err
	}
	answerPass := graphProductStoryExplorationAnswerPass(finalMessage)
	activityPass := turnMetrics.GraphContextReportUsed &&
		graphContextReportFirstActionAllowed(turnMetrics) &&
		turnMetrics.CommandExecutions <= 3 &&
		!turnMetrics.OpenClerkRetrievalHelpUsed &&
		!turnMetrics.SearchUsed &&
		!turnMetrics.ListDocumentsUsed &&
		!turnMetrics.GetDocumentUsed &&
		!turnMetrics.DocumentLinksUsed &&
		!turnMetrics.GraphNeighborhoodUsed &&
		!turnMetrics.ProjectionStatesUsed &&
		!turnMetrics.ProvenanceEventsUsed &&
		!turnMetrics.CreateDocumentUsed &&
		!turnMetrics.AppendDocumentUsed &&
		!turnMetrics.ReplaceSectionUsed &&
		!turnMetrics.IngestSourceURLUsed &&
		!turnMetrics.IngestVideoURLUsed
	failures := []string{}
	if !result.DatabasePass {
		failures = append(failures, result.Details)
	}
	if !activityPass {
		failures = append(failures, "graph product story row did not stay on the promoted graph_context_report path without follow-up primitives or writes")
	}
	if !answerPass {
		failures = append(failures, "final answer did not cover all graph product stories, candidate surfaces, posture fields, and concrete outcomes")
	}
	return verificationResult{
		Passed:        result.DatabasePass && activityPass && answerPass,
		DatabasePass:  result.DatabasePass,
		AssistantPass: activityPass && answerPass,
		Details:       missingDetails(failures),
		Documents:     result.Documents,
	}, nil
}

func verifyGraphRelationshipReportAction(ctx context.Context, paths evalPaths, finalMessage string, turnMetrics metrics) (verificationResult, error) {
	cfg := runclient.Config{DatabasePath: paths.DatabasePath}
	reportResult, err := runner.RunRetrievalTask(ctx, cfg, runner.RetrievalTaskRequest{
		Action: runner.RetrievalTaskActionGraphRelationship,
		GraphRelationship: runner.GraphRelationshipOptions{
			Path:  graphSemanticsIndexPath,
			Limit: 20,
		},
	})
	if err != nil {
		return verificationResult{}, err
	}
	report := reportResult.GraphRelationship
	databasePass := report != nil &&
		report.SourceDocument != nil &&
		report.SourceDocument.Path == graphSemanticsIndexPath &&
		report.SourceSelection == "path_exact_match" &&
		graphRelationshipPathContains(report.RelationshipPaths, "outgoing", graphSemanticsRoutingPath) &&
		graphRelationshipPathContains(report.RelationshipPaths, "incoming", graphSemanticsRoutingPath) &&
		graphRelationshipEvidenceContains(report.DirectRelationships, "requires") &&
		graphRelationshipEvidenceContains(report.DirectRelationships, "supersedes") &&
		graphRelationshipEvidenceContains(report.DirectRelationships, "related_to") &&
		graphRelationshipEvidenceContains(report.DirectRelationships, "operationalizes") &&
		len(report.DerivedRelationships) > 0 &&
		graphRelationshipTypedCandidateContains(report.TypedRelationshipCandidates, "requires") &&
		graphRelationshipTypedCandidateContains(report.TypedRelationshipCandidates, "markdown_link") &&
		graphRelationshipAuditFinding(report.AuditFindings, "stale_graph_projection", "clear") &&
		graphRelationshipAuditFinding(report.AuditFindings, "orphaned_graph_context", "clear") &&
		graphRelationshipAuditFinding(report.AuditFindings, "contradictory_relationship_text", "clear_limited") &&
		report.GraphProjection != nil &&
		len(report.GraphProjection.Projections) == 1 &&
		report.GraphProjection.Projections[0].Freshness == "fresh" &&
		report.Provenance != nil &&
		eventTypesInclude(report.Provenance.Events, "document_created") &&
		graphRelationshipCandidatesInclude(report.CandidateSurfaces, "current_primitives_plus_graph_context_report") &&
		graphRelationshipCandidatesInclude(report.CandidateSurfaces, "graph_relationship_report") &&
		graphRelationshipCandidatesInclude(report.CandidateSurfaces, "split_specialized_reports") &&
		strings.Contains(report.ValidationBoundaries, "no graph memory") &&
		strings.Contains(report.AuthorityLimits, "canonical markdown")
	answerPass := graphRelationshipReportAnswerPass(finalMessage)
	activityPass := turnMetrics.GraphRelationshipReportUsed &&
		!turnMetrics.GraphContextReportUsed &&
		turnMetrics.WorkflowActionCallCount > 0 &&
		graphRelationshipReportFirstActionAllowed(turnMetrics) &&
		turnMetrics.CommandExecutions <= 3 &&
		!turnMetrics.OpenClerkRetrievalHelpUsed &&
		!turnMetrics.SearchUsed &&
		!turnMetrics.ListDocumentsUsed &&
		!turnMetrics.GetDocumentUsed &&
		!turnMetrics.DocumentLinksUsed &&
		!turnMetrics.GraphNeighborhoodUsed &&
		!turnMetrics.ProjectionStatesUsed &&
		!turnMetrics.ProvenanceEventsUsed &&
		!turnMetrics.CreateDocumentUsed &&
		!turnMetrics.AppendDocumentUsed &&
		!turnMetrics.ReplaceSectionUsed &&
		!turnMetrics.IngestSourceURLUsed &&
		!turnMetrics.IngestVideoURLUsed

	failures := []string{}
	if !databasePass {
		failures = append(failures, "graph_relationship_report did not return complete relationship paths, direct-vs-derived evidence, typed candidates, limited audit findings, freshness, provenance, and candidate-surface evidence")
	}
	if !turnMetrics.GraphRelationshipReportUsed {
		failures = append(failures, "agent did not use graph_relationship_report")
	}
	if turnMetrics.GraphContextReportUsed {
		failures = append(failures, "agent used graph_context_report before or alongside graph_relationship_report")
	}
	if !graphRelationshipReportFirstActionAllowed(turnMetrics) {
		failures = append(failures, "agent ran setup or primitives before graph_relationship_report")
	}
	if turnMetrics.CommandExecutions > 3 {
		failures = append(failures, "graph_relationship_report row used too many commands")
	}
	if turnMetrics.OpenClerkRetrievalHelpUsed || turnMetrics.SearchUsed || turnMetrics.ListDocumentsUsed || turnMetrics.GetDocumentUsed || turnMetrics.DocumentLinksUsed || turnMetrics.GraphNeighborhoodUsed || turnMetrics.ProjectionStatesUsed || turnMetrics.ProvenanceEventsUsed {
		failures = append(failures, "agent used follow-up help or primitive inspection despite graph_relationship_report success")
	}
	if turnMetrics.CreateDocumentUsed || turnMetrics.AppendDocumentUsed || turnMetrics.ReplaceSectionUsed || turnMetrics.IngestSourceURLUsed || turnMetrics.IngestVideoURLUsed {
		failures = append(failures, "agent used a mutating document or source action")
	}
	if !answerPass {
		failures = append(failures, "final answer did not compare graph relationship report candidates and concrete outcome")
	}

	return verificationResult{
		Passed:        databasePass && answerPass && activityPass,
		DatabasePass:  databasePass,
		AssistantPass: answerPass && activityPass,
		Details:       missingDetails(failures),
		Documents:     []string{graphSemanticsIndexPath, graphSemanticsRoutingPath, graphSemanticsFreshnessPath, graphSemanticsOperationsPath},
	}, nil
}

func verifyGraphRelationshipMaintenanceAction(ctx context.Context, paths evalPaths, finalMessage string, turnMetrics metrics) (verificationResult, error) {
	cfg := runclient.Config{DatabasePath: paths.DatabasePath}
	planResult, err := runner.RunRetrievalTask(ctx, cfg, runner.RetrievalTaskRequest{
		Action: runner.RetrievalTaskActionGraphRelationshipMaintenance,
		GraphRelationshipMaintenance: runner.GraphRelationshipMaintenanceOptions{
			Path:  graphSemanticsIndexPath,
			Limit: 20,
		},
	})
	if err != nil {
		return verificationResult{}, err
	}
	plan := planResult.GraphRelationshipMaintenance
	databasePass := plan != nil &&
		plan.SourceDocument != nil &&
		plan.SourceDocument.Path == graphSemanticsIndexPath &&
		plan.SourceSelection == "path_exact_match" &&
		graphRelationshipMaintenanceActionContains(plan.ProposedActions, "typed_relationship_annotation", "candidate_requires_approval") &&
		strings.Contains(plan.CandidateSectionContent, "requires") &&
		strings.Contains(plan.NextReplaceSectionRequest, `"action":"replace_section"`) &&
		strings.Contains(plan.NextAppendDocumentRequest, `"action":"append_document"`) &&
		plan.WriteStatus == "planned_no_write" &&
		strings.Contains(plan.ApprovalBoundary, "not durable-write approval") &&
		strings.Contains(plan.DuplicateHandling, "do not create duplicate") &&
		strings.Contains(plan.RollbackAuditPath, "git_lifecycle_report") &&
		len(plan.FailureModes) > 0 &&
		plan.GraphProjection != nil &&
		len(plan.GraphProjection.Projections) == 1 &&
		plan.GraphProjection.Projections[0].Freshness == "fresh" &&
		graphRelationshipMaintenanceCandidatesInclude(plan.CandidateSurfaces, "current_primitives_plus_graph_relationship_report") &&
		graphRelationshipMaintenanceCandidatesInclude(plan.CandidateSurfaces, "graph_relationship_maintenance_plan") &&
		graphRelationshipMaintenanceCandidatesInclude(plan.CandidateSurfaces, "durable_semantic_graph_maintenance") &&
		strings.Contains(plan.ValidationBoundaries, "read-only maintenance plan") &&
		strings.Contains(plan.AuthorityLimits, "canonical markdown")
	answerPass := graphRelationshipMaintenanceAnswerPass(finalMessage)
	activityPass := turnMetrics.GraphRelationshipMaintenanceUsed &&
		!turnMetrics.GraphRelationshipReportUsed &&
		!turnMetrics.GraphContextReportUsed &&
		turnMetrics.WorkflowActionCallCount > 0 &&
		graphRelationshipMaintenanceFirstActionAllowed(turnMetrics) &&
		turnMetrics.CommandExecutions <= 4 &&
		!turnMetrics.OpenClerkRetrievalHelpUsed &&
		!turnMetrics.SearchUsed &&
		!turnMetrics.ListDocumentsUsed &&
		!turnMetrics.GetDocumentUsed &&
		!turnMetrics.DocumentLinksUsed &&
		!turnMetrics.GraphNeighborhoodUsed &&
		!turnMetrics.ProjectionStatesUsed &&
		!turnMetrics.ProvenanceEventsUsed &&
		!turnMetrics.CreateDocumentUsed &&
		!turnMetrics.AppendDocumentUsed &&
		!turnMetrics.ReplaceSectionUsed &&
		!turnMetrics.IngestSourceURLUsed &&
		!turnMetrics.IngestVideoURLUsed

	failures := []string{}
	if !databasePass {
		failures = append(failures, "graph_relationship_maintenance_plan did not return complete approval-gated maintenance plan evidence")
	}
	if !turnMetrics.GraphRelationshipMaintenanceUsed {
		failures = append(failures, "agent did not use graph_relationship_maintenance_plan")
	}
	if turnMetrics.GraphRelationshipReportUsed || turnMetrics.GraphContextReportUsed {
		failures = append(failures, "agent used graph report primitives before or alongside graph_relationship_maintenance_plan")
	}
	if !graphRelationshipMaintenanceFirstActionAllowed(turnMetrics) {
		failures = append(failures, "agent ran setup or primitives before graph_relationship_maintenance_plan")
	}
	if turnMetrics.CommandExecutions > 4 {
		failures = append(failures, "graph_relationship_maintenance_plan row used too many commands")
	}
	if turnMetrics.OpenClerkRetrievalHelpUsed || turnMetrics.SearchUsed || turnMetrics.ListDocumentsUsed || turnMetrics.GetDocumentUsed || turnMetrics.DocumentLinksUsed || turnMetrics.GraphNeighborhoodUsed || turnMetrics.ProjectionStatesUsed || turnMetrics.ProvenanceEventsUsed {
		failures = append(failures, "agent used follow-up help or primitive inspection despite graph_relationship_maintenance_plan success")
	}
	if turnMetrics.CreateDocumentUsed || turnMetrics.AppendDocumentUsed || turnMetrics.ReplaceSectionUsed || turnMetrics.IngestSourceURLUsed || turnMetrics.IngestVideoURLUsed {
		failures = append(failures, "agent used a mutating document or source action")
	}
	if !answerPass {
		failures = append(failures, "final answer did not compare graph relationship maintenance candidates and concrete outcome")
	}

	return verificationResult{
		Passed:        databasePass && answerPass && activityPass,
		DatabasePass:  databasePass,
		AssistantPass: answerPass && activityPass,
		Details:       missingDetails(failures),
		Documents:     []string{graphSemanticsIndexPath, graphSemanticsRoutingPath, graphSemanticsFreshnessPath, graphSemanticsOperationsPath},
	}, nil
}

func graphContextReportFirstActionAllowed(turnMetrics metrics) bool {
	if turnMetrics.WorkflowActionFirstCommandIndex <= 1 {
		return true
	}
	return turnMetrics.WorkflowActionFirstCommandIndex == 2 && turnMetrics.OpenClerkSkillCheckUsed &&
		turnMetrics.PreActionSetupDiscoveryCount == 0 &&
		turnMetrics.PreActionPrimitiveCommandCount == 0
}

func graphRelationshipReportFirstActionAllowed(turnMetrics metrics) bool {
	if turnMetrics.WorkflowActionFirstCommandIndex <= 1 {
		return true
	}
	return turnMetrics.WorkflowActionFirstCommandIndex == 2 && turnMetrics.OpenClerkSkillCheckUsed &&
		turnMetrics.PreActionSetupDiscoveryCount == 0 &&
		turnMetrics.PreActionPrimitiveCommandCount == 0
}

func graphRelationshipMaintenanceFirstActionAllowed(turnMetrics metrics) bool {
	if turnMetrics.WorkflowActionFirstCommandIndex <= 1 {
		return true
	}
	return turnMetrics.WorkflowActionFirstCommandIndex <= 3 &&
		turnMetrics.PreActionSetupDiscoveryCount == 0 &&
		turnMetrics.PreActionPrimitiveCommandCount == 0
}

func verifyGraphSemanticsWorkflow(ctx context.Context, paths evalPaths, finalMessage string, turnMetrics metrics, requireListDocuments bool, assistantPass bool, assistantFailure string) (verificationResult, error) {
	cfg := runclient.Config{DatabasePath: paths.DatabasePath}
	search, err := runner.RunRetrievalTask(ctx, cfg, runner.RetrievalTaskRequest{
		Action: runner.RetrievalTaskActionSearch,
		Search: runner.SearchOptions{Text: graphSemanticsSearchText, Limit: 10},
	})
	if err != nil {
		return verificationResult{}, err
	}
	list, err := runner.RunDocumentTask(ctx, cfg, runner.DocumentTaskRequest{
		Action: runner.DocumentTaskActionList,
		List:   runner.DocumentListOptions{PathPrefix: graphSemanticsPrefix, Limit: 10},
	})
	if err != nil {
		return verificationResult{}, err
	}

	wantedPaths := []string{graphSemanticsIndexPath, graphSemanticsRoutingPath, graphSemanticsFreshnessPath, graphSemanticsOperationsPath}
	foundPaths := map[string]bool{}
	indexDocID := ""
	onlyPrefix := true
	for _, doc := range list.Documents {
		if !strings.HasPrefix(doc.Path, graphSemanticsPrefix) {
			onlyPrefix = false
		}
		foundPaths[doc.Path] = true
		if doc.Path == graphSemanticsIndexPath {
			indexDocID = doc.DocID
		}
	}

	got, err := runner.RunDocumentTask(ctx, cfg, runner.DocumentTaskRequest{
		Action: runner.DocumentTaskActionGet,
		DocID:  indexDocID,
	})
	if err != nil {
		return verificationResult{}, err
	}
	body := ""
	if got.Document != nil {
		body = got.Document.Body
	}

	links, err := runner.RunRetrievalTask(ctx, cfg, runner.RetrievalTaskRequest{
		Action: runner.RetrievalTaskActionDocumentLinks,
		DocID:  indexDocID,
	})
	if err != nil {
		return verificationResult{}, err
	}
	hasOutgoing := links.Links != nil &&
		documentLinksContainPath(links.Links.Outgoing, graphSemanticsRoutingPath) &&
		documentLinksContainPath(links.Links.Outgoing, graphSemanticsFreshnessPath) &&
		documentLinksContainPath(links.Links.Outgoing, graphSemanticsOperationsPath) &&
		documentLinksHaveCitations(links.Links.Outgoing)
	hasIncoming := links.Links != nil &&
		documentLinksContainPath(links.Links.Incoming, graphSemanticsRoutingPath) &&
		documentLinksContainPath(links.Links.Incoming, graphSemanticsFreshnessPath) &&
		documentLinksContainPath(links.Links.Incoming, graphSemanticsOperationsPath) &&
		documentLinksHaveCitations(links.Links.Incoming)

	graph, err := runner.RunRetrievalTask(ctx, cfg, runner.RetrievalTaskRequest{
		Action: runner.RetrievalTaskActionGraph,
		DocID:  indexDocID,
		Limit:  20,
	})
	if err != nil {
		return verificationResult{}, err
	}
	hasGraph := graph.Graph != nil &&
		graphContainsNodeLabels(graph.Graph.Nodes, []string{"Graph Semantics Reference", "Routing", "Freshness", "Operations"}) &&
		graphContainsStructuralEdge(graph.Graph.Edges) &&
		graphEdgesHaveCitations(graph.Graph.Edges) &&
		graphEdgesOnlyStructural(graph.Graph.Edges)

	projections, err := runner.RunRetrievalTask(ctx, cfg, runner.RetrievalTaskRequest{
		Action: runner.RetrievalTaskActionProjectionStates,
		Projection: runner.ProjectionStateOptions{
			Projection: "graph",
			RefKind:    "document",
			RefID:      indexDocID,
			Limit:      5,
		},
	})
	if err != nil {
		return verificationResult{}, err
	}
	hasProjection := projections.Projections != nil &&
		len(projections.Projections.Projections) == 1 &&
		projections.Projections.Projections[0].Freshness == "fresh" &&
		projections.Projections.Projections[0].Details["path"] == graphSemanticsIndexPath

	failures := []string{}
	if !searchContainsPath(search, graphSemanticsIndexPath) || !searchResultHasCitations(search) {
		failures = append(failures, "search did not expose cited canonical relationship text")
	}
	for _, path := range wantedPaths {
		if !foundPaths[path] {
			failures = append(failures, "path-prefix listing did not find "+path)
		}
	}
	if !onlyPrefix || len(list.Documents) != len(wantedPaths) {
		failures = append(failures, "path-prefix listing did not stay scoped to graph semantics fixture")
	}
	if !messageContainsAll(body, []string{"requires", "supersedes", "related to", "operationalizes"}) {
		failures = append(failures, "get_document did not expose expected relationship words")
	}
	if !hasOutgoing {
		failures = append(failures, "document_links missing cited outgoing relationships")
	}
	if !hasIncoming {
		failures = append(failures, "document_links missing cited incoming backlinks")
	}
	if !hasGraph {
		failures = append(failures, "graph_neighborhood missing cited structural graph context")
	}
	if !hasProjection {
		failures = append(failures, "graph projection state missing or not fresh")
	}
	if !turnMetrics.SearchUsed {
		failures = append(failures, "agent did not use retrieval search")
	}
	if requireListDocuments && !turnMetrics.ListDocumentsUsed {
		failures = append(failures, "agent did not use list_documents")
	}
	if !turnMetrics.GetDocumentUsed {
		failures = append(failures, "agent did not use get_document")
	}
	if !turnMetrics.DocumentLinksUsed {
		failures = append(failures, "agent did not use document_links")
	}
	if !turnMetrics.GraphNeighborhoodUsed {
		failures = append(failures, "agent did not use graph_neighborhood")
	}
	if !turnMetrics.ProjectionStatesUsed {
		failures = append(failures, "agent did not inspect graph projection state")
	}
	if turnMetrics.CreateDocumentUsed || turnMetrics.AppendDocumentUsed || turnMetrics.ReplaceSectionUsed || turnMetrics.IngestSourceURLUsed || turnMetrics.IngestVideoURLUsed {
		failures = append(failures, "agent used a mutating document or source action")
	}

	if !assistantPass {
		failures = append(failures, assistantFailure)
	}

	databasePass := searchContainsPath(search, graphSemanticsIndexPath) &&
		searchResultHasCitations(search) &&
		allPathsFound(foundPaths, wantedPaths) &&
		onlyPrefix &&
		len(list.Documents) == len(wantedPaths) &&
		messageContainsAll(body, []string{"requires", "supersedes", "related to", "operationalizes"}) &&
		hasOutgoing &&
		hasIncoming &&
		hasGraph &&
		hasProjection
	activityPass := turnMetrics.SearchUsed &&
		turnMetrics.GetDocumentUsed &&
		turnMetrics.DocumentLinksUsed &&
		turnMetrics.GraphNeighborhoodUsed &&
		turnMetrics.ProjectionStatesUsed &&
		!turnMetrics.CreateDocumentUsed &&
		!turnMetrics.AppendDocumentUsed &&
		!turnMetrics.ReplaceSectionUsed &&
		!turnMetrics.IngestSourceURLUsed &&
		!turnMetrics.IngestVideoURLUsed
	if requireListDocuments {
		activityPass = activityPass && turnMetrics.ListDocumentsUsed
	}
	return verificationResult{
		Passed:        databasePass && assistantPass && activityPass,
		DatabasePass:  databasePass,
		AssistantPass: assistantPass && activityPass,
		Details:       missingDetails(failures),
		Documents:     wantedPaths,
	}, nil
}

func graphContextCandidatesInclude(candidates []runner.GraphContextCandidate, surface string) bool {
	for _, candidate := range candidates {
		if candidate.Surface == surface {
			return true
		}
	}
	return false
}

func graphRelationshipPathContains(paths []runner.GraphRelationshipPath, direction string, path string) bool {
	for _, candidate := range paths {
		if candidate.Direction == direction && candidate.Path == path && len(candidate.Citations) > 0 {
			return true
		}
	}
	return false
}

func graphRelationshipEvidenceContains(evidence []runner.GraphRelationshipEvidence, relationshipType string) bool {
	for _, candidate := range evidence {
		if candidate.RelationshipType == relationshipType && len(candidate.Citations) > 0 {
			return true
		}
	}
	return false
}

func graphRelationshipTypedCandidateContains(candidates []runner.GraphRelationshipTypeCandidate, relationshipType string) bool {
	for _, candidate := range candidates {
		if candidate.RelationshipType == relationshipType && candidate.Citation.Path != "" {
			return true
		}
	}
	return false
}

func graphRelationshipAuditFinding(findings []runner.GraphRelationshipAuditFinding, kind string, status string) bool {
	for _, finding := range findings {
		if finding.Kind == kind && finding.Status == status {
			return true
		}
	}
	return false
}

func graphRelationshipCandidatesInclude(candidates []runner.GraphRelationshipCandidate, surface string) bool {
	for _, candidate := range candidates {
		if candidate.Surface == surface {
			return true
		}
	}
	return false
}

func graphRelationshipMaintenanceActionContains(actions []runner.GraphRelationshipMaintenanceAction, kind string, status string) bool {
	for _, action := range actions {
		if action.Kind == kind && action.Status == status {
			return true
		}
	}
	return false
}

func graphRelationshipMaintenanceCandidatesInclude(candidates []runner.GraphRelationshipMaintenanceCandidate, surface string) bool {
	for _, candidate := range candidates {
		if candidate.Surface == surface {
			return true
		}
	}
	return false
}
