fix: 修复 PaC reused delivery 终态证据

从目标 TaskRun condition/result 与结构化合同日志恢复 artifact catalog、digest 和 GitOps 证据,并为真实 PipelineRun 增加有界 debug-step。严格保留缺 artifact、缺 GitOps、runtime mismatch 与冲突证据的 fail-closed closeout。
This commit is contained in:
Codex
2026-07-11 01:29:22 +02:00
parent ea8e3ed204
commit 1eafb1f89e
5 changed files with 665 additions and 139 deletions
+317 -21
View File
@@ -23,12 +23,85 @@ function exactSkipMarker(records, event) {
&& item.reason === "no-build-no-rollout-plan") || null;
}
const pacContractTasks = new Set(["plan-artifacts", "collect-artifacts", "gitops-promote"]);
function taskTerminalRecord(value) {
const taskRun = record(value);
const metadata = record(taskRun.metadata);
const labels = record(metadata.labels);
const pipelineTask = stringOrNull(labels["tekton.dev/pipelineTask"]);
if (pipelineTask === null || !pacContractTasks.has(pipelineTask)) return null;
const status = record(taskRun.status);
const condition = Array.isArray(status.conditions)
? status.conditions.map(record).find((item) => item.type === "Succeeded") || {}
: {};
const conditionStatus = stringOrNull(condition.status);
const normalizedStatus = conditionStatus === "True"
? "succeeded"
: conditionStatus === "False"
? "failed"
: "running";
return {
event: "pac-task-terminal",
pipelineTask,
taskRun: stringOrNull(metadata.name),
status: normalizedStatus,
reason: stringOrNull(condition.reason),
results: Array.isArray(status.results)
? status.results.map(record).map((item) => stringOrNull(item.name)).filter(Boolean)
: [],
};
}
function parsePacLogRecords(value) {
const records = [];
const lines = String(value || "").split(/\r?\n/u);
let pending = "";
for (const line of lines) {
const trimmed = line.trim();
if (pending.length === 0) {
if (!trimmed.startsWith("{")) continue;
pending = trimmed;
} else {
pending += `\n${line}`;
}
try {
records.push(JSON.parse(pending));
pending = "";
} catch {
if (pending.length > 2 * 1024 * 1024) pending = "";
}
}
return records;
}
function exactTaskTerminal(records, pipelineTask) {
return [...records].reverse().find((item) => item.event === "pac-task-terminal"
&& item.pipelineTask === pipelineTask) || null;
}
function terminalSource(task, marker, markerStatus = "skipped", markerSource = "structured-log-marker") {
return {
present: task !== null || marker !== null,
status: task?.status || (marker === null ? "missing" : markerStatus),
source: task !== null ? "taskrun-condition" : marker === null ? null : markerSource,
taskRun: task?.taskRun || null,
reason: task?.reason || marker?.reason || null,
results: Array.isArray(task?.results) ? task.results : [],
markerPresent: marker !== null,
markerSource: marker === null ? null : markerSource,
};
}
function extractPacSourceObservation(recordsInput) {
const records = Array.isArray(recordsInput) ? recordsInput.map(record) : [];
const plan = [...records].reverse().find((item) => item.event === "g14-ci-plan") || null;
const collectMarker = exactSkipMarker(records, "collect-artifacts");
const promoteMarker = exactSkipMarker(records, "gitops-promote");
if (plan === null && collectMarker === null && promoteMarker === null) return null;
const planTask = exactTaskTerminal(records, "plan-artifacts");
const collectTask = exactTaskTerminal(records, "collect-artifacts");
const promoteTask = exactTaskTerminal(records, "gitops-promote");
if (plan === null && collectMarker === null && promoteMarker === null && planTask === null && collectTask === null && promoteTask === null) return null;
const affected = stringArray(plan?.affectedServices);
const rollout = stringArray(plan?.rolloutServices);
@@ -44,6 +117,12 @@ function extractPacSourceObservation(recordsInput) {
const noRuntimeChangeMarkers = collectMarker !== null && promoteMarker !== null;
const skipMarkerSeen = collectMarker !== null || promoteMarker !== null;
const skipMarkerConflict = skipMarkerSeen && !noRuntimeChangePlan;
const deliveryTerminalReady = planTask?.status === "succeeded"
&& collectTask?.status === "succeeded"
&& promoteTask?.status === "succeeded";
const deliveryTerminalFailed = planTask?.status === "failed"
|| collectTask?.status === "failed"
|| promoteTask?.status === "failed";
let mode = "inconsistent";
let valid = false;
@@ -52,7 +131,7 @@ function extractPacSourceObservation(recordsInput) {
mode = "no-runtime-change";
valid = true;
reason = "no-build-no-rollout-plan";
} else if (planShapeValid && sourceCommitValid && !noRuntimeChangePlan && !skipMarkerConflict) {
} else if (planShapeValid && sourceCommitValid && !noRuntimeChangePlan && !skipMarkerConflict && deliveryTerminalReady) {
mode = "delivery";
valid = true;
reason = "artifact-or-runtime-delivery-required";
@@ -62,6 +141,10 @@ function extractPacSourceObservation(recordsInput) {
reason = "delivery-plan-shape-invalid-or-missing";
} else if (skipMarkerConflict) {
reason = "no-runtime-change-marker-conflicts-with-delivery-plan";
} else if (deliveryTerminalFailed) {
reason = "delivery-terminal-failed";
} else if (!noRuntimeChangePlan) {
reason = "delivery-terminal-evidence-incomplete";
}
return {
@@ -78,9 +161,134 @@ function extractPacSourceObservation(recordsInput) {
reusedServiceCount: reused?.length ?? null,
},
terminal: {
collectArtifacts: collectMarker === null ? "missing" : "no-build-no-rollout-plan",
gitopsPromote: promoteMarker === null ? "missing" : "no-build-no-rollout-plan",
planArtifacts: planTask?.status || (plan === null ? "missing" : "observed"),
collectArtifacts: collectMarker !== null ? "no-build-no-rollout-plan" : collectTask?.status || "missing",
gitopsPromote: promoteMarker !== null ? "no-build-no-rollout-plan" : promoteTask?.status || "missing",
},
terminalSources: {
planArtifacts: terminalSource(planTask, plan, "observed", "g14-ci-plan-structured-log"),
collectArtifacts: terminalSource(collectTask, collectMarker),
gitopsPromote: terminalSource(promoteTask, promoteMarker),
},
valuesPrinted: false,
};
}
function digestOf(item) {
const digest = stringOrNull(item.digest);
if (digest !== null) return digest;
const digestRef = stringOrNull(item.digestRef);
return digestRef !== null && digestRef.includes("@") ? digestRef.split("@").slice(1).join("@") : null;
}
function extractPacArtifactEvidence(recordsInput, logText) {
const records = Array.isArray(recordsInput) ? recordsInput.map(record) : [];
const sourceObservation = extractPacSourceObservation(records);
const catalog = [...records].reverse().find((item) => item.status === "published"
&& Array.isArray(item.services)
&& typeof item.registryVerified === "boolean") || null;
const publish = [...records].reverse().find((item) => item.phase === "gitops-publish" || stringOrNull(item.gitopsCommit) !== null) || null;
const image = publish || [...records].reverse().find((item) => stringOrNull(item.imageStatus) !== null
|| item.status === "reused"
|| item.status === "built") || null;
const logLines = String(logText || "").split(/\r?\n/u);
const gitopsCommit = stringOrNull(image?.gitopsCommit)
|| [...logLines].reverse().map((line) => line.match(/^\[[^\]]+\s+([0-9a-f]{7,64})\]/u)?.[1] || null).find(Boolean)
|| null;
const catalogDigests = catalog === null
? []
: [...new Set(catalog.services.map(record).map(digestOf).filter((value) => /^sha256:[0-9a-f]{64}$/iu.test(value || "")))];
const loggedDigests = [...new Set(logLines.flatMap((line) => [...line.matchAll(/"(?:digest|environmentDigest)"\s*:\s*"(sha256:[0-9a-f]{64})"/gu)].map((match) => match[1])))];
const digests = catalogDigests.length > 0 ? catalogDigests : loggedDigests;
const envHeaderIndex = logLines.findIndex((line) => /^ENV_REUSE\s+NODE_DEPS\s+/u.test(line.trim()));
let humanEnv = null;
if (envHeaderIndex >= 0) {
const row = (logLines.slice(envHeaderIndex + 1).find((line) => line.trim()) || "").trim().split(/\s+/u);
if (row.length >= 5) humanEnv = { envReuse: row[0], nodeDeps: row[1], buildPackage: row[2], buildNetwork: row[3], cache: row[4] };
}
if (catalog !== null) {
const reusedCount = Number.isInteger(catalog.reusedCount) ? catalog.reusedCount : null;
const publishedCount = Number.isInteger(catalog.publishedCount) ? catalog.publishedCount : null;
return {
imageStatus: reusedCount !== null && reusedCount > 0 && publishedCount === 0 ? "reused" : "published",
envIdentity: null,
envReuse: reusedCount !== null && reusedCount > 0 ? "hit" : null,
nodeDepsReuse: null,
buildCache: null,
digest: digests.length === 1 ? digests[0] : null,
digests,
gitopsCommit,
sourceCommit: stringOrNull(catalog.sourceCommitId) || sourceObservation?.sourceCommit || null,
runtimeSourceCommit: null,
action: "artifact-catalog-published",
reason: reusedCount !== null && reusedCount > 0 ? "reused-env-catalog" : "artifact-catalog",
catalog: {
present: true,
status: stringOrNull(catalog.status),
sourceCommit: stringOrNull(catalog.sourceCommitId),
registryVerified: catalog.registryVerified === true,
publishedCount,
reusedCount,
requiredServiceCount: Number.isInteger(catalog.requiredServiceCount) ? catalog.requiredServiceCount : null,
digestCount: digests.length,
source: "collect-artifacts-structured-log",
valuesPrinted: false,
},
sourceObservation,
valuesPrinted: false,
};
}
if (image !== null) {
const env = record(image.envReuse);
return {
imageStatus: stringOrNull(image.imageStatus) || stringOrNull(image.status) || (stringOrNull(image.digestRef) !== null ? "built" : null),
envIdentity: stringOrNull(image.envIdentity),
envReuse: stringOrNull(env.dependencyReuse) || stringOrNull(env.mode) || stringOrNull(image.envReuseStatus),
nodeDepsReuse: null,
buildCache: null,
digest: digestOf(image) || (digests.length === 1 ? digests[0] : null),
digests,
gitopsCommit,
sourceCommit: stringOrNull(image.sourceCommit) || sourceObservation?.sourceCommit || null,
runtimeSourceCommit: stringOrNull(image.runtimeSourceCommit),
baselineSourceCommit: stringOrNull(image.baselineSourceCommit),
action: stringOrNull(image.action),
reason: stringOrNull(image.reason),
sourceObservation,
valuesPrinted: false,
};
}
if (sourceObservation !== null) {
return {
imageStatus: sourceObservation.mode === "no-runtime-change" && sourceObservation.valid === true ? "retained" : null,
envIdentity: null,
envReuse: null,
nodeDepsReuse: null,
buildCache: null,
digest: null,
digests: [],
gitopsCommit,
sourceCommit: sourceObservation.sourceCommit || null,
runtimeSourceCommit: null,
action: sourceObservation.mode,
reason: sourceObservation.reason,
sourceObservation,
valuesPrinted: false,
};
}
return {
imageStatus: humanEnv !== null ? "published" : null,
envIdentity: null,
envReuse: humanEnv?.envReuse || null,
nodeDepsReuse: humanEnv?.nodeDeps || null,
buildCache: humanEnv?.cache || null,
buildPackage: humanEnv?.buildPackage || null,
buildNetwork: humanEnv?.buildNetwork || null,
digest: digests.length === 1 ? digests[0] : null,
digests,
gitopsCommit,
sourceCommit: null,
sourceObservation: null,
valuesPrinted: false,
};
}
@@ -117,7 +325,20 @@ function evaluatePacStatus(inputValue) {
const deliveryDisabled = artifact.imageStatus === "disabled";
const deliverySkipped = artifact.imageStatus === "skipped";
const sourceObservationMode = stringOrNull(sourceObservation.mode);
const hwlabCatalogDelivery = sourceObservationMode === "delivery"
&& sourceObservation.contract === "hwlab-g14-ci-plan";
const noRuntimeChange = sourceObservationMode === "no-runtime-change";
const catalog = record(artifact.catalog);
const catalogPresent = catalog.present === true;
const catalogSourceCommit = stringOrNull(catalog.sourceCommit);
const catalogSourceMatches = catalogSourceCommit !== null && catalogSourceCommit === sourceCommit;
const catalogDeliveryEvidence = sourceObservationMode === "delivery"
&& sourceObservation.valid === true
&& catalogPresent
&& catalog.status === "published"
&& catalog.registryVerified === true
&& catalogSourceMatches;
const registryEvidenceReady = input.registryPresent === true || catalogDeliveryEvidence;
const sourceObservationInconsistent = sourceObservationMode === "inconsistent"
|| (sourceObservationMode !== null && sourceObservation.valid !== true);
const observationSourceCommit = stringOrNull(sourceObservation.sourceCommit);
@@ -146,12 +367,12 @@ function evaluatePacStatus(inputValue) {
ok = false;
code = "pac-source-observation-inconsistent";
phase = "source-observation-inconsistent";
hint = "structured delivery plan and no-runtime-change terminal markers are incomplete or contradictory";
hint = "structured delivery plan or terminal evidence is incomplete or contradictory";
} else if (sourceObservationMode !== null && !observationSourceMatches) {
ok = false;
code = "pac-source-observation-mismatch";
phase = "source-observation-mismatch";
hint = "the structured no-runtime-change observation does not belong to the selected PipelineRun source commit";
hint = "the structured source observation does not belong to the selected PipelineRun source commit";
} else if (noRuntimeChange && retainedOutputConflict) {
ok = false;
code = "pac-retained-output-conflict";
@@ -191,11 +412,31 @@ function evaluatePacStatus(inputValue) {
code = "pac-ready-no-runtime-change";
phase = "ready-no-runtime-change";
hint = "the successful source observation requires no artifact, GitOps or runtime change; retained Argo and runtime provenance remain healthy";
} else if (stringOrNull(input.imageRepository) !== null && input.registryPresent !== true) {
} else if (catalogPresent && !catalogSourceMatches) {
ok = false;
code = "pac-artifact-catalog-source-mismatch";
phase = "artifact-catalog-source-mismatch";
hint = "the artifact catalog source commit does not match the selected PipelineRun";
} else if (hwlabCatalogDelivery && (!catalogPresent || catalog.status !== "published")) {
ok = false;
code = "pac-artifact-catalog-missing";
phase = "artifact-catalog-missing";
hint = "the HWLAB delivery plan completed but a published artifact catalog is not visible";
} else if (hwlabCatalogDelivery && catalog.registryVerified !== true) {
ok = false;
code = "pac-artifact-catalog-unverified";
phase = "artifact-catalog-unverified";
hint = "the HWLAB artifact catalog did not prove registry verification";
} else if (hwlabCatalogDelivery && selectedArtifactDigests.length === 0) {
ok = false;
code = "pac-artifact-catalog-digest-missing";
phase = "artifact-catalog-digest-missing";
hint = "the HWLAB artifact catalog did not expose any sha256 digest";
} else if (stringOrNull(input.imageRepository) !== null && !registryEvidenceReady) {
ok = false;
code = "pac-registry-missing";
phase = "source-ready-registry-missing";
hint = "PaC source commit is known but the configured registry tag is missing";
hint = "PaC source commit is known but neither the configured registry tag nor a verified artifact catalog is visible";
} else if (!registryMatchesArtifact) {
ok = false;
code = "pac-artifact-registry-mismatch";
@@ -216,6 +457,11 @@ function evaluatePacStatus(inputValue) {
code = `pac-argo-revision-${gitopsRelation}`;
phase = `gitops-ready-argo-revision-${gitopsRelation}`;
hint = `Argo revision relation is ${gitopsRelation}; only exact or commit-graph-proven descendant is deployable`;
} else if (catalogDeliveryEvidence && !selectedArtifactMatchesRuntime) {
ok = false;
code = "pac-catalog-artifact-runtime-mismatch";
phase = "artifact-catalog-runtime-mismatch";
hint = "the runtime digest is not present in the verified artifact catalog for this PipelineRun";
} else if (gitopsRelation === "descendant" && !selectedArtifactMatchesRuntime) {
ok = false;
code = "pac-descendant-artifact-runtime-mismatch";
@@ -261,7 +507,9 @@ function evaluatePacStatus(inputValue) {
registryProbeBase: stringOrNull(input.registryProbeBase),
imageTag: stringOrNull(input.imageTag),
registry: {
present: input.registryPresent === true,
present: registryEvidenceReady,
probePresent: input.registryPresent === true,
catalogVerified: catalogDeliveryEvidence,
digest: registryDigest,
url: stringOrNull(input.registryUrl),
},
@@ -350,14 +598,23 @@ function noRuntimeChangeObservation(sourceCommit = "c".repeat(40)) {
}
function deliveryObservation(sourceCommit = "c".repeat(40)) {
return extractPacSourceObservation([{
event: "g14-ci-plan",
sourceCommitId: sourceCommit,
affectedServices: ["service"],
rolloutServices: ["service"],
buildServices: ["service"],
reusedServices: [],
}]);
return extractPacSourceObservation([
{
event: "g14-ci-plan",
sourceCommitId: sourceCommit,
affectedServices: ["service"],
rolloutServices: ["service"],
buildServices: ["service"],
reusedServices: [],
},
{ event: "pac-task-terminal", pipelineTask: "plan-artifacts", taskRun: "plan", status: "succeeded", reason: "Succeeded", results: [] },
{ event: "pac-task-terminal", pipelineTask: "collect-artifacts", taskRun: "collect", status: "succeeded", reason: "Succeeded", results: [] },
{ event: "pac-task-terminal", pipelineTask: "gitops-promote", taskRun: "promote", status: "succeeded", reason: "Succeeded", results: [] },
]);
}
function publishedCatalog(sourceCommit = "c".repeat(40)) {
return { present: true, status: "published", sourceCommit, registryVerified: true };
}
function runPacStatusFixtureChecks() {
@@ -384,19 +641,55 @@ function runPacStatusFixtureChecks() {
id: "delivery-ready-exact",
expectedOk: true,
expectedCode: "pac-ready-gitops-exact",
input: fixtureInput({ artifact: { imageStatus: "built", digest: digestA, digests: [digestA], gitopsCommit: revision, sourceObservation: delivery }, argo: exactArgo, runtime: { image: `registry/service@${digestA}`, digest: digestA, replicas: 1, readyReplicas: 1 } }),
input: fixtureInput({ artifact: { imageStatus: "built", digest: digestA, digests: [digestA], gitopsCommit: revision, sourceObservation: delivery, catalog: publishedCatalog() }, argo: exactArgo, runtime: { image: `registry/service@${digestA}`, digest: digestA, replicas: 1, readyReplicas: 1 } }),
},
{
id: "delivery-gitops-missing",
expectedOk: false,
expectedCode: "pac-gitops-missing",
input: fixtureInput({ artifact: { imageStatus: "built", digest: digestA, sourceObservation: delivery }, argo: { sync: "Synced", health: "Healthy", revision: null, revisionRelation: { relation: "unknown" } } }),
input: fixtureInput({ artifact: { imageStatus: "built", digest: digestA, digests: [digestA], sourceObservation: delivery, catalog: publishedCatalog() }, argo: { sync: "Synced", health: "Healthy", revision: null, revisionRelation: { relation: "unknown" } } }),
},
{
id: "delivery-runtime-mismatch",
expectedOk: false,
expectedCode: "pac-runtime-not-aligned",
input: fixtureInput({ artifact: { imageStatus: "built", digest: digestA, digests: [digestA], gitopsCommit: revision, sourceObservation: delivery }, argo: exactArgo, runtime: { image: `registry/service@${digestD}`, digest: digestD, replicas: 1, readyReplicas: 1 } }),
expectedCode: "pac-catalog-artifact-runtime-mismatch",
input: fixtureInput({ artifact: { imageStatus: "built", digest: digestA, digests: [digestA], gitopsCommit: revision, sourceObservation: delivery, catalog: publishedCatalog() }, argo: exactArgo, runtime: { image: `registry/service@${digestD}`, digest: digestD, replicas: 1, readyReplicas: 1 } }),
},
{
id: "catalog-delivery-ready",
expectedOk: true,
expectedCode: "pac-ready-gitops-exact",
input: fixtureInput({ imageRepository: "registry/service", artifact: { imageStatus: "reused", digests: [digestA], gitopsCommit: revision, sourceObservation: delivery, catalog: { present: true, status: "published", sourceCommit: "c".repeat(40), registryVerified: true } }, argo: exactArgo, runtime: { image: `registry/service@${digestA}`, digest: digestA, replicas: 1, readyReplicas: 1 } }),
},
{
id: "catalog-delivery-missing",
expectedOk: false,
expectedCode: "pac-artifact-catalog-missing",
input: fixtureInput({ imageRepository: "registry/service", artifact: { imageStatus: "reused", digest: digestA, digests: [digestA], gitopsCommit: revision, sourceObservation: delivery }, registryPresent: true, argo: exactArgo, runtime: { image: `registry/service@${digestA}`, digest: digestA, replicas: 1, readyReplicas: 1 } }),
},
{
id: "catalog-delivery-unverified",
expectedOk: false,
expectedCode: "pac-artifact-catalog-unverified",
input: fixtureInput({ imageRepository: "registry/service", artifact: { imageStatus: "reused", digest: digestA, digests: [digestA], gitopsCommit: revision, sourceObservation: delivery, catalog: { present: true, status: "published", sourceCommit: "c".repeat(40), registryVerified: false } }, registryPresent: true, argo: exactArgo, runtime: { image: `registry/service@${digestA}`, digest: digestA, replicas: 1, readyReplicas: 1 } }),
},
{
id: "catalog-delivery-digest-missing",
expectedOk: false,
expectedCode: "pac-artifact-catalog-digest-missing",
input: fixtureInput({ imageRepository: "registry/service", artifact: { imageStatus: "reused", gitopsCommit: revision, sourceObservation: delivery, catalog: { present: true, status: "published", sourceCommit: "c".repeat(40), registryVerified: true } }, registryPresent: true, argo: exactArgo, runtime: { image: `registry/service@${digestA}`, digest: digestA, replicas: 1, readyReplicas: 1 } }),
},
{
id: "catalog-delivery-runtime-mismatch",
expectedOk: false,
expectedCode: "pac-catalog-artifact-runtime-mismatch",
input: fixtureInput({ imageRepository: "registry/service", artifact: { imageStatus: "reused", digests: [digestA], gitopsCommit: revision, sourceObservation: delivery, catalog: { present: true, status: "published", sourceCommit: "c".repeat(40), registryVerified: true } }, argo: exactArgo, runtime: { image: `registry/service@${digestD}`, digest: digestD, replicas: 1, readyReplicas: 1 } }),
},
{
id: "delivery-terminal-evidence-missing",
expectedOk: false,
expectedCode: "pac-source-observation-inconsistent",
input: fixtureInput({ artifact: { sourceObservation: extractPacSourceObservation([{ event: "g14-ci-plan", sourceCommitId: "c".repeat(40), affectedServices: ["service"], rolloutServices: ["service"], buildServices: [], reusedServices: ["service"] }]) } }),
},
{
id: "incomplete-no-runtime-change-markers",
@@ -436,6 +729,9 @@ function runPacStatusFixtureChecks() {
module.exports = {
evaluatePacStatus,
extractPacArtifactEvidence,
extractPacSourceObservation,
parsePacLogRecords,
runPacStatusFixtureChecks,
taskTerminalRecord,
};