diff --git a/scripts/src/cicd-types.ts b/scripts/src/cicd-types.ts index 32132f64..7e1a10bf 100644 --- a/scripts/src/cicd-types.ts +++ b/scripts/src/cicd-types.ts @@ -222,6 +222,7 @@ export interface NativeCloseoutWaitResult { polls: number; elapsedMs: number; refresh: Record | null; + gitMirrorFlush: Record | null; summary: Record | null; statusAuthority: "k8s-native"; parsedDownstreamCliOutput: false; diff --git a/scripts/src/cicd.ts b/scripts/src/cicd.ts index 678e0a33..379762ba 100644 --- a/scripts/src/cicd.ts +++ b/scripts/src/cicd.ts @@ -1279,10 +1279,36 @@ async function waitNativeSentinelCloseout( const deadline = startedAt + Math.max(1, timeoutSeconds) * 1000; let polls = 0; let latest: AdapterSummary | null = null; + let gitMirrorFlush: Record | null = null; while (Date.now() <= deadline) { polls += 1; const remainingSeconds = Math.max(1, Math.ceil((deadline - Date.now()) / 1000)); latest = await readAdapterStatus(registry, follower, { ...options, timeoutSeconds: Math.min(10, remainingSeconds) }); + const latestPayload = asOptionalRecord(latest.payload); + const latestGitMirror = asOptionalRecord(latestPayload?.gitMirror); + if (latest.observedSha === observedSha && gitMirrorFlush === null && shouldFlushNativeGitMirrorDuringCloseout(follower, latestGitMirror)) { + const flush = runNativeGitMirrorStage(follower, observedSha, "flush", Math.max(1, Math.min(remainingSeconds, follower.budgets.sourceSyncSeconds))); + gitMirrorFlush = flush === null ? null : { + jobName: flush.jobName, + namespace: flush.namespace, + result: compactNativeK8sJobResult(flush.result), + }; + if (flush !== null && !flush.result.ok) { + return { + ok: false, + completed: false, + timedOut: flush.result.timedOut, + polls, + elapsedMs: Date.now() - startedAt, + refresh: refreshResult === null ? null : commandCompact(refreshResult, options), + gitMirrorFlush, + summary: nativeCloseoutSummary(latest), + statusAuthority: "k8s-native", + parsedDownstreamCliOutput: false, + }; + } + continue; + } if (latest.observedSha === observedSha && latest.aligned === true) { return { ok: true, @@ -1291,6 +1317,7 @@ async function waitNativeSentinelCloseout( polls, elapsedMs: Date.now() - startedAt, refresh: refreshResult === null ? null : commandCompact(refreshResult, options), + gitMirrorFlush, summary: nativeCloseoutSummary(latest), statusAuthority: "k8s-native", parsedDownstreamCliOutput: false, @@ -1306,12 +1333,38 @@ async function waitNativeSentinelCloseout( polls, elapsedMs: Date.now() - startedAt, refresh: refreshResult === null ? null : commandCompact(refreshResult, options), + gitMirrorFlush, summary: latest === null ? null : nativeCloseoutSummary(latest), statusAuthority: "k8s-native", parsedDownstreamCliOutput: false, }; } +function shouldFlushNativeGitMirrorDuringCloseout(follower: FollowerSpec, gitMirror: Record | null): boolean { + if (!nativeGitMirrorRequired(follower) || gitMirror === null) return false; + if (gitMirror.sourceSnapshotReady === false) return false; + return gitMirror.pendingFlush === true || gitMirror.githubInSync === false; +} + +function compactNativeK8sJobResult(result: NativeK8sJobResult): Record { + return { + ok: result.ok, + completed: result.completed, + failed: result.failed, + timedOut: result.timedOut, + created: result.created, + reused: result.reused, + jobName: result.jobName, + namespace: result.namespace, + polls: result.polls, + elapsedMs: result.elapsedMs, + conditionReason: result.conditionReason, + conditionMessage: result.conditionMessage, + statusAuthority: result.statusAuthority, + parsedDownstreamCliOutput: false, + }; +} + async function waitNativeFollowerCloseout( registry: BranchFollowerRegistry, follower: FollowerSpec, @@ -2016,6 +2069,7 @@ function compactStateCommand(command: Record | undefined): Reco timedOut: closeout.timedOut === true, polls: numberOrNull(closeout.polls), elapsedMs: numberOrNull(closeout.elapsedMs), + gitMirrorFlush: compactCloseoutGitMirrorFlush(asOptionalRecord(closeout.gitMirrorFlush)), summary: closeoutSummary, statusAuthority: stringOrNull(closeout.statusAuthority), parsedDownstreamCliOutput: false, @@ -2028,6 +2082,32 @@ function compactStateCommand(command: Record | undefined): Reco }; } +function compactCloseoutGitMirrorFlush(value: Record | null): Record | null { + if (value === null) return null; + const result = asOptionalRecord(value.result); + return { + jobName: stringOrNull(value.jobName), + namespace: stringOrNull(value.namespace), + result: result === null + ? null + : { + ok: result.ok === true, + completed: result.completed === true, + failed: result.failed === true, + timedOut: result.timedOut === true, + created: result.created === true, + reused: result.reused === true, + jobName: stringOrNull(result.jobName), + namespace: stringOrNull(result.namespace), + elapsedMs: numberOrNull(result.elapsedMs), + conditionReason: stringOrNull(result.conditionReason), + conditionMessage: stringOrNull(result.conditionMessage), + statusAuthority: stringOrNull(result.statusAuthority), + parsedDownstreamCliOutput: false, + }, + }; +} + function compactStateWarnings(warnings: string[]): string[] { return warnings.slice(0, 4).map((item) => redactText(tailText(item, 400))); } @@ -2312,6 +2392,9 @@ function stageTimingsFromCommand(command: Record | undefined): } const closeout = asOptionalRecord(command.closeout); if (closeout !== null) { + const gitMirrorFlush = asOptionalRecord(closeout.gitMirrorFlush); + const gitMirrorFlushStage = k8sJobTiming("git-mirror-flush", asOptionalRecord(gitMirrorFlush?.result), stringOrNull(gitMirrorFlush?.jobName)); + if (gitMirrorFlushStage !== null) stages.push(gitMirrorFlushStage); const status = closeout.completed === true ? "completed" : closeout.timedOut === true ? "timed-out" : "pending"; stages.push(stageTiming("closeout", status, secondsFromMs(numberOrNull(closeout.elapsedMs)), null, "k8s-native-closeout", stringOrNull(command.pipelineRun))); }