fix(cicd): use follower budget for AgentRun reuse reads

This commit is contained in:
Codex
2026-07-04 08:20:21 +00:00
parent 3a0407f87c
commit c40e0c4723
2 changed files with 25 additions and 19 deletions
+19 -15
View File
@@ -46,10 +46,10 @@ interface AgentRunIdentityComparison {
previousMissingCount: number | null;
}
export function buildAgentRunReusePlan(follower: FollowerSpec, reuseConfig: RuntimeReuseConfig, sourceCommit: string): AgentRunReusePlan {
export function buildAgentRunReusePlan(follower: FollowerSpec, reuseConfig: RuntimeReuseConfig, sourceCommit: string, timeoutSeconds: number): AgentRunReusePlan {
const stageRef = `${follower.source.snapshotPrefix.replace(/\/+$/u, "")}/${sourceCommit}`;
const service = runtimeReuseService(reuseConfig, ["agentrun-mgr", "manager"]);
const decisions = service === null ? [] : [agentRunReuseDecision(follower.nativeStatus.source.repoPath, stageRef, service)];
const decisions = service === null ? [] : [agentRunReuseDecision(follower.nativeStatus.source.repoPath, stageRef, service, timeoutSeconds)];
const summary = agentRunDecisionSummary(decisions);
return {
ok: reuseConfig.ok && decisions.length > 0,
@@ -111,9 +111,9 @@ export function applyAgentRunReuseConfig(spec: AgentRunLaneSpec, reuseConfig: Ru
};
}
export function reusableAgentRunImageArtifact(spec: AgentRunLaneSpec, repoPath: string, sourceCommit: string): { image: AgentRunArtifactService | null; evidence: Record<string, unknown> } {
export function reusableAgentRunImageArtifact(spec: AgentRunLaneSpec, repoPath: string, sourceCommit: string, timeoutSeconds: number): { image: AgentRunArtifactService | null; evidence: Record<string, unknown> } {
const artifactRef = `refs/heads/${spec.gitops.branch}:${spec.deployment.artifactCatalogPath}`;
const result = runCommand(["git", "--git-dir", repoPath, "show", artifactRef], repoRoot, { timeoutMs: 5_000 });
const result = runCommand(["git", "--git-dir", repoPath, "show", artifactRef], repoRoot, { timeoutMs: budgetMs(timeoutSeconds) });
if (result.exitCode !== 0) {
return {
image: null,
@@ -259,14 +259,14 @@ export function compactAgentRunPayload(value: Record<string, unknown> | null): R
};
}
function agentRunReuseDecision(repoPath: string, stageRef: string, service: NonNullable<ReturnType<typeof runtimeReuseService>>): AgentRunReuseDecision {
const baseRef = parentRef(repoPath, stageRef);
function agentRunReuseDecision(repoPath: string, stageRef: string, service: NonNullable<ReturnType<typeof runtimeReuseService>>, timeoutSeconds: number): AgentRunReuseDecision {
const baseRef = parentRef(repoPath, stageRef, timeoutSeconds);
const runtime = service.runtimeReuse;
const envReuse = service.envReuse;
const codePaths = runtime?.codeIdentityPaths ?? [];
const envPaths = uniqueStrings([...(runtime?.envIdentityPaths ?? []), ...(envReuse?.envIdentityFiles ?? [])]);
const sourceIdentity = identityComparison(repoPath, stageRef, baseRef, codePaths);
const envIdentity = identityComparison(repoPath, stageRef, baseRef, envPaths);
const sourceIdentity = identityComparison(repoPath, stageRef, baseRef, codePaths, timeoutSeconds);
const envIdentity = identityComparison(repoPath, stageRef, baseRef, envPaths, timeoutSeconds);
const runtimeEnabled = runtime?.enabled !== false;
const envEnabled = envReuse?.enabled !== false;
const runtimeHit = runtimeEnabled && sourceIdentity.hit && envIdentity.hit;
@@ -286,9 +286,9 @@ function agentRunReuseDecision(repoPath: string, stageRef: string, service: NonN
};
}
function identityComparison(repoPath: string, currentRef: string, baseRef: string | null, paths: string[]): AgentRunIdentityComparison {
const current = identityDigest(repoPath, currentRef, paths);
const previous = baseRef === null ? null : identityDigest(repoPath, baseRef, paths);
function identityComparison(repoPath: string, currentRef: string, baseRef: string | null, paths: string[], timeoutSeconds: number): AgentRunIdentityComparison {
const current = identityDigest(repoPath, currentRef, paths, timeoutSeconds);
const previous = baseRef === null ? null : identityDigest(repoPath, baseRef, paths, timeoutSeconds);
const configured = paths.length > 0;
const hit = configured && previous !== null && current.sha256 !== null && previous.sha256 !== null && current.sha256 === previous.sha256;
return {
@@ -301,12 +301,12 @@ function identityComparison(repoPath: string, currentRef: string, baseRef: strin
};
}
function identityDigest(repoPath: string, ref: string, paths: string[]): { sha256: string | null; missingCount: number } {
function identityDigest(repoPath: string, ref: string, paths: string[], timeoutSeconds: number): { sha256: string | null; missingCount: number } {
if (paths.length === 0) return { sha256: null, missingCount: 0 };
const hash = createHash("sha256");
let missingCount = 0;
for (const path of paths) {
const result = runCommand(["git", "--git-dir", repoPath, "ls-tree", "-r", "-z", "--full-tree", ref, "--", path], repoRoot, { timeoutMs: 5_000 });
const result = runCommand(["git", "--git-dir", repoPath, "ls-tree", "-r", "-z", "--full-tree", ref, "--", path], repoRoot, { timeoutMs: budgetMs(timeoutSeconds) });
const entries = result.exitCode === 0 ? result.stdout.split("\0").filter(Boolean).sort() : [];
if (entries.length === 0) missingCount += 1;
hash.update(path);
@@ -319,8 +319,8 @@ function identityDigest(repoPath: string, ref: string, paths: string[]): { sha25
return { sha256: hash.digest("hex"), missingCount };
}
function parentRef(repoPath: string, ref: string): string | null {
const result = runCommand(["git", "--git-dir", repoPath, "rev-parse", "--verify", `${ref}^`], repoRoot, { timeoutMs: 5_000 });
function parentRef(repoPath: string, ref: string, timeoutSeconds: number): string | null {
const result = runCommand(["git", "--git-dir", repoPath, "rev-parse", "--verify", `${ref}^`], repoRoot, { timeoutMs: budgetMs(timeoutSeconds) });
const value = result.stdout.trim();
return result.exitCode === 0 && /^[0-9a-f]{40}$/iu.test(value) ? value : null;
}
@@ -420,3 +420,7 @@ function shortText(value: string): string {
const text = value.replace(/\s+/gu, " ").trim();
return text.length <= 300 ? text : text.slice(0, 300);
}
function budgetMs(timeoutSeconds: number): number {
return Math.max(1, timeoutSeconds) * 1000;
}