fix: expose AgentRun branch-follower CI reuse evidence

This commit is contained in:
Codex
2026-07-04 08:08:41 +00:00
parent 111aa222bb
commit 3a0407f87c
5 changed files with 562 additions and 44 deletions
+92 -2
View File
@@ -18,6 +18,8 @@ const pipelineRunPrefix = process.env.PIPELINE_RUN_PREFIX || "";
const argoNamespace = process.env.ARGO_NAMESPACE || "";
const argoApplication = process.env.ARGO_APPLICATION || "";
const runtimeNamespace = process.env.RUNTIME_NAMESPACE || "";
const stateNamespace = process.env.STATE_NAMESPACE || "";
const stateConfigMap = process.env.STATE_CONFIGMAP || "";
const workloads = parseWorkloads(process.env.WORKLOADS_B64 || "");
const healthUrl = process.env.HEALTH_URL || "";
const slowTaskSeconds = requiredPositiveIntEnv("SLOW_TASK_SECONDS");
@@ -91,8 +93,9 @@ async function ciTaskRunEvidence(commit) {
const taskRuns = await getJson(`/apis/tekton.dev/v1/namespaces/${encodeURIComponent(tektonNamespace)}/taskruns?labelSelector=${encodeURIComponent(`tekton.dev/pipelineRun=${pipelineRunName}`)}`, false);
const taskSummary = taskRunsSummary(taskRuns);
const planArtifacts = planArtifactsEvidence(pipelineRunName);
const storedCiConsumption = await storedAgentRunCiConsumption(commit);
const buildTaskRunServices = buildTaskServices(taskRuns);
const ciConsumption = ciConsumptionSummary(reusePlan.decisions || [], planArtifacts, buildTaskRunServices);
const ciConsumption = ciConsumptionSummary(reusePlan.decisions || [], planArtifacts, buildTaskRunServices, storedCiConsumption);
return {
ok: prStatus.succeeded === true && taskSummary.failedCount === 0 && taskSummary.activeCount === 0 && ciConsumption.ok === true,
reusePlan: {
@@ -100,6 +103,7 @@ async function ciTaskRunEvidence(commit) {
decisionSummary: compactDecisionSummary(reusePlan.decisionSummary),
},
planArtifacts,
storedCiConsumption,
ciConsumption,
pipelineRun: prStatus,
pipeline: {
@@ -250,9 +254,55 @@ function planArtifactsEvidence(pipelineRunName) {
}
}
function ciConsumptionSummary(decisions, planArtifacts, buildTaskRunServices) {
function ciConsumptionSummary(decisions, planArtifacts, buildTaskRunServices, storedCiConsumption) {
const skipExpected = decisions.filter((item) => item.skipImageBuild === true).map((item) => item.serviceId).sort();
const buildExpected = decisions.filter((item) => item.skipImageBuild !== true).map((item) => item.serviceId).sort();
if (/agentrun/u.test(follower) && storedCiConsumption?.present === true && storedCiConsumption.ok !== true) {
return {
ok: false,
reason: storedCiConsumption.reason || "ci-consumption-evidence-missing",
source: "branch-follower-compact-state",
expected: {
skipImageBuildCount: skipExpected.length,
buildImageCount: buildExpected.length,
},
observed: {
buildServicesCount: 0,
buildTaskRunServices,
reusedServicesCount: 0,
skippedOrReusedServicesCount: 0,
buildSkippedCount: null,
},
mismatches: [],
};
}
if (storedCiConsumption?.ok === true) {
const observed = storedCiConsumption.observed || {};
const unexpectedStoredBuild = skipExpected.filter((serviceId) => observed.imageBuildDecision === "buildImage" && serviceId === "agentrun-mgr");
const missingStoredBuild = buildExpected.filter((serviceId) => observed.imageBuildDecision !== "buildImage" && serviceId === "agentrun-mgr");
const storedOk = unexpectedStoredBuild.length === 0 && missingStoredBuild.length === 0;
return {
ok: storedOk,
reason: storedOk ? "ci-consumed-reuse-plan" : "ci-consumption-mismatch",
source: "branch-follower-compact-state",
expected: {
skipImageBuildCount: skipExpected.length,
buildImageCount: buildExpected.length,
},
observed: {
buildServicesCount: numberOrNull(observed.buildServicesCount),
buildTaskRunServices,
reusedServicesCount: numberOrNull(observed.reusedServicesCount),
skippedOrReusedServicesCount: numberOrNull(observed.reusedServicesCount),
buildSkippedCount: numberOrNull(observed.buildSkippedCount),
imageBuildDecision: str(observed.imageBuildDecision),
imageBuildStatus: str(observed.imageBuildStatus),
imageBuildCreated: observed.imageBuildCreated === true,
imageBuildReused: observed.imageBuildReused === true,
},
mismatches: compactCiMismatches(unexpectedStoredBuild, missingStoredBuild),
};
}
const buildObserved = uniqueStrings([...strings(planArtifacts?.buildServices), ...buildTaskRunServices]).sort();
const reusedObserved = strings(planArtifacts?.reusedServices).sort();
const skippedObserved = uniqueStrings(reusedObserved).sort();
@@ -278,6 +328,33 @@ function ciConsumptionSummary(decisions, planArtifacts, buildTaskRunServices) {
};
}
async function storedAgentRunCiConsumption(commit) {
if (!stateNamespace || !stateConfigMap || !follower || !commit) return null;
const cm = await getJson(`/api/v1/namespaces/${encodeURIComponent(stateNamespace)}/configmaps/${encodeURIComponent(stateConfigMap)}`, false);
const raw = cm?.data?.[follower];
if (typeof raw !== "string" || raw.length === 0) return { ok: false, present: false, reason: "compact-state-missing" };
const state = parseJson(raw);
if (state === null) return { ok: false, present: true, reason: "state-json-parse-failed" };
const command = state?.command || {};
const payload = command.payload || {};
const agentrun = payload.agentrun || {};
const sourceCommit = str(agentrun.sourceCommit) || str(command.sourceCommit);
const ci = agentrun.ciConsumption || null;
if (sourceCommit === null && (!ci || typeof ci !== "object")) return { ok: false, present: true, reason: "ci-consumption-evidence-missing", sourceCommit: null };
if (sourceCommit !== commit) return { ok: false, present: true, reason: "state-source-commit-mismatch", sourceCommit };
if (!ci || typeof ci !== "object") return { ok: false, present: true, reason: "ci-consumption-evidence-missing", sourceCommit };
return {
ok: ci.ok === true,
present: true,
sourceCommit,
source: str(ci.source),
expected: ci.expected || null,
observed: ci.observed || null,
mismatches: Array.isArray(ci.mismatches) ? ci.mismatches.slice(0, 6) : [],
valuesRedacted: true,
};
}
function compactCiMismatches(unexpectedBuild, missingBuild) {
return [
...(unexpectedBuild.length > 0 ? [{ reason: "expected-skipImageBuild-but-ci-buildServices-includes-service", serviceIds: unexpectedBuild }] : []),
@@ -692,6 +769,19 @@ function str(value) {
return typeof value === "string" && value.length > 0 ? value : null;
}
function parseJson(value) {
try {
const parsed = JSON.parse(value);
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
} catch {
return null;
}
}
function numberOrNull(value) {
return typeof value === "number" && Number.isFinite(value) ? value : null;
}
function strings(value) {
return Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
}