import { readdirSync, readFileSync } from "node:fs";
import { fileURLToPath } from "node:url";
import { sql } from "drizzle-orm";
import { createPgliteClient } from "@platform-modules/db/postgres/pglite";
import { describe, expect, it } from "vitest";
import {
  MergeRefusedError,
  WorkspaceExecutionDispatcher,
  executionCallbackToken,
  type AutoMergeContinuation,
  type Clock,
  type ExecutionDispatcher,
  type ForgeProvider,
  type IdGenerator,
  type RequiredChecksProvider,
  type TrustedMerger,
  type TrustedPublisher,
  type WorkspaceProvider,
} from "@awp/application";
import {
  ProviderFailure,
  authorityContext,
  unsafeOpaqueId,
  type AgentRunId,
  type AttemptId,
  type AwpId,
  type ChangeSetId,
  type ConnectionId,
  type CorrelationId,
  type CredentialReferenceId,
  type FactoryRunId,
  type OperationId,
  type PlanId,
  type PlanRevisionId,
  type PrincipalId,
  type ProjectId,
  type ProviderId,
  type ProviderReference,
  type ReviewFindingId,
  type TaskId,
  type VerificationEvidenceId,
  type WorkspaceId,
} from "@awp/contracts";
import { createAttemptSelectionProvenance } from "@awp/domain";
import { PostgresUnitOfWork, schema } from "@awp/persistence";

const migrationDirectory = fileURLToPath(
  new URL("../../packages/persistence/drizzle/", import.meta.url),
);

async function database() {
  const db = createPgliteClient({ schema });
  for (const name of readdirSync(migrationDirectory)
    .filter((name) => name.endsWith(".sql"))
    .sort()) {
    const migration = readFileSync(`${migrationDirectory}/${name}`, "utf8");
    for (const statement of migration.split("--> statement-breakpoint")) {
      const sqlText = statement.trim();
      if (sqlText.length > 0) await db.execute(sql.raw(sqlText));
    }
  }
  return db;
}

class TestIds implements IdGenerator {
  private sequence = 0;
  next<T extends AwpId>(): T {
    this.sequence += 1;
    return unsafeOpaqueId<T>(`verification-gate-generated-${this.sequence}`);
  }
}

const observedAt = "2026-08-22T13:00:00.000Z";
const providerId = unsafeOpaqueId<ProviderId>("provider:test-workspace");
const workspaceProvider: WorkspaceProvider = {
  async describe() {
    return {
      providerId,
      kind: "workspace",
      adapterVersion: "test",
      capabilities: [],
      authModes: [],
      resourceTypes: ["workspace"],
      healthFeatures: [],
    };
  },
  async create() {
    return { value: { state: "ready", details: {} }, references: [], observedAt };
  },
  async attestCheckpoint(_context, _workspaceId, _profileKey, digest, checkpointedAt) {
    return {
      value: { state: "checkpoint-attested", details: { checkpointDigest: digest } },
      references: [],
      observedAt: checkpointedAt,
    };
  },
  async cleanupCheckpoint() {},
  async replaceCompute() {
    return { value: { state: "ready", details: {} }, references: [], observedAt };
  },
  async destroy() {
    return { value: { state: "destroyed", details: {} }, references: [], observedAt };
  },
  async reconcile() {
    return { value: { state: "ready", details: {} }, references: [], observedAt };
  },
};

const clock: Clock = { now: () => new Date(observedAt) };
const callbackSecret = "verification-merge-gate-secret";

const trustedProviderId = unsafeOpaqueId<ProviderId>("provider:test-github");
const publicationReference: ProviderReference = {
  providerId: trustedProviderId,
  resourceType: "publication",
  nativeId: "publication:test",
  nativeRevision: "4".repeat(40),
  observedAt,
};
const mergeReference: ProviderReference = {
  providerId: trustedProviderId,
  resourceType: "merge",
  nativeId: "merge:test",
  nativeRevision: "3".repeat(40),
  observedAt,
};

function mutationContext(projectId: ProjectId, suffix: string) {
  return {
    operationId: unsafeOpaqueId<OperationId>(`operation:${suffix}`),
    correlationId: unsafeOpaqueId<CorrelationId>(`correlation:${suffix}`),
    idempotencyKey: `verification-gate:${suffix}`,
    authority: authorityContext(
      {
        id: unsafeOpaqueId<PrincipalId>("principal:verification-reviewer"),
        kind: "human",
        capabilities: [],
      },
      [],
      projectId,
    ),
  };
}

