@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
@@ -0,0 +1,72 @@
1
+ import { resumePipelineSession } from "../../workflow/pipeline/session.js";
2
+ import { pipelinePublicationJournal } from "./journal.js";
3
+ import { retainedPipelineStableWait } from "./stable-wait.js";
4
+
5
+ export async function claimPipelinePublicationWorker(
6
+ attempt,
7
+ publisherSha,
8
+ host,
9
+ ) {
10
+ const { run: execution } = await host.runs.read(host.runId, host.runAttempt);
11
+ const definitions = (execution.referenced_workflows || []).filter((entry) =>
12
+ entry.path?.startsWith(
13
+ "kungfu-systems/buildchain/.github/workflows/.release-pipeline-products.yml@",
14
+ ),
15
+ );
16
+ if (
17
+ !/^[0-9a-f]{40}$/u.test(publisherSha || "") ||
18
+ definitions.length !== 1 ||
19
+ definitions[0].sha !== publisherSha
20
+ )
21
+ throw new Error(
22
+ "Publisher definition is not the exact hosted reusable workflow",
23
+ );
24
+ const session = await resumePipelineSession({ ...host, attempt }, host);
25
+ const observed = session.observed;
26
+ if (observed.phases.merge?.payload.state !== "success")
27
+ throw new Error(
28
+ "Product publication requires completed protected integration",
29
+ );
30
+ if (observed.status === "complete") return null;
31
+ const phase = observed.missing[0];
32
+ if (!["publish", "distribution", "next-development"].includes(phase))
33
+ throw new Error("Attempt is not at product publication");
34
+ const prior = observed.history
35
+ .at(-1)
36
+ .events.filter((event) =>
37
+ event.payload.materials.some((material) =>
38
+ material.id.startsWith("publication/worker/"),
39
+ ),
40
+ )
41
+ .at(-1)?.payload.writer;
42
+ if (
43
+ prior &&
44
+ (prior.runId !== String(host.runId) ||
45
+ prior.runAttempt !== String(host.runAttempt))
46
+ ) {
47
+ const { run } = await host.runs.read(
48
+ Number(prior.runId),
49
+ Number(prior.runAttempt),
50
+ );
51
+ // A time-only wait releases publication ownership before its outer run ends.
52
+ // The next worker still claims the exact native head under the publication lock.
53
+ if (
54
+ run.status !== "completed" &&
55
+ !(await retainedPipelineStableWait(observed, host))
56
+ )
57
+ return null;
58
+ }
59
+ const journal = pipelinePublicationJournal(session, host);
60
+ const claim = {
61
+ schema: "buildchain.pipeline-publication-worker/v1",
62
+ attempt,
63
+ runId: host.runId,
64
+ runAttempt: host.runAttempt,
65
+ };
66
+ await journal.record("publication/worker", claim, {
67
+ phase,
68
+ expectedHead: observed.head,
69
+ });
70
+ await journal.fence();
71
+ return { session, journal, phase };
72
+ }
@@ -1,3 +1,4 @@
1
+ import { readNpmPackResult } from "./npm/pack-result.js";
1
2
  import crypto from "node:crypto";
2
3
  import fs from "node:fs";
3
4
  import os from "node:os";
@@ -564,8 +565,7 @@ function packPublication({ buildRoot, packageDir, source, toolchain }) {
564
565
  label: "npm pack reproducibility candidate",
565
566
  },
566
567
  );
