@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,198 @@
1
+ import { execFileSync } from "node:child_process";
2
+ import { recordDigest } from "../../release/discussion/envelope.js";
3
+ import { sourceQualificationPredicates } from "../../dev-delivery/source-proof/predicates.js";
4
+ import { createNativeCommandContract } from "../../dev-delivery/dev-delivery-warrant.js";
5
+ import { withPipelineSourceObjects } from "../../providers/github/pipeline-checkout.js";
6
+ import { pipelinePlatforms } from "./platforms.js";
7
+ import { pipelineCandidateRoot } from "./reconcile.js";
8
+ import { consumerWorkflows } from "../../consumer/contract/entries.js";
9
+ import { pipelineRunEntry } from "../../providers/github/pipeline-run-entry.js";
10
+ import {
11
+ PIPELINE_BUILD_QUALIFICATION,
12
+ qualifyPipelineBuild,
13
+ } from "./build-qualification.js";
14
+
15
+ const quote = (value) => `'${value.replaceAll("'", "'\\''")}'`;
16
+
17
+ export function qualifyPipelineSourceRun(run, current, build) {
18
+ const source = current.generation.source;
19
+ const pull = run.pull_requests?.find(
20
+ (pr) => pr.number === current.intent.source.pullRequest,
21
+ );
22
+ if (
23
+ !Number.isSafeInteger(run.id) ||
24
+ run.id < 1 ||
25
+ run.status !== "completed" ||
26
+ run.conclusion !== "success" ||
27
+ ![
28
+ "pull_request",
29
+ "repository_dispatch",
30
+ "pull_request_review",
31
+ "workflow_dispatch",
32
+ ].includes(run.event) ||
33
+ run.repository?.full_name !== current.intent.repository ||
34
+ (run.event === "pull_request" && run.head_sha !== source.commit) ||
35
+ (run.event === "pull_request" &&
36
+ pull?.base?.sha !== current.generation.baseCommit)
37
+ )
38
+ throw new Error(
39
+ "Pipeline source run does not qualify the exact PR generation",
40
+ );
41
+ if (build?.schema === PIPELINE_BUILD_QUALIFICATION) {
42
+ const verified = qualifyPipelineBuild({ ...build, source });
43
+ if (
44
+ recordDigest(verified) !== recordDigest(build) ||
45
+ build.runId !== run.id ||
46
+ build.runAttempt !== run.run_attempt ||
47
+ build.outcome !== "success"
48
+ )
49
+ throw new Error(
50
+ "Recovered source run does not bind complete independently qualified product segments",
51
+ );
52
+ } else if (run.event !== "pull_request") {
53
+ const { root, ...body } = build || {};
54
+ if (
55
+ root !== recordDigest(body) ||
56
+ body.schema !== "buildchain.pipeline-build-readback/v1" ||
57
+ body.runId !== run.id ||
58
+ body.runAttempt !== run.run_attempt ||
59
+ body.outcome !== "success" ||
60
+ recordDigest(body.source) !== recordDigest(source)
61
+ )
62
+ throw new Error(
63
+ "Attempt wake requires retained exact product build provider readback",
64
+ );
65
+ }
66
+ pipelineEntry(run);
67
+ return run;
68
+ }
69
+
70
+ function pipelineEntry(run) {
71
+ return pipelineRunEntry(run).channel;
72
+ }
73
+
74
+ function predicateInput(directory, current, run) {
75
+ const source = current.generation.source;
76
+ for (const [file, expected] of Object.entries(
77
+ consumerWorkflows(pipelineEntry(run), source.configPath),
78
+ )) {
79
+ const bytes = execFileSync(
80
+ "git",
81
+ ["-C", directory, "show", `${source.commit}:${file}`],
82
+ { encoding: "utf8", maxBuffer: 1024 * 1024 },
83
+ );
84
+ if (bytes !== expected)
85
+ throw new Error(
86
+ "Qualified source changed the minimal consumer workflow contract",
87
+ );
88
+ }
89
+ const paths = execFileSync(
90
+ "git",
91
+ ["-C", directory, "ls-tree", "-r", "--name-only", "-z", source.commit],
92
+ { maxBuffer: 32 * 1024 * 1024 },
93
+ )
94
+ .toString("utf8")
95
+ .split("\0")
96
+ .filter(Boolean)
97
+ .sort();
98
+ if (
99
+ !paths.length ||
100
+ paths.length > 50_000 ||
101
+ paths.some((file) => /[\r\n]/u.test(file))
102
+ )
103
+ throw new Error(
104
+ "Pipeline source inventory exceeds its qualification contract",
105
+ );
106
+ const policyPaths = paths.filter(
107
+ (file) =>
108
+ file === source.configPath || file.startsWith(".github/workflows/"),
109
+ );
110
+ const dependencies = paths.filter((file) =>
111
+ /(?:^|\/)(?:package(?:-lock)?\.json|pnpm-lock\.yaml|yarn\.lock|Cargo\.(?:toml|lock)|go\.(?:mod|sum))$/u.test(
112
+ file,
113
+ ),
114
+ );
115
+ return {
116
+ cwd: directory,
117
+ repository: current.intent.repository,
118
+ protectedBase: current.intent.source.targetBranch,
119
+ sourceHead: source.commit,
120
+ qualifiedBase: current.generation.baseCommit,
121
+ nodeVersion: "24",
122
+ workingDirectory: ".",
123
+ policyPaths: JSON.stringify(policyPaths),
124
+ closurePaths: JSON.stringify(paths),
125
+ dependencyPaths: JSON.stringify(
126
+ dependencies.length ? dependencies : policyPaths,
127
+ ),
128
+ requiredContexts: JSON.stringify(["check"]),
129
+ };
130
+ }
131
+
132
+ export async function preparePipelineDelivery(
133
+ { current, run, build, runtimeSha, token, plan },
134
+ withObjects = withPipelineSourceObjects,
135
+ ) {
136
+ qualifyPipelineSourceRun(run, current, build);
137
+ if (!/^[0-9a-f]{40}$/u.test(runtimeSha || ""))
138
+ throw new Error("Pipeline delivery requires the prepared exact runtime");
139
+ const source = current.generation.source;
140
+ const platforms = plan
141
+ ? pipelinePlatforms(plan).map((entry) => entry.platform)
142
+ : ["linux-x64"];
143
+ const nativePlatform = platforms.includes("linux-x64")
144
+ ? "linux-x64"
145
+ : platforms[0];
146
+ const predicates = await withObjects(
147
+ {
148
+ repository: current.intent.repository,
149
+ sourceHead: source.commit,
150
+ baseCommit: current.generation.baseCommit,
151
+ token,
152
+ },
153
+ (directory) =>
154
+ sourceQualificationPredicates(predicateInput(directory, current, run)),
155
+ );
156
+ // Native composition owns .buildchain/candidate beside the prepared runtime.
157
+ // Do not bind one job's absolute workspace path into another runner's command.
158
+ const nativeCommand = `node ../runtime/packages/core/workflow/commands/pipeline-build.mjs --platform ${nativePlatform} --config-path ${quote(source.configPath)}`;
159
+ const environmentRoot = recordDigest({
160
+ schema: "buildchain.pipeline-native-environment/v1",
161
+ runtimeSha,
162
+ node: "24",
163
+ platform: nativePlatform,
164
+ sourceContract: source.contract,
165
+ });
166
+ return {
167
+ "target-branch": current.intent.source.targetBranch,
168
+ "expected-pr-number": current.intent.source.pullRequest,
169
+ "expected-head-sha": source.commit,
170
+ "source-workflow-run-id": run.id,
171
+ "pipeline-attempt": current.identity.id,
172
+ "delivery-warrant-mode": "required",
173
+ "delivery-class": "native-proof-required",
174
+ "source-root": pipelineCandidateRoot(current),
175
+ "source-identity-root": predicates.sourceIdentityRoot,
176
+ "source-patch-root": predicates.sourcePatchRoot,
177
+ "plan-root": predicates.planRoot,
178
+ "closure-root": predicates.closureRoot,
179
+ "dependency-root": predicates.dependencyRoot,
180
+ "toolchain-root": predicates.toolchainRoot,
181
+ "environment-root": environmentRoot,
182
+ "native-command": nativeCommand,
183
+ "native-platform": nativePlatform,
184
+ "native-command-root":
185
+ createNativeCommandContract(nativeCommand).commandRoot,
186
+ "affected-paths-json": JSON.stringify(predicates.affectedPaths),
187
+ "shard-evidence-roots-json": "[]",
188
+ "required-status-checks": "check",
189
+ "ready-label": "ready",
190
+ "landing-mode": "queue",
191
+ "queue-admission-context": "Queue admission lease",
192
+ "active-lease-context": "Queue family lease/exact",
193
+ "require-approval": true,
194
+ "same-repository-only": true,
195
+ "max-merges": 1,
196
+ "dry-run": false,
197
+ };
198
+ }
@@ -0,0 +1,84 @@
1
+ import { admitEvent } from "../../consumer/contract/entries.js";
2
+
3
+ const SHA = /^[0-9a-f]{40}$/u;
4
+ function prNumber(value) {
5
+ if (!Number.isSafeInteger(value) || value < 1)
6
+ throw new Error("Pipeline event requires an exact PR number");
7
+ return value;
8
+ }
9
+
10
+ // Webhook payloads select readback work only. They never carry source, approval,
11
+ // Warrant or mutation authority. Live provider state owns every decision.
12
+ export function pipelineEvent(name, event, repository) {
13
+ admitEvent(name, event.action || "");
14
+ if (event.repository?.full_name !== repository)
15
+ throw new Error("Pipeline event repository mismatch");
16
+ if (
17
+ ["pull_request", "pull_request_target", "pull_request_review"].includes(
18
+ name,
19
+ )
20
+ )
21
+ return {
22
+ kind: "pull-request",
23
+ pullRequest: prNumber(event.pull_request?.number),
24
+ terminalOnly: name === "pull_request_target",
25
+ };
26
+ if (name === "repository_dispatch") {
27
+ const payload = event.client_payload;
28
+ if (
29
+ !payload ||
30
+ Object.keys(payload).length !== 1 ||
31
+ !/^attempt-[0-9a-f]{64}$/u.test(payload.attempt || "")
32
+ )
33
+ throw new Error("Attempt wake requires only its exact business selector");
34
+ return { kind: "attempt", attempt: payload.attempt, terminalOnly: false };
35
+ }
36
+ if (name === "merge_group") {
37
+ const group = event.merge_group;
38
+ if (
39
+ !SHA.test(group?.head_sha || "") ||
40
+ !SHA.test(group?.base_sha || "") ||
41
+ !group.base_ref?.startsWith("refs/heads/")
42
+ )
43
+ throw new Error("Merge group requires exact provider coordinates");
44
+ return {
45
+ kind: "merge-group",
46
+ commit: group.head_sha,
47
+ baseCommit: group.base_sha,
48
+ branch: group.base_ref.slice(11),
49
+ terminalOnly: false,
50
+ };
51
+ }
52
+ if (!event.ref?.startsWith("refs/heads/") || event.deleted === true)
53
+ return { kind: "ignore", reason: "not-a-live-branch" };
54
+ if (!SHA.test(event.after || ""))
55
+ throw new Error("Branch event requires an exact commit");
56
+ // Internal journal and lock writes must not recursively wake the pipeline.
57
+ const branch = event.ref.slice(11);
58
+ return /^(?:dev|alpha|release|publish-gate)\//u.test(branch)
59
+ ? { kind: "branch", branch, commit: event.after, terminalOnly: false }
60
+ : { kind: "ignore", reason: "not-a-channel-branch" };
61
+ }
62
+
63
+ function matches(pattern, branch) {
64
+ const escaped = pattern
65
+ .split("*")
66
+ .map((part) => part.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&"))
67
+ .join(".*");
68
+ return new RegExp(`^${escaped}$`, "u").test(branch);
69
+ }
70
+
71
+ export function channelRoute(plan, pullRequest, repository) {
72
+ if (
73
+ pullRequest.base?.repo?.full_name !== repository ||
74
+ pullRequest.head?.repo?.full_name !== repository
75
+ )
76
+ throw new Error("Pipeline delivery requires a repository-owned channel PR");
77
+ const routes = plan.channels.filter(
78
+ (channel) =>
79
+ channel.to === pullRequest.base.ref &&
80
+ matches(channel.from, pullRequest.head.ref),
81
+ );
82
+ if (routes.length > 1) throw new Error("Ambiguous protected channel route");
83
+ return routes[0] || null;
84
+ }
@@ -0,0 +1,42 @@
1
+ import { recordDigest } from "../../release/discussion/envelope.js";
2
+
3
+ export const STOP_REQUESTED = "pipeline-stop-requested:";
4
+
5
+ // Credentialed controllers observe this source/attempt fence before Warrant
6
+ // renewal and finalization. The isolated native job receives no read token.
7
+ // Stopping renewal does not release its Warrant: provider-terminal native/seal
8
+ // evidence is still required before a separate controller can transfer ownership.
9
+ export function assertPipelineExecution(observed, expected) {
10
+ if (
11
+ observed.attempt !== expected.attempt ||
12
+ observed.generation !== expected.generation ||
13
+ observed.intent.id !== expected.intent
14
+ )
15
+ throw new Error("Pipeline execution lost its current attempt fence");
16
+ const current = observed.history.at(-1);
17
+ if (
18
+ !current ||
19
+ recordDigest(current.generation.source) !== expected.sourceRoot
20
+ )
21
+ throw new Error("Pipeline execution source fence changed");
22
+ if (
23
+ ["cancelled", "superseded", "failure", "complete"].includes(
24
+ observed.status,
25
+ ) ||
26
+ Object.values(observed.phases).some((record) =>
27
+ record.payload.reason.startsWith(STOP_REQUESTED),
28
+ )
29
+ )
30
+ throw new Error("Pipeline execution has a terminal or requested stop");
31
+ return true;
32
+ }
33
+
34
+ export function pipelineHeartbeat(journal, expected, warrantHeartbeat) {
35
+ return async () => {
36
+ assertPipelineExecution(await journal.read(), expected);
37
+ await warrantHeartbeat();
38
+ // Catch cancellation that raced the provider heartbeat without granting
39
+ // another controller interval from the stale pre-heartbeat read.
40
+ assertPipelineExecution(await journal.read(), expected);
41
+ };
42
+ }
@@ -0,0 +1,59 @@
1
+ import { recordDigest } from "../../release/discussion/envelope.js";
2
+ import { object } from "../../consumer/contract/shape.js";
3
+
4
+ export async function recordPipelineGroup(context, host) {
5
+ object(context, [
6
+ "schema",
7
+ "source",
8
+ "branch",
9
+ "baseCommit",
10
+ "runId",
11
+ "runAttempt",
12
+ "platforms",
13
+ ]);
14
+ if (
15
+ context.schema !== "buildchain.pipeline-group-build-context/v1" ||
16
+ context.runId !== host.runId ||
17
+ context.runAttempt !== host.runAttempt
18
+ )
19
+ throw new Error("Merge group callback execution drift");
20
+ const { run } = await host.runs.read(context.runId, context.runAttempt);
21
+ if (run.event !== "merge_group" || run.head_sha !== context.source.commit)
22
+ throw new Error("Merge group build is not the exact provider execution");
23
+ const queue = await host.queue.getMergeQueueState(context.branch);
24
+ if (
25
+ !queue.enabled ||
26
+ !queue.entries.some(
27
+ (entry) =>
28
+ entry.headSha === context.source.commit &&
29
+ entry.baseSha === context.baseCommit,
30
+ )
31
+ )
32
+ throw new Error("Merge group was removed or changed before verification");
33
+ const readback = await host.runs.build(
34
+ context.runId,
35
+ context.runAttempt,
36
+ context.source,
37
+ context.platforms.map((platform) => platform.platform),
38
+ );
39
+ if (readback.outcome === "success")
40
+ await host.qualifyChannel(context.source, context.branch);
41
+ await host.publishBuildStatus?.(context, readback);
42
+ await host.request(`/repos/${host.repository}/check-runs`, {
43
+ method: "POST",
44
+ body: {
45
+ name: "check",
46
+ head_sha: context.source.commit,
47
+ external_id: `buildchain:merge-group:${recordDigest(context)}`,
48
+ status: "completed",
49
+ conclusion: readback.outcome,
50
+ output: {
51
+ title: "Buildchain exact merge-group verification",
52
+ summary: `Provider product jobs: ${readback.root}`,
53
+ },
54
+ },
55
+ });
56
+ if (readback.outcome !== "success")
57
+ throw new Error("Merge-group products failed verification");
58
+ return readback;
59
+ }
@@ -0,0 +1,74 @@
1
+ import { githubPipelineRuns } from "../../providers/github/pipeline-runs.js";
2
+ import { readPipelineCaller } from "../../providers/github/pipeline-run-entry.js";
3
+ import { pipelineBuildEvidence } from "./build-evidence.js";
4
+ import { qualifyPipelineSourceRun } from "./delivery-request.js";
5
+
6
+ export async function guardPipelineBuild(
7
+ input,
8
+ observed,
9
+ {
10
+ material,
11
+ source,
12
+ request,
13
+ repository,
14
+ runs = githubPipelineRuns(request, repository),
15
+ },
16
+ ) {
17
+ const session = { observed, journal: { read: async () => observed } };
18
+ const build = await pipelineBuildEvidence(session, {
19
+ runId: 0,
20
+ runs,
21
+ source,
22
+ materialStore: () => material,
23
+ });
24
+ if (!build || build.run.id !== Number(input["source-workflow-run-id"]))
25
+ throw new Error(
26
+ "Delivery requires its independently qualified exact product build",
27
+ );
28
+ const current = { ...observed.history.at(-1), intent: observed.intent };
29
+ qualifyPipelineSourceRun(build.run, current, build.readback);
30
+ await readPipelineCaller(
31
+ build.run,
32
+ current.generation.source.configPath,
33
+ request,
34
+ repository,
35
+ );
36
+ return build;
37
+ }
38
+
39
+ // A commit status is source-bound and survives later workflow suites. Publish
40
+ // only the independently reread build at the protected landing boundary, whose
41
+ // existing Actions credential owns statuses:write. Retained build receipts
42
+ // preserve the original executions and their outcomes.
43
+ export async function publishPipelineBuildStatus(
44
+ input,
45
+ build,
46
+ request,
47
+ repository,
48
+ ) {
49
+ if (
50
+ build.readback.source.repository !== repository ||
51
+ build.readback.source.commit !== input["expected-head-sha"] ||
52
+ build.sourceHead !== input["expected-head-sha"] ||
53
+ build.run.id !== Number(input["source-workflow-run-id"]) ||
54
+ build.readback.runId !== build.run.id ||
55
+ build.readback.runAttempt !== build.run.run_attempt ||
56
+ build.outcome !== "success" ||
57
+ build.readback.outcome !== "success"
58
+ )
59
+ throw new Error(
60
+ "Landing status differs from its qualified exact-source build",
61
+ );
62
+ return request(`/repos/${repository}/statuses/${build.sourceHead}`, {
63
+ method: "POST",
64
+ body: {
65
+ context: "check",
66
+ state: "success",
67
+ description: `Qualified product build: ${build.readback.root}`.slice(
68
+ 0,
69
+ 140,
70
+ ),
71
+ target_url: `https://github.com/${repository}/actions/runs/${build.run.id}/attempts/${build.run.run_attempt}`,
72
+ },
73
+ });
74
+ }
@@ -0,0 +1,158 @@
1
+ import { githubJsonClient } from "../../providers/github/json-client.js";
2
+ import { githubAttemptJournal } from "../../providers/github/attempt-journal.js";
3
+ import { githubAttemptIndex } from "../../providers/github/attempt-index.js";
4
+ import { readBusinessAttempt } from "../attempt/reader.js";
5
+ import { assertPipelineExecution } from "./fence.js";
6
+ import { recordDigest } from "../../release/discussion/envelope.js";
7
+ import { githubPipelineSource } from "../../providers/github/pipeline-source.js";
8
+ import { githubPipelinePolicy } from "../../providers/github/pipeline-policy.js";
9
+ import { getOctokit } from "@actions/github";
10
+ import { discussionMaterials } from "../../providers/github/discussions/materials.js";
11
+ import { pipelineMaterials } from "./materials.js";
12
+ import { guardPipelineBuild } from "./guard-build.js";
13
+
14
+ async function assertRetainedRequest(input, connection, observed) {
15
+ const current = observed.history.at(-1);
16
+ const event = [...current.events]
17
+ .reverse()
18
+ .find((entry) =>
19
+ entry.payload.materials.some((material) =>
20
+ material.id.startsWith("delivery/execution-"),
21
+ ),
22
+ );
23
+ const reference = event?.payload.materials.find((material) =>
24
+ material.id.startsWith("delivery/execution-"),
25
+ );
26
+ if (!reference)
27
+ throw new Error("Pipeline delivery has no admitted execution request");
28
+ const archive = discussionMaterials({
29
+ octokit: getOctokit(connection.token),
30
+ repository: connection.repository,
31
+ intentId: observed.intent.id,
32
+ });
33
+ const material = pipelineMaterials(archive, {
34
+ repository: connection.repository,
35
+ attempt: current.identity,
36
+ });
37
+ const execution = await material.read(reference);
38
+ validatePipelineExecutionRequest(execution, input, observed, {
39
+ runId: Number(process.env.GITHUB_RUN_ID),
40
+ runAttempt: Number(process.env.GITHUB_RUN_ATTEMPT),
41
+ });
42
+ return material;
43
+ }
44
+
45
+ export function validatePipelineExecutionRequest(
46
+ execution,
47
+ input,
48
+ observed,
49
+ coordinates,
50
+ ) {
51
+ if (execution.schema !== "buildchain.pipeline-delivery-execution/v1")
52
+ throw new Error("Pipeline delivery request schema is unsupported");
53
+ if (
54
+ execution.attempt !== observed.attempt ||
55
+ execution.generation !== observed.generation ||
56
+ execution.runId !== coordinates.runId ||
57
+ execution.runAttempt !== coordinates.runAttempt
58
+ )
59
+ throw new Error(
60
+ "Pipeline delivery is not the exact admitted provider execution",
61
+ );
62
+ for (const [key, value] of Object.entries(execution.request))
63
+ if (
64
+ !Object.hasOwn(input, key) ||
65
+ recordDigest(input[key]) !== recordDigest(value)
66
+ )
67
+ throw new Error(
68
+ `Pipeline delivery changed its retained request field: ${key}`,
69
+ );
70
+ }
71
+
72
+ export async function guardPipelineAdmission(input, connection) {
73
+ const observed = await guardPipelineDelivery(input, connection);
74
+ if (!observed) return null;
75
+ const material = await assertRetainedRequest(input, connection, observed);
76
+ const request = githubJsonClient({
77
+ token: connection.token,
78
+ userAgent: "buildchain-pipeline-admission",
79
+ });
80
+ const current = { ...observed.history.at(-1), intent: observed.intent };
81
+ const source = githubPipelineSource(request, connection.repository);
82
+ const admission = await source.observeIntent(
83
+ observed.intent,
84
+ current.generation,
85
+ { "config-path": current.generation.source.configPath },
86
+ );
87
+ const { live } = admission;
88
+ if (
89
+ admission.cleanupOnly ||
90
+ !live.source ||
91
+ live.state !== "open" ||
92
+ live.draft ||
93
+ !live.ready ||
94
+ live.baseCommit !== current.generation.baseCommit ||
95
+ recordDigest(live.source) !== recordDigest(current.generation.source)
96
+ )
97
+ throw new Error(
98
+ "Pipeline source is no longer admitted for a delivery effect",
99
+ );
100
+ const policy = await githubPipelinePolicy(
101
+ request,
102
+ connection.repository,
103
+ ).observe(current, admission.protectedPlan.review);
104
+ if (!policy.review || !policy.checksPassing)
105
+ throw new Error(
106
+ "Pipeline protected review or required checks changed before delivery effect",
107
+ );
108
+ const build = await guardPipelineBuild(input, observed, {
109
+ material,
110
+ source,
111
+ request,
112
+ repository: connection.repository,
113
+ });
114
+ const fresh = await guardPipelineDelivery(input, connection);
115
+ await connection.publishBuildStatus?.(build);
116
+ return fresh;
117
+ }
118
+
119
+ export async function guardPipelineDelivery(input, connection, lookup) {
120
+ const attempt = input["pipeline-attempt"];
121
+ // Existing advanced delivery callers retain their domain authority. The new
122
+ // normal entry always supplies its internally selected business attempt.
123
+ if (!attempt) return null;
124
+ if (!connection.token)
125
+ throw new Error("Pipeline controller requires its scoped read credential");
126
+ const request = githubJsonClient({
127
+ token: connection.token,
128
+ userAgent: "buildchain-pipeline",
129
+ });
130
+ const index =
131
+ lookup ||
132
+ githubAttemptIndex(
133
+ request,
134
+ githubAttemptJournal(request),
135
+ connection.repository,
136
+ );
137
+ const loaded = await index.resolve(attempt);
138
+ const observed = readBusinessAttempt(loaded.snapshot);
139
+ const current = observed.history.at(-1);
140
+ if (
141
+ !current ||
142
+ observed.intent.repository !== connection.repository ||
143
+ observed.intent.source.targetBranch !== input["target-branch"] ||
144
+ observed.intent.source.pullRequest !==
145
+ Number(input["expected-pr-number"]) ||
146
+ current.generation.source.commit !== input["expected-head-sha"]
147
+ )
148
+ throw new Error(
149
+ "Pipeline delivery request changed its admitted source intent",
150
+ );
151
+ assertPipelineExecution(observed, {
152
+ attempt,
153
+ intent: observed.intent.id,
154
+ generation: current.generation.id,
155
+ sourceRoot: recordDigest(current.generation.source),
156
+ });
157
+ return observed;
158
+ }