import { createHash, timingSafeEqual } from "node:crypto";
import type {
  AgentRunId,
  AttemptId,
  AuditRecordId,
  ChangeSetId,
  ConnectionId,
  CorrelationId,
  CredentialReferenceId,
  EventId,
  MutationContext,
  OutboxMessageId,
  PrincipalId,
  ProviderId,
  ReviewId,
  ReviewFindingId,
  VerificationEvidenceId,
  WorkspaceId,
} from "@awp/contracts";
import type {
  AgentRun,
  Attempt,
  AttemptToolCall,
  ChangeSet,
  FactoryRun,
  Review,
  ReviewFinding,
  Task,
  VerificationEvidence,
  Workspace,
} from "@awp/domain";
import { createAttemptSelectionProvenance, createCandidateManifest } from "@awp/domain";
import type { AgentProvider, WorkspaceProvider } from "./ports/providers.js";
import type { ApplicationTransaction, UnitOfWork } from "./ports/repositories.js";
import type { Clock, IdGenerator } from "./ports/runtime.js";

export interface ExecutionSelection {
  readonly accountId: string;
  readonly model?: string;
}

export interface ExecutionDispatchRequest {
  readonly factoryRun: FactoryRun;
  readonly task: Task;
  readonly context: MutationContext;
  readonly selection?: ExecutionSelection;
}

export interface ExecutionDispatcher {
  dispatch(request: ExecutionDispatchRequest): Promise<void>;
}

export interface ExecutionVerificationEvidenceInput {
  readonly name: string;
  readonly state: "passed" | "failed" | "stale" | "missing";
  readonly source: string;
  readonly observedAt: string;
  readonly required?: boolean;
  readonly details?: Readonly<Record<string, unknown>>;
}

export interface ExecutionCompletionInput {
  readonly attemptId: AttemptId;
  readonly token: string;
  readonly diff: string;
  readonly baseRevision: string;
  readonly candidateTreeDigest: string;
  readonly changedPaths: readonly string[];
  readonly changes: readonly {
    readonly path: string;
    readonly kind: "add" | "modify" | "delete";
  }[];
  readonly toolCalls: readonly AttemptToolCall[];
  readonly evidence: readonly ExecutionVerificationEvidenceInput[];
}

export interface RecordVerificationEvidenceInput extends ExecutionVerificationEvidenceInput {
  readonly changeSetId: ChangeSetId;
}

export interface RecordReviewFindingInput {
  readonly changeSetId: ChangeSetId;
  readonly severity: "blocking" | "warning" | "recommendation" | "info";
  readonly summary: string;
  readonly source?: string;
}

export interface ExecutionFailureInput {
  readonly attemptId: AttemptId;
  readonly token: string;
  readonly reason: string;
}

export interface ChangeSetMergeInput {
  readonly changeSet: ChangeSet;
  readonly factoryRun: FactoryRun;
}

export interface ChangeSetMergeAdapter {
  merge(input: ChangeSetMergeInput): Promise<string>;
}

export class MergeRefusedError extends Error {
  constructor(message: string) {
    super(message);
    this.name = "MergeRefusedError";
  }
}

function verificationSatisfied(
  changeSet: ChangeSet,
  evidence: readonly VerificationEvidence[],
  findings: readonly ReviewFinding[],
): boolean {
  const latestByCheck = new Map<string, VerificationEvidence>();
  for (const item of evidence) {
    if (item.changeSetId !== changeSet.id || item.candidateDigest !== changeSet.candidateDigest)
      continue;
    const key = `${item.source}\u0000${item.name}`;
    const previous = latestByCheck.get(key);
    if (!previous || Date.parse(item.observedAt) >= Date.parse(previous.observedAt)) {
      latestByCheck.set(key, item);
    }
  }
  const required = [...latestByCheck.values()].filter((item) => item.required);
  if (required.length === 0 || required.some((item) => item.state !== "passed")) return false;
  return !findings.some(
    (finding) =>
      finding.changeSetId === changeSet.id &&
      finding.candidateDigest === changeSet.candidateDigest &&
      finding.severity === "blocking" &&
      !finding.resolved,
  );
}

export function executionCallbackToken(secret: string, attemptId: string): string {
  if (!secret.trim()) throw new Error("Execution callback secret must not be empty");
  if (!attemptId.trim()) throw new Error("Execution callback token requires Attempt identity");
  return createHash("sha256").update(`${secret}:${attemptId}`).digest("hex");
}

export interface WorkspaceDispatchConfiguration {
  readonly profileKey: string;
  readonly callbackBaseUrl: string;
  readonly callbackSecret: string;
  readonly agentProviderId: ProviderId;
  readonly accountId?: string;
  readonly model?: string;
  readonly connectionId: ConnectionId;
  readonly credentialReferenceId: CredentialReferenceId;
  readonly forceFirstAttemptFailure?: boolean;
  readonly agentProvider?: AgentProvider;
  readonly agentConnectionId?: ConnectionId;
  readonly agentCredentialReferenceId?: CredentialReferenceId;
}

