@kungfu-tech/buildchain 3.0.2-alpha.0 → 3.0.2-alpha.10

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.
Files changed (83) hide show
  1. package/README.md +4 -2
  2. package/actions/github-artifact-attestation/README.md +10 -0
  3. package/actions/promote-buildchain-ref/README.md +7 -0
  4. package/bin/buildchain.mjs +5 -0
  5. package/bin/internal/trust-release-cli.mjs +74 -3
  6. package/contracts/auditable-demo-media-profiles-v1.json +168 -0
  7. package/contracts/evidence/auditable-demo-web-delivery-v1.json +103 -0
  8. package/contracts/fixtures/auditable-demo-web-delivery-v1/complete-transcript.txt +2 -0
  9. package/contracts/fixtures/auditable-demo-web-delivery-v1/public-projection.json +16 -0
  10. package/contracts/fixtures/auditable-demo-web-delivery-v1/scene.json +12 -0
  11. package/dist/site/artifact-schemas.json +5 -1
  12. package/dist/site/buildchain-contract.json +133 -34
  13. package/dist/site/buildchain-site.json +159 -40
  14. package/dist/site/capability-registry.json +14 -13
  15. package/dist/site/cli-registry.json +12 -0
  16. package/dist/site/controller-registry.json +72 -4
  17. package/dist/site/kfd-claims.json +338 -20
  18. package/dist/site/kfd-upstream-aggregate.json +1 -1
  19. package/dist/site/manual-registry.json +24 -9
  20. package/dist/site/node-api-registry.json +89 -11
  21. package/dist/site/page-registry.json +135 -25
  22. package/dist/site/public-surface-audit.json +203 -21
  23. package/dist/site/publication-authority-registry.json +72 -2
  24. package/dist/site/publication-registry.json +4 -4
  25. package/dist/site/release-model.json +2 -1
  26. package/dist/site/release-passport-check-manifest.json +1 -0
  27. package/dist/site/release-provenance.json +6 -0
  28. package/dist/site/schemas/release-passport-v1.schema.json +6 -0
  29. package/dist/site/site-manifest.json +21 -13
  30. package/dist/site/workflow-registry.json +167 -13
  31. package/docs/MAP.md +5 -1
  32. package/docs/auditable-demo.md +55 -3
  33. package/docs/binary-distribution.md +7 -0
  34. package/docs/cli.md +9 -0
  35. package/docs/dev-alpha-candidate-patrol.md +111 -0
  36. package/docs/github-artifact-attestation.md +219 -0
  37. package/docs/release-governance.md +32 -0
  38. package/docs/release-passport.md +13 -0
  39. package/docs/reusable-build-surface.md +83 -61
  40. package/docs/runtime-train-validation.md +21 -0
  41. package/docs/versioning.md +1 -0
  42. package/package.json +8 -1
  43. package/packages/core/artifact-signing-result.js +228 -0
  44. package/packages/core/artifact-signing.js +412 -0
  45. package/packages/core/buildchain-config.js +58 -0
  46. package/packages/core/buildchain-contract.js +14 -0
  47. package/packages/core/buildchain-kfd-claims.js +5 -0
  48. package/packages/core/buildchain-publication-authority.js +3 -0
  49. package/packages/core/cache-evidence.js +288 -0
  50. package/packages/core/channel-candidate.js +186 -0
  51. package/packages/core/detached-artifact-signature.js +121 -0
  52. package/packages/core/diagnostics.js +276 -10
  53. package/packages/core/github-artifact-attestation.js +642 -0
  54. package/packages/core/github-governance-authority.js +77 -16
  55. package/packages/core/index.js +62 -0
  56. package/packages/core/publication-authority.js +1 -1
  57. package/packages/core/release-passport-contract.js +2 -0
  58. package/packages/core/release-passport.js +60 -3
  59. package/scripts/auditable-demo.mjs +520 -28
  60. package/scripts/build-contract-core.mjs +31 -0
  61. package/scripts/buildchain-channel-router.mjs +8 -2
  62. package/scripts/check-inventory.mjs +9 -0
  63. package/scripts/create-github-artifact-attestation-policy.mjs +62 -0
  64. package/scripts/dev-alpha-candidate-patrol.mjs +864 -0
  65. package/scripts/dispatch-artifact-signing-authority.mjs +152 -0
  66. package/scripts/finalize-native-artifact-signing-result.mjs +96 -0
  67. package/scripts/generate-channel-promotion-workflow.mjs +6 -0
  68. package/scripts/generate-site-bundle.mjs +20 -0
  69. package/scripts/import-artifact-signing-results.mjs +76 -0
  70. package/scripts/inspect-artifact-signing-requests.mjs +101 -0
  71. package/scripts/locked-source-checkout.mjs +48 -0
  72. package/scripts/materialize-artifact-signing-request.mjs +66 -0
  73. package/scripts/merge-artifact-signing-results.mjs +76 -0
  74. package/scripts/publish-github-artifact-attestation-evidence.mjs +201 -0
  75. package/scripts/reconcile-github-governance.mjs +158 -25
  76. package/scripts/release-candidate-resolver.mjs +12 -0
  77. package/scripts/release-line-policy.mjs +27 -0
  78. package/scripts/runtime-ref-core.mjs +10 -6
  79. package/scripts/seal-artifact-signing-requests.mjs +368 -0
  80. package/scripts/shifu-gate-profile.mjs +26 -20
  81. package/scripts/sign-detached-artifact-requests.mjs +237 -0
  82. package/scripts/stage-github-artifact-attestation-inputs.mjs +65 -0
  83. package/scripts/verify-artifact-signing-results.mjs +99 -0
