@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,141 @@
1
+ import { pipelineEvent } from "./events.js";
2
+ import { selectPipelineSession } from "./selection.js";
3
+ import { beginPipelineBuild } from "./build-control.js";
4
+ import { controlPipelineDelivery } from "./delivery-control.js";
5
+ import {
6
+ githubPipelineEvents,
7
+ githubPipelineQueueExit,
8
+ } from "../../providers/github/pipeline-events.js";
9
+ import { pipelinePlatforms } from "./platforms.js";
10
+ import { recordDigest } from "../../release/discussion/envelope.js";
11
+
12
+ async function groupBuild(event, inputs, host) {
13
+ const queue = await host.queue.getMergeQueueState(event.branch);
14
+ if (
15
+ !queue.enabled ||
16
+ !queue.entries.some(
17
+ (entry) =>
18
+ entry.headSha === event.commit && entry.baseSha === event.baseCommit,
19
+ )
20
+ )
21
+ throw new Error(
22
+ "Merge-group execution is not in the exact protected provider queue",
23
+ );
24
+ const admitted = await host.source.source(
25
+ event.commit,
26
+ inputs["config-path"],
27
+ );
28
+ const context = {
29
+ schema: "buildchain.pipeline-group-build-context/v1",
30
+ source: admitted.identity,
31
+ branch: event.branch,
32
+ baseCommit: event.baseCommit,
33
+ runId: host.runId,
34
+ runAttempt: host.runAttempt,
35
+ platforms: pipelinePlatforms(admitted.plan),
36
+ };
37
+ return { operation: "build", context };
38
+ }
39
+
40
+ export async function controlPipeline(name, payload, inputs, host) {
41
+ const event = pipelineEvent(name, payload, host.repository);
42
+ host.terminalOnly = event.terminalOnly;
43
+ host.eventAction = payload.action || "";
44
+ if (event.kind === "ignore")
45
+ return { operation: "wait", reason: event.reason };
46
+ if (event.kind === "branch") {
47
+ const events = githubPipelineEvents(
48
+ host.request,
49
+ host.provider,
50
+ host.repository,
51
+ );
52
+ const attempts = await events.branch(event.branch, event.commit);
53
+ for (const attempt of attempts) await host.wake(attempt);
54
+ return {
55
+ operation: "wait",
56
+ reason: "existing-channel-intents-woken",
57
+ count: attempts.length,
58
+ };
59
+ }
60
+ if (event.kind === "merge-group") return groupBuild(event, inputs, host);
61
+ const selected = await selectPipelineSession(event, payload, inputs, host);
62
+ if (!selected)
63
+ return { operation: "wait", reason: "no-admitted-pipeline-intent" };
64
+ const { session, admission } = selected;
65
+ const current = session.observed.history.at(-1);
66
+ if (
67
+ current.identity.requestKey.startsWith("recover:") &&
68
+ recordDigest(current.events[0].runtime) !== recordDigest(host.runtime)
69
+ ) {
70
+ await host.wake(current.identity.id);
71
+ return {
72
+ operation: "wait",
73
+ reason: "admitted-recovery-runtime-continuation-required",
74
+ attempt: current.identity.id,
75
+ };
76
+ }
77
+ if (
78
+ ["failure", "cancelled", "superseded", "complete"].includes(
79
+ session.observed.status,
80
+ )
81
+ ) {
82
+ await host.notifyTerminal?.(session);
83
+ await host.project(session);
84
+ return {
85
+ operation: "wait",
86
+ reason: `attempt-${session.observed.status}`,
87
+ attempt: session.observed.attempt,
88
+ };
89
+ }
90
+ if (
91
+ admission.live.source &&
92
+ host.selection.source.sha !== admission.live.source.commit
93
+ ) {
94
+ await host.wake(session.observed.attempt);
95
+ return {
96
+ operation: "wait",
97
+ reason: "source-generation-runtime-selection-required",
98
+ attempt: session.observed.attempt,
99
+ };
100
+ }
101
+ if (session.intent.expectedNodes.includes("warrant"))
102
+ host.queueExit ||= githubPipelineQueueExit(
103
+ host.github.graphql,
104
+ host.repository,
105
+ );
106
+ const operation = session.intent.expectedNodes.includes("warrant")
107
+ ? await controlPipelineDelivery(session, inputs, host)
108
+ : await host.channel(session, admission, inputs);
109
+ if (operation.operation === "build") {
110
+ const context = await beginPipelineBuild(
111
+ session,
112
+ operation.admission || admission,
113
+ host,
114
+ );
115
+ await host.project(session);
116
+ if (!context)
117
+ return { operation: "wait", reason: "existing-build-execution-retained" };
118
+ return { operation: "build", context };
119
+ }
120
+ await host.project(session);
121
+ if (operation.operation === "successor") {
122
+ // Closed or retargeted PRs do not authorize a new attempt on the old route.
123
+ // A later normal event re-admits a lawful source after the old candidate ends.
124
+ await host.wake(session.observed.attempt);
125
+ return {
126
+ operation: "wait",
127
+ reason: operation.reason,
128
+ attempt: session.observed.attempt,
129
+ };
130
+ }
131
+ if (operation.operation === "settle") {
132
+ const settled = await host.settle(
133
+ session,
134
+ operation.fresh,
135
+ operation.delivery,
136
+ );
137
+ await host.project(session);
138
+ return settled;
139
+ }
140
+ return operation;
141
+ }
@@ -0,0 +1,249 @@
1
+ import { recordDigest } from "../../release/discussion/envelope.js";
2
+ import { preparePipelineDelivery } from "./delivery-request.js";
3
+ import { pipelineBuildEvidence } from "./build-evidence.js";
4
+ import {
5
+ observePipelineWorker,
6
+ pipelineDeliveryExecution,
7
+ pipelineDeliveryStore,
8
+ } from "./delivery-observation.js";
9
+ import { reconcilePipeline, pipelineCandidate } from "./reconcile.js";
10
+ import {
11
+ cancelPipelineCandidate,
12
+ recoverPipelineCancellation,
13
+ } from "./cancellation.js";
14
+ import { readBusinessAttempt } from "../attempt/reader.js";
15
+
16
+ export async function observePipelineDelivery(session, inputs, host, delivery) {
17
+ const observed = await session.journal.read();
18
+ const current = { ...observed.history.at(-1), intent: session.intent };
19
+ const admission = await host.source.observeIntent(
20
+ session.intent,
21
+ current.generation,
22
+ inputs,
23
+ );
24
+ admission.live.terminalOnly = host.terminalOnly === true;
25
+ const queue = await delivery.read();
26
+ admission.live.queueExit =
27
+ admission.live.state === "open" &&
28
+ admission.live.source?.commit === current.generation.source.commit &&
29
+ admission.live.targetBranch === session.intent.source.targetBranch
30
+ ? await host.queueExit(current, pipelineCandidate(queue, current))
31
+ : null;
32
+ admission.live.dequeued = Boolean(admission.live.queueExit);
33
+ const worker = await observePipelineWorker(session, current, queue, host);
34
+ const input = { current, live: admission.live, queue, worker, evidence: {} };
35
+ return {
36
+ ...input,
37
+ admission,
38
+ observed,
39
+ attempt: observed.attempt,
40
+ phase: observed.missing[0],
41
+ decision: reconcilePipeline(input),
42
+ };
43
+ }
44
+
45
+ async function cleanup(fresh, session, inputs, host, delivery) {
46
+ const ports = {
47
+ observe: () => observePipelineDelivery(session, inputs, host, delivery),
48
+ progress: session.progress,
49
+ service: delivery.service,
50
+ materials: host.materialStore(session),
51
+ wake: async (successor) => {
52
+ const retained = await host.provider.lookup(
53
+ host.repository,
54
+ successor.pullRequestNumber,
55
+ session.intent.source.targetBranch,
56
+ );
57
+ if (retained)
58
+ await host.wake(readBusinessAttempt(retained.snapshot).attempt);
59
+ },
60
+ };
61
+ if (fresh.decision.operation === "record-cancellation")
62
+ return recoverPipelineCancellation(fresh, ports);
63
+ if (
64
+ ["stop-worker", "cancel-queued", "settle-cancelled"].includes(
65
+ fresh.decision.operation,
66
+ )
67
+ )
68
+ return cancelPipelineCandidate(fresh, ports);
69
+ return null;
70
+ }
71
+
72
+ async function retainDeliveryRequest(fresh, session, build, host) {
73
+ const scheduling = await session.journal.read();
74
+ const schedulingCurrent = {
75
+ ...scheduling.history.at(-1),
76
+ intent: session.intent,
77
+ };
78
+ // The provider execution owns the delivery slot before it acquires a
79
+ // Warrant. A later wake must not replace its retained coordinates while its
80
+ // queued or running job is still entitled to perform final admission.
81
+ const executionOwner = await pipelineDeliveryExecution(
82
+ session,
83
+ schedulingCurrent,
84
+ host,
85
+ );
86
+ if (executionOwner) {
87
+ const { run } = await host.runs.read(
88
+ executionOwner.runId,
89
+ executionOwner.runAttempt,
90
+ );
91
+ if (
92
+ run.id !== executionOwner.runId ||
93
+ run.run_attempt !== executionOwner.runAttempt
94
+ )
95
+ throw new Error("Retained delivery execution provider identity drift");
96
+ if (run.status !== "completed")
97
+ return {
98
+ operation: "wait",
99
+ reason: "admitted-delivery-execution-running",
100
+ };
101
+ }
102
+ const active = await observePipelineWorker(
103
+ session,
104
+ schedulingCurrent,
105
+ fresh.queue,
106
+ host,
107
+ );
108
+ if (active?.status === "in_progress")
109
+ return { operation: "wait", reason: "native-worker-running" };
110
+ const request = await (host.prepareDelivery || preparePipelineDelivery)({
111
+ current: fresh.current,
112
+ run: build.run,
113
+ build: build.readback,
114
+ runtimeSha: host.runtime.sha,
115
+ token: host.token,
116
+ plan: fresh.admission.plan,
117
+ });
118
+ const execution = {
119
+ schema: "buildchain.pipeline-delivery-execution/v1",
120
+ attempt: fresh.attempt,
121
+ generation: fresh.current.generation.id,
122
+ runId: host.runId,
123
+ runAttempt: host.runAttempt,
124
+ request,
125
+ };
126
+ const reference = await host
127
+ .materialStore(session)
128
+ .retain(`delivery/execution-${host.runId}-${host.runAttempt}`, execution);
129
+ await session.progress.progress({
130
+ attempt: fresh.attempt,
131
+ phase: "warrant",
132
+ state: "running",
133
+ eventKey: `delivery:${host.runId}:${host.runAttempt}`,
134
+ materials: [reference],
135
+ expectedHead: scheduling.head,
136
+ });
137
+ return { operation: "deliver", request, attempt: fresh.attempt };
138
+ }
139
+
140
+ export async function controlPipelineDelivery(session, inputs, host) {
141
+ const delivery = host.delivery
142
+ ? host.delivery(session)
143
+ : pipelineDeliveryStore(session, host);
144
+ const fresh = await observePipelineDelivery(session, inputs, host, delivery);
145
+ const candidate = pipelineCandidate(fresh.queue, fresh.current);
146
+ if (["cancelled", "dequeued"].includes(candidate?.status))
147
+ fresh.decision = {
148
+ operation: "record-cancellation",
149
+ candidateId: candidate.candidateId,
150
+ };
151
+ if (candidate?.status === "terminal-failure") {
152
+ const material = await host
153
+ .materialStore(session)
154
+ .retain(
155
+ `failure/${fresh.queue.stateRoot.slice(7)}`,
156
+ fresh.queue,
157
+ "provider-readback",
158
+ );
159
+ await session.progress.progress({
160
+ attempt: fresh.attempt,
161
+ phase: fresh.phase,
162
+ state: "failure",
163
+ eventKey: `failure:${fresh.queue.stateRoot}`,
164
+ reason: "native-execution-failed",
165
+ materials: [material],
166
+ });
167
+ return { operation: "wait", reason: "native-execution-failed" };
168
+ }
169
+ const result = await cleanup(fresh, session, inputs, host, delivery);
170
+ if (result) {
171
+ if (result.status === "cancelled") await host.wake(fresh.attempt);
172
+ return { operation: "wait", reason: result.reason || result.status };
173
+ }
174
+ if (fresh.decision.operation === "supersede") {
175
+ if (
176
+ !["cancelled", "failure", "superseded", "complete"].includes(
177
+ fresh.observed.status,
178
+ )
179
+ ) {
180
+ try {
181
+ await session.progress.progress({
182
+ attempt: fresh.attempt,
183
+ phase: fresh.phase,
184
+ state: "superseded",
185
+ eventKey: `supersede:${recordDigest(fresh.live)}`,
186
+ reason: fresh.decision.reason,
187
+ });
188
+ } catch (error) {
189
+ // Concurrent notifications can finish the same attempt after readback.
190
+ // Its immutable terminal result is sufficient; never rewrite that phase.
191
+ const latest = await session.journal.read();
192
+ if (
193
+ latest.attempt !== fresh.attempt ||
194
+ !["cancelled", "failure", "superseded", "complete"].includes(
195
+ latest.status,
196
+ )
197
+ )
198
+ throw error;
199
+ }
200
+ }
201
+ return { operation: "successor", reason: fresh.decision.reason };
202
+ }
203
+ if (fresh.live.merged) return { operation: "settle", fresh, delivery };
204
+ if (fresh.live.state !== "open" || fresh.live.terminalOnly)
205
+ return { operation: "wait", reason: "no-execution-admission" };
206
+ const build = await pipelineBuildEvidence(session, host);
207
+ if (!build) return { operation: "build", admission: fresh.admission };
208
+ if (build.run.status !== "completed")
209
+ return { operation: "wait", reason: "source-workflow-still-running" };
210
+ const policy = await host.policy.observe(
211
+ fresh.current,
212
+ fresh.admission.protectedPlan.review,
213
+ );
214
+ if (
215
+ !policy.review ||
216
+ !policy.checksPassing ||
217
+ !fresh.live.ready ||
218
+ fresh.live.draft
219
+ )
220
+ return { operation: "wait", reason: "protected-source-gates-pending" };
221
+ if (!fresh.current.phases.review) {
222
+ const material = await host
223
+ .materialStore(session)
224
+ .retain(`review/${policy.root.slice(7)}`, policy, "provider-readback");
225
+ await session.progress.progress({
226
+ attempt: fresh.attempt,
227
+ phase: "review",
228
+ state: "success",
229
+ eventKey: `review:${policy.root}`,
230
+ materials: [material],
231
+ });
232
+ }
233
+ const decision = reconcilePipeline({
234
+ ...fresh,
235
+ evidence: {
236
+ build,
237
+ review: policy,
238
+ checks: policy,
239
+ qualification: fresh.queue.activeWarrant?.phase === "qualified",
240
+ },
241
+ });
242
+ if (fresh.worker?.status === "in_progress")
243
+ return { operation: "wait", reason: "native-worker-running" };
244
+ if (
245
+ ["reserve", "select", "qualify-native", "land"].includes(decision.operation)
246
+ )
247
+ return retainDeliveryRequest(fresh, session, build, host);
248
+ return decision;
249
+ }
@@ -0,0 +1,124 @@
1
+ import { GitHubDevDeliveryStore } from "../../providers/dev-delivery/store.js";
2
+ import { defaultDevDeliveryStateRef } from "../../dev-delivery/warrant/values.js";
3
+ import { createDeliveryWarrantService } from "../../dev-delivery/warrant/service.js";
4
+ import { PIPELINE_WORKER } from "../../providers/github/pipeline-worker.js";
5
+ import { pipelineCandidate } from "./reconcile.js";
6
+
7
+ function workerJob(jobs, name) {
8
+ const selected = jobs.filter(
9
+ (job) => job.name === name || job.name.endsWith(` / ${name}`),
10
+ );
11
+ if (selected.length > 1)
12
+ throw new Error("Native worker job identity is ambiguous");
13
+ return selected[0];
14
+ }
15
+
16
+ export async function pipelineDeliveryExecution(session, current, host) {
17
+ const phase = [...current.events]
18
+ .reverse()
19
+ .find((event) =>
20
+ event.payload.materials.some((material) =>
21
+ material.id.startsWith("delivery/execution-"),
22
+ ),
23
+ );
24
+ const references =
25
+ phase?.payload.materials.filter((material) =>
26
+ material.id.startsWith("delivery/execution-"),
27
+ ) || [];
28
+ if (!references.length) return null;
29
+ if (references.length !== 1)
30
+ throw new Error("Native worker requires one admitted execution");
31
+ const execution = await host.materialStore(session).read(references[0]);
32
+ if (
33
+ execution.attempt !== current.identity.id ||
34
+ execution.generation !== current.generation.id
35
+ )
36
+ throw new Error("Native worker execution belongs to another attempt");
37
+ return execution;
38
+ }
39
+
40
+ export async function observePipelineWorker(session, current, queue, host) {
41
+ const warrant = queue.activeWarrant;
42
+ const candidate = pipelineCandidate(queue, current);
43
+ if (
44
+ !warrant ||
45
+ candidate?.candidateId !== warrant.candidateId ||
46
+ warrant.sourceHead !== current.generation.source.commit ||
47
+ warrant.pullRequestNumber !== current.intent.source.pullRequest
48
+ )
49
+ return null;
50
+ const execution = await pipelineDeliveryExecution(session, current, host);
51
+ if (!execution) return null;
52
+ const { run, jobs } = await host.runs.read(
53
+ execution.runId,
54
+ execution.runAttempt,
55
+ );
56
+ if (run.status !== "completed") {
57
+ const terminalJobs = [
58
+ "Credentialless native execution",
59
+ "Credentialless native evidence seal",
60
+ ].map((name) => workerJob(jobs, name));
61
+ if (terminalJobs.some((job) => !job || job.status !== "completed"))
62
+ return {
63
+ status: "in_progress",
64
+ reason: "admitted-execution-not-terminal",
65
+ run,
66
+ };
67
+ }
68
+ const entries = (run.referenced_workflows || []).filter((entry) =>
69
+ entry.path?.startsWith(
70
+ "kungfu-systems/buildchain/.github/workflows/public-ops-dev-auto-merge.yml@",
71
+ ),
72
+ );
73
+ if (entries.length !== 1) return null;
74
+ const native = workerJob(jobs, "Credentialless native execution"),
75
+ seal = workerJob(jobs, "Credentialless native evidence seal");
76
+ if (!native || !seal) return null;
77
+ if (
78
+ run.status !== "completed" &&
79
+ run.id !== host.runId &&
80
+ native.status === "completed" &&
81
+ seal.status === "completed"
82
+ )
83
+ await host.runs.completed(execution.runId, execution.runAttempt);
84
+ return host.workers.observe(
85
+ {
86
+ schema: PIPELINE_WORKER,
87
+ repository: host.repository,
88
+ attempt: current.identity.id,
89
+ generation: current.generation.id,
90
+ sourceHead: warrant.sourceHead,
91
+ candidateId: warrant.candidateId,
92
+ fencingToken: warrant.fencingToken,
93
+ leaseGeneration: warrant.generation,
94
+ runId: execution.runId,
95
+ runAttempt: execution.runAttempt,
96
+ nativeJobId: native.id,
97
+ sealJobId: seal.id,
98
+ },
99
+ current,
100
+ warrant,
101
+ );
102
+ }
103
+
104
+ export function pipelineDeliveryStore(session, host) {
105
+ const branch = session.intent.source.targetBranch;
106
+ const store = new GitHubDevDeliveryStore({
107
+ repository: host.repository,
108
+ token: host.token,
109
+ });
110
+ return {
111
+ read: async () =>
112
+ (
113
+ await store.read({
114
+ stateRef: defaultDevDeliveryStateRef(branch),
115
+ protectedBase: branch,
116
+ now: new Date().toISOString(),
117
+ })
118
+ ).queue,
119
+ service: createDeliveryWarrantService(
120
+ { repository: host.repository, token: host.token, branch },
121
+ store,
122
+ ),
123
+ };
124
+ }