@kungfu-tech/buildchain 4.1.3-alpha.1 → 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 (263) 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 +266 -10
  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 +362 -7
  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 +328 -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 +4 -4
  23. package/architecture/universal-workflow-fault-campaign.json +3 -1
  24. package/architecture/workflow-taxonomy.json +77 -328
  25. package/dist/readers/business-attempt.cjs +809 -0
  26. package/dist/site/buildchain-contract.json +16 -68
  27. package/dist/site/buildchain-site.json +40 -25
  28. package/dist/site/capability-registry.json +4 -4
  29. package/dist/site/controller-registry.json +1 -47
  30. package/dist/site/kfd-claims.json +499 -494
  31. package/dist/site/kfd-upstream-aggregate.json +1 -1
  32. package/dist/site/manual-registry.json +3 -3
  33. package/dist/site/node-api-registry.json +54 -54
  34. package/dist/site/page-registry.json +33 -18
  35. package/dist/site/public-surface-audit.json +286 -349
  36. package/dist/site/publication-authority-registry.json +203 -523
  37. package/dist/site/publication-registry.json +4 -4
  38. package/dist/site/site-manifest.json +7 -7
  39. package/dist/site/workflow-registry.json +590 -940
  40. package/docs/code-organization.md +9 -6
  41. package/docs/node-api-reference.md +102 -102
  42. package/docs/release-governance.md +10 -12
  43. package/docs/runtime-entry.md +73 -5
  44. package/docs/runtime-train-validation.md +39 -11
  45. package/docs/stable-candidate-patrol.md +6 -5
  46. package/docs/workflow-catalog.md +9 -32
  47. package/package.json +4 -3
  48. package/packages/core/adoption/buildchain-kfd-claims.js +7 -7
  49. package/packages/core/build/standalone/build.js +7 -1
  50. package/packages/core/consumer/buildchain-config.js +2 -21
  51. package/packages/core/consumer/contract/entries.js +85 -0
  52. package/packages/core/consumer/contract/examples.js +96 -0
  53. package/packages/core/consumer/contract/identity.js +65 -0
  54. package/packages/core/consumer/contract/inspection.js +45 -0
  55. package/packages/core/consumer/contract/plan.js +149 -0
  56. package/packages/core/consumer/contract/products.js +119 -0
  57. package/packages/core/consumer/contract/reader.js +47 -0
  58. package/packages/core/consumer/contract/shape.js +46 -0
  59. package/packages/core/dev-delivery/candidate/admission.js +9 -5
  60. package/packages/core/dev-delivery/candidate/reservation.js +11 -0
  61. package/packages/core/dev-delivery/candidate/source-paths.js +12 -4
  62. package/packages/core/dev-delivery/commands/project-cut-merge-queue-admission.mjs +47 -20
  63. package/packages/core/dev-delivery/dev-delivery-provider-heartbeat.js +18 -15
  64. package/packages/core/dev-delivery/native/actions.js +9 -1
  65. package/packages/core/dev-delivery/native/heartbeat-action.js +16 -0
  66. package/packages/core/dev-delivery/native/heartbeat.js +6 -4
  67. package/packages/core/dev-delivery/native/process-entry.js +19 -0
  68. package/packages/core/dev-delivery/queue/landing-action.js +53 -0
  69. package/packages/core/dev-delivery/warrant/failure-settlement.js +8 -6
  70. package/packages/core/dev-delivery/warrant/service.js +7 -0
  71. package/packages/core/governance/buildchain-publication-authority.js +8 -23
  72. package/packages/core/governance/github-governance-authority.js +1 -0
  73. package/packages/core/observability/controller-evidence.js +0 -8
  74. package/packages/core/paper/operations/bootstrap.js +2 -0
  75. package/packages/core/paper/paper-npm-bootstrap.js +2 -2
  76. package/packages/core/providers/github/attempt-index.js +60 -0
  77. package/packages/core/providers/github/attempt-journal.js +154 -0
  78. package/packages/core/providers/github/discussions/materials.js +19 -6
  79. package/packages/core/providers/github/pipeline-checkout.js +49 -0
  80. package/packages/core/providers/github/pipeline-entry-runs.js +210 -0
  81. package/packages/core/providers/github/pipeline-events.js +131 -0
  82. package/packages/core/providers/github/pipeline-integration.js +95 -0
  83. package/packages/core/providers/github/pipeline-policy.js +240 -0
  84. package/packages/core/providers/github/pipeline-product-payload.js +50 -0
  85. package/packages/core/providers/github/pipeline-product-release.js +198 -0
  86. package/packages/core/providers/github/pipeline-publication-artifacts.js +153 -0
  87. package/packages/core/providers/github/pipeline-release-evidence.js +30 -0
  88. package/packages/core/providers/github/pipeline-run-entry.js +86 -0
  89. package/packages/core/providers/github/pipeline-runs.js +132 -0
  90. package/packages/core/providers/github/pipeline-source.js +195 -0
  91. package/packages/core/providers/github/pipeline-stable-baseline.js +86 -0
  92. package/packages/core/providers/github/pipeline-stable-source.js +372 -0
  93. package/packages/core/providers/github/pipeline-version-artifacts.js +91 -0
  94. package/packages/core/providers/github/pipeline-version-readback.js +117 -0
  95. package/packages/core/providers/github/pipeline-version.js +199 -0
  96. package/packages/core/providers/github/pipeline-worker.js +108 -0
  97. package/packages/core/providers/github/release-evidence-assets.js +97 -0
  98. package/packages/core/publication/candidate/registry-hydration.js +2 -2
  99. package/packages/core/publication/npm/pack-preview.js +2 -2
  100. package/packages/core/publication/npm/pack-result.js +31 -0
  101. package/packages/core/publication/npm/package.js +3 -6
  102. package/packages/core/publication/npm/pipeline-channel.js +36 -0
  103. package/packages/core/publication/npm/pipeline-provider.js +137 -0
  104. package/packages/core/publication/npm/registry.js +12 -9
  105. package/packages/core/publication/pipeline/actions.js +116 -0
  106. package/packages/core/publication/pipeline/apply.js +189 -0
  107. package/packages/core/publication/pipeline/build-segments.js +107 -0
  108. package/packages/core/publication/pipeline/capsules.js +92 -0
  109. package/packages/core/publication/pipeline/context.js +39 -0
  110. package/packages/core/publication/pipeline/development-anchor.js +91 -0
  111. package/packages/core/publication/pipeline/development-current.js +63 -0
  112. package/packages/core/publication/pipeline/development-pr.js +112 -0
  113. package/packages/core/publication/pipeline/development-proof.js +76 -0
  114. package/packages/core/publication/pipeline/development-transition.js +112 -0
  115. package/packages/core/publication/pipeline/distribution.js +130 -0
  116. package/packages/core/publication/pipeline/documents.js +192 -0
  117. package/packages/core/publication/pipeline/effects.js +192 -0
  118. package/packages/core/publication/pipeline/files.js +66 -0
  119. package/packages/core/publication/pipeline/imported-materials.js +32 -0
  120. package/packages/core/publication/pipeline/journal.js +122 -0
  121. package/packages/core/publication/pipeline/next-development.js +117 -0
  122. package/packages/core/publication/pipeline/pack.js +180 -0
  123. package/packages/core/publication/pipeline/package-policy.js +22 -0
  124. package/packages/core/publication/pipeline/plan.js +136 -0
  125. package/packages/core/publication/pipeline/prepare.js +139 -0
  126. package/packages/core/publication/pipeline/qualification.js +175 -0
  127. package/packages/core/publication/pipeline/qualify.js +104 -0
  128. package/packages/core/publication/pipeline/recovery-admission.js +122 -0
  129. package/packages/core/publication/pipeline/recovery-build-download.js +80 -0
  130. package/packages/core/publication/pipeline/recovery-build-plan.js +122 -0
  131. package/packages/core/publication/pipeline/recovery-build-readback.js +68 -0
  132. package/packages/core/publication/pipeline/recovery-capsules.js +54 -0
  133. package/packages/core/publication/pipeline/recovery-import.js +80 -0
  134. package/packages/core/publication/pipeline/recovery-inspection.js +218 -0
  135. package/packages/core/publication/pipeline/recovery-materials.js +104 -0
  136. package/packages/core/publication/pipeline/recovery-plan.js +46 -0
  137. package/packages/core/publication/pipeline/recovery-prepare.js +73 -0
  138. package/packages/core/publication/pipeline/recovery-qualification.js +128 -0
  139. package/packages/core/publication/pipeline/recovery-readback.js +56 -0
  140. package/packages/core/publication/pipeline/recovery-signing.js +58 -0
  141. package/packages/core/publication/pipeline/sealed-products.js +79 -0
  142. package/packages/core/publication/pipeline/settle.js +42 -0
  143. package/packages/core/publication/pipeline/signing.js +140 -0
  144. package/packages/core/publication/pipeline/source-plan.js +83 -0
  145. package/packages/core/publication/pipeline/stable-entry.js +219 -0
  146. package/packages/core/publication/pipeline/stable-products.js +317 -0
  147. package/packages/core/publication/pipeline/stable-wait.js +170 -0
  148. package/packages/core/publication/pipeline/stable.js +82 -0
  149. package/packages/core/publication/pipeline/version-actions.js +93 -0
  150. package/packages/core/publication/pipeline/version-build.js +130 -0
  151. package/packages/core/publication/pipeline/version-context.js +143 -0
  152. package/packages/core/publication/pipeline/version-material.js +36 -0
  153. package/packages/core/publication/pipeline/version-preparation.js +69 -0
  154. package/packages/core/publication/pipeline/version-regeneration.js +162 -0
  155. package/packages/core/publication/pipeline/version-source-guard.js +69 -0
  156. package/packages/core/publication/pipeline/version.js +81 -0
  157. package/packages/core/publication/pipeline/worker.js +72 -0
  158. package/packages/core/publication/publication-reproducibility.js +2 -2
  159. package/packages/core/release/next-development-transition.js +15 -15
  160. package/packages/core/release/promote-candidate/product-provider-adapters.js +4 -3
  161. package/packages/core/release/promote-ref/internal/npm-existing-evidence.js +2 -1
  162. package/packages/core/release/promote-ref/internal/publish-command.js +16 -5
  163. package/packages/core/release/stable-release-gate.js +11 -0
  164. package/packages/core/runtime/buildchain-domain.wasm +0 -0
  165. package/packages/core/runtime/domain-wasm-artifact.js +2 -2
  166. package/packages/core/runtime/entry/actions.js +27 -25
  167. package/packages/core/runtime/entry/attempt.js +25 -0
  168. package/packages/core/runtime/entry/selection.js +14 -0
  169. package/packages/core/runtime/entry/source.js +62 -0
  170. package/packages/core/workflow/attempt/identity.js +170 -0
  171. package/packages/core/workflow/attempt/journal.js +89 -0
  172. package/packages/core/workflow/attempt/materials.js +72 -0
  173. package/packages/core/workflow/attempt/reader-entry.js +12 -0
  174. package/packages/core/workflow/attempt/reader.js +149 -0
  175. package/packages/core/workflow/attempt/records.js +153 -0
  176. package/packages/core/workflow/attempt/store.js +75 -0
  177. package/packages/core/workflow/commands/pipeline-build.mjs +23 -0
  178. package/packages/core/workflow/pipeline/action-output.js +11 -0
  179. package/packages/core/workflow/pipeline/actions.js +52 -0
  180. package/packages/core/workflow/pipeline/build-control.js +140 -0
  181. package/packages/core/workflow/pipeline/build-evidence.js +210 -0
  182. package/packages/core/workflow/pipeline/build-qualification.js +145 -0
  183. package/packages/core/workflow/pipeline/build-result.js +78 -0
  184. package/packages/core/workflow/pipeline/build.js +142 -0
  185. package/packages/core/workflow/pipeline/cancellation.js +169 -0
  186. package/packages/core/workflow/pipeline/channel-control.js +157 -0
  187. package/packages/core/workflow/pipeline/channel-source.js +53 -0
  188. package/packages/core/workflow/pipeline/controller.js +141 -0
  189. package/packages/core/workflow/pipeline/delivery-control.js +249 -0
  190. package/packages/core/workflow/pipeline/delivery-observation.js +124 -0
  191. package/packages/core/workflow/pipeline/delivery-request.js +198 -0
  192. package/packages/core/workflow/pipeline/events.js +84 -0
  193. package/packages/core/workflow/pipeline/fence.js +42 -0
  194. package/packages/core/workflow/pipeline/group-control.js +59 -0
  195. package/packages/core/workflow/pipeline/guard-build.js +74 -0
  196. package/packages/core/workflow/pipeline/guard.js +158 -0
  197. package/packages/core/workflow/pipeline/host.js +144 -0
  198. package/packages/core/workflow/pipeline/integration-status.js +113 -0
  199. package/packages/core/workflow/pipeline/materials.js +58 -0
  200. package/packages/core/workflow/pipeline/notifications.js +36 -0
  201. package/packages/core/workflow/pipeline/parent-notification.js +27 -0
  202. package/packages/core/workflow/pipeline/platforms.js +21 -0
  203. package/packages/core/workflow/pipeline/progress.js +108 -0
  204. package/packages/core/workflow/pipeline/projection.js +20 -0
  205. package/packages/core/workflow/pipeline/reconcile.js +194 -0
  206. package/packages/core/workflow/pipeline/record-action.js +27 -0
  207. package/packages/core/workflow/pipeline/recorder-status.js +69 -0
  208. package/packages/core/workflow/pipeline/recovery-action.js +30 -0
  209. package/packages/core/workflow/pipeline/recovery-admission.js +122 -0
  210. package/packages/core/workflow/pipeline/recovery-build-control.js +95 -0
  211. package/packages/core/workflow/pipeline/recovery-build-evidence.js +71 -0
  212. package/packages/core/workflow/pipeline/recovery-build.js +121 -0
  213. package/packages/core/workflow/pipeline/recovery-controller.js +91 -0
  214. package/packages/core/workflow/pipeline/recovery-integration.js +79 -0
  215. package/packages/core/workflow/pipeline/recovery-merge-proof.js +90 -0
  216. package/packages/core/workflow/pipeline/recovery-ownership-settlement.js +150 -0
  217. package/packages/core/workflow/pipeline/recovery-ownership.js +91 -0
  218. package/packages/core/workflow/pipeline/recovery-plan.js +126 -0
  219. package/packages/core/workflow/pipeline/recovery-runtime.js +68 -0
  220. package/packages/core/workflow/pipeline/recovery-session.js +129 -0
  221. package/packages/core/workflow/pipeline/recovery-transition.js +135 -0
  222. package/packages/core/workflow/pipeline/recovery-unrecorded-build.js +74 -0
  223. package/packages/core/workflow/pipeline/runtime-source.js +111 -0
  224. package/packages/core/workflow/pipeline/selection.js +111 -0
  225. package/packages/core/workflow/pipeline/session.js +72 -0
  226. package/packages/core/workflow/pipeline/settlement.js +137 -0
  227. package/packages/core/workflow/pipeline/wake.js +38 -0
  228. package/packages/core/workflow/pipeline/web-status.js +108 -0
  229. package/packages/core/workflow/workflow-taxonomy.mjs +67 -47
  230. package/scripts/build-release-discussion-reader.mjs +11 -7
  231. package/scripts/check-inventory.mjs +7 -70
  232. package/scripts/check-pipeline-publication-topology.mjs +50 -0
  233. package/scripts/check-public-dogfood-contract.mjs +38 -51
  234. package/scripts/check-release-topology.mjs +15 -8
  235. package/scripts/check-self-consumer-contract.mjs +133 -0
  236. package/scripts/check-universal-workflow-bootstrap.mjs +4 -6
  237. package/scripts/generate-minimal-consumer-contract.mjs +155 -0
  238. package/scripts/generate-site-bundle.mjs +1 -1
  239. package/scripts/inventory/binary.mjs +9 -19
  240. package/scripts/inventory/promotion.mjs +2 -13
  241. package/scripts/prepare-product-toolchain.mjs +10 -0
  242. package/scripts/verify-golden-path.mjs +5 -1
  243. package/scripts/verify-product-platform.mjs +65 -0
  244. package/scripts/verify-standalone-product.mjs +42 -0
  245. package/templates/minimal-consumer/binary/.buildchain/buildchain.toml +45 -0
  246. package/templates/minimal-consumer/binary/.github/workflows/buildchain-recover.yml +30 -0
  247. package/templates/minimal-consumer/binary/.github/workflows/buildchain.yml +28 -0
  248. package/templates/minimal-consumer/binary/package.json +6 -0
  249. package/templates/minimal-consumer/binary/src/build.mjs +5 -0
  250. package/templates/minimal-consumer/binary/src/hello.c +2 -0
  251. package/templates/minimal-consumer/binary/src/verify.mjs +3 -0
  252. package/templates/minimal-consumer/npm/.buildchain/buildchain.toml +46 -0
  253. package/templates/minimal-consumer/npm/.github/workflows/buildchain-recover.yml +30 -0
  254. package/templates/minimal-consumer/npm/.github/workflows/buildchain.yml +28 -0
  255. package/templates/minimal-consumer/npm/package.json +6 -0
  256. package/templates/minimal-consumer/npm/src/build.mjs +5 -0
  257. package/templates/minimal-consumer/npm/src/verify.mjs +3 -0
  258. package/templates/minimal-consumer/paper/.buildchain/buildchain.toml +45 -0
  259. package/templates/minimal-consumer/paper/.github/workflows/buildchain-recover.yml +30 -0
  260. package/templates/minimal-consumer/paper/.github/workflows/buildchain.yml +28 -0
  261. package/templates/minimal-consumer/paper/package.json +6 -0
  262. package/templates/minimal-consumer/paper/src/build.mjs +12 -0
  263. package/templates/minimal-consumer/paper/src/verify.mjs +3 -0
