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

// Moved mechanically from scripts/src/hwlab-legacy-runtime.ts:2788-3345 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, LegacyRuntimeControlPlaneOptions } from "./types";
import { legacyRuntimeHostScript } from "./images";
import { statusText } from "./pr-monitor";
import { legacyRuntimeK3s, isCommandSuccess, nested, shellQuote, shortSha, stringOrNull } from "./remote";
import { runtimeLanePipelineRunName, defaultRuntimeLanePipelineRunName } from "./source";
import { CI_NAMESPACE } from "./types";

export interface CleanupPipelineRunRow {
  name: string;
  createdAt: string;
  ageMinutes: number | null;
  status: string | null;
  reason: string | null;
}

export function pipelinePrefixesForLane(lane: LegacyRuntimeControlPlaneOptions["lane"]): string[] {
  if (isHwlabRuntimeLane(lane)) return [`${hwlabRuntimeLaneSpec(lane).pipelineRunPrefix}-`];
  if (lane === "g14") return ["hwlab-g14-ci-poll-"];
  return [...hwlabRuntimeLaneIds().map((runtimeLane) => `${hwlabRuntimeLaneSpec(runtimeLane).pipelineRunPrefix}-`), "hwlab-g14-ci-poll-"];
}

export function commandErrorSummary(result: CommandJsonResult): string {
  const remoteStderr = String(nested(result.parsed, ["data", "stderr"]) ?? "").trim();
  const remoteStdout = String(nested(result.parsed, ["data", "stdout"]) ?? "").trim();
  const text = [result.stderr.trim(), remoteStderr, remoteStdout, result.stdout.trim()].find((item) => item.length > 0) ?? "";
  return text.slice(0, 4000);
}

export function tailText(value: unknown, maxBytes = 2000): string {
  const text = String(value ?? "").trim();
  return text.length <= maxBytes ? text : text.slice(-maxBytes);
}

export function cleanupPipelineRunFieldsJsonPath(range: boolean): string {
  const body = [
    "{.metadata.name}",
    "{\"\\t\"}",
    "{.metadata.creationTimestamp}",
    "{\"\\t\"}",
    "{.status.conditions[0].status}",
    "{\"\\t\"}",
    "{.status.conditions[0].reason}",
    "{\"\\n\"}",
  ].join("");
  return range ? `jsonpath={range .items[*]}${body}{end}` : `jsonpath=${body}`;
}

export function parseCleanupPipelineRunLine(line: string, nowMs: number): CleanupPipelineRunRow | null {
  const [name = "", createdAt = "", status = "", reason = ""] = line.trim().split("\t");
  if (name.length === 0) return null;
  const createdMs = Date.parse(createdAt);
  const ageMinutes = Number.isFinite(createdMs) ? Math.floor((nowMs - createdMs) / 60000) : null;
  return { name, createdAt, ageMinutes, status: status || null, reason: reason || null };
}

export function formatRetentionBytes(bytes: number): string | null {
  if (!Number.isFinite(bytes)) return null;
  const units = ["B", "KiB", "MiB", "GiB", "TiB"];
  let value = bytes;
  let unit = 0;
  while (value >= 1024 && unit < units.length - 1) {
    value /= 1024;
    unit += 1;
  }
  return `${value.toFixed(unit === 0 ? 0 : 1)}${units[unit]}`;
}

export function storageHostPathFromClaim(volume: string | null, namespace: string, claimName: string | null): string | null {
  if (volume === null || claimName === null) return null;
  if (!/^pvc-[a-z0-9-]+$/iu.test(volume) || !/^pvc-[a-z0-9]+$/iu.test(claimName)) return null;
  return `/var/lib/rancher/k3s/storage/${volume}_${namespace}_${claimName}`;
}

export function safeStorageHostPath(value: unknown): string | null {
  const path = stringOrNull(value);
  return path !== null && path.startsWith("/var/lib/rancher/k3s/storage/") ? path : null;
}