export class WorkspaceExecutionDispatcher implements ExecutionDispatcher {
  constructor(
    private readonly uow: UnitOfWork,
    private readonly workspaceProvider: WorkspaceProvider,
    private readonly ids: IdGenerator,
    private readonly clock: Clock,
    private readonly configuration: WorkspaceDispatchConfiguration,
    private readonly merger?: ChangeSetMergeAdapter,
  ) {}

  async dispatch(request: ExecutionDispatchRequest): Promise<void> {
    const selectedAccountId = request.selection?.accountId ?? this.configuration.accountId;
    if (!selectedAccountId)
      throw new Error("Execution requires an explicit provider account selection");
    const selectedModel =
      request.selection?.model ?? this.configuration.model ?? "provider-default";

    const execution = await this.uow.transaction(async (tx) => {
      const existingAgentRun = (
        await tx.agentRuns.listByProject(request.factoryRun.projectId)
      ).find(
        (candidate) =>
          candidate.factoryRunId === request.factoryRun.id && candidate.taskId === request.task.id,
      );
      if (existingAgentRun) {
        const attempts = await tx.attempts.listByAgentRunIds([existingAgentRun.id]);
        const initialAttempt = attempts.find((candidate) => candidate.selection.kind === "initial");
        if (!initialAttempt) {
          throw new Error("Existing AgentRun is missing its initial Attempt");
        }
        const existingWorkspace = await tx.workspaces.getById(initialAttempt.workspaceId);
        if (!existingWorkspace) {
          throw new Error("Existing AgentRun is missing its Workspace");
        }
        return {
          workspace: existingWorkspace,
          agentRun: existingAgentRun,
          attempt: initialAttempt,
          shouldProvision:
            initialAttempt.status === "created" &&
            (existingAgentRun.status === "queued" || existingAgentRun.status === "waiting"),
        };
      }

      const workspace: Workspace = {
        id: this.ids.next<WorkspaceId>(),
        projectId: request.factoryRun.projectId,
        revision: 1,
      };
      const agentRunId = this.ids.next<AgentRunId>();
      const agentRun: AgentRun = {
        id: agentRunId,
        factoryRunId: request.factoryRun.id,
        taskId: request.task.id,
        agentPrincipalId: `principal:agent:${agentRunId}` as PrincipalId,
        status: "queued",
        revision: 1,
      };
      const attempt: Attempt = {
        id: this.ids.next<AttemptId>(),
        agentRunId: agentRun.id,
        workspaceId: workspace.id,
        status: "created",
        providerId: this.configuration.agentProviderId,
        accountId: selectedAccountId,
        model: selectedModel,
        selection: createAttemptSelectionProvenance({
          kind: "initial",
          reason: "automatic first legal Task dispatch",
        }),
        revision: 1,
      };

      await tx.workspaces.insert(workspace);
      await tx.agentRuns.insert(agentRun);
      await tx.attempts.insert(attempt);
      const currentFactoryRun =
        (await tx.factoryRuns.getById(request.factoryRun.id)) ?? request.factoryRun;
      await tx.factoryRuns.update({
        ...currentFactoryRun,
        status: "starting",
        reason: "Provisioning K3s Workspace",
        revision: currentFactoryRun.revision + 1,
      });
      await this.record(tx, request, "AgentRunQueued", "AgentRun", agentRun.id, 1, {
        taskId: request.task.id,
        workspaceId: workspace.id,
        attemptId: attempt.id,
      });
      return { workspace, agentRun, attempt, shouldProvision: true };
    });

    if (!execution.shouldProvision) return;
    const { workspace, agentRun, attempt } = execution;

    try {
      const fixtureEnvironment = {
        AWP_AGENT_RUN_ID: agentRun.id,
        AWP_ATTEMPT_ID: attempt.id,
        AWP_TASK_ID: request.task.id,
        AWP_BASE_IDENTITY: request.factoryRun.projectId,
        AWP_SELECTION_KIND: "initial",
        AWP_FORCE_FIRST_ATTEMPT_FAILURE: this.configuration.forceFirstAttemptFailure ? "1" : "0",
        AWP_CALLBACK_URL: `${this.configuration.callbackBaseUrl}/internal/execution/complete`,
        AWP_FAILURE_URL: `${this.configuration.callbackBaseUrl}/internal/execution/fail`,
        AWP_CALLBACK_TOKEN: this.completionToken(attempt.id),
      };
      const workspaceResult = await this.workspaceProvider.create(
        {
          operationId: request.context.operationId,
          idempotencyKey: `${request.context.idempotencyKey}:workspace:${workspace.id}`,
          correlationId: request.context.correlationId,
          authority: request.context.authority,
          connectionId: this.configuration.connectionId,
          credentialReferenceId: this.configuration.credentialReferenceId,
        },
        workspace.id,
        this.configuration.profileKey,
        agentRun.id,
        {
          environment:
            this.configuration.agentProvider === undefined
              ? fixtureEnvironment
              : {
                  AWP_AGENT_RUN_ID: agentRun.id,
                  AWP_ATTEMPT_ID: attempt.id,
                  AWP_TASK_ID: request.task.id,
                },
        },
      );
      const agentResult = await this.startAgentAttempt(
        request.context,
        attempt,
        agentRun.id,
        request.task.id,
        workspace.id,
        "initial",
      );
      await this.uow.transaction(async (tx) => {
        const currentFactoryRun = await tx.factoryRuns.getById(request.factoryRun.id);
        const currentAgentRun = await tx.agentRuns.getById(agentRun.id);
        const currentAttempt = await tx.attempts.getById(attempt.id);
        if (!currentFactoryRun || !currentAgentRun || !currentAttempt) {
          throw new Error("Execution state disappeared during Workspace provisioning");
        }
        if (currentAgentRun.status === "active" && currentAttempt.status === "running") return;
        await tx.factoryRuns.update({
          ...currentFactoryRun,
          status: "running",
          reason: "K3s Workspace accepted",
          revision: currentFactoryRun.revision + 1,
        });
        await tx.agentRuns.update({
          ...currentAgentRun,
          status: "active",
          revision: currentAgentRun.revision + 1,
        });
        await tx.attempts.update({
          ...currentAttempt,
          status: "running",
          revision: currentAttempt.revision + 1,
        });
        await this.record(
          tx,
          request,
          "AgentRunStarted",
          "AgentRun",
          agentRun.id,
          currentAgentRun.revision + 1,
          {
            taskId: request.task.id,
            workspaceId: workspace.id,
            attemptId: attempt.id,
            workspaceProviderState: workspaceResult.value.state,
            workspaceProviderReferences: workspaceResult.references,
            ...(agentResult === undefined
              ? {}
              : {
                  agentProviderState: agentResult.value.state,
                  agentProviderReferences: agentResult.references,
                }),
          },
        );
      });
    } catch (error) {
      const reason = error instanceof Error ? error.message : "Workspace provisioning failed";
      await this.uow.transaction(async (tx) => {
        const currentFactoryRun = await tx.factoryRuns.getById(request.factoryRun.id);
        const currentAgentRun = await tx.agentRuns.getById(agentRun.id);
        const currentAttempt = await tx.attempts.getById(attempt.id);
        if (currentFactoryRun) {
          await tx.factoryRuns.update({
            ...currentFactoryRun,
            status: "retryable",
            reason,
            revision: currentFactoryRun.revision + 1,
          });
        }
        if (currentAgentRun) {
          await tx.agentRuns.update({
            ...currentAgentRun,
            status: "waiting",
            reason,
            revision: currentAgentRun.revision + 1,
          });
        }
        if (currentAttempt && currentAttempt.status !== "terminal") {
          await tx.attempts.update({
            ...currentAttempt,
            status: "created",
            reason,
            revision: currentAttempt.revision + 1,
          });
        }
        await this.record(
          tx,
          request,
          "AgentRunProvisioningFailed",
          "AgentRun",
          agentRun.id,
          (currentAgentRun?.revision ?? 1) + 1,
          {
            taskId: request.task.id,
            workspaceId: workspace.id,
            attemptId: attempt.id,
            reason,
          },
        );
      });
      throw error;
    }
  }