async function seedExecution(
  options: {
    trusted?: boolean;
    requiredCheckState?: "passed" | "failed" | "missing";
    requiredChecksUnavailable?: boolean;
    trackAutoMerge?: boolean;
    trackNextTaskDispatch?: boolean;
    dependentTask?: boolean;
    failFirstDependentDispatch?: boolean;
    reviewerProvisionFailures?: number;
    mergeConflictOnce?: boolean;
  } = {},
) {
  const db = await database();
  const uow = new PostgresUnitOfWork(db);
  const projectId = unsafeOpaqueId<ProjectId>("project-verification-gate");
  const planId = unsafeOpaqueId<PlanId>("plan-verification-gate");
  const planRevisionId = unsafeOpaqueId<PlanRevisionId>("revision-verification-gate");
  const taskId = unsafeOpaqueId<TaskId>("task-verification-gate");
  const dependentTaskId = unsafeOpaqueId<TaskId>("task-verification-dependent");
  const factoryRunId = unsafeOpaqueId<FactoryRunId>("factory-verification-gate");
  const agentRunId = unsafeOpaqueId<AgentRunId>("agent-verification-gate");
  const workspaceId = unsafeOpaqueId<WorkspaceId>("workspace-verification-gate");
  const attemptId = unsafeOpaqueId<AttemptId>("attempt-verification-gate");

  await uow.transaction(async (tx) => {
    await tx.projects.insert({
      id: projectId,
      name: "Verification gate",
      repositoryUrl: "https://github.com/platform-modules/awp.git",
      requiredChecks: ["CI", "ci/test"],
      status: "active",
      revision: 1,
    });
    await tx.plans.insert({
      id: planId,
      projectId,
      title: "Verification plan",
      status: "active",
      revision: 1,
    });
    await tx.planRevisions.insert({
      id: planRevisionId,
      planId,
      projectId,
      sequence: 1,
      title: "Verification plan",
      goalIds: [],
    });
    await tx.tasks.insert({
      id: taskId,
      projectId,
      planRevisionId,
      title: "Candidate task",
      status: "executing",
      dependencyIds: [],
      revision: 1,
    });
    if (options.dependentTask) {
      await tx.tasks.insert({
        id: dependentTaskId,
        projectId,
        planRevisionId,
        title: "Dependent task",
        status: "blocked",
        dependencyIds: [taskId],
        revision: 1,
      });
    }
    await tx.factoryRuns.insert({
      id: factoryRunId,
      projectId,
      planRevisionId,
      accountId: "account-verification",
      model: "model-verification",
      status: "running",
      revision: 1,
    });
    await tx.agentRuns.insert({
      id: agentRunId,
      factoryRunId,
      taskId,
      agentPrincipalId: unsafeOpaqueId<PrincipalId>("principal:verification-agent"),
      role: "coder",
      status: "active",
      revision: 1,
    });
    await tx.workspaces.insert({ id: workspaceId, projectId, revision: 1 });
    await tx.attempts.insert({
      id: attemptId,
      agentRunId,
      workspaceId,
      status: "running",
      providerId: unsafeOpaqueId<ProviderId>("provider:agent-acp"),
      accountId: "account-verification",
      model: "model-verification",
      selection: createAttemptSelectionProvenance({ kind: "initial", reason: "test" }),
      revision: 1,
    });
  });

  let workspaceDestroyCalls = 0;
  let workspaceCleanupCalls = 0;
  let dependentCreateCalls = 0;
  let reviewerCreateCalls = 0;
  let failDependent = options.failFirstDependentDispatch ?? false;
  let reviewerFailuresRemaining = options.reviewerProvisionFailures ?? 0;
  const trackedWorkspaceProvider: WorkspaceProvider = {
    ...workspaceProvider,
    async create(context, workspaceId, profileKey, agentRunId, launch) {
      if (launch?.environment.AWP_AGENT_ROLE === "reviewer") {
        reviewerCreateCalls += 1;
        if (reviewerFailuresRemaining > 0) {
          reviewerFailuresRemaining -= 1;
          throw new Error("injected reviewer provisioning failure");
        }
      }
      if (launch?.environment.AWP_TASK_ID === dependentTaskId) {
        dependentCreateCalls += 1;
        if (failDependent) {
          failDependent = false;
          throw new Error("injected dependent dispatch failure");
        }
      }
      return workspaceProvider.create(context, workspaceId, profileKey, agentRunId, launch);
    },
    async destroy() {
      workspaceDestroyCalls += 1;
      return { value: { state: "destroyed", details: {} }, references: [], observedAt };
    },
    async cleanupCheckpoint() {
      workspaceCleanupCalls += 1;
    },
  };
  let publishCalls = 0;
  let mergeCalls = 0;
  const mergePrincipalKinds: string[] = [];
  let forgeHeadRevision = "1".repeat(40);
  const forge: ForgeProvider = {
    async describe() {
      return {
        providerId: trustedProviderId,
        kind: "forge-vcs",
        adapterVersion: "test",
        capabilities: ["repository.inspect"],
        authModes: ["credential-reference"],
        resourceTypes: ["repository", "publication", "merge"],
        healthFeatures: [],
      };
    },
    async inspectRepository(_context, repositoryKey) {
      expect(repositoryKey).toBe("platform-modules/awp");
      return {
        value: { defaultBranch: "main", headRevision: forgeHeadRevision },
        references: [],
        observedAt,
      };
    },
    async reconcile() {
      return { value: { state: "published", details: {} }, references: [], observedAt };
    },
  };
  let expectedPublishCandidate = "2".repeat(40);
  const publisher: TrustedPublisher = {
    async publish(request) {
      publishCalls += 1;
      expect(request.repositoryKey).toBe("platform-modules/awp");
      expect(request.baseRevision).toBe("1".repeat(40));
      expect(request.candidateDigest).toBe(expectedPublishCandidate);
      expect(request.factoryRunId).toBe(factoryRunId);
      return {
        value: {
          state: "published",
          details: {
            repositoryKey: request.repositoryKey,
            targetRef: "refs/heads/awp/test",
            candidateDigest: request.candidateDigest,
          },
        },
        references: [publicationReference],
        observedAt,
      };
    },
    async reconcile() {
      return {
        value: { state: "published", details: {} },
        references: [publicationReference],
        observedAt,
      };
    },
  };
  let mergeConflictRemaining = options.mergeConflictOnce ?? false;
  const merger: TrustedMerger = {
    async merge(request) {
      mergeCalls += 1;
      mergePrincipalKinds.push(request.context.authority.principal.kind);
      expect(request.repositoryKey).toBe("platform-modules/awp");
      expect(request.publicationReference.nativeId).toBe(publicationReference.nativeId);
      if (mergeConflictRemaining) {
        mergeConflictRemaining = false;
        throw new ProviderFailure({
          category: "conflict-stale",
          retryable: false,
          providerId: trustedProviderId,
          safeMessage: "Repository candidate requires correction before trusted merge",
          observedAt,
        });
      }
      return {
        value: {
          changeSetId: request.changeSetId,
          resultingRevision: "3".repeat(40),
          mergeReference,
        },
        references: [mergeReference],
        observedAt,
      };
    },
    async reconcile() {
      return { value: { state: "not-merged" }, references: [], observedAt };
    },
  };
  let requiredCheckState = options.requiredCheckState ?? "passed";
  let requiredChecksUnavailable = options.requiredChecksUnavailable ?? false;
  let requiredCheckRefreshCalls = 0;
  const requiredChecksProvider: RequiredChecksProvider = {
    async describe() {
      return {
        providerId: unsafeOpaqueId<ProviderId>("provider:required-checks-test"),
        kind: "ci",
        adapterVersion: "test",
        capabilities: ["required-checks"],
        authModes: ["credential-reference"],
        resourceTypes: ["required-check-snapshot"],
        healthFeatures: [],
      };
    },
    async observeRequiredChecks(_context, repositoryKey, branch, revision) {
      requiredCheckRefreshCalls += 1;
      if (requiredChecksUnavailable) throw new Error("required checks temporarily unavailable");
      expect(repositoryKey).toBe("platform-modules/awp");
      expect(branch).toBe("main");
      expect(revision).toBe("4".repeat(40));
      const checkObservedAt = `2026-08-22T13:0${Math.min(requiredCheckRefreshCalls, 9)}:00.000Z`;
      return {
        value: {
          repositoryKey,
          branch,
          revision,
          requiredChecks: ["ci/test"],
          checks: [
            {
              name: "CI",
              state: requiredCheckState,
              observedAt: "2026-08-22T12:00:00.000Z",
              details: { providerStatus: requiredCheckState },
            },
            {
              name: "ci/test",
              state: requiredCheckState,
              observedAt: checkObservedAt,
              details: { providerStatus: requiredCheckState },
            },
          ],
        },
        references: [
          {
            providerId: unsafeOpaqueId<ProviderId>("provider:required-checks-test"),
            resourceType: "required-check-snapshot",
            nativeId: `${repositoryKey}@${revision}`,
            nativeRevision: revision,
            observedAt: checkObservedAt,
          },
        ],
        observedAt: checkObservedAt,
      };
    },
  };
  const autoMergeSchedules: Array<{
    changeSetId: ChangeSetId;
    context: ReturnType<typeof mutationContext>;
  }> = [];
  const autoMergeContinuation: AutoMergeContinuation | undefined = options.trackAutoMerge
    ? {
        async schedule(changeSetId, scheduleContext) {
          autoMergeSchedules.push({ changeSetId, context: scheduleContext });
        },
      }
    : undefined;
  const nextTaskDispatches: Parameters<ExecutionDispatcher["dispatch"]>[0][] = [];
  const nextTaskDispatcher: ExecutionDispatcher | undefined = options.trackNextTaskDispatch
    ? {
        async dispatch(request) {
          nextTaskDispatches.push(request);
        },
      }
    : undefined;

  const dispatcher = new WorkspaceExecutionDispatcher(
    uow,
    trackedWorkspaceProvider,
    new TestIds(),
    clock,
    {
      profileKey: "verification-test",
      callbackBaseUrl: "http://control-plane.test",
      callbackSecret,
      agentProviderId: unsafeOpaqueId<ProviderId>("provider:agent-acp"),
      connectionId: unsafeOpaqueId<ConnectionId>("connection:workspace-test"),
      credentialReferenceId: unsafeOpaqueId<CredentialReferenceId>("credential:workspace-test"),
    },
    options.trusted === false
      ? undefined
      : {
          forge,
          publisher,
          merger,
          repositoryKey(repositoryUrl) {
            const match = /^https:\/\/github\.com\/([^/]+)\/([^/]+?)(?:\.git)?$/u.exec(
              repositoryUrl,
            );
            if (!match) throw new Error("test repository parser rejected URL");
            return `${match[1]}/${match[2]}`;
          },
          connectionId: unsafeOpaqueId<ConnectionId>("connection:github-test"),
          credentialReferenceId: unsafeOpaqueId<CredentialReferenceId>("credential:github-test"),
        },
    options.trusted === false
      ? undefined
      : {
          provider: requiredChecksProvider,
          connectionId: unsafeOpaqueId<ConnectionId>("connection:required-checks-test"),
          credentialReferenceId: unsafeOpaqueId<CredentialReferenceId>(
            "credential:required-checks-test",
          ),
        },
    autoMergeContinuation,
    nextTaskDispatcher,
  );

  return {
    db,
    uow,
    dispatcher,
    projectId,
    planRevisionId,
    taskId,
    factoryRunId,
    attemptId,
    workspaceId,
    dependentTaskId,
    context: mutationContext(projectId, "main"),
    publishCalls: () => publishCalls,
    mergeCalls: () => mergeCalls,
    mergePrincipalKinds: () => [...mergePrincipalKinds],
    autoMergeSchedules: () => [...autoMergeSchedules],
    nextTaskDispatches: () => [...nextTaskDispatches],
    requiredCheckRefreshCalls: () => requiredCheckRefreshCalls,
    workspaceDestroyCalls: () => workspaceDestroyCalls,
    workspaceCleanupCalls: () => workspaceCleanupCalls,
    dependentCreateCalls: () => dependentCreateCalls,
    reviewerCreateCalls: () => reviewerCreateCalls,
    setRequiredCheckState(state: "passed" | "failed" | "missing") {
      requiredCheckState = state;
    },
    setRequiredChecksUnavailable(value: boolean) {
      requiredChecksUnavailable = value;
    },
    setForgeHead(revision: string) {
      forgeHeadRevision = revision;
    },
    setExpectedPublishCandidate(digest: string) {
      expectedPublishCandidate = digest;
    },
  };
}

