@kungfu-tech/buildchain 3.0.2 → 3.0.3-alpha.1

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 (127) hide show
  1. package/AGENTS.md +1 -1
  2. package/CONTRIBUTING.md +1 -1
  3. package/README.md +27 -1
  4. package/actions/promote-buildchain-ref/README.md +90 -19
  5. package/actions/report-buildchain-issue/README.md +20 -2
  6. package/actions/run-lifecycle/README.md +5 -0
  7. package/actions/validate-config/README.md +19 -1
  8. package/bin/buildchain.mjs +56 -2
  9. package/bin/internal/command-registry.mjs +82 -0
  10. package/bin/internal/trust-release-cli.mjs +1 -0
  11. package/contracts/auditable-demo-media-profiles-v1.json +116 -1
  12. package/contracts/buildchain-v2-residuals-v1.json +146 -0
  13. package/contracts/evidence/auditable-demo-responsive-web-delivery-v1.json +143 -0
  14. package/contracts/evidence/auditable-demo-web-delivery-v1.json +7 -3
  15. package/contracts/fixtures/auditable-demo-responsive-web-delivery-v1/complete-transcript.txt +2 -0
  16. package/contracts/fixtures/auditable-demo-responsive-web-delivery-v1/public-projection.json +16 -0
  17. package/contracts/fixtures/auditable-demo-responsive-web-delivery-v1/scene.json +12 -0
  18. package/dist/site/buildchain-contract.json +188 -46
  19. package/dist/site/buildchain-site.json +181 -90
  20. package/dist/site/capability-registry.json +5 -5
  21. package/dist/site/cli-registry.json +860 -98
  22. package/dist/site/controller-registry.json +118 -5
  23. package/dist/site/kfd-claims.json +233 -19
  24. package/dist/site/kfd-upstream-aggregate.json +1 -1
  25. package/dist/site/manual-registry.json +20 -20
  26. package/dist/site/node-api-registry.json +374 -59
  27. package/dist/site/page-registry.json +156 -65
  28. package/dist/site/public-surface-audit.json +231 -22
  29. package/dist/site/publication-registry.json +4 -4
  30. package/dist/site/release-provenance.json +3 -0
  31. package/dist/site/site-manifest.json +24 -24
  32. package/dist/site/workflow-registry.json +575 -79
  33. package/docs/MAP.md +22 -4
  34. package/docs/auditable-demo.md +54 -13
  35. package/docs/aws-us-elastic-runner-burst-plane.md +381 -0
  36. package/docs/cli.md +119 -12
  37. package/docs/consumer-issue-reporting.md +20 -2
  38. package/docs/dev-alpha-candidate-patrol.md +24 -10
  39. package/docs/github-governance-authority.md +24 -31
  40. package/docs/lifecycle-protocol.md +20 -2
  41. package/docs/migration-inventory.md +24 -5
  42. package/docs/observed-evidence-patrol.md +28 -12
  43. package/docs/ownership.md +19 -1
  44. package/docs/publication-artifacts.md +158 -18
  45. package/docs/publication-authority.md +4 -4
  46. package/docs/publish-transaction.md +78 -31
  47. package/docs/release-candidate.md +24 -0
  48. package/docs/release-flow.md +50 -32
  49. package/docs/release-governance.md +39 -32
  50. package/docs/release-passport.md +4 -0
  51. package/docs/release-propagation.md +57 -12
  52. package/docs/reusable-build-surface.md +218 -90
  53. package/docs/runtime-train-validation.md +20 -2
  54. package/docs/shifu-gate-profiles.md +7 -1
  55. package/docs/stable-candidate-patrol.md +6 -6
  56. package/docs/toolkit-observability.md +22 -0
  57. package/docs/versioning.md +46 -24
  58. package/docs/web-surface-deployments.md +82 -1
  59. package/package.json +7 -5
  60. package/packages/core/README.md +60 -4
  61. package/packages/core/artifact-signing.js +1 -0
  62. package/packages/core/buildchain-contract.js +9 -4
  63. package/packages/core/cache-evidence.js +1 -0
  64. package/packages/core/controller-evidence.js +2 -0
  65. package/packages/core/diagnostics.js +105 -30
  66. package/packages/core/github-governance-authority.js +25 -17
  67. package/packages/core/index.js +40 -0
  68. package/packages/core/paper.js +3226 -0
  69. package/packages/core/public-surface-audit.js +3 -0
  70. package/packages/core/publication-artifact.js +1 -6
  71. package/packages/core/publication-authority.js +1 -0
  72. package/packages/core/publication-control-plane-audit.js +28 -0
  73. package/packages/core/publication-reproducibility.js +947 -0
  74. package/packages/core/publication-sealed-bundle.js +190 -0
  75. package/packages/core/publish-transaction.js +147 -17
  76. package/packages/core/release-candidate.js +234 -0
  77. package/packages/core/release-passport.js +519 -228
  78. package/packages/core/release-propagation.js +175 -1
  79. package/packages/core/stable-release-gate.js +4 -1
  80. package/scripts/artifact-signing-delegation.mjs +268 -0
  81. package/scripts/audit-github-governance.mjs +32 -13
  82. package/scripts/audit-publication-control-plane.mjs +17 -8
  83. package/scripts/auditable-demo.mjs +266 -7
  84. package/scripts/aws-codebuild-toolchain.mjs +285 -0
  85. package/scripts/aws-macos-jit-core.mjs +378 -0
  86. package/scripts/aws-macos-jit.mjs +123 -0
  87. package/scripts/aws-runner-burst-core.mjs +248 -0
  88. package/scripts/aws-runner-burst.mjs +79 -0
  89. package/scripts/aws-windows-jit-core.mjs +374 -0
  90. package/scripts/aws-windows-jit.mjs +121 -0
  91. package/scripts/build-contract-core.mjs +69 -6
  92. package/scripts/build-standalone-binary.mjs +23 -0
  93. package/scripts/buildchain-contract-lock.mjs +1 -1
  94. package/scripts/buildchain-patrol.mjs +1 -1
  95. package/scripts/check-internal-architecture.mjs +69 -2
  96. package/scripts/check-inventory.mjs +70 -2
  97. package/scripts/check-javascript-syntax.mjs +31 -0
  98. package/scripts/check-maintainability.mjs +371 -0
  99. package/scripts/compiler-cache-evidence.mjs +221 -0
  100. package/scripts/dev-alpha-candidate-patrol.mjs +288 -43
  101. package/scripts/dispatch-artifact-signing-authority.mjs +7 -1
  102. package/scripts/finalize-native-artifact-signing-result.mjs +185 -31
  103. package/scripts/gate-profile-core.mjs +6 -1
  104. package/scripts/generate-channel-promotion-workflow.mjs +3 -12
  105. package/scripts/generate-release-candidate-passport.mjs +4 -0
  106. package/scripts/generate-site-bundle.mjs +84 -18
  107. package/scripts/import-artifact-signing-results.mjs +64 -5
  108. package/scripts/inspect-artifact-signing-requests.mjs +52 -14
  109. package/scripts/maintainability-metrics.mjs +339 -0
  110. package/scripts/npm-publish-transaction.mjs +57 -6
  111. package/scripts/observed-evidence.mjs +151 -33
  112. package/scripts/paper.mjs +645 -0
  113. package/scripts/promotion-channel-router.mjs +16 -4
  114. package/scripts/publication-reproducibility.mjs +62 -0
  115. package/scripts/reconcile-github-governance.mjs +8 -1
  116. package/scripts/release-propagation.mjs +39 -0
  117. package/scripts/resolve-artifact-signing-upload-route.mjs +55 -0
  118. package/scripts/resolve-build-contract.mjs +6 -0
  119. package/scripts/route-offline-runners.mjs +236 -0
  120. package/scripts/run-candidate-body-prefix-renderer.mjs +187 -0
  121. package/scripts/run-lifecycle-core.mjs +62 -1
  122. package/scripts/runtime-ref-core.mjs +36 -4
  123. package/scripts/seal-artifact-signing-requests.mjs +64 -12
  124. package/scripts/stable-candidate-qualification.mjs +50 -0
  125. package/scripts/verify-artifact-signing-results.mjs +26 -1
  126. package/scripts/web-surface-core.mjs +272 -86
  127. package/scripts/web-surface-production-decision.mjs +19 -3
