// SPEC: PJ2026-01060307 控制面模块化 draft-2026-06-25-p0. cd-trigger module for scripts/src/hwlab-g14.ts.

// Moved mechanically from scripts/src/hwlab-g14.ts:9508-10162 for #903.

import { chmodSync, existsSync, mkdirSync, readFileSync, rmSync, statSync, writeFileSync } from "node:fs";
import { dirname, join } from "node:path";
import { createHash, randomBytes } from "node:crypto";
import { repoRoot, rootPath, type Config } from "../config";
import { runCommand } from "../command";
import { cancelJob, listJobs, readJob, startJob } from "../jobs";
import { hwlabRequiredNoProxyEntries, hwlabRuntimeLaneConfigPath, hwlabRuntimeLaneIds, hwlabRuntimeLaneSpec, isHwlabRuntimeLane, type HwlabRuntimeLane, type HwlabRuntimeLaneSpec } from "../hwlab-node-lanes";

import type { CommandJsonResult, G14MonitorOptions, OpenPullRequest } from "./types";
import { runRuntimeLaneControlPlane, runV02ControlPlane, runtimeLaneControlPlaneStatus, v02ControlPlaneStatus } from "./control-plane";
import { runG14GitMirror, runGitMirrorStatus } from "./git-mirror";
import { activeV02PipelineRuns, commentRuntimeLanePullRequest, commentV02PullRequest, durationSeconds, reportRuntimeLaneAutomationIssue, runtimeLaneCdFailed, runtimeLaneCdPassed, sourceCommitFromMergeResult, summarizeRuntimeLaneCdStatus, summarizeV02CdStatus, v02CdFailed, v02CdPassed, v02PipelineSucceeded, waitForV02Head } from "./pr-monitor";
import { commandData, compactCommandResult, isCommandSuccess, nested, printEvent, printRuntimeLanePrMonitorProgress, printV02PrMonitorProgress, record, sleep, stringOrNull } from "./remote";
import { resolveRuntimeLaneHead, runtimeLanePipelineRunName, v02PipelineRunName } from "./source";

export function triggerV02Current(timeoutSeconds: number): Record<string, unknown> {
  return runV02ControlPlane({
    action: "trigger-current",
    lane: "v02",
    dryRun: false,
    confirm: true,
    wait: true,
    allowLiveDbRead: false,
    timeoutSeconds,
    minAgeMinutes: 30,
    limit: 20,
  });
}

export function flushV02GitMirrorIfNeeded(status: Record<string, unknown>, timeoutSeconds: number): Record<string, unknown> {
  const pendingFlush = nested(status, ["gitMirror", "summary", "pendingFlush"]);
  if (pendingFlush !== true) return { ok: true, skipped: true, reason: "git-mirror-already-flushed" };
  return runG14GitMirror({
    action: "flush",
    lane: "v02",
    confirm: true,
    dryRun: false,
    wait: true,
    timeoutSeconds,
  });
}

export function triggerRuntimeLaneCurrent(spec: HwlabRuntimeLaneSpec, timeoutSeconds: number): Record<string, unknown> {
  return runRuntimeLaneControlPlane(spec, {
    action: "trigger-current",
    lane: spec.lane,
    dryRun: false,
    confirm: true,
    wait: true,
    allowLiveDbRead: false,
    timeoutSeconds,
    minAgeMinutes: 30,
    limit: 20,
    history: false,
  });
}

export function runtimeLaneStatusWithGitMirror(spec: HwlabRuntimeLaneSpec, sourceCommit: string): Record<string, unknown> {
  const status = runtimeLaneControlPlaneStatus(spec, { sourceCommit, mode: "source-commit" });
  const gitMirrorStatus = runGitMirrorStatus(spec.lane);
  return {
    ...status,
    gitMirror: record(gitMirrorStatus.cache),
    gitMirrorStatus,
  };
}

export function runtimeLaneGitopsSynced(spec: HwlabRuntimeLaneSpec, status: Record<string, unknown>): boolean {
  const summary = record(nested(status, ["gitMirror", "summary"]));
  const inSync = spec.lane === "v03" ? summary.v03GitopsInSync : summary.githubInSync;
  return summary.pendingFlush !== true && inSync === true;
}

export function runtimeLaneCdPassedWithMirror(spec: HwlabRuntimeLaneSpec, status: Record<string, unknown>): boolean {
  return runtimeLaneCdPassed(status) && runtimeLaneGitopsSynced(spec, status);
}