  async failAttempt(input: ExecutionFailureInput, context: MutationContext): Promise<Attempt> {
    const expected = this.completionToken(input.attemptId);
    const suppliedBuffer = Buffer.from(input.token);
    const expectedBuffer = Buffer.from(expected);
    if (
      suppliedBuffer.length !== expectedBuffer.length ||
      !timingSafeEqual(suppliedBuffer, expectedBuffer)
    ) {
      throw new Error("Invalid Attempt failure token");
    }
    const reason = input.reason.trim();
    if (!reason) throw new Error("Attempt failure reason must not be empty");
    if (!this.workspaceProvider.replaceCompute) {
      throw new Error("Workspace provider does not support preserved-WIP retry");
    }

    const retryDecision = await this.uow.transaction(async (tx) => {
      const failed = await tx.attempts.getById(input.attemptId);
      if (!failed) throw new Error("Attempt does not exist");
      const history = await tx.attempts.listByAgentRunIds([failed.agentRunId]);
      const existing = history.find(
        (candidate) => candidate.selection.previousAttemptId === failed.id,
      );
      if (existing) {
        return { retry: existing, shouldLaunch: existing.status === "created" };
      }
      const agentRun = await tx.agentRuns.getById(failed.agentRunId);
      if (!agentRun) throw new Error("AgentRun does not exist");
      const factoryRun = await tx.factoryRuns.getById(agentRun.factoryRunId);
      const task = await tx.tasks.getById(agentRun.taskId);
      if (!factoryRun || !task) throw new Error("Execution hierarchy does not exist");
      const next: Attempt = {
        id: this.ids.next<AttemptId>(),
        agentRunId: failed.agentRunId,
        workspaceId: failed.workspaceId,
        status: "created",
        ...(failed.providerId === undefined ? {} : { providerId: failed.providerId }),
        ...(failed.accountId === undefined ? {} : { accountId: failed.accountId }),
        ...(failed.model === undefined ? {} : { model: failed.model }),
        selection: createAttemptSelectionProvenance({
          kind: "retry",
          reason: "Automatic retry with preserved Workspace WIP",
          previousAttemptId: failed.id,
        }),
        revision: 1,
      };
      await tx.attempts.update({
        ...failed,
        status: "terminal",
        reason,
        revision: failed.revision + 1,
      });
      await tx.attempts.insert(next);
      await tx.agentRuns.update({
        ...agentRun,
        status: "waiting",
        reason: `Retrying after Attempt failure: ${reason}`,
        revision: agentRun.revision + 1,
      });
      await tx.factoryRuns.update({
        ...factoryRun,
        status: "retryable",
        reason,
        revision: factoryRun.revision + 1,
      });
      await this.record(
        tx,
        { factoryRun, task, context },
        "AttemptFailed",
        "Attempt",
        failed.id,
        failed.revision + 1,
        { reason, retryAttemptId: next.id, workspaceId: next.workspaceId },
      );
      return { retry: next, shouldLaunch: true };
    });
    const retry = retryDecision.retry;
    if (!retryDecision.shouldLaunch) return retry;

    const agentRun = await this.uow.transaction((tx) => tx.agentRuns.getById(retry.agentRunId));
    if (!agentRun) throw new Error("AgentRun disappeared before retry");
    const operationContext = {
      operationId: context.operationId,
      idempotencyKey: `${context.idempotencyKey}:retry:${retry.id}`,
      correlationId: context.correlationId,
      authority: context.authority,
      connectionId: this.configuration.connectionId,
      credentialReferenceId: this.configuration.credentialReferenceId,
    };
    await this.uow.transaction(async (tx) => {
      const current = await tx.attempts.getById(retry.id);
      if (!current) throw new Error("Retry Attempt disappeared before compute replacement");
      if (current.status !== "created") return;
      await tx.attempts.update({
        ...current,
        status: "running",
        revision: current.revision + 1,
      });
    });
    try {
      await this.workspaceProvider.replaceCompute(
        operationContext,
        retry.workspaceId,
        this.configuration.profileKey,
        retry.agentRunId,
        {
          environment:
            this.configuration.agentProvider === undefined
              ? {
                  AWP_AGENT_RUN_ID: retry.agentRunId,
                  AWP_ATTEMPT_ID: retry.id,
                  AWP_TASK_ID: agentRun.taskId,
                  AWP_SELECTION_KIND: "retry",
                  AWP_CALLBACK_URL: `${this.configuration.callbackBaseUrl}/internal/execution/complete`,
                  AWP_FAILURE_URL: `${this.configuration.callbackBaseUrl}/internal/execution/fail`,
                  AWP_CALLBACK_TOKEN: this.completionToken(retry.id),
                }
              : {
                  AWP_AGENT_RUN_ID: retry.agentRunId,
                  AWP_ATTEMPT_ID: retry.id,
                  AWP_TASK_ID: agentRun.taskId,
                },
        },
      );
      await this.startAgentAttempt(
        context,
        retry,
        retry.agentRunId,
        agentRun.taskId,
        retry.workspaceId,
        "retry",
      );
    } catch (error) {
      await this.uow.transaction(async (tx) => {
        const current = await tx.attempts.getById(retry.id);
        if (!current || current.status !== "running") return;
        await tx.attempts.update({
          ...current,
          status: "created",
          revision: current.revision + 1,
        });
      });
      throw error;
    }
    return this.uow.transaction(async (tx) => {
      const currentAttempt = await tx.attempts.getById(retry.id);
      const currentAgentRun = await tx.agentRuns.getById(retry.agentRunId);
      const factoryRun = currentAgentRun
        ? await tx.factoryRuns.getById(currentAgentRun.factoryRunId)
        : undefined;
      if (!currentAttempt || !currentAgentRun || !factoryRun) {
        throw new Error("Retry state disappeared");
      }
      await tx.agentRuns.update({
        ...currentAgentRun,
        status: "active",
        reason: "Retry Attempt running with preserved Workspace WIP",
        revision: currentAgentRun.revision + 1,
      });
      await tx.factoryRuns.update({
        ...factoryRun,
        status: "running",
        reason: "Retry Attempt running",
        revision: factoryRun.revision + 1,
      });
      return currentAttempt;
    });
  }

