// SPEC: PJ2026-01060703 CI/CD branch follower draft-2026-07-03-p0-branch-follower. // Responsibility: compact native Kubernetes status summaries for branch-follower closeout. import { resolveAgentRunLaneTarget } from "./agentrun-lanes"; import type { FollowerSpec, NativeStatusSpec, NativeWorkloadSpec } from "./cicd-types"; import { hwlabRuntimeLaneSpecForNode } from "./hwlab-node-lanes"; export function pipelineRunSucceeded(pipelineRun: Record | null): boolean | null { if (pipelineRun === null) return null; const conditions = Array.isArray(asOptionalRecord(pipelineRun.status)?.conditions) ? asOptionalRecord(pipelineRun.status)?.conditions : []; for (const condition of conditions as unknown[]) { const record = asOptionalRecord(condition); if (record?.type !== "Succeeded") continue; if (record.status === "True") return true; if (record.status === "False") return false; return null; } return null; } export function argoApplicationReady(application: Record | null): boolean { if (application === null) return false; const status = asOptionalRecord(application.status); const sync = asOptionalRecord(status?.sync); const health = asOptionalRecord(status?.health); return sync?.status === "Synced" && health?.status === "Healthy"; } export function runtimeWorkloadsReady(runtime: NativeStatusSpec["runtime"], workloads: Record[]): boolean { if (runtime === null) return true; if (workloads.length < runtime.workloads.length) return false; return runtime.workloads.every((spec, index) => workloadReady(spec, workloads[index] ?? null)); } export function runtimeTargetShaFromWorkloads(runtime: NativeStatusSpec["runtime"], workloads: Record[]): string | null { if (runtime === null) return null; const commits: string[] = []; runtime.workloads.forEach((spec, index) => { const commit = workloadSourceCommit(spec, workloads[index] ?? null); if (commit !== null) commits.push(commit); }); const unique = Array.from(new Set(commits)); return unique.length === 1 ? unique[0] ?? null : null; } export function nativePipelineRunSummary(pipelineRun: Record | null): Record | null { if (pipelineRun === null) return null; const metadata = asOptionalRecord(pipelineRun.metadata); const status = asOptionalRecord(pipelineRun.status); const condition = latestCondition(status, "Succeeded"); return { name: stringOrNull(metadata?.name), namespace: stringOrNull(metadata?.namespace), pipelineRefName: stringOrNull(asOptionalRecord(pipelineRun.spec)?.pipelineRef?.name), succeeded: pipelineRunSucceeded(pipelineRun), reason: stringOrNull(condition?.reason), startTime: stringOrNull(status?.startTime), completionTime: stringOrNull(status?.completionTime), durationSeconds: numberOrNull(status?.durationSeconds), }; } export function nativeArgoSummary(application: Record | null): Record | null { if (application === null) return null; const metadata = asOptionalRecord(application.metadata); const status = asOptionalRecord(application.status); const sync = asOptionalRecord(status?.sync); const health = asOptionalRecord(status?.health); const operationState = asOptionalRecord(status?.operationState); return { name: stringOrNull(metadata?.name), namespace: stringOrNull(metadata?.namespace), syncStatus: stringOrNull(sync?.status), healthStatus: stringOrNull(health?.status), healthMessage: stringOrNull(health?.message), revision: stringOrNull(sync?.revision), operationPhase: stringOrNull(operationState?.phase), operationMessage: stringOrNull(operationState?.message), operationStartedAt: stringOrNull(operationState?.startedAt), operationFinishedAt: stringOrNull(operationState?.finishedAt), operationDurationSeconds: numberOrNull(operationState?.durationSeconds), conditions: Array.isArray(status?.conditions) ? status.conditions.slice(0, 5) : [], nonReadyResources: Array.isArray(status?.nonReadyResources) ? status.nonReadyResources.slice(0, 5) : [], ready: argoApplicationReady(application), }; } export function nativeRuntimeSummary(runtime: NativeStatusSpec["runtime"], workloads: Record[], expectedSha: string | null): Record | null { if (runtime === null) return null; const targetSha = runtimeTargetShaFromWorkloads(runtime, workloads); return { namespace: runtime.namespace, ready: runtimeWorkloadsReady(runtime, workloads), targetSha, expectedSha, aligned: expectedSha !== null && targetSha !== null ? targetSha === expectedSha : null, workloads: runtime.workloads.map((spec, index) => { const workload = workloads[index] ?? null; const sourceCommit = workloadSourceCommit(spec, workload); return { kind: spec.kind, name: spec.name, ready: workloadReady(spec, workload), sourceCommit, aligned: expectedSha !== null && sourceCommit !== null ? sourceCommit === expectedSha : null, }; }), }; } export function nativeGitMirrorRequired(follower: FollowerSpec): boolean { return nativeGitMirrorGitopsBranch(follower) !== null && (follower.closeoutChecks.includes("gitMirrorPostFlush") || follower.closeoutChecks.includes("gitops")); } export function nativeGitMirrorReady(gitMirror: Record | null): boolean { if (gitMirror === null) return false; if (gitMirror.ok === false) return false; if (gitMirror.sourceSnapshotReady === false) return false; if (gitMirror.pendingFlush === true) return false; if (gitMirror.githubInSync === false) return false; return true; } export function nativeGitMirrorSummary(gitMirror: Record | null): Record | null { if (gitMirror === null) return null; return { ok: gitMirror.ok === true, repository: stringOrNull(gitMirror.repository), sourceBranch: stringOrNull(gitMirror.sourceBranch), gitopsBranch: stringOrNull(gitMirror.gitopsBranch), localSource: stringOrNull(gitMirror.localSource), githubSource: stringOrNull(gitMirror.githubSource), sourceStageRef: stringOrNull(gitMirror.sourceStageRef), sourceSnapshotReady: gitMirror.sourceSnapshotReady === true, localGitops: stringOrNull(gitMirror.localGitops), githubGitops: stringOrNull(gitMirror.githubGitops), pendingFlush: gitMirror.pendingFlush === true, githubInSync: gitMirror.githubInSync === true, statusAuthority: stringOrNull(gitMirror.statusAuthority) ?? "k8s-git-mirror-cache", }; } function nativeGitMirrorGitopsBranch(follower: FollowerSpec): string | null { if (follower.adapter === "hwlab-node-runtime") { return hwlabRuntimeLaneSpecForNode(follower.target.lane, follower.target.node).gitopsBranch; } if (follower.adapter === "agentrun-yaml-lane") { return resolveAgentRunLaneTarget({ node: follower.target.node, lane: follower.target.lane }).spec.gitops.branch; } return null; } function workloadReady(spec: NativeWorkloadSpec, workload: Record | null): boolean { if (workload === null) return false; const status = asOptionalRecord(workload.status); if (spec.kind === "Deployment") { const desired = numberOrNull(asOptionalRecord(workload.spec)?.replicas) ?? 1; const available = numberOrNull(status?.availableReplicas) ?? 0; const updated = numberOrNull(status?.updatedReplicas) ?? 0; return available >= desired && updated >= desired; } const desired = numberOrNull(asOptionalRecord(workload.spec)?.replicas) ?? 1; const ready = numberOrNull(status?.readyReplicas) ?? 0; return ready >= desired; } function workloadSourceCommit(spec: NativeWorkloadSpec, workload: Record | null): string | null { if (workload === null) return null; const metadata = asOptionalRecord(workload.metadata); const labels = asOptionalRecord(metadata?.labels); const annotations = asOptionalRecord(metadata?.annotations); const template = asOptionalRecord(asOptionalRecord(workload.spec)?.template); const podMetadata = asOptionalRecord(template?.metadata); const podLabels = asOptionalRecord(podMetadata?.labels); const podAnnotations = asOptionalRecord(podMetadata?.annotations); for (const key of spec.sourceCommit.labels) { const value = shaOrNull(labels?.[key]); if (value !== null) return value; } for (const key of spec.sourceCommit.annotations) { const value = shaOrNull(annotations?.[key]); if (value !== null) return value; } for (const key of spec.sourceCommit.podLabels) { const value = shaOrNull(podLabels?.[key]); if (value !== null) return value; } for (const key of spec.sourceCommit.podAnnotations) { const value = shaOrNull(podAnnotations?.[key]); if (value !== null) return value; } const containers = Array.isArray(asOptionalRecord(template?.spec)?.containers) ? asOptionalRecord(template?.spec)?.containers : []; for (const envName of spec.sourceCommit.env) { for (const container of containers as unknown[]) { const env = Array.isArray(asOptionalRecord(container)?.env) ? asOptionalRecord(container)?.env : []; for (const entry of env as unknown[]) { const record = asOptionalRecord(entry); if (record?.name !== envName) continue; const value = shaOrNull(record.value); if (value !== null) return value; } } } return null; } function latestCondition(status: Record | null, type: string): Record | null { const conditions = Array.isArray(status?.conditions) ? status.conditions : []; for (const condition of conditions as unknown[]) { const record = asOptionalRecord(condition); if (record?.type === type) return record; } return null; } function shaOrNull(value: unknown): string | null { return typeof value === "string" && /^[0-9a-f]{40}$/iu.test(value) ? value : null; } function asOptionalRecord(value: unknown): Record | null { if (typeof value !== "object" || value === null || Array.isArray(value)) return null; return value as Record; } function stringOrNull(value: unknown): string | null { return typeof value === "string" && value.length > 0 ? value : null; } function numberOrNull(value: unknown): number | null { return typeof value === "number" && Number.isFinite(value) ? value : null; }