@kungfu-tech/buildchain 4.0.2-alpha.9 → 4.0.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.
Files changed (157) hide show
  1. package/AGENTS.md +10 -1
  2. package/CONTRIBUTING.md +24 -0
  3. package/README.md +2 -2
  4. package/actions/promote-buildchain-ref/README.md +1 -1
  5. package/actions/v4-release-candidate-promote/README.md +28 -0
  6. package/architecture/agent-change-map.md +64 -7
  7. package/architecture/ci-lane-change-budget.json +366 -0
  8. package/architecture/internal-capabilities.json +53 -8
  9. package/architecture/maintainability-debt.json +147 -93
  10. package/architecture/maintainability-policy.json +32 -28
  11. package/architecture/release-tail-contract-inventory.json +6 -6
  12. package/architecture/v4-architecture-constitution.md +23 -20
  13. package/architecture/v4-cross-platform-adopter-qualification.json +1 -1
  14. package/architecture/v4-delivery-authority-parity.json +2 -2
  15. package/architecture/v4-delivery-warrant-shadow-bootstrap-plan.json +1 -1
  16. package/architecture/v4-floating-consumer-policy.json +2 -2
  17. package/architecture/v4-next-development-parity.json +5 -5
  18. package/architecture/v4-partial-mutation-recovery-qualification.json +4 -1
  19. package/architecture/v4-platform-stage-checkpoints.json +1 -1
  20. package/architecture/v4-provider-operation-journal-contract.json +5 -2
  21. package/architecture/v4-publication-facade-patches.json +64 -0
  22. package/architecture/v4-release-activation-shadow-domain.json +5 -2
  23. package/architecture/v4-release-topology.json +74 -39
  24. package/architecture/v4-rust-wasm-production-authority.json +58 -0
  25. package/architecture/v4-stable-publication-fence.json +5 -2
  26. package/architecture/v4-stage-capsule-qualification.json +2 -2
  27. package/architecture/v4-tail-reseal-parity.json +8 -6
  28. package/architecture/v4-universal-workflow-bootstrap.json +171 -0
  29. package/architecture/v4-universal-workflow-fault-campaign.json +72 -0
  30. package/architecture/v4-universal-workflow-train-admission.json +40 -0
  31. package/architecture/workflow-taxonomy.json +1451 -0
  32. package/bin/buildchain.mjs +7 -7
  33. package/contracts/buildchain-v2-residuals-v1.json +57 -48
  34. package/contracts/dev-delivery-authority-v2.schema.json +16 -2
  35. package/contracts/fixtures/v4-tail-reseal-v1/valid.json +1 -1
  36. package/contracts/v4-canonical-contracts-v1.schema.json +1 -0
  37. package/dist/site/buildchain-contract.json +93 -48
  38. package/dist/site/buildchain-site.json +222 -69
  39. package/dist/site/capability-registry.json +4 -4
  40. package/dist/site/controller-registry.json +50 -13
  41. package/dist/site/kfd-claims.json +2585 -1000
  42. package/dist/site/kfd-upstream-aggregate.json +1 -1
  43. package/dist/site/manual-registry.json +13 -13
  44. package/dist/site/node-api-registry.json +120 -115
  45. package/dist/site/page-registry.json +204 -51
  46. package/dist/site/public-surface-audit.json +3205 -1219
  47. package/dist/site/publication-authority-registry.json +1124 -362
  48. package/dist/site/publication-registry.json +4 -4
  49. package/dist/site/schemas/dev-delivery-authority-v2.schema.json +16 -2
  50. package/dist/site/site-manifest.json +17 -17
  51. package/dist/site/workflow-registry.json +5061 -1659
  52. package/docs/MAP.md +1 -0
  53. package/docs/auditable-demo.md +1 -1
  54. package/docs/cli.md +21 -8
  55. package/docs/dev-delivery-warrant.md +1 -1
  56. package/docs/engineering-housekeeper.md +3 -3
  57. package/docs/kfd-support.md +26 -2
  58. package/docs/next-development-transition.md +2 -2
  59. package/docs/node-api-reference.md +190 -190
  60. package/docs/publication-authority.md +2 -2
  61. package/docs/release-candidate.md +3 -3
  62. package/docs/release-flow.md +67 -19
  63. package/docs/release-governance.md +8 -8
  64. package/docs/release-passport.md +27 -2
  65. package/docs/reusable-build-surface.md +16 -0
  66. package/docs/v4-adopter-delivery.md +2 -2
  67. package/docs/v4-production-release.md +71 -8
  68. package/docs/v4-rust-wasm-production-authority.md +58 -0
  69. package/docs/v4-stage-capsule.md +2 -2
  70. package/docs/v4-tail-reseal.md +1 -1
  71. package/docs/workflow-catalog.md +154 -0
  72. package/docs/workflow-path-migration.md +109 -0
  73. package/package.json +10 -6
  74. package/packages/core/artifact-verification-envelope.js +5 -5
  75. package/packages/core/buildchain-contract.js +1 -1
  76. package/packages/core/buildchain-kfd-claims.js +2 -2
  77. package/packages/core/buildchain-publication-authority.js +42 -26
  78. package/packages/core/buildchain-v4-domain.wasm +0 -0
  79. package/packages/core/ci-lane-change-budget.js +14 -1
  80. package/packages/core/controller-evidence.js +4 -1
  81. package/packages/core/dev-delivery-candidate-identity.js +40 -6
  82. package/packages/core/dev-delivery-warrant-state.js +3 -4
  83. package/packages/core/dev-delivery-warrant.js +12 -2
  84. package/packages/core/github-governance-authority.js +4 -4
  85. package/packages/core/kfd-adopter-manifest.js +46 -47
  86. package/packages/core/next-development-projection.js +2 -2
  87. package/packages/core/paper-agent-entry.js +30 -4
  88. package/packages/core/paper-fleet.js +1 -1
  89. package/packages/core/paper-runtime-channels.js +348 -0
  90. package/packages/core/paper-scaffold-content.js +10 -0
  91. package/packages/core/paper.js +37 -71
  92. package/packages/core/release-line-dry-run.js +1 -1
  93. package/packages/core/release-passport-contract.js +5 -5
  94. package/packages/core/release-passport.js +5 -4
  95. package/packages/core/release-tail-provider-plane.js +68 -1048
  96. package/packages/core/v4-canonical-contracts.js +10 -75
  97. package/packages/core/v4-domain-wasm-artifact.js +7 -0
  98. package/packages/core/v4-domain-wasm.js +216 -0
  99. package/packages/core/v4-floating-consumer-evidence.js +1 -1
  100. package/packages/core/v4-floating-consumer-policy.js +1 -1
  101. package/packages/core/v4-partial-mutation-recovery-qualification.js +9 -386
  102. package/packages/core/v4-product-publication.js +19 -349
  103. package/packages/core/v4-protected-publication-source.js +19 -21
  104. package/packages/core/v4-provider-operation-journal.js +18 -487
  105. package/packages/core/v4-provider-readback-idempotency.js +12 -97
  106. package/packages/core/v4-release-activation-shadow.js +13 -500
  107. package/packages/core/v4-release-invocation.js +28 -389
  108. package/packages/core/v4-stable-publication-fence.js +13 -342
  109. package/packages/core/v4-universal-workflow-bootstrap.js +586 -0
  110. package/scripts/binary-publication-evidence.mjs +135 -0
  111. package/scripts/build-contract-core.mjs +1 -14
  112. package/scripts/build-v4-domain-wasm.mjs +189 -0
  113. package/scripts/check-action-bundles.mjs +12 -2
  114. package/scripts/check-ci-lane-change-budget.mjs +4 -1
  115. package/scripts/check-core-mechanism-inventory.mjs +4 -3
  116. package/scripts/check-inventory.mjs +35 -44
  117. package/scripts/check-maintainability.mjs +16 -113
  118. package/scripts/check-v4-floating-consumer-policy-contract.mjs +2 -2
  119. package/scripts/check-v4-public-dogfood-contract.mjs +22 -9
  120. package/scripts/check-v4-release-topology.mjs +90 -33
  121. package/scripts/check-v4-runtime-semantic-closure.mjs +4 -1
  122. package/scripts/check-v4-universal-workflow-bootstrap.mjs +266 -0
  123. package/scripts/check-workflow-taxonomy.mjs +6 -0
  124. package/scripts/copy-v4-domain-wasm.mjs +14 -0
  125. package/scripts/dev-delivery-authority-command-adapters.mjs +1 -1
  126. package/scripts/dev-delivery-authority.mjs +8 -8
  127. package/scripts/dev-delivery-source-proof-reuse.mjs +2 -1
  128. package/scripts/dev-delivery-warrant-options.mjs +14 -9
  129. package/scripts/dev-delivery-warrant-store.mjs +73 -7
  130. package/scripts/dev-delivery-warrant-transition.mjs +109 -0
  131. package/scripts/dev-delivery-warrant.mjs +76 -99
  132. package/scripts/dev-pr-auto-merge.mjs +15 -15
  133. package/scripts/generate-channel-build-workflow.mjs +7 -2
  134. package/scripts/generate-channel-promotion-workflow.mjs +7 -5
  135. package/scripts/generate-site-bundle.mjs +11 -11
  136. package/scripts/generate-v4-universal-workflow-facades.mjs +458 -0
  137. package/scripts/generate-workflow-taxonomy.mjs +25 -0
  138. package/scripts/github-output.mjs +31 -0
  139. package/scripts/maintainability-metrics.mjs +16 -5
  140. package/scripts/maintainability-public-surface.mjs +115 -0
  141. package/scripts/next-development-review.mjs +307 -0
  142. package/scripts/paper-work-fleet-cli.mjs +2 -0
  143. package/scripts/release-asset-client.mjs +91 -0
  144. package/scripts/release-candidate-resolver.mjs +9 -9
  145. package/scripts/source-verification-evidence.mjs +344 -0
  146. package/scripts/stable-candidate-qualification.mjs +1 -1
  147. package/scripts/universal-facade-maintainability.mjs +82 -0
  148. package/scripts/v4-product-publication-intent.mjs +13 -6
  149. package/scripts/v4-publication-settlement.mjs +211 -0
  150. package/scripts/v4-universal-workflow-backflow.mjs +212 -0
  151. package/scripts/v4-universal-workflow-engine.mjs +618 -0
  152. package/scripts/v4-universal-workflow-self-dogfood.mjs +205 -0
  153. package/scripts/verify-version-state-delta.mjs +173 -0
  154. package/scripts/workflow-taxonomy.mjs +507 -0
  155. package/templates/native-dev-delivery.yml +4 -7
  156. package/templates/universal-buildchain-bootstrap-recovery.yml +282 -0
  157. package/templates/universal-buildchain-bootstrap.yml +28 -0