export function remoteStoragePathEstimates(paths: Array<string | null | undefined>): Map<string, number | null> {
  const uniquePaths = [...new Set(paths.map((item) => safeStorageHostPath(item)).filter((item): item is string => item !== null))];
  const estimates = new Map<string, number | null>(uniquePaths.map((path) => [path, null]));
  if (uniquePaths.length === 0) return estimates;
  const script = [
    "set -eu",
    `for path in ${uniquePaths.map((path) => shellQuote(path)).join(" ")}; do`,
    "  bytes=\"\"",
    "  if [ -e \"$path\" ]; then bytes=$(du -sB1 \"$path\" 2>/dev/null | awk '{print $1}' || true); fi",
    "  printf '%s\\t%s\\n' \"$path\" \"$bytes\"",
    "done",
  ].join("\n");
  const result = legacyRuntimeHostScript(script, 120_000);
  if (!isCommandSuccess(result)) return estimates;
  for (const line of statusText(result).split(/\r?\n/u)) {
    const [path = "", rawBytes = ""] = line.trim().split("\t");
    if (!estimates.has(path)) continue;
    const bytes = /^\d+$/u.test(rawBytes) ? Number(rawBytes) : null;
    estimates.set(path, Number.isFinite(bytes) ? bytes : null);
  }
  return estimates;
}

export function sumEstimatedBytes(items: Record<string, unknown>[]): number {
  return items.reduce((sum, item) => {
    const bytes = item.estimatedBytes;
    return sum + (typeof bytes === "number" && Number.isFinite(bytes) ? bytes : 0);
  }, 0);
}

export function cleanupPipelineRunTargetCandidate(input: {
  targetPipelineRun: string;
  text: string;
  commandOk: boolean;
  exitCode: number | null;
  stderr: string;
  minAgeMinutes: number;
  nowMs: number;
}): Record<string, unknown> {
  if (!input.commandOk) {
    const queryError = tailText(input.stderr);
    const notFound = /notfound|not found/i.test(queryError);
    return {
      name: input.targetPipelineRun,
      createdAt: null,
      ageMinutes: null,
      status: null,
      reason: notFound ? "target-pipelinerun-not-found" : "target-pipelinerun-query-failed",
      selected: false,
      queryExitCode: input.exitCode,
      ...(queryError.length > 0 ? { queryError } : {}),
    };
  }
  const target = parseCleanupPipelineRunLine(input.text, input.nowMs);
  if (target === null || target.name !== input.targetPipelineRun) {
    return {
      name: input.targetPipelineRun,
      createdAt: null,
      ageMinutes: null,
      status: null,
      reason: "target-pipelinerun-query-empty",
      selected: false,
      queryOutputPreview: tailText(input.text),
    };
  }
  if (target.status !== "True" && target.status !== "False") {
    return {
      ...target,
      selected: false,
      selectedReason: "target-pipelinerun-not-terminal",
    };
  }
  const ageMinutes = typeof target.ageMinutes === "number" ? target.ageMinutes : null;
  const belowMinAge = ageMinutes === null || ageMinutes < input.minAgeMinutes;
  return {
    ...target,
    selected: !belowMinAge,
    ...(belowMinAge
      ? { selectedReason: ageMinutes === null ? "missing-creation-timestamp" : "below-min-age" }
      : {}),
  };
}

export function cleanupPipelineRunTargetCandidateFromTextForTest(input: {
  targetPipelineRun: string;
  text: string;
  commandOk?: boolean;
  exitCode?: number | null;
  stderr?: string;
  minAgeMinutes?: number;
  nowMs?: number;
}): Record<string, unknown> {
  return cleanupPipelineRunTargetCandidate({
    targetPipelineRun: input.targetPipelineRun,
    text: input.text,
    commandOk: input.commandOk ?? true,
    exitCode: input.exitCode ?? 0,
    stderr: input.stderr ?? "",
    minAgeMinutes: input.minAgeMinutes ?? 60,
    nowMs: input.nowMs ?? Date.now(),
  });
}