  async complete(input: ExecutionCompletionInput, context: MutationContext): Promise<ChangeSet> {
    const expected = this.completionToken(input.attemptId);
    const suppliedBuffer = Buffer.from(input.token);
    const expectedBuffer = Buffer.from(expected);
    if (
      suppliedBuffer.length !== expectedBuffer.length ||
      !timingSafeEqual(suppliedBuffer, expectedBuffer)
    ) {
      throw new Error("Invalid Attempt completion token");
    }
    if (!input.diff.trim()) throw new Error("Attempt completion diff must not be empty");
    if (
      input.toolCalls.length === 0 ||
      input.toolCalls.some((call) => !call.name.trim() || !call.summary.trim())
    ) {
      throw new Error("Attempt completion requires at least one named tool call");
    }
    if (
      input.evidence.length === 0 ||
      !input.evidence.some((item) => item.required !== false) ||
      input.evidence.some(
        (item) =>
          !item.name.trim() || !item.source.trim() || !Number.isFinite(Date.parse(item.observedAt)),
      )
    ) {
      throw new Error("Attempt completion requires timestamped required VerificationEvidence");
    }

    const completed = await this.uow.transaction(async (tx) => {
      const attempt = await tx.attempts.getById(input.attemptId);
      if (!attempt) throw new Error("Attempt does not exist");
      const agentRun = await tx.agentRuns.getById(attempt.agentRunId);
      if (!agentRun) throw new Error("AgentRun does not exist");
      const task = await tx.tasks.getById(agentRun.taskId);
      if (!task) throw new Error("Task does not exist");
      const factoryRun = await tx.factoryRuns.getById(agentRun.factoryRunId);
      if (!factoryRun) throw new Error("FactoryRun does not exist");
      const existing = (await tx.changeSets.listByProject(factoryRun.projectId)).find(
        (candidate) => candidate.producerAttemptId === attempt.id,
      );
      if (existing) return { changeSet: existing, workspaceId: attempt.workspaceId };

      const baseRevision = input.baseRevision.trim();
      const candidateTreeDigest = input.candidateTreeDigest.trim();
      if (!/^[0-9a-f]{40,64}$/i.test(baseRevision)) {
        throw new Error("Attempt completion requires a valid Git base revision");
      }
      if (!/^[0-9a-f]{40,64}$/i.test(candidateTreeDigest)) {
        throw new Error("Attempt completion requires a valid candidate tree digest");
      }
      const patchDigest = createHash("sha256").update(input.diff).digest("hex");
      const manifest = createCandidateManifest({
        treeDigest: candidateTreeDigest,
        patchDigest,
        changedPaths: input.changedPaths.map((path) => path.trim()),
        changes: input.changes.map((change) => ({
          path: change.path.trim(),
          kind: change.kind,
        })),
      });
      if (manifest.changedPaths.length === 0) {
        throw new Error("Attempt completion must identify at least one changed path");
      }
      const changeSet: ChangeSet = {
        id: this.ids.next<ChangeSetId>(),
        projectId: factoryRun.projectId,
        taskId: task.id,
        producerAttemptId: attempt.id,
        baseIdentity: baseRevision,
        candidateDigest: candidateTreeDigest,
        candidateManifest: manifest,
        diff: input.diff,
        revision: 1,
        status: "candidate-ready",
      };
      await tx.changeSets.insert(changeSet);
      for (const item of input.evidence) {
        const evidence: VerificationEvidence = {
          id: this.ids.next<VerificationEvidenceId>(),
          changeSetId: changeSet.id,
          candidateDigest: changeSet.candidateDigest,
          name: item.name.trim(),
          state: item.state,
          source: item.source.trim(),
          observedAt: new Date(item.observedAt).toISOString(),
          required: item.required !== false,
          ...(item.details === undefined ? {} : { details: item.details }),
        };
        await tx.verificationEvidence.insert(evidence);
      }
      const review: Review = {
        id: this.ids.next<ReviewId>(),
        changeSetId: changeSet.id,
        candidateDigest: changeSet.candidateDigest,
        reviewerPrincipalId: context.authority.principal.id,
        status: "requested",
      };
      await tx.reviews.insert(review);
      await tx.attempts.update({
        ...attempt,
        status: "terminal",
        toolCalls: input.toolCalls.map((call) => ({
          name: call.name.trim(),
          summary: call.summary.trim(),
        })),
        revision: attempt.revision + 1,
      });
      await tx.agentRuns.update({
        ...agentRun,
        status: "completed",
        reason: "ChangeSet durably collected",
        revision: agentRun.revision + 1,
      });
      await tx.factoryRuns.update({
        ...factoryRun,
        status: "running",
        reason: "ChangeSet ready for independent review while the Plan graph remains active",
        revision: factoryRun.revision + 1,
      });
      await tx.tasks.update({ ...task, status: "review", revision: task.revision + 1 });
      await this.record(
        tx,
        { factoryRun, task, context },
        "ChangeSetCreated",
        "ChangeSet",
        changeSet.id,
        1,
        {
          attemptId: attempt.id,
          agentRunId: agentRun.id,
          candidateDigest: changeSet.candidateDigest,
          reviewId: review.id,
          verificationEvidenceCount: input.evidence.length,
        },
      );
      return { changeSet, workspaceId: attempt.workspaceId };
    });
    await this.workspaceProvider.destroy(
      {
        operationId: context.operationId,
        idempotencyKey: `${context.idempotencyKey}:workspace-cleanup:${completed.workspaceId}`,
        correlationId: context.correlationId,
        authority: context.authority,
        connectionId: this.configuration.connectionId,
        credentialReferenceId: this.configuration.credentialReferenceId,
      },
      completed.workspaceId,
    );
    return completed.changeSet;
  }

