fix: recognize retained PaC source observations

This commit is contained in:
Codex
2026-07-11 00:11:08 +02:00
parent cf0da8cb32
commit 95ab2f5d3e
5 changed files with 849 additions and 119 deletions
+314 -21
View File
@@ -21,6 +21,7 @@ import { materializeYamlComposition } from "./yaml-composition";
const configFile = rootPath("config", "platform-infra", "pipelines-as-code.yaml");
const configLabel = "config/platform-infra/pipelines-as-code.yaml";
const remoteScriptFile = rootPath("scripts", "src", "platform-infra-pipelines-as-code-remote.sh");
const evaluatorFile = rootPath("scripts", "native", "cicd", "pac-status-evaluator.cjs");
const fieldManager = "unidesk-platform-infra-pipelines-as-code";
const y = createYamlFieldReader(configLabel);
@@ -138,6 +139,7 @@ interface CommonOptions {
consumerId: string | null;
full: boolean;
raw: boolean;
json: boolean;
}
export interface PipelinesAsCodeNodeStatusOptions {
@@ -182,48 +184,54 @@ export async function runPlatformInfraPipelinesAsCodeCommand(config: UniDeskConf
if (action === "plan") {
const options = parseCommonOptions(args.slice(1));
const result = plan(options);
return options.full || options.raw ? result : renderPlan(result);
return options.json || options.full || options.raw ? result : renderPlan(result);
}
if (action === "apply") {
const options = parseApplyOptions(args.slice(1));
const result = await apply(config, options);
return options.full || options.raw ? result : renderApply(result);
return options.json || options.full || options.raw ? result : renderApply(result);
}
if (action === "status") {
const options = parseCommonOptions(args.slice(1));
const result = await status(config, options);
return options.full || options.raw ? result : renderStatus(result);
return options.full || options.raw ? result : options.json ? compactStatusJson(result) : renderStatus(result);
}
if (action === "closeout") {
const options = parseCloseoutOptions(args.slice(1));
const result = await closeout(config, options);
return options.full || options.raw ? result : renderCloseout(result);
return options.full || options.raw ? result : options.json ? compactCloseoutJson(result) : renderCloseout(result);
}
if (action === "history" || action === "runs") {
const options = parseHistoryOptions(args.slice(1));
const result = await history(config, options);
return options.full || options.raw ? result : renderHistory(result);
return options.full || options.raw ? result : options.json ? compactHistoryJson(result) : renderHistory(result);
}
if (action === "debug-step") {
const options = parseCommonOptions(args.slice(1));
const result = await debugStep(config, options);
return options.full || options.raw ? result : options.json ? result : renderDebugStep(result);
}
if (action === "webhook-test") {
const options = parseWebhookTestOptions(args.slice(1));
const result = await webhookTest(config, options);
return options.full || options.raw ? result : renderWebhookTest(result);
return options.json || options.full || options.raw ? result : renderWebhookTest(result);
}
return { ok: false, error: "unsupported-platform-infra-pipelines-as-code-command", args, help: help() };
}
function help(): Record<string, unknown> {
return {
command: "platform-infra pipelines-as-code plan|apply|status|closeout|history",
command: "platform-infra pipelines-as-code plan|apply|status|closeout|history|debug-step",
configTruth: configLabel,
usage: [
"bun scripts/cli.ts platform-infra pipelines-as-code plan --target JD01",
"bun scripts/cli.ts platform-infra pipelines-as-code apply --target JD01 --dry-run",
"bun scripts/cli.ts platform-infra pipelines-as-code apply --target JD01 --confirm",
"bun scripts/cli.ts platform-infra pipelines-as-code status --target JD01 [--full|--raw]",
"bun scripts/cli.ts platform-infra pipelines-as-code status --target JD01 [--json|--full|--raw]",
"bun scripts/cli.ts platform-infra pipelines-as-code closeout --target JD01 --consumer <consumer> --source-commit <sha> --wait",
"bun scripts/cli.ts platform-infra pipelines-as-code history --target JD01 [--consumer hwlab-jd01-v03] [--limit 10]",
"bun scripts/cli.ts platform-infra pipelines-as-code history --target JD01 --id <pipelinerun>",
"bun scripts/cli.ts platform-infra pipelines-as-code debug-step --target JD01 [--json]",
],
diagnostics: "webhook-test exists only for bounded connectivity diagnosis and must not be used as delivery evidence.",
boundary: "Sole CI trigger path for GH-1552/GH-1607: GitHub PR merge -> GitHub webhook bridge -> Gitea mirror/snapshot -> Pipelines-as-Code -> Tekton -> GitOps/Argo/k8s runtime.",
@@ -469,7 +477,7 @@ async function status(config: UniDeskConfig, options: CommonOptions): Promise<Re
consumer,
coverage: consumerCoverage(pac, target.id),
summary,
remote: parsed === null ? compactCapture(result, { full: true }) : options.raw || options.full ? parsed : summary,
remote: parsed === null ? compactCapture(result, { full: true }) : options.raw ? parsed : undefined,
next: nextCommands(target.id, consumer.id, pac.defaults.consumerId),
};
}
@@ -486,7 +494,7 @@ export async function getPlatformInfraPipelinesAsCodeNodeStatus(config: UniDeskC
const statuses = await Promise.all(consumers.map(async (consumer) => {
const repository = resolveRepository(pac, consumer.repositoryRef);
try {
const current = await status(config, { targetId: target.id, consumerId: consumer.id, full: false, raw: false });
const current = await status(config, { targetId: target.id, consumerId: consumer.id, full: false, raw: false, json: false });
return nodeStatusRow(target.id, consumer, repository, current);
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
@@ -564,7 +572,7 @@ async function closeout(config: UniDeskConfig, options: CloseoutOptions): Promis
let observedSourceCommit = observedCloseoutSourceCommit(latest, diagnostics);
let sourceMatched = options.sourceCommit === null || observedSourceCommit === options.sourceCommit;
const ciReady = closeoutCiReady(current, options.sourceCommit);
let gitOpsMirrorFlush = await runCloseoutGitOpsMirrorFlush(config, pac, consumer, latest, ciReady, observedSourceCommit);
let gitOpsMirrorFlush = await runCloseoutGitOpsMirrorFlush(config, pac, consumer, summary, latest, ciReady, observedSourceCommit);
const gitOpsMirrorFlushReady = record(gitOpsMirrorFlush).ok !== false;
const runtimeStartedAt = Date.now();
if (ciReady && gitOpsMirrorFlushReady) {
@@ -621,8 +629,12 @@ async function closeout(config: UniDeskConfig, options: CloseoutOptions): Promis
async function history(config: UniDeskConfig, options: HistoryOptions): Promise<Record<string, unknown>> {
const pac = readPacConfig();
const target = resolveTarget(pac, options.targetId);
const targetConsumers = pac.consumers.filter((consumer) => consumer.node.toLowerCase() === target.id.toLowerCase());
const inferredDetailConsumers = options.detailId === null
? []
: targetConsumers.filter((consumer) => options.detailId?.startsWith(consumer.pipelineRunPrefix));
const selectedConsumers = options.consumerId === null
? pac.consumers
? inferredDetailConsumers.length === 1 ? inferredDetailConsumers : targetConsumers
: [resolveConsumer(pac, options.consumerId)];
const firstConsumer = selectedConsumers[0];
if (firstConsumer === undefined) throw new Error("no Pipelines-as-Code consumers are configured");
@@ -648,11 +660,41 @@ async function history(config: UniDeskConfig, options: HistoryOptions): Promise<
detailId: options.detailId,
rows: arrayRecords(record(remote).rows),
historyErrors,
remote,
controlPlane: parsed === null ? undefined : {
crdPresent: parsed.crdPresent === true,
controllerReady: parsed.controllerReady,
repositoryCondition: parsed.repositoryCondition,
repository: record(parsed.repository),
consumerRows: arrayRecords(parsed.consumerRows),
webhookCount: arrayRecords(parsed.webhooks).length,
valuesPrinted: false,
},
remote: parsed === null || options.raw ? remote : undefined,
next: nextCommands(target.id, options.consumerId ?? pac.defaults.consumerId, pac.defaults.consumerId),
};
}
async function debugStep(config: UniDeskConfig, options: CommonOptions): Promise<Record<string, unknown>> {
const pac = readPacConfig();
const target = resolveTarget(pac, options.targetId);
const consumer = resolveConsumer(pac, options.consumerId);
const repository = resolveRepository(pac, consumer.repositoryRef);
const result = await capture(config, target.route, ["sh"], remoteScript("debug-step", pac, target, repository, consumer, { ...options, confirm: false, dryRun: true, wait: false }, emptySecretMaterial(), ""));
const parsed = parseJsonOutput(result.stdout);
return {
ok: result.exitCode === 0 && parsed?.ok === true,
action: "platform-infra-pipelines-as-code-debug-step",
mutation: false,
target: targetSummary(target),
consumer: consumer.id,
evaluator: "scripts/native/cicd/pac-status-evaluator.cjs",
checks: parsed === null ? [] : arrayRecords(parsed.checks),
remote: parsed === null || options.raw ? parsed ?? compactCapture(result, { full: true }) : undefined,
next: nextCommands(target.id, consumer.id, pac.defaults.consumerId),
valuesPrinted: false,
};
}
async function webhookTest(config: UniDeskConfig, options: WebhookTestOptions): Promise<Record<string, unknown>> {
const pac = readPacConfig();
const target = resolveTarget(pac, options.targetId);
@@ -681,10 +723,11 @@ async function fetchReleaseManifest(pac: PacConfig): Promise<string> {
return text;
}
function remoteScript(action: "apply" | "status" | "history" | "webhook-test", pac: PacConfig, target: PacTarget, repository: PacRepository, consumer: PacConsumer, options: ApplyOptions | WebhookTestOptions | HistoryOptions, secrets: SecretMaterial, releaseManifest: string, historyConsumers: PacConsumer[] = [consumer]): string {
function remoteScript(action: "apply" | "status" | "history" | "debug-step" | "webhook-test", pac: PacConfig, target: PacTarget, repository: PacRepository, consumer: PacConsumer, options: ApplyOptions | WebhookTestOptions | HistoryOptions, secrets: SecretMaterial, releaseManifest: string, historyConsumers: PacConsumer[] = [consumer]): string {
const webhookUrl = `${pac.gitea.internalBaseUrl.replace(/\/+$/u, "").replace(/gitea-http\.[^.]+\.svc\.cluster\.local:3000/u, `${pac.release.controllerServiceName}.${pac.release.namespace}.svc.cluster.local:${pac.release.controllerServicePort}`)}`;
const env: Record<string, string> = {
UNIDESK_PAC_ACTION: action,
UNIDESK_PAC_EVALUATOR_B64: Buffer.from(readFileSync(evaluatorFile, "utf8"), "utf8").toString("base64"),
UNIDESK_PAC_FIELD_MANAGER: fieldManager,
UNIDESK_PAC_TARGET_ID: target.id,
UNIDESK_PAC_TARGET_NAMESPACE: consumer.namespace,
@@ -791,6 +834,17 @@ function parseLinePair(path: string, usernameLine: number, passwordLine: number)
return { username, password };
}
function emptySecretMaterial(): SecretMaterial {
return {
adminUsername: "",
adminPassword: "",
adminFingerprint: "",
webhookSecret: "",
webhookFingerprint: "",
webhookPath: "",
};
}
function ensureSecrets(pac: PacConfig, createWebhookSecret: boolean, allowMissingWebhookSecret = false): SecretMaterial {
const adminPath = credentialPath(pac.gitea.admin.sourceRoot, pac.gitea.admin.sourceRef);
if (!existsSync(adminPath)) throw new Error(`${pac.gitea.admin.sourceRef} is missing`);
@@ -858,11 +912,18 @@ function statusSummary(payload: Record<string, unknown>): Record<string, unknown
const runtime = record(payload.runtime);
const diagnostics = record(payload.diagnostics);
const webhooks = arrayRecords(payload.webhooks);
const rawArtifact = record(payload.artifact);
const sourceObservation = record(rawArtifact.sourceObservation);
const provenance = record(diagnostics.provenance);
const noRuntimeChange = stringValue(sourceObservation.mode) === "no-runtime-change"
&& sourceObservation.valid === true
&& stringValue(diagnostics.code) === "pac-ready-no-runtime-change";
const artifact = {
...record(payload.artifact),
imageStatus: record(payload.artifact).imageStatus ?? (runtime.image === undefined ? undefined : "runtime"),
digest: record(payload.artifact).digest ?? runtime.digest,
gitopsCommit: record(payload.artifact).gitopsCommit ?? argo.revision,
...rawArtifact,
imageStatus: noRuntimeChange ? "retained" : rawArtifact.imageStatus ?? (runtime.image === undefined ? undefined : "runtime"),
digest: noRuntimeChange ? provenance.runtimeDigest ?? runtime.digest : rawArtifact.digest ?? runtime.digest,
gitopsCommit: noRuntimeChange ? provenance.gitopsRevision ?? argo.revision : rawArtifact.gitopsCommit ?? argo.revision,
provenanceRelation: noRuntimeChange ? "retained" : record(argo.revisionRelation).relation,
};
const pipelineGate = pipelineRunGate(latest);
return {
@@ -877,6 +938,8 @@ function statusSummary(payload: Record<string, unknown>): Record<string, unknown
taskRuns,
pipelineRunGate: pipelineGate,
artifact,
sourceObservation: Object.keys(sourceObservation).length === 0 ? null : sourceObservation,
provenance: Object.keys(provenance).length === 0 ? null : provenance,
argo,
runtime,
diagnostics,
@@ -1028,7 +1091,7 @@ function repositorySummary(repository: PacRepository): Record<string, unknown> {
}
function consumerCoverage(pac: PacConfig, targetId: string): Record<string, unknown>[] {
return pac.consumers.map((consumer) => {
return pac.consumers.filter((consumer) => consumer.node.toLowerCase() === targetId.toLowerCase()).map((consumer) => {
const repository = resolveRepository(pac, consumer.repositoryRef);
const suffix = consumerSuffix(consumer.id, pac.defaults.consumerId);
return {
@@ -1057,6 +1120,167 @@ function nextCommands(targetId: string, consumerId: string, defaultConsumerId: s
};
}
function compactStatusJson(result: Record<string, unknown>): Record<string, unknown> {
const consumer = record(result.consumer);
return {
ok: result.ok === true,
action: result.action,
mutation: false,
target: result.target,
consumer: {
id: consumer.id,
node: consumer.node,
lane: consumer.lane,
namespace: consumer.namespace,
pipeline: consumer.pipeline,
argoApplication: consumer.argoApplication,
},
summary: compactStatusSummary(record(result.summary)),
next: result.next,
valuesPrinted: false,
};
}
function compactStatusSummary(summary: Record<string, unknown>): Record<string, unknown> {
const latest = record(summary.latestPipelineRun);
const artifact = record(summary.artifact);
const argo = record(summary.argo);
const runtime = record(summary.runtime);
const diagnostics = record(summary.diagnostics);
const taskRuns = arrayRecords(summary.taskRuns);
const longest = longestTaskRun(taskRuns);
return {
ready: summary.ready === true,
crdPresent: summary.crdPresent === true,
controllerReady: summary.controllerReady,
repositoryCondition: summary.repositoryCondition,
webhookCount: summary.webhookCount,
latestPipelineRun: {
name: latest.name,
status: latest.status,
reason: latest.reason,
durationSeconds: latest.durationSeconds,
sourceCommit: latest.sourceCommit,
},
taskRuns: {
count: taskRuns.length,
longest: longest === null ? null : {
name: longest.name,
status: longest.status,
reason: longest.reason,
durationSeconds: longest.durationSeconds,
},
},
pipelineRunGate: summary.pipelineRunGate,
sourceObservation: summary.sourceObservation,
artifact: {
imageStatus: artifact.imageStatus,
envReuse: artifact.envReuse,
envIdentity: artifact.envIdentity,
digest: artifact.digest,
gitopsCommit: artifact.gitopsCommit,
provenanceRelation: artifact.provenanceRelation,
},
argo: {
sync: argo.sync,
health: argo.health,
revision: argo.revision,
repoURL: argo.repoURL,
targetRevision: argo.targetRevision,
revisionRelation: argo.revisionRelation,
},
runtime: {
deployment: runtime.deployment,
readyReplicas: runtime.readyReplicas,
replicas: runtime.replicas,
image: runtime.image,
digest: runtime.digest,
},
diagnostics: {
ok: diagnostics.ok !== false,
code: diagnostics.code,
phase: diagnostics.phase,
hint: diagnostics.hint,
deliveryMode: diagnostics.deliveryMode,
registry: diagnostics.registry,
},
provenance: summary.provenance,
valuesPrinted: false,
};
}
function compactCloseoutJson(result: Record<string, unknown>): Record<string, unknown> {
const consumer = record(result.consumer);
const flush = record(result.gitOpsMirrorFlush);
return {
ok: result.ok === true,
action: result.action,
mutation: result.mutation === true,
target: result.target,
consumer: { id: consumer.id, node: consumer.node, lane: consumer.lane },
sourceCommit: result.sourceCommit,
observedSourceCommit: result.observedSourceCommit,
sourceMatched: result.sourceMatched === true,
ready: result.ready === true,
wait: result.wait,
summary: compactStatusSummary(record(result.summary)),
gitOpsMirrorFlush: {
ok: flush.ok !== false,
enabled: flush.enabled,
required: flush.required,
applicable: flush.applicable,
mode: flush.mode,
executed: flush.executed === true,
reason: flush.reason,
},
blocker: result.blocker,
next: result.next,
valuesPrinted: false,
};
}
function compactHistoryJson(result: Record<string, unknown>): Record<string, unknown> {
return {
ok: result.ok === true,
action: result.action,
mutation: false,
target: result.target,
config: result.config,
consumers: result.consumers,
detailId: result.detailId,
rows: arrayRecords(result.rows).map((row) => {
const taskRuns = record(row.taskRuns);
return {
id: row.id ?? row.pipelineRun,
consumer: row.consumer,
repo: row.repo,
pipeline: row.pipeline,
triggeredAt: row.triggeredAt,
startTime: row.startTime,
completionTime: row.completionTime,
displayTime: row.displayTime,
displayTimeZone: row.displayTimeZone,
durationSeconds: row.durationSeconds,
status: row.status,
reason: row.reason,
commit: row.commit,
branch: row.branch,
envReuse: row.envReuse,
sourceObservation: row.sourceObservation,
taskRuns: {
count: taskRuns.count,
longest: taskRuns.longest,
failed: taskRuns.failed,
logsCommand: taskRuns.logsCommand,
},
};
}),
historyErrors: result.historyErrors,
next: result.next,
valuesPrinted: false,
};
}
function renderPlan(result: Record<string, unknown>): RenderedCliResult {
const target = record(result.target);
const config = record(result.config);
@@ -1116,6 +1340,8 @@ function renderStatus(result: Record<string, unknown>): RenderedCliResult {
const artifact = record(summary.artifact);
const argo = record(summary.argo);
const diagnostics = record(summary.diagnostics);
const sourceObservation = record(summary.sourceObservation);
const deliveryPlan = record(sourceObservation.plan);
const repository = record(summary.repository);
const webhooks = arrayRecords(summary.webhooks);
const lines = [
@@ -1134,6 +1360,18 @@ function renderStatus(result: Record<string, unknown>): RenderedCliResult {
"LATEST PIPELINERUN",
...table(["NAME", "STATUS", "REASON", "DURATION_S", "SOURCE"], [[stringValue(latest.name), stringValue(latest.status), stringValue(latest.reason), stringValue(latest.durationSeconds), short(stringValue(latest.sourceCommit))]]),
"",
"DELIVERY OBSERVATION",
...table(["MODE", "VALID", "SOURCE_MATCHED", "AFFECTED", "ROLLOUT", "BUILD", "REUSED", "REASON"], [[
stringValue(sourceObservation.mode),
boolText(sourceObservation.valid),
boolText(sourceObservation.sourceMatched ?? (stringValue(sourceObservation.sourceCommit) === stringValue(latest.sourceCommit))),
stringValue(deliveryPlan.affectedServiceCount),
stringValue(deliveryPlan.rolloutServiceCount),
stringValue(deliveryPlan.buildServiceCount),
stringValue(deliveryPlan.reusedServiceCount),
stringValue(sourceObservation.reason),
]]),
"",
"TASKRUN DURATIONS",
...(taskRuns.length === 0 ? ["-"] : table(["TASKRUN", "STATUS", "REASON", "FAILED_STEP", "EXIT", "DURATION_S"], taskRuns.map((item) => {
const failedStep = record(item.failedStep);
@@ -1184,6 +1422,7 @@ function renderCloseout(result: Record<string, unknown>): RenderedCliResult {
const argo = record(summary.argo);
const runtime = record(summary.runtime);
const diagnostics = record(summary.diagnostics);
const sourceObservation = record(summary.sourceObservation);
const blocker = record(result.blocker);
const gitOpsMirrorFlush = record(result.gitOpsMirrorFlush);
const lines = [
@@ -1203,6 +1442,7 @@ function renderCloseout(result: Record<string, unknown>): RenderedCliResult {
"",
"SOURCE / PIPELINERUN",
...table(["EXPECTED", "OBSERVED", "PIPELINERUN", "STATUS", "DURATION_S"], [[short(stringValue(result.sourceCommit), 16), short(stringValue(result.observedSourceCommit), 16), stringValue(latest.name), stringValue(latest.reason ?? latest.status), stringValue(latest.durationSeconds)]]),
` delivery: ${stringValue(sourceObservation.mode)} (${stringValue(sourceObservation.reason)})`,
"",
"RUNTIME",
...table(["IMAGE_STATUS", "ENV_REUSE", "DIGEST", "GITOPS", "ARGO", "RELATION", "RUNTIME"], [[
@@ -1289,6 +1529,32 @@ function renderWebhookTest(result: Record<string, unknown>): RenderedCliResult {
return rendered(result, "platform-infra pipelines-as-code webhook-test", lines);
}
function renderDebugStep(result: Record<string, unknown>): RenderedCliResult {
const checks = arrayRecords(result.checks);
const lines = [
"PLATFORM-INFRA PIPELINES-AS-CODE DEBUG STEP",
...table(["TARGET", "CONSUMER", "OK", "CHECKS", "MUTATION"], [[
stringValue(record(result.target).id),
stringValue(result.consumer),
boolText(result.ok),
stringValue(checks.length),
boolText(result.mutation),
]]),
"",
"STATUS EVALUATOR FIXTURES",
...(checks.length === 0 ? ["-"] : table(["CASE", "OK", "EXPECTED", "ACTUAL"], checks.map((item) => [
stringValue(item.id),
boolText(item.ok),
`${stringValue(item.expectedOk)}/${stringValue(item.expectedCode)}`,
`${stringValue(item.actualOk)}/${stringValue(item.actualCode)}`,
]))),
"",
"NEXT",
` status: ${stringValue(record(result.next).status)}`,
];
return rendered(result, "platform-infra pipelines-as-code debug-step", lines);
}
function parseApplyOptions(args: string[]): ApplyOptions {
const commonArgs: string[] = [];
let confirm = false;
@@ -1388,6 +1654,7 @@ function parseCommonOptions(args: string[]): CommonOptions {
let consumerId: string | null = null;
let full = false;
let raw = false;
let json = false;
for (let index = 0; index < args.length; index += 1) {
const arg = args[index];
if (arg === "--target" || arg === "--node" || arg === "--consumer") {
@@ -1402,11 +1669,13 @@ function parseCommonOptions(args: string[]): CommonOptions {
} else if (arg === "--raw") {
raw = true;
full = true;
} else if (arg === "--json") {
json = true;
} else {
throw new Error(`unsupported pipelines-as-code option: ${arg}`);
}
}
return { targetId, consumerId, full, raw };
return { targetId, consumerId, full, raw, json };
}
function closeoutReady(value: Record<string, unknown>, sourceCommit: string | null): boolean {
@@ -1427,6 +1696,7 @@ function closeoutCiReady(value: Record<string, unknown>, sourceCommit: string |
if (pipelineGate.ok !== true) return false;
const observedSourceCommit = observedCloseoutSourceCommit(latest, diagnostics);
if (sourceCommit !== null && observedSourceCommit !== sourceCommit) return false;
if (stringValue(diagnostics.code) === "pac-ready-no-runtime-change") return true;
const artifact = record(summary.artifact);
if (stringValue(artifact.imageStatus) === "disabled") return true;
return stringValue(artifact.gitopsCommit) !== "-";
@@ -1479,10 +1749,23 @@ function sleep(ms: number): Promise<void> {
return new Promise((resolve) => setTimeout(resolve, ms));
}
async function runCloseoutGitOpsMirrorFlush(config: UniDeskConfig, pac: PacConfig, consumer: PacConsumer, latest: Record<string, unknown>, baseReady: boolean, observedSourceCommit: string): Promise<Record<string, unknown>> {
async function runCloseoutGitOpsMirrorFlush(config: UniDeskConfig, pac: PacConfig, consumer: PacConsumer, summary: Record<string, unknown>, latest: Record<string, unknown>, baseReady: boolean, observedSourceCommit: string): Promise<Record<string, unknown>> {
const cfg = pac.closeout.gitOpsMirrorFlush;
const configSource = `${configLabel}.closeout.gitOpsMirrorFlush`;
const required = cfg.enabled && consumer.closeoutGitOpsMirrorFlush;
if (baseReady && stringValue(record(summary.diagnostics).code) === "pac-ready-no-runtime-change") {
return {
ok: true,
enabled: cfg.enabled,
required,
applicable: false,
mode: "retained-no-runtime-change",
executed: false,
reason: "the successful source observation produced no GitOps change, so there is no new revision to flush",
configSource,
valuesPrinted: false,
};
}
if (!required) {
return {
ok: true,
@@ -1766,6 +2049,8 @@ function runtimeText(runtime: Record<string, unknown>): string {
function renderHistoryDetail(row: Record<string, unknown>): string[] {
const envReuse = record(row.envReuse);
const sourceObservation = record(row.sourceObservation);
const deliveryPlan = record(sourceObservation.plan);
const taskRuns = record(row.taskRuns);
const longest = record(taskRuns.longest);
const failed = record(taskRuns.failed);
@@ -1789,6 +2074,14 @@ function renderHistoryDetail(row: Record<string, unknown>): string[] {
["failedStepTermination", stringValue(failedStep.terminationReason ?? failedStep.reason)],
["failure", compactLine(stringValue(failed.message))],
["envReuseSource", stringValue(envReuse.source)],
["deliveryMode", stringValue(sourceObservation.mode)],
["deliveryValid", stringValue(sourceObservation.valid)],
["deliverySourceMatched", stringValue(sourceObservation.sourceMatched)],
["deliveryReason", stringValue(sourceObservation.reason)],
["affectedServices", stringValue(deliveryPlan.affectedServiceCount)],
["rolloutServices", stringValue(deliveryPlan.rolloutServiceCount)],
["buildServices", stringValue(deliveryPlan.buildServiceCount)],
["reusedServices", stringValue(deliveryPlan.reusedServiceCount)],
]),
"",
"LOGS",