export function listCleanupPipelineRuns(options: LegacyRuntimeControlPlaneOptions): Record<string, unknown>[] {
  if (options.lane !== "g14" && options.lane !== "all" && !isHwlabRuntimeLane(options.lane)) {
    throw new Error("control-plane cleanup-runs requires --lane v02|v03|g14|all");
  }
  const targetPipelineRun = options.pipelineRun ?? (options.sourceCommit === undefined
    ? undefined
    : isHwlabRuntimeLane(options.lane)
      ? runtimeLanePipelineRunName(hwlabRuntimeLaneSpec(options.lane), options.sourceCommit)
      : defaultRuntimeLanePipelineRunName(options.sourceCommit));
  const now = Date.now();
  if (targetPipelineRun !== undefined) {
    const targetResult = legacyRuntimeK3s([
      "kubectl",
      "get",
      "pipelinerun",
      "-n",
      CI_NAMESPACE,
      targetPipelineRun,
      "-o",
      cleanupPipelineRunFieldsJsonPath(false),
    ], 60_000);
    return [cleanupPipelineRunTargetCandidate({
      targetPipelineRun,
      text: statusText(targetResult),
      commandOk: isCommandSuccess(targetResult),
      exitCode: targetResult.exitCode,
      stderr: commandErrorSummary(targetResult),
      minAgeMinutes: options.minAgeMinutes,
      nowMs: now,
    })];
  }
  const result = legacyRuntimeK3s([
    "kubectl",
    "get",
    "pipelinerun",
    "-n",
    CI_NAMESPACE,
    "-o",
    cleanupPipelineRunFieldsJsonPath(true),
  ], 60_000);
  if (!isCommandSuccess(result)) {
    throw new Error(`failed to list hwlab-ci PipelineRuns: ${commandErrorSummary(result)}`);
  }
  const prefixes = pipelinePrefixesForLane(options.lane);
  const terminalRuns = statusText(result)
    .split(/\r?\n/u)
    .map((line) => line.trim())
    .filter(Boolean)
    .map((line) => parseCleanupPipelineRunLine(line, now))
    .filter((item): item is CleanupPipelineRunRow => item !== null)
    .filter((item) => item.name.length > 0 && prefixes.some((prefix) => item.name.startsWith(prefix)))
    .filter((item) => item.status === "True" || item.status === "False")
    .sort((a, b) => String(a.createdAt).localeCompare(String(b.createdAt)));
  const protectedLatestByPrefix = new Map<string, string>();
  for (const prefix of prefixes) {
    const latest = terminalRuns
      .filter((item) => item.name.startsWith(prefix))
      .sort((a, b) => String(b.createdAt).localeCompare(String(a.createdAt)))[0];
    if (latest !== undefined) protectedLatestByPrefix.set(prefix, latest.name);
  }
  const candidates = terminalRuns
    .filter((item) => typeof item.ageMinutes === "number" && item.ageMinutes >= options.minAgeMinutes)
    .map((item) => {
      const protectedLatest = [...protectedLatestByPrefix.values()].includes(item.name);
      return protectedLatest
        ? { ...item, selected: false, selectedReason: "protected-latest-pipelinerun" }
        : item;
    })
    .slice(0, options.limit);
  return candidates;
}

