feat(cicd): add branch follower reconcile timeline

This commit is contained in:
Codex
2026-07-04 01:37:45 +00:00
parent 4a16a1f5a0
commit 28f0a0c4b9
3 changed files with 228 additions and 2 deletions
+34
View File
@@ -110,6 +110,7 @@ function renderStatusHuman(payload: Record<string, unknown>, _options: ParsedOpt
const next = asOptionalRecord(payload.next);
const errors = Array.isArray(payload.errors) ? payload.errors : [];
const timingRows = followers.flatMap(timingRowsForFollower).slice(0, 48);
const reconcileRows = followers.flatMap(reconcileRowsForFollower).slice(0, 48);
return [
`CI/CD BRANCH-FOLLOWER STATUS (${payload.ok === false ? "degraded" : "ok"})`,
"",
@@ -120,6 +121,7 @@ function renderStatusHuman(payload: Record<string, unknown>, _options: ParsedOpt
"",
table(["FOLLOWER", "PHASE", "ADAPTER", "OBSERVED", "TARGET", "TRIGGERED", "SUCCEEDED", "IN_FLIGHT", "BUDGET", "MESSAGE"], rows),
timingRows.length === 0 ? "" : `\nSTAGE TIMINGS\n${table(["FOLLOWER", "STAGE", "STATUS", "SECONDS", "BUDGET", "OBJECT"], timingRows)}`,
reconcileRows.length === 0 ? "" : `\nRECONCILE TIMELINE\n${table(["FOLLOWER", "STEP", "STATUS", "SECONDS", "STARTED", "OBJECT"], reconcileRows)}`,
errors.length === 0 ? "" : `\nERRORS\n${errors.map((item) => `- ${item}`).join("\n")}`,
"",
"NEXT",
@@ -147,6 +149,7 @@ function renderRunOnceHuman(payload: Record<string, unknown>): string {
});
const next = asOptionalRecord(payload.next);
const timingRows = followers.flatMap(timingRowsForFollower).slice(0, 48);
const reconcileRows = reconcileRowsFromRunOnce(payload, followers).slice(0, 48);
const writeRows = stateWrites.map((item) => [
item.follower,
item.ok === true ? "ok" : "failed",
@@ -161,6 +164,7 @@ function renderRunOnceHuman(payload: Record<string, unknown>): string {
"",
table(["FOLLOWER", "PHASE", "OBSERVED", "TARGET", "TRIGGERED", "IN_FLIGHT", "DECISION"], rows),
timingRows.length === 0 ? "" : `\nSTAGE TIMINGS\n${table(["FOLLOWER", "STAGE", "STATUS", "SECONDS", "BUDGET", "OBJECT"], timingRows)}`,
reconcileRows.length === 0 ? "" : `\nRECONCILE TIMELINE\n${table(["FOLLOWER", "STEP", "STATUS", "SECONDS", "STARTED", "OBJECT"], reconcileRows)}`,
writeRows.length === 0 ? "" : `\nSTATE WRITES\n${table(["FOLLOWER", "STATUS", "BEFORE_RV", "AFTER_RV", "PRESERVED", "EXIT", "MESSAGE"], writeRows)}`,
"",
"NEXT",
@@ -223,6 +227,36 @@ function timingRowsForFollower(item: Record<string, unknown>): unknown[][] {
return rows;
}
function reconcileRowsFromRunOnce(payload: Record<string, unknown>, followers: Record<string, unknown>[]): unknown[][] {
const timeline = asOptionalRecord(payload.reconcileTimeline);
if (timeline !== null) return reconcileRowsForTimeline(timeline, null);
return followers.flatMap(reconcileRowsForFollower);
}
function reconcileRowsForFollower(item: Record<string, unknown>): unknown[][] {
return reconcileRowsForTimeline(asOptionalRecord(item.reconcileTimeline), stringOrNull(item.id));
}
function reconcileRowsForTimeline(timeline: Record<string, unknown> | null, fallbackFollower: string | null): unknown[][] {
if (timeline === null) return [];
const steps = arrayRecords(timeline.steps);
if (steps.length === 0 && stringOrNull(timeline.missingReason) !== null) {
return [[fallbackFollower ?? "-", "controller-loop", "-", "-", "-", stringOrNull(timeline.missingReason)]];
}
return steps.map((step) => [
stringOrNull(step.follower) ?? fallbackFollower ?? "-",
step.step ?? "-",
step.status ?? "-",
formatSeconds(secondsFromMs(numberOrNull(step.elapsedMs))),
stringOrNull(step.startedAt) ?? "-",
stringOrNull(step.object) ?? stringOrNull(step.pipelineRun) ?? shortSha(stringOrNull(step.observedSha)),
]);
}
function secondsFromMs(value: number | null): number | null {
return value === null ? null : Math.round(value / 100) / 10;
}
function formatSeconds(value: number | null): string {
return value === null ? "-" : `${value}s`;
}