export function flushRuntimeLaneGitMirrorIfNeeded(spec: HwlabRuntimeLaneSpec, status: Record<string, unknown>, timeoutSeconds: number): Record<string, unknown> {
  if (runtimeLaneGitopsSynced(spec, status)) return { ok: true, skipped: true, reason: `${spec.lane}-git-mirror-already-flushed` };
  return runG14GitMirror({
    action: "flush",
    lane: spec.lane,
    confirm: true,
    dryRun: false,
    wait: true,
    timeoutSeconds,
  });
}

export function pullRequestFromMergeCommand(merge: CommandJsonResult): Record<string, unknown> {
  const data = commandData(merge);
  const direct = record(data.pullRequest);
  if (Object.keys(direct).length > 0) return direct;
  return record(nested(data, ["details", "details", "pullRequest"]));
}

export function mergeCommandRaceState(merge: CommandJsonResult): "merged" | "closed" | null {
  const pullRequest = pullRequestFromMergeCommand(merge);
  if (pullRequest.merged === true || pullRequest.stateDetail === "merged") return "merged";
  if (pullRequest.closed === true || pullRequest.state === "closed") return "closed";
  return null;
}

export async function waitForRuntimeLaneHead(spec: HwlabRuntimeLaneSpec, expectedCommit: string | null, timeoutSeconds: number): Promise<Record<string, unknown>> {
  const started = Date.now();
  let head: string | null = null;
  while (Date.now() - started < timeoutSeconds * 1000) {
    head = resolveRuntimeLaneHead(spec).sourceCommit;
    if (expectedCommit === null) {
      if (head !== null) return { ok: true, sourceCommit: head, expectedCommit, matched: true, waitedSeconds: Math.round((Date.now() - started) / 1000) };
    } else if (head === expectedCommit) {
      return { ok: true, sourceCommit: head, expectedCommit, matched: true, waitedSeconds: Math.round((Date.now() - started) / 1000) };
    }
    printEvent(`${spec.lane}.source.wait`, { lane: spec.lane, expectedCommit, head, waitedSeconds: Math.round((Date.now() - started) / 1000) });
    await sleep(5_000);
  }
  return { ok: false, sourceCommit: head, expectedCommit, matched: false, waitedSeconds: Math.round((Date.now() - started) / 1000), degradedReason: `${spec.lane}-source-head-not-aligned-after-merge` };
}