@@ -19,17 +19,23 @@ export function runNpm({ cwd, args, env, allowFailure = false }) {
19
19
  return result;
20
20
  }
21
21
 
22
- function parseNpmView(stdout) {
22
+ export function parseNpmView(stdout) {
23
23
  const raw = String(stdout || "").trim();
24
- if (!raw) {
25
- return undefined;
26
- }
27
- const parsed = JSON.parse(raw);
24
+ if (!raw) throw new Error("npm view omitted its exact package response");
25
+ const value = JSON.parse(raw);
26
+ if (Array.isArray(value) && value.length !== 1)
27
+ throw new Error("npm view must return exactly one package version");
28
+ const parsed = Array.isArray(value) ? value[0] : value;
29
+ if (typeof parsed === "string" && parsed)
30
+ return { integrity: parsed, shasum: "" };
28
31
  const dist = parsed?.dist || parsed;
29
- return {
32
+ const result = {
30
33
  integrity: dist?.integrity || parsed?.["dist.integrity"] || "",
31
34
  shasum: dist?.shasum || parsed?.["dist.shasum"] || "",
32
35
  };
36
+ if (!result.integrity && !result.shasum)
37
+ throw new Error("npm view omitted the exact package integrity");
38
+ return result;
33
39
  }
34
40
 
35
41
  export function publishedDigest({ cwd, name, version, registry, env }) {
@@ -54,8 +60,5 @@ export function publishedDigest({ cwd, name, version, registry, env }) {
54
60
  throw new Error(`npm view ${name}@${version} failed\n${output}`.trim());
55
61
  }
56
62
  const view = parseNpmView(result.stdout);
57
- if (!view) {
58
- return undefined;
59
- }
60
63
  return view.integrity || (view.shasum ? `sha1:${view.shasum}` : "");
61
64
  }
@@ -0,0 +1,116 @@
1
+ import path from "node:path";
2
+ import { pipelineHost } from "../../workflow/pipeline/host.js";
3
+ import { buildPipelineSource } from "../../workflow/pipeline/build.js";
4
+ import { githubPipelinePublicationArtifacts } from "../../providers/github/pipeline-publication-artifacts.js";
5
+ import { preparePipelinePublication } from "./prepare.js";
6
+ import { packPipelineProducts } from "./pack.js";
7
+ import {
8
+ preparePipelineQualification,
9
+ sealPipelineQualification,
10
+ } from "./qualify.js";
11
+ import { applyPipelinePublication } from "./apply.js";
12
+ import { settlePipelinePublication } from "./settle.js";
13
+ import { githubJsonClient } from "../../providers/github/json-client.js";
14
+
15
+ const contextInput = (core) =>
16
+ JSON.parse(core.getInput("context", { required: true }));
17
+ const directory = (env) =>
18
+ path.join(env.GITHUB_WORKSPACE, ".buildchain/publication");
19
+
20
+ export async function preparePipelinePublicationAction(core, env) {
21
+ const host = await pipelineHost(core, env, "Prepare publication");
22
+ const result = await preparePipelinePublication(
23
+ core.getInput("attempt", { required: true }),
24
+ core.getInput("definition-sha", { required: true }),
25
+ host,
26
+ );
27
+ core.setOutput("operation", result.operation);
28
+ core.setOutput("stable-wait", result.wait ? JSON.stringify(result.wait) : "");
29
+ core.setOutput("context", JSON.stringify(result.context || {}));
30
+ core.setOutput(
31
+ "matrix",
32
+ JSON.stringify({ include: result.context?.platforms || [] }),
33
+ );
34
+ }
35
+
36
+ export async function buildPipelinePublicationAction(core, env) {
37
+ const context = contextInput(core);
38
+ const platform = core.getInput("platform", { required: true });
39
+ const cwd = path.join(env.GITHUB_WORKSPACE, ".buildchain/product");
40
+ await buildPipelineSource({
41
+ cwd,
42
+ source: context.materialization.source,
43
+ platform,
44
+ environment: env,
45
+ });
46
+ const output = path.join(directory(env), "products");
47
+ const manifest = packPipelineProducts({
48
+ cwd,
49
+ output,
50
+ plan: context.plan,
51
+ platform,
52
+ source: context.materialization.source,
53
+ environment: env,
54
+ });
55
+ const artifacts = githubPipelinePublicationArtifacts({});
56
+ await artifacts.upload(context.plan, manifest, output);
57
+ }
58
+
59
+ export async function preparePipelineQualificationAction(core, env) {
60
+ const host = await pipelineHost(core, env, "Qualify and sign products");
61
+ const prepared = await preparePipelineQualification(
62
+ contextInput(core),
63
+ host,
64
+ directory(env),
65
+ );
66
+ core.setOutput("subject-path", prepared.subjectPath);
67
+ core.setOutput("predicate-path", prepared.predicatePath);
68
+ core.setOutput("predicate-type", prepared.predicateType);
69
+ }
70
+
71
+ export async function sealPipelineQualificationAction(core, env) {
72
+ const host = await pipelineHost(core, env, "Qualify and sign products");
73
+ await sealPipelineQualification(
74
+ contextInput(core),
75
+ host,
76
+ directory(env),
77
+ core.getInput("bundle-path", { required: true }),
78
+ );
79
+ }
80
+
81
+ export async function applyPipelinePublicationAction(core, env) {
82
+ const host = await pipelineHost(core, env, "Apply qualified publication");
83
+ const result = await applyPipelinePublication(
84
+ contextInput(core),
85
+ host,
86
+ directory(env),
87
+ env,
88
+ );
89
+ core.setOutput("stable-wait", result.wait ? JSON.stringify(result.wait) : "");
90
+ }
91
+
92
+ export async function settlePipelinePublicationAction(core, env) {
93
+ const host = await pipelineHost(core, env, "Settle product follow-ups");
94
+ const prToken = core.getInput("pr-token");
95
+ if (prToken && prToken !== host.token)
96
+ host.pullRequests = githubJsonClient({
97
+ token: prToken,
98
+ userAgent: "buildchain-next-development",
99
+ });
100
+ const result = await settlePipelinePublication(
101
+ contextInput(core),
102
+ host,
103
+ directory(env),
104
+ env,
105
+ );
106
+ core.info(result.reason);
107
+ core.setOutput(
108
+ "version-context",
109
+ JSON.stringify(result.versionContext || {}),
110
+ );
111
+ core.setOutput("operation", result.versionContext ? "regenerate" : "wait");
112
+ core.setOutput(
113
+ "matrix",
114
+ JSON.stringify({ include: result.versionContext?.platforms || [] }),
115
+ );
116
+ }
@@ -0,0 +1,189 @@
1
+ import path from "node:path";
2
+ import fs from "node:fs";
3
+ import { recordDigest } from "../../release/discussion/envelope.js";
4
+ import { preparePipelineStableQualification } from "./stable-wait.js";
5
+ import {
6
+ createReleaseReceipt,
7
+ RELEASE_RECEIPT_CONTRACT,
8
+ } from "../../release/release-invocation.js";
9
+ import { githubPipelineProductRelease } from "../../providers/github/pipeline-product-release.js";
10
+ import { pipelineNpmProvider } from "../npm/pipeline-provider.js";
11
+ import { publicationContext, uniquePublicationMaterial } from "./context.js";
12
+ import { restorePipelineProducts } from "./sealed-products.js";
13
+ import {
14
+ pipelineReleaseDocuments,
15
+ pipelineReleaseEvidence,
16
+ } from "./documents.js";
17
+ import { writeImmutablePublicationFile } from "./files.js";
18
+ import { verifyPipelineSigning } from "./signing.js";
19
+ import { pipelinePublicationEffects, applyPipelineEffects } from "./effects.js";
20
+ import { observeRecoveryPublicationEffects } from "./recovery-readback.js";
21
+ import {
22
+ createRecoveryPublicationAdmission,
23
+ verifyRecoveryPublicationAdmission,
24
+ } from "./recovery-admission.js";
25
+
26
+ export async function applyPipelinePublication(
27
+ context,
28
+ host,
29
+ directory,
30
+ environment,
31
+ {
32
+ verifySigning = verifyPipelineSigning,
33
+ npmProvider = pipelineNpmProvider,
34
+ } = {},
35
+ ) {
36
+ const { journal, archive } = await publicationContext(context, host);
37
+ const wait = await preparePipelineStableQualification(
38
+ context.plan,
39
+ host,
40
+ journal,
41
+ {
42
+ attempt: context.attempt,
43
+ generation: context.generation,
44
+ phase: "publish",
45
+ },
46
+ );
47
+ if (wait) return { operation: "wait", wait };
48
+ const retained = await uniquePublicationMaterial(
49
+ journal,
50
+ "publication/qualified/",
51
+ );
52
+ const { plan, materialization } = context;
53
+ const { qualified, capsules, sealed } = retained;
54
+ const evaluatedAt = context.recovery?.preserveTransaction
55
+ ? qualified.qualification.issuedAt
56
+ : undefined;
57
+ const products = await restorePipelineProducts(
58
+ archive,
59
+ qualified,
60
+ sealed,
61
+ path.join(directory, "sealed-products"),
62
+ );
63
+ const bundlePath = writeImmutablePublicationFile(
64
+ path.join(directory, "attestation.json"),
65
+ await archive.read(retained.bundle),
66
+ );
67
+ const signing = verifySigning({
68
+ plan,
69
+ materialization,
70
+ qualified,
71
+ bundlePath,
72
+ directory: path.join(directory, "signing-readback"),
73
+ token: host.token,
74
+ evaluatedAt,
75
+ });
76
+ const documents = pipelineReleaseDocuments({
77
+ plan,
78
+ materialization,
79
+ qualified,
80
+ capsules,
81
+ signing,
82
+ evaluatedAt,
83
+ });
84
+ if (
85
+ recordDigest(documents) !== recordDigest(retained.documents) ||
86
+ recordDigest(signing) !== recordDigest(retained.signing)
87
+ )
88
+ throw new Error(
89
+ "Retained release documents changed during publisher admission",
90
+ );
91
+ const npm = npmProvider({
92
+ directory: products,
93
+ artifacts: qualified.artifacts,
94
+ environment,
95
+ });
96
+ const evidence = pipelineReleaseEvidence({
97
+ plan,
98
+ qualified,
99
+ capsules,
100
+ documents,
101
+ bundle: fs.readFileSync(bundlePath),
102
+ });
103
+ const github = githubPipelineProductRelease({
104
+ ...host,
105
+ plan,
106
+ qualified,
107
+ documents,
108
+ directory: products,
109
+ evidence,
110
+ });
111
+ const providerFor = (effect) =>
112
+ effect.kind === "npm-package" ? npm : github;
113
+ const provider = {
114
+ observe: (effect) => providerFor(effect).observe(effect),
115
+ matches: (effect, value) => providerFor(effect).matches(effect, value),
116
+ apply: (effect) => providerFor(effect).apply(effect),
117
+ };
118
+ const receipts = await journal.materials("publication/effect/");
119
+ const effects = pipelinePublicationEffects({
120
+ plan,
121
+ qualified,
122
+ documents,
123
+ evidence: evidence.map(({ bytes, ...asset }) => asset),
124
+ receipts,
125
+ });
126
+ let fence = journal.fence;
127
+ if (context.recovery?.preserveTransaction) {
128
+ const readback = await observeRecoveryPublicationEffects({
129
+ effects,
130
+ receipts,
131
+ retained,
132
+ provider,
133
+ fence,
134
+ });
135
+ const input = {
136
+ context,
137
+ retained,
138
+ execution: context.recovery.execution,
139
+ readback,
140
+ };
141
+ const admission = createRecoveryPublicationAdmission(input);
142
+ await journal.record("publication/recovery-authorization", {
143
+ admission,
144
+ readback,
145
+ });
146
+ fence = async () => {
147
+ await journal.fence();
148
+ verifyRecoveryPublicationAdmission(admission, input);
149
+ };
150
+ }
151
+ const results = await applyPipelineEffects({
152
+ effects,
153
+ transactionRoot: documents.transaction.transactionRoot,
154
+ receipts,
155
+ provider,
156
+ fence,
157
+ retain: (receipt) => journal.record("publication/effect", receipt),
158
+ });
159
+ const receipt = createReleaseReceipt({
160
+ schema: RELEASE_RECEIPT_CONTRACT,
161
+ transactionRoot: documents.transaction.transactionRoot,
162
+ outcome: "complete",
163
+ releasePassportRoot: documents.passport.passportRoot,
164
+ providerTransactionRoot: recordDigest(effects),
165
+ providerStateRoot: recordDigest(results),
166
+ providerReceiptRoots: [...new Set(results.map(({ root }) => root))].sort(),
167
+ });
168
+ // This is publication success only. Distribution and next-development remain
169
+ // separate mandatory nodes of the same business attempt.
170
+ let complete = {
171
+ schema: "buildchain.pipeline-publication-complete/v1",
172
+ release: receipt,
173
+ completedAt: new Date().toISOString(),
174
+ };
175
+ const originals = await journal.materials(
176
+ "publication/predecessor-complete/",
177
+ );
178
+ if (
179
+ originals.length > 1 ||
180
+ (originals.length &&
181
+ recordDigest(originals[0].release) !== recordDigest(receipt))
182
+ )
183
+ throw new Error(
184
+ "Recovery cannot replace the original completed publication receipt",
185
+ );
186
+ if (originals.length) complete = originals[0];
187
+ await journal.record("publication/complete", complete, { state: "success" });
188
+ return complete;
189
+ }
@@ -0,0 +1,107 @@
1
+ import { recordDigest } from "../../release/discussion/envelope.js";
2
+ import { assertRecoveryProductContract } from "./recovery-build-plan.js";
3
+
4
+ export const PUBLICATION_BUILD_AGGREGATE =
5
+ "buildchain.pipeline-publication-build-qualification/v1";
6
+
7
+ export function qualifyPublicationBuildSegments(
8
+ context,
9
+ segments,
10
+ providerSource,
11
+ ) {
12
+ if (!/^[0-9a-f]{40}$/u.test(providerSource || ""))
13
+ throw new Error(
14
+ "Publication qualification requires the actual current signing execution source",
15
+ );
16
+ if (!segments.length || segments.length > 100)
17
+ throw new Error("Publication build segments exceed their exact bound");
18
+ const platforms = [],
19
+ artifactIds = [];
20
+ for (const segment of segments) {
21
+ const { build, plan, materialization } = segment;
22
+ assertRecoveryProductContract(
23
+ plan,
24
+ context.plan,
25
+ materialization.source,
26
+ context.materialization,
27
+ );
28
+ const { root, ...body } = build;
29
+ if (
30
+ root !== recordDigest(body) ||
31
+ build.schema !== "buildchain.pipeline-publication-build-readback/v1" ||
32
+ build.outcome !== "success" ||
33
+ build.planRoot !== plan.root ||
34
+ recordDigest(build.source) !== recordDigest(materialization.source) ||
35
+ build.platforms.length !== build.jobs.length ||
36
+ build.platforms.length !== build.artifactIds.length
37
+ )
38
+ throw new Error(
39
+ "Publication segment lacks exact independently observed build evidence",
40
+ );
41
+ for (const [index, platform] of build.platforms.entries()) {
42
+ const job = build.jobs[index],
43
+ name = `Build publication (${platform})`;
44
+ if (
45
+ job.run_id !== build.runId ||
46
+ job.run_attempt !== build.runAttempt ||
47
+ job.status !== "completed" ||
48
+ job.conclusion !== "success" ||
49
+ !(job.name === name || job.name.endsWith(` / ${name}`))
50
+ )
51
+ throw new Error("Publication segment producer job or platform changed");
52
+ }
53
+ platforms.push(...build.platforms);
54
+ artifactIds.push(...build.artifactIds);
55
+ }
56
+ const expected = [
57
+ ...new Set(context.plan.outputs.map(({ platform }) => platform)),
58
+ ].sort();
59
+ if (
60
+ recordDigest([...platforms].sort()) !== recordDigest(expected) ||
61
+ new Set(artifactIds).size !== artifactIds.length
62
+ )
63
+ throw new Error(
64
+ "Publication segments must cover each declared platform and artifact exactly once",
65
+ );
66
+ const body = {
67
+ schema: PUBLICATION_BUILD_AGGREGATE,
68
+ operation: "requalify-original-product-segments",
69
+ outcome: "success",
70
+ planRoot: context.plan.root,
71
+ source: context.materialization.source,
72
+ providerSource,
73
+ runId: context.runId,
74
+ runAttempt: context.runAttempt,
75
+ platforms: expected,
76
+ artifactIds: artifactIds.sort((a, b) => a - b),
77
+ segments,
78
+ };
79
+ return { ...body, root: recordDigest(body) };
80
+ }
81
+
82
+ export function publicationArtifactProducer(build, artifactId) {
83
+ if (build.schema !== PUBLICATION_BUILD_AGGREGATE)
84
+ return { build, plan: null };
85
+ const selected = build.segments.filter((segment) =>
86
+ segment.build.artifactIds.includes(artifactId),
87
+ );
88
+ if (selected.length !== 1)
89
+ throw new Error("Publication artifact has no unique original producer");
90
+ return selected[0];
91
+ }
92
+
93
+ export function verifyPublicationBuildAggregate(build, context) {
94
+ if (
95
+ recordDigest(build) !==
96
+ recordDigest(
97
+ qualifyPublicationBuildSegments(
98
+ context,
99
+ build.segments,
100
+ build.providerSource,
101
+ ),
102
+ )
103
+ )
104
+ throw new Error(
105
+ "Publication aggregate changed its exact source or original segments",
106
+ );
107
+ }
@@ -0,0 +1,92 @@
1
+ import { publicationArtifactProducer } from "./build-segments.js";
2
+ import { recordDigest } from "../../release/discussion/envelope.js";
3
+ import {
4
+ STAGE_CAPSULE_CONTRACT,
5
+ STAGE_CAPSULE_IDENTITY_CONTRACT,
6
+ stageCapsuleIdentityRoot,
7
+ stageCapsuleRoot,
8
+ validateStageCapsule,
9
+ } from "../../build/stage-capsule.js";
10
+ import { verifyPipelineQualification } from "./documents.js";
11
+
12
+ export function pipelineProductCapsules({
13
+ plan,
14
+ materialization,
15
+ qualified,
16
+ bundles,
17
+ evaluatedAt,
18
+ }) {
19
+ verifyPipelineQualification({
20
+ plan,
21
+ materialization,
22
+ qualified,
23
+ evaluatedAt,
24
+ });
25
+ const capsules = qualified.artifacts.map((artifact) => {
26
+ const bundle = bundles.find(
27
+ ({ providerArtifact }) =>
28
+ providerArtifact.id === artifact.providerArtifactId,
29
+ );
30
+ if (
31
+ !bundle ||
32
+ !bundle.providerArtifact.expires_at ||
33
+ Date.parse(bundle.providerArtifact.expires_at) <=
34
+ Date.parse(qualified.qualification.issuedAt)
35
+ )
36
+ throw new Error(
37
+ "Product Capsule requires a live provider retention promise",
38
+ );
39
+ const producer = publicationArtifactProducer(
40
+ qualified.build,
41
+ artifact.providerArtifactId,
42
+ );
43
+ const identity = {
44
+ schema: STAGE_CAPSULE_IDENTITY_CONTRACT,
45
+ sourceRoot: recordDigest(qualified.source),
46
+ platform: artifact.platform,
47
+ platformRoot: recordDigest({ platform: artifact.platform }),
48
+ stage: "verify",
49
+ toolchainRoots: [],
50
+ runtimeRoot: recordDigest(producer.plan?.runtime || plan.runtime),
51
+ policyRoot: plan.contractRoot,
52
+ declaredInputs: [
53
+ { name: "publication-plan", root: producer.plan?.root || plan.root },
54
+ ],
55
+ transformationRoot: recordDigest({
56
+ source: qualified.source,
57
+ product: artifact.product,
58
+ artifact: artifact.id,
59
+ kind: artifact.kind,
60
+ }),
61
+ outputManifestRoot: artifact.manifestRoot,
62
+ qualificationRoot: qualified.qualification.receiptRoot,
63
+ observationRoots: [
64
+ { name: "provider-build", root: qualified.build.root },
65
+ ],
66
+ };
67
+ const capsule = {
68
+ schema: STAGE_CAPSULE_CONTRACT,
69
+ writerAuthority: "typescript-v3",
70
+ rustAuthority: "validation-only",
71
+ identity,
72
+ identityRoot: stageCapsuleIdentityRoot(identity),
73
+ retentionPromise: {
74
+ class: "github-artifact",
75
+ retainUntil: bundle.providerArtifact.expires_at.replace(
76
+ /T(\d{2}:\d{2}:\d{2})Z$/u,
77
+ "T$1.000Z",
78
+ ),
79
+ },
80
+ capsuleRoot: `sha256:${"0".repeat(64)}`,
81
+ };
82
+ capsule.capsuleRoot = stageCapsuleRoot(capsule);
83
+ validateStageCapsule(capsule);
84
+ return { id: artifact.id, artifact, capsule };
85
+ });
86
+ const body = {
87
+ schema: "buildchain.pipeline-product-capsules/v1",
88
+ qualificationRoot: qualified.root,
89
+ capsules,
90
+ };
91
+ return { ...body, root: recordDigest(body) };
92
+ }
@@ -0,0 +1,39 @@
1
+ import { recordDigest } from "../../release/discussion/envelope.js";
2
+ import { resumePipelineSession } from "../../workflow/pipeline/session.js";
3
+ import { pipelinePublicationJournal } from "./journal.js";
4
+
5
+ export async function publicationContext(context, host) {
6
+ if (
7
+ context.schema !== "buildchain.pipeline-publication-context/v1" ||
8
+ context.runId !== host.runId ||
9
+ context.runAttempt !== host.runAttempt
10
+ )
11
+ throw new Error(
12
+ "Publication context is not from this exact provider execution",
13
+ );
14
+ const session = await resumePipelineSession(
15
+ { ...host, attempt: context.attempt },
16
+ host,
17
+ );
18
+ if (session.observed.generation !== context.generation)
19
+ throw new Error(
20
+ "Publication context belongs to a superseded source generation",
21
+ );
22
+ const journal = pipelinePublicationJournal(session, host);
23
+ await journal.fence();
24
+ const contexts = await journal.materials("publication/context/");
25
+ if (!contexts.some((value) => recordDigest(value) === recordDigest(context)))
26
+ throw new Error(
27
+ "Publication context is not retained in the authoritative attempt",
28
+ );
29
+ return { session, journal, archive: host.productArchive(session) };
30
+ }
31
+
32
+ export async function uniquePublicationMaterial(journal, prefix) {
33
+ const values = await journal.materials(prefix);
34
+ if (values.length !== 1)
35
+ throw new Error(
36
+ `Publication requires one exact retained material: ${prefix}`,
37
+ );
38
+ return values[0];
39
+ }
@@ -0,0 +1,91 @@
1
+ import { createHash } from "node:crypto";
2
+ import { recordDigest } from "../../release/discussion/envelope.js";
3
+ import { bindPipelineDevelopmentAnchor } from "./development-transition.js";
4
+ import { githubPipelineVersion } from "../../providers/github/pipeline-version.js";
5
+ import { developmentTransitionReadback } from "./development-proof.js";
6
+
7
+ export async function observePipelineVersionAnchor(context, host, transition) {
8
+ const { plan } = context;
9
+ const commit = await host.source.branchHead(plan.developmentBranch);
10
+ const source = await host.source.source(commit, plan.source.configPath);
11
+ const ancestry =
12
+ plan.channel === "stable"
13
+ ? null
14
+ : await host.request(
15
+ `/repos/${host.repository}/compare/${plan.intentSource.commit}...${commit}`,
16
+ );
17
+ if (
18
+ ancestry &&
19
+ (!["identical", "ahead"].includes(ancestry.status) ||
20
+ ancestry.merge_base_commit?.sha !== plan.intentSource.commit)
21
+ )
22
+ throw new Error(
23
+ "Version anchor is not descended from the released development source",
24
+ );
25
+ if (recordDigest(source.plan) !== plan.contractRoot)
26
+ throw new Error("Protected anchor changed the product contract");
27
+ const version = await githubPipelineVersion(
28
+ host.request,
29
+ host.repository,
30
+ ).inspect(source.identity, plan.versionPolicy);
31
+ if (version.version === plan.version)
32
+ return {
33
+ state: "waiting",
34
+ reason: "waiting-for-protected-version-anchor",
35
+ transitionRoot: transition.idempotencyKey,
36
+ };
37
+ const pulls = await host.request(
38
+ `/repos/${host.repository}/commits/${commit}/pulls?per_page=100`,
39
+ );
40
+ const candidates = pulls.filter(
41
+ (pr) =>
42
+ pr.base?.ref === plan.developmentBranch && pr.merge_commit_sha === commit,
43
+ );
44
+ if (candidates.length !== 1)
45
+ throw new Error("Version anchor requires one exact protected merged PR");
46
+ const pr = await host.request(
47
+ `/repos/${host.repository}/pulls/${candidates[0].number}`,
48
+ );
49
+ const integration = await host.integration.observe({
50
+ intent: {
51
+ source: { pullRequest: pr.number, targetBranch: plan.developmentBranch },
52
+ },
53
+ generation: {
54
+ source: { commit: pr.head.sha, configPath: plan.source.configPath },
55
+ },
56
+ });
57
+ if (
58
+ plan.channel === "stable" &&
59
+ Date.parse(pr.merged_at) <=
60
+ Date.parse(transition.completedStable.completedAt)
61
+ )
62
+ return {
63
+ state: "waiting",
64
+ reason: "waiting-for-protected-version-anchor",
65
+ transitionRoot: transition.idempotencyKey,
66
+ };
67
+ const file = plan.versionPolicy.files[0].path;
68
+ const anchor = {
69
+ manifestPath: file,
70
+ manifestRoot: `sha256:${createHash("sha256").update(version.files[file]).digest("hex")}`,
71
+ };
72
+ const bound = bindPipelineDevelopmentAnchor(transition, {
73
+ targetVersion: version.version,
74
+ anchor,
75
+ });
76
+ const verified = developmentTransitionReadback(bound, {
77
+ before: version,
78
+ after: version,
79
+ evidence: integration,
80
+ createdAt: pr.created_at,
81
+ mergedAt: pr.merged_at,
82
+ });
83
+ return {
84
+ state: "success",
85
+ reason: "protected-version-anchor-verified",
86
+ transition: verified,
87
+ source: source.identity,
88
+ integration,
89
+ anchor,
90
+ };
91
+ }