@kungfu-tech/buildchain 4.1.3-alpha.0 → 4.1.3-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 (278) hide show
  1. package/AGENTS.md +41 -75
  2. package/CONTRIBUTING.md +19 -26
  3. package/architecture/action-taxonomy.json +24 -0
  4. package/architecture/agent-change-map.md +272 -12
  5. package/architecture/capability-state-machine-manifest.json +1 -4
  6. package/architecture/ci-lane-change-budget.json +190 -807
  7. package/architecture/core-mechanisms.json +3 -6
  8. package/architecture/decisions/0005-minimal-consumer-contract.md +122 -0
  9. package/architecture/decisions/0006-business-attempt-journal.md +89 -0
  10. package/architecture/decisions/0007-hosted-pipeline-controller.md +106 -0
  11. package/architecture/decisions/0008-pipeline-product-publication.md +120 -0
  12. package/architecture/decisions/0009-pipeline-version-preparation.md +186 -0
  13. package/architecture/internal-capabilities.json +371 -9
  14. package/architecture/maintainability-debt.json +50 -34
  15. package/architecture/maintainability-policy.json +8 -8
  16. package/architecture/minimal-consumer-migration.json +5670 -0
  17. package/architecture/platform-stage-checkpoints.json +31 -7
  18. package/architecture/product-upstreams.json +20 -0
  19. package/architecture/release-topology.json +331 -74
  20. package/architecture/stage-capsule-qualification.json +3 -1
  21. package/architecture/universal-workflow-bootstrap.json +15 -1
  22. package/architecture/universal-workflow-capability-policy.json +9 -9
  23. package/architecture/universal-workflow-fault-campaign.json +3 -1
  24. package/architecture/workflow-taxonomy.json +77 -328
  25. package/contracts/release-discussion-v1.schema.json +3 -0
  26. package/dist/readers/business-attempt.cjs +809 -0
  27. package/dist/readers/release-discussion.cjs +4 -0
  28. package/dist/site/buildchain-contract.json +16 -68
  29. package/dist/site/buildchain-site.json +47 -27
  30. package/dist/site/capability-registry.json +4 -4
  31. package/dist/site/controller-registry.json +1 -47
  32. package/dist/site/kfd-claims.json +499 -494
  33. package/dist/site/kfd-upstream-aggregate.json +1 -1
  34. package/dist/site/manual-registry.json +3 -3
  35. package/dist/site/node-api-registry.json +56 -56
  36. package/dist/site/page-registry.json +40 -20
  37. package/dist/site/public-surface-audit.json +286 -349
  38. package/dist/site/publication-authority-registry.json +203 -523
  39. package/dist/site/publication-registry.json +4 -4
  40. package/dist/site/site-manifest.json +7 -7
  41. package/dist/site/workflow-registry.json +590 -940
  42. package/docs/code-organization.md +9 -6
  43. package/docs/node-api-reference.md +103 -103
  44. package/docs/release-discussions.md +44 -5
  45. package/docs/release-governance.md +10 -12
  46. package/docs/runtime-entry.md +73 -5
  47. package/docs/runtime-train-validation.md +39 -11
  48. package/docs/stable-candidate-patrol.md +6 -5
  49. package/docs/workflow-catalog.md +9 -32
  50. package/package.json +4 -3
  51. package/packages/core/adoption/buildchain-kfd-claims.js +7 -7
  52. package/packages/core/build/standalone/build.js +7 -1
  53. package/packages/core/consumer/buildchain-config.js +2 -21
  54. package/packages/core/consumer/contract/entries.js +85 -0
  55. package/packages/core/consumer/contract/examples.js +96 -0
  56. package/packages/core/consumer/contract/identity.js +65 -0
  57. package/packages/core/consumer/contract/inspection.js +45 -0
  58. package/packages/core/consumer/contract/plan.js +149 -0
  59. package/packages/core/consumer/contract/products.js +119 -0
  60. package/packages/core/consumer/contract/reader.js +47 -0
  61. package/packages/core/consumer/contract/shape.js +46 -0
  62. package/packages/core/dev-delivery/candidate/admission.js +9 -5
  63. package/packages/core/dev-delivery/candidate/reservation.js +11 -0
  64. package/packages/core/dev-delivery/candidate/source-paths.js +12 -4
  65. package/packages/core/dev-delivery/commands/project-cut-merge-queue-admission.mjs +47 -20
  66. package/packages/core/dev-delivery/dev-delivery-provider-heartbeat.js +18 -15
  67. package/packages/core/dev-delivery/native/actions.js +9 -1
  68. package/packages/core/dev-delivery/native/heartbeat-action.js +16 -0
  69. package/packages/core/dev-delivery/native/heartbeat.js +6 -4
  70. package/packages/core/dev-delivery/native/process-entry.js +19 -0
  71. package/packages/core/dev-delivery/queue/landing-action.js +53 -0
  72. package/packages/core/dev-delivery/warrant/failure-settlement.js +8 -6
  73. package/packages/core/dev-delivery/warrant/service.js +7 -0
  74. package/packages/core/governance/buildchain-publication-authority.js +8 -23
  75. package/packages/core/governance/github-governance-authority.js +1 -0
  76. package/packages/core/observability/controller-evidence.js +0 -8
  77. package/packages/core/paper/operations/bootstrap.js +2 -0
  78. package/packages/core/paper/paper-npm-bootstrap.js +2 -2
  79. package/packages/core/providers/github/attempt-index.js +60 -0
  80. package/packages/core/providers/github/attempt-journal.js +154 -0
  81. package/packages/core/providers/github/discussions/materials.js +35 -11
  82. package/packages/core/providers/github/discussions/transport.js +16 -5
  83. package/packages/core/providers/github/pipeline-checkout.js +49 -0
  84. package/packages/core/providers/github/pipeline-entry-runs.js +210 -0
  85. package/packages/core/providers/github/pipeline-events.js +131 -0
  86. package/packages/core/providers/github/pipeline-integration.js +95 -0
  87. package/packages/core/providers/github/pipeline-policy.js +240 -0
  88. package/packages/core/providers/github/pipeline-product-payload.js +50 -0
  89. package/packages/core/providers/github/pipeline-product-release.js +198 -0
  90. package/packages/core/providers/github/pipeline-publication-artifacts.js +153 -0
  91. package/packages/core/providers/github/pipeline-release-evidence.js +30 -0
  92. package/packages/core/providers/github/pipeline-run-entry.js +86 -0
  93. package/packages/core/providers/github/pipeline-runs.js +132 -0
  94. package/packages/core/providers/github/pipeline-source.js +195 -0
  95. package/packages/core/providers/github/pipeline-stable-baseline.js +86 -0
  96. package/packages/core/providers/github/pipeline-stable-source.js +372 -0
  97. package/packages/core/providers/github/pipeline-version-artifacts.js +91 -0
  98. package/packages/core/providers/github/pipeline-version-readback.js +117 -0
  99. package/packages/core/providers/github/pipeline-version.js +199 -0
  100. package/packages/core/providers/github/pipeline-worker.js +108 -0
  101. package/packages/core/providers/github/release-evidence-assets.js +97 -0
  102. package/packages/core/publication/candidate/registry-hydration.js +2 -2
  103. package/packages/core/publication/npm/pack-preview.js +2 -2
  104. package/packages/core/publication/npm/pack-result.js +31 -0
  105. package/packages/core/publication/npm/package.js +3 -6
  106. package/packages/core/publication/npm/pipeline-channel.js +36 -0
  107. package/packages/core/publication/npm/pipeline-provider.js +137 -0
  108. package/packages/core/publication/npm/registry.js +12 -9
  109. package/packages/core/publication/pipeline/actions.js +116 -0
  110. package/packages/core/publication/pipeline/apply.js +189 -0
  111. package/packages/core/publication/pipeline/build-segments.js +107 -0
  112. package/packages/core/publication/pipeline/capsules.js +92 -0
  113. package/packages/core/publication/pipeline/context.js +39 -0
  114. package/packages/core/publication/pipeline/development-anchor.js +91 -0
  115. package/packages/core/publication/pipeline/development-current.js +63 -0
  116. package/packages/core/publication/pipeline/development-pr.js +112 -0
  117. package/packages/core/publication/pipeline/development-proof.js +76 -0
  118. package/packages/core/publication/pipeline/development-transition.js +112 -0
  119. package/packages/core/publication/pipeline/distribution.js +130 -0
  120. package/packages/core/publication/pipeline/documents.js +192 -0
  121. package/packages/core/publication/pipeline/effects.js +192 -0
  122. package/packages/core/publication/pipeline/files.js +66 -0
  123. package/packages/core/publication/pipeline/imported-materials.js +32 -0
  124. package/packages/core/publication/pipeline/journal.js +122 -0
  125. package/packages/core/publication/pipeline/next-development.js +117 -0
  126. package/packages/core/publication/pipeline/pack.js +180 -0
  127. package/packages/core/publication/pipeline/package-policy.js +22 -0
  128. package/packages/core/publication/pipeline/plan.js +136 -0
  129. package/packages/core/publication/pipeline/prepare.js +139 -0
  130. package/packages/core/publication/pipeline/qualification.js +175 -0
  131. package/packages/core/publication/pipeline/qualify.js +104 -0
  132. package/packages/core/publication/pipeline/recovery-admission.js +122 -0
  133. package/packages/core/publication/pipeline/recovery-build-download.js +80 -0
  134. package/packages/core/publication/pipeline/recovery-build-plan.js +122 -0
  135. package/packages/core/publication/pipeline/recovery-build-readback.js +68 -0
  136. package/packages/core/publication/pipeline/recovery-capsules.js +54 -0
  137. package/packages/core/publication/pipeline/recovery-import.js +80 -0
  138. package/packages/core/publication/pipeline/recovery-inspection.js +218 -0
  139. package/packages/core/publication/pipeline/recovery-materials.js +104 -0
  140. package/packages/core/publication/pipeline/recovery-plan.js +46 -0
  141. package/packages/core/publication/pipeline/recovery-prepare.js +73 -0
  142. package/packages/core/publication/pipeline/recovery-qualification.js +128 -0
  143. package/packages/core/publication/pipeline/recovery-readback.js +56 -0
  144. package/packages/core/publication/pipeline/recovery-signing.js +58 -0
  145. package/packages/core/publication/pipeline/sealed-products.js +79 -0
  146. package/packages/core/publication/pipeline/settle.js +42 -0
  147. package/packages/core/publication/pipeline/signing.js +140 -0
  148. package/packages/core/publication/pipeline/source-plan.js +83 -0
  149. package/packages/core/publication/pipeline/stable-entry.js +219 -0
  150. package/packages/core/publication/pipeline/stable-products.js +317 -0
  151. package/packages/core/publication/pipeline/stable-wait.js +170 -0
  152. package/packages/core/publication/pipeline/stable.js +82 -0
  153. package/packages/core/publication/pipeline/version-actions.js +93 -0
  154. package/packages/core/publication/pipeline/version-build.js +130 -0
  155. package/packages/core/publication/pipeline/version-context.js +143 -0
  156. package/packages/core/publication/pipeline/version-material.js +36 -0
  157. package/packages/core/publication/pipeline/version-preparation.js +69 -0
  158. package/packages/core/publication/pipeline/version-regeneration.js +162 -0
  159. package/packages/core/publication/pipeline/version-source-guard.js +69 -0
  160. package/packages/core/publication/pipeline/version.js +81 -0
  161. package/packages/core/publication/pipeline/worker.js +72 -0
  162. package/packages/core/publication/publication-reproducibility.js +2 -2
  163. package/packages/core/release/discussion/binary.js +11 -0
  164. package/packages/core/release/discussion/checkpoints.js +45 -4
  165. package/packages/core/release/discussion/envelope.js +1 -0
  166. package/packages/core/release/discussion/evidence.js +51 -0
  167. package/packages/core/release/discussion/presentation.js +92 -0
  168. package/packages/core/release/discussion/publication.js +3 -1
  169. package/packages/core/release/discussion/qualification.js +1 -0
  170. package/packages/core/release/discussion/reader.js +9 -0
  171. package/packages/core/release/discussion/session.js +2 -0
  172. package/packages/core/release/discussion/store.js +28 -31
  173. package/packages/core/release/discussion/threads.js +83 -0
  174. package/packages/core/release/next-development-transition.js +15 -15
  175. package/packages/core/release/promote-candidate/product-provider-adapters.js +4 -3
  176. package/packages/core/release/promote-ref/internal/npm-existing-evidence.js +2 -1
  177. package/packages/core/release/promote-ref/internal/publish-command.js +16 -5
  178. package/packages/core/release/stable-release-gate.js +11 -0
  179. package/packages/core/runtime/buildchain-domain.wasm +0 -0
  180. package/packages/core/runtime/domain-wasm-artifact.js +2 -2
  181. package/packages/core/runtime/entry/actions.js +27 -25
  182. package/packages/core/runtime/entry/attempt.js +25 -0
  183. package/packages/core/runtime/entry/selection.js +14 -0
  184. package/packages/core/runtime/entry/source.js +62 -0
  185. package/packages/core/workflow/attempt/identity.js +170 -0
  186. package/packages/core/workflow/attempt/journal.js +89 -0
  187. package/packages/core/workflow/attempt/materials.js +72 -0
  188. package/packages/core/workflow/attempt/reader-entry.js +12 -0
  189. package/packages/core/workflow/attempt/reader.js +149 -0
  190. package/packages/core/workflow/attempt/records.js +153 -0
  191. package/packages/core/workflow/attempt/store.js +75 -0
  192. package/packages/core/workflow/commands/pipeline-build.mjs +23 -0
  193. package/packages/core/workflow/pipeline/action-output.js +11 -0
  194. package/packages/core/workflow/pipeline/actions.js +52 -0
  195. package/packages/core/workflow/pipeline/build-control.js +140 -0
  196. package/packages/core/workflow/pipeline/build-evidence.js +210 -0
  197. package/packages/core/workflow/pipeline/build-qualification.js +145 -0
  198. package/packages/core/workflow/pipeline/build-result.js +78 -0
  199. package/packages/core/workflow/pipeline/build.js +142 -0
  200. package/packages/core/workflow/pipeline/cancellation.js +169 -0
  201. package/packages/core/workflow/pipeline/channel-control.js +157 -0
  202. package/packages/core/workflow/pipeline/channel-source.js +53 -0
  203. package/packages/core/workflow/pipeline/controller.js +141 -0
  204. package/packages/core/workflow/pipeline/delivery-control.js +249 -0
  205. package/packages/core/workflow/pipeline/delivery-observation.js +124 -0
  206. package/packages/core/workflow/pipeline/delivery-request.js +198 -0
  207. package/packages/core/workflow/pipeline/events.js +84 -0
  208. package/packages/core/workflow/pipeline/fence.js +42 -0
  209. package/packages/core/workflow/pipeline/group-control.js +59 -0
  210. package/packages/core/workflow/pipeline/guard-build.js +74 -0
  211. package/packages/core/workflow/pipeline/guard.js +158 -0
  212. package/packages/core/workflow/pipeline/host.js +144 -0
  213. package/packages/core/workflow/pipeline/integration-status.js +113 -0
  214. package/packages/core/workflow/pipeline/materials.js +58 -0
  215. package/packages/core/workflow/pipeline/notifications.js +36 -0
  216. package/packages/core/workflow/pipeline/parent-notification.js +27 -0
  217. package/packages/core/workflow/pipeline/platforms.js +21 -0
  218. package/packages/core/workflow/pipeline/progress.js +108 -0
  219. package/packages/core/workflow/pipeline/projection.js +20 -0
  220. package/packages/core/workflow/pipeline/reconcile.js +194 -0
  221. package/packages/core/workflow/pipeline/record-action.js +27 -0
  222. package/packages/core/workflow/pipeline/recorder-status.js +69 -0
  223. package/packages/core/workflow/pipeline/recovery-action.js +30 -0
  224. package/packages/core/workflow/pipeline/recovery-admission.js +122 -0
  225. package/packages/core/workflow/pipeline/recovery-build-control.js +95 -0
  226. package/packages/core/workflow/pipeline/recovery-build-evidence.js +71 -0
  227. package/packages/core/workflow/pipeline/recovery-build.js +121 -0
  228. package/packages/core/workflow/pipeline/recovery-controller.js +91 -0
  229. package/packages/core/workflow/pipeline/recovery-integration.js +79 -0
  230. package/packages/core/workflow/pipeline/recovery-merge-proof.js +90 -0
  231. package/packages/core/workflow/pipeline/recovery-ownership-settlement.js +150 -0
  232. package/packages/core/workflow/pipeline/recovery-ownership.js +91 -0
  233. package/packages/core/workflow/pipeline/recovery-plan.js +126 -0
  234. package/packages/core/workflow/pipeline/recovery-runtime.js +68 -0
  235. package/packages/core/workflow/pipeline/recovery-session.js +129 -0
  236. package/packages/core/workflow/pipeline/recovery-transition.js +135 -0
  237. package/packages/core/workflow/pipeline/recovery-unrecorded-build.js +74 -0
  238. package/packages/core/workflow/pipeline/runtime-source.js +111 -0
  239. package/packages/core/workflow/pipeline/selection.js +111 -0
  240. package/packages/core/workflow/pipeline/session.js +72 -0
  241. package/packages/core/workflow/pipeline/settlement.js +137 -0
  242. package/packages/core/workflow/pipeline/wake.js +38 -0
  243. package/packages/core/workflow/pipeline/web-status.js +108 -0
  244. package/packages/core/workflow/workflow-taxonomy.mjs +67 -47
  245. package/scripts/build-release-discussion-reader.mjs +11 -7
  246. package/scripts/check-inventory.mjs +7 -70
  247. package/scripts/check-pipeline-publication-topology.mjs +50 -0
  248. package/scripts/check-public-dogfood-contract.mjs +38 -51
  249. package/scripts/check-release-topology.mjs +15 -8
  250. package/scripts/check-self-consumer-contract.mjs +133 -0
  251. package/scripts/check-universal-workflow-bootstrap.mjs +4 -6
  252. package/scripts/generate-minimal-consumer-contract.mjs +155 -0
  253. package/scripts/generate-site-bundle.mjs +1 -1
  254. package/scripts/inventory/binary.mjs +9 -19
  255. package/scripts/inventory/promotion.mjs +2 -13
  256. package/scripts/prepare-product-toolchain.mjs +10 -0
  257. package/scripts/verify-golden-path.mjs +5 -1
  258. package/scripts/verify-product-platform.mjs +65 -0
  259. package/scripts/verify-standalone-product.mjs +42 -0
  260. package/templates/minimal-consumer/binary/.buildchain/buildchain.toml +45 -0
  261. package/templates/minimal-consumer/binary/.github/workflows/buildchain-recover.yml +30 -0
  262. package/templates/minimal-consumer/binary/.github/workflows/buildchain.yml +28 -0
  263. package/templates/minimal-consumer/binary/package.json +6 -0
  264. package/templates/minimal-consumer/binary/src/build.mjs +5 -0
  265. package/templates/minimal-consumer/binary/src/hello.c +2 -0
  266. package/templates/minimal-consumer/binary/src/verify.mjs +3 -0
  267. package/templates/minimal-consumer/npm/.buildchain/buildchain.toml +46 -0
  268. package/templates/minimal-consumer/npm/.github/workflows/buildchain-recover.yml +30 -0
  269. package/templates/minimal-consumer/npm/.github/workflows/buildchain.yml +28 -0
  270. package/templates/minimal-consumer/npm/package.json +6 -0
  271. package/templates/minimal-consumer/npm/src/build.mjs +5 -0
  272. package/templates/minimal-consumer/npm/src/verify.mjs +3 -0
  273. package/templates/minimal-consumer/paper/.buildchain/buildchain.toml +45 -0
  274. package/templates/minimal-consumer/paper/.github/workflows/buildchain-recover.yml +30 -0
  275. package/templates/minimal-consumer/paper/.github/workflows/buildchain.yml +28 -0
  276. package/templates/minimal-consumer/paper/package.json +6 -0
  277. package/templates/minimal-consumer/paper/src/build.mjs +12 -0
  278. package/templates/minimal-consumer/paper/src/verify.mjs +3 -0
