fix: 统一 HWLAB PaC Pipeline provenance 合同

This commit is contained in:
Codex
2026-07-11 08:17:50 +02:00
parent 764ff398c3
commit 2f93a44115
13 changed files with 362 additions and 65 deletions
@@ -11,10 +11,12 @@ import { hwlabRuntimeLaneSpecForNode, isHwlabRuntimeLane, type HwlabRuntimeLaneS
import { nodeRuntimeGitopsRoot } from "./hwlab-node/cleanup";
import { nodeRuntimePipelinePostprocessScript } from "./hwlab-node/render";
import { nodeRuntimeRenderOverlay } from "./hwlab-node/web-probe";
import { hwlabRuntimePipelineProvenance } from "./hwlab-node/pipeline-provenance";
import { applyNodeRuntimeDeployYamlOverlay } from "./hwlab-node/deploy-overlay";
import type { RenderedCliResult } from "./output";
import { renderedCliResult } from "./agentrun/render";
import { stableJsonSha256, stableJsonValue } from "./stable-json";
import { pipelineProvenanceAnnotations, pipelineProvenanceFromManifest, withPipelineProvenanceAnnotations } from "./pipeline-provenance";
export type PacSourceArtifactMode = "embedded-pipeline-spec" | "remote-pipeline-annotation";
export type PacSourceArtifactRenderer = "agentrun-control-plane" | "hwlab-runtime-lane";
@@ -167,13 +169,6 @@ export function pacSourceArtifactSafeError(error: unknown): PacSourceArtifactSaf
};
}
const provenanceKeys = {
configRef: "unidesk.ai/owning-config-ref",
effectiveConfigSha256: "unidesk.ai/effective-config-sha256",
renderer: "unidesk.ai/source-artifact-renderer",
mode: "unidesk.ai/source-artifact-mode",
} as const;
const knownDriftPathKeys = new Set([
"accessModes", "annotations", "apiVersion", "args", "command", "computeResources", "default", "description", "dnsPolicy", "env", "envFrom",
"executionMode", "finally", "fsGroup", "generateName", "hostNetwork", "image", "kind", "labels", "length", "metadata", "mode", "mountPath", "name", "namespace", "onError", "operator",
@@ -442,7 +437,12 @@ function renderDesiredArtifact(binding: PacSourceArtifactBinding, sourceWorktree
const rendered = sourceArtifact.renderer === "agentrun-control-plane"
? renderAgentRunDesiredPipeline(binding)
: renderHwlabDesiredPipeline(binding, sourceWorktree);
const pipeline = withPipelineProvenance(rendered.pipeline, rendered.provenance);
const pipeline = sourceArtifact.renderer === "hwlab-runtime-lane"
? rendered.pipeline
: withPipelineProvenanceAnnotations(rendered.pipeline, rendered.provenance);
if (!provenanceEquals(provenanceFromManifest(pipeline), rendered.provenance)) {
throw new Error(`${sourceArtifact.renderer} did not render the declared Pipeline provenance contract`);
}
const desiredSpec = requiredRecord(pipeline.spec, "rendered Pipeline.spec");
assertPipelineIdentity(pipeline, binding);
const pipelineRun = sourceArtifact.mode === "embedded-pipeline-spec"
@@ -485,8 +485,11 @@ function renderAgentRunDesiredPipeline(binding: PacSourceArtifactBinding): { pip
function renderHwlabDesiredPipeline(binding: PacSourceArtifactBinding, sourceWorktree: string): { pipeline: Record<string, unknown>; provenance: PacSourceArtifactProvenance } {
if (!isHwlabRuntimeLane(binding.consumer.lane)) throw new Error(`HWLAB source artifact lane ${binding.consumer.lane} is not declared`);
const spec = hwlabRuntimeLaneSpecForNode(binding.consumer.lane, binding.consumer.node);
const expectedConfigRef = `config/hwlab-node-lanes.yaml#lanes.${spec.lane}.targets.${spec.nodeId}`;
assertConfigRef(binding.consumer.sourceArtifact.configRef, expectedConfigRef);
const provenance = hwlabRuntimePipelineProvenance(spec);
if (binding.consumer.sourceArtifact.renderer !== provenance.renderer || binding.consumer.sourceArtifact.mode !== provenance.mode) {
throw new Error(`sourceArtifact renderer/mode must match ${provenance.configRef}.pipelineProvenance`);
}
assertConfigRef(binding.consumer.sourceArtifact.configRef, provenance.configRef);
assertBindingIdentity(binding, {
node: spec.nodeId,
lane: spec.lane,
@@ -500,12 +503,7 @@ function renderHwlabDesiredPipeline(binding: PacSourceArtifactBinding, sourceWor
const pipeline = renderHwlabPipelineFromOwningYaml(spec, sourceWorktree);
return {
pipeline,
provenance: {
configRef: expectedConfigRef,
effectiveConfigSha256: stableJsonSha256(spec),
renderer: "hwlab-runtime-lane",
mode: binding.consumer.sourceArtifact.mode,
},
provenance,
};
}
@@ -547,7 +545,7 @@ function renderHwlabPipelineFromOwningYaml(spec: HwlabRuntimeLaneSpec, sourceWor
`overlay_b64=${shellQuote(overlay)}`,
...nodeRuntimePipelinePostprocessScript(),
].join("\n");
runChecked("sh", ["-c", postprocess], temporarySource, 120_000, "HWLAB UniDesk domain postprocess/verify renderer");
runChecked("sh", [], temporarySource, 120_000, "HWLAB UniDesk domain postprocess/verify renderer", postprocess);
const path = resolve(output, spec.tektonDir, "pipeline.yaml");
if (!existsSync(path)) throw new Error(`HWLAB domain renderer did not produce ${path}`);
return parseYamlRecord(readFileSync(path, "utf8"), path);
@@ -637,10 +635,7 @@ function pipelineRunAnnotations(binding: PacSourceArtifactBinding, provenance: P
"pipelinesascode.tekton.dev/on-target-branch": `[${branch}]`,
"pipelinesascode.tekton.dev/on-cel-expression": `event == 'push' && target_branch == '${branch}' && node == '${binding.consumer.node}'`,
"pipelinesascode.tekton.dev/max-keep-runs": String(binding.consumer.sourceArtifact.maxKeepRuns),
[provenanceKeys.configRef]: provenance.configRef,
[provenanceKeys.effectiveConfigSha256]: provenance.effectiveConfigSha256,
[provenanceKeys.renderer]: provenance.renderer,
[provenanceKeys.mode]: binding.consumer.sourceArtifact.mode,
...pipelineProvenanceAnnotations(provenance),
};
}
@@ -745,20 +740,6 @@ function pipelineRunWorkspaces(binding: PacSourceArtifactBinding, desiredSpec: R
});
}
function withPipelineProvenance(pipeline: Record<string, unknown>, provenance: PacSourceArtifactProvenance): Record<string, unknown> {
const next = structuredClone(pipeline);
const metadata = requiredRecord(next.metadata, "rendered Pipeline.metadata");
const annotations = isRecord(metadata.annotations) ? metadata.annotations : {};
metadata.annotations = {
...annotations,
[provenanceKeys.configRef]: provenance.configRef,
[provenanceKeys.effectiveConfigSha256]: provenance.effectiveConfigSha256,
[provenanceKeys.renderer]: provenance.renderer,
[provenanceKeys.mode]: provenance.mode,
};
return next;
}
function inspectSourceArtifact(sourceWorktree: string, binding: PacSourceArtifactBinding, desired: DesiredArtifact): SourceInspection {
const sourceArtifact = binding.consumer.sourceArtifact;
const pipelineRunPath = safeArtifactPath(sourceWorktree, sourceArtifact.pipelineRunPath);
@@ -932,14 +913,11 @@ function assertPipelineIdentity(pipeline: Record<string, unknown>, binding: PacS
}
function provenanceFromManifest(manifest: Record<string, unknown>): PacSourceArtifactProvenance | null {
const metadata = isRecord(manifest.metadata) ? manifest.metadata : {};
const annotations = isRecord(metadata.annotations) ? metadata.annotations : {};
const configRef = annotations[provenanceKeys.configRef];
const effectiveConfigSha256 = annotations[provenanceKeys.effectiveConfigSha256];
const renderer = annotations[provenanceKeys.renderer];
const mode = annotations[provenanceKeys.mode];
if (typeof configRef !== "string" || typeof effectiveConfigSha256 !== "string" || (renderer !== "agentrun-control-plane" && renderer !== "hwlab-runtime-lane") || (mode !== "embedded-pipeline-spec" && mode !== "remote-pipeline-annotation")) return null;
return { configRef, effectiveConfigSha256, renderer, mode };
const provenance = pipelineProvenanceFromManifest(manifest);
if (provenance === null) return null;
if (provenance.renderer !== "agentrun-control-plane" && provenance.renderer !== "hwlab-runtime-lane") return null;
if (provenance.mode !== "embedded-pipeline-spec" && provenance.mode !== "remote-pipeline-annotation") return null;
return provenance as PacSourceArtifactProvenance;
}
function provenanceEquals(actual: PacSourceArtifactProvenance | null, expected: PacSourceArtifactProvenance): boolean {
@@ -1098,9 +1076,13 @@ function git(worktree: string, args: readonly string[]): string {
return result.stdout.trim();
}
function runChecked(command: string, args: readonly string[], cwd: string, timeout: number, label: string): void {
const result = spawnSync(command, [...args], { cwd, encoding: "utf8", timeout, maxBuffer: 32 * 1024 * 1024 });
if (result.status !== 0) throw new Error(`${label} failed: ${(result.stderr || result.stdout).trim().slice(-4000)}`);
function runChecked(command: string, args: readonly string[], cwd: string, timeout: number, label: string, input?: string): void {
const result = spawnSync(command, [...args], { cwd, encoding: "utf8", timeout, maxBuffer: 32 * 1024 * 1024, input });
if (result.status !== 0) {
const output = [result.stderr, result.stdout].find((value) => typeof value === "string" && value.trim().length > 0)?.trim();
const reason = output ?? result.error?.message ?? `exit=${String(result.status)} signal=${String(result.signal)}`;
throw new Error(`${label} failed: ${reason.slice(-4000)}`);
}
}
function shellQuote(value: string): string {