async function submitIndependentReview(
  setup: Awaited<ReturnType<typeof seedExecution>>,
  changeSetId: ChangeSetId,
) {
  const [review] = await setup.uow.transaction((tx) =>
    tx.reviews.listByChangeSetIds([changeSetId]),
  );
  expect(review?.reviewerAgentRunId).toBeDefined();
  const reviewerAgentRun = await setup.uow.transaction((tx) =>
    tx.agentRuns.getById(review!.reviewerAgentRunId!),
  );
  expect(reviewerAgentRun).toMatchObject({
    role: "reviewer",
    agentPrincipalId: review!.reviewerPrincipalId,
  });
  const producerAgentRun = await setup.uow.transaction(async (tx) => {
    const changeSet = await tx.changeSets.getById(changeSetId);
    const attempt = changeSet ? await tx.attempts.getById(changeSet.producerAttemptId) : undefined;
    return attempt ? tx.agentRuns.getById(attempt.agentRunId) : undefined;
  });
  expect(producerAgentRun?.role).toBe("coder");
  expect(reviewerAgentRun?.agentPrincipalId).not.toBe(producerAgentRun?.agentPrincipalId);
  expect(reviewerAgentRun?.id).not.toBe(producerAgentRun?.id);
  const [reviewerAttempt] = await setup.uow.transaction((tx) =>
    tx.attempts.listByAgentRunIds([reviewerAgentRun!.id]),
  );
  expect(reviewerAttempt).toMatchObject({ status: "running", accountId: "account-verification" });
  await expect(setup.dispatcher.approveReview(review!.id, setup.context)).rejects.toThrow(
    /reviewer Attempt callback/,
  );
  const changeSet = await setup.uow.transaction((tx) => tx.changeSets.getById(changeSetId));
  const submitted = await setup.dispatcher.completeReview(
    {
      attemptId: reviewerAttempt!.id,
      reviewId: review!.id,
      token: executionCallbackToken(callbackSecret, reviewerAttempt!.id),
      candidateDigest: changeSet!.candidateDigest,
      workspaceCheckpointDigest: changeSet!.candidateDigest,
      workspaceCheckpointedAt: observedAt,
      disposition: "approved",
      toolCalls: [{ name: "review", summary: "Reviewed the exact immutable candidate" }],
      findings: [],
    },
    mutationContext(setup.projectId, `review-${String(changeSetId)}`),
  );
  expect(submitted).toMatchObject({ status: "submitted", disposition: "approved" });
  return { review: submitted, reviewerAgentRun, reviewerAttempt };
}