export function listOwnedWorkspacePvcs(pipelineRunNames: string[]): Record<string, unknown>[] {
  if (pipelineRunNames.length === 0) {
    return [];
  }
  const result = legacyRuntimeK3s([
    "kubectl",
    "get",
    "pvc",
    "-n",
    CI_NAMESPACE,
    "-o",
    'jsonpath={range .items[*]}{.metadata.name}{"\\t"}{.spec.volumeName}{"\\t"}{.status.phase}{"\\t"}{.metadata.ownerReferences[0].kind}{"\\t"}{.metadata.ownerReferences[0].name}{"\\n"}{end}',
  ], 60_000);
  if (!isCommandSuccess(result)) {
    throw new Error(`failed to list hwlab-ci PipelineRun PVCs: ${commandErrorSummary(result)}`);
  }
  const pvResult = legacyRuntimeK3s([
    "kubectl",
    "get",
    "pv",
    "-o",
    'jsonpath={range .items[*]}{.metadata.name}{"\\t"}{.spec.storageClassName}{"\\t"}{.spec.persistentVolumeReclaimPolicy}{"\\t"}{.spec.local.path}{"\\t"}{.spec.hostPath.path}{"\\n"}{end}',
  ], 60_000);
  if (!isCommandSuccess(pvResult)) {
    throw new Error(`failed to list hwlab-ci PipelineRun PV backing paths: ${commandErrorSummary(pvResult)}`);
  }
  const podResult = legacyRuntimeK3s([
    "kubectl",
    "get",
    "pod",
    "-n",
    CI_NAMESPACE,
    "-o",
    'jsonpath={range .items[*]}{.metadata.name}{"\\t"}{.status.phase}{"\\t"}{range .spec.volumes[*]}{.persistentVolumeClaim.claimName}{","}{end}{"\\n"}{end}',
  ], 60_000);
  if (!isCommandSuccess(podResult)) {
    throw new Error(`failed to list hwlab-ci active pod PVC mounts: ${commandErrorSummary(podResult)}`);
  }
  const pvByName = new Map<string, Record<string, unknown>>();
  for (const line of statusText(pvResult).split(/\r?\n/u)) {
    const [name = "", storageClass = "", reclaimPolicy = "", localPath = "", hostPath = ""] = line.trim().split("\t");
    if (name.length === 0) continue;
    pvByName.set(name, {
      storageClass: storageClass || null,
      reclaimPolicy: reclaimPolicy || null,
      hostPath: safeStorageHostPath(localPath) ?? safeStorageHostPath(hostPath),
    });
  }
  const activeClaimPods = new Map<string, string[]>();
  for (const line of statusText(podResult).split(/\r?\n/u)) {
    const [podName = "", phase = "", claims = ""] = line.trim().split("\t");
    if (podName.length === 0 || phase === "Succeeded" || phase === "Failed") continue;
    for (const claimName of claims.split(",").map((item) => item.trim()).filter(Boolean)) {
      const entry = activeClaimPods.get(claimName) ?? [];
      entry.push(podName);
      activeClaimPods.set(claimName, entry);
    }
  }
  const wanted = new Set(pipelineRunNames);
  const ownedPvcs = statusText(result)
    .split(/\r?\n/u)
    .map((line) => line.trim())
    .filter(Boolean)
    .map((line) => {
      const [name = "", volume = "", phase = "", ownerKind = "", owner = ""] = line.split("\t");
      const pv = volume.length > 0 ? pvByName.get(volume) : undefined;
      const hostPath = safeStorageHostPath(pv?.hostPath) ?? storageHostPathFromClaim(volume || null, CI_NAMESPACE, name || null);
      return {
        name,
        volume: volume || null,
        phase: phase || null,
        ownerKind: ownerKind || null,
        owner: owner || null,
        storageClass: pv?.storageClass ?? null,
        reclaimPolicy: pv?.reclaimPolicy ?? null,
        hostPath,
        activeMountPods: activeClaimPods.get(name) ?? [],
      };
    })
    .filter((item) => item.ownerKind === "PipelineRun" && typeof item.owner === "string" && wanted.has(item.owner));
  const estimates = remoteStoragePathEstimates(ownedPvcs.map((item) => stringOrNull(item.hostPath)));
  return ownedPvcs.map((item) => ({
    ...item,
    estimatedBytes: item.hostPath === null ? null : estimates.get(String(item.hostPath)) ?? null,
  }));
}

export function deletePipelineRuns(names: string[], timeoutMs: number): CommandJsonResult {
  if (names.length === 0) {
    return {
      ok: true,
      command: [],
      exitCode: 0,
      stdout: "no candidates",
      stderr: "",
      parsed: null,
    };
  }
  return legacyRuntimeK3s(["kubectl", "delete", "pipelinerun", "-n", CI_NAMESPACE, ...names, "--ignore-not-found=true"], timeoutMs);
}

export function listReleasedCiWorkspacePvs(options: LegacyRuntimeControlPlaneOptions): Record<string, unknown>[] {
  const result = legacyRuntimeK3s([
    "kubectl",
    "get",
    "pv",
    "-o",
    'jsonpath={range .items[*]}{.metadata.name}{"\\t"}{.metadata.creationTimestamp}{"\\t"}{.status.phase}{"\\t"}{.spec.storageClassName}{"\\t"}{.spec.persistentVolumeReclaimPolicy}{"\\t"}{.spec.claimRef.namespace}{"\\t"}{.spec.claimRef.name}{"\\t"}{.spec.capacity.storage}{"\\t"}{.spec.local.path}{"\\t"}{.spec.hostPath.path}{"\\n"}{end}',
  ], 60_000);
  if (!isCommandSuccess(result)) {
    throw new Error(`failed to list released hwlab-ci PVs: ${commandErrorSummary(result)}`);
  }
  const candidates = statusText(result)
    .split(/\r?\n/u)
    .map((line) => line.trim())
    .filter(Boolean)
    .map((line) => {
      const [name = "", createdAt = "", phase = "", storageClass = "", reclaimPolicy = "", claimNamespace = "", claimName = "", capacity = "", localPath = "", hostPath = ""] = line.split("\t");
      return {
        name,
        createdAt,
        phase,
        storageClass,
        reclaimPolicy,
        claimNamespace,
        claimName,
        capacity,
        hostPath: safeStorageHostPath(localPath) ?? safeStorageHostPath(hostPath) ?? (claimNamespace === CI_NAMESPACE ? storageHostPathFromClaim(name || null, claimNamespace, claimName || null) : null),
      };
    })
    .filter((item) => item.phase === "Released")
    .filter((item) => item.storageClass === "local-path" && item.reclaimPolicy === "Delete")
    .filter((item) => item.claimNamespace === CI_NAMESPACE && typeof item.claimName === "string" && /^pvc-[a-z0-9]+$/u.test(item.claimName))
    .sort((a, b) => String(a.createdAt).localeCompare(String(b.createdAt)))
    .slice(0, options.limit);
  const estimates = remoteStoragePathEstimates(candidates.map((item) => stringOrNull(item.hostPath)));
  return candidates.map((item) => ({
    ...item,
    estimatedBytes: item.hostPath === null ? null : estimates.get(String(item.hostPath)) ?? null,
  }));
}