567
- const parsed = JSON.parse(output);
568
- const result = Array.isArray(parsed) ? parsed[0] : parsed;
568
+ const result = readNpmPackResult(output);
569
569
  if (!result?.filename || !result?.integrity || !result?.shasum) {
570
570
  throw new Error(
571
571
  "npm pack reproducibility candidate did not report filename, integrity, and shasum",
@@ -3,6 +3,7 @@
3
3
  import crypto from "node:crypto";
4
4
  import fs from "node:fs";
5
5
  import path from "node:path";
6
+ import { isDeepStrictEqual } from "node:util";
6
7
 
7
8
  import {
8
9
  discoverConfiguredDerivedVersionMaterial,
@@ -606,7 +607,7 @@ function assertStaticTransition(record, recreated) {
606
607
  throw new Error(`next-development ${field} drifted`);
607
608
  }
608
609
  for (const field of ["adapter", "declaredPaths", "effectBounds", "target"]) {
609
- if (JSON.stringify(record[field]) !== JSON.stringify(recreated[field])) {
610
+ if (!isDeepStrictEqual(record[field], recreated[field])) {
610
611
  const label = field === "effectBounds" ? "effect bounds" : field;
611
612
  throw new Error(`next-development ${label} drifted`);
612
613
  }
@@ -638,20 +639,19 @@ function assertStaticTransition(record, recreated) {
638
639
  }
639
640
 
640
641
  function assertMaterialization(record) {
641
- if (record.materialization !== null) {
642
- const { materializationRoot, ...body } = record.materialization;
643
- if (materializationRoot !== nextDevelopmentRoot(body)) {
644
- throw new Error("next-development materialization root drifted");
645
- }
646
- const materializedPaths = (record.materialization.paths || []).map(
647
- (entry) => entry.path,
648
- );
649
- if (
650
- JSON.stringify(materializedPaths) !==
651
- JSON.stringify(record.adapter.sourcePaths)
652
- ) {
653
- throw new Error("next-development materialization escaped source paths");
654
- }
642
+ if (record.materialization === null) return;
643
+ const { materializationRoot, ...body } = record.materialization;
644
+ if (materializationRoot !== nextDevelopmentRoot(body))
645
+ throw new Error("next-development materialization root drifted");
646
+ const { paths = [] } = record.materialization;
647
+ const materializedPaths = paths.map(({ path }) => path);
648
+ // Reference adapters cover sources; transaction adapters cover all outputs.
649
+ if (
650
+ ![record.adapter.sourcePaths, record.adapter.allowedChangePaths].some(
651
+ (expected) => isDeepStrictEqual(materializedPaths, expected),
652
+ )
653
+ ) {
654
+ throw new Error("next-development materialization paths drifted");
655
655
  }
656
656
  }
657
657
 
@@ -1,3 +1,5 @@
1
+ import { parseNpmView } from "../../publication/npm/registry.js";
2
+ import { readNpmPackResult } from "../../publication/npm/pack-result.js";
1
3
  import { createOciPublicationAdapter } from "./oci-provider.js";
2
4
  import crypto from "node:crypto";
3
5
  import fs from "node:fs";
@@ -306,8 +308,7 @@ function createPackedPackage(context) {
306
308
  { cwd: context.cwd, encoding: "utf8" },
307
309
  "npm pack",
308
310
  );
309
- const payload = JSON.parse(String(result.stdout || "[]"));
310
- const pack = Array.isArray(payload) ? payload[0] : payload;
311
+ const pack = readNpmPackResult(String(result.stdout || "[]"));
311
312
  if (
312
313
  pack?.name !== context.intent.packageName ||
313
314
  pack?.version !== context.intent.version ||
@@ -363,7 +364,7 @@ async function npmReadback(context, packedPackage, effect) {
363
364
  "npm-readback-failed",
364
365
  );
365
366
  }
366
- const integrity = JSON.parse(String(result.stdout || '""'));
367
+ const { integrity } = parseNpmView(result.stdout);
367
368
  if (integrity !== expected.integrity) return conflict("npm-integrity-conflict");
368
369
  return observed(effect, {
369
370
  kind: "npm-package",
@@ -1,3 +1,4 @@
1
+ import { parseNpmView } from "../../../publication/npm/registry.js";
1
2
  import { npmPackageSpec } from "./publish-contract.js";
2
3
  import { execNpmSync } from "./npm-distribution.js";
3
4
  import fs from "node:fs";
@@ -14,7 +15,7 @@ export function readExistingNpmIntegrity({ cwd, artifact }) {
14
15
  if (!output) {
15
16
  throw new Error("empty dist.integrity");
16
17
  }
17
- return JSON.parse(output);
18
+ return parseNpmView(output).integrity;
18
19
  } catch (error) {
19
20
  const message = error.stderr?.toString?.().trim() || error.message;
20
21
  throw new Error(
@@ -1,3 +1,4 @@
1
+ import { readNpmPackResult } from "../../../publication/npm/pack-result.js";
1
2
  import { npmPublishTransaction } from "../../../publication/npm/transaction.js";
2
3
  import { npmPublicationEnvironment } from "../../../publication/npm/environment.js";
3
4
  import {
@@ -15,10 +16,21 @@ import {
15
16
  updateVersionStateContents,
16
17
  } from "../../version-state.js";
17
18
  import { publishTransactionEnvironment } from "./transaction-context.js";
18
- export function runPublishCommand({ cwd, command, provider, loadedConfig, env }) {
19
+ export function runPublishCommand({
20
+ cwd,
21
+ command,
22
+ provider,
23
+ loadedConfig,
24
+ env,
25
+ }) {
19
26
  if (provider) {
20
- if (provider.kind !== "npm" || !provider.directory || command) throw new Error("Invalid or ambiguous declarative publication provider");
21
- npmPublishTransaction({ cwd: path.resolve(cwd, provider.directory), publication: npmPublicationEnvironment(env), env: { ...process.env, ...env } });
27
+ if (provider.kind !== "npm" || !provider.directory || command)
28
+ throw new Error("Invalid or ambiguous declarative publication provider");
29
+ npmPublishTransaction({
30
+ cwd: path.resolve(cwd, provider.directory),
31
+ publication: npmPublicationEnvironment(env),
32
+ env: { ...process.env, ...env },
33
+ });
22
34
  return "provider:npm";
23
35
  }
24
36
  const lifecyclePublish = getLifecycleStage(loadedConfig, "publish");
@@ -64,7 +76,7 @@ export function rematerializedNpmPackEnvironment({
64
76
  path.join(os.tmpdir(), "buildchain-rematerialized-npm-"),
65
77
  );
66
78
  try {
67
- const packed = JSON.parse(
79
+ const result = readNpmPackResult(
68
80
  execNpmSync(
69
81
  [
70
82
  "pack",
@@ -82,7 +94,6 @@ export function rematerializedNpmPackEnvironment({
82
94
  },
83
95
  ),
84
96
  );
85
- const result = Array.isArray(packed) ? packed[0] : packed;
86
97
  if (!result?.filename)
87
98
  throw new Error("rematerialized npm pack did not return a filename");
88
99
  if (result.name !== pkg.name || result.version !== version) {
@@ -184,8 +184,19 @@ export function evaluateStableReleaseGate({
184
184
  "version-bound impact declares a summary and at least one surface",
185
185
  { summary: string(impact?.summary), surfaceIds: surfaceImpacts.map((entry) => string(entry?.id)).filter(Boolean) },
186
186
  ));
187
+ checks.push(check(
188
+ string(impact?.release?.version) === candidateTag.replace(/^v/, ""),
189
+ "stable.impact_version",
190
+ "product impact belongs to the exact candidate version",
191
+ { expectedVersion: candidateTag.replace(/^v/, ""), observedVersion: string(impact?.release?.version) },
192
+ ));
187
193
 
188
194
  const canaryById = new Map(canaries.map((canary) => [string(canary?.id), canary]));
195
+ checks.push(check(
196
+ canaryById.size === canaries.length,
197
+ "canary.evidence_inventory",
198
+ "each canary has one unambiguous observation",
199
+ ));
189
200
  const canaryCompletionTimes = [];
190
201
  for (const required of policy.requiredCanaries) {
191
202
  const evidence = canaryById.get(required.id);
@@ -2,6 +2,6 @@
2
2
  export const DOMAIN_WASM_ABI_VERSION = 1;
3
3
  export const DOMAIN_WASM_RUSTC = "rustc 1.96.0 (ac68faa20 2026-05-25)";
4
4
  export const DOMAIN_WASM_SOURCE_SHA256 =
5
- "83550684878224e6820594a44fc48f805c5ed1b3182ac32ebeb51b74cfd20a7f";
5
+ "9275a02bbc3daac66cac3021384bcaaf43bd52ed9af56b4f1ac67f1cbc69fb71";
6
6
  export const DOMAIN_WASM_SHA256 =
7
- "504e23c9241f43611813f0fbd213b7a694946328b00e23d22f6ba678e7b0c841";
7
+ "d42ab5ae8d42253d40ab49057e460285a848925cece176c4d68e63e90ca86c72";
@@ -1,45 +1,38 @@
1
1
  import { resolveRecoverySource } from "./recovery.js";
2
2
  import { getOctokit } from "@actions/github";
3
3
  import { runtimeEntryProvider } from "./github.js";
4
+ import { resolveRuntimeActionSource } from "./source.js";
4
5
  import {
5
6
  preparedRuntimeSelection,
6
7
  selectExecutionRuntime,
7
8
  } from "./selection.js";
9
+ import { selectAttemptRecoveryRuntime } from "./attempt.js";
8
10
 
9
11
  export async function selectExecutionRuntimeAction(
10
12
  core,
11
13
  env,
12
- { providerFactory = runtimeEntryProvider, githubFactory = getOctokit } = {},
14
+ {
15
+ providerFactory = runtimeEntryProvider,
16
+ githubFactory = getOctokit,
17
+ pipelineLookup,
18
+ } = {},
13
19
  ) {
14
20
  const retained = core.getInput("selection");
15
21
  const transport = retained
16
22
  ? preparedRuntimeSelection(JSON.parse(retained))
17
23
  : undefined;
18
24
  const github = githubFactory(core.getInput("token", { required: true }));
19
- let source = {
20
- repository: core.getInput("source-repository") || env.GITHUB_REPOSITORY,
21
- sha: core.getInput("source-sha") || env.GITHUB_SHA,
22
- ref: env.GITHUB_REF,
23
- };
24
- const resumeRunId = core.getInput("resume-run-id");
25
- if (resumeRunId) {
26
- const reader = providerFactory(github, {
27
- sourceRepository: source.repository,
28
- sourceSha: source.sha,
29
- actor: env.GITHUB_ACTOR,
30
- eventName: env.GITHUB_EVENT_NAME,
31
- });
32
- await reader.authorize({ origin: "runtime-parameter" });
33
- source = await resolveRecoverySource(
34
- {
35
- repository: source.repository,
36
- runId: resumeRunId,
37
- currentRunId: env.GITHUB_RUN_ID,
38
- workflow: env.GITHUB_WORKFLOW_REF?.slice(source.repository.length + 1),
39
- },
40
- reader.readRun,
41
- );
42
- }
25
+ const {
26
+ source: resolvedSource,
27
+ continuation,
28
+ resumeRunId,
29
+ pipelineAttempt,
30
+ } = await resolveRuntimeActionSource(core, env, {
31
+ providerFactory,
32
+ github,
33
+ pipelineLookup,
34
+ });
35
+ let source = resolvedSource;
43
36
  const provider = providerFactory(github, {
44
37
  sourceRepository: source.repository,
45
38
  sourceSha: source.sha,
@@ -91,6 +84,15 @@ export async function selectExecutionRuntimeAction(
91
84
  selection.source?.sha !== source.sha
92
85
  )
93
86
  throw new Error("Runtime transport changed the admitted consumer source");
87
+ } else if (continuation?.recovery && (!runtimeRef || pipelineAttempt)) {
88
+ if (runtimeRef)
89
+ throw new Error(
90
+ "An admitted recovery runtime can change only through a new recovery attempt",
91
+ );
92
+ selection = await selectAttemptRecoveryRuntime(
93
+ continuation.recovery,
94
+ provider,
95
+ );
94
96
  } else {
95
97
  selection = await selectExecutionRuntime(
96
98
  {
@@ -0,0 +1,25 @@
1
+ import { validateRuntime } from "../../release/discussion/envelope.js";
2
+ import { selectExecutionRuntime } from "./selection.js";
3
+
4
+ // This input is read from the canonical current attempt, never from workflow
5
+ // inputs. Continuation retains its admitted repair without reopening an override.
6
+ export async function selectAttemptRecoveryRuntime(recovery, provider) {
7
+ validateRuntime(recovery.runtime);
8
+ if (
9
+ !/^attempt-[0-9a-f]{64}$/u.test(recovery.attempt || "") ||
10
+ !/^sha256:[0-9a-f]{64}$/u.test(recovery.recordRoot || "") ||
11
+ recovery.runtime.repository !== "kungfu-systems/buildchain"
12
+ )
13
+ throw new Error(
14
+ "Attempt runtime continuation requires its canonical recovery admission",
15
+ );
16
+ const selected = await selectExecutionRuntime(
17
+ { runtimeRef: recovery.runtime.sha },
18
+ {
19
+ ...provider,
20
+ authorize: (input) =>
21
+ provider.authorize({ ...input, origin: "attempt-recovery" }),
22
+ },
23
+ );
24
+ return { ...selected, origin: "attempt-recovery", recovery };
25
+ }
@@ -1,3 +1,6 @@
1
+ import { consumerContractLock } from "../../consumer/contract/identity.js";
2
+ import { canonicalJson } from "../../release/discussion/envelope.js";
3
+
1
4
  const exactSha = /^[a-f0-9]{40}$/iu;
2
5
  const repository = "kungfu-systems/buildchain";
3
6
  const trainRef =
@@ -28,6 +31,14 @@ export function runtimeSelector({ runtimeRef = "", lock, workflowSha }) {
28
31
  return { ref: runtimeRef, origin: "runtime-parameter" };
29
32
  }
30
33
  if (lock) {
34
+ if (lock.schema === "buildchain.consumer-contract-lock/v2") {
35
+ const validated = consumerContractLock(lock);
36
+ if (canonicalJson(validated) !== canonicalJson(lock))
37
+ throw new Error(
38
+ "Minimal consumer contract lock contains unsupported fields",
39
+ );
40
+ return { ref: validated.runtime.sha, origin: "consumer-contract-lock" };
41
+ }
31
42
  if (lock.contract !== "kungfu-buildchain-contract-lock")
32
43
  throw new Error("Unsupported Buildchain contract lock");
33
44
  const sha = commit(lock.buildchain?.resolvedSha, "Contract lock runtime");
@@ -92,6 +103,9 @@ export async function selectExecutionRuntime(
92
103
  ...(selected.origin === "contract-lock"
93
104
  ? { contract: { digest: request.lock.buildchain.contractDigest } }
94
105
  : {}),
106
+ ...(selected.origin === "consumer-contract-lock"
107
+ ? { contract: { consumerConfigDigest: request.lock.configDigest } }
108
+ : {}),
95
109
  class:
96
110
  selected.origin === "contract-lock" &&
97
111
  request.lock.buildchain.ref === "v4-alpha"
@@ -0,0 +1,62 @@
1
+ import { resolveRecoverySource } from "./recovery.js";
2
+ import {
3
+ pipelineRuntimeContinuation,
4
+ pipelineRecoveryContinuation,
5
+ } from "../../workflow/pipeline/runtime-source.js";
6
+
7
+ export async function resolveRuntimeActionSource(
8
+ core,
9
+ env,
10
+ { providerFactory, github, pipelineLookup },
11
+ ) {
12
+ let source = {
13
+ repository: core.getInput("source-repository") || env.GITHUB_REPOSITORY,
14
+ sha: core.getInput("source-sha") || env.GITHUB_SHA,
15
+ ref: env.GITHUB_REF,
16
+ };
17
+ const pipelineAttempt = core.getInput("pipeline-attempt");
18
+ const recoveryAttempt = core.getInput("recovery-attempt");
19
+ const resumeRunId = core.getInput("resume-run-id");
20
+ if (
21
+ [pipelineAttempt, recoveryAttempt, resumeRunId].filter(Boolean).length > 1
22
+ )
23
+ throw new Error("Runtime source selectors are mutually exclusive");
24
+ let continuation;
25
+ if (recoveryAttempt) {
26
+ continuation = await pipelineRecoveryContinuation(
27
+ recoveryAttempt,
28
+ source.repository,
29
+ core.getInput("token", { required: true }),
30
+ pipelineLookup,
31
+ );
32
+ source = continuation.source;
33
+ }
34
+ if (pipelineAttempt) {
35
+ continuation = await pipelineRuntimeContinuation(
36
+ pipelineAttempt,
37
+ source.repository,
38
+ core.getInput("token", { required: true }),
39
+ pipelineLookup,
40
+ );
41
+ source = continuation.source;
42
+ }
43
+ if (resumeRunId) {
44
+ const reader = providerFactory(github, {
45
+ sourceRepository: source.repository,
46
+ sourceSha: source.sha,
47
+ actor: env.GITHUB_ACTOR,
48
+ eventName: env.GITHUB_EVENT_NAME,
49
+ });
50
+ await reader.authorize({ origin: "runtime-parameter" });
51
+ source = await resolveRecoverySource(
52
+ {
53
+ repository: source.repository,
54
+ runId: resumeRunId,
55
+ currentRunId: env.GITHUB_RUN_ID,
56
+ workflow: env.GITHUB_WORKFLOW_REF?.slice(source.repository.length + 1),
57
+ },
58
+ reader.readRun,
59
+ );
60
+ }
61
+ return { source, continuation, resumeRunId, pipelineAttempt };
62
+ }
@@ -0,0 +1,170 @@
1
+ import {
2
+ createIntent,
3
+ recordDigest,
4
+ } from "../../release/discussion/envelope.js";
5
+ import {
6
+ object,
7
+ text,
8
+ list,
9
+ choice,
10
+ unique,
11
+ relativePath,
12
+ } from "../../consumer/contract/shape.js";
13
+ import { CONSUMER_CONTRACT } from "../../consumer/contract/plan.js";
14
+
15
+ export const PIPELINE_PHASES = [
16
+ "admission",
17
+ "build",
18
+ "review",
19
+ "warrant",
20
+ "merge",
21
+ "publish",
22
+ "distribution",
23
+ "next-development",
24
+ ];
25
+ export const SOURCE_GENERATION = "buildchain.source-generation/v1";
26
+ export const BUSINESS_ATTEMPT = "buildchain.business-attempt/v1";
27
+ const ROOT = /^sha256:[0-9a-f]{64}$/u;
28
+
29
+ export function pipelineIntent({
30
+ repository,
31
+ repositoryId,
32
+ pullRequest,
33
+ targetBranch,
34
+ phases,
35
+ runtime,
36
+ }) {
37
+ text(repository, "repository", /^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/u);
38
+ text(repositoryId, "repositoryId");
39
+ if (!Number.isSafeInteger(pullRequest) || pullRequest < 1)
40
+ throw new Error("Invalid PR intent number");
41
+ text(
42
+ targetBranch,
43
+ "targetBranch",
44
+ /^(?:dev|alpha|release|publish-gate)\/[A-Za-z0-9/._-]+$/u,
45
+ );
46
+ list(phases, "phases", (phase, field) =>
47
+ choice(phase, PIPELINE_PHASES, field),
48
+ );
49
+ unique(phases, "phases");
50
+ if (
51
+ phases.some(
52
+ (phase, i) =>
53
+ i &&
54
+ PIPELINE_PHASES.indexOf(phase) <=
55
+ PIPELINE_PHASES.indexOf(phases[i - 1]),
56
+ )
57
+ )
58
+ throw new Error("Intent phases must follow pipeline order");
59
+ if (phases[0] !== "admission")
60
+ throw new Error("Intent must begin with admission");
61
+ return createIntent({
62
+ repository,
63
+ key: `pr-${pullRequest}:${targetBranch}`,
64
+ expectedNodes: phases,
65
+ runtime,
66
+ source: { kind: "pipeline", repositoryId, pullRequest, targetBranch },
67
+ });
68
+ }
69
+
70
+ export function validateConsumerSource(source, repository) {
71
+ object(
72
+ source,
73
+ [
74
+ "schema",
75
+ "repository",
76
+ "commit",
77
+ "tree",
78
+ "configPath",
79
+ "configBlob",
80
+ "configDigest",
81
+ "contract",
82
+ ],
83
+ [],
84
+ "source",
85
+ );
86
+ if (
87
+ source.schema !== "buildchain.consumer-source/v1" ||
88
+ source.contract !== CONSUMER_CONTRACT ||
89
+ source.repository !== repository
90
+ )
91
+ throw new Error("Generation requires the admitted consumer source");
92
+ for (const key of ["commit", "tree", "configBlob"])
93
+ text(source[key], `source.${key}`, /^[0-9a-f]{40}$/u);
94
+ relativePath(source.configPath, "source.configPath");
95
+ text(source.configDigest, "source.configDigest", ROOT);
96
+ return source;
97
+ }
98
+
99
+ export function sourceGeneration(intent, source, baseCommit) {
100
+ validateConsumerSource(source, intent.repository);
101
+ text(baseCommit, "baseCommit", /^[0-9a-f]{40}$/u);
102
+ const value = {
103
+ schema: SOURCE_GENERATION,
104
+ intent: intent.id,
105
+ source: structuredClone(source),
106
+ baseCommit,
107
+ };
108
+ return { ...value, id: recordDigest(value) };
109
+ }
110
+
111
+ export function validateGeneration(value, intent) {
112
+ object(
113
+ value,
114
+ ["schema", "intent", "source", "baseCommit", "id"],
115
+ [],
116
+ "generation",
117
+ );
118
+ const expected = sourceGeneration(intent, value.source, value.baseCommit);
119
+ if (recordDigest(value) !== recordDigest(expected))
120
+ throw new Error("Generation identity mismatch");
121
+ return value;
122
+ }
123
+
124
+ export function businessAttempt({
125
+ intent,
126
+ generation,
127
+ predecessor = "",
128
+ requestKey,
129
+ }) {
130
+ validateGeneration(generation, intent);
131
+ text(requestKey, "requestKey");
132
+ if (requestKey.length > 240)
133
+ throw new Error("Attempt request key exceeds its bound");
134
+ if (predecessor) text(predecessor, "predecessor", /^attempt-[0-9a-f]{64}$/u);
135
+ const value = {
136
+ schema: BUSINESS_ATTEMPT,
137
+ intent: intent.id,
138
+ generation: generation.id,
139
+ predecessor,
140
+ requestKey,
141
+ };
142
+ return { ...value, id: `attempt-${recordDigest(value).slice(7)}` };
143
+ }
144
+
145
+ export function validateAttempt(value, intent, generation) {
146
+ object(
147
+ value,
148
+ ["schema", "intent", "generation", "predecessor", "requestKey", "id"],
149
+ [],
150
+ "attempt",
151
+ );
152
+ const expected = businessAttempt({
153
+ intent,
154
+ generation,
155
+ predecessor: value.predecessor,
156
+ requestKey: value.requestKey,
157
+ });
158
+ if (recordDigest(value) !== recordDigest(expected))
159
+ throw new Error("Business attempt identity mismatch");
160
+ return value;
161
+ }
162
+
163
+ export function providerWriter(value, repository) {
164
+ object(value, ["repository", "runId", "runAttempt", "jobId"], [], "writer");
165
+ if (value.repository !== repository)
166
+ throw new Error("Writer belongs to a different repository");
167
+ for (const key of ["runId", "runAttempt", "jobId"])
168
+ text(value[key], `writer.${key}`, /^[1-9][0-9]*$/u);
169
+ return `${value.runId}:${value.runAttempt}:${value.jobId}`;
170
+ }