  async recordVerificationEvidence(
    input: RecordVerificationEvidenceInput,
    context: MutationContext,
  ): Promise<VerificationEvidence> {
    if (
      !input.name.trim() ||
      !input.source.trim() ||
      !Number.isFinite(Date.parse(input.observedAt))
    ) {
      throw new Error("VerificationEvidence requires name, source, and observedAt");
    }
    return this.uow.transaction(async (tx) => {
      const changeSet = await tx.changeSets.getById(input.changeSetId);
      if (!changeSet) throw new Error("ChangeSet does not exist");
      const evidence: VerificationEvidence = {
        id: this.ids.next<VerificationEvidenceId>(),
        changeSetId: changeSet.id,
        candidateDigest: changeSet.candidateDigest,
        name: input.name.trim(),
        state: input.state,
        source: input.source.trim(),
        observedAt: new Date(input.observedAt).toISOString(),
        required: input.required !== false,
        ...(input.details === undefined ? {} : { details: input.details }),
      };
      await tx.verificationEvidence.insert(evidence);
      const allEvidence = await tx.verificationEvidence.listByChangeSetIds([changeSet.id]);
      const findings = await tx.reviewFindings.listByChangeSetIds([changeSet.id]);
      const reviews = await tx.reviews.listByChangeSetIds([changeSet.id]);
      const approved = reviews.some(
        (review) =>
          review.candidateDigest === changeSet.candidateDigest &&
          review.status === "submitted" &&
          review.disposition === "approved",
      );
      const ready = approved && verificationSatisfied(changeSet, allEvidence, findings);
      if (changeSet.status !== "merged") {
        const nextStatus: ChangeSet["status"] = ready ? "ready-to-merge" : "verifying";
        if (changeSet.status !== nextStatus) {
          await tx.changeSets.update({
            ...changeSet,
            status: nextStatus,
            revision: changeSet.revision + 1,
          });
        }
      }
      await this.recordChangeSetMutation(
        tx,
        changeSet,
        context,
        "VerificationEvidenceRecorded",
        "VerificationEvidence",
        evidence.id,
        {
          name: evidence.name,
          state: evidence.state,
          source: evidence.source,
          required: evidence.required,
          candidateDigest: evidence.candidateDigest,
          mergeGateSatisfied: ready,
        },
      );
      return evidence;
    });
  }