@@ -0,0 +1,152 @@
1
+ #!/usr/bin/env node
2
+ import { setTimeout as delay } from "node:timers/promises";
3
+ import { pathToFileURL } from "node:url";
4
+
5
+ import { writeGitHubOutputs } from "./build-contract-core.mjs";
6
+
7
+ function required(value, label) {
8
+ const normalized = String(value || "").trim();
9
+ if (!normalized) throw new Error(`${label} is required`);
10
+ return normalized;
11
+ }
12
+
13
+ function repository(value, label) {
14
+ const normalized = required(value, label);
15
+ if (!/^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/u.test(normalized)) throw new Error(`${label} must be owner/repository`);
16
+ return normalized;
17
+ }
18
+
19
+ const TRANSIENT_GITHUB_STATUSES = new Set([408, 429, 500, 502, 503, 504]);
20
+
21
+ function retryDelayMs(attempt) {
22
+ return Math.min(1_000 * (2 ** (attempt - 1)), 10_000);
23
+ }
24
+
25
+ export async function githubRequest(url, {
26
+ token,
27
+ method = "GET",
28
+ body,
29
+ fetchImpl = fetch,
30
+ delayImpl = delay,
31
+ maxAttempts = 5,
32
+ warnImpl = console.warn,
33
+ } = {}) {
34
+ const methodName = String(method).toUpperCase();
35
+ const retrySafe = methodName === "GET";
36
+ const requestedAttempts = Number(maxAttempts);
37
+ const attemptLimit = retrySafe && Number.isSafeInteger(requestedAttempts) && requestedAttempts > 0
38
+ ? requestedAttempts
39
+ : 1;
40
+ for (let attempt = 1; attempt <= attemptLimit; attempt += 1) {
41
+ let response;
42
+ try {
43
+ response = await fetchImpl(`https://api.github.com${url}`, {
44
+ method: methodName,
45
+ headers: {
46
+ Accept: "application/vnd.github+json",
47
+ Authorization: `Bearer ${token}`,
48
+ "X-GitHub-Api-Version": "2022-11-28",
49
+ "User-Agent": "kungfu-buildchain-artifact-signing",
50
+ ...(body ? { "Content-Type": "application/json" } : {}),
51
+ },
52
+ ...(body ? { body: JSON.stringify(body) } : {}),
53
+ });
54
+ } catch (error) {
55
+ if (retrySafe && attempt < attemptLimit) {
56
+ warnImpl(`Buildchain signing authority: GitHub API GET transport failure; retry ${attempt + 1}/${attemptLimit}`);
57
+ await delayImpl(retryDelayMs(attempt));
58
+ continue;
59
+ }
60
+ throw error;
61
+ }
62
+ if (!response.ok) {
63
+ const text = await response.text();
64
+ if (retrySafe && TRANSIENT_GITHUB_STATUSES.has(response.status) && attempt < attemptLimit) {
65
+ warnImpl(`Buildchain signing authority: GitHub API GET returned ${response.status}; retry ${attempt + 1}/${attemptLimit}`);
66
+ await delayImpl(retryDelayMs(attempt));
67
+ continue;
68
+ }
69
+ throw new Error(`GitHub API ${methodName} ${url} failed (${response.status}): ${text.slice(0, 500)}`);
70
+ }
71
+ if (response.status === 204) return {};
72
+ try {
73
+ return await response.json();
74
+ } catch (error) {
75
+ if (retrySafe && attempt < attemptLimit) {
76
+ warnImpl(`Buildchain signing authority: GitHub API GET response failed to decode; retry ${attempt + 1}/${attemptLimit}`);
77
+ await delayImpl(retryDelayMs(attempt));
78
+ continue;
79
+ }
80
+ throw error;
81
+ }
82
+ }
83
+ throw new Error("GitHub API retry loop exhausted");
84
+ }
85
+
86
+ export async function dispatchArtifactSigningAuthority({
87
+ token = process.env.BUILDCHAIN_AUTHORITY_DISPATCH_TOKEN,
88
+ authorityRepository = process.env.BUILDCHAIN_AUTHORITY_REPOSITORY || "kungfu-systems/buildchain",
89
+ authorityRef = process.env.BUILDCHAIN_AUTHORITY_REF,
90
+ sourceRepository = process.env.GITHUB_REPOSITORY,
91
+ sourceRunId = process.env.GITHUB_RUN_ID,
92
+ sourceRunAttempt = process.env.GITHUB_RUN_ATTEMPT || "1",
93
+ requestArtifact = process.env.BUILDCHAIN_SIGNING_REQUEST_ARTIFACT,
94
+ runtimeSha = process.env.BUILDCHAIN_RUNTIME_SHA,
95
+ resultArtifact = process.env.BUILDCHAIN_SIGNING_RESULT_ARTIFACT,
96
+ timeoutSeconds = process.env.BUILDCHAIN_SIGNING_TIMEOUT_SECONDS || "7200",
97
+ } = {}) {
98
+ const authToken = required(token, "Buildchain authority dispatch token");
99
+ const authorityRepo = repository(authorityRepository, "authority repository");
100
+ const sourceRepo = repository(sourceRepository, "source repository");
101
+ const ref = required(authorityRef, "authority ref");
102
+ const runtime = required(runtimeSha, "Buildchain runtime SHA");
103
+ if (!/^[0-9a-f]{40}$/u.test(runtime)) throw new Error("Buildchain runtime SHA must be exact");
104
+ const correlationId = `${required(sourceRunId, "source run ID")}-${required(sourceRunAttempt, "source run attempt")}-${runtime.slice(0, 12)}-${required(requestArtifact, "request artifact").replace(/[^A-Za-z0-9._-]+/gu, "-")}`;
105
+ const resultName = required(resultArtifact, "result artifact");
106
+ const workflow = "artifact-signing-authority.yml";
107
+ const startedAt = Date.now() - 30_000;
108
+ await githubRequest(`/repos/${authorityRepo}/actions/workflows/${workflow}/dispatches`, {
109
+ token: authToken,
110
+ method: "POST",
111
+ body: {
112
+ ref,
113
+ inputs: {
114
+ "source-repository": sourceRepo,
115
+ "source-run-id": String(sourceRunId),
116
+ "request-artifact-pattern": required(requestArtifact, "request artifact"),
117
+ "result-artifact-name": resultName,
118
+ "correlation-id": correlationId,
119
+ "expected-runtime-sha": runtime,
120
+ },
121
+ },
122
+ });
123
+ const deadline = Date.now() + Number(timeoutSeconds) * 1000;
124
+ let run;
125
+ while (Date.now() < deadline) {
126
+ const response = await githubRequest(`/repos/${authorityRepo}/actions/workflows/${workflow}/runs?event=workflow_dispatch&per_page=50`, { token: authToken });
127
+ run = (response.workflow_runs || []).find((entry) =>
128
+ new Date(entry.created_at).getTime() >= startedAt &&
129
+ String(entry.display_title || "").includes(correlationId),
130
+ );
131
+ if (run?.status === "completed") break;
132
+ await delay(10_000);
133
+ }
134
+ if (!run || run.status !== "completed") throw new Error("timed out waiting for Buildchain signing authority");
135
+ if (run.conclusion !== "success") throw new Error(`Buildchain signing authority failed: ${run.html_url}`);
136
+ writeGitHubOutputs({
137
+ "authority-run-id": String(run.id),
138
+ "authority-run-url": run.html_url,
139
+ "result-artifact": resultName,
140
+ "correlation-id": correlationId,
141
+ });
142
+ return { runId: run.id, runUrl: run.html_url, resultArtifact: resultName, correlationId };
143
+ }
144
+
145
+ if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
146
+ try {
147
+ await dispatchArtifactSigningAuthority();
148
+ } catch (error) {
149
+ console.error(`::error::${String(error?.message || error).replace(/\r?\n/gu, "%0A")}`);
150
+ process.exitCode = 1;
151
+ }
152
+ }
@@ -0,0 +1,96 @@
1
+ #!/usr/bin/env node
2
+ import crypto from "node:crypto";
3
+ import fs from "node:fs";
4
+ import path from "node:path";
5
+ import { pathToFileURL } from "node:url";
6
+
7
+ import { createArtifactSigningReceipt, validateArtifactSigningRequest } from "../packages/core/artifact-signing.js";
8
+ import { artifactSigningEvidenceDigest, createArtifactSigningResult } from "../packages/core/artifact-signing-result.js";
9
+ import { writeGitHubOutputs } from "./build-contract-core.mjs";
10
+
11
+ function required(value, label) {
12
+ const normalized = String(value || "").trim();
13
+ if (!normalized) throw new Error(`${label} is required`);
14
+ return normalized;
15
+ }
16
+
17
+ function sha256File(filePath) {
18
+ return `sha256:${crypto.createHash("sha256").update(fs.readFileSync(filePath)).digest("hex")}`;
19
+ }
20
+
21
+ function resolveBelow(root, relative, label) {
22
+ const target = path.resolve(root, required(relative, label));
23
+ const rel = path.relative(path.resolve(root), target);
24
+ if (rel.startsWith("..") || path.isAbsolute(rel)) throw new Error(`${label} escapes its root`);
25
+ return target;
26
+ }
27
+
28
+ export function finalizeNativeArtifactSigningResult({
29
+ requestRoot = process.env.BUILDCHAIN_SIGNING_REQUEST_ROOT,
30
+ requestPath = process.env.BUILDCHAIN_SIGNING_REQUEST_PATH,
31
+ signedPayload = process.env.BUILDCHAIN_SIGNED_PAYLOAD,
32
+ evidencePath = process.env.BUILDCHAIN_SIGNING_EVIDENCE,
33
+ outputRoot = process.env.BUILDCHAIN_SIGNING_RESULT_ROOT,
34
+ checks = process.env.BUILDCHAIN_SIGNING_VERIFICATION_CHECKS,
35
+ } = {}) {
36
+ const requests = path.resolve(required(requestRoot, "signing request root"));
37
+ const request = JSON.parse(fs.readFileSync(resolveBelow(requests, requestPath, "request path"), "utf8"));
38
+ const requestCheck = validateArtifactSigningRequest(request);
39
+ if (!requestCheck.ok) throw new Error(`invalid signing request: ${requestCheck.issues.join(", ")}`);
40
+ if (request.signature.semantics !== "native-platform-signature") {
41
+ throw new Error("native result finalizer rejects non-native signature profiles");
42
+ }
43
+ const payloadSource = path.resolve(required(signedPayload, "signed payload"));
44
+ const evidenceSource = path.resolve(required(evidencePath, "signing evidence"));
45
+ const resultDirectory = path.resolve(required(outputRoot, "signing result root"));
46
+ fs.mkdirSync(path.join(resultDirectory, "payload"), { recursive: true });
47
+ const payloadPath = path.join(resultDirectory, "payload", path.basename(payloadSource));
48
+ fs.copyFileSync(payloadSource, payloadPath, fs.constants.COPYFILE_EXCL);
49
+ const evidenceOutput = path.join(resultDirectory, "provider-evidence.json");
50
+ fs.copyFileSync(evidenceSource, evidenceOutput, fs.constants.COPYFILE_EXCL);
51
+ const evidenceDocument = JSON.parse(fs.readFileSync(evidenceOutput, "utf8"));
52
+ if (evidenceDocument.status !== "passed" || evidenceDocument.provider !== request.signature.provider) {
53
+ throw new Error("provider evidence does not prove the requested native signature");
54
+ }
55
+ const evidence = [{ kind: `${request.signature.profile}-verification`, path: "provider-evidence.json", digest: sha256File(evidenceOutput) }];
56
+ const payloadDigest = sha256File(payloadPath);
57
+ const receipt = createArtifactSigningReceipt({
58
+ request,
59
+ result: { artifactDigest: payloadDigest, evidenceDigest: artifactSigningEvidenceDigest(evidence) },
60
+ signatures: [{ kind: request.signature.profile, digest: evidence[0].digest }],
61
+ });
62
+ fs.writeFileSync(path.join(resultDirectory, "receipt.json"), `${JSON.stringify(receipt, null, 2)}\n`);
63
+ const verificationChecks = String(checks || "").split(",").map((value) => value.trim()).filter(Boolean);
64
+ const result = createArtifactSigningResult({
65
+ request,
66
+ receipt,
67
+ payload: { path: `payload/${path.basename(payloadPath)}`, bytes: fs.statSync(payloadPath).size, digest: payloadDigest },
68
+ evidence,
69
+ verification: { status: "passed", provider: request.signature.provider, checks: verificationChecks },
70
+ });
71
+ fs.writeFileSync(path.join(resultDirectory, "result.json"), `${JSON.stringify(result, null, 2)}\n`);
72
+ const index = {
73
+ schemaVersion: 1,
74
+ contract: "kungfu-buildchain-artifact-signing-result-index/v1",
75
+ results: [{
76
+ id: request.artifact.id,
77
+ requestDigest: request.digest,
78
+ resultDigest: result.digest,
79
+ result: "result.json",
80
+ payload: result.artifact.path,
81
+ receipt: "receipt.json",
82
+ }],
83
+ };
84
+ fs.writeFileSync(path.join(resultDirectory, "index.json"), `${JSON.stringify(index, null, 2)}\n`);
85
+ writeGitHubOutputs({ "result-root": resultDirectory, "result-index": path.join(resultDirectory, "index.json") });
86
+ return index;
87
+ }
88
+
89
+ if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
90
+ try {
91
+ finalizeNativeArtifactSigningResult();
92
+ } catch (error) {
93
+ console.error(`::error::${String(error?.message || error).replace(/\r?\n/gu, "%0A")}`);
94
+ process.exitCode = 1;
95
+ }
96
+ }
@@ -205,6 +205,8 @@ ${publicOutputs(outputs)}
205
205
 