@@ -0,0 +1,82 @@
1
+ import { recordDigest } from "../../release/discussion/envelope.js";
2
+ import { evaluateStableReleaseGate } from "../../release/stable-release-gate.js";
3
+ import { readPipelineStableSource } from "../../providers/github/pipeline-stable-source.js";
4
+ import { verifyPipelinePublicationPlan } from "./plan.js";
5
+ import { readPipelineStableProducts } from "./stable-products.js";
6
+ import { readPipelineStableEntry } from "./stable-entry.js";
7
+
8
+ function stablePolicy(plan) {
9
+ const value = plan.stablePolicy;
10
+ const requiredCanaries = [
11
+ {
12
+ id: "product-build",
13
+ source: "release-candidate",
14
+ context: "",
15
+ allowedAttestors: [],
16
+ },
17
+ ];
18
+ if (value.require_published_entry)
19
+ requiredCanaries.push({
20
+ id: "published-entry",
21
+ source: "public-build",
22
+ context: "",
23
+ allowedAttestors: ["github-actions[bot]"],
24
+ });
25
+ return {
26
+ enabled: true,
27
+ minimumStableIntervalSeconds: value.minimum_interval_seconds,
28
+ minimumCanarySoakSeconds: value.minimum_soak_seconds,
29
+ productPathPrefixes: value.product_paths,
30
+ requiredCanaries,
31
+ };
32
+ }
33
+
34
+ export async function assertPipelineStableQualification(
35
+ plan,
36
+ host,
37
+ { now = new Date().toISOString() } = {},
38
+ ) {
39
+ verifyPipelinePublicationPlan(plan);
40
+ if (plan.channel !== "stable" || !plan.stablePolicy) return null;
41
+ const source = await readPipelineStableSource(plan, host);
42
+ const products = await readPipelineStableProducts(plan, source, host);
43
+ const entry =
44
+ plan.stablePolicy.require_published_entry && products.canary
45
+ ? await readPipelineStableEntry(plan, source, host)
46
+ : { status: "not-evaluated" };
47
+ if (
48
+ products.canary &&
49
+ recordDigest(await readPipelineStableSource(plan, host)) !==
50
+ recordDigest(source)
51
+ )
52
+ throw new Error("Stable source facts changed during product qualification");
53
+ const report = evaluateStableReleaseGate({
54
+ policy: stablePolicy(plan),
55
+ channel: "release",
56
+ now,
57
+ candidate: source.candidate,
58
+ previousStable: source.previousStable || undefined,
59
+ impact: source.impact.value,
60
+ changedPaths: source.changedPaths,
61
+ canaries: [products.canary, entry.canary].filter(Boolean),
62
+ });
63
+ report.previousStable ??= null;
64
+ const body = {
65
+ schema: "buildchain.pipeline-stable-eligibility/v1",
66
+ planRoot: plan.root,
67
+ source,
68
+ products,
69
+ entry,
70
+ evaluatedAt: now,
71
+ report,
72
+ };
73
+ const eligibility = { ...body, root: recordDigest(body) };
74
+ if (!report.ok)
75
+ throw Object.assign(
76
+ new Error(
77
+ `Stable pipeline qualification blocked: ${report.summary.failedChecks.join(", ")}`,
78
+ ),
79
+ { eligibility },
80
+ );
81
+ return eligibility;
82
+ }
@@ -0,0 +1,93 @@
1
+ import path from "node:path";
2
+ import { pipelineHost } from "../../workflow/pipeline/host.js";
3
+ import { githubJsonClient } from "../../providers/github/json-client.js";
4
+ import { githubPipelineVersionArtifacts } from "../../providers/github/pipeline-version-artifacts.js";
5
+ import { buildPipelineVersionMaterial } from "./version-build.js";
6
+ import { qualifyPipelineVersionContext } from "./version-context.js";
7
+ import { preparePipelinePublication } from "./prepare.js";
8
+ import { settlePipelinePublication } from "./settle.js";
9
+
10
+ const contextInput = (core) =>
11
+ JSON.parse(core.getInput("context", { required: true }));
12
+ const directory = (env) =>
13
+ path.join(env.GITHUB_WORKSPACE, ".buildchain/version-material");
14
+
15
+ export async function buildPipelineVersionAction(core, env) {
16
+ const context = contextInput(core);
17
+ if (
18
+ context.schema !== "buildchain.pipeline-version-context/v1" ||
19
+ context.runId !== Number(env.GITHUB_RUN_ID) ||
20
+ context.runAttempt !== Number(env.GITHUB_RUN_ATTEMPT)
21
+ )
22
+ throw new Error(
23
+ "Version build requires its exact provider execution context",
24
+ );
25
+ const result = await buildPipelineVersionMaterial({
26
+ cwd: path.join(env.GITHUB_WORKSPACE, ".buildchain/product"),
27
+ preparation: context.preparation,
28
+ platform: core.getInput("platform", { required: true }),
29
+ environment: env,
30
+ });
31
+ await githubPipelineVersionArtifacts({}).upload(
32
+ context,
33
+ result,
34
+ directory(env),
35
+ );
36
+ }
37
+
38
+ export async function materializePipelinePublicationAction(core, env) {
39
+ const context = contextInput(core);
40
+ const operation = core.getInput("operation", { required: true });
41
+ let result = { operation, context };
42
+ if (operation === "regenerate") {
43
+ if (context.preparation.purpose !== "publication")
44
+ throw new Error(
45
+ "Publication materialization received a different version purpose",
46
+ );
47
+ const host = await pipelineHost(core, env, "Materialize publication");
48
+ await qualifyPipelineVersionContext(context, host, directory(env));
49
+ result = await preparePipelinePublication(
50
+ context.attempt,
51
+ context.definitionSha,
52
+ host,
53
+ );
54
+ if (result.operation === "regenerate")
55
+ throw new Error(
56
+ "Qualified version preparation did not materialize its source",
57
+ );
58
+ }
59
+ core.setOutput("operation", result.operation);
60
+ core.setOutput("stable-wait", result.wait ? JSON.stringify(result.wait) : "");
61
+ core.setOutput("context", JSON.stringify(result.context || {}));
62
+ core.setOutput(
63
+ "matrix",
64
+ JSON.stringify({ include: result.context?.platforms || [] }),
65
+ );
66
+ }
67
+
68
+ export async function materializePipelineDevelopmentAction(core, env) {
69
+ const context = contextInput(core);
70
+ if (context.preparation.purpose !== "development")
71
+ throw new Error(
72
+ "Development materialization received a different version purpose",
73
+ );
74
+ const host = await pipelineHost(core, env, "Materialize next development");
75
+ const prToken = core.getInput("pr-token", { required: true });
76
+ if (prToken !== host.token)
77
+ host.pullRequests = githubJsonClient({
78
+ token: prToken,
79
+ userAgent: "buildchain-next-development",
80
+ });
81
+ await qualifyPipelineVersionContext(context, host, directory(env));
82
+ const result = await settlePipelinePublication(
83
+ context.publication,
84
+ host,
85
+ directory(env),
86
+ env,
87
+ );
88
+ if (result.versionContext)
89
+ throw new Error(
90
+ "Qualified development preparation did not materialize its source",
91
+ );
92
+ core.info(result.reason);
93
+ }
@@ -0,0 +1,130 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { execFileSync } from "node:child_process";
4
+ import { recordDigest } from "../../release/discussion/envelope.js";
5
+ import {
6
+ inspectPipelineSource,
7
+ buildPipelineProducts,
8
+ } from "../../workflow/pipeline/build.js";
9
+ import { verifyPipelineVersionPreparation } from "./version-preparation.js";
10
+ import { guardPipelineVersionSource } from "./version-source-guard.js";
11
+ import { materializePipelineVersion } from "./version.js";
12
+ import {
13
+ pipelineVersionMaterialPaths,
14
+ pipelineVersionRegenerationResult,
15
+ collectPipelineVersionMaterial,
16
+ } from "./version-regeneration.js";
17
+
18
+ function materialFiles(cwd, paths) {
19
+ return Object.fromEntries(
20
+ paths.map((file) => {
21
+ const absolute = path.resolve(cwd, file);
22
+ const stat = fs.lstatSync(absolute);
23
+ if (
24
+ !stat.isFile() ||
25
+ stat.size > 4 * 1024 * 1024 ||
26
+ fs.realpathSync(absolute) !== absolute
27
+ )
28
+ throw new Error(
29
+ "Version preparation requires bounded regular source files without symlinks",
30
+ );
31
+ const bytes = fs.readFileSync(absolute);
32
+ const content = bytes.toString("utf8");
33
+ if (!Buffer.from(content).equals(bytes))
34
+ throw new Error("Version preparation cannot rewrite non-UTF8 material");
35
+ return [file, content];
36
+ }),
37
+ );
38
+ }
39
+
40
+ function trackedModes(cwd, paths) {
41
+ const entries = execFileSync(
42
+ "git",
43
+ ["-C", cwd, "ls-files", "--stage", "-z", "--", ...paths],
44
+ { encoding: "utf8" },
45
+ )
46
+ .split("\0")
47
+ .filter(Boolean);
48
+ const modes = Object.fromEntries(
49
+ entries.map((entry) => {
50
+ const match = /^(100644|100755) [0-9a-f]{40} 0\t(.+)$/u.exec(entry);
51
+ if (!match)
52
+ throw new Error("Version preparation requires tracked regular files");
53
+ return [match[2], match[1]];
54
+ }),
55
+ );
56
+ if (recordDigest(Object.keys(modes).sort()) !== recordDigest(paths))
57
+ throw new Error(
58
+ "Version preparation material is absent from the source tree",
59
+ );
60
+ return modes;
61
+ }
62
+
63
+ function assertPreparedDiff(cwd, paths, modes) {
64
+ const changes = execFileSync(
65
+ "git",
66
+ ["-C", cwd, "diff", "HEAD", "--name-status", "-z"],
67
+ { encoding: "utf8" },
68
+ )
69
+ .split("\0")
70
+ .filter(Boolean);
71
+ for (let index = 0; index < changes.length; index += 2)
72
+ if (changes[index] !== "M" || !paths.includes(changes[index + 1]))
73
+ throw new Error(
74
+ "Version product commands changed undeclared source paths",
75
+ );
76
+ if (recordDigest(trackedModes(cwd, paths)) !== recordDigest(modes))
77
+ throw new Error("Version product commands changed tracked file modes");
78
+ const raw = execFileSync("git", ["-C", cwd, "diff", "HEAD", "--summary"], {
79
+ encoding: "utf8",
80
+ });
81
+ if (raw.trim())
82
+ throw new Error(
83
+ "Version product commands changed source file kinds or modes",
84
+ );
85
+ }
86
+
87
+ export async function buildPipelineVersionMaterial({
88
+ cwd,
89
+ preparation,
90
+ platform,
91
+ environment = process.env,
92
+ }) {
93
+ cwd = fs.realpathSync(cwd);
94
+ const contract = inspectPipelineSource(cwd, preparation.source);
95
+ verifyPipelineVersionPreparation(preparation, contract);
96
+ if (environment.BUILDCHAIN_RUNTIME_SHA !== preparation.runtime.sha)
97
+ throw new Error(
98
+ "Version preparation is not using its admitted execution runtime",
99
+ );
100
+ const paths = pipelineVersionMaterialPaths(
101
+ preparation.versionPolicy,
102
+ preparation.source.configPath,
103
+ );
104
+ const modes = trackedModes(cwd, paths);
105
+ const guard = guardPipelineVersionSource(cwd, preparation.source, paths);
106
+ const before = materialFiles(cwd, paths);
107
+ const material = materializePipelineVersion(
108
+ preparation.versionPolicy,
109
+ before,
110
+ preparation.version,
111
+ );
112
+ // Product code runs only in this credentialless job. The provider writer
113
+ // independently observes job completion and verifies these exact bytes.
114
+ for (const { path: file, content } of material.changes)
115
+ fs.writeFileSync(path.join(cwd, file), content);
116
+ await buildPipelineProducts({ cwd, plan: contract, platform, environment });
117
+ guard();
118
+ assertPreparedDiff(cwd, paths, modes);
119
+ const result = pipelineVersionRegenerationResult(
120
+ preparation,
121
+ platform,
122
+ materialFiles(cwd, paths),
123
+ );
124
+ collectPipelineVersionMaterial(
125
+ { ...preparation, platforms: [platform] },
126
+ before,
127
+ [result],
128
+ );
129
+ return result;
130
+ }
@@ -0,0 +1,143 @@
1
+ import { recordDigest } from "../../release/discussion/envelope.js";
2
+ import { resumePipelineSession } from "../../workflow/pipeline/session.js";
3
+ import { pipelinePlatforms } from "../../workflow/pipeline/platforms.js";
4
+ import { githubPipelineVersion } from "../../providers/github/pipeline-version.js";
5
+ import { githubPipelineVersionArtifacts } from "../../providers/github/pipeline-version-artifacts.js";
6
+ import { pipelinePublicationJournal } from "./journal.js";
7
+ import {
8
+ planPipelineVersionPreparation,
9
+ verifyPipelineVersionPreparation,
10
+ } from "./version-preparation.js";
11
+ import { collectPipelineVersionMaterial } from "./version-regeneration.js";
12
+
13
+ const phaseOf = (purpose) =>
14
+ purpose === "publication" ? "publish" : "next-development";
15
+
16
+ export async function preparePipelineVersionContext(
17
+ parent,
18
+ purpose,
19
+ publication,
20
+ host,
21
+ journal,
22
+ definitionSha,
23
+ ) {
24
+ const admitted = await host.source.source(
25
+ parent.source.commit,
26
+ parent.source.configPath,
27
+ );
28
+ if (
29
+ recordDigest(admitted.identity) !== recordDigest(parent.source) ||
30
+ recordDigest(admitted.plan) !== publication.plan.contractRoot
31
+ )
32
+ throw new Error(
33
+ "Version preparation source or contract differs from its retained parent",
34
+ );
35
+ const preparation = planPipelineVersionPreparation({
36
+ attempt: publication.attempt,
37
+ generation: publication.generation,
38
+ source: parent.source,
39
+ contract: admitted.plan,
40
+ version: parent.version,
41
+ runtime: host.runtime,
42
+ purpose,
43
+ parentRoot: parent.root,
44
+ });
45
+ const context = {
46
+ schema: "buildchain.pipeline-version-context/v1",
47
+ attempt: publication.attempt,
48
+ generation: publication.generation,
49
+ preparation,
50
+ definitionSha,
51
+ runId: host.runId,
52
+ runAttempt: host.runAttempt,
53
+ platforms: pipelinePlatforms(admitted.plan),
54
+ publication,
55
+ };
56
+ await journal.record(`publication/version-${purpose}-context`, context, {
57
+ phase: phaseOf(purpose),
58
+ });
59
+ return context;
60
+ }
61
+
62
+ export async function qualifyPipelineVersionContext(
63
+ context,
64
+ host,
65
+ directory,
66
+ artifactClient,
67
+ ) {
68
+ if (
69
+ context.schema !== "buildchain.pipeline-version-context/v1" ||
70
+ context.runId !== host.runId ||
71
+ context.runAttempt !== host.runAttempt
72
+ )
73
+ throw new Error(
74
+ "Version context belongs to a different provider execution",
75
+ );
76
+ const session = await resumePipelineSession(
77
+ { ...host, attempt: context.attempt },
78
+ host,
79
+ );
80
+ const journal = pipelinePublicationJournal(session, host);
81
+ await journal.fence();
82
+ const { preparation } = context;
83
+ if (
84
+ context.generation !== session.observed.generation ||
85
+ preparation.attempt !== context.attempt ||
86
+ preparation.generation !== context.generation ||
87
+ recordDigest(preparation.runtime) !== recordDigest(host.runtime)
88
+ )
89
+ throw new Error(
90
+ "Version context changed its admitted attempt, generation or runtime",
91
+ );
92
+ const retained = await journal.materials(
93
+ `publication/version-${preparation.purpose}-context/`,
94
+ );
95
+ if (!retained.some((value) => recordDigest(value) === recordDigest(context)))
96
+ throw new Error(
97
+ "Version context is not retained in the authoritative attempt",
98
+ );
99
+ const admitted = await host.source.source(
100
+ preparation.source.commit,
101
+ preparation.source.configPath,
102
+ );
103
+ if (recordDigest(admitted.identity) !== recordDigest(preparation.source))
104
+ throw new Error("Version context source changed before qualification");
105
+ verifyPipelineVersionPreparation(preparation, admitted.plan);
106
+ const artifacts = githubPipelineVersionArtifacts(host, artifactClient);
107
+ const observed = await artifacts.download(context, directory);
108
+ const adapter = githubPipelineVersion(host.request, host.repository);
109
+ const before = await adapter.inspect(
110
+ preparation.source,
111
+ preparation.versionPolicy,
112
+ );
113
+ collectPipelineVersionMaterial(preparation, before.files, observed.results);
114
+ const regeneration = { context, ...observed };
115
+ await journal.fence();
116
+ await journal.record(
117
+ `publication/version-${preparation.purpose}-qualified`,
118
+ regeneration,
119
+ { phase: phaseOf(preparation.purpose) },
120
+ );
121
+ return { session, journal, regeneration };
122
+ }
123
+
124
+ export async function retainedPipelineVersionRegeneration(
125
+ journal,
126
+ parent,
127
+ purpose,
128
+ runtime,
129
+ ) {
130
+ const values = (
131
+ await journal.materials(`publication/version-${purpose}-qualified/`)
132
+ ).filter(
133
+ ({ context }) =>
134
+ context.preparation.parentRoot === parent.root &&
135
+ recordDigest(context.preparation.runtime) === recordDigest(runtime),
136
+ );
137
+ if (
138
+ values.length > 1 &&
139
+ new Set(values.map(({ results }) => recordDigest(results))).size !== 1
140
+ )
141
+ throw new Error("Version regeneration has conflicting qualified bytes");
142
+ return values.at(-1);
143
+ }
@@ -0,0 +1,36 @@
1
+ import { recordDigest } from "../../release/discussion/envelope.js";
2
+ import { collectPipelineVersionMaterial } from "./version-regeneration.js";
3
+ import { materializePipelineVersion } from "./version.js";
4
+
5
+ // Provider execution is qualified and journaled by version-context before this
6
+ // pure byte check. A self-reported digest never grants Git write authority.
7
+ export function preparedPipelineVersionMaterial(plan, files, regeneration) {
8
+ if (!regeneration) {
9
+ if (plan.versionPolicy.derived_files?.length)
10
+ throw new Error(
11
+ "Derived version files require retained qualified regeneration",
12
+ );
13
+ return materializePipelineVersion(plan.versionPolicy, files, plan.version);
14
+ }
15
+ const { context, build, results } = regeneration;
16
+ const { preparation } = context;
17
+ const { root: buildRoot, ...buildBody } = build;
18
+ if (
19
+ preparation.parentRoot !== plan.root ||
20
+ preparation.version !== plan.version ||
21
+ recordDigest(preparation.source) !== recordDigest(plan.source) ||
22
+ recordDigest(preparation.versionPolicy) !==
23
+ recordDigest(plan.versionPolicy) ||
24
+ build.preparationRoot !== preparation.root ||
25
+ build.runId !== context.runId ||
26
+ build.runAttempt !== context.runAttempt ||
27
+ buildRoot !== recordDigest(buildBody)
28
+ )
29
+ throw new Error(
30
+ "Qualified regeneration does not belong to this exact version materialization",
31
+ );
32
+ return {
33
+ ...collectPipelineVersionMaterial(preparation, files, results),
34
+ regenerationBuildRoot: buildRoot,
35
+ };
36
+ }
@@ -0,0 +1,69 @@
1
+ import {
2
+ recordDigest,
3
+ validateRuntime,
4
+ } from "../../release/discussion/envelope.js";
5
+ import { validateConsumerSource } from "../../workflow/attempt/identity.js";
6
+ import { pipelinePlatforms } from "../../workflow/pipeline/platforms.js";
7
+ import { choice, text } from "../../consumer/contract/shape.js";
8
+ import { pipelineVersionMaterialPaths } from "./version-regeneration.js";
9
+
10
+ export function planPipelineVersionPreparation({
11
+ attempt,
12
+ generation,
13
+ source,
14
+ contract,
15
+ version,
16
+ runtime,
17
+ purpose,
18
+ parentRoot,
19
+ }) {
20
+ text(attempt, "version preparation attempt", /^attempt-[0-9a-f]{64}$/u);
21
+ text(generation, "version preparation generation", /^sha256:[0-9a-f]{64}$/u);
22
+ text(parentRoot, "version preparation parent", /^sha256:[0-9a-f]{64}$/u);
23
+ text(
24
+ source.repository,
25
+ "version preparation repository",
26
+ /^[\w.-]+\/[\w.-]+$/u,
27
+ );
28
+ validateConsumerSource(source, source.repository);
29
+ validateRuntime(runtime);
30
+ choice(
31
+ runtime.repository,
32
+ ["kungfu-systems/buildchain"],
33
+ "version preparation runtime",
34
+ );
35
+ choice(
36
+ purpose,
37
+ ["publication", "development"],
38
+ "version preparation purpose",
39
+ );
40
+ text(
41
+ version,
42
+ "version preparation target",
43
+ /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/u,
44
+ );
45
+ pipelineVersionMaterialPaths(contract.version, source.configPath);
46
+ const body = {
47
+ schema: "buildchain.pipeline-version-preparation/v1",
48
+ attempt,
49
+ generation,
50
+ source,
51
+ version,
52
+ runtime,
53
+ purpose,
54
+ parentRoot,
55
+ contractRoot: recordDigest(contract),
56
+ versionPolicy: contract.version,
57
+ platforms: pipelinePlatforms(contract).map(({ platform }) => platform),
58
+ };
59
+ return structuredClone({ ...body, root: recordDigest(body) });
60
+ }
61
+
62
+ export function verifyPipelineVersionPreparation(preparation, contract) {
63
+ const expected = planPipelineVersionPreparation({ ...preparation, contract });
64
+ if (recordDigest(preparation) !== recordDigest(expected))
65
+ throw new Error(
66
+ "Version preparation changed its source-bound product contract or root",
67
+ );
68
+ return preparation;
69
+ }