  async recordReviewFinding(
    input: RecordReviewFindingInput,
    context: MutationContext,
  ): Promise<ReviewFinding> {
    const summary = input.summary.trim();
    if (!summary) throw new Error("Review finding summary must not be empty");
    return this.uow.transaction(async (tx) => {
      const changeSet = await tx.changeSets.getById(input.changeSetId);
      if (!changeSet) throw new Error("ChangeSet does not exist");
      const finding: ReviewFinding = {
        id: this.ids.next<ReviewFindingId>(),
        changeSetId: changeSet.id,
        candidateDigest: changeSet.candidateDigest,
        severity: input.severity,
        summary,
        ...(input.source?.trim() ? { source: input.source.trim() } : {}),
        resolved: false,
      };
      await tx.reviewFindings.insert(finding);
      if (input.severity === "blocking" && changeSet.status === "ready-to-merge") {
        await tx.changeSets.update({
          ...changeSet,
          status: "verifying",
          revision: changeSet.revision + 1,
        });
      }
      await this.recordChangeSetMutation(
        tx,
        changeSet,
        context,
        "ReviewFindingRecorded",
        "ReviewFinding",
        finding.id,
        {
          severity: finding.severity,
          summary: finding.summary,
          candidateDigest: finding.candidateDigest,
        },
      );
      return finding;
    });
  }

  async approveReview(reviewId: ReviewId, context: MutationContext): Promise<Review> {
    return this.uow.transaction(async (tx) => {
      const review = await tx.reviews.getById(reviewId);
      if (!review) throw new Error("Review does not exist");
      if (review.reviewerPrincipalId !== context.authority.principal.id) {
        throw new Error("Review approval requires the assigned independent reviewer");
      }
      if (review.status === "submitted" && review.disposition === "approved") return review;
      if (review.status !== "requested" && review.status !== "reviewing") {
        throw new Error("Review is not pending");
      }
      const changeSet = await tx.changeSets.getById(review.changeSetId);
      if (!changeSet) throw new Error("ChangeSet does not exist");
      if (changeSet.candidateDigest !== review.candidateDigest) {
        throw new Error("Review candidate identity is stale");
      }
      const attempt = await tx.attempts.getById(changeSet.producerAttemptId);
      if (!attempt) throw new Error("Producing Attempt does not exist");
      const agentRun = await tx.agentRuns.getById(attempt.agentRunId);
      if (!agentRun) throw new Error("Producing AgentRun does not exist");
      const factoryRun = await tx.factoryRuns.getById(agentRun.factoryRunId);
      const task = await tx.tasks.getById(changeSet.taskId);
      if (!factoryRun || !task) throw new Error("Execution hierarchy does not exist");

      const approved: Review = {
        ...review,
        status: "submitted",
        disposition: "approved",
      };
      await tx.reviews.update(approved);
      const evidence = await tx.verificationEvidence.listByChangeSetIds([changeSet.id]);
      const findings = await tx.reviewFindings.listByChangeSetIds([changeSet.id]);
      const verified = verificationSatisfied(changeSet, evidence, findings);
      await tx.changeSets.update({
        ...changeSet,
        status: verified ? "ready-to-merge" : "verifying",
        revision: changeSet.revision + 1,
      });
      await this.record(
        tx,
        { factoryRun, task, context },
        "ReviewApproved",
        "Review",
        review.id,
        1,
        {
          changeSetId: changeSet.id,
          candidateDigest: changeSet.candidateDigest,
          reviewerPrincipalId: approved.reviewerPrincipalId,
          verificationSatisfied: verified,
        },
      );
      return approved;
    });
  }