@@ -0,0 +1,618 @@
1
+ #!/usr/bin/env node
2
+ import crypto from "node:crypto";
3
+ import fs from "node:fs";
4
+ import { currentWorkflowPath } from "./workflow-taxonomy.mjs";
5
+ import path from "node:path";
6
+ import { execFileSync } from "node:child_process";
7
+ import {
8
+ admitV4UniversalWorkflow,
9
+ completeV4UniversalWorkflow,
10
+ selectV4RecoveredProductPublicationVersion, v4ProductStateVersion,
11
+ validateV4UniversalWorkflowRequest,
12
+ v4UniversalWorkflowRequestRoot,
13
+ } from "../packages/core/v4-universal-workflow-bootstrap.js";
14
+ import {
15
+ createV4ReleaseInvocation,
16
+ createV4ReleaseReceipt,
17
+ createV4ReleaseTransaction,
18
+ planV4ReleaseRoute,
19
+ } from "../packages/core/v4-release-invocation.js";
20
+ import { assertV4DeclarativePromotionInputs } from "../packages/core/v4-publication-qualification.js";
21
+ import {
22
+ githubJson,
23
+ resolveReleaseCandidateArtifacts,
24
+ } from "./release-candidate-resolver.mjs";
25
+
26
+ function fail(message) {
27
+ throw new Error(message);
28
+ }
29
+ function readJsonEnvironment(name) {
30
+ const source = process.env[name];
31
+ if (!source) fail(`${name} is required`);
32
+ try {
33
+ return JSON.parse(source);
34
+ } catch (error) {
35
+ throw new Error(`${name} is not valid JSON`, { cause: error });
36
+ }
37
+ }
38
+ function exactRuntime(admission) {
39
+ const runtime = admission?.runtime;
40
+ if (
41
+ admission?.status !== "admitted" ||
42
+ runtime?.repository !== "kungfu-systems/buildchain" ||
43
+ !/^[0-9a-f]{40}$/u.test(runtime?.sha || "")
44
+ )
45
+ fail("an exact admitted Buildchain runtime is required");
46
+ return runtime;
47
+ }
48
+ function contentRoot(domain, value) {
49
+ const hash = crypto.createHash("sha256");
50
+ hash.update(domain, "utf8");
51
+ hash.update(Buffer.from([0]));
52
+ hash.update(`${JSON.stringify(value)}\n`, "utf8");
53
+ return `sha256:${hash.digest("hex")}`;
54
+ }
55
+ function emit(value) {
56
+ process.stdout.write(`${JSON.stringify(value)}\n`);
57
+ }
58
+ function exactObject(value, keys, label) {
59
+ if (!value || typeof value !== "object" || Array.isArray(value))
60
+ fail(`${label} must be an object`);
61
+ const actual = Object.keys(value).sort();
62
+ const expected = [...keys].sort();
63
+ if (
64
+ actual.length !== expected.length ||
65
+ actual.some((key, index) => key !== expected[index])
66
+ )
67
+ fail(`${label} has an invalid field set`);
68
+ }
69
+
70
+ function validateWorkflowInputs(workflowId, inputs) {
71
+ const registry = JSON.parse(
72
+ fs.readFileSync(
73
+ new URL("../dist/site/workflow-registry.json", import.meta.url),
74
+ ),
75
+ );
76
+ const workflow = registry.workflows.find((entry) => entry.id === workflowId);
77
+ if (!workflow) fail(`candidate workflow registry is missing ${workflowId}`);
78
+ const allowed = new Set(workflow.inputs);
79
+ const unknown = Object.keys(inputs).filter((name) => !allowed.has(name));
80
+ if (unknown.length > 0)
81
+ fail(
82
+ `candidate payload has unregistered workflow inputs: ${unknown.sort().join(", ")}`,
83
+ );
84
+ }
85
+ const TRUSTED_PUBLISHING_NPM_VERSION = "11.13.0";
86
+ function prepareTrustedPublishingNpm() {
87
+ if (
88
+ !process.env.ACTIONS_ID_TOKEN_REQUEST_URL ||
89
+ !process.env.ACTIONS_ID_TOKEN_REQUEST_TOKEN
90
+ )
91
+ fail("trusted publishing requires GitHub Actions OIDC authority");
92
+ const binDirectory = path.resolve(".buildchain/runtime/bin");
93
+ const npmShim = path.join(binDirectory, "npm");
94
+ fs.mkdirSync(binDirectory, { recursive: true });
95
+ fs.writeFileSync(
96
+ npmShim,
97
+ `#!/bin/sh\nexec corepack pnpm@11.7.0 dlx npm@${TRUSTED_PUBLISHING_NPM_VERSION} "$@"\n`,
98
+ { mode: 0o755 },
99
+ );
100
+ fs.chmodSync(npmShim, 0o755);
101
+ process.env.PATH = `${binDirectory}${path.delimiter}${process.env.PATH || ""}`;
102
+ }
103
+ function prepareReleasePromotionConsumerDependencies(repository) {
104
+ if (repository !== "kungfu-systems/buildchain") return;
105
+ const manifest = JSON.parse(fs.readFileSync("package.json", "utf8"));
106
+ if (manifest.packageManager !== "pnpm@11.7.0" || !fs.existsSync("pnpm-lock.yaml")) fail("Buildchain release consumer dependency lock is unavailable");
107
+ const consumerModules = path.resolve("node_modules"), runtimeModules = path.resolve(".buildchain/runtime/node_modules");
108
+ if (!fs.existsSync(consumerModules) && fs.existsSync(runtimeModules)) fs.symlinkSync(path.relative(path.dirname(consumerModules), runtimeModules), consumerModules, "dir");
109
+ if (!fs.existsSync("node_modules/@kungfu-tech/kfd/package.json")) execFileSync("corepack", "pnpm@11.7.0 install --frozen-lockfile --ignore-scripts".split(" "), { stdio: ["ignore", 2, 2] });
110
+ if (fs.existsSync(runtimeModules)) {
111
+ if (fs.realpathSync(consumerModules) !== fs.realpathSync(runtimeModules)) fail("Buildchain release consumer runtime bridge conflicts");
112
+ return;
113
+ }
114
+ fs.mkdirSync(path.dirname(runtimeModules), { recursive: true }); fs.renameSync(consumerModules, runtimeModules);
115
+ fs.symlinkSync(path.relative(path.dirname(consumerModules), runtimeModules), consumerModules, "dir");
116
+ }
117
+ function actionEnvironment(inputs, outputPath) {
118
+ const environment = { ...process.env, GITHUB_OUTPUT: outputPath };
119
+ for (const [name, value] of Object.entries(inputs))
120
+ environment[`INPUT_${name.replace(/ /gu, "_").toUpperCase()}`] =
121
+ typeof value === "boolean" ? String(value) : String(value ?? "");
122
+ return environment;
123
+ }
124
+
125
+ function readGitHubOutputs(outputPath) {
126
+ const source = fs.readFileSync(outputPath, "utf8");
127
+ const outputs = {};
128
+ const lines = source.split("\n");
129
+ for (let index = 0; index < lines.length; index += 1) {
130
+ const line = lines[index];
131
+ const heredoc = line.match(/^([^<]+)<<(.+)$/u);
132
+ if (heredoc) {
133
+ const values = [];
134
+ while (index + 1 < lines.length && lines[index + 1] !== heredoc[2])
135
+ values.push(lines[(index += 1)]);
136
+ if (index + 1 >= lines.length)
137
+ fail(`GitHub output ${heredoc[1]} has no closing delimiter`);
138
+ index += 1;
139
+ outputs[heredoc[1]] = values.join("\n");
140
+ continue;
141
+ }
142
+ const separator = line.indexOf("=");
143
+ if (separator > 0)
144
+ outputs[line.slice(0, separator)] = line.slice(separator + 1);
145
+ }
146
+ return outputs;
147
+ }
148
+
149
+ function rawFileRoot(relative) {
150
+ const hash = crypto.createHash("sha256");
151
+ hash.update(fs.readFileSync(new URL(`../${relative}`, import.meta.url)));
152
+ return `sha256:${hash.digest("hex")}`;
153
+ }
154
+
155
+ function executeBootstrapConformance(request, admission) {
156
+ exactObject(
157
+ request.payload,
158
+ ["schema", "expectedGovernedWorkflowCount"],
159
+ "bootstrap conformance payload",
160
+ );
161
+ if (
162
+ request.payload.schema !==
163
+ "kungfu-buildchain-v4-universal-bootstrap-conformance/v1"
164
+ )
165
+ fail("bootstrap conformance payload schema is unsupported");
166
+ if (
167
+ !Number.isSafeInteger(request.payload.expectedGovernedWorkflowCount) ||
168
+ request.payload.expectedGovernedWorkflowCount < 1
169
+ )
170
+ fail("bootstrap conformance workflow count is invalid");
171
+ const architecture = JSON.parse(
172
+ fs.readFileSync(
173
+ new URL(
174
+ "../architecture/v4-universal-workflow-bootstrap.json",
175
+ import.meta.url,
176
+ ),
177
+ ),
178
+ );
179
+ const policy = JSON.parse(
180
+ fs.readFileSync(
181
+ new URL(
182
+ "../architecture/v4-universal-workflow-train-admission.json",
183
+ import.meta.url,
184
+ ),
185
+ ),
186
+ );
187
+ if (
188
+ architecture.bootstrapGovernedWorkflows.length !==
189
+ request.payload.expectedGovernedWorkflowCount ||
190
+ architecture.migration.candidateEngine !== "bounded-capability-adapters" ||
191
+ policy.allowedCapabilities.includes("workflow-contract")
192
+ )
193
+ fail("candidate Bootstrap conformance does not close execution semantics");
194
+ for (const relative of architecture.bootstrapGovernedWorkflows) {
195
+ const source = fs.readFileSync(
196
+ path.join(path.resolve(import.meta.dirname, ".."), currentWorkflowPath(path.resolve(import.meta.dirname, ".."), relative)),
197
+ "utf8",
198
+ );
199
+ if (
200
+ relative !== architecture.bootstrap.publicWorkflow &&
201
+ !/(?:\.\/)?\.github\/workflows\/bootstrap\.yml/u.test(source)
202
+ )
203
+ fail(`candidate Bootstrap facade is not governed: ${relative}`);
204
+ }
205
+ return {
206
+ schema: "kungfu-buildchain-v4-universal-bootstrap-conformance-result/v1",
207
+ status: "candidate-engine-executed",
208
+ runtime: exactRuntime(admission),
209
+ governedWorkflowCount: architecture.bootstrapGovernedWorkflows.length,
210
+ architectureRoot: rawFileRoot(
211
+ "architecture/v4-universal-workflow-bootstrap.json",
212
+ ),
213
+ engineRoot: rawFileRoot("scripts/v4-universal-workflow-engine.mjs"),
214
+ };
215
+ }
216
+
217
+ async function observeReleaseRoute({
218
+ repository,
219
+ targetRef,
220
+ requestedSha,
221
+ inputs,
222
+ }) {
223
+ const token = process.env.GH_TOKEN || process.env.GITHUB_TOKEN || "";
224
+ const apiUrl = process.env.GITHUB_API_URL || "https://api.github.com";
225
+ const observed = await githubJson({
226
+ apiUrl,
227
+ token,
228
+ path: `/repos/${repository}/git/ref/heads/${targetRef}`,
229
+ });
230
+ const currentSha = observed.object.sha;
231
+ let comparisonStatus = "identical";
232
+ if (currentSha !== requestedSha) {
233
+ const comparison = await githubJson({
234
+ apiUrl,
235
+ token,
236
+ path: `/repos/${repository}/compare/${requestedSha}...${currentSha}`,
237
+ });
238
+ comparisonStatus = comparison.status;
239
+ }
240
+ return planV4ReleaseRoute({
241
+ requestedSha,
242
+ observedSha: currentSha,
243
+ comparisonStatus,
244
+ requestedChannel: inputs.channel,
245
+ targetRef,
246
+ dryRun: inputs["dry-run"] === true,
247
+ resume:
248
+ String(inputs["resume-candidate-run-id"] || "") !== "" ||
249
+ inputs["publish-transaction-override"] === true,
250
+ });
251
+ }
252
+
253
+ async function observedSourceTimestamp(repository, sourceSha) {
254
+ const token = process.env.GH_TOKEN || process.env.GITHUB_TOKEN || "";
255
+ const apiUrl = process.env.GITHUB_API_URL || "https://api.github.com";
256
+ const commit = await githubJson({
257
+ apiUrl,
258
+ token,
259
+ path: `/repos/${repository}/git/commits/${sourceSha}`,
260
+ });
261
+ const observed = String(
262
+ commit?.committer?.date || commit?.author?.date || "",
263
+ ).trim();
264
+ if (!observed || Number.isNaN(Date.parse(observed)))
265
+ fail("protected publication source timestamp is unavailable");
266
+ return new Date(observed).toISOString();
267
+ }
268
+ async function observeProductPublicationRecovery(repository, sourceSha, candidateVersion) {
269
+ const apiUrl = process.env.GITHUB_API_URL || "https://api.github.com", token = process.env.GH_TOKEN || process.env.GITHUB_TOKEN || "", prefix = `heads/buildchain/v4-product-state/${sourceSha}-`;
270
+ const stateRefs = (await githubJson({ apiUrl, token, path: `/repos/${repository}/git/matching-refs/${prefix}`, allowNotFound: true })) || [];
271
+ const recoveryStates = await Promise.all(stateRefs.map(async (stateRef) => { const version = v4ProductStateVersion(stateRef, sourceSha); const [stateCommit, exactTagRef] = await Promise.all([githubJson({ apiUrl, token, path: `/repos/${repository}/git/commits/${stateRef.object.sha}` }), githubJson({ apiUrl, token, path: `/repos/${repository}/git/ref/tags/v${version}`, allowNotFound: true })]); return { stateRef, stateCommit, exactTagRef }; }));
272
+ const exactTagRef = recoveryStates.length ? undefined : await githubJson({ apiUrl, token, path: `/repos/${repository}/git/ref/tags/v${encodeURIComponent(candidateVersion)}`, allowNotFound: true }); return { recoveryStates, exactTagRef };
273
+ }
274
+ async function materializeProductPublicationIntent({
275
+ candidate,
276
+ inputs,
277
+ repository,
278
+ route,
279
+ }) {
280
+ const intentPath = path.resolve(
281
+ ".buildchain/release-candidate/v4-product-publication-intent.json",
282
+ );
283
+ const sourceTimestamp = await observedSourceTimestamp(repository, route.requestedSha);
284
+ const version = String(candidate.publicationVersion || candidate.version || "").trim(), explicitResume = String(inputs["resume-transaction-id"] || "") !== "";
285
+ const recovery = route.decision !== "Resume" || explicitResume ? {} : await observeProductPublicationRecovery(repository, route.requestedSha, version);
286
+ const recoveredVersion = selectV4RecoveredProductPublicationVersion({ routeDecision: route.decision, candidateVersion: version, requestedSha: route.requestedSha, explicitResume, ...recovery });
287
+ execFileSync(
288
+ process.execPath,
289
+ [
290
+ path.resolve(
291
+ ".buildchain/candidate/scripts/v4-product-publication-intent.mjs",
292
+ ),
293
+ ],
294
+ {
295
+ env: {
296
+ ...process.env,
297
+ GITHUB_OUTPUT: "",
298
+ BUILDCHAIN_REPOSITORY: repository,
299
+ BUILDCHAIN_CHANNEL: route.channel,
300
+ BUILDCHAIN_TARGET_REF: route.targetRef,
301
+ BUILDCHAIN_SOURCE_SHA: route.requestedSha,
302
+ BUILDCHAIN_SOURCE_TIMESTAMP: sourceTimestamp,
303
+ BUILDCHAIN_CANDIDATE_VERSION:
304
+ candidate.publicationVersion || candidate.version,
305
+ BUILDCHAIN_RECOVERED_PUBLICATION_VERSION: recoveredVersion,
306
+ BUILDCHAIN_SEALED_BUNDLE_MANIFEST: candidate.paths.sealedBundleManifest,
307
+ BUILDCHAIN_REQUIRED_ARTIFACTS_PATH:
308
+ candidate.paths.publishRequiredArtifacts,
309
+ BUILDCHAIN_PUBLISH_PACKAGE_MAIN: inputs["publish-package-main"] || "",
310
+ BUILDCHAIN_PUBLISH_DIST_TAG: inputs["publish-dist-tag"] || "",
311
+ BUILDCHAIN_PRODUCT_PUBLICATION_INTENT_PATH: intentPath,
312
+ },
313
+ stdio: ["ignore", "ignore", "inherit"],
314
+ },
315
+ );
316
+ return intentPath;
317
+ }
318
+ function verifiedReleaseDocuments() {
319
+ const base = path.resolve(".buildchain/release-tail");
320
+ const invocation = JSON.parse(
321
+ fs.readFileSync(path.join(base, "release-invocation.json")),
322
+ );
323
+ const transaction = JSON.parse(
324
+ fs.readFileSync(path.join(base, "release-transaction.json")),
325
+ );
326
+ const storedReceipt = JSON.parse(
327
+ fs.readFileSync(path.join(base, "release-receipt.json")),
328
+ );
329
+ const invocationProjection = createV4ReleaseInvocation(invocation);
330
+ const transactionProjection = createV4ReleaseTransaction({
331
+ invocationRoot: transaction.invocationRoot,
332
+ publisherRoot: transaction.publisherRoot,
333
+ runtimeRoot: transaction.runtimeRoot,
334
+ providerRoot: transaction.providerRoot,
335
+ parentRoot: transaction.parentRoot,
336
+ });
337
+ const { receiptRoot, ...receipt } = storedReceipt;
338
+ const receiptProjection = createV4ReleaseReceipt(receipt);
339
+ if (
340
+ transaction.invocationRoot !== invocationProjection.roots.invocationRoot ||
341
+ transaction.transactionRoot !== transactionProjection.transactionRoot ||
342
+ receipt.transactionRoot !== transactionProjection.transactionRoot ||
343
+ receiptProjection.receiptRoot !== receiptRoot ||
344
+ receipt.outcome !== "complete"
345
+ )
346
+ fail("terminal ReleaseReceipt lineage does not verify");
347
+ return { invocation, transaction, receipt: storedReceipt };
348
+ }
349
+
350
+ async function executeReleasePromotion(request, admission) {
351
+ const payload = request.payload;
352
+ exactObject(
353
+ payload,
354
+ ["schema", "inputs", "dryRunObservation"],
355
+ "release promotion payload",
356
+ );
357
+ if (payload.schema !== "kungfu-buildchain-v4-universal-release-promotion/v1")
358
+ fail("release promotion payload schema is unsupported");
359
+ exactObject(
360
+ payload.inputs,
361
+ Object.keys(payload.inputs),
362
+ "release promotion inputs",
363
+ );
364
+ validateWorkflowInputs("release-candidate-promote", payload.inputs);
365
+ assertV4DeclarativePromotionInputs(payload.inputs);
366
+ const repository = request.consumer.repository;
367
+ const targetRef = String(payload.inputs["target-ref"] || "").replace(
368
+ /^refs\/heads\//u,
369
+ "",
370
+ );
371
+ const requestedSha = String(
372
+ payload.inputs["target-sha"] || request.consumer.sourceSha,
373
+ );
374
+ let route;
375
+ if (payload.inputs["dry-run"] === true && payload.dryRunObservation) {
376
+ exactObject(
377
+ payload.dryRunObservation,
378
+ ["observedSha", "comparisonStatus"],
379
+ "dry-run observation",
380
+ );
381
+ route = planV4ReleaseRoute({
382
+ requestedSha,
383
+ observedSha: payload.dryRunObservation.observedSha,
384
+ comparisonStatus: payload.dryRunObservation.comparisonStatus,
385
+ requestedChannel: payload.inputs.channel,
386
+ targetRef,
387
+ dryRun: true,
388
+ resume: false,
389
+ });
390
+ } else {
391
+ route = await observeReleaseRoute({
392
+ repository,
393
+ targetRef,
394
+ requestedSha,
395
+ inputs: payload.inputs,
396
+ });
397
+ }
398
+ if (route.decision === "Blocked")
399
+ fail(`release route blocked: ${route.reason}`);
400
+ if (route.decision === "NoOp" || payload.inputs["dry-run"] === true)
401
+ return { route, dryRun: payload.inputs["dry-run"] === true };
402
+ prepareReleasePromotionConsumerDependencies(repository);
403
+ if (payload.inputs["trusted-publishing"] === true)
404
+ prepareTrustedPublishingNpm();
405
+ const candidate = await resolveReleaseCandidateArtifacts({
406
+ repository,
407
+ targetRef: route.targetRef,
408
+ targetSha: route.requestedSha,
409
+ token: process.env.GH_TOKEN || process.env.GITHUB_TOKEN,
410
+ workflowFile: payload.inputs["release-candidate-workflow-file"],
411
+ workflowName: payload.inputs["release-candidate-workflow-name"],
412
+ artifactName: payload.inputs["artifact-name"],
413
+ artifactPatterns: payload.inputs["artifact-patterns"],
414
+ githubReleasePayloadPatterns:
415
+ payload.inputs["github-release-payload-patterns"],
416
+ requiredArtifactCount: payload.inputs["required-artifact-count"],
417
+ publishArtifactKind: payload.inputs["publish-artifact-kind"],
418
+ publishPackageMain: payload.inputs["publish-package-main"],
419
+ runtimeSha: admission.runtime.sha,
420
+ outputDir: ".buildchain/release-candidate",
421
+ waitSeconds: payload.inputs["release-candidate-wait-seconds"],
422
+ });
423
+ if (!candidate.enabled)
424
+ fail(candidate.reason || "release candidate is unavailable");
425
+ const productPublicationIntentPath = await materializeProductPublicationIntent({
426
+ candidate,
427
+ inputs: payload.inputs,
428
+ repository,
429
+ route,
430
+ });
431
+ const productPublicationIntent = JSON.parse(fs.readFileSync(productPublicationIntentPath));
432
+ const runtimeTree = execFileSync(
433
+ "git",
434
+ ["-C", ".buildchain/candidate", "rev-parse", "HEAD^{tree}"],
435
+ { encoding: "utf8" },
436
+ ).trim();
437
+ const actionInputs = {
438
+ token: process.env.GH_TOKEN || process.env.GITHUB_TOKEN,
439
+ "mutation-token":
440
+ process.env.BUILDCHAIN_PROMOTION_TOKEN || process.env.GH_TOKEN,
441
+ repository,
442
+ "source-sha": route.requestedSha,
443
+ version: productPublicationIntent.version,
444
+ tag: productPublicationIntent.exactTag,
445
+ channel: route.channel,
446
+ "target-ref": route.targetRef,
447
+ "target-sha": route.requestedSha,
448
+ "candidate-passport-path": candidate.paths.passport,
449
+ "candidate-build-summary-path": candidate.paths.buildSummary,
450
+ "stage-capsules-path": candidate.paths.stageCapsules,
451
+ "publication-qualification-path": candidate.paths.publicationQualification,
452
+ "sealed-bundle-root": candidate.paths.sealedBundleRoot,
453
+ "sealed-bundle-manifest": candidate.paths.sealedBundleManifest,
454
+ "required-artifacts-path": candidate.paths.publishRequiredArtifacts,
455
+ "product-publication-intent-path": productPublicationIntentPath,
456
+ "publisher-workflow-sha": admission.runtime.sha,
457
+ "runtime-commit": admission.runtime.sha,
458
+ "runtime-tree": runtimeTree,
459
+ "required-status-check": payload.inputs["required-status-check"],
460
+ "publish-command": payload.inputs["publish-command"],
461
+ "publish-mode": payload.inputs["publish-mode"],
462
+ "publish-dist-tag": payload.inputs["publish-dist-tag"],
463
+ "publish-package-set-order": payload.inputs["publish-package-set-order"],
464
+ "publish-package-main": payload.inputs["publish-package-main"],
465
+ "publish-auth": payload.inputs["trusted-publishing"]
466
+ ? "trusted-publishing"
467
+ : "npm-token",
468
+ "publish-rematerialize-on-resume":
469
+ payload.inputs["publish-rematerialize-on-resume"],
470
+ "publish-transaction-override":
471
+ payload.inputs["publish-transaction-override"],
472
+ "resume-transaction-id": payload.inputs["resume-transaction-id"],
473
+ "artifact-paths": candidate.paths.releaseAssets.join("\n"),
474
+ "state-path": ".buildchain/release-tail/state.json",
475
+ "failure-after-capability":
476
+ payload.inputs["provider-failure-after-capability"],
477
+ };
478
+ const actionOutput = path.resolve(".buildchain/universal-release-action.out");
479
+ fs.mkdirSync(path.dirname(actionOutput), { recursive: true });
480
+ fs.writeFileSync(actionOutput, "");
481
+ execFileSync(
482
+ process.execPath,
483
+ [
484
+ path.resolve(
485
+ ".buildchain/candidate/actions/v4-release-candidate-promote/dist/index.js",
486
+ ),
487
+ ],
488
+ { env: actionEnvironment(actionInputs, actionOutput), stdio: ["ignore", 2, 2] },
489
+ );
490
+ return {
491
+ route,
492
+ candidate: {
493
+ runId: candidate.run.id,
494
+ sourceSha: candidate.artifacts.sourceSha,
495
+ version: candidate.version,
496
+ },
497
+ provider: readGitHubOutputs(actionOutput),
498
+ release: verifiedReleaseDocuments(),
499
+ };
500
+ }
501
+
502
+ async function capabilityResult(request, admission) {
503
+ if (request.capability.id === "bootstrap-conformance")
504
+ return executeBootstrapConformance(request, admission);
505
+ if (request.capability.id === "release-invocation") {
506
+ const release = createV4ReleaseInvocation(request.payload);
507
+ return {
508
+ releaseInvocation: release.invocation,
509
+ releaseRoots: release.roots,
510
+ };
511
+ }
512
+ if (request.capability.id === "release-candidate-promote")
513
+ return executeReleasePromotion(request, admission);
514
+ fail(`candidate capability is not implemented: ${request.capability.id}`);
515
+ }
516
+ async function executeCandidate(request, admission) {
517
+ const runtime = exactRuntime(admission);
518
+ if (v4UniversalWorkflowRequestRoot(request) !== admission.requestRoot)
519
+ fail("candidate request does not match the admitted request root");
520
+ const engineSha = String(process.env.BUILDCHAIN_UNIVERSAL_ENGINE_SHA || "")
521
+ .trim()
522
+ .toLowerCase();
523
+ if (engineSha !== runtime.sha)
524
+ fail("candidate engine checkout does not match the admitted runtime SHA");
525
+ try {
526
+ return {
527
+ status: "succeeded",
528
+ output: await capabilityResult(request, admission),
529
+ };
530
+ } catch (error) {
531
+ return {
532
+ status: "failed",
533
+ error: {
534
+ code: String(error?.code || "candidate-execution-failed"),
535
+ message: "candidate capability execution failed",
536
+ },
537
+ };
538
+ }
539
+ }
540
+
541
+ function assertResultLineage(admission, result) {
542
+ if (
543
+ result?.schema !== "kungfu-buildchain-v4-universal-workflow-result/v1" ||
544
+ result.requestRoot !== admission.requestRoot ||
545
+ result.capabilityRoot !== admission.capabilityRoot ||
546
+ result.runtime?.repository !== admission.runtime?.repository ||
547
+ result.runtime?.sha !== admission.runtime?.sha ||
548
+ !["succeeded", "failed"].includes(result.status)
549
+ )
550
+ fail("candidate result does not match the admitted lineage");
551
+ }
552
+
553
+ const command = process.argv[2];
554
+ if (!command) fail("a command is required");
555
+
556
+ if (command === "inspect") {
557
+ const request = validateV4UniversalWorkflowRequest(
558
+ readJsonEnvironment("BUILDCHAIN_UNIVERSAL_REQUEST_JSON"),
559
+ );
560
+ emit({
561
+ schema: "kungfu-buildchain-v4-universal-workflow-inspection/v1",
562
+ requestRoot: v4UniversalWorkflowRequestRoot(request),
563
+ mode: request.mode,
564
+ candidate: request.candidate,
565
+ consumer: request.consumer,
566
+ capability: request.capability,
567
+ });
568
+ } else if (command === "admit") {
569
+ emit(
570
+ admitV4UniversalWorkflow({
571
+ request: readJsonEnvironment("BUILDCHAIN_UNIVERSAL_REQUEST_JSON"),
572
+ policy: readJsonEnvironment("BUILDCHAIN_UNIVERSAL_ADMISSION_POLICY_JSON"),
573
+ observedRefSha: process.env.BUILDCHAIN_UNIVERSAL_OBSERVED_SHA,
574
+ observedConsumerRepository:
575
+ process.env.BUILDCHAIN_UNIVERSAL_CONSUMER_REPOSITORY,
576
+ observedConsumerSha: process.env.BUILDCHAIN_UNIVERSAL_CONSUMER_SHA,
577
+ observedConsumerWorkflowRef:
578
+ process.env.BUILDCHAIN_UNIVERSAL_CONSUMER_WORKFLOW_REF,
579
+ reviewEvidence: readJsonEnvironment(
580
+ "BUILDCHAIN_UNIVERSAL_REVIEW_EVIDENCE_JSON",
581
+ ),
582
+ now: process.env.BUILDCHAIN_UNIVERSAL_OBSERVED_AT,
583
+ }),
584
+ );
585
+ } else if (command === "execute") {
586
+ const request = validateV4UniversalWorkflowRequest(
587
+ readJsonEnvironment("BUILDCHAIN_UNIVERSAL_REQUEST_JSON"),
588
+ );
589
+ const admission = readJsonEnvironment("BUILDCHAIN_UNIVERSAL_ADMISSION_JSON");
590
+ const runtime = exactRuntime(admission);
591
+ const execution = await executeCandidate(request, admission);
592
+ const result = {
593
+ schema: "kungfu-buildchain-v4-universal-workflow-result/v1",
594
+ status: execution.status,
595
+ requestRoot: admission.requestRoot,
596
+ runtime,
597
+ capabilityRoot: admission.capabilityRoot,
598
+ enginePath: "scripts/v4-universal-workflow-engine.mjs",
599
+ ...execution,
600
+ };
601
+ emit({
602
+ ...result,
603
+ resultRoot: contentRoot("universal-workflow-result", result),
604
+ });
605
+ } else if (command === "terminal") {
606
+ const admission = readJsonEnvironment("BUILDCHAIN_UNIVERSAL_ADMISSION_JSON");
607
+ const result = readJsonEnvironment("BUILDCHAIN_UNIVERSAL_RESULT_JSON");
608
+ assertResultLineage(admission, result);
609
+ emit(
610
+ completeV4UniversalWorkflow({
611
+ admission,
612
+ status: result.status,
613
+ resultRoot: result.resultRoot,
614
+ }),
615
+ );
616
+ } else {
617
+ fail(`unsupported command: ${command}`);
618
+ }