1076 lines
52 KiB
JavaScript
1076 lines
52 KiB
JavaScript
"use strict";
|
|
|
|
const { createHash } = require("node:crypto");
|
|
|
|
function record(value) {
|
|
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : {};
|
|
}
|
|
|
|
function stringOrNull(value) {
|
|
return typeof value === "string" && value.length > 0 ? value : null;
|
|
}
|
|
|
|
function stringArray(value) {
|
|
return Array.isArray(value) && value.every((item) => typeof item === "string") ? value : null;
|
|
}
|
|
|
|
function digestFromImage(value) {
|
|
if (typeof value !== "string" || !value.includes("@")) return null;
|
|
return value.split("@").slice(1).join("@") || null;
|
|
}
|
|
|
|
function exactSkipMarker(records, event) {
|
|
return [...records].reverse().find((item) => item.event === event
|
|
&& item.status === "skipped"
|
|
&& item.reason === "no-build-no-rollout-plan") || null;
|
|
}
|
|
|
|
const pacContractTasks = new Set(["plan-artifacts", "collect-artifacts", "gitops-promote"]);
|
|
|
|
function firstString(...values) {
|
|
for (const value of values) {
|
|
if (typeof value === "string" && value.length > 0) return value;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
function pipelineRunMatchesConsumer(consumerInput, itemInput) {
|
|
const consumer = record(consumerInput);
|
|
const item = record(itemInput);
|
|
const metadata = record(item.metadata);
|
|
const labels = record(metadata.labels);
|
|
const spec = record(item.spec);
|
|
const pipelineRef = record(spec.pipelineRef);
|
|
const name = stringOrNull(metadata.name) || "";
|
|
const prefix = stringOrNull(consumer.pipelineRunPrefix) || "";
|
|
const pipeline = stringOrNull(consumer.pipeline) || "";
|
|
return (prefix.length > 0 && name.startsWith(prefix))
|
|
|| (pipeline.length > 0 && labels["tekton.dev/pipeline"] === pipeline)
|
|
|| (pipeline.length > 0 && labels["tekton.dev/pipelineName"] === pipeline)
|
|
|| (pipeline.length > 0 && pipelineRef.name === pipeline);
|
|
}
|
|
|
|
function stableCanonicalJson(value) {
|
|
if (Array.isArray(value)) return `[${value.map(stableCanonicalJson).join(",")}]`;
|
|
if (typeof value === "object" && value !== null) {
|
|
return `{${Object.entries(value).sort(([left], [right]) => left.localeCompare(right)).map(([key, item]) => `${JSON.stringify(key)}:${stableCanonicalJson(item)}`).join(",")}}`;
|
|
}
|
|
return JSON.stringify(value);
|
|
}
|
|
|
|
function liveAdmissionSpecSha256(targetId, policyName, bindingName, policySpecInput, bindingSpecInput) {
|
|
const policySpec = normalizeAdmissionPolicySpec(policySpecInput);
|
|
const bindingSpec = normalizeAdmissionBindingSpec(bindingSpecInput);
|
|
const normalized = {
|
|
targetId,
|
|
policyName,
|
|
bindingName,
|
|
policySpec,
|
|
bindingSpec,
|
|
};
|
|
return `sha256:${createHash("sha256").update(stableCanonicalJson(normalized)).digest("hex")}`;
|
|
}
|
|
|
|
function normalizeAdmissionPolicySpec(value) {
|
|
const spec = { ...record(value) };
|
|
if (Object.prototype.hasOwnProperty.call(spec, "matchConstraints")) {
|
|
const matchConstraints = { ...record(spec.matchConstraints) };
|
|
if (!Object.prototype.hasOwnProperty.call(matchConstraints, "matchPolicy")) matchConstraints.matchPolicy = "Equivalent";
|
|
spec.matchConstraints = matchConstraints;
|
|
}
|
|
return spec;
|
|
}
|
|
|
|
function normalizeAdmissionBindingSpec(value) {
|
|
const spec = { ...record(value) };
|
|
if (Object.prototype.hasOwnProperty.call(spec, "matchResources")) {
|
|
const matchResources = { ...record(spec.matchResources) };
|
|
if (!Object.prototype.hasOwnProperty.call(matchResources, "matchPolicy")) matchResources.matchPolicy = "Equivalent";
|
|
spec.matchResources = matchResources;
|
|
}
|
|
return spec;
|
|
}
|
|
|
|
function evaluatePacAdmissionState(inputValue) {
|
|
const input = record(inputValue);
|
|
const policy = record(input.policy);
|
|
const binding = record(input.binding);
|
|
const role = record(input.role);
|
|
const roleBinding = record(input.roleBinding);
|
|
const expected = record(input.expected);
|
|
const namespace = stringOrNull(input.namespace);
|
|
const policyMetadata = record(policy.metadata);
|
|
const bindingMetadata = record(binding.metadata);
|
|
const roleMetadata = record(role.metadata);
|
|
const roleBindingMetadata = record(roleBinding.metadata);
|
|
const policyAnnotations = record(policyMetadata.annotations);
|
|
const bindingAnnotations = record(bindingMetadata.annotations);
|
|
const roleAnnotations = record(roleMetadata.annotations);
|
|
const roleBindingAnnotations = record(roleBindingMetadata.annotations);
|
|
const policySpec = record(policy.spec);
|
|
const bindingSpec = record(binding.spec);
|
|
const policyStatus = record(policy.status);
|
|
const typeChecking = record(policyStatus.typeChecking);
|
|
const warnings = Array.isArray(typeChecking.expressionWarnings) ? typeChecking.expressionWarnings : [];
|
|
const reasons = [];
|
|
const targetId = stringOrNull(expected.targetId);
|
|
const liveSpecSha256 = targetId === null
|
|
? null
|
|
: liveAdmissionSpecSha256(targetId, expected.policyName, expected.bindingName, policySpec, bindingSpec);
|
|
if (targetId === null) reasons.push("admission-target-id-missing");
|
|
if (liveSpecSha256 !== expected.configSha256) reasons.push("admission-live-spec-sha256-mismatch");
|
|
if (policyMetadata.name !== expected.policyName) reasons.push("policy-missing");
|
|
if (bindingMetadata.name !== expected.bindingName) reasons.push("binding-missing");
|
|
for (const [kind, annotations] of [["policy", policyAnnotations], ["binding", bindingAnnotations]]) {
|
|
if (annotations["unidesk.ai/pac-provenance-version"] !== expected.version) reasons.push(`${kind}-version-mismatch`);
|
|
if (annotations["unidesk.ai/pac-controller-identity"] !== expected.controllerIdentity) reasons.push(`${kind}-controller-identity-mismatch`);
|
|
if (annotations["unidesk.ai/effective-config-sha256"] !== expected.configSha256) reasons.push(`${kind}-config-sha256-mismatch`);
|
|
}
|
|
if (policySpec.failurePolicy !== "Fail") reasons.push("policy-failure-policy-not-fail");
|
|
if (bindingSpec.policyName !== expected.policyName) reasons.push("binding-policy-name-mismatch");
|
|
if (!Array.isArray(bindingSpec.validationActions) || bindingSpec.validationActions.length !== 1 || bindingSpec.validationActions[0] !== "Deny") reasons.push("binding-validation-actions-not-deny");
|
|
if (!Number.isInteger(policyMetadata.generation) || !Number.isInteger(policyStatus.observedGeneration) || policyStatus.observedGeneration < policyMetadata.generation) reasons.push("policy-generation-not-observed");
|
|
if (warnings.length > 0) reasons.push("policy-expression-warning");
|
|
if (roleMetadata.name !== "unidesk-pac-consumer-runner") reasons.push("default-role-missing");
|
|
if (roleBindingMetadata.name !== "unidesk-pac-consumer-runner-default") reasons.push("default-rolebinding-missing");
|
|
if (roleAnnotations["unidesk.ai/effective-config-sha256"] !== expected.rbacConfigSha256) reasons.push("default-role-config-sha256-mismatch");
|
|
if (roleBindingAnnotations["unidesk.ai/effective-config-sha256"] !== expected.rbacConfigSha256) reasons.push("default-rolebinding-config-sha256-mismatch");
|
|
const forbiddenVerbs = new Set(["create", "patch", "update", "delete", "deletecollection"]);
|
|
const roleRules = Array.isArray(role.rules) ? role.rules : [];
|
|
const roleHasMutation = roleRules.some((ruleValue) => {
|
|
const rule = record(ruleValue);
|
|
return (Array.isArray(rule.verbs) ? rule.verbs : []).some((verb) => forbiddenVerbs.has(verb));
|
|
});
|
|
if (roleHasMutation) reasons.push("default-role-declares-mutation");
|
|
const expectedRoleRules = [
|
|
{ apiGroups: ["tekton.dev"], resources: ["pipelineruns", "taskruns"], verbs: ["get", "list", "watch"] },
|
|
{ apiGroups: [""], resources: ["pods", "pods/log"], verbs: ["get", "list", "watch"] },
|
|
];
|
|
if (stableCanonicalJson(roleRules) !== stableCanonicalJson(expectedRoleRules)) reasons.push("default-role-rules-mismatch");
|
|
const roleRef = record(roleBinding.roleRef);
|
|
const subjects = Array.isArray(roleBinding.subjects) ? roleBinding.subjects.map(record) : [];
|
|
if (roleRef.apiGroup !== "rbac.authorization.k8s.io" || roleRef.kind !== "Role" || roleRef.name !== "unidesk-pac-consumer-runner") reasons.push("default-rolebinding-role-ref-mismatch");
|
|
if (namespace === null || subjects.length !== 1 || subjects[0].kind !== "ServiceAccount" || subjects[0].name !== "default" || subjects[0].namespace !== namespace) reasons.push("default-rolebinding-subject-mismatch");
|
|
if (typeof input.defaultCanMutate !== "boolean") reasons.push("default-serviceaccount-mutation-probe-unavailable");
|
|
if (input.defaultCanMutate === true) reasons.push("default-serviceaccount-can-mutate-tekton-runs");
|
|
const timestamps = [policyMetadata.creationTimestamp, bindingMetadata.creationTimestamp]
|
|
.filter((value) => typeof value === "string" && Number.isFinite(Date.parse(value)))
|
|
.sort((left, right) => Date.parse(left) - Date.parse(right));
|
|
const activeAfter = timestamps.length === 2 ? timestamps[1] : null;
|
|
if (activeAfter === null) reasons.push("admission-creation-time-missing");
|
|
return {
|
|
configured: true,
|
|
required: true,
|
|
ready: reasons.length === 0,
|
|
source: "live-admissionregistration-v1",
|
|
policyName: stringOrNull(policyMetadata.name),
|
|
bindingName: stringOrNull(bindingMetadata.name),
|
|
version: stringOrNull(policyAnnotations["unidesk.ai/pac-provenance-version"]),
|
|
controllerIdentity: stringOrNull(policyAnnotations["unidesk.ai/pac-controller-identity"]),
|
|
configSha256: stringOrNull(policyAnnotations["unidesk.ai/effective-config-sha256"]),
|
|
liveSpecSha256,
|
|
policyUid: stringOrNull(policyMetadata.uid),
|
|
bindingUid: stringOrNull(bindingMetadata.uid),
|
|
policyGeneration: Number.isInteger(policyMetadata.generation) ? policyMetadata.generation : null,
|
|
observedGeneration: Number.isInteger(policyStatus.observedGeneration) ? policyStatus.observedGeneration : null,
|
|
rbacConfigSha256: stringOrNull(roleAnnotations["unidesk.ai/effective-config-sha256"]),
|
|
defaultCanMutate: typeof input.defaultCanMutate === "boolean" ? input.defaultCanMutate : null,
|
|
activeAfter,
|
|
reasons,
|
|
valuesPrinted: false,
|
|
};
|
|
}
|
|
|
|
function classifyPacPipelineRun(consumerInput, itemInput) {
|
|
const consumer = record(consumerInput);
|
|
const item = record(itemInput);
|
|
const metadata = record(item.metadata);
|
|
const labels = record(metadata.labels);
|
|
const annotations = record(metadata.annotations);
|
|
const candidate = pipelineRunMatchesConsumer(consumer, item);
|
|
const eventType = firstString(
|
|
labels["pipelinesascode.tekton.dev/event-type"],
|
|
annotations["pipelinesascode.tekton.dev/event-type"],
|
|
);
|
|
const repository = firstString(
|
|
labels["pipelinesascode.tekton.dev/repository"],
|
|
annotations["pipelinesascode.tekton.dev/repository"],
|
|
);
|
|
const managedBy = stringOrNull(labels["app.kubernetes.io/managed-by"]);
|
|
const provider = stringOrNull(annotations["pipelinesascode.tekton.dev/git-provider"]);
|
|
const expectedRepository = stringOrNull(consumer.repository);
|
|
const provenance = record(consumer.deliveryProvenance);
|
|
const admissionState = record(consumer.admissionState);
|
|
const outer = candidate
|
|
&& eventType === "push"
|
|
&& expectedRepository !== null
|
|
&& repository === expectedRepository
|
|
&& managedBy === "pipelinesascode.tekton.dev"
|
|
&& provider === "gitea";
|
|
const inner = candidate
|
|
&& !outer
|
|
&& labels["app.kubernetes.io/part-of"] === "hwlab-web-probe-sentinel"
|
|
&& labels["unidesk.ai/ci-system"] === "tekton"
|
|
&& annotations["unidesk.ai/source-authority"] === "gitea-snapshot"
|
|
&& typeof annotations["unidesk.ai/publish-gitops"] === "string";
|
|
const provenanceRequired = provenance.required === true;
|
|
const markerAnnotation = stringOrNull(provenance.markerAnnotation);
|
|
const markerValue = stringOrNull(provenance.markerValue);
|
|
const executionServiceAccountName = stringOrNull(provenance.executionServiceAccountName);
|
|
const taskRunTemplate = record(record(item.spec).taskRunTemplate);
|
|
const createdAt = stringOrNull(metadata.creationTimestamp);
|
|
const activeAfter = stringOrNull(admissionState.activeAfter);
|
|
const createdAfterAdmission = createdAt !== null
|
|
&& activeAfter !== null
|
|
&& Number.isFinite(Date.parse(createdAt))
|
|
&& Number.isFinite(Date.parse(activeAfter))
|
|
&& Date.parse(createdAt) >= Date.parse(activeAfter);
|
|
const admissionProvenanceVerified = outer
|
|
&& provenanceRequired
|
|
&& admissionState.ready === true
|
|
&& admissionState.source === "live-admissionregistration-v1"
|
|
&& admissionState.policyName === provenance.policyName
|
|
&& admissionState.bindingName === provenance.bindingName
|
|
&& admissionState.version === provenance.version
|
|
&& admissionState.controllerIdentity === provenance.controllerIdentity
|
|
&& admissionState.configSha256 === provenance.configSha256
|
|
&& markerAnnotation !== null
|
|
&& markerValue !== null
|
|
&& annotations[markerAnnotation] === markerValue
|
|
&& executionServiceAccountName !== null
|
|
&& taskRunTemplate.serviceAccountName === executionServiceAccountName
|
|
&& createdAfterAdmission;
|
|
|
|
const deliveryClass = outer
|
|
? "outer-pac-event"
|
|
: inner
|
|
? "inner-deterministic-publish"
|
|
: "unknown";
|
|
const reason = outer
|
|
? admissionProvenanceVerified
|
|
? "admission-owned-pac-push-provenance-verified"
|
|
: provenanceRequired
|
|
? "pac-push-metadata-observed-but-admission-provenance-unverified"
|
|
: "observed-pac-push-metadata-matches-consumer"
|
|
: inner
|
|
? "deterministic-sentinel-publish-without-proven-parent"
|
|
: candidate
|
|
? "consumer-candidate-lacks-classification-evidence"
|
|
: "not-a-consumer-candidate";
|
|
return {
|
|
deliveryClass,
|
|
reason,
|
|
candidate,
|
|
deliveryAuthorityEligible: outer && (!provenanceRequired || admissionProvenanceVerified),
|
|
deliveryOwner: outer ? admissionProvenanceVerified ? "pac-controller-admission" : "pac-controller-observed" : null,
|
|
executionOwner: inner ? "tekton-child-unattached" : null,
|
|
parentPipelineRun: null,
|
|
parentRelation: inner ? "unproven" : null,
|
|
metadataObservationOnly: outer && !admissionProvenanceVerified,
|
|
admissionProvenanceVerified,
|
|
admissionPolicyReady: admissionState.ready === true,
|
|
admissionPolicyName: stringOrNull(admissionState.policyName),
|
|
admissionBindingName: stringOrNull(admissionState.bindingName),
|
|
admissionConfigSha256: stringOrNull(admissionState.configSha256),
|
|
admissionActiveAfter: activeAfter,
|
|
createdAfterAdmission,
|
|
valuesPrinted: false,
|
|
};
|
|
}
|
|
|
|
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 === "pac-delivery-plan" || item.event === "g14-ci-plan") || null;
|
|
const collectMarker = exactSkipMarker(records, "collect-artifacts");
|
|
const promoteMarker = exactSkipMarker(records, "gitops-promote");
|
|
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);
|
|
const build = stringArray(plan?.buildServices);
|
|
const reused = stringArray(plan?.reusedServices);
|
|
const sourceCommit = stringOrNull(plan?.sourceCommitId);
|
|
const planShapeValid = affected !== null && rollout !== null && build !== null;
|
|
const sourceCommitValid = sourceCommit !== null && /^[0-9a-f]{40}$/iu.test(sourceCommit);
|
|
const noRuntimeChangePlan = planShapeValid
|
|
&& affected.length === 0
|
|
&& rollout.length === 0
|
|
&& build.length === 0;
|
|
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;
|
|
let reason = "structured-plan-or-terminal-marker-is-incomplete";
|
|
if (planShapeValid && sourceCommitValid && noRuntimeChangePlan && noRuntimeChangeMarkers) {
|
|
mode = "no-runtime-change";
|
|
valid = true;
|
|
reason = "no-build-no-rollout-plan";
|
|
} else if (planShapeValid && sourceCommitValid && !noRuntimeChangePlan && !skipMarkerConflict && deliveryTerminalReady) {
|
|
mode = "delivery";
|
|
valid = true;
|
|
reason = "artifact-or-runtime-delivery-required";
|
|
} else if (!sourceCommitValid) {
|
|
reason = "source-commit-invalid-or-missing";
|
|
} else if (!planShapeValid) {
|
|
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";
|
|
}
|
|
|
|
const planMarkerSource = plan?.event === "pac-delivery-plan"
|
|
? "pac-delivery-plan-structured-log"
|
|
: plan?.event === "g14-ci-plan"
|
|
? "g14-ci-plan-structured-log"
|
|
: "delivery-plan-structured-log";
|
|
return {
|
|
schemaVersion: "v1",
|
|
contract: plan?.event === "pac-delivery-plan" ? "pac-delivery-plan-v1" : "hwlab-g14-ci-plan",
|
|
mode,
|
|
valid,
|
|
reason,
|
|
sourceCommit,
|
|
plan: {
|
|
affectedServiceCount: affected?.length ?? null,
|
|
rolloutServiceCount: rollout?.length ?? null,
|
|
buildServiceCount: build?.length ?? null,
|
|
reusedServiceCount: reused?.length ?? null,
|
|
},
|
|
terminal: {
|
|
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", planMarkerSource),
|
|
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,
|
|
};
|
|
}
|
|
|
|
function evaluatePacStatus(inputValue) {
|
|
const input = record(inputValue);
|
|
const artifact = record(input.artifact);
|
|
const argo = record(input.argo);
|
|
const runtime = record(input.runtime);
|
|
const sourceObservation = record(artifact.sourceObservation);
|
|
const revisionRelation = record(argo.revisionRelation);
|
|
const gitopsRelation = stringOrNull(revisionRelation.relation) || "unknown";
|
|
const sourceCommit = stringOrNull(input.sourceCommit);
|
|
const registryDigest = stringOrNull(input.registryDigest);
|
|
const artifactDigest = stringOrNull(artifact.digest);
|
|
const expectedDigest = registryDigest || artifactDigest;
|
|
const runtimeImage = stringOrNull(runtime.image);
|
|
const runtimeDigest = stringOrNull(runtime.digest) || digestFromImage(runtimeImage);
|
|
const runtimeMatches = expectedDigest === null || runtimeDigest === expectedDigest;
|
|
const runtimeReady = Number.isInteger(runtime.replicas)
|
|
&& runtime.replicas > 0
|
|
&& runtime.readyReplicas === runtime.replicas;
|
|
const selectedArtifactDigests = [...new Set([
|
|
artifactDigest,
|
|
...(Array.isArray(artifact.digests) ? artifact.digests : []),
|
|
].filter((value) => typeof value === "string" && value.length > 0))];
|
|
const selectedArtifactMatchesRuntime = runtimeDigest !== null && selectedArtifactDigests.includes(runtimeDigest);
|
|
const registryMatchesArtifact = registryDigest === null || artifactDigest === null || registryDigest === artifactDigest;
|
|
const healthUrl = stringOrNull(input.healthUrl);
|
|
const healthStatus = stringOrNull(input.healthStatus);
|
|
const healthReady = healthUrl === null || healthStatus === "200";
|
|
const gitopsReady = stringOrNull(artifact.gitopsCommit) !== null && stringOrNull(argo.revision) !== null;
|
|
const gitopsRevisionAligned = gitopsRelation === "exact" || gitopsRelation === "descendant";
|
|
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);
|
|
const observationSourceMatches = sourceCommit !== null && observationSourceCommit === sourceCommit;
|
|
const retainedRevision = stringOrNull(argo.revision);
|
|
const retainedDigestReady = runtimeDigest !== null && /^sha256:[0-9a-f]{64}$/iu.test(runtimeDigest);
|
|
const retainedOutputConflict = noRuntimeChange
|
|
&& (stringOrNull(artifact.gitopsCommit) !== null
|
|
|| artifactDigest !== null
|
|
|| selectedArtifactDigests.length > 0);
|
|
|
|
let code = "pac-diagnostics-not-applicable";
|
|
let phase = "not-applicable";
|
|
let ok = true;
|
|
let hint = "consumer did not expose artifact or runtime alignment evidence";
|
|
if (deliveryDisabled) {
|
|
code = "pac-delivery-disabled";
|
|
phase = "disabled";
|
|
hint = "delivery is disabled by YAML; registry, GitOps and runtime artifacts are intentionally absent";
|
|
} else if (!sourceCommit) {
|
|
ok = false;
|
|
code = "pac-source-unknown";
|
|
phase = "source-unknown";
|
|
hint = "latest PaC PipelineRun did not expose a source commit";
|
|
} else if (sourceObservationInconsistent) {
|
|
ok = false;
|
|
code = "pac-source-observation-inconsistent";
|
|
phase = "source-observation-inconsistent";
|
|
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 source observation does not belong to the selected PipelineRun source commit";
|
|
} else if (noRuntimeChange && retainedOutputConflict) {
|
|
ok = false;
|
|
code = "pac-retained-output-conflict";
|
|
phase = "source-only-output-conflict";
|
|
hint = "a no-runtime-change PipelineRun unexpectedly reported a new artifact or GitOps output";
|
|
} else if (noRuntimeChange && retainedRevision === null) {
|
|
ok = false;
|
|
code = "pac-retained-gitops-missing";
|
|
phase = "retained-gitops-missing";
|
|
hint = "the no-runtime-change observation is valid but the retained Argo GitOps revision is missing";
|
|
} else if (noRuntimeChange && (argo.sync !== "Synced" || argo.health !== "Healthy")) {
|
|
ok = false;
|
|
code = "pac-retained-argo-not-ready";
|
|
phase = "retained-argo-not-ready";
|
|
hint = "the no-runtime-change observation is valid but retained Argo is not Synced/Healthy";
|
|
} else if (noRuntimeChange && gitopsRelation !== "retained") {
|
|
ok = false;
|
|
code = "pac-retained-revision-unproven";
|
|
phase = "retained-revision-unproven";
|
|
hint = "the retained GitOps revision was not linked to the structured no-runtime-change observation";
|
|
} else if (noRuntimeChange && !runtimeReady) {
|
|
ok = false;
|
|
code = "pac-retained-runtime-not-ready";
|
|
phase = "retained-runtime-not-ready";
|
|
hint = "the retained runtime ready replicas do not match the desired replica count";
|
|
} else if (noRuntimeChange && !retainedDigestReady) {
|
|
ok = false;
|
|
code = "pac-retained-runtime-digest-missing";
|
|
phase = "retained-runtime-digest-missing";
|
|
hint = "the retained runtime is not pinned to a visible sha256 digest";
|
|
} else if (noRuntimeChange && !healthReady) {
|
|
ok = false;
|
|
code = "pac-retained-health-not-ready";
|
|
phase = "retained-health-not-ready";
|
|
hint = "the retained runtime health endpoint is not ready";
|
|
} else if (noRuntimeChange) {
|
|
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 (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 neither the configured registry tag nor a verified artifact catalog is visible";
|
|
} else if (!registryMatchesArtifact) {
|
|
ok = false;
|
|
code = "pac-artifact-registry-mismatch";
|
|
phase = "artifact-registry-mismatch";
|
|
hint = "PipelineRun artifact digest does not match the configured registry digest";
|
|
} else if (!gitopsReady) {
|
|
ok = false;
|
|
code = "pac-gitops-missing";
|
|
phase = "artifact-ready-gitops-missing";
|
|
hint = "PipelineRun completed but its GitOps commit is not visible";
|
|
} else if (argo.sync !== "Synced" || argo.health !== "Healthy") {
|
|
ok = false;
|
|
code = "pac-argo-not-ready";
|
|
phase = "gitops-ready-argo-pending";
|
|
hint = "GitOps exists but Argo is not Synced/Healthy";
|
|
} else if (!gitopsRevisionAligned) {
|
|
ok = false;
|
|
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";
|
|
phase = "argo-descendant-runtime-mismatch";
|
|
hint = "a GitOps descendant is ready only when the selected PipelineRun artifact digest exactly matches the runtime digest";
|
|
} else if (!runtimeMatches) {
|
|
ok = false;
|
|
code = "pac-runtime-not-aligned";
|
|
phase = "argo-ready-runtime-mismatch";
|
|
hint = "runtime image digest does not match the artifact digest produced by the selected PipelineRun";
|
|
} else if (!runtimeReady) {
|
|
ok = false;
|
|
code = "pac-runtime-not-ready";
|
|
phase = "runtime-replicas-not-ready";
|
|
hint = "runtime ready replicas do not match the desired replica count";
|
|
} else if (!healthReady) {
|
|
ok = false;
|
|
code = "pac-health-not-ready";
|
|
phase = "runtime-ready-health-pending";
|
|
hint = "runtime image is aligned but the configured health endpoint is not ready";
|
|
} else if (artifactDigest || selectedArtifactDigests.length > 0 || stringOrNull(input.imageRepository) !== null) {
|
|
code = deliverySkipped
|
|
? "pac-ready-runtime-unchanged"
|
|
: gitopsRelation === "descendant"
|
|
? "pac-ready-gitops-descendant"
|
|
: "pac-ready-gitops-exact";
|
|
phase = "ready";
|
|
hint = deliverySkipped
|
|
? "YAML-declared runtime inputs are unchanged; the existing image, GitOps manifest, runtime and health remain aligned"
|
|
: gitopsRelation === "descendant"
|
|
? "Argo is on a commit-graph-proven GitOps descendant and the selected artifact, runtime, Argo and health are aligned"
|
|
: "Argo is on the exact selected GitOps commit and the artifact, runtime, Argo and health are aligned";
|
|
}
|
|
|
|
return {
|
|
ok,
|
|
code,
|
|
phase,
|
|
hint,
|
|
sourceCommit,
|
|
runtimeSourceCommit: stringOrNull(input.runtimeSourceCommit),
|
|
imageRepository: stringOrNull(input.imageRepository),
|
|
registryProbeBase: stringOrNull(input.registryProbeBase),
|
|
imageTag: stringOrNull(input.imageTag),
|
|
registry: {
|
|
present: registryEvidenceReady,
|
|
probePresent: input.registryPresent === true,
|
|
catalogVerified: catalogDeliveryEvidence,
|
|
digest: registryDigest,
|
|
url: stringOrNull(input.registryUrl),
|
|
},
|
|
selectedArtifactDigests,
|
|
gitops: {
|
|
branch: stringOrNull(input.gitopsBranch),
|
|
manifestPath: stringOrNull(input.gitopsManifestPath),
|
|
commit: noRuntimeChange ? retainedRevision : stringOrNull(artifact.gitopsCommit) || retainedRevision,
|
|
revisionRelation,
|
|
},
|
|
argo: {
|
|
sync: stringOrNull(argo.sync),
|
|
health: stringOrNull(argo.health),
|
|
revision: retainedRevision,
|
|
repoURL: stringOrNull(argo.repoURL),
|
|
targetRevision: stringOrNull(argo.targetRevision),
|
|
revisionRelation,
|
|
},
|
|
runtime: {
|
|
image: runtimeImage,
|
|
digest: runtimeDigest,
|
|
readyReplicas: runtime.readyReplicas ?? null,
|
|
replicas: runtime.replicas ?? null,
|
|
},
|
|
health: { url: healthUrl, status: healthStatus, ready: healthReady },
|
|
pipelineRun: stringOrNull(input.pipelineRun),
|
|
deliveryDisabled,
|
|
deliverySkipped,
|
|
deliveryMode: noRuntimeChange ? "no-runtime-change" : sourceObservationMode || "delivery",
|
|
sourceObservation: Object.keys(sourceObservation).length > 0 ? sourceObservation : null,
|
|
provenance: noRuntimeChange ? {
|
|
relation: "retained",
|
|
sourceCommit,
|
|
gitopsRevision: retainedRevision,
|
|
runtimeImage,
|
|
runtimeDigest,
|
|
newArtifact: false,
|
|
newGitopsRevision: false,
|
|
runtimeChanged: false,
|
|
valuesPrinted: false,
|
|
} : {
|
|
relation: gitopsRelation,
|
|
sourceCommit,
|
|
gitopsRevision: retainedRevision,
|
|
runtimeImage,
|
|
runtimeDigest,
|
|
valuesPrinted: false,
|
|
},
|
|
valuesPrinted: false,
|
|
};
|
|
}
|
|
|
|
function fixtureInput(overrides = {}) {
|
|
const digest = `sha256:${"a".repeat(64)}`;
|
|
const revision = "b".repeat(40);
|
|
return {
|
|
sourceCommit: "c".repeat(40),
|
|
artifact: {},
|
|
argo: {
|
|
sync: "Synced",
|
|
health: "Healthy",
|
|
revision,
|
|
revisionRelation: { relation: "retained", observedRevision: revision },
|
|
},
|
|
runtime: { image: `registry/service@${digest}`, digest, replicas: 1, readyReplicas: 1 },
|
|
registryPresent: false,
|
|
healthUrl: null,
|
|
healthStatus: null,
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
function noRuntimeChangeObservation(sourceCommit = "c".repeat(40)) {
|
|
return extractPacSourceObservation([
|
|
{
|
|
event: "g14-ci-plan",
|
|
sourceCommitId: sourceCommit,
|
|
affectedServices: [],
|
|
rolloutServices: [],
|
|
buildServices: [],
|
|
reusedServices: ["service"],
|
|
},
|
|
{ event: "collect-artifacts", status: "skipped", reason: "no-build-no-rollout-plan" },
|
|
{ event: "gitops-promote", status: "skipped", reason: "no-build-no-rollout-plan" },
|
|
]);
|
|
}
|
|
|
|
function deliveryObservation(sourceCommit = "c".repeat(40)) {
|
|
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() {
|
|
const digestA = `sha256:${"a".repeat(64)}`;
|
|
const digestD = `sha256:${"d".repeat(64)}`;
|
|
const revision = "b".repeat(40);
|
|
const retained = noRuntimeChangeObservation();
|
|
const delivery = deliveryObservation();
|
|
const exactArgo = { sync: "Synced", health: "Healthy", revision, revisionRelation: { relation: "exact", expectedRevision: revision, observedRevision: revision } };
|
|
const cases = [
|
|
{
|
|
id: "retained-ready",
|
|
expectedOk: true,
|
|
expectedCode: "pac-ready-no-runtime-change",
|
|
input: fixtureInput({ artifact: { imageStatus: "retained", sourceObservation: retained } }),
|
|
},
|
|
{
|
|
id: "retained-gitops-missing",
|
|
expectedOk: false,
|
|
expectedCode: "pac-retained-gitops-missing",
|
|
input: fixtureInput({ artifact: { imageStatus: "retained", sourceObservation: retained }, argo: { sync: "Synced", health: "Healthy", revision: null, revisionRelation: { relation: "unknown" } } }),
|
|
},
|
|
{
|
|
id: "delivery-ready-exact",
|
|
expectedOk: true,
|
|
expectedCode: "pac-ready-gitops-exact",
|
|
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-ready-descendant",
|
|
expectedOk: true,
|
|
expectedCode: "pac-ready-gitops-descendant",
|
|
input: fixtureInput({ artifact: { imageStatus: "built", digest: digestA, digests: [digestA], gitopsCommit: revision, sourceObservation: delivery, catalog: publishedCatalog() }, argo: { ...exactArgo, revision: "d".repeat(40), revisionRelation: { relation: "descendant", expectedRevision: revision, observedRevision: "d".repeat(40) } }, runtime: { image: `registry/service@${digestA}`, digest: digestA, replicas: 1, readyReplicas: 1 } }),
|
|
},
|
|
{
|
|
id: "delivery-stale-gitops-fails-closed",
|
|
expectedOk: false,
|
|
expectedCode: "pac-argo-revision-stale",
|
|
input: fixtureInput({ artifact: { imageStatus: "built", digest: digestA, digests: [digestA], gitopsCommit: revision, sourceObservation: delivery, catalog: publishedCatalog() }, argo: { ...exactArgo, revision: "d".repeat(40), revisionRelation: { relation: "stale", expectedRevision: revision, observedRevision: "d".repeat(40) } }, 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, digests: [digestA], sourceObservation: delivery, catalog: publishedCatalog() }, argo: { sync: "Synced", health: "Healthy", revision: null, revisionRelation: { relation: "unknown" } } }),
|
|
},
|
|
{
|
|
id: "delivery-runtime-mismatch",
|
|
expectedOk: false,
|
|
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",
|
|
expectedOk: false,
|
|
expectedCode: "pac-source-observation-inconsistent",
|
|
input: fixtureInput({ artifact: { sourceObservation: extractPacSourceObservation([{ event: "g14-ci-plan", sourceCommitId: "c".repeat(40), affectedServices: [], rolloutServices: [], buildServices: [], reusedServices: [] }]) } }),
|
|
},
|
|
{
|
|
id: "retained-source-mismatch",
|
|
expectedOk: false,
|
|
expectedCode: "pac-source-observation-mismatch",
|
|
input: fixtureInput({ sourceCommit: "e".repeat(40), artifact: { imageStatus: "retained", sourceObservation: retained } }),
|
|
},
|
|
{
|
|
id: "delivery-one-sided-skip-conflict",
|
|
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: ["service"], reusedServices: [] },
|
|
{ event: "gitops-promote", status: "skipped", reason: "no-build-no-rollout-plan" },
|
|
]) } }),
|
|
},
|
|
];
|
|
const checks = cases.map((item) => {
|
|
const result = evaluatePacStatus(item.input);
|
|
return {
|
|
id: item.id,
|
|
ok: result.ok === item.expectedOk && result.code === item.expectedCode,
|
|
expectedOk: item.expectedOk,
|
|
actualOk: result.ok,
|
|
expectedCode: item.expectedCode,
|
|
actualCode: result.code,
|
|
};
|
|
});
|
|
const provenance = {
|
|
required: true,
|
|
version: "admission-pac-v1",
|
|
controllerIdentity: "system:serviceaccount:pipelines-as-code:pipelines-as-code-controller",
|
|
policyName: "unidesk-pac-delivery-provenance-v1",
|
|
bindingName: "unidesk-pac-delivery-provenance-v1",
|
|
configSha256: `sha256:${"1".repeat(64)}`,
|
|
markerAnnotation: "unidesk.ai/pac-admission-provenance",
|
|
markerValue: "admission-pac-v1:fixture",
|
|
executionServiceAccountName: "fixture-pac-runner",
|
|
};
|
|
const admissionState = {
|
|
ready: true,
|
|
source: "live-admissionregistration-v1",
|
|
policyName: provenance.policyName,
|
|
bindingName: provenance.bindingName,
|
|
version: provenance.version,
|
|
controllerIdentity: provenance.controllerIdentity,
|
|
configSha256: provenance.configSha256,
|
|
activeAfter: "2026-01-01T00:00:00Z",
|
|
};
|
|
const consumer = {
|
|
id: "fixture",
|
|
repository: "fixture-repository",
|
|
pipeline: "fixture-pipeline",
|
|
pipelineRunPrefix: "fixture-run-",
|
|
deliveryProvenance: provenance,
|
|
admissionState,
|
|
};
|
|
const verifiedRun = {
|
|
metadata: {
|
|
name: "fixture-run-verified",
|
|
creationTimestamp: "2026-01-01T00:00:01Z",
|
|
labels: {
|
|
"app.kubernetes.io/managed-by": "pipelinesascode.tekton.dev",
|
|
"pipelinesascode.tekton.dev/event-type": "push",
|
|
"pipelinesascode.tekton.dev/repository": "fixture-repository",
|
|
},
|
|
annotations: {
|
|
"pipelinesascode.tekton.dev/git-provider": "gitea",
|
|
[provenance.markerAnnotation]: provenance.markerValue,
|
|
},
|
|
},
|
|
spec: { taskRunTemplate: { serviceAccountName: provenance.executionServiceAccountName } },
|
|
};
|
|
const classificationCases = [
|
|
{ id: "admission-provenance-verified", expected: true, item: verifiedRun, state: admissionState },
|
|
{ id: "pre-bootstrap-run-fails-closed", expected: false, item: { ...verifiedRun, metadata: { ...verifiedRun.metadata, creationTimestamp: "2025-12-31T23:59:59Z" } }, state: admissionState },
|
|
{ id: "forged-name-candidate-fails-closed", expected: false, item: { ...verifiedRun, metadata: { ...verifiedRun.metadata, annotations: { ...verifiedRun.metadata.annotations, [provenance.markerAnnotation]: "forged" } } }, state: admissionState },
|
|
{ id: "forged-label-candidate-fails-closed", expected: false, item: { ...verifiedRun, metadata: { ...verifiedRun.metadata, name: "unrelated", labels: { ...verifiedRun.metadata.labels, "tekton.dev/pipeline": consumer.pipeline }, annotations: { ...verifiedRun.metadata.annotations, [provenance.markerAnnotation]: "forged" } } }, state: admissionState },
|
|
{ id: "forged-pipeline-ref-candidate-fails-closed", expected: false, item: { ...verifiedRun, metadata: { ...verifiedRun.metadata, name: "unrelated", annotations: { ...verifiedRun.metadata.annotations, [provenance.markerAnnotation]: "forged" } }, spec: { ...verifiedRun.spec, pipelineRef: { name: consumer.pipeline } } }, state: admissionState },
|
|
{ id: "wrong-service-account-fails-closed", expected: false, item: { ...verifiedRun, spec: { taskRunTemplate: { serviceAccountName: "default" } } }, state: admissionState },
|
|
{ id: "policy-identity-mismatch-fails-closed", expected: false, item: verifiedRun, state: { ...admissionState, configSha256: `sha256:${"2".repeat(64)}` } },
|
|
];
|
|
for (const item of classificationCases) {
|
|
const result = classifyPacPipelineRun({ ...consumer, admissionState: item.state }, item.item);
|
|
checks.push({
|
|
id: item.id,
|
|
ok: result.admissionProvenanceVerified === item.expected && result.deliveryAuthorityEligible === item.expected,
|
|
expectedOk: item.expected,
|
|
actualOk: result.deliveryAuthorityEligible,
|
|
expectedCode: item.expected ? "admission-owned-pac-push-provenance-verified" : "pac-push-metadata-observed-but-admission-provenance-unverified",
|
|
actualCode: result.reason,
|
|
});
|
|
}
|
|
return { ok: checks.every((item) => item.ok), checks, valuesPrinted: false };
|
|
}
|
|
|
|
module.exports = {
|
|
classifyPacPipelineRun,
|
|
evaluatePacAdmissionState,
|
|
evaluatePacStatus,
|
|
extractPacArtifactEvidence,
|
|
extractPacSourceObservation,
|
|
parsePacLogRecords,
|
|
runPacStatusFixtureChecks,
|
|
pipelineRunMatchesConsumer,
|
|
taskTerminalRecord,
|
|
};
|