describe("authoritative VerificationEvidence merge gate", () => {
  it("persists evidence, fails closed, accepts a newer passing observation, and honors blocking findings", async () => {
    const setup = await seedExecution();
    const candidateDigest = "2".repeat(40);
    const baseRevision = "1".repeat(40);

    const completion = {
      attemptId: setup.attemptId,
      token: executionCallbackToken(callbackSecret, setup.attemptId),
      diff: "diff --git a/README.md b/README.md\n--- a/README.md\n+++ b/README.md\n@@ -1 +1 @@\n-old\n+new\n",
      baseRevision,
      candidateTreeDigest: candidateDigest,
      workspaceCheckpointDigest: candidateDigest,
      workspaceCheckpointedAt: observedAt,
      changedPaths: ["README.md"],
      changes: [{ path: "README.md", kind: "modify" as const }],
      toolCalls: [{ name: "edit", summary: "Updated README" }],
      evidence: [
        {
          name: "required-check",
          state: "failed" as const,
          source: "workspace:test",
          observedAt: "2026-08-22T13:00:00.000Z",
          required: true,
        },
      ],
    };
    const changeSet = await setup.dispatcher.complete(completion, setup.context);

    const persistedEvidence = (
      await setup.uow.transaction((tx) =>
        tx.verificationEvidence.listByChangeSetIds([changeSet.id]),
      )
    ).filter((evidence) => evidence.source === "workspace:test");
    expect(persistedEvidence).toHaveLength(1);
    expect(persistedEvidence[0]).toMatchObject({
      changeSetId: changeSet.id,
      candidateDigest,
      state: "failed",
      required: true,
    });
    expect((await setup.uow.transaction((tx) => tx.tasks.getById(setup.taskId)))?.status).toBe(
      "review",
    );

    const [review] = await setup.uow.transaction((tx) =>
      tx.reviews.listByChangeSetIds([changeSet.id]),
    );
    expect(review?.reviewerAgentRunId).toBeDefined();
    expect(changeSet.status).toBe("reviewing");
    expect(changeSet.repositoryKey).toBe("platform-modules/awp");
    expect(changeSet.publicationReference?.nativeId).toBe(publicationReference.nativeId);
    expect(changeSet.targetReference).toBe("refs/heads/main");
    expect(changeSet.targetRevision).toBe(baseRevision);
    expect(setup.publishCalls()).toBe(1);
    const replayed = await setup.dispatcher.complete(
      completion,
      mutationContext(setup.projectId, "completion-replay"),
    );
    expect(replayed.id).toBe(changeSet.id);
    expect(setup.publishCalls()).toBe(1);
    const reviewerRuns = (
      await setup.uow.transaction((tx) => tx.agentRuns.listByProject(setup.projectId))
    ).filter((run) => run.role === "reviewer");
    expect(reviewerRuns).toHaveLength(1);
    expect(
      await setup.uow.transaction((tx) => tx.reviews.listByChangeSetIds([changeSet.id])),
    ).toHaveLength(1);
    expect(
      (
        await setup.uow.transaction((tx) =>
          tx.verificationEvidence.listByChangeSetIds([changeSet.id]),
        )
      ).filter((evidence) => evidence.source === "workspace:test"),
    ).toHaveLength(1);
    await setup.uow.transaction((tx) =>
      tx.verificationEvidence.insert({
        id: unsafeOpaqueId<VerificationEvidenceId>("verification-stale-other-candidate"),
        changeSetId: changeSet.id,
        candidateDigest: "f".repeat(40),
        name: "required-check",
        state: "passed",
        source: "workspace:test",
        observedAt: "2026-08-22T13:02:00.000Z",
        required: true,
      }),
    );
    await submitIndependentReview(setup, changeSet.id);
    expect((await setup.uow.transaction((tx) => tx.changeSets.getById(changeSet.id)))?.status).toBe(
      "verifying",
    );
    await expect(setup.dispatcher.requestMerge(changeSet.id, setup.context)).rejects.toBeInstanceOf(
      MergeRefusedError,
    );
    expect(setup.mergeCalls()).toBe(0);

    await expect(
      setup.dispatcher.recordVerificationEvidence(
        {
          changeSetId: changeSet.id,
          name: "bare-self-report",
          state: "passed",
          source: "workspace:self-report",
          observedAt: "2026-08-22T13:00:30.000Z",
          required: true,
        },
        mutationContext(setup.projectId, "bare-evidence"),
      ),
    ).rejects.toThrow(/immutable evidence reference/);

    await setup.dispatcher.recordVerificationEvidence(
      {
        changeSetId: changeSet.id,
        name: "required-check",
        state: "passed",
        source: "workspace:test",
        observedAt: "2026-08-22T13:01:00.000Z",
        required: true,
        reference: {
          kind: "workspace-checkpoint",
          workspaceId: setup.workspaceId,
          checkpointDigest: candidateDigest,
          observedAt,
        },
      },
      mutationContext(setup.projectId, "passing-evidence"),
    );
    expect((await setup.uow.transaction((tx) => tx.changeSets.getById(changeSet.id)))?.status).toBe(
      "ready-to-merge",
    );

    const finding = await setup.dispatcher.recordReviewFinding(
      {
        changeSetId: changeSet.id,
        severity: "blocking",
        summary: "Candidate needs one explicit reviewer concern resolved",
        source: "review:test",
      },
      mutationContext(setup.projectId, "finding"),
    );
    expect((await setup.uow.transaction((tx) => tx.changeSets.getById(changeSet.id)))?.status).toBe(
      "verifying",
    );
    await expect(setup.dispatcher.requestMerge(changeSet.id, setup.context)).rejects.toBeInstanceOf(
      MergeRefusedError,
    );
    expect(setup.mergeCalls()).toBe(0);

    await setup.dispatcher.resolveReviewFinding(
      unsafeOpaqueId<ReviewFindingId>(finding.id),
      mutationContext(setup.projectId, "resolve-finding"),
    );
    expect((await setup.uow.transaction((tx) => tx.changeSets.getById(changeSet.id)))?.status).toBe(
      "ready-to-merge",
    );

    const merged = await setup.dispatcher.requestMerge(
      changeSet.id,
      mutationContext(setup.projectId, "merge"),
    );
    expect(merged.status).toBe("merged");
    expect((await setup.uow.transaction((tx) => tx.tasks.getById(setup.taskId)))?.status).toBe(
      "completed",
    );
    expect(merged.resultingRevision).toBe("3".repeat(40));
    expect(merged.mergeReference?.nativeId).toBe(mergeReference.nativeId);
    expect(setup.mergeCalls()).toBe(1);
    const replay = await setup.dispatcher.requestMerge(
      changeSet.id,
      mutationContext(setup.projectId, "merge-replay"),
    );
    expect(replay.resultingRevision).toBe(merged.resultingRevision);
    expect(setup.mergeCalls()).toBe(1);
    expect(setup.workspaceCleanupCalls()).toBe(2);
    expect(setup.workspaceDestroyCalls()).toBe(4);
    const cleanedWorkspaces = await setup.db.select().from(schema.workspaces);
    expect(cleanedWorkspaces).toHaveLength(2);
    expect(cleanedWorkspaces.every((workspace) => workspace.cleanedAt !== null)).toBe(true);
    expect(
      (await setup.db.select().from(schema.verificationEvidence)).filter(
        (evidence) =>
          evidence.source === "workspace:test" && evidence.candidateDigest === candidateDigest,
      ),
    ).toHaveLength(2);
    expect(await setup.db.select().from(schema.reviewFindings)).toHaveLength(1);
    await setup.db.$client.close();
  }, 15_000);

  it("continues approved Review through durable automatic trusted merge with zero Approval rows", async () => {
    const setup = await seedExecution({
      requiredCheckState: "passed",
      trackAutoMerge: true,
      trackNextTaskDispatch: true,
    });
    const dependentTaskId = unsafeOpaqueId<TaskId>("task-verification-dependent");
    await setup.uow.transaction(async (tx) => {
      await tx.tasks.insert({
        id: dependentTaskId,
        projectId: setup.projectId,
        planRevisionId: setup.planRevisionId,
        title: "Dependent candidate task",
        status: "blocked",
        dependencyIds: [setup.taskId],
        position: 2,
        revision: 1,
      });
    });

    const candidateDigest = "2".repeat(40);
    const changeSet = await setup.dispatcher.complete(
      {
        attemptId: setup.attemptId,
        token: executionCallbackToken(callbackSecret, setup.attemptId),
        diff: "diff --git a/README.md b/README.md\n--- a/README.md\n+++ b/README.md\n@@ -1 +1 @@\n-old\n+new\n",
        baseRevision: "1".repeat(40),
        candidateTreeDigest: candidateDigest,
        workspaceCheckpointDigest: candidateDigest,
        workspaceCheckpointedAt: observedAt,
        changedPaths: ["README.md"],
        changes: [{ path: "README.md", kind: "modify" }],
        toolCalls: [{ name: "edit", summary: "Updated README" }],
        evidence: [
          {
            name: "workspace-check",
            state: "passed",
            source: "workspace:test",
            observedAt,
            required: true,
          },
        ],
      },
      setup.context,
    );

    await submitIndependentReview(setup, changeSet.id);
    expect(setup.autoMergeSchedules()).toHaveLength(1);
    expect(setup.autoMergeSchedules()[0]?.changeSetId).toBe(changeSet.id);
    expect((await setup.uow.transaction((tx) => tx.changeSets.getById(changeSet.id)))?.status).toBe(
      "ready-to-merge",
    );

    const reconciled = await setup.dispatcher.reconcileAutoMerge(
      changeSet.id,
      mutationContext(setup.projectId, "durable-automerge"),
    );
    expect(reconciled).toMatchObject({ state: "merged", changeSetId: changeSet.id });
    expect(setup.mergeCalls()).toBe(1);
    expect(setup.mergePrincipalKinds()).toEqual(["system"]);
    expect(await setup.db.select().from(schema.approvals)).toHaveLength(0);
    expect(setup.nextTaskDispatches()).toHaveLength(1);
    expect(setup.nextTaskDispatches()[0]?.factoryRun.id).toBe(setup.factoryRunId);
    expect(setup.nextTaskDispatches()[0]?.task.id).toBe(dependentTaskId);

    const replay = await setup.dispatcher.reconcileAutoMerge(
      changeSet.id,
      mutationContext(setup.projectId, "durable-automerge-replay"),
    );
    expect(replay.state).toBe("merged");
    expect(setup.mergeCalls()).toBe(1);
    expect(setup.nextTaskDispatches()).toHaveLength(2);
    expect(
      new Set(
        setup
          .nextTaskDispatches()
          .map((request) => `${String(request.factoryRun.id)}:${String(request.task.id)}`),
      ),
    ).toEqual(new Set([`${String(setup.factoryRunId)}:${String(dependentTaskId)}`]));
    await setup.db.$client.close();
  }, 15_000);

  it("ingests repository-required checks for the trusted publication commit and refreshes them before merge", async () => {
    const setup = await seedExecution({ requiredCheckState: "missing" });
    const candidateDigest = "2".repeat(40);
    const changeSet = await setup.dispatcher.complete(
      {
        attemptId: setup.attemptId,
        token: executionCallbackToken(callbackSecret, setup.attemptId),
        diff: "diff --git a/README.md b/README.md\n--- a/README.md\n+++ b/README.md\n@@ -1 +1 @@\n-old\n+new\n",
        baseRevision: "1".repeat(40),
        candidateTreeDigest: candidateDigest,
        workspaceCheckpointDigest: candidateDigest,
        workspaceCheckpointedAt: observedAt,
        changedPaths: ["README.md"],
        changes: [{ path: "README.md", kind: "modify" }],
        toolCalls: [{ name: "edit", summary: "Updated README" }],
        evidence: [
          {
            name: "workspace-check",
            state: "passed",
            source: "workspace:test",
            observedAt,
            required: true,
          },
        ],
      },
      setup.context,
    );
    expect(setup.requiredCheckRefreshCalls()).toBe(1);
    const providerEvidence = (
      await setup.uow.transaction((tx) =>
        tx.verificationEvidence.listByChangeSetIds([changeSet.id]),
      )
    ).filter((evidence) => evidence.source === "repository-required-checks:platform-modules/awp");
    expect(providerEvidence).toEqual(
      expect.arrayContaining([
        expect.objectContaining({ name: "policy", state: "passed", required: true }),
        expect.objectContaining({ name: "required:CI", state: "missing", required: true }),
        expect.objectContaining({ name: "required:ci/test", state: "missing", required: true }),
      ]),
    );
    const check = providerEvidence.find((evidence) => evidence.name === "required:ci/test");
    expect(check?.candidateDigest).toBe(candidateDigest);
    expect(check?.details).toMatchObject({
      candidateDigest,
      publicationRevision: "4".repeat(40),
      branch: "main",
    });

    await submitIndependentReview(setup, changeSet.id);
    expect(setup.requiredCheckRefreshCalls()).toBe(2);
    expect((await setup.uow.transaction((tx) => tx.changeSets.getById(changeSet.id)))?.status).toBe(
      "verifying",
    );

    setup.setRequiredCheckState("passed");
    await setup.dispatcher.refreshRequiredChecks(
      changeSet.id,
      mutationContext(setup.projectId, "required-check-refresh"),
    );
    expect(setup.requiredCheckRefreshCalls()).toBe(3);
    expect((await setup.uow.transaction((tx) => tx.changeSets.getById(changeSet.id)))?.status).toBe(
      "ready-to-merge",
    );

    setup.setRequiredCheckState("failed");
    await expect(
      setup.dispatcher.requestMerge(
        changeSet.id,
        mutationContext(setup.projectId, "required-check-failed-before-merge"),
      ),
    ).rejects.toBeInstanceOf(MergeRefusedError);
    expect(setup.requiredCheckRefreshCalls()).toBe(4);
    expect((await setup.uow.transaction((tx) => tx.changeSets.getById(changeSet.id)))?.status).toBe(
      "verifying",
    );
    expect(setup.mergeCalls()).toBe(0);

    setup.setRequiredCheckState("passed");
    const merged = await setup.dispatcher.requestMerge(
      changeSet.id,
      mutationContext(setup.projectId, "required-check-passed-before-merge"),
    );
    expect(setup.requiredCheckRefreshCalls()).toBe(5);
    expect(merged.status).toBe("merged");
    expect(setup.mergeCalls()).toBe(1);
    await setup.db.$client.close();
  }, 15_000);

  it("records a fail-closed repository policy receipt when GitHub required checks are unavailable", async () => {
    const setup = await seedExecution({ requiredChecksUnavailable: true });
    const changeSet = await setup.dispatcher.complete(
      {
        attemptId: setup.attemptId,
        token: executionCallbackToken(callbackSecret, setup.attemptId),
        diff: "diff --git a/README.md b/README.md\n--- a/README.md\n+++ b/README.md\n@@ -1 +1 @@\n-old\n+new\n",
        baseRevision: "1".repeat(40),
        candidateTreeDigest: "2".repeat(40),
        workspaceCheckpointDigest: "2".repeat(40),
        workspaceCheckpointedAt: observedAt,
        changedPaths: ["README.md"],
        changes: [{ path: "README.md", kind: "modify" }],
        toolCalls: [{ name: "edit", summary: "Updated README" }],
        evidence: [
          {
            name: "workspace-check",
            state: "passed",
            source: "workspace:test",
            observedAt,
            required: true,
          },
        ],
      },
      setup.context,
    );
    const evidence = (
      await setup.uow.transaction((tx) =>
        tx.verificationEvidence.listByChangeSetIds([changeSet.id]),
      )
    ).filter((item) => item.source === "repository-required-checks:platform-modules/awp");
    expect(evidence).toEqual(
      expect.arrayContaining([
        expect.objectContaining({ name: "policy", state: "missing", required: true }),
        expect.objectContaining({ name: "required:CI", state: "missing", required: true }),
      ]),
    );
    await submitIndependentReview(setup, changeSet.id);
    expect((await setup.uow.transaction((tx) => tx.changeSets.getById(changeSet.id)))?.status).toBe(
      "verifying",
    );
    await setup.db.$client.close();
  });

  it("rejects coder attempts to forge the reserved repository-check evidence source", async () => {
    const setup = await seedExecution();
    await expect(
      setup.dispatcher.complete(
        {
          attemptId: setup.attemptId,
          token: executionCallbackToken(callbackSecret, setup.attemptId),
          diff: "diff --git a/README.md b/README.md\n--- a/README.md\n+++ b/README.md\n@@ -1 +1 @@\n-old\n+new\n",
          baseRevision: "1".repeat(40),
          candidateTreeDigest: "2".repeat(40),
          workspaceCheckpointDigest: "2".repeat(40),
          workspaceCheckpointedAt: observedAt,
          changedPaths: ["README.md"],
          changes: [{ path: "README.md", kind: "modify" }],
          toolCalls: [{ name: "edit", summary: "Updated README" }],
          evidence: [
            {
              name: "policy",
              state: "passed",
              source: "repository-required-checks:platform-modules/awp",
              observedAt,
              required: true,
            },
          ],
        },
        setup.context,
      ),
    ).rejects.toThrow(/only be recorded by the CI provider boundary/);
    expect(
      await setup.uow.transaction((tx) => tx.changeSets.listByProject(setup.projectId)),
    ).toHaveLength(0);
    await setup.db.$client.close();
  });

  it("refuses merge when the repository target moves after trusted publication", async () => {
    const setup = await seedExecution();
    const changeSet = await setup.dispatcher.complete(
      {
        attemptId: setup.attemptId,
        token: executionCallbackToken(callbackSecret, setup.attemptId),
        diff: "diff --git a/README.md b/README.md\n--- a/README.md\n+++ b/README.md\n@@ -1 +1 @@\n-old\n+new\n",
        baseRevision: "1".repeat(40),
        candidateTreeDigest: "2".repeat(40),
        workspaceCheckpointDigest: "2".repeat(40),
        workspaceCheckpointedAt: observedAt,
        changedPaths: ["README.md"],
        changes: [{ path: "README.md", kind: "modify" }],
        toolCalls: [{ name: "edit", summary: "Updated README" }],
        evidence: [
          {
            name: "required-check",
            state: "passed",
            source: "workspace:test",
            observedAt,
            required: true,
          },
        ],
      },
      setup.context,
    );
    await submitIndependentReview(setup, changeSet.id);
    setup.setForgeHead("8".repeat(40));

    const corrected = await setup.dispatcher.requestMerge(
      changeSet.id,
      mutationContext(setup.projectId, "moved-target"),
    );
    expect(corrected.status).toBe("changes-requested");
    expect(setup.mergeCalls()).toBe(0);
    const producer = await setup.uow.transaction((tx) => tx.attempts.getById(setup.attemptId));
    const attempts = await setup.uow.transaction((tx) =>
      tx.attempts.listByAgentRunIds([producer!.agentRunId]),
    );
    expect(
      attempts.filter((attempt) =>
        attempt.selection.reason.startsWith("Automatic repository correction:"),
      ),
    ).toHaveLength(1);
    await setup.db.$client.close();
  });

  it("refuses merge when Project repository identity changes after publication", async () => {
    const setup = await seedExecution();
    const changeSet = await setup.dispatcher.complete(
      {
        attemptId: setup.attemptId,
        token: executionCallbackToken(callbackSecret, setup.attemptId),
        diff: "diff --git a/README.md b/README.md\n--- a/README.md\n+++ b/README.md\n@@ -1 +1 @@\n-old\n+new\n",
        baseRevision: "1".repeat(40),
        candidateTreeDigest: "2".repeat(40),
        workspaceCheckpointDigest: "2".repeat(40),
        workspaceCheckpointedAt: observedAt,
        changedPaths: ["README.md"],
        changes: [{ path: "README.md", kind: "modify" }],
        toolCalls: [{ name: "edit", summary: "Updated README" }],
        evidence: [
          {
            name: "required-check",
            state: "passed",
            source: "workspace:test",
            observedAt,
            required: true,
          },
        ],
      },
      setup.context,
    );
    await submitIndependentReview(setup, changeSet.id);
    await setup.uow.transaction(async (tx) => {
      const project = await tx.projects.getById(setup.projectId);
      await tx.projects.update({
        ...project!,
        repositoryUrl: "https://github.com/platform-modules/other.git",
        revision: project!.revision + 1,
      });
    });

    await expect(
      setup.dispatcher.requestMerge(changeSet.id, mutationContext(setup.projectId, "repo-drift")),
    ).rejects.toBeInstanceOf(MergeRefusedError);
    expect(setup.mergeCalls()).toBe(0);
    await setup.db.$client.close();
  });

  it("reuses one pending reviewer across coder callback replay and startup reconciliation", async () => {
    const setup = await seedExecution({ reviewerProvisionFailures: 2 });
    const candidateDigest = "2".repeat(40);
    const completion = {
      attemptId: setup.attemptId,
      token: executionCallbackToken(callbackSecret, setup.attemptId),
      diff: "diff --git a/README.md b/README.md\n--- a/README.md\n+++ b/README.md\n@@ -1 +1 @@\n-old\n+new\n",
      baseRevision: "1".repeat(40),
      candidateTreeDigest: candidateDigest,
      workspaceCheckpointDigest: candidateDigest,
      workspaceCheckpointedAt: observedAt,
      changedPaths: ["README.md"],
      changes: [{ path: "README.md", kind: "modify" as const }],
      toolCalls: [{ name: "edit", summary: "Updated README" }],
      evidence: [
        {
          name: "workspace-check",
          state: "passed" as const,
          source: "workspace:test",
          observedAt,
          required: true,
        },
      ],
    };

    await expect(setup.dispatcher.complete(completion, setup.context)).rejects.toThrow(
      /injected reviewer provisioning failure/,
    );
    const [changeSet] = await setup.uow.transaction((tx) =>
      tx.changeSets.listByProject(setup.projectId),
    );
    expect(changeSet?.publicationReference).toBeDefined();
    expect(changeSet?.status).toBe("reviewing");
    expect(setup.publishCalls()).toBe(1);
    expect(setup.reviewerCreateCalls()).toBe(1);
    const initialReviews = await setup.uow.transaction((tx) =>
      tx.reviews.listByChangeSetIds([changeSet!.id]),
    );
    expect(initialReviews).toHaveLength(1);
    const reviewerId = initialReviews[0]!.reviewerAgentRunId!;
    expect((await setup.uow.transaction((tx) => tx.agentRuns.getById(reviewerId)))?.status).toBe(
      "waiting",
    );

    await expect(
      setup.dispatcher.complete(
        completion,
        mutationContext(setup.projectId, "completion-reviewer-replay"),
      ),
    ).rejects.toThrow(/injected reviewer provisioning failure/);
    expect(setup.publishCalls()).toBe(1);
    expect(setup.reviewerCreateCalls()).toBe(2);
    const replayReviews = await setup.uow.transaction((tx) =>
      tx.reviews.listByChangeSetIds([changeSet!.id]),
    );
    expect(replayReviews).toHaveLength(1);
    expect(replayReviews[0]?.reviewerAgentRunId).toBe(reviewerId);

    const reconciled = await setup.dispatcher.reconcilePendingReviews(
      setup.projectId,
      mutationContext(setup.projectId, "startup-reviewer-reconcile"),
    );
    expect(reconciled).toBe(1);
    expect(setup.publishCalls()).toBe(1);
    expect(setup.reviewerCreateCalls()).toBe(3);
    const recoveredRun = await setup.uow.transaction((tx) => tx.agentRuns.getById(reviewerId));
    expect(recoveredRun).toMatchObject({ role: "reviewer", status: "active" });
    const recoveredAttempts = await setup.uow.transaction((tx) =>
      tx.attempts.listByAgentRunIds([reviewerId]),
    );
    expect(recoveredAttempts).toHaveLength(1);
    expect(recoveredAttempts[0]).toMatchObject({
      status: "running",
      accountId: "account-verification",
      model: "model-verification",
    });
    expect(
      await setup.uow.transaction((tx) => tx.reviews.listByChangeSetIds([changeSet!.id])),
    ).toHaveLength(1);
    await setup.db.$client.close();
  }, 15_000);

  it("replays merged downstream dispatch after a crash window without duplicating merge or AgentRun", async () => {
    const setup = await seedExecution({ dependentTask: true, failFirstDependentDispatch: true });
    const candidateDigest = "2".repeat(40);
    const changeSet = await setup.dispatcher.complete(
      {
        attemptId: setup.attemptId,
        token: executionCallbackToken(callbackSecret, setup.attemptId),
        diff: "diff --git a/README.md b/README.md\n--- a/README.md\n+++ b/README.md\n@@ -1 +1 @@\n-old\n+new\n",
        baseRevision: "1".repeat(40),
        candidateTreeDigest: candidateDigest,
        workspaceCheckpointDigest: candidateDigest,
        workspaceCheckpointedAt: observedAt,
        changedPaths: ["README.md"],
        changes: [{ path: "README.md", kind: "modify" as const }],
        toolCalls: [{ name: "edit", summary: "Updated README" }],
        evidence: [
          {
            name: "workspace-check",
            state: "passed" as const,
            source: "workspace:test",
            observedAt,
            required: true,
          },
        ],
      },
      setup.context,
    );
    await submitIndependentReview(setup, changeSet.id);
    expect((await setup.uow.transaction((tx) => tx.changeSets.getById(changeSet.id)))?.status).toBe(
      "ready-to-merge",
    );

    await expect(
      setup.dispatcher.requestMerge(
        changeSet.id,
        mutationContext(setup.projectId, "merge-dispatch-crash-window"),
      ),
    ).rejects.toThrow(/injected dependent dispatch failure/);

    expect(setup.mergeCalls()).toBe(1);
    expect(setup.dependentCreateCalls()).toBe(1);
    expect((await setup.uow.transaction((tx) => tx.changeSets.getById(changeSet.id)))?.status).toBe(
      "merged",
    );
    expect(
      (await setup.uow.transaction((tx) => tx.tasks.getById(setup.dependentTaskId)))?.status,
    ).toBe("dispatched");
    const failedWindowRuns = (
      await setup.uow.transaction((tx) => tx.agentRuns.listByProject(setup.projectId))
    ).filter((run) => run.taskId === setup.dependentTaskId && run.role === "coder");
    expect(failedWindowRuns).toHaveLength(1);
    expect(failedWindowRuns[0]?.status).toBe("waiting");
    const failedWindowAttempts = await setup.uow.transaction((tx) =>
      tx.attempts.listByAgentRunIds([failedWindowRuns[0]!.id]),
    );
    expect(failedWindowAttempts).toHaveLength(1);
    expect(failedWindowAttempts[0]?.status).toBe("created");

    const replayed = await setup.dispatcher.requestMerge(
      changeSet.id,
      mutationContext(setup.projectId, "merge-dispatch-replay"),
    );
    expect(replayed.status).toBe("merged");
    expect(setup.mergeCalls()).toBe(1);
    expect(setup.dependentCreateCalls()).toBe(2);
    const recoveredRuns = (
      await setup.uow.transaction((tx) => tx.agentRuns.listByProject(setup.projectId))
    ).filter((run) => run.taskId === setup.dependentTaskId && run.role === "coder");
    expect(recoveredRuns).toHaveLength(1);
    expect(recoveredRuns[0]?.status).toBe("active");
    const recoveredAttempts = await setup.uow.transaction((tx) =>
      tx.attempts.listByAgentRunIds([recoveredRuns[0]!.id]),
    );
    expect(recoveredAttempts).toHaveLength(1);
    expect(recoveredAttempts[0]?.status).toBe("running");
    expect(recoveredAttempts[0]).toMatchObject({
      accountId: "account-verification",
      model: "model-verification",
    });
    await setup.db.$client.close();
  }, 15_000);

  it("fails closed without trusted GitHub authority and preserves the workspace for publication retry", async () => {
    const setup = await seedExecution({ trusted: false });
    await expect(
      setup.dispatcher.complete(
        {
          attemptId: setup.attemptId,
          token: executionCallbackToken(callbackSecret, setup.attemptId),
          diff: "diff --git a/README.md b/README.md\n--- a/README.md\n+++ b/README.md\n@@ -1 +1 @@\n-old\n+new\n",
          baseRevision: "1".repeat(40),
          candidateTreeDigest: "2".repeat(40),
          workspaceCheckpointDigest: "2".repeat(40),
          workspaceCheckpointedAt: observedAt,
          changedPaths: ["README.md"],
          changes: [{ path: "README.md", kind: "modify" }],
          toolCalls: [{ name: "edit", summary: "Updated README" }],
          evidence: [
            {
              name: "required-check",
              state: "passed",
              source: "workspace:test",
              observedAt,
              required: true,
            },
          ],
        },
        setup.context,
      ),
    ).rejects.toThrow(/trusted publication is not configured/);

    const changeSets = await setup.uow.transaction((tx) =>
      tx.changeSets.listByProject(setup.projectId),
    );
    expect(changeSets).toHaveLength(1);
    expect(changeSets[0]?.status).toBe("publication-failed");
    const reviews = await setup.uow.transaction((tx) =>
      tx.reviews.listByChangeSetIds([changeSets[0]!.id]),
    );
    expect(reviews).toHaveLength(0);
    expect(setup.workspaceDestroyCalls()).toBe(0);
    await setup.db.$client.close();
  });
  it("conflict-resolves-without-user by scheduling one fresh-Workspace correction Attempt", async () => {
    const setup = await seedExecution({ requiredCheckState: "passed", mergeConflictOnce: true });
    const candidateDigest = "2".repeat(40);
    const changeSet = await setup.dispatcher.complete(
      {
        attemptId: setup.attemptId,
        token: executionCallbackToken(callbackSecret, setup.attemptId),
        diff: "diff --git a/README.md b/README.md\n--- a/README.md\n+++ b/README.md\n@@ -1 +1 @@\n-old\n+new\n",
        baseRevision: "1".repeat(40),
        candidateTreeDigest: candidateDigest,
        workspaceCheckpointDigest: candidateDigest,
        workspaceCheckpointedAt: observedAt,
        changedPaths: ["README.md"],
        changes: [{ path: "README.md", kind: "modify" }],
        toolCalls: [{ name: "edit", summary: "Updated README" }],
        evidence: [
          {
            name: "workspace-check",
            state: "passed",
            source: "workspace:test",
            observedAt,
            required: true,
          },
        ],
      },
      setup.context,
    );
    await submitIndependentReview(setup, changeSet.id);
    const result = await setup.dispatcher.reconcileAutoMerge(changeSet.id, setup.context);
    expect(result).toMatchObject({ state: "waiting", changeSetId: changeSet.id });

    const original = await setup.uow.transaction((tx) => tx.changeSets.getById(changeSet.id));
    expect(original?.status).toBe("changes-requested");
    expect((await setup.uow.transaction((tx) => tx.tasks.getById(setup.taskId)))?.status).toBe(
      "correction",
    );
    const producer = await setup.uow.transaction((tx) => tx.attempts.getById(setup.attemptId));
    const history = await setup.uow.transaction((tx) =>
      tx.attempts.listByAgentRunIds([producer!.agentRunId]),
    );
    const corrections = history.filter((attempt) =>
      attempt.selection.reason.startsWith("Automatic repository correction:"),
    );
    expect(corrections).toHaveLength(1);
    expect(corrections[0]).toMatchObject({
      status: "running",
      selection: { kind: "retry", previousAttemptId: setup.attemptId },
    });
    expect(corrections[0]!.selection.reason).toContain(
      "strategy=new-workspace-reconcile-current-target",
    );
    expect(corrections[0]!.workspaceId).not.toBe(setup.workspaceId);
    const sourceWorkspace = await setup.uow.transaction((tx) =>
      tx.workspaces.getById(setup.workspaceId),
    );
    expect(sourceWorkspace?.checkpointDigest).toBe(candidateDigest);
    expect(sourceWorkspace?.cleanedAt).toBeUndefined();
    const correctionWorkspace = await setup.uow.transaction((tx) =>
      tx.workspaces.getById(corrections[0]!.workspaceId),
    );
    expect(correctionWorkspace).toMatchObject({
      id: corrections[0]!.workspaceId,
      projectId: setup.projectId,
      revision: 1,
    });
    expect(correctionWorkspace?.checkpointDigest).toBeUndefined();
    const findings = await setup.uow.transaction((tx) =>
      tx.reviewFindings.listByChangeSetIds([changeSet.id]),
    );
    expect(findings).toEqual([
      expect.objectContaining({ source: "vcs:auto-correction", severity: "blocking" }),
    ]);

    const replay = await setup.dispatcher.reconcileAutoMerge(changeSet.id, setup.context);
    expect(replay.state).toBe("terminal");
    const replayHistory = await setup.uow.transaction((tx) =>
      tx.attempts.listByAgentRunIds([producer!.agentRunId]),
    );
    expect(
      replayHistory.filter((attempt) =>
        attempt.selection.reason.startsWith("Automatic repository correction:"),
      ),
    ).toHaveLength(1);
    expect(await setup.db.select().from(schema.approvals)).toHaveLength(0);
    await setup.db.$client.close();
  }, 15_000);

  it("requires a new candidate, fresh evidence and independent Review before retry Merge", async () => {
    const setup = await seedExecution({ requiredCheckState: "passed", mergeConflictOnce: true });
    const originalDigest = "2".repeat(40);
    const original = await setup.dispatcher.complete(
      {
        attemptId: setup.attemptId,
        token: executionCallbackToken(callbackSecret, setup.attemptId),
        diff: "diff --git a/README.md b/README.md\n--- a/README.md\n+++ b/README.md\n@@ -1 +1 @@\n-old\n+original\n",
        baseRevision: "1".repeat(40),
        candidateTreeDigest: originalDigest,
        workspaceCheckpointDigest: originalDigest,
        workspaceCheckpointedAt: observedAt,
        changedPaths: ["README.md"],
        changes: [{ path: "README.md", kind: "modify" }],
        toolCalls: [{ name: "edit", summary: "Created original candidate" }],
        evidence: [
          {
            name: "workspace-check",
            state: "passed",
            source: "workspace:test",
            observedAt,
            required: true,
          },
        ],
      },
      setup.context,
    );
    const firstReview = await submitIndependentReview(setup, original.id);
    const correctionScheduled = await setup.dispatcher.reconcileAutoMerge(
      original.id,
      setup.context,
    );
    expect(correctionScheduled.state).toBe("waiting");
    expect(setup.mergeCalls()).toBe(1);

    const producer = await setup.uow.transaction((tx) => tx.attempts.getById(setup.attemptId));
    const attempts = await setup.uow.transaction((tx) =>
      tx.attempts.listByAgentRunIds([producer!.agentRunId]),
    );
    const correctionAttempt = attempts.find((attempt) =>
      attempt.selection.reason.startsWith("Automatic repository correction:"),
    );
    expect(correctionAttempt).toBeDefined();

    const correctedDigest = "5".repeat(40);
    setup.setExpectedPublishCandidate(correctedDigest);
    const corrected = await setup.dispatcher.complete(
      {
        attemptId: correctionAttempt!.id,
        token: executionCallbackToken(callbackSecret, correctionAttempt!.id),
        diff: "diff --git a/README.md b/README.md\n--- a/README.md\n+++ b/README.md\n@@ -1 +1 @@\n-old\n+corrected\n",
        baseRevision: "1".repeat(40),
        candidateTreeDigest: correctedDigest,
        workspaceCheckpointDigest: correctedDigest,
        workspaceCheckpointedAt: "2026-08-22T13:10:00.000Z",
        changedPaths: ["README.md"],
        changes: [{ path: "README.md", kind: "modify" }],
        toolCalls: [{ name: "edit", summary: "Reconciled candidate with current target" }],
        evidence: [
          {
            name: "workspace-check",
            state: "passed",
            source: "workspace:correction",
            observedAt: "2026-08-22T13:10:00.000Z",
            required: true,
          },
        ],
      },
      mutationContext(setup.projectId, "correction-complete"),
    );
    expect(corrected.id).not.toBe(original.id);
    expect(corrected.producerAttemptId).toBe(correctionAttempt!.id);
    expect(corrected.candidateDigest).toBe(correctedDigest);
    expect((await setup.uow.transaction((tx) => tx.changeSets.getById(original.id)))?.status).toBe(
      "changes-requested",
    );

    const correctedEvidence = await setup.uow.transaction((tx) =>
      tx.verificationEvidence.listByChangeSetIds([corrected.id]),
    );
    expect(correctedEvidence.length).toBeGreaterThan(0);
    expect(
      correctedEvidence.every((evidence) => evidence.candidateDigest === correctedDigest),
    ).toBe(true);
    expect(
      correctedEvidence.some(
        (evidence) =>
          evidence.reference?.kind === "workspace-checkpoint" &&
          evidence.reference.workspaceId === correctionAttempt!.workspaceId &&
          evidence.reference.checkpointDigest === correctedDigest,
      ),
    ).toBe(true);

    const [correctedReview] = await setup.uow.transaction((tx) =>
      tx.reviews.listByChangeSetIds([corrected.id]),
    );
    expect(correctedReview).toBeDefined();
    expect(correctedReview!.id).not.toBe(firstReview.review.id);
    expect(correctedReview!.candidateDigest).toBe(correctedDigest);
    expect(correctedReview!.status).not.toBe("submitted");

    await expect(
      setup.dispatcher.requestMerge(
        corrected.id,
        mutationContext(setup.projectId, "correction-before-review"),
      ),
    ).rejects.toBeInstanceOf(MergeRefusedError);
    expect(setup.mergeCalls()).toBe(1);

    const secondReview = await submitIndependentReview(setup, corrected.id);
    expect(secondReview.review.id).not.toBe(firstReview.review.id);
    const merged = await setup.dispatcher.requestMerge(
      corrected.id,
      mutationContext(setup.projectId, "correction-merge"),
    );
    expect(merged.status).toBe("merged");
    expect(setup.mergeCalls()).toBe(2);
    expect((await setup.uow.transaction((tx) => tx.tasks.getById(setup.taskId)))?.status).toBe(
      "completed",
    );
    expect(await setup.db.select().from(schema.approvals)).toHaveLength(0);
    await setup.db.$client.close();
  }, 20_000);
});