@@ -4,8 +4,14 @@ import fs from "node:fs";
4
4
  import path from "node:path";
5
5
  import { pathToFileURL } from "node:url";
6
6
 
7
- import { createArtifactSigningReceipt, validateArtifactSigningRequest } from "../packages/core/artifact-signing.js";
8
- import { artifactSigningEvidenceDigest, createArtifactSigningResult } from "../packages/core/artifact-signing-result.js";
7
+ import {
8
+ createArtifactSigningReceipt,
9
+ validateArtifactSigningRequest,
10
+ } from "../packages/core/artifact-signing.js";
11
+ import {
12
+ artifactSigningEvidenceDigest,
13
+ createArtifactSigningResult,
14
+ } from "../packages/core/artifact-signing-result.js";
9
15
  import { writeGitHubOutputs } from "./build-contract-core.mjs";
10
16
 
11
17
  function required(value, label) {
@@ -21,7 +27,8 @@ function sha256File(filePath) {
21
27
  function resolveBelow(root, relative, label) {
22
28
  const target = path.resolve(root, required(relative, label));
23
29
  const rel = path.relative(path.resolve(root), target);
24
- if (rel.startsWith("..") || path.isAbsolute(rel)) throw new Error(`${label} escapes its root`);
30
+ if (rel.startsWith("..") || path.isAbsolute(rel))
31
+ throw new Error(`${label} escapes its root`);
25
32
  return target;
26
33
  }
27
34
 
@@ -30,67 +37,214 @@ export function finalizeNativeArtifactSigningResult({
30
37
  requestPath = process.env.BUILDCHAIN_SIGNING_REQUEST_PATH,
31
38
  signedPayload = process.env.BUILDCHAIN_SIGNED_PAYLOAD,
32
39
  evidencePath = process.env.BUILDCHAIN_SIGNING_EVIDENCE,
40
+ credentialArtifactRoot =
41
+ process.env.BUILDCHAIN_SIGNING_CREDENTIAL_ARTIFACT_ROOT,
33
42
  outputRoot = process.env.BUILDCHAIN_SIGNING_RESULT_ROOT,
34
43
  checks = process.env.BUILDCHAIN_SIGNING_VERIFICATION_CHECKS,
35
44
  } = {}) {
36
45
  const requests = path.resolve(required(requestRoot, "signing request root"));
37
- const request = JSON.parse(fs.readFileSync(resolveBelow(requests, requestPath, "request path"), "utf8"));
46
+ const request = JSON.parse(
47
+ fs.readFileSync(
48
+ resolveBelow(requests, requestPath, "request path"),
49
+ "utf8",
50
+ ),
51
+ );
38
52
  const requestCheck = validateArtifactSigningRequest(request);
39
- if (!requestCheck.ok) throw new Error(`invalid signing request: ${requestCheck.issues.join(", ")}`);
53
+ if (!requestCheck.ok)
54
+ throw new Error(
55
+ `invalid signing request: ${requestCheck.issues.join(", ")}`,
56
+ );
40
57
  if (request.signature.semantics !== "native-platform-signature") {
41
- throw new Error("native result finalizer rejects non-native signature profiles");
58
+ throw new Error(
59
+ "native result finalizer rejects non-native signature profiles",
60
+ );
42
61
  }
43
62
  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"));
63
+ const evidenceSource = path.resolve(
64
+ required(evidencePath, "signing evidence"),
65
+ );
66
+ const resultDirectory = path.resolve(
67
+ required(outputRoot, "signing result root"),
68
+ );
46
69
  fs.mkdirSync(path.join(resultDirectory, "payload"), { recursive: true });
47
- const payloadPath = path.join(resultDirectory, "payload", path.basename(payloadSource));
70
+ const payloadPath = path.join(
71
+ resultDirectory,
72
+ "payload",
73
+ path.basename(payloadSource),
74
+ );
48
75
  fs.copyFileSync(payloadSource, payloadPath, fs.constants.COPYFILE_EXCL);
49
76
  const evidenceOutput = path.join(resultDirectory, "provider-evidence.json");
50
77
  fs.copyFileSync(evidenceSource, evidenceOutput, fs.constants.COPYFILE_EXCL);
51
78
  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");
79
+ const appBundleResult = request.artifact.kind === "app-bundle";
80
+ const providerEvidencePassed = appBundleResult
81
+ ? evidenceDocument.schema ===
82
+ "buildchain.macos-credential-island-evidence/v1" &&
83
+ evidenceDocument.status === "accepted" &&
84
+ evidenceDocument.source?.repository === request.source.repository &&
85
+ evidenceDocument.source?.sha === request.source.sha &&
86
+ evidenceDocument.source?.treeSha === request.source.treeSha &&
87
+ evidenceDocument.buildchain?.runtimeSha === request.runtime.sha &&
88
+ evidenceDocument.app?.architecture === request.artifact.arch &&
89
+ evidenceDocument.notarization?.application?.status === "Accepted" &&
90
+ evidenceDocument.notarization?.diskImage?.status === "Accepted"
91
+ : evidenceDocument.status === "passed" &&
92
+ evidenceDocument.provider === request.signature.provider;
93
+ if (!providerEvidencePassed) {
94
+ throw new Error(
95
+ "provider evidence does not prove the requested native signature",
96
+ );
54
97
  }
55
- const evidence = [{ kind: `${request.signature.profile}-verification`, path: "provider-evidence.json", digest: sha256File(evidenceOutput) }];
98
+ let credentialOutput = "";
99
+ let credentialEvidence = [];
100
+ if (appBundleResult) {
101
+ const credentialSource = path.resolve(
102
+ required(
103
+ credentialArtifactRoot,
104
+ "credential artifact root for app-bundle result",
105
+ ),
106
+ );
107
+ const credentialManifest = path.join(credentialSource, "manifest.json");
108
+ if (!fs.statSync(credentialManifest).isFile())
109
+ throw new Error("credential artifact manifest is missing");
110
+ credentialOutput = path.join(resultDirectory, "credential-artifact");
111
+ fs.cpSync(credentialSource, credentialOutput, {
112
+ recursive: true,
113
+ force: false,
114
+ errorOnExist: true,
115
+ });
116
+ const manifestOutput = path.join(credentialOutput, "manifest.json");
117
+ const manifest = JSON.parse(fs.readFileSync(manifestOutput, "utf8"));
118
+ if (!Array.isArray(manifest.files) || manifest.files.length === 0)
119
+ throw new Error("credential artifact manifest contains no files");
120
+ credentialEvidence = [
121
+ {
122
+ kind: "credential-artifact-manifest",
123
+ path: "credential-artifact/manifest.json",
124
+ digest: sha256File(manifestOutput),
125
+ },
126
+ ...manifest.files.map((file, index) => {
127
+ const filePath = resolveBelow(
128
+ credentialOutput,
129
+ file.path,
130
+ `credential artifact file ${index}`,
131
+ );
132
+ const observed = sha256File(filePath);
133
+ if (observed !== `sha256:${String(file.sha256 || "").toLowerCase()}`)
134
+ throw new Error(
135
+ `credential artifact file digest mismatch: ${file.path}`,
136
+ );
137
+ return {
138
+ kind: `credential-artifact-${index}`,
139
+ path: `credential-artifact/${String(file.path).replaceAll("\\", "/")}`,
140
+ digest: observed,
141
+ };
142
+ }),
143
+ ];
144
+ }
145
+ const evidence = [
146
+ {
147
+ kind: `${request.signature.profile}-verification`,
148
+ path: "provider-evidence.json",
149
+ digest: sha256File(evidenceOutput),
150
+ },
151
+ ...credentialEvidence,
152
+ ];
56
153
  const payloadDigest = sha256File(payloadPath);
57
154
  const receipt = createArtifactSigningReceipt({
58
155
  request,
59
- result: { artifactDigest: payloadDigest, evidenceDigest: artifactSigningEvidenceDigest(evidence) },
60
- signatures: [{ kind: request.signature.profile, digest: evidence[0].digest }],
156
+ result: {
157
+ artifactDigest: payloadDigest,
158
+ evidenceDigest: artifactSigningEvidenceDigest(evidence),
159
+ },
160
+ signatures: [
161
+ { kind: request.signature.profile, digest: evidence[0].digest },
162
+ ],
61
163
  });
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);
164
+ fs.writeFileSync(
165
+ path.join(resultDirectory, "receipt.json"),
166
+ `${JSON.stringify(receipt, null, 2)}\n`,
167
+ );
168
+ const verificationChecks = String(checks || "")
169
+ .split(",")
170
+ .map((value) => value.trim())
171
+ .filter(Boolean);
172
+ if (
173
+ verificationChecks.length === 0 &&
174
+ Array.isArray(evidenceDocument.checks)
175
+ ) {
176
+ verificationChecks.push(
177
+ ...evidenceDocument.checks
178
+ .map((value) => String(value).trim())
179
+ .filter(Boolean),
180
+ );
181
+ }
182
+ if (verificationChecks.length === 0 && appBundleResult) {
183
+ verificationChecks.push(
184
+ ...Object.entries(evidenceDocument.verification || {})
185
+ .filter(([, passed]) => passed === true)
186
+ .map(([name]) => name),
187
+ "application-notarytool-accepted",
188
+ "disk-image-notarytool-accepted",
189
+ );
190
+ }
191
+ if (verificationChecks.length === 0)
192
+ throw new Error("provider evidence contains no verification checks");
64
193
  const result = createArtifactSigningResult({
65
194
  request,
66
195
  receipt,
67
- payload: { path: `payload/${path.basename(payloadPath)}`, bytes: fs.statSync(payloadPath).size, digest: payloadDigest },
196
+ payload: {
197
+ path: `payload/${path.basename(payloadPath)}`,
198
+ bytes: fs.statSync(payloadPath).size,
199
+ digest: payloadDigest,
200
+ },
68
201
  evidence,
69
- verification: { status: "passed", provider: request.signature.provider, checks: verificationChecks },
202
+ verification: {
203
+ status: "passed",
204
+ provider: request.signature.provider,
205
+ checks: verificationChecks,
206
+ },
70
207
  });
71
- fs.writeFileSync(path.join(resultDirectory, "result.json"), `${JSON.stringify(result, null, 2)}\n`);
208
+ fs.writeFileSync(
209
+ path.join(resultDirectory, "result.json"),
210
+ `${JSON.stringify(result, null, 2)}\n`,
211
+ );
72
212
  const index = {
73
213
  schemaVersion: 1,
74
214
  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
- }],
215
+ results: [
216
+ {
217
+ id: request.artifact.id,
218
+ requestDigest: request.digest,
219
+ resultDigest: result.digest,
220
+ result: "result.json",
221
+ payload: result.artifact.path,
222
+ receipt: "receipt.json",
223
+ },
224
+ ],
83
225
  };
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") });
226
+ fs.writeFileSync(
227
+ path.join(resultDirectory, "index.json"),
228
+ `${JSON.stringify(index, null, 2)}\n`,
229
+ );
230
+ writeGitHubOutputs({
231
+ "result-root": resultDirectory,
232
+ "result-index": path.join(resultDirectory, "index.json"),
233
+ "credential-artifact-root": credentialOutput,
234
+ });
86
235
  return index;
