fix: 收敛 PaC 自动交付提示

This commit is contained in:
Codex
2026-07-11 11:42:16 +02:00
parent b12dd85375
commit b607c3a996
55 changed files with 2953 additions and 1472 deletions
+3 -119
View File
@@ -5,9 +5,8 @@ import { existsSync, readFileSync } from "node:fs";
import { runCommand, type CommandResult } from "./command";
import { repoRoot, rootPath } from "./config";
import type { AdapterSummary, BranchFollowerDebugStep, BranchFollowerRegistry, FollowerSpec, FollowerState, K8sStateRead, ParsedOptions } from "./cicd-types";
import { renderControllerDebugJob, waitForJobShell } from "./cicd-controller-render";
import { taskRunItems } from "./cicd-taskruns";
import { redactText, shQuote } from "./platform-infra-ops-library";
import { redactText } from "./platform-infra-ops-library";
type KubeScriptRunner = (registry: BranchFollowerRegistry, options: ParsedOptions, script: string, input: string, timeoutMs: number) => CommandResult;
@@ -23,7 +22,6 @@ export interface CicdDebugDeps {
export async function buildDebugStep(registry: BranchFollowerRegistry, options: ParsedOptions, deps: CicdDebugDeps): Promise<Record<string, unknown>> {
const step = options.debugStep ?? "state-read";
if (options.followerId === null) throw new Error("debug-step requires --follower <id>");
if (!options.inCluster) return runTargetDebugStepJob(registry, options, step, deps);
const selected = deps.selectFollowers(registry, options, { includeDisabled: true });
if (selected.length !== 1) throw new Error("debug-step operates on exactly one follower");
@@ -53,7 +51,7 @@ export async function buildDebugStep(registry: BranchFollowerRegistry, options:
action: "debug-step",
step,
follower: follower.id,
execution: "k8s-native-in-cluster",
execution: options.inCluster ? "k8s-native-in-cluster" : "operator-readonly",
dryRun: !options.confirm,
stateBefore: stateSnapshot(before, follower.id),
stateWrite: { ok: false, skipped: true, reason: "stored-state-missing" },
@@ -74,7 +72,7 @@ export async function buildDebugStep(registry: BranchFollowerRegistry, options:
action: "debug-step",
step,
follower: follower.id,
execution: "k8s-native-in-cluster",
execution: options.inCluster ? "k8s-native-in-cluster" : "operator-readonly",
dryRun: !options.confirm,
stateBefore: stateSnapshot(before, follower.id),
controllerSource,
@@ -128,123 +126,10 @@ export function renderDebugStepHuman(payload: Record<string, unknown>): string {
`controller-source: ${next?.controllerSource ?? "-"}`,
`status-read: ${next?.statusRead ?? "-"}`,
`decide: ${next?.decide ?? "-"}`,
`state-write: ${next?.stateWrite ?? "-"}`,
"",
].filter((line) => line !== "").join("\n");
}
function runTargetDebugStepJob(registry: BranchFollowerRegistry, options: ParsedOptions, step: BranchFollowerDebugStep, deps: CicdDebugDeps): Record<string, unknown> {
const timeoutSeconds = options.timeoutSeconds ?? registry.controller.budgets.runOnceSeconds;
const jobName = `${registry.controller.deploymentName}-debug-${step}-${Date.now().toString(36)}`.replace(/[^a-z0-9-]+/gu, "-").slice(0, 63);
const manifest = renderControllerDebugJob(registry, options, jobName, step, timeoutSeconds);
const manifestYaml = `${Bun.YAML.stringify(manifest).trim()}\n`;
const script = [
"set -eu",
"tmp=$(mktemp)",
"base64 -d >\"$tmp\" <<'UNIDESK_CICD_DEBUG_JOB_B64'",
Buffer.from(manifestYaml, "utf8").toString("base64"),
"UNIDESK_CICD_DEBUG_JOB_B64",
`kubectl -n ${shQuote(registry.controller.namespace)} delete job ${shQuote(jobName)} --ignore-not-found=true >/dev/null 2>&1 || true`,
`kubectl apply --server-side --force-conflicts --field-manager=${shQuote(registry.controller.fieldManager)} -f "$tmp" >/dev/null`,
waitForJobShell(registry.controller.namespace, jobName, timeoutSeconds),
].join("\n");
const result = deps.runKubeScript(registry, options, script, "", (timeoutSeconds + registry.controller.budgets.reconcileTransportGraceSeconds) * 1000);
const parsed = parseLastJsonObject(result.stdout);
const state = deps.readK8sState(registry, options);
const followerId = options.followerId ?? "";
const compact = compactTargetDebugResult(parsed);
const ok = result.exitCode === 0 && parsed?.ok !== false;
const includeTargetTail = !ok || parsed === null;
const fallbackStateAfter = stateSnapshot(state, followerId);
return {
ok,
action: "debug-step",
step,
follower: followerId,
execution: "k8s-native-debug-job",
dryRun: !options.confirm,
stateBefore: compact?.stateBefore ?? compactStateLike(asOptionalRecord(parsed?.stateBefore)),
controllerSource: compact?.controllerSource ?? asOptionalRecord(parsed?.controllerSource),
status: compact?.status ?? null,
decision: compact?.decision ?? null,
stateWrite: compact?.stateWrite ?? null,
target: {
name: jobName,
namespace: registry.controller.namespace,
exitCode: result.exitCode,
timedOut: result.timedOut,
parsed: parsed !== null,
stdoutTail: includeTargetTail ? redactText(tailText(result.stdout, 1000)) : "",
stderrTail: includeTargetTail ? redactText(tailText(result.stderr, 800)) : "",
},
stateAfter: compact?.stateAfter ?? compactStateLike(asOptionalRecord(parsed?.stateAfter) ?? fallbackStateAfter),
parsedDownstreamCliOutput: false,
next: debugNext(followerId),
};
}
function compactTargetDebugResult(parsed: Record<string, unknown> | null): Record<string, unknown> | null {
if (parsed === null) return null;
const stateBefore = asOptionalRecord(parsed.stateBefore);
const controllerSource = asOptionalRecord(parsed.controllerSource);
const status = asOptionalRecord(parsed.status);
const decision = asOptionalRecord(parsed.decision);
const stateWrite = asOptionalRecord(parsed.stateWrite);
const stateAfter = asOptionalRecord(parsed.stateAfter);
return {
ok: parsed.ok === true,
action: stringOrNull(parsed.action),
step: stringOrNull(parsed.step),
follower: stringOrNull(parsed.follower),
stateBefore: compactStateLike(stateBefore),
controllerSource: controllerSource === null ? null : {
ok: controllerSource.ok === true,
repository: stringOrNull(controllerSource.repository),
branch: stringOrNull(controllerSource.branch),
head: stringOrNull(controllerSource.head),
registrySha256: stringOrNull(controllerSource.registrySha256),
closeoutRereadMarker: controllerSource.closeoutRereadMarker === true,
branchFollowerFile: asOptionalRecord(controllerSource.branchFollowerFile),
errors: Array.isArray(controllerSource.errors) ? controllerSource.errors.map(String).slice(0, 5) : [],
},
status: status === null ? null : {
ok: status.ok === true,
phase: stringOrNull(status.phase),
observedSha: stringOrNull(status.observedSha),
targetSha: stringOrNull(status.targetSha),
aligned: status.aligned === true ? true : status.aligned === false ? false : null,
pipelineRun: stringOrNull(status.pipelineRun),
message: stringOrNull(status.message),
gates: asOptionalRecord(status.gates),
},
decision: decision === null ? null : {
phase: stringOrNull(decision.phase),
observedSha: stringOrNull(decision.observedSha),
targetSha: stringOrNull(decision.targetSha),
decision: stringOrNull(decision.decision),
totalSeconds: numberOrNull(asOptionalRecord(decision.timings)?.totalSeconds),
totalStatus: stringOrNull(asOptionalRecord(decision.timings)?.totalStatus),
},
stateWrite,
stateAfter: compactStateLike(stateAfter),
};
}
function compactStateLike(value: Record<string, unknown> | null): Record<string, unknown> | null {
if (value === null) return null;
const metadata = asOptionalRecord(value.metadata);
return {
ok: value.ok === true,
phase: stringOrNull(value.phase),
observedSha: stringOrNull(value.observedSha),
targetSha: stringOrNull(value.targetSha),
totalSeconds: numberOrNull(value.totalSeconds),
timingStatus: stringOrNull(value.timingStatus),
resourceVersion: stringOrNull(metadata?.resourceVersion),
rawStateDiagnostic: asOptionalRecord(value.rawStateDiagnostic),
};
}
function controllerSourceSnapshot(registry: BranchFollowerRegistry): Record<string, unknown> {
const head = commandText(["git", "rev-parse", "HEAD"]);
const branch = commandText(["git", "symbolic-ref", "--short", "HEAD"]);
@@ -573,7 +458,6 @@ function debugNext(followerId: string): Record<string, string> {
controllerSource: `bun scripts/cli.ts cicd branch-follower debug-step --follower ${followerId} --step controller-source`,
statusRead: `bun scripts/cli.ts cicd branch-follower debug-step --follower ${followerId} --step status-read`,
decide: `bun scripts/cli.ts cicd branch-follower debug-step --follower ${followerId} --step decide`,
stateWrite: `bun scripts/cli.ts cicd branch-follower debug-step --follower ${followerId} --step state-write --confirm`,
};
}