export async function waitForV02Cd(sourceCommit: string, timeoutSeconds: number): Promise<Record<string, unknown>> {
  const started = Date.now();
  const startedAt = new Date(started).toISOString();
  const pipelineRun = v02PipelineRunName(sourceCommit);
  let lastStatus: Record<string, unknown> = {};
  let firstPassedAt: string | null = null;
  while (Date.now() - started < timeoutSeconds * 1000) {
    lastStatus = v02ControlPlaneStatus({ sourceCommit, mode: "source-commit" });
    const summary = summarizeV02CdStatus(lastStatus);
    printEvent("v02.cd.status", { sourceCommit, pipelineRun, summary });
    printV02PrMonitorProgress({ stage: "cd-status", status: "running", sourceCommit, pipelineRun, targetValidationState: summary.targetValidationState ?? null, pipelineStatus: summary.pipelineStatus ?? null, pendingFlush: summary.pendingFlush ?? null });
    if (v02PipelineSucceeded(lastStatus) && summary.pendingFlush === true) {
      const flush = flushV02GitMirrorIfNeeded(lastStatus, Math.min(timeoutSeconds, 600));
      if (record(flush).ok !== true) {
        return {
          ok: false,
          phase: "git-mirror-flush",
          startedAt,
          finishedAt: new Date().toISOString(),
          sourceCommit,
          pipelineRun,
          status: summary,
          rawStatus: lastStatus,
          flush,
        };
      }
      const finalStatus = v02ControlPlaneStatus({ sourceCommit, mode: "source-commit" });
      const finalSummary = summarizeV02CdStatus(finalStatus);
      if (v02CdPassed(finalStatus)) {
        printV02PrMonitorProgress({ stage: "git-mirror-flush", status: "succeeded", sourceCommit, pipelineRun, pendingFlush: finalSummary.pendingFlush ?? null });
        return {
          ok: true,
          phase: "cd-passed",
          startedAt,
          finishedAt: new Date().toISOString(),
          sourceCommit,
          pipelineRun,
          status: finalSummary,
          rawStatus: finalStatus,
          flush,
        };
      }
      lastStatus = finalStatus;
      printEvent("v02.cd.after-flush", { sourceCommit, pipelineRun, flushOk: record(flush).ok, summary: finalSummary });
      printV02PrMonitorProgress({ stage: "git-mirror-flush", status: record(flush).ok === true ? "succeeded" : "failed", sourceCommit, pipelineRun, pendingFlush: finalSummary.pendingFlush ?? null });
    }
    if (v02CdPassed(lastStatus)) {
      firstPassedAt ??= new Date().toISOString();
      const flush = flushV02GitMirrorIfNeeded(lastStatus, Math.min(timeoutSeconds, 600));
      const finalStatus = v02ControlPlaneStatus({ sourceCommit, mode: "source-commit" });
      printV02PrMonitorProgress({ stage: "cd-status", status: v02CdPassed(finalStatus) ? "succeeded" : "failed", sourceCommit, pipelineRun, targetValidationState: nested(finalStatus, ["targetValidation", "state"]) ?? null });
      return {
        ok: v02CdPassed(finalStatus),
        phase: v02CdPassed(finalStatus) ? "cd-passed" : "git-mirror-flush",
        startedAt,
        finishedAt: new Date().toISOString(),
        sourceCommit,
        pipelineRun,
        status: summarizeV02CdStatus(finalStatus),
        rawStatus: finalStatus,
        flush,
      };
    }
    if (v02CdFailed(lastStatus)) {
      printV02PrMonitorProgress({ stage: "cd-status", status: "failed", sourceCommit, pipelineRun, pipelineStatus: summary.pipelineStatus ?? null, pipelineReason: summary.pipelineReason ?? null });
      return {
        ok: false,
        phase: "cd-failed",
        startedAt,
        finishedAt: new Date().toISOString(),
        sourceCommit,
        pipelineRun,
        status: summary,
        rawStatus: lastStatus,
      };
    }
    await sleep(30_000);
  }
  return {
    ok: false,
    phase: "timeout",
    startedAt,
    finishedAt: new Date().toISOString(),
    sourceCommit,
    pipelineRun,
    timeoutSeconds,
    firstPassedAt,
    status: summarizeV02CdStatus(lastStatus),
    rawStatus: lastStatus,
  };
}

export async function waitForRuntimeLaneCd(spec: HwlabRuntimeLaneSpec, sourceCommit: string, timeoutSeconds: number): Promise<Record<string, unknown>> {
  const started = Date.now();
  const startedAt = new Date(started).toISOString();
  let pipelineRun = runtimeLanePipelineRunName(spec, sourceCommit);
  let lastStatus: Record<string, unknown> = {};
  let firstPassedAt: string | null = null;
  while (Date.now() - started < timeoutSeconds * 1000) {
    lastStatus = runtimeLaneStatusWithGitMirror(spec, sourceCommit);
    const summary = summarizeRuntimeLaneCdStatus(spec, lastStatus);
    pipelineRun = stringOrNull(summary.pipelineRun) ?? pipelineRun;
    printEvent(`${spec.lane}.cd.status`, { lane: spec.lane, sourceCommit, pipelineRun, summary });
    printRuntimeLanePrMonitorProgress(spec, {
      stage: "cd-status",
      status: "running",
      sourceCommit,
      pipelineRun,
      pipelineStatus: summary.pipelineStatus ?? null,
      pipelineReason: summary.pipelineReason ?? null,
      argoSync: summary.argoSync ?? null,
      argoHealth: summary.argoHealth ?? null,
      publicProbesOk: summary.publicProbesOk ?? null,
      pendingFlush: summary.pendingFlush ?? null,
    });
    if (runtimeLaneCdPassed(lastStatus)) {
      firstPassedAt ??= new Date().toISOString();
      const flush = flushRuntimeLaneGitMirrorIfNeeded(spec, lastStatus, Math.min(timeoutSeconds, 600));
      if (record(flush).ok !== true) {
        return {
          ok: false,
          phase: "git-mirror-flush",
          startedAt,
          finishedAt: new Date().toISOString(),
          sourceCommit,
          pipelineRun,
          status: summary,
          rawStatus: lastStatus,
          flush,
        };
      }
      const finalStatus = runtimeLaneStatusWithGitMirror(spec, sourceCommit);
      const finalSummary = summarizeRuntimeLaneCdStatus(spec, finalStatus);
      printRuntimeLanePrMonitorProgress(spec, {
        stage: "git-mirror-flush",
        status: runtimeLaneCdPassedWithMirror(spec, finalStatus) ? "succeeded" : "failed",
        sourceCommit,
        pipelineRun,
        pendingFlush: finalSummary.pendingFlush ?? null,
      });
      return {
        ok: runtimeLaneCdPassedWithMirror(spec, finalStatus),
        phase: runtimeLaneCdPassedWithMirror(spec, finalStatus) ? "cd-passed" : "git-mirror-flush",
        startedAt,
        finishedAt: new Date().toISOString(),
        sourceCommit,
        pipelineRun,
        status: finalSummary,
        rawStatus: finalStatus,
        flush,
      };
    }
    if (runtimeLaneCdFailed(lastStatus)) {
      printRuntimeLanePrMonitorProgress(spec, { stage: "cd-status", status: "failed", sourceCommit, pipelineRun, pipelineStatus: summary.pipelineStatus ?? null, pipelineReason: summary.pipelineReason ?? null });
      return {
        ok: false,
        phase: "cd-failed",
        startedAt,
        finishedAt: new Date().toISOString(),
        sourceCommit,
        pipelineRun,
        status: summary,
        rawStatus: lastStatus,
      };
    }
    await sleep(30_000);
  }
  return {
    ok: false,
    phase: "timeout",
    startedAt,
    finishedAt: new Date().toISOString(),
    sourceCommit,
    pipelineRun,
    timeoutSeconds,
    firstPassedAt,
    status: summarizeRuntimeLaneCdStatus(spec, lastStatus),
    rawStatus: lastStatus,
  };
}

