@kungfu-tech/buildchain 4.0.9-alpha.0 → 4.0.9-alpha.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env node
2
+ import { createHash } from "node:crypto";
2
3
 
3
4
  const DEFAULTS = {
4
5
  buildWorkflowFile: "self-build-fixture.yml",
@@ -41,27 +42,49 @@ function optionalSha(value, label) {
41
42
  return normalized;
42
43
  }
43
44
 
44
- export function resolveStableCandidateQualificationCandidate({ eventName, inputCandidateSha = "", selfDogfoodEvidence } = {}) {
45
- if (text(eventName) === "workflow_dispatch") {
46
- return optionalSha(inputCandidateSha, "candidate SHA");
45
+ export function validatePublicBuildRun(run, workflow, repositoryName) {
46
+ if (run?.repository?.full_name !== repositoryName || run?.head_repository?.full_name !== repositoryName
47
+ || run?.workflow_id !== workflow?.id || workflow?.path !== ".github/workflows/self-build-alpha-dogfood.yml"
48
+ || run?.name !== "Buildchain Alpha Self-Dogfood" || !successful(run) || !optionalSha(run?.head_sha, "source SHA")) {
49
+ throw new Error("qualification requires the exact successful repository-owned public build run");
47
50
  }
48
- if (text(eventName) !== "workflow_run") {
49
- throw new Error(`qualification candidate resolver does not admit event ${eventName || "<empty>"}`);
50
- }
51
- const evidence = selfDogfoodEvidence;
52
- if (
53
- evidence?.contract !== "kungfu-buildchain-alpha-self-dogfood"
54
- || evidence?.status !== "passed"
55
- || evidence?.observed?.alpha?.ref !== "v4-alpha"
56
- ) {
57
- throw new Error("self-dogfood evidence is not a passing v4-alpha observation");
51
+ return run;
52
+ }
53
+
54
+ export function resolveStableCandidateQualificationCandidate({ sourceRun, buildSummary, repositoryName } = {}) {
55
+ const evidence = buildSummary, runtime = evidence?.runtime;
56
+ if (!successful(sourceRun) || sourceRun?.repository?.full_name !== repositoryName
57
+ || evidence?.artifactName !== "buildchain" || evidence?.contract !== "kungfu-buildchain-build-summary" || evidence?.git?.repository !== repositoryName
58
+ || evidence?.git?.sha !== sourceRun?.head_sha || String(evidence?.git?.runId) !== String(sourceRun?.id)
59
+ || String(evidence?.git?.runAttempt) !== String(sourceRun?.run_attempt)
60
+ || runtime?.ref !== "v4-alpha" || runtime?.workflowShellRef !== "v4-alpha" || runtime?.class !== "alpha"
61
+ || runtime?.override !== false || runtime?.trustDecision !== "workflow-identity") {
62
+ throw new Error("public build summary does not bind the exact source run and alpha workflow identity");
58
63
  }
59
- const observedSha = optionalSha(evidence.observed.alpha.sha, "observed v4-alpha SHA");
60
- const expectedSha = optionalSha(evidence.observed.alpha.expectedSha, "expected v4-alpha SHA");
61
- if (!observedSha || observedSha !== expectedSha) {
62
- throw new Error("self-dogfood evidence does not bind observed and expected v4-alpha SHAs");
64
+ const platforms = evidence.platforms || [];
65
+ const ids = platforms.map((entry) => entry.platform?.id).sort();
66
+ if (evidence.platformCount !== 3 || JSON.stringify(ids) !== JSON.stringify(["linux-x64", "macos", "windows-x64"])
67
+ || platforms.some((entry) => entry.expectedArtifacts?.ok !== true || !/^[a-f0-9]{64}$/u.test(entry.summary?.digest || "")
68
+ || ["install", "build", "verify"].some((stage) => !(entry.observability?.lifecycle?.stages?.[stage]?.eventCount > 0)))) {
69
+ throw new Error("public build summary requires verified artifacts for all three platforms");
63
70
  }
64
- return observedSha;
71
+ const sha = optionalSha(runtime.sha, "observed alpha SHA");
72
+ if (!sha) throw new Error("public build summary is missing its runtime SHA");
73
+ return sha;
74
+ }
75
+
76
+ export async function qualifyPublicBuild({ repositoryName, sourceRun, buildSummary }, client) {
77
+ const sha = resolveStableCandidateQualificationCandidate({ repositoryName, sourceRun, buildSummary });
78
+ const candidate = await client.resolveExactAlpha(repositoryName, sha);
79
+ if (!candidate || candidate.sha !== sha) throw new Error("observed runtime must be an exact published alpha, never an ancestor");
80
+ const context = "buildchain-canary/buildchain-zero-input";
81
+ const status = await client.createCommitStatus({ repository: repositoryName, sha, context,
82
+ targetUrl: sourceRun.html_url, description: "Zero-input public build passed on all three platforms" });
83
+ if (status.state !== "success") throw new Error("public build qualification status readback failed");
84
+ return { contract: "kungfu-buildchain-public-build-qualification/v1", candidate, sourceSha: sourceRun.head_sha,
85
+ runId: sourceRun.id, runAttempt: sourceRun.run_attempt, context, status: status.state,
86
+ summaryRoot: `sha256:${createHash("sha256").update(JSON.stringify(buildSummary)).digest("hex")}`,
87
+ artifacts: buildSummary.platforms.map((entry) => ({ platform: entry.platform.id, digest: entry.summary.digest })) };
65
88
  }
66
89
 
67
90
  function optionalRef(value, label) {
@@ -253,6 +276,12 @@ export function createGitHubQualificationClient({
253
276
  .sort((left, right) => String(right.created_at).localeCompare(String(left.created_at)))[0];
254
277
  }
255
278
  return {
279
+ async readPublicBuildRun(repositoryName, runId) {
280
+ if (!/^[1-9][0-9]*$/u.test(String(runId))) throw new Error("source run ID must be a positive integer");
281
+ const run = await api(`/repos/${repositoryName}/actions/runs/${runId}`);
282
+ const workflow = await api(`/repos/${repositoryName}/actions/workflows/self-build-alpha-dogfood.yml`);
283
+ return validatePublicBuildRun(run, workflow, repositoryName);
284
+ },
256
285
  async resolveExactAlpha(repositoryName, candidateSha) {
257
286
  const releases = (await api(`/repos/${repositoryName}/releases?per_page=100`)).sort((left, right) => right.tag_name.localeCompare(left.tag_name, "en", { numeric: true }));
258
287
  for (const release of releases) {
@@ -297,22 +326,18 @@ export function createGitHubQualificationClient({
297
326
  }
298
327
 
299
328
  async function main() {
300
- if (bool(process.env.BUILDCHAIN_QUALIFICATION_RESOLVE_CANDIDATE, false)) {
301
- let selfDogfoodEvidence;
302
- if (text(process.env.BUILDCHAIN_QUALIFICATION_EVENT_NAME) === "workflow_run") {
303
- const fs = await import("node:fs");
304
- const evidencePath = text(process.env.BUILDCHAIN_QUALIFICATION_SELF_DOGFOOD_EVIDENCE);
305
- selfDogfoodEvidence = JSON.parse(fs.readFileSync(evidencePath, "utf8"));
306
- }
307
- const sha = resolveStableCandidateQualificationCandidate({
308
- eventName: process.env.BUILDCHAIN_QUALIFICATION_EVENT_NAME,
309
- inputCandidateSha: process.env.BUILDCHAIN_QUALIFICATION_INPUT_CANDIDATE_SHA,
310
- selfDogfoodEvidence,
311
- });
312
- process.stdout.write(`${sha}\n`);
313
- if (process.env.GITHUB_OUTPUT) {
314
- const fs = await import("node:fs");
315
- fs.appendFileSync(process.env.GITHUB_OUTPUT, `sha=${sha}\n`);
329
+ if (["resolve-public-build", "qualify-public-build"].includes(process.argv[2])) {
330
+ const fs = await import("node:fs");
331
+ const repositoryName = repository(process.env.GITHUB_REPOSITORY);
332
+ const client = createGitHubQualificationClient({ token: process.env.GITHUB_TOKEN });
333
+ const sourceRun = await client.readPublicBuildRun(repositoryName, process.env.BUILDCHAIN_QUALIFICATION_RUN_ID);
334
+ if (process.argv[2] === "resolve-public-build") {
335
+ fs.appendFileSync(process.env.GITHUB_OUTPUT, `artifact-name=buildchain-summary-${sourceRun.head_sha}\n`);
336
+ } else {
337
+ const buildSummary = JSON.parse(fs.readFileSync(".buildchain/qualification/source/build-summary.json", "utf8"));
338
+ const result = await qualifyPublicBuild({ repositoryName, sourceRun, buildSummary }, client);
339
+ fs.writeFileSync(".buildchain/qualification/result.json", JSON.stringify(result, null, 2) + "\n");
340
+ process.stdout.write(JSON.stringify(result) + "\n");
316
341
  }
317
342
  return;
318
343
  }