export function deletePersistentVolumes(names: string[], timeoutMs: number): CommandJsonResult {
  if (names.length === 0) {
    return {
      ok: true,
      command: [],
      exitCode: 0,
      stdout: "no candidates",
      stderr: "",
      parsed: null,
    };
  }
  return legacyRuntimeK3s(["kubectl", "delete", "pv", ...names, "--ignore-not-found=true"], timeoutMs);
}

export function runControlPlaneReleasedPvCleanup(options: LegacyRuntimeControlPlaneOptions): Record<string, unknown> {
  const candidates = listReleasedCiWorkspacePvs(options);
  const candidateNames = candidates.map((item) => String(item.name));
  const estimatedReclaimBytes = sumEstimatedBytes(candidates);
  if (options.dryRun) {
    return {
      ok: true,
      command: "hwlab g14 control-plane cleanup-released-pvs",
      mode: "dry-run",
      lane: options.lane,
      limit: options.limit,
      candidates,
      candidateCount: candidates.length,
      selectedPersistentVolumes: candidateNames,
      selectedPersistentVolumeCount: candidateNames.length,
      estimatedReclaimBytes,
      estimatedReclaimHuman: formatRetentionBytes(estimatedReclaimBytes),
      mutation: false,
      next: { confirm: `bun scripts/cli.ts hwlab g14 control-plane cleanup-released-pvs --lane all --limit ${options.limit} --confirm` },
    };
  }
  const deletion = deletePersistentVolumes(candidateNames, options.timeoutSeconds * 1000);
  return {
    ok: isCommandSuccess(deletion),
    command: "hwlab g14 control-plane cleanup-released-pvs",
    mode: "confirmed-cleanup",
    lane: options.lane,
    limit: options.limit,
    deletedPersistentVolumes: candidateNames,
    deletedPersistentVolumeCount: candidateNames.length,
    candidatesBefore: candidates,
    estimatedReclaimBytes,
    estimatedReclaimHuman: formatRetentionBytes(estimatedReclaimBytes),
    deletion,
    followUp: {
      diskPressure: "trans G14:k3s kubectl get node ubuntu-rog-zephyrus-g14-ga401iv-ga401iv -o jsonpath='{range .status.conditions[*]}{.type}{\"=\"}{.status}{\" \"}{.reason}{\"\\n\"}{end}'",
      storage: "trans G14 sh -- 'df -h /; sudo du -xh -d 1 /var/lib/rancher/k3s/storage 2>/dev/null | sort -h | tail -20'",
    },
  };
}

