@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,144 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { createHash } from "node:crypto";
4
+ import { getOctokit } from "@actions/github";
5
+ import { installationRoot } from "../../runtime/installation-root.js";
6
+ import { preparedRuntimeSelection } from "../../runtime/entry/selection.js";
7
+ import { githubJsonClient } from "../../providers/github/json-client.js";
8
+ import { githubAttemptJournal } from "../../providers/github/attempt-journal.js";
9
+ import { githubAttemptIndex } from "../../providers/github/attempt-index.js";
10
+ import { githubPipelineSource } from "../../providers/github/pipeline-source.js";
11
+ import { githubPipelineRuns } from "../../providers/github/pipeline-runs.js";
12
+ import { githubPipelineWorker } from "../../providers/github/pipeline-worker.js";
13
+ import { githubPipelinePolicy } from "../../providers/github/pipeline-policy.js";
14
+ import { discussionMaterials } from "../../providers/github/discussions/materials.js";
15
+ import { pipelineMaterials } from "./materials.js";
16
+ import { GitHubClient } from "../../dev-delivery/admission/github-client.js";
17
+ import { githubPipelineIntegration } from "../../providers/github/pipeline-integration.js";
18
+ import { recordPipelineGroup } from "./group-control.js";
19
+ import { controlPipelineChannel } from "./channel-control.js";
20
+ import { qualifyPipelineChannelSource } from "./channel-source.js";
21
+ import { settlePipeline } from "./settlement.js";
22
+ import { pipelineProjection } from "./projection.js";
23
+ import { resumePipelineNotifications } from "./notifications.js";
24
+ import { projectPipelineStatus } from "./web-status.js";
25
+ import { inspectRecoveryPublication } from "../../publication/pipeline/recovery-inspection.js";
26
+
27
+ export function pipelineQueue({ queueToken, ...options }) {
28
+ const reader = new GitHubClient(options);
29
+ return {
30
+ getMergeQueueState: (branch) => reader.getMergeQueueState(branch),
31
+ enqueuePullRequest: async (input) => {
32
+ // GITHUB_TOKEN enqueue suppresses the merge_group workflow it requires.
33
+ if (!queueToken || queueToken === options.token)
34
+ throw new Error(
35
+ "Channel enqueue requires a distinct App or automation credential",
36
+ );
37
+ return new GitHubClient({
38
+ ...options,
39
+ token: queueToken,
40
+ }).enqueuePullRequest(input);
41
+ },
42
+ };
43
+ }
44
+
45
+ export async function pipelineHost(core, env, jobName) {
46
+ const token = core.getInput("token", { required: true });
47
+ const repository = env.GITHUB_REPOSITORY;
48
+ const selection = preparedRuntimeSelection(
49
+ JSON.parse(env.BUILDCHAIN_RUNTIME_SELECTION),
50
+ );
51
+ const runtimeRoot = installationRoot(import.meta.url);
52
+ const reader = fs.readFileSync(
53
+ path.join(runtimeRoot, "dist/readers/business-attempt.cjs"),
54
+ );
55
+ const runtime = {
56
+ repository: selection.repository,
57
+ sha: selection.sha,
58
+ readerDigest: `sha256:${createHash("sha256").update(reader).digest("hex")}`,
59
+ };
60
+ const request = githubJsonClient({ token, userAgent: "buildchain-pipeline" });
61
+ const github = getOctokit(token);
62
+ const provider = githubAttemptJournal(request);
63
+ const index = githubAttemptIndex(request, provider, repository);
64
+ const runs = githubPipelineRuns(request, repository);
65
+ const runId = Number(env.GITHUB_RUN_ID),
66
+ runAttempt = Number(env.GITHUB_RUN_ATTEMPT);
67
+ const writer = await runs.writer(runId, runAttempt, jobName);
68
+ const source = githubPipelineSource(request, repository);
69
+ const [owner, repo] = repository.split("/");
70
+ const archives = new Map();
71
+ function archive(session) {
72
+ if (!archives.has(session.intent.id))
73
+ archives.set(
74
+ session.intent.id,
75
+ discussionMaterials({
76
+ octokit: github,
77
+ repository,
78
+ intentId: session.intent.id,
79
+ // Storage is partitioned by verified provider job, not attempt authority.
80
+ // Journal references still select exact immutable asset IDs and digests.
81
+ partition: `${writer.runId}-${writer.runAttempt}-${writer.jobId}`,
82
+ authorityDescription:
83
+ "The canonical Buildchain intent Git journal owns transaction state.",
84
+ }),
85
+ );
86
+ return archives.get(session.intent.id);
87
+ }
88
+ const host = {
89
+ repository,
90
+ token,
91
+ github,
92
+ selection,
93
+ runtime,
94
+ runtimeRoot,
95
+ request,
96
+ provider,
97
+ index,
98
+ runs,
99
+ runId,
100
+ runAttempt,
101
+ writer,
102
+ source,
103
+ workers: githubPipelineWorker(request),
104
+ policy: githubPipelinePolicy(request, repository),
105
+ queue: pipelineQueue({
106
+ repository: { owner, repo },
107
+ token,
108
+ queueToken: core.getInput("queue-token"),
109
+ }),
110
+ integration: githubPipelineIntegration(request, repository, source, runs),
111
+ groupRecord: (context) => recordPipelineGroup(context, host),
112
+ channel: (session, admission, inputs) =>
113
+ controlPipelineChannel(session, admission, inputs, host),
114
+ qualifyChannel: (source, branch) =>
115
+ qualifyPipelineChannelSource(source, branch, host),
116
+ settle: (session, fresh, delivery) =>
117
+ settlePipeline(session, fresh, delivery, host),
118
+ project: (session) =>
119
+ projectPipelineStatus(session, {
120
+ project: pipelineProjection(github.graphql),
121
+ core,
122
+ }),
123
+ notifyTerminal: (session) => resumePipelineNotifications(session, host),
124
+ recoverPublication: (session, admitted) =>
125
+ inspectRecoveryPublication(session, admitted, host),
126
+ materialStore: (session) =>
127
+ pipelineMaterials(archive(session), {
128
+ repository,
129
+ attempt: session.observed.history.at(-1).identity,
130
+ }),
131
+ productArchive: archive,
132
+ wake: async (attempt) => {
133
+ await index.resolve(attempt);
134
+ await request(`/repos/${repository}/dispatches`, {
135
+ method: "POST",
136
+ body: {
137
+ event_type: "buildchain-attempt-wake",
138
+ client_payload: { attempt },
139
+ },
140
+ });
141
+ },
142
+ };
143
+ return host;
144
+ }
@@ -0,0 +1,113 @@
1
+ import { recordDigest } from "../../release/discussion/envelope.js";
2
+
3
+ // The landing job already owns statuses:write. Keep its exact admission alive
4
+ // through the actual queue result so later channel PRs need not borrow the
5
+ // temporary queue branch's Actions check suite.
6
+ async function observeProtectedQueue(current, ports) {
7
+ const { repository, request, queue } = ports;
8
+ const now = ports.now || Date.now;
9
+ const pause =
10
+ ports.pause || ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
11
+ const deadline = now() + 120 * 60 * 1000;
12
+ const number = current.intent.source.pullRequest;
13
+ const branch = current.intent.source.targetBranch;
14
+ let queueExitAt;
15
+ while (true) {
16
+ const pr = await request(`/repos/${repository}/pulls/${number}`);
17
+ if (
18
+ pr.head?.sha !== current.generation.source.commit ||
19
+ pr.head?.repo?.full_name !== repository ||
20
+ pr.base?.repo?.full_name !== repository ||
21
+ pr.base?.ref !== branch
22
+ )
23
+ throw new Error("Integrated status lost its exact admitted PR source");
24
+ if (pr.merged) return { pr, deadline, now, pause };
25
+ if (now() >= deadline)
26
+ throw new Error(
27
+ "Protected queue did not complete the admitted integration",
28
+ );
29
+ const pending = await queue(branch);
30
+ if (
31
+ pr.state !== "open" ||
32
+ !pending.enabled ||
33
+ !pending.entries.some(
34
+ (entry) =>
35
+ entry.pullRequestNumber === number &&
36
+ entry.pullRequestHeadSha === current.generation.source.commit,
37
+ )
38
+ ) {
39
+ // Queue removal can precede the PR endpoint's merged readback. Bound
40
+ // convergence without treating absence or a closed PR as merge evidence.
41
+ queueExitAt ??= now();
42
+ if (now() - queueExitAt >= 30_000)
43
+ throw new Error("Admitted PR left its protected merge queue");
44
+ } else queueExitAt = undefined;
45
+ await pause(10000);
46
+ }
47
+ }
48
+
49
+ async function observeIntegrationWorkflow(merged, ports) {
50
+ const { pr, deadline, now, pause } = merged;
51
+ if (!/^[0-9a-f]{40}$/u.test(pr.merge_commit_sha || ""))
52
+ throw new Error("Merged PR has no exact integration commit");
53
+ while (true) {
54
+ const result = await ports.request(
55
+ `/repos/${ports.repository}/actions/runs?event=merge_group&head_sha=${pr.merge_commit_sha}&per_page=100`,
56
+ );
57
+ if (!Array.isArray(result.workflow_runs) || result.total_count > 100)
58
+ throw new Error("Integration workflow inventory exceeds its bound");
59
+ const runs = result.workflow_runs.filter(
60
+ (run) =>
61
+ run.path?.split("@")[0] === ".github/workflows/buildchain.yml" &&
62
+ run.event === "merge_group" &&
63
+ run.head_sha === pr.merge_commit_sha,
64
+ );
65
+ if (
66
+ runs.some(
67
+ (run) => run.status === "completed" && run.conclusion === "success",
68
+ )
69
+ )
70
+ return;
71
+ if (!runs.some((run) => run.status !== "completed") || now() >= deadline)
72
+ throw new Error("Merged integration has no successful queue workflow");
73
+ await pause(10000);
74
+ }
75
+ }
76
+
77
+ function validateIntegratedStatus(proof, current, repository) {
78
+ const { root, ...body } = proof;
79
+ if (
80
+ recordDigest(body) !== root ||
81
+ proof.repository !== repository ||
82
+ proof.pullRequest !== current.intent.source.pullRequest ||
83
+ proof.sourceHead !== current.generation.source.commit ||
84
+ proof.branch !== current.intent.source.targetBranch ||
85
+ proof.build.source.commit !== proof.mergeCommit ||
86
+ proof.build.source.repository !== repository ||
87
+ proof.build.outcome !== "success" ||
88
+ proof.run.conclusion !== "success" ||
89
+ proof.run.status !== "completed" ||
90
+ proof.run.event !== "merge_group" ||
91
+ proof.run.head_sha !== proof.mergeCommit
92
+ )
93
+ throw new Error("Integrated status differs from its verified queue result");
94
+ }
95
+
96
+ export async function publishIntegratedPipelineStatus(current, ports) {
97
+ const merged = await observeProtectedQueue(current, ports);
98
+ await observeIntegrationWorkflow(merged, ports);
99
+ const { repository, integration, status } = ports;
100
+ const proof = await integration.observe(current);
101
+ validateIntegratedStatus(proof, current, repository);
102
+ const { root } = proof;
103
+ await status(`/repos/${repository}/statuses/${proof.mergeCommit}`, {
104
+ method: "POST",
105
+ body: {
106
+ context: "check",
107
+ state: "success",
108
+ description: `Qualified protected integration: ${root}`.slice(0, 140),
109
+ target_url: `https://github.com/${repository}/actions/runs/${proof.run.id}/attempts/${proof.run.run_attempt}`,
110
+ },
111
+ });
112
+ return proof;
113
+ }
@@ -0,0 +1,58 @@
1
+ import {
2
+ canonicalJson,
3
+ recordDigest,
4
+ } from "../../release/discussion/envelope.js";
5
+ import {
6
+ materialReference,
7
+ verifyMaterialBytes,
8
+ } from "../attempt/materials.js";
9
+
10
+ // Receipts are retained before the atomic journal references them. An interrupted
11
+ // upload can leave unreferenced material; it cannot advance attempt authority.
12
+ export function pipelineMaterials(archive, { repository, attempt }) {
13
+ async function retain(id, receipt, kind = "receipt") {
14
+ const bytes = Buffer.from(`${canonicalJson(receipt)}\n`);
15
+ const handle = await archive.put(bytes, {
16
+ name: `${id.replaceAll("/", "-")}.json`,
17
+ mediaType: "application/json",
18
+ });
19
+ const reference = materialReference(
20
+ {
21
+ id,
22
+ kind,
23
+ digest: handle.digest,
24
+ bytes: handle.size,
25
+ url: `https://api.github.com/repos/${repository}/releases/assets/${handle.id}`,
26
+ producerAttempt: attempt.id,
27
+ generation: attempt.generation,
28
+ },
29
+ attempt,
30
+ repository,
31
+ );
32
+ verifyMaterialBytes(reference, await archive.read(handle));
33
+ return reference;
34
+ }
35
+ async function read(reference) {
36
+ materialReference(reference, attempt, repository);
37
+ const prefix = `https://api.github.com/repos/${repository}/releases/assets/`;
38
+ if (!reference.url.startsWith(prefix))
39
+ throw new Error(
40
+ "Pipeline receipt is not in its retained material archive",
41
+ );
42
+ const id = reference.url.slice(prefix.length);
43
+ if (!/^[1-9][0-9]*$/u.test(id) || !Number.isSafeInteger(Number(id)))
44
+ throw new Error("Pipeline receipt asset identity is invalid");
45
+ const bytes = await archive.read({
46
+ id: Number(id),
47
+ size: reference.bytes,
48
+ digest: reference.digest,
49
+ });
50
+ verifyMaterialBytes(reference, bytes);
51
+ const receipt = JSON.parse(bytes.toString("utf8"));
52
+ // Normalize the supported JSON value domain, rejecting undefined or NaN
53
+ // from alternate injected archive implementations before returning it.
54
+ recordDigest(receipt);
55
+ return receipt;
56
+ }
57
+ return { retain, read };
58
+ }
@@ -0,0 +1,36 @@
1
+ import { wakePipelineCandidate } from "./wake.js";
2
+ import { pipelineCandidate } from "./reconcile.js";
3
+ import { notifyPipelineParent } from "./parent-notification.js";
4
+
5
+ export async function resumePipelineNotifications(session, host) {
6
+ await notifyPipelineParent(session, host);
7
+ const observed = await session.journal.read();
8
+ const current = { ...observed.history.at(-1), intent: session.intent };
9
+ const terminal = Object.values(current.phases).filter((phase) =>
10
+ ["success", "cancelled", "failure"].includes(phase.payload.state),
11
+ );
12
+ for (const phase of terminal) {
13
+ for (const reference of phase.payload.materials.filter((material) =>
14
+ /^(?:merge\/settlement-|cancellation\/receipt-|failure\/)/u.test(
15
+ material.id,
16
+ ),
17
+ )) {
18
+ const receipt = await host.materialStore(session).read(reference);
19
+ const successor =
20
+ receipt.receipt?.successorWake ||
21
+ (Array.isArray(receipt.candidates)
22
+ ? pipelineCandidate(receipt, current)?.terminal?.successorWake
23
+ : null);
24
+ if (successor)
25
+ await wakePipelineCandidate(
26
+ successor,
27
+ {
28
+ repository: host.repository,
29
+ token: host.token,
30
+ branch: session.intent.source.targetBranch,
31
+ },
32
+ host,
33
+ );
34
+ }
35
+ }
36
+ }
@@ -0,0 +1,27 @@
1
+ import { readBusinessAttempt } from "../attempt/reader.js";
2
+
3
+ export async function notifyPipelineParent(session, host) {
4
+ const pr = await host.request(
5
+ `/repos/${host.repository}/pulls/${session.intent.source.pullRequest}`,
6
+ );
7
+ if (!/^feature\/buildchain-next\/[0-9a-f]{64}$/u.test(pr.head?.ref || ""))
8
+ return;
9
+ const matches = [
10
+ ...String(pr.body || "").matchAll(
11
+ /^Buildchain-Parent-Attempt: (attempt-[0-9a-f]{64})$/gmu,
12
+ ),
13
+ ];
14
+ if (matches.length !== 1) return;
15
+ const attempt = matches[0][1];
16
+ const parent = await host.index.resolve(attempt);
17
+ const state = readBusinessAttempt(parent.snapshot);
18
+ if (
19
+ state.attempt !== attempt ||
20
+ state.status === "complete" ||
21
+ !state.intent.expectedNodes.includes("next-development")
22
+ )
23
+ return;
24
+ // The body is only a bounded wake selector. Current journal source and exact
25
+ // generated PR readback are re-admitted before any parent mutation.
26
+ await host.wake(attempt);
27
+ }
@@ -0,0 +1,21 @@
1
+ // Standard labels verified against GitHub's runner reference on 2026-09-13:
2
+ // https://docs.github.com/en/actions/reference/runners/github-hosted-runners
3
+ const RUNNERS = Object.freeze({
4
+ "linux-x64": "ubuntu-24.04",
5
+ "linux-arm64": "ubuntu-24.04-arm",
6
+ "macos-arm64": "macos-15",
7
+ "macos-x64": "macos-15-intel",
8
+ "windows-x64": "windows-2025",
9
+ });
10
+
11
+ export function pipelinePlatforms(plan) {
12
+ const platforms = [
13
+ ...new Set(plan.products.flatMap((product) => product.platforms)),
14
+ ].sort();
15
+ if (
16
+ !platforms.length ||
17
+ platforms.some((platform) => !Object.hasOwn(RUNNERS, platform))
18
+ )
19
+ throw new Error("Pipeline product platform has no admitted hosted runner");
20
+ return platforms.map((platform) => ({ platform, runner: RUNNERS[platform] }));
21
+ }
@@ -0,0 +1,108 @@
1
+ import { businessAttempt, sourceGeneration } from "../attempt/identity.js";
2
+ import { attemptRecord } from "../attempt/records.js";
3
+ import { canonicalJson } from "../../release/discussion/envelope.js";
4
+ import { STOP_REQUESTED } from "./fence.js";
5
+
6
+ export function pipelineProgress(journal, { intent, writer, runtime }) {
7
+ async function open(source, baseCommit, { successor = false } = {}) {
8
+ const observed = await journal.read();
9
+ const generation = sourceGeneration(intent, source, baseCommit);
10
+ if (observed.attempt && observed.generation === generation.id && !successor)
11
+ return observed;
12
+ if (
13
+ observed.attempt &&
14
+ !["cancelled", "superseded", "failure", "complete"].includes(
15
+ observed.status,
16
+ )
17
+ )
18
+ throw new Error(
19
+ "Prior attempt must reach terminal state before its successor opens",
20
+ );
21
+ const attempt = businessAttempt({
22
+ intent,
23
+ generation,
24
+ predecessor: observed.attempt || "",
25
+ requestKey: `source:${generation.id}`,
26
+ });
27
+ const root = attemptRecord({
28
+ intent,
29
+ attempt,
30
+ generation,
31
+ writer,
32
+ runtime,
33
+ eventKey: `open:${attempt.id}`,
34
+ phase: "attempt",
35
+ state: "running",
36
+ });
37
+ return journal.append(root, observed.head || "");
38
+ }
39
+ async function progress({
40
+ attempt,
41
+ phase,
42
+ state,
43
+ eventKey,
44
+ reason = "",
45
+ materials = [],
46
+ expectedHead,
47
+ }) {
48
+ const observed = await journal.read();
49
+ if (observed.attempt !== attempt)
50
+ throw new Error("Late pipeline result belongs to a superseded attempt");
51
+ if (expectedHead !== undefined && observed.head !== expectedHead)
52
+ throw new Error(
53
+ "Pipeline scheduling head changed; reobserve the active execution",
54
+ );
55
+ const current = observed.history.at(-1);
56
+ if (
57
+ state === "success" &&
58
+ observed.phases[phase]?.payload.reason.startsWith(STOP_REQUESTED)
59
+ )
60
+ throw new Error("A stopped pipeline phase cannot report success");
61
+ const duplicate = current.events.find(
62
+ (event) => event.payload.eventKey === eventKey,
63
+ );
64
+ if (duplicate) {
65
+ if (
66
+ duplicate.node !== phase ||
67
+ duplicate.payload.state !== state ||
68
+ duplicate.payload.reason !== reason ||
69
+ canonicalJson(duplicate.payload.materials) !== canonicalJson(materials)
70
+ )
71
+ throw new Error("Duplicate pipeline event changed its meaning");
72
+ return observed;
73
+ }
74
+ const record = attemptRecord({
75
+ intent,
76
+ attempt: current.identity,
77
+ generation: current.generation,
78
+ writer,
79
+ runtime,
80
+ previous: current.events.at(-1),
81
+ eventKey,
82
+ phase,
83
+ state,
84
+ reason,
85
+ materials,
86
+ });
87
+ return journal.append(record, observed.head);
88
+ }
89
+ async function requestStop(reason) {
90
+ const observed = await journal.read();
91
+ if (
92
+ !observed.attempt ||
93
+ ["complete", "cancelled", "failure", "superseded"].includes(
94
+ observed.status,
95
+ )
96
+ )
97
+ return observed;
98
+ const phase = observed.missing[0];
99
+ return progress({
100
+ attempt: observed.attempt,
101
+ phase,
102
+ state: "waiting",
103
+ eventKey: `stop:${observed.attempt}:${reason}`,
104
+ reason: `${STOP_REQUESTED}${reason}`,
105
+ });
106
+ }
107
+ return { open, progress, requestStop };
108
+ }
@@ -0,0 +1,20 @@
1
+ import { releaseDiscussionStore } from "../../release/discussion/store.js";
2
+ import { discussionTransport } from "../../providers/github/discussions/transport.js";
3
+
4
+ // Copy committed journal records into their Discussion for human navigation.
5
+ // A lagging, unavailable or duplicated projection never supplies write authority.
6
+ export function pipelineProjection(
7
+ graphql,
8
+ transport = discussionTransport(graphql),
9
+ ) {
10
+ const store = releaseDiscussionStore(transport);
11
+ return async (session) => {
12
+ const observed = await session.journal.read();
13
+ const target = await store.initialize({ intent: session.intent });
14
+ const prior = await store.read(target);
15
+ const known = new Set(prior.records.map((record) => record.id));
16
+ for (const record of observed.snapshot.records)
17
+ if (!known.has(record.id)) await store.append(target, record);
18
+ return { url: target.discussion.url, journalHead: observed.head };
19
+ };
20
+ }