  async requestMerge(changeSetId: ChangeSetId, context: MutationContext): Promise<ChangeSet> {
    if (!this.merger) throw new MergeRefusedError("Trusted merge is not configured");
    const input = await this.uow.transaction(async (tx) => {
      const changeSet = await tx.changeSets.getById(changeSetId);
      if (!changeSet) throw new Error("ChangeSet does not exist");
      const reviews = await tx.reviews.listByChangeSetIds([changeSet.id]);
      const approved = reviews.some(
        (review) =>
          review.candidateDigest === changeSet.candidateDigest &&
          review.status === "submitted" &&
          review.disposition === "approved",
      );
      if (!approved || changeSet.status !== "ready-to-merge") {
        throw new MergeRefusedError(
          "Merge requires an approved Review for the immutable candidate",
        );
      }
      const evidence = await tx.verificationEvidence.listByChangeSetIds([changeSet.id]);
      const findings = await tx.reviewFindings.listByChangeSetIds([changeSet.id]);
      if (!verificationSatisfied(changeSet, evidence, findings)) {
        throw new MergeRefusedError(
          "Merge requires current passing required VerificationEvidence and no unresolved blocking findings",
        );
      }
      const attempt = await tx.attempts.getById(changeSet.producerAttemptId);
      const agentRun = attempt ? await tx.agentRuns.getById(attempt.agentRunId) : undefined;
      const factoryRun = agentRun ? await tx.factoryRuns.getById(agentRun.factoryRunId) : undefined;
      const task = await tx.tasks.getById(changeSet.taskId);
      if (!attempt || !agentRun || !factoryRun || !task) {
        throw new Error("Execution hierarchy does not exist");
      }
      return { changeSet, factoryRun, task, attempt };
    });

    const resultingRevision = await this.merger.merge(input);
    const nextDispatches: ExecutionDispatchRequest[] = [];
    const merged = await this.uow.transaction(async (tx) => {
      const current = await tx.changeSets.getById(changeSetId);
      const task = await tx.tasks.getById(input.task.id);
      const currentFactoryRun = await tx.factoryRuns.getById(input.factoryRun.id);
      if (!current || !task || !currentFactoryRun) throw new Error("Merge state disappeared");
      if (current.status === "merged") return current;
      if (current.status !== "ready-to-merge") {
        throw new MergeRefusedError("ChangeSet is no longer ready to merge");
      }
      const merged: ChangeSet = { ...current, status: "merged", revision: current.revision + 1 };
      await tx.changeSets.update(merged);
      const completedTask: Task = { ...task, status: "completed", revision: task.revision + 1 };
      await tx.tasks.update(completedTask);
      const projectTasks = await tx.tasks.listByProject(input.factoryRun.projectId);
      const planTasks = projectTasks
        .filter((candidate) => candidate.planRevisionId === task.planRevisionId)
        .map((candidate) => (candidate.id === task.id ? completedTask : candidate));
      const completedIds = new Set(
        planTasks
          .filter((candidate) => candidate.status === "completed")
          .map((candidate) => candidate.id),
      );
      const activeStatuses = new Set<Task["status"]>([
        "completed",
        "cancelled",
        "dispatched",
        "queued",
        "executing",
        "review",
      ]);
      const newlyReady = planTasks
        .filter(
          (candidate) =>
            !activeStatuses.has(candidate.status) &&
            candidate.dependencyIds.every((dependencyId) => completedIds.has(dependencyId)),
        )
        .sort((left, right) => (left.position ?? 0) - (right.position ?? 0));
      const newlyReadyIds = new Set(newlyReady.map((candidate) => candidate.id));
      for (const candidate of planTasks) {
        if (candidate.id === task.id || activeStatuses.has(candidate.status)) continue;
        const nextStatus: Task["status"] = newlyReadyIds.has(candidate.id)
          ? "dispatched"
          : "blocked";
        const updated: Task = {
          ...candidate,
          status: nextStatus,
          revision: candidate.revision + 1,
        };
        await tx.tasks.update(updated);
        if (nextStatus === "dispatched") {
          nextDispatches.push({
            factoryRun: currentFactoryRun,
            task: updated,
            context,
            ...(input.attempt.accountId === undefined
              ? {}
              : {
                  selection: {
                    accountId: input.attempt.accountId,
                    ...(input.attempt.model === undefined ? {} : { model: input.attempt.model }),
                  },
                }),
          });
        }
      }
      const planComplete = planTasks.every((candidate) => candidate.status === "completed");
      if (planComplete) {
        const revision = await tx.planRevisions.getById(task.planRevisionId);
        const plan = revision ? await tx.plans.getById(revision.planId) : undefined;
        if (plan) {
          await tx.plans.update({ ...plan, status: "completed", revision: plan.revision + 1 });
        }
        await tx.factoryRuns.update({
          ...currentFactoryRun,
          status: "completed",
          reason: "All Tasks in the PlanRevision completed through trusted merge",
          revision: currentFactoryRun.revision + 1,
        });
      } else if (currentFactoryRun.status !== "running") {
        await tx.factoryRuns.update({
          ...currentFactoryRun,
          status: "running",
          reason: "Plan graph still has active or dependency-blocked Tasks",
          revision: currentFactoryRun.revision + 1,
        });
      }
      await this.record(
        tx,
        { factoryRun: currentFactoryRun, task, context },
        "MergeCompleted",
        "ChangeSet",
        merged.id,
        merged.revision,
        { resultingRevision, mergedBy: context.authority.principal.id },
      );
      return merged;
    });
    if (nextDispatches.length > 0) {
      await Promise.all(nextDispatches.map((request) => this.dispatch(request)));
    }
    return merged;
  }