export async function runV02PrAutoCd(pr: OpenPullRequest, preflight: Record<string, unknown>, merge: CommandJsonResult, options: G14MonitorOptions, startedAt: string): Promise<Record<string, unknown>> {
  const mergeRaceState = isCommandSuccess(merge) ? null : mergeCommandRaceState(merge);
  if (!isCommandSuccess(merge) && mergeRaceState !== "merged") {
    printV02PrMonitorProgress({ stage: "merge", status: "failed", pr: pr.number, mergeRaceState });
    const comment = commentV02PullRequest({
      pr,
      phase: "merge",
      state: "merge-failed",
      startedAt,
      observedAt: new Date().toISOString(),
      elapsedSeconds: durationSeconds(startedAt, new Date().toISOString()),
      preflight,
      merge: compactCommandResult(merge),
      dryRun: options.dryRun,
      message: mergeRaceState === "closed"
        ? "PR preflight 曾通过，但自动合并前 PR 已被关闭且未确认 merged；本轮不会触发 CD。"
        : "PR preflight 已通过，但自动合并失败；需要处理 GitHub 返回的 merge blocker 后重试。",
    });
    return { ok: mergeRaceState === "closed", phase: "merge", action: "merge-race-closed", pr, merge, comment };
  }
  const expectedCommit = options.dryRun ? null : sourceCommitFromMergeResult(merge, pr.number);
  if (!options.dryRun && expectedCommit === null) {
    printV02PrMonitorProgress({ stage: "source-head", status: "failed", pr: pr.number, sourceCommit: null, pipelineRun: null, degradedReason: "merge-commit-unresolved" });
    const comment = commentV02PullRequest({
      pr,
      phase: "merge-commit",
      state: "cd-failed",
      startedAt,
      observedAt: new Date().toISOString(),
      elapsedSeconds: durationSeconds(startedAt, new Date().toISOString()),
      preflight,
      merge: commandData(merge),
      sourceCommit: null,
      pipelineRun: null,
      dryRun: false,
      message: "PR 已合并，但 UniDesk 未能从 GitHub merge/read 结果解析 merge commit；为避免触发错误 source head，本轮未启动 CD。",
    });
    return { ok: false, phase: "merge-commit", pr, merge: commandData(merge), comment, degradedReason: "merge-commit-unresolved" };
  }
  const headWait = options.dryRun ? { ok: true, sourceCommit: null, expectedCommit, matched: true } : await waitForV02Head(expectedCommit, 120);
  const sourceCommit = typeof record(headWait).sourceCommit === "string" ? String(record(headWait).sourceCommit) : null;
  const pipelineRun = sourceCommit === null ? null : v02PipelineRunName(sourceCommit);
  printV02PrMonitorProgress({ stage: "merge", status: "succeeded", pr: pr.number, sourceCommit, pipelineRun, mergeRaceState });
  const startedComment = commentV02PullRequest({
    pr,
    phase: options.dryRun ? "dry-run" : "cd-trigger",
    state: "cd-started",
    startedAt,
    observedAt: new Date().toISOString(),
    elapsedSeconds: durationSeconds(startedAt, new Date().toISOString()),
    preflight,
    merge: commandData(merge),
    sourceCommit,
    pipelineRun,
    dryRun: options.dryRun,
    message: options.dryRun
      ? "dry-run：PR 已达到自动合并条件；本轮不会写 GitHub merge 或 CD。"
      : mergeRaceState === "merged"
        ? "PR 在本轮 merge 前已被合并；worker 继续对齐 merge commit 并驱动 v0.2 CD。后续成功、失败或超时会继续在本 PR 下回复。"
        : "PR 已自动合并，v0.2 CD 准备开始；其他 commit 的运行中 PipelineRun 不会阻塞本轮 CI，旧 run 若发现 source head 已推进会以 superseded/no-op 收口。后续成功、superseded、失败或超时会继续在本 PR 下回复。",
  });
  if (!options.dryRun && record(startedComment).ok !== true) {
    return { ok: false, phase: "pr-comment", pr, sourceCommit, pipelineRun, comment: startedComment };
  }
  if (options.dryRun) return { ok: true, action: "dry-run-merge", pr, merge: commandData(merge), comment: startedComment };
  if (sourceCommit === null || record(headWait).ok !== true) {
    printV02PrMonitorProgress({ stage: "source-head", status: "failed", pr: pr.number, sourceCommit, pipelineRun, expectedCommit });
    const comment = commentV02PullRequest({
      pr,
      phase: "v02-source-head",
      state: "cd-timeout",
      startedAt,
      observedAt: new Date().toISOString(),
      elapsedSeconds: durationSeconds(startedAt, new Date().toISOString()),
      preflight,
      merge: commandData(merge),
      sourceCommit,
      pipelineRun,
      dryRun: false,
      message: "PR 已合并，但 G14 v0.2 CI/CD source repo 没有在等待窗口内对齐 merge commit；为避免触发旧 head，本轮未启动 CD。",
    });
    return { ok: false, phase: "v02-head", pr, merge: commandData(merge), expectedCommit, headWait, comment: record(comment).ok === true ? comment : startedComment, degradedReason: "v02-head-unresolved-after-merge" };
  }
  const before = v02ControlPlaneStatus({ sourceCommit, mode: "source-commit" });
  const beforeSummary = summarizeV02CdStatus(before);
  const activeRuns = activeV02PipelineRuns(before);
  if (activeRuns.length > 0) {
    printEvent("v02.cd.active-runs-observed", { pr: pr.number, sourceCommit, pipelineRun, activeCount: activeRuns.length, activeRuns: activeRuns.slice(0, 5), latestOnlyPolicy: "do-not-wait-or-cancel-old-runs" });
  }
  const trigger = v02CdPassed(before) ? { ok: true, skipped: true, reason: "source-commit-already-deployed" } : triggerV02Current(Math.min(options.timeoutSeconds, 600));
  printEvent("v02.cd.trigger", { pr: pr.number, sourceCommit, pipelineRun, ok: record(trigger).ok, skipped: record(trigger).skipped ?? false, degradedReason: record(trigger).degradedReason ?? null });
  printV02PrMonitorProgress({ stage: "cd-trigger", status: record(trigger).ok === true ? "succeeded" : "failed", pr: pr.number, sourceCommit, pipelineRun, activeCount: activeRuns.length, skipped: record(trigger).skipped ?? false, degradedReason: record(trigger).degradedReason ?? null });
  if (record(trigger).ok !== true) {
    const comment = commentV02PullRequest({
      pr,
      phase: "cd-trigger",
      state: "cd-failed",
      startedAt,
      observedAt: new Date().toISOString(),
      elapsedSeconds: durationSeconds(startedAt, new Date().toISOString()),
      preflight,
      merge: commandData(merge),
      sourceCommit,
      pipelineRun,
      cd: beforeSummary,
      dryRun: false,
      message: "PR 已合并，但 v0.2 CD 触发失败；需要查看 trigger 阶段的 degradedReason。",
    });
    return { ok: false, phase: "cd-trigger", pr, sourceCommit, pipelineRun, trigger, comment };
  }
  const cd = await waitForV02Cd(sourceCommit, options.timeoutSeconds);
  const cdStatus = record(cd.status);
  const flush = record(cd.flush);
  const cdOk = cd.ok === true;
  const cdPhase = String(cd.phase ?? "");
  const cdSuperseded = cdOk && cdStatus.targetValidationState === "superseded";
  const finalComment = commentV02PullRequest({
    pr,
    phase: cdPhase,
    state: cdOk ? (cdSuperseded ? "cd-superseded" : "cd-succeeded") : cdPhase === "timeout" ? "cd-timeout" : "cd-failed",
    startedAt,
    observedAt: new Date().toISOString(),
    elapsedSeconds: durationSeconds(startedAt, new Date().toISOString()),
    preflight,
    merge: commandData(merge),
    sourceCommit,
    pipelineRun,
    cd: cdStatus,
    flush,
    dryRun: false,
    message: cdOk
      ? cdSuperseded
        ? "v0.2 自动 CD 已收口：本 merge head 的 PipelineRun 已完成，但 source head 已被后续提交推进，本轮按 latest-only 语义 superseded/no-op，未回写旧 GitOps revision。"
        : "v0.2 自动 CD 已完成：PipelineRun、Argo/runtime、公开探针和 Git mirror flush 收口均已检查。"
      : cdPhase === "timeout"
        ? "v0.2 自动 CD 超时未收口；本评论保留最后一次 targetValidation / PipelineRun / Git mirror 状态用于接续排障。"
        : "v0.2 自动 CD 失败；本评论保留失败阶段和最后一次状态用于接续排障。",
  });
  return {
    ok: cdOk && record(finalComment).ok === true,
    action: cdOk ? (cdSuperseded ? "merged-and-superseded-v02" : "merged-and-rolled-v02") : "v02-cd-failed",
    phase: cdOk ? "cd-passed" : cdPhase,
    pr,
    sourceCommit,
    pipelineRun,
    trigger,
    cd,
    comment: finalComment,
  };
}

