fix: reuse env catalog across hash metadata drift

This commit is contained in:
lyon
2026-06-26 08:03:57 +08:00
committed by root
parent 91c8d6009c
commit ddf52e02d2
2 changed files with 114 additions and 7 deletions
+54 -5
View File
@@ -130,24 +130,60 @@ export async function createCiPlan(options = {}) {
const codeInputHash = envReuse ? await hashGitPaths(repoRoot, targetRef, codeInputPaths, {
skipPath: (filePath) => isTestOnlyPath(filePath) || isDocsOnlyPath(filePath)
}) : null;
const environmentImage = envReuse ? envReuseImageRef(registryPrefix, model.serviceId, environmentInputHash) : null;
const currentEnvironmentImage = envReuse ? envReuseImageRef(registryPrefix, model.serviceId, environmentInputHash) : null;
const catalogEnvironmentImage = envReuse ? environmentImageFromCatalog(model.serviceId, catalogRecord) : null;
const catalogEnvironmentDigest = envReuse ? environmentDigestFromCatalog(catalogRecord) : null;
const catalogEnvironmentSourceCommitId = envReuse ? text(catalogRecord?.sourceCommitId) : "";
const catalogEnvironmentInputHashAtSource = envReuse && catalogEnvironmentSourceCommitId
? await hashEnvReuseInputsIfCommitExists(repoRoot, catalogEnvironmentSourceCommitId, envInputPaths, serviceEnvReuseRecipe, {
skipPath: (filePath) => isTestOnlyPath(filePath) || isDocsOnlyPath(filePath),
semanticPackageJson: true
})
: null;
const environmentSourceUnchanged = envReuse && Boolean(catalogEnvironmentInputHashAtSource) && catalogEnvironmentInputHashAtSource === environmentInputHash;
let environmentImage = currentEnvironmentImage;
let environmentDigest = envReuse ? environmentDigestFromCatalogForHash(catalogRecord, environmentInputHash) : null;
const environmentInputChanged = envReuse ? catalogRecord?.environmentInputHash !== environmentInputHash : null;
const reuseRegistry = envReuse && reuseRegistryProbe && environmentImage
const currentReuseRegistry = envReuse && reuseRegistryProbe && currentEnvironmentImage
? await reuseRegistryProbe({
serviceId: model.serviceId,
image: environmentImage,
image: currentEnvironmentImage,
digest: environmentDigest,
registryPrefix,
timeoutMs: Number.isFinite(registryProbeTimeoutMs) ? registryProbeTimeoutMs : 3000
})
: null;
let reuseRegistry = currentReuseRegistry;
let environmentMetadataHashDrift = false;
const catalogEnvironmentReusable = envReuse
&& environmentInputChanged === true
&& environmentSourceUnchanged
&& Boolean(catalogEnvironmentImage)
&& /^sha256:[a-f0-9]{64}$/u.test(catalogEnvironmentDigest ?? "");
if (catalogEnvironmentReusable && currentReuseRegistry?.status !== "present") {
const catalogReuseRegistry = reuseRegistryProbe && catalogEnvironmentImage
? await reuseRegistryProbe({
serviceId: model.serviceId,
image: catalogEnvironmentImage,
digest: catalogEnvironmentDigest,
registryPrefix,
timeoutMs: Number.isFinite(registryProbeTimeoutMs) ? registryProbeTimeoutMs : 3000
})
: null;
if (!reuseRegistryProbe || catalogReuseRegistry?.status === "present") {
environmentImage = catalogEnvironmentImage;
environmentDigest = catalogReuseRegistry?.digest ?? catalogEnvironmentDigest;
reuseRegistry = catalogReuseRegistry ? { ...catalogReuseRegistry, reuseSource: "catalog-metadata-hash-drift" } : null;
environmentMetadataHashDrift = true;
}
}
if (reuseRegistry?.status === "present" && /^sha256:[a-f0-9]{64}$/u.test(reuseRegistry.digest ?? "")) {
environmentDigest = reuseRegistry.digest;
}
const reuseRegistryUnavailable = Boolean(reuseRegistry && reuseRegistry.status !== "present");
const digestReady = /^sha256:[a-f0-9]{64}$/u.test(environmentDigest ?? "");
const environmentReady = envReuse && Boolean(environmentImage) && (reuseRegistryProbe ? reuseRegistry?.status === "present" && digestReady : !environmentInputChanged && digestReady);
const effectiveEnvironmentInputChanged = environmentInputChanged === true && !environmentMetadataHashDrift;
const environmentReady = envReuse && Boolean(environmentImage) && (reuseRegistryProbe ? reuseRegistry?.status === "present" && digestReady : !effectiveEnvironmentInputChanged && digestReady);
const envChanged = envReuse ? Boolean(relevantEnvMatches.length > 0 || !environmentReady || reuseRegistryUnavailable) : null;
const codeChanged = envReuse ? relevantCodeMatches.length > 0 || catalogRecord?.codeInputHash !== codeInputHash : null;
const componentInputPaths = uniqueSorted([
@@ -230,6 +266,10 @@ export async function createCiPlan(options = {}) {
envChanged,
runtimeConfigChanged,
environmentInputChanged,
effectiveEnvironmentInputChanged,
environmentMetadataHashDrift,
catalogEnvironmentInputHashAtSource,
environmentSourceUnchanged,
codeChanged,
reuseRegistry,
environmentInputHash,
@@ -242,7 +282,7 @@ export async function createCiPlan(options = {}) {
envChangedPaths: relevantEnvMatches,
codeChangedPaths: relevantCodeMatches,
reason: affected
? reasonForService({ directMatches, sharedMatches, runtimeDepMatches, buildSystemMatches, catalogReuse, envReuse, envChanged, runtimeConfigChanged, environmentInputChanged, codeChanged, reuseRegistry })
? reasonForService({ directMatches, sharedMatches, runtimeDepMatches, buildSystemMatches, catalogReuse, envReuse, envChanged, runtimeConfigChanged, environmentInputChanged: effectiveEnvironmentInputChanged, codeChanged, reuseRegistry })
: reasonForUnchanged(globalChange),
reuse: catalogReuse
});
@@ -721,6 +761,15 @@ async function hashEnvReuseInputs(repoRoot, targetRef, paths, recipe, options =
});
}
async function hashEnvReuseInputsIfCommitExists(repoRoot, targetRef, paths, recipe, options = {}) {
try {
await gitValue(repoRoot, ["rev-parse", "--verify", `${targetRef}^{commit}`]);
return await hashEnvReuseInputs(repoRoot, targetRef, paths, recipe, options);
} catch {
return null;
}
}
function envReuseIdentityRecipe(recipe) {
const identity = JSON.parse(JSON.stringify(recipe ?? {}));
if (identity.downloadStack && typeof identity.downloadStack === "object") {