  private async startAgentAttempt(
    context: MutationContext,
    attempt: Attempt,
    agentRunId: AgentRunId,
    taskId: Task["id"],
    workspaceId: WorkspaceId,
    phase: "initial" | "retry",
  ) {
    const provider = this.configuration.agentProvider;
    if (!provider) return undefined;
    const connectionId = this.configuration.agentConnectionId;
    const credentialReferenceId = this.configuration.agentCredentialReferenceId;
    if (!connectionId || !credentialReferenceId) {
      throw new Error(
        "Agent provider execution requires connection and credential-reference authority",
      );
    }
    return provider.startAttempt(
      {
        operationId: context.operationId,
        idempotencyKey: `${context.idempotencyKey}:agent:${phase}:${attempt.id}`,
        correlationId: context.correlationId,
        authority: context.authority,
        connectionId,
        credentialReferenceId,
      },
      attempt.id,
      agentRunId,
      taskId,
      workspaceId,
    );
  }

  private async recordChangeSetMutation(
    tx: ApplicationTransaction,
    changeSet: ChangeSet,
    context: MutationContext,
    type: string,
    aggregateType: string,
    aggregateId: string,
    payload: unknown,
  ): Promise<void> {
    const attempt = await tx.attempts.getById(changeSet.producerAttemptId);
    const agentRun = attempt ? await tx.agentRuns.getById(attempt.agentRunId) : undefined;
    const factoryRun = agentRun ? await tx.factoryRuns.getById(agentRun.factoryRunId) : undefined;
    const task = await tx.tasks.getById(changeSet.taskId);
    if (!attempt || !agentRun || !factoryRun || !task) {
      throw new Error("ChangeSet execution hierarchy does not exist");
    }
    await this.record(
      tx,
      { factoryRun, task, context },
      type,
      aggregateType,
      aggregateId,
      1,
      payload,
    );
  }

  private completionToken(attemptId: AttemptId): string {
    return executionCallbackToken(this.configuration.callbackSecret, String(attemptId));
  }

  private async record(
    tx: ApplicationTransaction,
    request: ExecutionDispatchRequest,
    type: string,
    aggregateType: string,
    aggregateId: string,
    aggregateRevision: number,
    payload: unknown,
  ): Promise<void> {
    const occurredAt = this.clock.now().toISOString();
    const projectId = request.factoryRun.projectId;
    const principalId = request.context.authority.principal.id;
    await tx.events.append({
      id: this.ids.next<EventId>(),
      type,
      schemaVersion: 1,
      occurredAt,
      aggregateType,
      aggregateId,
      aggregateRevision,
      projectId,
      principalId,
      correlationId: request.context.correlationId as CorrelationId,
      payload,
    });
    await tx.audit.append({
      id: this.ids.next<AuditRecordId>(),
      occurredAt,
      principalId,
      action: type,
      targetType: aggregateType,
      targetId: aggregateId,
      projectId,
      disposition: "allowed",
      correlationId: request.context.correlationId,
      safeMetadata: { operationId: request.context.operationId },
    });
    await tx.outbox.append({
      id: this.ids.next<OutboxMessageId>(),
      topic: type,
      payload: { aggregateType, aggregateId, aggregateRevision, projectId, payload },
      occurredAt,
    });
  }
}