87
236
  }
88
237
 
89
- if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
238
+ if (
239
+ process.argv[1] &&
240
+ import.meta.url === pathToFileURL(process.argv[1]).href
241
+ ) {
90
242
  try {
91
243
  finalizeNativeArtifactSigningResult();
92
244
  } catch (error) {
93
- console.error(`::error::${String(error?.message || error).replace(/\r?\n/gu, "%0A")}`);
245
+ console.error(
246
+ `::error::${String(error?.message || error).replace(/\r?\n/gu, "%0A")}`,
247
+ );
94
248
  process.exitCode = 1;
95
249
  }
96
250
  }
@@ -2,6 +2,7 @@ import crypto from "node:crypto";
2
2
 
3
3
  export const GATE_MATRIX_CONTRACT = "buildchain.shifu-gate-matrix/v1";
4
4
  export const GATE_AGGREGATE_CONTRACT = "buildchain.shifu-gate-aggregate/v1";
5
+ export const GATE_JOB_CONTROL_PLANE_OVERHEAD_MINUTES = 30;
5
6
 
6
7
  function stableValue(value) {
7
8
  if (Array.isArray(value)) return value.map(stableValue);
@@ -265,7 +266,11 @@ export function createGateExecutionMatrix({
265
266
  planDigest: inspected.planDigest,
266
267
  timeoutMinutes: Math.max(
267
268
  1,
268
- Math.min(360, Math.ceil(timeoutSeconds / 60)),
269
+ Math.min(
270
+ 360,
271
+ Math.ceil(timeoutSeconds / 60) +
272
+ GATE_JOB_CONTROL_PLANE_OVERHEAD_MINUTES,
273
+ ),
269
274
  ),
270
275
  gates: inspected.gates.map((gate) => ({
271
276
  id: gate.id,
@@ -284,24 +284,15 @@ jobs:
284
284
  --publication-channel "\${{ inputs.channel }}"
285
285
  --target-ref "\${{ inputs.target-ref || github.ref_name }}"
286
286
  --router-ref "\${{ steps.router.outputs.ref }}"
287
+ --router-sha "\${{ steps.router.outputs.sha }}"
287
288
 
288
289
  - name: Authorize trusted runtime override
289
290
  if: \${{ steps.route.outputs.override-used == 'true' }}
290
291
  uses: actions/github-script@v8
291
292
  with:
292
293
  script: |
293
- if (context.eventName !== "workflow_dispatch") {
294
- throw new Error("promotion runtime override is only allowed for trusted workflow_dispatch runs");
295
- }
296
- const permission = await github.rest.repos.getCollaboratorPermissionLevel({
297
- owner: context.repo.owner,
298
- repo: context.repo.repo,
299
- username: context.actor,
300
- });
301
- const level = permission.data.user?.permissions || permission.data.permission || "none";
302
- if (!["write", "maintain", "admin"].includes(level)) {
303
- throw new Error(\`promotion runtime override requires write, maintain, or admin permission; actor has \${level}\`);
304
- }
294
+ const script = require(process.env.GITHUB_WORKSPACE + "/.buildchain/router/scripts/authorize-promotion-runtime-override.cjs");
295
+ await script.authorizePromotionRuntimeOverride({ github, context });
305
296
 
306
297
  - name: Resolve immutable promotion identities
307
298
  id: identities
@@ -29,6 +29,8 @@ export function generateReleaseCandidatePassportCli() {
29
29
  const gateAggregate = gateAggregateJson ? JSON.parse(gateAggregateJson) : undefined;
30
30
  const controllerReceiptJson = env("BUILDCHAIN_CONTROLLER_RECEIPT_JSON");
31
31
  const controllerReceipts = controllerReceiptJson ? [JSON.parse(controllerReceiptJson)] : [];
32
+ const familyEvidenceJson = env("BUILDCHAIN_RC_FAMILY_EVIDENCE_JSON");
33
+ const familyEvidence = familyEvidenceJson ? JSON.parse(familyEvidenceJson) : undefined;
32
34
  const passport = createReleaseCandidatePassport({
33
35
  repository: env("GITHUB_REPOSITORY", buildSummary.git?.repository || ""),
34
36
  pullRequest: {
@@ -51,6 +53,7 @@ export function generateReleaseCandidatePassportCli() {
51
53
  workflowShellRef: env("BUILDCHAIN_WORKFLOW_SHELL_REF", buildSummary.runtime?.workflowShellRef || ""),
52
54
  },
53
55
  gateAggregate,
56
+ familyEvidence,
54
57
  controllerReceipts,
55
58
  workflow: {
56
59
  name: env("GITHUB_WORKFLOW"),
@@ -80,6 +83,7 @@ export function generateReleaseCandidatePassportCli() {
80
83
  candidateHash: passport.candidateHash,
81
84
  platformCount: passport.platformMatrix.length,
82
85
  gateProfileEvidence: passport.gateProfileEvidence,
86
+ familyEvidence: passport.familyEvidence,
83
87
  controllerReceipts: passport.controllerReceipts || [],
84
88
  }),
85
89
  });
@@ -34,6 +34,10 @@ import {
34
34
  } from "../packages/core/public-surface-audit.js";
35
35
  import { createSurfaceTimestampPolicy } from "../packages/core/surface-manifest.js";
36
36
  import { projectHomepageIntro } from "./site-bundle-homepage.mjs";
37
+ import {
38
+ BUILDCHAIN_COMMAND_REGISTRY,
39
+ resolveBuildchainCommand,
40
+ } from "../bin/internal/command-registry.mjs";
37
41
 
38
42
  const SITE_BUNDLE_CONTRACT = "kungfu-buildchain-site-bundle";
39
43
  const PUBLICATION_RELEASE_REGISTRY_CONTRACT = "kungfu-buildchain-publication-release-registry";
@@ -322,6 +326,18 @@ function capabilityGroup(id) {
322
326
  return id;
323
327
  }
324
328
 
329
+ function publicSurfaceLifecycle({ owner, maturity, nonDuplicationRationale }) {
330
+ return {
331
+ owner,
332
+ maturity,
333
+ introducedVersion: "pre-3.0.2-alpha.4",
334
+ compatibilityPromise: "preserved-through-the-v3-major-line",
335
+ deprecationReplacement: "",
336
+ sunsetCondition: "explicit-breaking-change-review-in-a-future-major-line",
337
+ nonDuplicationRationale,
338
+ };
339
+ }
340
+
325
341
  const manualMetaById = new Map(Object.entries({
326
342
  map: { capabilityGroup: "getting-started", audience: ["agent", "consumer"], maturity: "stable", order: 10 },
327
343
  install: { capabilityGroup: "getting-started", audience: ["consumer"], maturity: "stable", order: 20 },
@@ -477,12 +493,22 @@ function cliCommandMeta(id) {
477
493
  "portable-cache-plan": { group: "observability-diagnostics", purpose: "Validate a consumer-neutral cache manifest and emit exact GitHub Actions cache inputs." },
478
494
  "portable-cache-receipt": { group: "observability-diagnostics", purpose: "Seal exact, compatible, miss, or corrupt provider evidence against one cache plan." },
479
495
  "npm-dry-run": { group: "release-passport-trust", purpose: "Verify npm publish shape before a release transaction." },
496
+ paper: { group: "reusable-build", purpose: "Inspect the unified paper repository and publication command families." },
497
+ "paper-alpha": { group: "release-passport-trust", purpose: "Plan or open the protected dev-to-alpha paper publication PR without direct publication." },
498
+ "paper-bootstrap-npm": { group: "release-passport-trust", purpose: "Dry-run or explicitly bootstrap the public npm package and bind GitHub Trusted Publishing." },
499
+ "paper-build": { group: "reusable-build", purpose: "Plan or execute the existing two-clean-build reproducibility gate for a paper." },
500
+ "paper-migrate": { group: "getting-started", purpose: "Plan or write the bounded Buildchain-owned control-file migration for an existing paper repository." },
501
+ "paper-preflight": { group: "reusable-build", purpose: "Report exact source, runtime, permission, npm trust, deterministic build, and release-state readiness." },
502
+ "paper-resume": { group: "release-passport-trust", purpose: "Plan or dispatch recovery of the exact existing sealed paper release transaction." },
503
+ "paper-scaffold": { group: "getting-started", purpose: "Plan or write an idempotent, no-overwrite, release-ready paper repository scaffold." },
504
+ "paper-status": { group: "reusable-build", purpose: "Report explicit evidence for every paper lifecycle state without unsupported inference." },
480
505
  "publish-source": { group: "release-passport-trust", purpose: "Create, inspect, or verify publish-gate source-lock refs." },
481
506
  "publication-artifact": { group: "reusable-build", purpose: "Generate publication artifact manifests, passports, and source bundles for paper/report repositories." },
482
507
  "publication-artifact-manifest": { group: "reusable-build", purpose: "Write a site-consumable publication artifact manifest, publication passport, and source bundle." },
483
508
  "publication-artifact-npm-package": { group: "reusable-build", purpose: "Synthesize the declared npm paper package from a publication artifact manifest, passport, registry, source bundle, and primary artifact." },
484
509
  project: { group: "release-passport-trust", purpose: "Inspect read-only consumer projection command families." },
485
510
  "project-kfx-admission": { group: "release-passport-trust", purpose: "Project one verified sealed envelope into direct KFX admission inputs without reconstructing evidence." },
511
+ "publication-artifact-reproducibility": { group: "reusable-build", purpose: "Prove exact PDF, source bundle, publication evidence, and npm tarball bytes across two independent clean builds." },
486
512
  "release-dry-run": { group: "governance-versioning", purpose: "Explain what a channel merge would publish before the PR is merged." },
487
513
  "release-line-open": { group: "governance-versioning", purpose: "Plan or write the initial version-state commit for a new minor release line." },
488
514
  "release-governance": { group: "governance-versioning", purpose: "Reconcile a managed branch's Buildchain aggregate check to an already-tested pull request SHA." },
@@ -527,6 +553,9 @@ function nodeApiMeta(exportName) {
527
553
  "./portable-dev-cache": { group: "observability-diagnostics", summary: "Portable dependency/compiler cache plan, exact-root verification, and provider receipt APIs." },
528
554
  "./publication-artifact": { group: "reusable-build", summary: "Publication artifact manifest, source bundle, and publication passport APIs." },
529
555
  "./publication-package": { group: "reusable-build", summary: "Publication npm package synthesis APIs for Buildchain-managed paper release presets." },
556
+ "./publication-reproducibility": { group: "reusable-build", summary: "Two-clean-build publication byte reproducibility receipt APIs." },
557
+ "./publication-sealed-bundle": { group: "reusable-build", summary: "Build-once publication bundle manifests and exact-byte resume verification APIs." },
558
+ "./paper": { group: "reusable-build", summary: "Unified paper scaffold, preflight, npm bootstrap, build, Alpha, status, and resume planning APIs." },
530
559
  "./publication-authority": { group: "release-passport-trust", summary: "Sealed publication authority registry, runner provenance, control-plane audit, admission, and independent verification APIs." },
531
560
  "./publication-control-plane-audit": { group: "release-passport-trust", summary: "Read-only publication control-plane snapshot evaluation APIs." },
532
561
  "./buildchain-publication-authority": { group: "release-passport-trust", summary: "Buildchain-owned closed-world publication authority descriptor registry." },
@@ -564,6 +593,45 @@ function nodeApiMeta(exportName) {
564
593
  return { capabilityGroup: capabilityGroup(meta.group), summary: meta.summary, audience: ["developer", "agent"], maturity: "stable" };
565
594
  }
566
595
 
596
+ function createCliRegistry(packageJson) {
597
+ const commands = enumerateCliCommandsFromBin({ root });
598
+ const documentedTopLevelCommands = new Set();
599
+ for (const entry of commands) {
600
+ const command = entry.usage.split(/\s+/)[1] || "";
601
+ const registration = resolveBuildchainCommand(command);
602
+ if (!registration) {
603
+ throw new Error(`CLI help documents an unregistered top-level command: ${command}`);
604
+ }
605
+ documentedTopLevelCommands.add(registration.id);
606
+ }
607
+ for (const entry of BUILDCHAIN_COMMAND_REGISTRY) {
608
+ if (!documentedTopLevelCommands.has(entry.id)) {
609
+ throw new Error(`CLI runtime command is absent from help enumeration: ${entry.id}`);
610
+ }
611
+ }
612
+ return {
613
+ schemaVersion: 1,
614
+ contract: "kungfu-buildchain-cli-registry",
615
+ binary: "buildchain",
616
+ npmPackage: packageJson.name,
617
+ commandSource: "bin/internal/command-registry.mjs plus bin/buildchain.mjs help enumeration",
618
+ commands: commands.map((entry) => {
619
+ const meta = cliCommandMeta(entry.id);
620
+ return {
621
+ ...entry,
622
+ purpose: meta.purpose,
623
+ capabilityGroup: meta.capabilityGroup,
624
+ audience: meta.audience,
625
+ ...publicSurfaceLifecycle({
626
+ owner: "buildchain-cli",
627
+ maturity: meta.maturity,
628
+ nonDuplicationRationale: "Existing command identity retained for CLI compatibility and discoverability.",
629
+ }),
630
+ };
631
+ }),
632
+ };
633
+ }
634
+
567
635
  function buildCapabilityRegistry({ docs, pages, cliRegistry, manualRegistry, nodeApiRegistry, workflowRegistry }) {
568
636
  const groupCounts = new Map(CAPABILITY_GROUPS.map((group) => [group.id, {
569
637
  manualCount: 0,
@@ -779,23 +847,7 @@ function buildSiteBundle() {
779
847
  artifactDigestScope: "npm package dist/site JSON files",
780
848
  });
781
849
 
782
- const cliRegistry = {
783
- schemaVersion: 1,
784
- contract: "kungfu-buildchain-cli-registry",
785
- binary: "buildchain",
786
- npmPackage: packageJson.name,
787
- commandSource: "bin/buildchain.mjs reverse enumeration",
788
- commands: enumerateCliCommandsFromBin({ root }).map((entry) => {
789
- const meta = cliCommandMeta(entry.id);
790
- return {
791
- ...entry,
792
- purpose: meta.purpose,
793
- capabilityGroup: meta.capabilityGroup,
794
- audience: meta.audience,
795
- maturity: meta.maturity,
796
- };
797
- }),
798
- };
850
+ const cliRegistry = createCliRegistry(packageJson);
799
851
 
800
852
  const manualRegistry = {
801
853
  schemaVersion: 1,
@@ -874,7 +926,11 @@ function buildSiteBundle() {
874
926
  summary: meta.summary,
875
927
  capabilityGroup: meta.capabilityGroup,
876
928
  audience: meta.audience,
877
- maturity: meta.maturity,
929
+ ...publicSurfaceLifecycle({
930
+ owner: "buildchain-core",
931
+ maturity: meta.maturity,
932
+ nonDuplicationRationale: "Existing package subpath retained as the canonical API boundary for this capability.",
933
+ }),
878
934
  };
879
935
  }),
880
936
  docs: [
@@ -937,6 +993,11 @@ function buildSiteBundle() {
937
993
  status: statusById.get(entry.id) || "active",
938
994
  }),
939
995
  status: statusById.get(entry.id) || "active",
996
+ ...publicSurfaceLifecycle({
997
+ owner: "buildchain-workflows",
998
+ maturity: statusById.get(entry.id) || "active",
999
+ nonDuplicationRationale: "Existing workflow identity retained for caller compatibility and repository orchestration.",
1000
+ }),
940
1001
  };
941
1002
  }),
942
1003
  actionSource: "actions/*/action.yml reverse input enumeration",
@@ -944,6 +1005,11 @@ function buildSiteBundle() {
944
1005
  ...entry,
945
1006
  capabilityGroup: actionCapabilityGroup(entry.id),
946
1007
  status: "active",
1008
+ ...publicSurfaceLifecycle({
1009
+ owner: "buildchain-actions",
1010
+ maturity: "stable",
1011
+ nonDuplicationRationale: "Existing action identity retained as the canonical composite or JavaScript action boundary.",
1012
+ }),
947
1013
  })),
948
1014
  };
949
1015
  const controllerRegistry = createControllerRegistry({ workflows: workflowRegistry.workflows });
@@ -20,6 +20,30 @@ function resolveBelow(root, relative, label) {
20
20
  return target;
21
21
  }
22
22
 
23
+ function projectCredentialArtifact(source, destination) {
24
+ const manifest = JSON.parse(
25
+ fs.readFileSync(path.join(source, "manifest.json"), "utf8"),
26
+ );
27
+ if (!Array.isArray(manifest.files) || manifest.files.length === 0)
28
+ throw new Error("credential artifact manifest contains no files");
29
+ for (const [index, file] of manifest.files.entries()) {
30
+ const from = resolveBelow(
31
+ source,
32
+ file.path,
33
+ `credential artifact file ${index}`,
34
+ );
35
+ if (!fs.statSync(from).isFile() || fs.lstatSync(from).isSymbolicLink())
36
+ throw new Error(`credential artifact file is not regular: ${file.path}`);
37
+ const to = resolveBelow(
38
+ destination,
39
+ file.path,
40
+ `credential artifact projection ${index}`,
41
+ );
42
+ fs.mkdirSync(path.dirname(to), { recursive: true });
43
+ fs.copyFileSync(from, to, fs.constants.COPYFILE_EXCL);
44
+ }
45
+ }
46
+
23
47
  export function importArtifactSigningResults({
24
48
  workspace = process.env.GITHUB_WORKSPACE || process.cwd(),
25
49
  cwd = process.env.BUILDCHAIN_SIGNING_CWD || ".",
@@ -41,29 +65,64 @@ export function importArtifactSigningResults({
41
65
  byId.set(entry.id, request);
42
66
  }
43
67
  const imported = [];
68
+ const credentialArtifacts = [];
44
69
  for (const entry of resultIndex.results || []) {
45
70
  const request = byId.get(entry.id);
46
71
  if (!request) throw new Error(`result has no local sealed request: ${entry.id}`);
47
72
  const resultPath = resolveBelow(results, entry.result, "result path");
48
73
  const result = JSON.parse(fs.readFileSync(resultPath, "utf8"));
49
74
  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);
75
+ if (request.artifact.kind === "app-bundle") {
76
+ const credentialArtifact = path.join(
77
+ path.dirname(resultPath),
78
+ "credential-artifact",
79
+ );
80
+ if (!fs.statSync(credentialArtifact).isDirectory())
81
+ throw new Error("native app signing result has no credential artifact");
82
+ projectCredentialArtifact(credentialArtifact, sourceRoot);
83
+ } else {
84
+ const target = resolveBelow(
85
+ sourceRoot,
86
+ request.artifact.path,
87
+ "consumer artifact path",
88
+ );
89
+ const mode = fs.statSync(target).mode;
90
+ fs.copyFileSync(payload, target);
91
+ fs.chmodSync(target, mode);
92
+ }
54
93
  const destination = path.resolve(sourceRoot, evidenceRoot, entry.id);
55
94
  const rel = path.relative(sourceRoot, destination);
56
95
  if (rel.startsWith("..") || path.isAbsolute(rel)) throw new Error("imported signing evidence escapes consumer source root");
57
96
  fs.mkdirSync(path.dirname(destination), { recursive: true });
58
97
  fs.cpSync(path.dirname(resultPath), destination, { recursive: true, force: false, errorOnExist: true });
98
+ if (request.artifact.kind === "app-bundle") {
99
+ credentialArtifacts.push({
100
+ id: entry.id,
101
+ root: path.join(destination, "credential-artifact"),
102
+ manifest: path.join(
103
+ destination,
104
+ "credential-artifact",
105
+ "manifest.json",
106
+ ),
107
+ });
108
+ }
59
109
  imported.push({ id: entry.id, path: request.artifact.path, resultDigest: result.digest });
60
110
  }
111
+ if (credentialArtifacts.length > 1)
112
+ throw new Error(
113
+ "one platform artifact may project at most one native app release payload",
114
+ );
61
115
  writeGitHubOutputs({
62
116
  "imported-count": String(imported.length),
63
117
  "imported-json": JSON.stringify(imported),
64
118
  "evidence-root": path.resolve(sourceRoot, evidenceRoot),
119
+ "credential-artifact-count": String(credentialArtifacts.length),
120
+ "credential-artifact-root":
121
+ credentialArtifacts.length === 1 ? credentialArtifacts[0].root : "",
122
+ "credential-manifest-path":
123
+ credentialArtifacts.length === 1 ? credentialArtifacts[0].manifest : "",
65
124
  });
66
- return { ...verification, imported };
125
+ return { ...verification, imported, credentialArtifacts };
67
126
  }
68
127
 
69
128
  if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {