@kungfu-tech/buildchain 4.1.3-alpha.0 → 4.1.3-alpha.2

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 (217) hide show
  1. package/architecture/action-taxonomy.json +16 -0
  2. package/architecture/agent-change-map.md +245 -4
  3. package/architecture/ci-lane-change-budget.json +289 -0
  4. package/architecture/decisions/0005-minimal-consumer-contract.md +122 -0
  5. package/architecture/decisions/0006-business-attempt-journal.md +89 -0
  6. package/architecture/decisions/0007-hosted-pipeline-controller.md +106 -0
  7. package/architecture/decisions/0008-pipeline-product-publication.md +99 -0
  8. package/architecture/internal-capabilities.json +340 -4
  9. package/architecture/maintainability-debt.json +3 -1
  10. package/architecture/maintainability-policy.json +8 -8
  11. package/architecture/minimal-consumer-migration.json +7558 -0
  12. package/architecture/release-topology.json +248 -11
  13. package/architecture/universal-workflow-bootstrap.json +10 -0
  14. package/architecture/universal-workflow-capability-policy.json +6 -6
  15. package/architecture/workflow-taxonomy.json +55 -0
  16. package/contracts/release-discussion-v1.schema.json +3 -0
  17. package/dist/readers/business-attempt.cjs +805 -0
  18. package/dist/readers/release-discussion.cjs +4 -0
  19. package/dist/site/buildchain-contract.json +7 -5
  20. package/dist/site/buildchain-site.json +25 -15
  21. package/dist/site/capability-registry.json +3 -3
  22. package/dist/site/kfd-claims.json +311 -10
  23. package/dist/site/kfd-upstream-aggregate.json +1 -1
  24. package/dist/site/manual-registry.json +1 -1
  25. package/dist/site/node-api-registry.json +9 -9
  26. package/dist/site/page-registry.json +20 -10
  27. package/dist/site/public-surface-audit.json +201 -7
  28. package/dist/site/publication-authority-registry.json +121 -1
  29. package/dist/site/publication-registry.json +4 -4
  30. package/dist/site/site-manifest.json +5 -5
  31. package/dist/site/workflow-registry.json +416 -7
  32. package/docs/node-api-reference.md +12 -12
  33. package/docs/release-discussions.md +44 -5
  34. package/docs/runtime-entry.md +45 -5
  35. package/docs/workflow-catalog.md +5 -0
  36. package/package.json +4 -3
  37. package/packages/core/consumer/contract/entries.js +85 -0
  38. package/packages/core/consumer/contract/examples.js +96 -0
  39. package/packages/core/consumer/contract/identity.js +65 -0
  40. package/packages/core/consumer/contract/inspection.js +45 -0
  41. package/packages/core/consumer/contract/plan.js +99 -0
  42. package/packages/core/consumer/contract/products.js +101 -0
  43. package/packages/core/consumer/contract/shape.js +46 -0
  44. package/packages/core/dev-delivery/candidate/admission.js +9 -5
  45. package/packages/core/dev-delivery/candidate/reservation.js +11 -0
  46. package/packages/core/dev-delivery/candidate/source-paths.js +12 -4
  47. package/packages/core/dev-delivery/native/actions.js +7 -1
  48. package/packages/core/dev-delivery/native/heartbeat-action.js +16 -0
  49. package/packages/core/dev-delivery/native/heartbeat.js +6 -4
  50. package/packages/core/dev-delivery/queue/landing-action.js +5 -0
  51. package/packages/core/governance/buildchain-publication-authority.js +5 -0
  52. package/packages/core/paper/operations/bootstrap.js +2 -0
  53. package/packages/core/paper/paper-npm-bootstrap.js +2 -2
  54. package/packages/core/providers/github/attempt-index.js +60 -0
  55. package/packages/core/providers/github/attempt-journal.js +154 -0
  56. package/packages/core/providers/github/discussions/materials.js +23 -7
  57. package/packages/core/providers/github/discussions/transport.js +16 -5
  58. package/packages/core/providers/github/pipeline-checkout.js +49 -0
  59. package/packages/core/providers/github/pipeline-events.js +52 -0
  60. package/packages/core/providers/github/pipeline-integration.js +95 -0
  61. package/packages/core/providers/github/pipeline-policy.js +213 -0
  62. package/packages/core/providers/github/pipeline-product-payload.js +50 -0
  63. package/packages/core/providers/github/pipeline-product-release.js +191 -0
  64. package/packages/core/providers/github/pipeline-publication-artifacts.js +153 -0
  65. package/packages/core/providers/github/pipeline-run-entry.js +86 -0
  66. package/packages/core/providers/github/pipeline-runs.js +132 -0
  67. package/packages/core/providers/github/pipeline-source.js +178 -0
  68. package/packages/core/providers/github/pipeline-version.js +201 -0
  69. package/packages/core/providers/github/pipeline-worker.js +108 -0
  70. package/packages/core/publication/candidate/registry-hydration.js +2 -2
  71. package/packages/core/publication/npm/pack-preview.js +2 -2
  72. package/packages/core/publication/npm/pack-result.js +31 -0
  73. package/packages/core/publication/npm/package.js +3 -6
  74. package/packages/core/publication/npm/pipeline-channel.js +87 -0
  75. package/packages/core/publication/npm/pipeline-provider.js +109 -0
  76. package/packages/core/publication/npm/registry.js +12 -9
  77. package/packages/core/publication/pipeline/actions.js +100 -0
  78. package/packages/core/publication/pipeline/apply.js +182 -0
  79. package/packages/core/publication/pipeline/build-segments.js +107 -0
  80. package/packages/core/publication/pipeline/capsules.js +89 -0
  81. package/packages/core/publication/pipeline/context.js +39 -0
  82. package/packages/core/publication/pipeline/development-anchor.js +91 -0
  83. package/packages/core/publication/pipeline/development-current.js +63 -0
  84. package/packages/core/publication/pipeline/development-pr.js +85 -0
  85. package/packages/core/publication/pipeline/development-proof.js +76 -0
  86. package/packages/core/publication/pipeline/development-transition.js +112 -0
  87. package/packages/core/publication/pipeline/distribution.js +124 -0
  88. package/packages/core/publication/pipeline/documents.js +156 -0
  89. package/packages/core/publication/pipeline/effects.js +162 -0
  90. package/packages/core/publication/pipeline/files.js +66 -0
  91. package/packages/core/publication/pipeline/imported-materials.js +32 -0
  92. package/packages/core/publication/pipeline/journal.js +110 -0
  93. package/packages/core/publication/pipeline/next-development.js +116 -0
  94. package/packages/core/publication/pipeline/pack.js +151 -0
  95. package/packages/core/publication/pipeline/package-policy.js +22 -0
  96. package/packages/core/publication/pipeline/plan.js +114 -0
  97. package/packages/core/publication/pipeline/prepare.js +151 -0
  98. package/packages/core/publication/pipeline/qualification.js +170 -0
  99. package/packages/core/publication/pipeline/qualify.js +104 -0
  100. package/packages/core/publication/pipeline/recovery-admission.js +122 -0
  101. package/packages/core/publication/pipeline/recovery-build-download.js +80 -0
  102. package/packages/core/publication/pipeline/recovery-build-plan.js +122 -0
  103. package/packages/core/publication/pipeline/recovery-build-readback.js +68 -0
  104. package/packages/core/publication/pipeline/recovery-capsules.js +54 -0
  105. package/packages/core/publication/pipeline/recovery-import.js +80 -0
  106. package/packages/core/publication/pipeline/recovery-inspection.js +218 -0
  107. package/packages/core/publication/pipeline/recovery-materials.js +104 -0
  108. package/packages/core/publication/pipeline/recovery-plan.js +46 -0
  109. package/packages/core/publication/pipeline/recovery-prepare.js +73 -0
  110. package/packages/core/publication/pipeline/recovery-qualification.js +128 -0
  111. package/packages/core/publication/pipeline/recovery-readback.js +56 -0
  112. package/packages/core/publication/pipeline/recovery-signing.js +58 -0
  113. package/packages/core/publication/pipeline/sealed-products.js +79 -0
  114. package/packages/core/publication/pipeline/settle.js +43 -0
  115. package/packages/core/publication/pipeline/signing.js +140 -0
  116. package/packages/core/publication/pipeline/source-plan.js +83 -0
  117. package/packages/core/publication/pipeline/version.js +81 -0
  118. package/packages/core/publication/publication-reproducibility.js +2 -2
  119. package/packages/core/release/discussion/binary.js +11 -0
  120. package/packages/core/release/discussion/checkpoints.js +45 -4
  121. package/packages/core/release/discussion/envelope.js +1 -0
  122. package/packages/core/release/discussion/evidence.js +51 -0
  123. package/packages/core/release/discussion/presentation.js +92 -0
  124. package/packages/core/release/discussion/publication.js +3 -1
  125. package/packages/core/release/discussion/qualification.js +1 -0
  126. package/packages/core/release/discussion/reader.js +9 -0
  127. package/packages/core/release/discussion/session.js +2 -0
  128. package/packages/core/release/discussion/store.js +28 -31
  129. package/packages/core/release/discussion/threads.js +83 -0
  130. package/packages/core/release/promote-candidate/product-provider-adapters.js +4 -3
  131. package/packages/core/release/promote-ref/internal/npm-existing-evidence.js +2 -1
  132. package/packages/core/release/promote-ref/internal/publish-command.js +16 -5
  133. package/packages/core/runtime/buildchain-domain.wasm +0 -0
  134. package/packages/core/runtime/domain-wasm-artifact.js +2 -2
  135. package/packages/core/runtime/entry/actions.js +27 -25
  136. package/packages/core/runtime/entry/attempt.js +25 -0
  137. package/packages/core/runtime/entry/selection.js +14 -0
  138. package/packages/core/runtime/entry/source.js +62 -0
  139. package/packages/core/workflow/attempt/identity.js +165 -0
  140. package/packages/core/workflow/attempt/journal.js +87 -0
  141. package/packages/core/workflow/attempt/materials.js +72 -0
  142. package/packages/core/workflow/attempt/reader-entry.js +12 -0
  143. package/packages/core/workflow/attempt/reader.js +149 -0
  144. package/packages/core/workflow/attempt/records.js +153 -0
  145. package/packages/core/workflow/attempt/store.js +75 -0
  146. package/packages/core/workflow/commands/pipeline-build.mjs +23 -0
  147. package/packages/core/workflow/pipeline/action-output.js +11 -0
  148. package/packages/core/workflow/pipeline/actions.js +62 -0
  149. package/packages/core/workflow/pipeline/build-control.js +140 -0
  150. package/packages/core/workflow/pipeline/build-evidence.js +92 -0
  151. package/packages/core/workflow/pipeline/build-qualification.js +145 -0
  152. package/packages/core/workflow/pipeline/build-result.js +69 -0
  153. package/packages/core/workflow/pipeline/build.js +95 -0
  154. package/packages/core/workflow/pipeline/cancellation.js +169 -0
  155. package/packages/core/workflow/pipeline/channel-control.js +127 -0
  156. package/packages/core/workflow/pipeline/controller.js +133 -0
  157. package/packages/core/workflow/pipeline/delivery-control.js +204 -0
  158. package/packages/core/workflow/pipeline/delivery-observation.js +118 -0
  159. package/packages/core/workflow/pipeline/delivery-request.js +198 -0
  160. package/packages/core/workflow/pipeline/events.js +84 -0
  161. package/packages/core/workflow/pipeline/fence.js +42 -0
  162. package/packages/core/workflow/pipeline/group-control.js +56 -0
  163. package/packages/core/workflow/pipeline/guard-build.js +37 -0
  164. package/packages/core/workflow/pipeline/guard.js +156 -0
  165. package/packages/core/workflow/pipeline/host.js +114 -0
  166. package/packages/core/workflow/pipeline/materials.js +58 -0
  167. package/packages/core/workflow/pipeline/notifications.js +36 -0
  168. package/packages/core/workflow/pipeline/parent-notification.js +27 -0
  169. package/packages/core/workflow/pipeline/platforms.js +21 -0
  170. package/packages/core/workflow/pipeline/progress.js +108 -0
  171. package/packages/core/workflow/pipeline/projection.js +20 -0
  172. package/packages/core/workflow/pipeline/reconcile.js +194 -0
  173. package/packages/core/workflow/pipeline/recovery-action.js +30 -0
  174. package/packages/core/workflow/pipeline/recovery-admission.js +122 -0
  175. package/packages/core/workflow/pipeline/recovery-build-control.js +95 -0
  176. package/packages/core/workflow/pipeline/recovery-build-evidence.js +71 -0
  177. package/packages/core/workflow/pipeline/recovery-build.js +121 -0
  178. package/packages/core/workflow/pipeline/recovery-controller.js +91 -0
  179. package/packages/core/workflow/pipeline/recovery-integration.js +66 -0
  180. package/packages/core/workflow/pipeline/recovery-merge-proof.js +90 -0
  181. package/packages/core/workflow/pipeline/recovery-ownership-settlement.js +150 -0
  182. package/packages/core/workflow/pipeline/recovery-ownership.js +91 -0
  183. package/packages/core/workflow/pipeline/recovery-plan.js +126 -0
  184. package/packages/core/workflow/pipeline/recovery-runtime.js +68 -0
  185. package/packages/core/workflow/pipeline/recovery-session.js +129 -0
  186. package/packages/core/workflow/pipeline/recovery-transition.js +135 -0
  187. package/packages/core/workflow/pipeline/recovery-unrecorded-build.js +74 -0
  188. package/packages/core/workflow/pipeline/runtime-source.js +111 -0
  189. package/packages/core/workflow/pipeline/selection.js +106 -0
  190. package/packages/core/workflow/pipeline/session.js +72 -0
  191. package/packages/core/workflow/pipeline/settlement.js +137 -0
  192. package/packages/core/workflow/pipeline/wake.js +38 -0
  193. package/packages/core/workflow/pipeline/web-status.js +108 -0
  194. package/scripts/build-release-discussion-reader.mjs +11 -7
  195. package/scripts/check-pipeline-publication-topology.mjs +36 -0
  196. package/scripts/check-release-topology.mjs +6 -1
  197. package/scripts/generate-minimal-consumer-contract.mjs +155 -0
  198. package/scripts/verify-golden-path.mjs +5 -1
  199. package/templates/minimal-consumer/binary/.buildchain/buildchain.toml +45 -0
  200. package/templates/minimal-consumer/binary/.github/workflows/buildchain-recover.yml +30 -0
  201. package/templates/minimal-consumer/binary/.github/workflows/buildchain.yml +28 -0
  202. package/templates/minimal-consumer/binary/package.json +6 -0
  203. package/templates/minimal-consumer/binary/src/build.mjs +5 -0
  204. package/templates/minimal-consumer/binary/src/hello.c +2 -0
  205. package/templates/minimal-consumer/binary/src/verify.mjs +3 -0
  206. package/templates/minimal-consumer/npm/.buildchain/buildchain.toml +46 -0
  207. package/templates/minimal-consumer/npm/.github/workflows/buildchain-recover.yml +30 -0
  208. package/templates/minimal-consumer/npm/.github/workflows/buildchain.yml +28 -0
  209. package/templates/minimal-consumer/npm/package.json +6 -0
  210. package/templates/minimal-consumer/npm/src/build.mjs +5 -0
  211. package/templates/minimal-consumer/npm/src/verify.mjs +3 -0
  212. package/templates/minimal-consumer/paper/.buildchain/buildchain.toml +45 -0
  213. package/templates/minimal-consumer/paper/.github/workflows/buildchain-recover.yml +30 -0
  214. package/templates/minimal-consumer/paper/.github/workflows/buildchain.yml +28 -0
  215. package/templates/minimal-consumer/paper/package.json +6 -0
  216. package/templates/minimal-consumer/paper/src/build.mjs +12 -0
  217. package/templates/minimal-consumer/paper/src/verify.mjs +3 -0
@@ -0,0 +1,204 @@
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
+ pipelineDeliveryStore,
7
+ } from "./delivery-observation.js";
8
+ import { reconcilePipeline, pipelineCandidate } from "./reconcile.js";
9
+ import {
10
+ cancelPipelineCandidate,
11
+ recoverPipelineCancellation,
12
+ } from "./cancellation.js";
13
+ import { readBusinessAttempt } from "../attempt/reader.js";
14
+
15
+ export async function observePipelineDelivery(session, inputs, host, delivery) {
16
+ const observed = await session.journal.read();
17
+ const current = { ...observed.history.at(-1), intent: session.intent };
18
+ const admission = await host.source.observeIntent(
19
+ session.intent,
20
+ current.generation,
21
+ inputs,
22
+ );
23
+ admission.live.terminalOnly = host.terminalOnly === true;
24
+ admission.live.dequeued = host.eventAction === "dequeued";
25
+ const queue = await delivery.read();
26
+ const worker = await observePipelineWorker(session, current, queue, host);
27
+ const input = { current, live: admission.live, queue, worker, evidence: {} };
28
+ return {
29
+ ...input,
30
+ admission,
31
+ observed,
32
+ attempt: observed.attempt,
33
+ phase: observed.missing[0],
34
+ decision: reconcilePipeline(input),
35
+ };
36
+ }
37
+
38
+ async function cleanup(fresh, session, inputs, host, delivery) {
39
+ const ports = {
40
+ observe: () => observePipelineDelivery(session, inputs, host, delivery),
41
+ progress: session.progress,
42
+ service: delivery.service,
43
+ materials: host.materialStore(session),
44
+ wake: async (successor) => {
45
+ const retained = await host.provider.lookup(
46
+ host.repository,
47
+ successor.pullRequestNumber,
48
+ session.intent.source.targetBranch,
49
+ );
50
+ if (retained)
51
+ await host.wake(readBusinessAttempt(retained.snapshot).attempt);
52
+ },
53
+ };
54
+ if (fresh.decision.operation === "record-cancellation")
55
+ return recoverPipelineCancellation(fresh, ports);
56
+ if (
57
+ ["stop-worker", "cancel-queued", "settle-cancelled"].includes(
58
+ fresh.decision.operation,
59
+ )
60
+ )
61
+ return cancelPipelineCandidate(fresh, ports);
62
+ return null;
63
+ }
64
+
65
+ async function retainDeliveryRequest(fresh, session, build, host) {
66
+ const scheduling = await session.journal.read();
67
+ const schedulingCurrent = {
68
+ ...scheduling.history.at(-1),
69
+ intent: session.intent,
70
+ };
71
+ const active = await observePipelineWorker(
72
+ session,
73
+ schedulingCurrent,
74
+ fresh.queue,
75
+ host,
76
+ );
77
+ if (active?.status === "in_progress")
78
+ return { operation: "wait", reason: "native-worker-running" };
79
+ const request = await (host.prepareDelivery || preparePipelineDelivery)({
80
+ current: fresh.current,
81
+ run: build.run,
82
+ build: build.readback,
83
+ runtimeSha: host.runtime.sha,
84
+ token: host.token,
85
+ plan: fresh.admission.plan,
86
+ });
87
+ const execution = {
88
+ schema: "buildchain.pipeline-delivery-execution/v1",
89
+ attempt: fresh.attempt,
90
+ generation: fresh.current.generation.id,
91
+ runId: host.runId,
92
+ runAttempt: host.runAttempt,
93
+ request,
94
+ };
95
+ const reference = await host
96
+ .materialStore(session)
97
+ .retain(`delivery/execution-${host.runId}-${host.runAttempt}`, execution);
98
+ await session.progress.progress({
99
+ attempt: fresh.attempt,
100
+ phase: "warrant",
101
+ state: "running",
102
+ eventKey: `delivery:${host.runId}:${host.runAttempt}`,
103
+ materials: [reference],
104
+ expectedHead: scheduling.head,
105
+ });
106
+ return { operation: "deliver", request, attempt: fresh.attempt };
107
+ }
108
+
109
+ export async function controlPipelineDelivery(session, inputs, host) {
110
+ const delivery = host.delivery
111
+ ? host.delivery(session)
112
+ : pipelineDeliveryStore(session, host);
113
+ const fresh = await observePipelineDelivery(session, inputs, host, delivery);
114
+ const candidate = pipelineCandidate(fresh.queue, fresh.current);
115
+ if (["cancelled", "dequeued"].includes(candidate?.status))
116
+ fresh.decision = {
117
+ operation: "record-cancellation",
118
+ candidateId: candidate.candidateId,
119
+ };
120
+ if (candidate?.status === "terminal-failure") {
121
+ const material = await host
122
+ .materialStore(session)
123
+ .retain(
124
+ `failure/${fresh.queue.stateRoot.slice(7)}`,
125
+ fresh.queue,
126
+ "provider-readback",
127
+ );
128
+ await session.progress.progress({
129
+ attempt: fresh.attempt,
130
+ phase: fresh.phase,
131
+ state: "failure",
132
+ eventKey: `failure:${fresh.queue.stateRoot}`,
133
+ reason: "native-execution-failed",
134
+ materials: [material],
135
+ });
136
+ return { operation: "wait", reason: "native-execution-failed" };
137
+ }
138
+ const result = await cleanup(fresh, session, inputs, host, delivery);
139
+ if (result) {
140
+ if (result.status === "cancelled") await host.wake(fresh.attempt);
141
+ return { operation: "wait", reason: result.reason || result.status };
142
+ }
143
+ if (fresh.decision.operation === "supersede") {
144
+ if (
145
+ !["cancelled", "failure", "superseded", "complete"].includes(
146
+ fresh.observed.status,
147
+ )
148
+ )
149
+ await session.progress.progress({
150
+ attempt: fresh.attempt,
151
+ phase: fresh.phase,
152
+ state: "superseded",
153
+ eventKey: `supersede:${recordDigest(fresh.live)}`,
154
+ reason: fresh.decision.reason,
155
+ });
156
+ return { operation: "successor", reason: fresh.decision.reason };
157
+ }
158
+ if (fresh.live.merged) return { operation: "settle", fresh, delivery };
159
+ if (fresh.live.state !== "open" || fresh.live.terminalOnly)
160
+ return { operation: "wait", reason: "no-execution-admission" };
161
+ const build = await pipelineBuildEvidence(session, host);
162
+ if (!build) return { operation: "build", admission: fresh.admission };
163
+ if (build.run.status !== "completed")
164
+ return { operation: "wait", reason: "source-workflow-still-running" };
165
+ const policy = await host.policy.observe(
166
+ fresh.current,
167
+ fresh.admission.protectedPlan.review,
168
+ );
169
+ if (
170
+ !policy.review ||
171
+ !policy.checksPassing ||
172
+ !fresh.live.ready ||
173
+ fresh.live.draft
174
+ )
175
+ return { operation: "wait", reason: "protected-source-gates-pending" };
176
+ if (!fresh.current.phases.review) {
177
+ const material = await host
178
+ .materialStore(session)
179
+ .retain(`review/${policy.root.slice(7)}`, policy, "provider-readback");
180
+ await session.progress.progress({
181
+ attempt: fresh.attempt,
182
+ phase: "review",
183
+ state: "success",
184
+ eventKey: `review:${policy.root}`,
185
+ materials: [material],
186
+ });
187
+ }
188
+ const decision = reconcilePipeline({
189
+ ...fresh,
190
+ evidence: {
191
+ build,
192
+ review: policy,
193
+ checks: policy,
194
+ qualification: fresh.queue.activeWarrant?.phase === "qualified",
195
+ },
196
+ });
197
+ if (fresh.worker?.status === "in_progress")
198
+ return { operation: "wait", reason: "native-worker-running" };
199
+ if (
200
+ ["reserve", "select", "qualify-native", "land"].includes(decision.operation)
201
+ )
202
+ return retainDeliveryRequest(fresh, session, build, host);
203
+ return decision;
204
+ }
@@ -0,0 +1,118 @@
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 observePipelineWorker(session, current, queue, host) {
17
+ const warrant = queue.activeWarrant;
18
+ const candidate = pipelineCandidate(queue, current);
19
+ if (
20
+ !warrant ||
21
+ candidate?.candidateId !== warrant.candidateId ||
22
+ warrant.sourceHead !== current.generation.source.commit ||
23
+ warrant.pullRequestNumber !== current.intent.source.pullRequest
24
+ )
25
+ return null;
26
+ const phase = [...current.events]
27
+ .reverse()
28
+ .find((event) =>
29
+ event.payload.materials.some((material) =>
30
+ material.id.startsWith("delivery/execution-"),
31
+ ),
32
+ );
33
+ const references =
34
+ phase?.payload.materials.filter((material) =>
35
+ material.id.startsWith("delivery/execution-"),
36
+ ) || [];
37
+ if (!references.length) return null;
38
+ if (references.length !== 1)
39
+ throw new Error("Native worker requires one admitted execution");
40
+ const execution = await host.materialStore(session).read(references[0]);
41
+ if (
42
+ execution.attempt !== current.identity.id ||
43
+ execution.generation !== current.generation.id
44
+ )
45
+ throw new Error("Native worker execution belongs to another attempt");
46
+ const { run, jobs } = await host.runs.read(
47
+ execution.runId,
48
+ execution.runAttempt,
49
+ );
50
+ if (run.status !== "completed") {
51
+ const terminalJobs = [
52
+ "Credentialless native execution",
53
+ "Credentialless native evidence seal",
54
+ ].map((name) => workerJob(jobs, name));
55
+ if (terminalJobs.some((job) => !job || job.status !== "completed"))
56
+ return {
57
+ status: "in_progress",
58
+ reason: "admitted-execution-not-terminal",
59
+ run,
60
+ };
61
+ }
62
+ const entries = (run.referenced_workflows || []).filter((entry) =>
63
+ entry.path?.startsWith(
64
+ "kungfu-systems/buildchain/.github/workflows/public-ops-dev-auto-merge.yml@",
65
+ ),
66
+ );
67
+ if (entries.length !== 1) return null;
68
+ const native = workerJob(jobs, "Credentialless native execution"),
69
+ seal = workerJob(jobs, "Credentialless native evidence seal");
70
+ if (!native || !seal) return null;
71
+ if (
72
+ run.status !== "completed" &&
73
+ run.id !== host.runId &&
74
+ native.status === "completed" &&
75
+ seal.status === "completed"
76
+ )
77
+ await host.runs.completed(execution.runId, execution.runAttempt);
78
+ return host.workers.observe(
79
+ {
80
+ schema: PIPELINE_WORKER,
81
+ repository: host.repository,
82
+ attempt: current.identity.id,
83
+ generation: current.generation.id,
84
+ sourceHead: warrant.sourceHead,
85
+ candidateId: warrant.candidateId,
86
+ fencingToken: warrant.fencingToken,
87
+ leaseGeneration: warrant.generation,
88
+ runId: execution.runId,
89
+ runAttempt: execution.runAttempt,
90
+ nativeJobId: native.id,
91
+ sealJobId: seal.id,
92
+ },
93
+ current,
94
+ warrant,
95
+ );
96
+ }
97
+
98
+ export function pipelineDeliveryStore(session, host) {
99
+ const branch = session.intent.source.targetBranch;
100
+ const store = new GitHubDevDeliveryStore({
101
+ repository: host.repository,
102
+ token: host.token,
103
+ });
104
+ return {
105
+ read: async () =>
106
+ (
107
+ await store.read({
108
+ stateRef: defaultDevDeliveryStateRef(branch),
109
+ protectedBase: branch,
110
+ now: new Date().toISOString(),
111
+ })
112
+ ).queue,
113
+ service: createDeliveryWarrantService(
114
+ { repository: host.repository, token: host.token, branch },
115
+ store,
116
+ ),
117
+ };
118
+ }
@@ -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
+ }