206
206
  permissions:
207
207
  actions: write
208
+ artifact-metadata: write
209
+ attestations: write
208
210
  checks: write
209
211
  contents: write
210
212
  id-token: write
@@ -406,6 +408,8 @@ jobs:
406
408
  uses: kungfu-systems/buildchain/${alphaRoute.workflowPath}@${alphaRoute.callRef}
407
409
  permissions:
408
410
  actions: write
411
+ artifact-metadata: write
412
+ attestations: write
409
413
  checks: write
410
414
  contents: write
411
415
  id-token: write
@@ -422,6 +426,8 @@ ${alphaForwarded}
422
426
  uses: kungfu-systems/buildchain/${stableRoute.workflowPath}@${stableRoute.callRef}
423
427
  permissions:
424
428
  actions: write
429
+ artifact-metadata: write
430
+ attestations: write
425
431
  checks: write
426
432
  contents: write
427
433
  id-token: write
@@ -328,6 +328,7 @@ const manualMetaById = new Map(Object.entries({
328
328
  "product-mechanism": { capabilityGroup: "getting-started", audience: ["agent", "maintainer"], maturity: "stable", order: 30 },
329
329
  cli: { capabilityGroup: "api-cli-reference", audience: ["agent", "developer"], maturity: "stable", order: 40 },
330
330
  "release-passport": { capabilityGroup: "release-passport-trust", audience: ["release-operator", "agent"], maturity: "stable", order: 100 },
331
+ "github-artifact-attestation": { capabilityGroup: "release-passport-trust", audience: ["release-operator", "agent"], maturity: "preview", order: 108 },
331
332
  "publication-authority": { capabilityGroup: "release-passport-trust", audience: ["release-operator", "agent"], maturity: "preview", order: 105 },
332
333
  "github-governance-authority": { capabilityGroup: "governance-versioning", audience: ["maintainer", "release-operator", "agent"], maturity: "preview", order: 106 },
333
334
  "controller-evidence": { capabilityGroup: "reusable-build", audience: ["consumer", "release-operator", "agent"], maturity: "draft", order: 205 },
@@ -337,6 +338,7 @@ const manualMetaById = new Map(Object.entries({
337
338
  "release-activation-transaction": { capabilityGroup: "release-passport-trust", audience: ["release-operator", "agent"], maturity: "preview", order: 125 },
338
339
  "release-candidate": { capabilityGroup: "reusable-build", audience: ["release-operator", "consumer"], maturity: "stable", order: 130 },
339
340
  "stable-candidate-patrol": { capabilityGroup: "governance-versioning", audience: ["release-operator", "consumer"], maturity: "preview", order: 135 },
341
+ "dev-alpha-candidate-patrol": { capabilityGroup: "governance-versioning", audience: ["release-operator", "consumer", "agent"], maturity: "preview", order: 137 },
340
342
  "observed-evidence-patrol": { capabilityGroup: "governance-versioning", audience: ["release-operator", "consumer", "agent"], maturity: "preview", order: 140 },
341
343
  "reusable-build-surface": { capabilityGroup: "reusable-build", audience: ["consumer", "release-operator"], maturity: "stable", order: 200 },
342
344
  "lifecycle-protocol": { capabilityGroup: "reusable-build", audience: ["consumer", "developer"], maturity: "stable", order: 210 },
@@ -404,6 +406,7 @@ function cliCommandMeta(id) {
404
406
  "collect-github-release": { group: "release-passport-trust", purpose: "Collect GitHub Release assets into a release passport." },
405
407
  create: { group: "release-passport-trust", purpose: "Create canonical sealed publication evidence documents." },
406
408
  "create-publication-admission": { group: "release-passport-trust", purpose: "Create a canonical short-lived publication admission envelope from exact consumer bindings." },
409
+ "create-github-artifact-attestation-policy": { group: "release-passport-trust", purpose: "Create an exact source, signer, Linux build, and Release Passport attestation policy." },
407
410
  "create-runner-provenance": { group: "release-passport-trust", purpose: "Create runner provenance evidence with an explicit qualification floor." },
408
411
  diagnostics: { group: "observability-diagnostics", purpose: "Inspect diagnostics command families." },
409
412
  "diagnostics-summary": { group: "observability-diagnostics", purpose: "Summarize diagnostics artifacts into JSON and cross-platform lifecycle timing tables." },
@@ -493,6 +496,7 @@ function cliCommandMeta(id) {
493
496
  verify: { group: "release-passport-trust", purpose: "Inspect release and artifact verification command families." },
494
497
  "verify-artifact": { group: "release-passport-trust", purpose: "Verify artifact subjects against release passport evidence." },
495
498
  "verify-artifact-envelope": { group: "release-passport-trust", purpose: "Verify exact roots, identity, lifecycle, revocation, and an existing KFD assessment in a sealed artifact envelope." },
499
+ "verify-github-artifact-attestation": { group: "release-passport-trust", purpose: "Verify GitHub keyless attestation identity plus local artifact, manifest, Passport, predicate, bundle, and evidence bindings." },
496
500
  "verify-infra-contract-evidence-bundle": { group: "governance-versioning", purpose: "Fail closed unless an infra-contract lifecycle evidence bundle is complete, hash-bound, and validation-consistent." },
497
501
  "verify-observability-log": { group: "observability-diagnostics", purpose: "Verify Buildchain observability log events." },
498
502
  "verify-publication-admission": { group: "release-passport-trust", purpose: "Independently verify sealed publication admission, runner provenance, control-plane audit, nonce freshness, and exact artifact bindings." },
@@ -516,6 +520,8 @@ function nodeApiMeta(exportName) {
516
520
  "./homebrew": { group: "distribution-indexes", summary: "Homebrew tap fact collection, Formula rendering, update, and check APIs." },
517
521
  "./build-facts": { group: "observability-diagnostics", summary: "Git source, version, module output, product artifact, and legacy Kungfu build fact APIs." },
518
522
  "./candidate-timeline": { group: "observability-diagnostics", summary: "Source-bound candidate event normalization, per-attempt critical-path-safe aggregation, and compact reporting APIs." },
523
+ "./channel-candidate": { group: "governance-versioning", summary: "Exact-source channel candidate decisions, same-SHA workflow evidence validation, and deterministic source-lock reference APIs." },
524
+ "./cache-evidence": { group: "observability-diagnostics", summary: "Content-addressed cache operation receipts and source/platform-bound evidence-set verification APIs." },
519
525
  "./diagnostics": { group: "observability-diagnostics", summary: "Native diagnostics collection, summarization, cache, compiler, and process-sampler APIs." },
520
526
  "./logging": { group: "observability-diagnostics", summary: "Buildchain JSONL logging, span, summary, and verification APIs." },
521
527
  "./portable-dev-cache": { group: "observability-diagnostics", summary: "Portable dependency/compiler cache plan, exact-root verification, and provider receipt APIs." },
@@ -527,8 +533,12 @@ function nodeApiMeta(exportName) {
527
533
  "./github-governance-authority": { group: "governance-versioning", summary: "Fail-closed GitHub ownership, effective-policy, managed-zone admission, rollout-plan, and immutable receipt APIs." },
528
534
  "./artifact-passport": { group: "release-passport-trust", summary: "Artifact passport digest and evidence helper APIs." },
529
535
  "./artifact-verification-envelope": { group: "release-passport-trust", summary: "Sealed exact-root, lifecycle, identity, and existing KFD assessment inputs for KFX admission." },
536
+ "./artifact-signing": { group: "reusable-build", summary: "Credential-free artifact signing declarations, source-bound requests, authority receipts, profile resolution, and fail-closed validation APIs." },
537
+ "./artifact-signing-result": { group: "release-passport-trust", summary: "Immutable signed-result, final-payload, receipt, evidence-root, and provider-verification binding APIs." },
538
+ "./detached-artifact-signature": { group: "release-passport-trust", summary: "Buildchain-authority Ed25519 detached signature and verification APIs for arbitrary binary artifacts." },
530
539
  "./anchored-version-material": { group: "reusable-build", summary: "Anchored/manual derived version material preflight, exact-tree binding, and digest evidence APIs." },
531
540
  "./release-passport": { group: "release-passport-trust", summary: "Release passport collection, verification, explanation, and evidence APIs." },
541
+ "./github-artifact-attestation": { group: "release-passport-trust", summary: "GitHub keyless artifact attestation policy, predicate, provider evidence, and fail-closed verification APIs." },
532
542
  "./kfd-agent-hub": { group: "kfd-trust", summary: "Declarative Agent Hub adapter inspection, fixed-suite execution, exact KFD cut locking, and agent explanation APIs." },
533
543
  "./release-passport-contract": { group: "release-passport-trust", summary: "Standalone release passport JSON Schema, ownership/check manifest, and structural validation APIs." },
534
544
  "./release-candidate": { group: "reusable-build", summary: "PR-stage release-candidate artifact, passport, and promote-only resolver APIs." },
@@ -603,6 +613,7 @@ function buildCapabilityRegistry({ docs, pages, cliRegistry, manualRegistry, nod
603
613
  }
604
614
 
605
615
  function workflowCapabilityGroup(entry) {
616
+ if (entry.id === "github-artifact-attestation") return capabilityGroup("release-passport-trust");
606
617
  if (["web-surface", "release-propagation"].includes(entry.id)) return capabilityGroup("site-and-propagation");
607
618
  if (["build", "release-candidate-promote", "publication-artifact", "paper-release"].includes(entry.id)) return capabilityGroup("reusable-build");
608
619
  if (["buildchain-ref-promotion", "release-line-bootstrap"].includes(entry.id)) return capabilityGroup("release-passport-trust");
@@ -612,6 +623,7 @@ function workflowCapabilityGroup(entry) {
612
623
  }
613
624
 
614
625
  function actionCapabilityGroup(id) {
626
+ if (id === "github-artifact-attestation") return capabilityGroup("release-passport-trust");
615
627
  if (id === "promote-buildchain-ref") return capabilityGroup("release-passport-trust");
616
628
  if (id === "run-lifecycle" || id === "validate-config") return capabilityGroup("reusable-build");
617
629
  if (id === "report-buildchain-issue") return capabilityGroup("observability-diagnostics");
@@ -813,6 +825,7 @@ function buildSiteBundle() {
813
825
  "docs/reusable-build-surface.md",
814
826
  "docs/release-candidate.md",
815
827
  "docs/stable-candidate-patrol.md",
828
+ "docs/dev-alpha-candidate-patrol.md",
816
829
  "docs/observed-evidence-patrol.md",
817
830
  "docs/release-governance.md",
818
831
  "docs/release-passport.md",
@@ -894,11 +907,13 @@ function buildSiteBundle() {
894
907
  ["dev-pr-auto-merge", "dev-governance"],
895
908
  ["github-governance-audit", "dev-governance"],
896
909
  ["binary-distribution", "release-passport"],
910
+ ["github-artifact-attestation", "release-passport"],
897
911
  ["buildchain-patrol", "repository-patrol"],
898
912
  ["buildchain-patrol-daily", "repository-patrol"],
899
913
  ["buildchain-patrol-weekly", "repository-patrol"],
900
914
  ["buildchain-patrol-monthly", "repository-patrol"],
901
915
  ["stable-candidate-patrol", "repository-patrol"],
916
+ ["dev-alpha-candidate-patrol", "repository-patrol"],
902
917
  ["buildchain-stable-candidate-patrol", "repository-patrol"],
903
918
  ["buildchain-stable-candidate-qualification", "repository-patrol"],
904
919
  ["patrol-daily", "repository-patrol"],
@@ -986,6 +1001,7 @@ function buildSiteBundle() {
986
1001
  "publish evidence JSON",
987
1002
  "buildchain.release.json",
988
1003
  "release passport assets",
1004
+ "GitHub artifact attestation Sigstore bundle and Buildchain evidence JSON",
989
1005
  ],
990
1006
  owner: "promote-buildchain-ref",
991
1007
  },
@@ -1018,6 +1034,10 @@ function buildSiteBundle() {
1018
1034
  "llms.txt",
1019
1035
  "buildchain-release-bundle.json",
1020
1036
  "buildchain-release-bundle.tar.gz",
1037
+ "github-artifact-attestation.policy.json",
1038
+ "github-artifact-attestation.predicate.json",
1039
+ "github-artifact-attestation.evidence.json",
1040
+ "attestation.sigstore.json",
1021
1041
  ],
1022
1042
  site: [
1023
1043
  "buildchain-site.json",
@@ -0,0 +1,76 @@
1
+ #!/usr/bin/env node
2
+ import fs from "node:fs";
3
+ import path from "node:path";
4
+ import { pathToFileURL } from "node:url";
5
+
6
+ import { validateArtifactSigningRequest } from "../packages/core/artifact-signing.js";
7
+ import { verifyArtifactSigningResults } from "./verify-artifact-signing-results.mjs";
8
+ import { writeGitHubOutputs } from "./build-contract-core.mjs";
9
+
10
+ function required(value, label) {
11
+ const normalized = String(value || "").trim();
12
+ if (!normalized) throw new Error(`${label} is required`);
13
+ return normalized;
14
+ }
15
+
16
+ function resolveBelow(root, relative, label) {
17
+ const target = path.resolve(root, required(relative, label));
18
+ const rel = path.relative(path.resolve(root), target);
19
+ if (!rel || rel.startsWith("..") || path.isAbsolute(rel)) throw new Error(`${label} must resolve below its root`);
20
+ return target;
21
+ }
22
+
23
+ export function importArtifactSigningResults({
24
+ workspace = process.env.GITHUB_WORKSPACE || process.cwd(),
25
+ cwd = process.env.BUILDCHAIN_SIGNING_CWD || ".",
26
+ requestRoot = process.env.BUILDCHAIN_SIGNING_REQUEST_ROOT,
27
+ resultRoot = process.env.BUILDCHAIN_SIGNING_RESULT_ROOT,
28
+ evidenceRoot = process.env.BUILDCHAIN_SIGNING_IMPORTED_EVIDENCE_ROOT || ".buildchain/artifacts/signing",
29
+ } = {}) {
30
+ const sourceRoot = path.resolve(workspace, cwd);
31
+ const requests = path.resolve(required(requestRoot, "signing request root"));
32
+ const results = path.resolve(required(resultRoot, "signing result root"));
33
+ const verification = verifyArtifactSigningResults({ requestRoot: requests, resultRoot: results });
34
+ const requestIndex = JSON.parse(fs.readFileSync(path.join(requests, "index.json"), "utf8"));
35
+ const resultIndex = JSON.parse(fs.readFileSync(path.join(results, "index.json"), "utf8"));
36
+ const byId = new Map();
37
+ for (const entry of requestIndex.requests || []) {
38
+ const request = JSON.parse(fs.readFileSync(resolveBelow(requests, entry.path, "request path"), "utf8"));
39
+ const check = validateArtifactSigningRequest(request);
40
+ if (!check.ok || request.digest !== entry.digest) throw new Error(`invalid request during result import: ${entry.id}`);
41
+ byId.set(entry.id, request);
42
+ }
43
+ const imported = [];
44
+ for (const entry of resultIndex.results || []) {
45
+ const request = byId.get(entry.id);
46
+ if (!request) throw new Error(`result has no local sealed request: ${entry.id}`);
47
+ const resultPath = resolveBelow(results, entry.result, "result path");
48
+ const result = JSON.parse(fs.readFileSync(resultPath, "utf8"));
49
+ const payload = resolveBelow(path.dirname(resultPath), result.artifact.path, "signed payload");
50
+ const target = resolveBelow(sourceRoot, request.artifact.path, "consumer artifact path");
51
+ const mode = fs.statSync(target).mode;
52
+ fs.copyFileSync(payload, target);
53
+ fs.chmodSync(target, mode);
54
+ const destination = path.resolve(sourceRoot, evidenceRoot, entry.id);
55
+ const rel = path.relative(sourceRoot, destination);
56
+ if (rel.startsWith("..") || path.isAbsolute(rel)) throw new Error("imported signing evidence escapes consumer source root");
57
+ fs.mkdirSync(path.dirname(destination), { recursive: true });
58
+ fs.cpSync(path.dirname(resultPath), destination, { recursive: true, force: false, errorOnExist: true });
59
+ imported.push({ id: entry.id, path: request.artifact.path, resultDigest: result.digest });
60
+ }
61
+ writeGitHubOutputs({
62
+ "imported-count": String(imported.length),
63
+ "imported-json": JSON.stringify(imported),
64
+ "evidence-root": path.resolve(sourceRoot, evidenceRoot),
65
+ });
66
+ return { ...verification, imported };
67
+ }
68
+
69
+ if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
70
+ try {
71
+ importArtifactSigningResults();
72
+ } catch (error) {
73
+ console.error(`::error::${String(error?.message || error).replace(/\r?\n/gu, "%0A")}`);
74
+ process.exitCode = 1;
75
+ }
76
+ }
@@ -0,0 +1,101 @@
1
+ #!/usr/bin/env node
2
+ import fs from "node:fs";
3
+ import path from "node:path";
4
+ import { pathToFileURL } from "node:url";
5
+
6
+ import { validateArtifactSigningRequest } from "../packages/core/artifact-signing.js";
7
+ import { writeGitHubOutputs } from "./build-contract-core.mjs";
8
+
9
+ function required(value, label) {
10
+ const normalized = String(value || "").trim();
11
+ if (!normalized) throw new Error(`${label} is required`);
12
+ return normalized;
13
+ }
14
+
15
+ function safeId(value) {
16
+ const normalized = String(value || "")
17
+ .replace(/[^A-Za-z0-9._-]+/gu, "-")
18
+ .replace(/^-+|-+$/gu, "");
19
+ if (!normalized || normalized === "." || normalized === "..") {
20
+ throw new Error("unsafe signing request id");
21
+ }
22
+ return normalized;
23
+ }
24
+
25
+ function walk(root, name, output = []) {
26
+ for (const entry of fs.readdirSync(root, { withFileTypes: true })) {
27
+ const child = path.join(root, entry.name);
28
+ if (entry.isDirectory()) walk(child, name, output);
29
+ else if (entry.isFile() && entry.name === name) output.push(child);
30
+ }
31
+ return output;
32
+ }
33
+
34
+ export function inspectArtifactSigningRequests({
35
+ inputRoot = process.env.BUILDCHAIN_SIGNING_REQUEST_ROOT,
36
+ expectedRepository = process.env.BUILDCHAIN_SIGNING_SOURCE_REPOSITORY,
37
+ expectedRuntimeSha = process.env.BUILDCHAIN_RUNTIME_SHA,
38
+ } = {}) {
39
+ const root = path.resolve(required(inputRoot, "signing request root"));
40
+ const indexes = walk(root, "index.json");
41
+ if (indexes.length === 0) throw new Error("no artifact signing request indexes found");
42
+ const seen = new Set();
43
+ const matrices = {
44
+ detached: [],
45
+ macos: [],
46
+ windows: [],
47
+ };
48
+ for (const indexPath of indexes) {
49
+ const index = JSON.parse(fs.readFileSync(indexPath, "utf8"));
50
+ if (index.contract !== "kungfu-buildchain-artifact-signing-request-index/v1") continue;
51
+ for (const entry of index.requests || []) {
52
+ const requestPath = path.resolve(path.dirname(indexPath), entry.path);
53
+ const relative = path.relative(root, requestPath);
54
+ if (relative.startsWith("..") || path.isAbsolute(relative)) {
55
+ throw new Error("signing request path escapes the authority intake root");
56
+ }
57
+ const request = JSON.parse(fs.readFileSync(requestPath, "utf8"));
58
+ const check = validateArtifactSigningRequest(request);
59
+ if (!check.ok || request.digest !== entry.digest) {
60
+ throw new Error(`invalid artifact signing request: ${entry.id}`);
61
+ }
62
+ if (expectedRepository && request.source.repository !== expectedRepository) {
63
+ throw new Error("signing request source repository mismatch");
64
+ }
65
+ if (expectedRuntimeSha && request.runtime.sha !== expectedRuntimeSha) {
66
+ throw new Error("signing request runtime SHA mismatch");
67
+ }
68
+ const key = `${request.source.sha}:${request.artifact.id}:${request.artifact.platform}`;
69
+ if (seen.has(key)) throw new Error(`duplicate artifact signing request: ${key}`);
70
+ seen.add(key);
71
+ const item = {
72
+ id: request.artifact.id,
73
+ slug: safeId(request.artifact.id),
74
+ request: path.relative(root, requestPath).split(path.sep).join("/"),
75
+ directory: path.relative(root, path.dirname(requestPath)).split(path.sep).join("/"),
76
+ indexRoot: path.relative(root, path.dirname(indexPath)).split(path.sep).join("/") || ".",
77
+ };
78
+ if (request.signature.profile === "detached-signature-v1") matrices.detached.push(item);
79
+ else if (request.signature.profile === "apple-developer-id") matrices.macos.push(item);
80
+ else if (request.signature.profile === "windows-authenticode") matrices.windows.push(item);
81
+ else throw new Error(`unsupported signing authority profile: ${request.signature.profile}`);
82
+ }
83
+ }
84
+ for (const entries of Object.values(matrices)) entries.sort((a, b) => a.request.localeCompare(b.request));
85
+ writeGitHubOutputs({
86
+ "detached-matrix": JSON.stringify(matrices.detached),
87
+ "macos-matrix": JSON.stringify(matrices.macos),
88
+ "windows-matrix": JSON.stringify(matrices.windows),
89
+ "request-count": String(seen.size),
90
+ });
91
+ return matrices;
92
+ }
93
+
94
+ if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
95
+ try {
96
+ inspectArtifactSigningRequests();
97
+ } catch (error) {
98
+ console.error(`::error::${String(error?.message || error).replace(/\r?\n/gu, "%0A")}`);
99
+ process.exitCode = 1;
100
+ }
101
+ }