fix: patch follower timing atomically

This commit is contained in:
Codex
2026-07-03 17:32:12 +00:00
parent 72f6a7a0e4
commit babcb7b364
3 changed files with 116 additions and 116 deletions
@@ -0,0 +1,108 @@
import { execFileSync } from "node:child_process";
const namespace = process.env.NAMESPACE || "";
const configMap = process.env.CONFIGMAP || "";
const followerId = process.env.FOLLOWER_ID || "";
const specRef = process.env.SPEC_REF || "";
const stateJson = Buffer.from(process.env.STATE_B64 || "", "base64").toString("utf8");
function kubectl(args, input) {
return execFileSync("kubectl", ["-n", namespace, ...args], {
input,
encoding: "utf8",
stdio: ["pipe", "pipe", "pipe"],
});
}
function readConfigMap() {
try {
return JSON.parse(kubectl(["get", "configmap", configMap, "-o", "json"]));
} catch (error) {
const stderr = String(error?.stderr || error?.message || "");
if (/not found/i.test(stderr)) return null;
throw error;
}
}
function ensureConfigMap() {
if (readConfigMap() !== null) return;
const object = {
apiVersion: "v1",
kind: "ConfigMap",
metadata: { name: configMap, namespace },
data: { _createdAt: new Date().toISOString(), _specRef: specRef },
};
kubectl(["apply", "-f", "-"], JSON.stringify(object));
}
function stringOrNull(value) {
return typeof value === "string" && value.length > 0 ? value : null;
}
function numberOrNull(value) {
return typeof value === "number" && Number.isFinite(value) ? value : null;
}
function timestampMs(value) {
const text = stringOrNull(value);
if (text === null) return null;
const parsed = Date.parse(text);
return Number.isFinite(parsed) ? parsed : null;
}
function roundSeconds(value) {
return Math.round(value * 10) / 10;
}
function totalSecondsFromRange(startedAt, finishedAt) {
const startedMs = timestampMs(startedAt);
if (startedMs === null) return null;
const finishedMs = timestampMs(finishedAt) ?? Date.now();
return finishedMs >= startedMs ? roundSeconds((finishedMs - startedMs) / 1000) : null;
}
function terminalPhase(phase) {
return ["Succeeded", "Failed", "Blocked", "Skipped", "Noop"].includes(phase);
}
function preserveExistingTiming(state, existing) {
if (numberOrNull(state?.timings?.totalSeconds) !== null) return state;
const existingTimings = existing?.timings;
const sourceCommit = stringOrNull(existingTimings?.sourceCommit);
if (sourceCommit === null || sourceCommit !== stringOrNull(state?.source?.observedSha)) return state;
const startedAt = stringOrNull(existingTimings?.startedAt);
const existingFinishedAt = stringOrNull(existingTimings?.finishedAt);
const finishedAt = existingFinishedAt ?? (terminalPhase(state.phase) ? new Date().toISOString() : null);
const seconds = totalSecondsFromRange(startedAt, finishedAt) ?? numberOrNull(existingTimings?.totalSeconds);
if (seconds === null) return state;
const budgetSeconds = numberOrNull(state?.timings?.budgetSeconds);
return {
...state,
timings: {
...state.timings,
totalSeconds: seconds,
totalStatus: terminalPhase(state.phase) ? "completed" : String(state.phase || "recorded").toLowerCase(),
totalSource: stringOrNull(existingTimings?.totalSource) ?? "stored-state",
sourceCommit,
startedAt,
finishedAt,
overBudget: budgetSeconds === null ? null : seconds > budgetSeconds,
},
};
}
ensureConfigMap();
const current = readConfigMap();
const currentText = current?.data?.[followerId];
const existing = typeof currentText === "string" && currentText.length > 0 ? JSON.parse(currentText) : null;
const incomingState = JSON.parse(stateJson);
const state = preserveExistingTiming(incomingState, existing);
const patch = {
data: {
[followerId]: JSON.stringify(state),
_updatedAt: new Date().toISOString(),
_specRef: specRef,
},
};
kubectl(["patch", "configmap", configMap, "--type", "merge", "-p", JSON.stringify(patch)]);
process.stdout.write(JSON.stringify({ ok: true, followerId, preservedTiming: state !== incomingState, statusAuthority: "target-node-summary", parsedDownstreamCliOutput: false }));
@@ -1,16 +0,0 @@
import { execFileSync } from "node:child_process";
const namespace = process.env.NAMESPACE || "";
const configMap = process.env.CONFIGMAP || "";
const followerId = process.env.FOLLOWER_ID || "";
try {
const raw = execFileSync("kubectl", ["-n", namespace, "get", "configmap", configMap, "-o", "json"], {
encoding: "utf8",
stdio: ["ignore", "pipe", "pipe"],
});
const data = JSON.parse(raw).data || {};
process.stdout.write(data[followerId] || "{}");
} catch {
process.stdout.write("{}");
}