export async function runRuntimeLanePrAutoCd(spec: HwlabRuntimeLaneSpec, pr: OpenPullRequest, preflight: Record<string, unknown>, merge: CommandJsonResult, options: G14MonitorOptions, startedAt: string): Promise<Record<string, unknown>> {
  const mergeRaceState = isCommandSuccess(merge) ? null : mergeCommandRaceState(merge);
  if (!isCommandSuccess(merge) && mergeRaceState !== "merged") {
    printRuntimeLanePrMonitorProgress(spec, { stage: "merge", status: "failed", pr: pr.number, mergeRaceState });
    const observedAt = new Date().toISOString();
    const comment = commentRuntimeLanePullRequest(spec, {
      pr,
      phase: "merge",
      state: "merge-failed",
      startedAt,
      observedAt,
      elapsedSeconds: durationSeconds(startedAt, observedAt),
      preflight,
      merge: compactCommandResult(merge),
      dryRun: options.dryRun,
      message: mergeRaceState === "closed"
        ? `PR preflight 曾通过，但自动合并前 PR 已被关闭且未确认 merged；本轮不会触发 ${spec.version} CD。`
        : "PR preflight 已通过，但自动合并失败；需要处理 GitHub 返回的 merge blocker 后重试。",
    });
    const issue = reportRuntimeLaneAutomationIssue({
      spec,
      pr,
      state: "merge-failed",
      phase: "merge",
      startedAt,
      observedAt,
      preflight,
      details: { merge: compactCommandResult(merge), mergeRaceState },
      message: "自动合并失败，v0.3 monitor 已暂停 CD 触发。",
      dryRun: options.dryRun,
    });
    return { ok: mergeRaceState === "closed" && record(comment).ok === true && record(issue).ok === true, phase: "merge", action: "merge-race-closed", pr, merge, comment, issue };
  }
  const expectedCommit = options.dryRun ? null : sourceCommitFromMergeResult(merge, pr.number);
  if (!options.dryRun && expectedCommit === null) {
    const observedAt = new Date().toISOString();
    printRuntimeLanePrMonitorProgress(spec, { stage: "source-head", status: "failed", pr: pr.number, sourceCommit: null, pipelineRun: null, degradedReason: "merge-commit-unresolved" });
    const comment = commentRuntimeLanePullRequest(spec, {
      pr,
      phase: "merge-commit",
      state: "cd-failed",
      startedAt,
      observedAt,
      elapsedSeconds: durationSeconds(startedAt, observedAt),
      preflight,
      merge: commandData(merge),
      sourceCommit: null,
      pipelineRun: null,
      dryRun: false,
      message: "PR 已合并，但 UniDesk 未能从 GitHub merge/read 结果解析 merge commit；为避免触发错误 source head，本轮未启动 CD。",
    });
    const issue = reportRuntimeLaneAutomationIssue({
      spec,
      pr,
      state: "cd-failed",
      phase: "merge-commit",
      startedAt,
      observedAt,
      preflight,
      details: { merge: commandData(merge) },
      message: "PR 已合并但 merge commit 不可解析，v0.3 CD 未启动。",
      dryRun: false,
    });
    return { ok: false, phase: "merge-commit", pr, merge: commandData(merge), comment, issue, degradedReason: "merge-commit-unresolved" };
  }
  const headWait = options.dryRun ? { ok: true, sourceCommit: null, expectedCommit, matched: true } : await waitForRuntimeLaneHead(spec, expectedCommit, 120);
  const sourceCommit = typeof record(headWait).sourceCommit === "string" ? String(record(headWait).sourceCommit) : null;
  const pipelineRun = sourceCommit === null ? null : runtimeLanePipelineRunName(spec, sourceCommit);
  printRuntimeLanePrMonitorProgress(spec, { stage: "merge", status: "succeeded", pr: pr.number, sourceCommit, pipelineRun, mergeRaceState });
  const startedComment = commentRuntimeLanePullRequest(spec, {
    pr,
    phase: options.dryRun ? "dry-run" : "cd-trigger",
    state: "cd-started",
    startedAt,
    observedAt: new Date().toISOString(),
    elapsedSeconds: durationSeconds(startedAt, new Date().toISOString()),
    preflight,
    merge: commandData(merge),
    sourceCommit,
    pipelineRun,
    dryRun: options.dryRun,
    message: options.dryRun
      ? `dry-run：PR 已达到 ${spec.version} 自动合并条件；本轮不会写 GitHub merge 或 CD。`
      : mergeRaceState === "merged"
        ? `PR 在本轮 merge 前已被合并；worker 继续对齐 merge commit 并驱动 ${spec.version} CD。后续成功、失败或超时会继续在本 PR 下回复。`
        : `PR 已自动合并，${spec.version} CD 准备开始；后续成功、失败或超时会继续在本 PR 下回复。`,
  });
  if (!options.dryRun && record(startedComment).ok !== true) {
    return { ok: false, phase: "pr-comment", pr, sourceCommit, pipelineRun, comment: startedComment };
  }
  if (options.dryRun) return { ok: true, action: "dry-run-merge", pr, merge: commandData(merge), comment: startedComment };
  if (sourceCommit === null || record(headWait).ok !== true) {
    const observedAt = new Date().toISOString();
    printRuntimeLanePrMonitorProgress(spec, { stage: "source-head", status: "failed", pr: pr.number, sourceCommit, pipelineRun, expectedCommit });
    const comment = commentRuntimeLanePullRequest(spec, {
      pr,
      phase: `${spec.lane}-source-head`,
      state: "cd-timeout",
      startedAt,
      observedAt,
      elapsedSeconds: durationSeconds(startedAt, observedAt),
      preflight,
      merge: commandData(merge),
      sourceCommit,
      pipelineRun,
      dryRun: false,
      message: `PR 已合并，但 G14 ${spec.version} CI/CD source repo 没有在等待窗口内对齐 merge commit；为避免触发旧 head，本轮未启动 CD。`,
    });
    const issue = reportRuntimeLaneAutomationIssue({
      spec,
      pr,
      state: "cd-timeout",
      phase: `${spec.lane}-source-head`,
      startedAt,
      observedAt,
      preflight,
      sourceCommit,
      pipelineRun,
      details: { expectedCommit, headWait },
      message: `${spec.version} source head 未在等待窗口内对齐 merge commit，CD 未启动。`,
      dryRun: false,
    });
    return { ok: false, phase: `${spec.lane}-head`, pr, merge: commandData(merge), expectedCommit, headWait, comment: record(comment).ok === true ? comment : startedComment, issue, degradedReason: `${spec.lane}-head-unresolved-after-merge` };
  }
  const before = runtimeLaneStatusWithGitMirror(spec, sourceCommit);
  const beforeSummary = summarizeRuntimeLaneCdStatus(spec, before);
  const trigger = runtimeLaneCdPassedWithMirror(spec, before) ? { ok: true, skipped: true, reason: "source-commit-already-deployed" } : triggerRuntimeLaneCurrent(spec, Math.min(options.timeoutSeconds, 600));
  printEvent(`${spec.lane}.cd.trigger`, { pr: pr.number, sourceCommit, pipelineRun, ok: record(trigger).ok, skipped: record(trigger).skipped ?? false, degradedReason: record(trigger).degradedReason ?? null });
  printRuntimeLanePrMonitorProgress(spec, { stage: "cd-trigger", status: record(trigger).ok === true ? "succeeded" : "failed", pr: pr.number, sourceCommit, pipelineRun, skipped: record(trigger).skipped ?? false, degradedReason: record(trigger).degradedReason ?? null });
  if (record(trigger).ok !== true) {
    const observedAt = new Date().toISOString();
    const comment = commentRuntimeLanePullRequest(spec, {
      pr,
      phase: "cd-trigger",
      state: "cd-failed",
      startedAt,
      observedAt,
      elapsedSeconds: durationSeconds(startedAt, observedAt),
      preflight,
      merge: commandData(merge),
      sourceCommit,
      pipelineRun,
      cd: beforeSummary,
      dryRun: false,
      message: `PR 已合并，但 ${spec.version} CD 触发失败；需要查看 trigger 阶段的 degradedReason。`,
    });
    const issue = reportRuntimeLaneAutomationIssue({
      spec,
      pr,
      state: "cd-failed",
      phase: "cd-trigger",
      startedAt,
      observedAt,
      preflight,
      sourceCommit,
      pipelineRun,
      cd: beforeSummary,
      details: { trigger },
      message: `${spec.version} CD 触发失败，PipelineRun 未进入可等待状态。`,
      dryRun: false,
    });
    return { ok: false, phase: "cd-trigger", pr, sourceCommit, pipelineRun, trigger, comment, issue };
  }
  const cd = await waitForRuntimeLaneCd(spec, sourceCommit, options.timeoutSeconds);
  const cdStatus = record(cd.status);
  const flush = record(cd.flush);
  const cdOk = cd.ok === true;
  const cdPhase = String(cd.phase ?? "");
  const cdPipelineRun = stringOrNull(cdStatus.pipelineRun) ?? pipelineRun;
  const observedAt = new Date().toISOString();
  const finalComment = commentRuntimeLanePullRequest(spec, {
    pr,
    phase: cdPhase,
    state: cdOk ? "cd-succeeded" : cdPhase === "timeout" ? "cd-timeout" : "cd-failed",
    startedAt,
    observedAt,
    elapsedSeconds: durationSeconds(startedAt, observedAt),
    preflight,
    merge: commandData(merge),
    sourceCommit,
    pipelineRun: cdPipelineRun,
    cd: cdStatus,
    flush,
    dryRun: false,
    message: cdOk
      ? `${spec.version} 自动 CD 已完成：PipelineRun、Argo/runtime、公开探针和 Git mirror flush 收口均已检查。`
      : cdPhase === "timeout"
        ? `${spec.version} 自动 CD 超时未收口；本评论保留最后一次 PipelineRun / Argo / public probe / Git mirror 状态用于接续排障。`
        : `${spec.version} 自动 CD 失败；本评论保留失败阶段和最后一次状态用于接续排障。`,
  });
  const issue = cdOk ? { ok: true, skipped: true, reason: "cd-succeeded" } : reportRuntimeLaneAutomationIssue({
    spec,
    pr,
    state: cdPhase === "timeout" ? "cd-timeout" : "cd-failed",
    phase: cdPhase || "cd",
    startedAt,
    observedAt,
    preflight,
    sourceCommit,
    pipelineRun: cdPipelineRun,
    cd: cdStatus,
    details: { cd },
    message: cdPhase === "timeout" ? `${spec.version} 自动 CD 超时。` : `${spec.version} 自动 CD 失败。`,
    dryRun: false,
  });
  return {
    ok: cdOk && record(finalComment).ok === true && record(issue).ok === true,
    action: cdOk ? `merged-and-rolled-${spec.lane}` : `${spec.lane}-cd-failed`,
    phase: cdOk ? "cd-passed" : cdPhase,
    pr,
    sourceCommit,
    pipelineRun: cdPipelineRun,
    trigger,
    cd,
    comment: finalComment,
    issue,
  };
}