export function runDefaultRuntimeLaneRuntimeMigration(options: LegacyRuntimeControlPlaneOptions, sourceCommit: string): Record<string, unknown> {
  const reportPath = `/tmp/hwlab-v02-runtime-migration-${shortSha(sourceCommit)}.json`;
  const migrationArgs = options.dryRun
    ? [
      ...(options.allowLiveDbRead ? ["--dry-run", "--allow-live-db-read", "--confirm-dev"] : ["--check"]),
      "--report",
      reportPath,
    ]
    : [
      "--apply",
      "--confirm-dev",
      "--confirmed-non-production",
      "--report",
      reportPath,
    ];
  const command = [
    "exec",
    "-n",
    "hwlab-v02",
    "deployment/hwlab-cloud-api",
    "-c",
    "hwlab-cloud-api",
    "--",
    "bun",
    "cmd/hwlab-cloud-api/migrate.ts",
    ...migrationArgs,
  ];
  const result = legacyRuntimeK3s(["kubectl", ...command], options.timeoutSeconds * 1000);
  const ok = isCommandSuccess(result);
  return {
    ok,
    command: "hwlab g14 control-plane runtime-migration --lane v02",
    lane: "v02",
    mode: options.dryRun ? options.allowLiveDbRead ? "live-read-dry-run" : "source-check" : "confirmed-apply",
    sourceCommit,
    runtimeNamespace: "hwlab-v02",
    target: "deployment/hwlab-cloud-api -c hwlab-cloud-api",
    migrationCommand: ["bun", "cmd/hwlab-cloud-api/migrate.ts", ...migrationArgs],
    reportPath,
    mutation: !options.dryRun,
    result,
    next: options.dryRun
      ? {
        liveReadDryRun: "bun scripts/cli.ts hwlab g14 control-plane runtime-migration --lane v02 --allow-live-db-read --dry-run",
        apply: "bun scripts/cli.ts hwlab g14 control-plane runtime-migration --lane v02 --confirm",
      }
      : {
        health: "curl -fsS --max-time 20 http://74.48.78.17:19667/health/live",
        triggerCurrent: "bun scripts/cli.ts hwlab g14 control-plane trigger-current --lane v02 --confirm",
      },
  };
}

export function runControlPlaneCleanup(options: LegacyRuntimeControlPlaneOptions): Record<string, unknown> {
  const candidates = listCleanupPipelineRuns(options);
  const candidateNames = candidates
    .filter((item) => item.selected !== false)
    .map((item) => String(item.name));
  const ownedPvcs = listOwnedWorkspacePvcs(candidateNames);
  const estimatedReclaimBytes = sumEstimatedBytes(ownedPvcs);
  const followUpStatusLane = isHwlabRuntimeLane(options.lane) ? options.lane : "v02";
  if (options.dryRun) {
    return {
      ok: true,
      command: "hwlab g14 control-plane cleanup-runs",
      mode: "dry-run",
      lane: options.lane,
      minAgeMinutes: options.minAgeMinutes,
      limit: options.limit,
      sourceCommit: options.sourceCommit,
      pipelineRun: options.pipelineRun,
      candidates,
      candidateCount: candidates.length,
      selectedPipelineRuns: candidateNames,
      selectedPipelineRunCount: candidateNames.length,
      ownedPvcs,
      ownedPvcCount: ownedPvcs.length,
      estimatedReclaimBytes,
      estimatedReclaimHuman: formatRetentionBytes(estimatedReclaimBytes),
      mutation: false,
      next: { confirm: [
        "bun scripts/cli.ts hwlab g14 control-plane cleanup-runs",
        `--lane ${options.lane}`,
        `--min-age-minutes ${options.minAgeMinutes}`,
        `--limit ${options.limit}`,
        options.pipelineRun === undefined ? "" : `--pipeline-run ${options.pipelineRun}`,
        options.sourceCommit === undefined ? "" : `--source-commit ${options.sourceCommit}`,
        "--confirm",
      ].filter(Boolean).join(" ") },
    };
  }
  const deletion = deletePipelineRuns(candidateNames, options.timeoutSeconds * 1000);
  return {
    ok: isCommandSuccess(deletion),
    command: "hwlab g14 control-plane cleanup-runs",
    mode: "confirmed-cleanup",
    lane: options.lane,
    minAgeMinutes: options.minAgeMinutes,
    limit: options.limit,
    sourceCommit: options.sourceCommit,
    pipelineRun: options.pipelineRun,
    deletedPipelineRuns: candidateNames,
    deletedPipelineRunCount: candidateNames.length,
    ownedPvcsBefore: ownedPvcs,
    ownedPvcCountBefore: ownedPvcs.length,
    estimatedReclaimBytes,
    estimatedReclaimHuman: formatRetentionBytes(estimatedReclaimBytes),
    deletion,
    followUp: {
      status: `bun scripts/cli.ts hwlab g14 control-plane status --lane ${followUpStatusLane}`,
      diskPressure: "trans G14:k3s kubectl get node ubuntu-rog-zephyrus-g14-ga401iv-ga401iv -o jsonpath='{.spec.taints}{\"\\n\"}{range .status.conditions[*]}{.type}{\"=\"}{.status}{\" \"}{.reason}{\"\\n\"}{end}'",
    },
  };
}
