@kungfu-tech/buildchain 4.0.1-alpha.2 → 4.0.1-alpha.21

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 (203) hide show
  1. package/actions/release-tail/README.md +8 -0
  2. package/architecture/ci-lane-change-budget.json +187 -0
  3. package/architecture/decisions/0002-next-development-transition.md +101 -0
  4. package/architecture/decisions/0003-two-phase-delivery-warrant.md +152 -0
  5. package/architecture/internal-capabilities.json +211 -8
  6. package/architecture/maintainability-policy.json +113 -28
  7. package/architecture/release-tail-contract-inventory.json +5 -2
  8. package/architecture/v3-core-mechanism-inventory.json +91 -0
  9. package/architecture/v4-adopter-delivery-parity.json +32 -5
  10. package/architecture/v4-capability-state-machine-manifest.json +73 -0
  11. package/architecture/v4-compatibility-facts-parity.json +77 -0
  12. package/architecture/v4-delivery-authority-parity.json +250 -0
  13. package/architecture/v4-delivery-warrant-shadow-fixtures.json +41 -9
  14. package/architecture/v4-floating-consumer-policy.json +6 -0
  15. package/architecture/v4-next-development-parity.json +192 -0
  16. package/architecture/v4-publication-rehearsal-parity.json +84 -0
  17. package/architecture/v4-release-train-parity.json +1 -0
  18. package/architecture/v4-tail-reseal-parity.json +150 -0
  19. package/bin/buildchain.mjs +32 -3
  20. package/bin/internal/adopter-delivery-cli.mjs +70 -0
  21. package/bin/internal/command-registry.mjs +3 -0
  22. package/bin/internal/compatibility-facts-cli.mjs +101 -0
  23. package/contracts/dev-delivery-authority-v2.schema.json +662 -0
  24. package/contracts/fixtures/next-development-transition-v1/anchored-manual-waiting.json +47 -0
  25. package/contracts/fixtures/next-development-transition-v1/semver-auto-planned.json +47 -0
  26. package/contracts/fixtures/next-development-transition-v1/version-model-cases.json +40 -0
  27. package/contracts/fixtures/v4-adopter-delivery-v1/archive-template.json +19 -0
  28. package/contracts/fixtures/v4-adopter-delivery-v1/bootstrap-positive.json +93 -0
  29. package/contracts/fixtures/v4-adopter-delivery-v1/gate-positive.json +29 -0
  30. package/contracts/fixtures/v4-adopter-delivery-v1/gate-unknown-selector.json +26 -0
  31. package/contracts/fixtures/v4-adopter-delivery-v1/offline-vectors.json +50 -0
  32. package/contracts/fixtures/v4-compatibility-facts-v1/shared.json +1129 -0
  33. package/contracts/fixtures/v4-floating-consumer-policy-v1/cases.json +7 -1
  34. package/contracts/fixtures/v4-publication-rehearsal-v1/candidate/artifacts/product.bin +1 -0
  35. package/contracts/fixtures/v4-publication-rehearsal-v1/candidate/config/buildchain.toml +5 -0
  36. package/contracts/fixtures/v4-publication-rehearsal-v1/candidate/documents/release-activation.json +1 -0
  37. package/contracts/fixtures/v4-publication-rehearsal-v1/candidate/documents/signed-channel.json +1 -0
  38. package/contracts/fixtures/v4-publication-rehearsal-v1/candidate/evidence/qualification.json +1 -0
  39. package/contracts/fixtures/v4-publication-rehearsal-v1/candidate/manifests/candidate.json +5 -0
  40. package/contracts/fixtures/v4-publication-rehearsal-v1/candidate/manifests/release-passport.json +1 -0
  41. package/contracts/fixtures/v4-publication-rehearsal-v1/capsule.json +707 -0
  42. package/contracts/fixtures/v4-publication-rehearsal-v1/offline-vectors.json +27 -0
  43. package/contracts/fixtures/v4-tail-reseal-v1/valid.json +229 -0
  44. package/contracts/next-development-request-v1.schema.json +66 -0
  45. package/contracts/next-development-transition-v1.schema.json +292 -0
  46. package/contracts/v4-adopter-delivery-v1.schema.json +81 -0
  47. package/contracts/v4-compatibility-facts-v1.schema.json +230 -0
  48. package/contracts/v4-floating-consumer-policy-receipt-v1.schema.json +4 -2
  49. package/contracts/v4-publication-rehearsal-capsule-v1.schema.json +233 -0
  50. package/contracts/v4-tail-reseal-v1.schema.json +276 -0
  51. package/dist/site/agent-index.json +5 -0
  52. package/dist/site/artifact-schemas.json +10 -0
  53. package/dist/site/buildchain-contract.json +1475 -27
  54. package/dist/site/buildchain-site.json +627 -31
  55. package/dist/site/capability-registry.json +17 -14
  56. package/dist/site/cli-registry.json +205 -3
  57. package/dist/site/kfd-claims.json +490 -16
  58. package/dist/site/kfd-upstream-aggregate.json +1 -1
  59. package/dist/site/manual-registry.json +52 -6
  60. package/dist/site/node-api-registry.json +14736 -8851
  61. package/dist/site/page-registry.json +593 -21
  62. package/dist/site/public-surface-audit.json +837 -48
  63. package/dist/site/publication-authority-registry.json +44 -8
  64. package/dist/site/publication-registry.json +4 -4
  65. package/dist/site/release-provenance.json +24 -0
  66. package/dist/site/schemas/dev-delivery-authority-v2.schema.json +662 -0
  67. package/dist/site/schemas/release-tail-capabilities-v1.schema.json +199 -0
  68. package/dist/site/schemas/v4-adopter-delivery-v1.schema.json +81 -0
  69. package/dist/site/schemas/v4-compatibility-facts-v1.schema.json +230 -0
  70. package/dist/site/schemas/v4-publication-rehearsal-capsule-v1.schema.json +233 -0
  71. package/dist/site/site-manifest.json +34 -10
  72. package/dist/site/workflow-registry.json +160 -10
  73. package/docs/MAP.md +8 -2
  74. package/docs/cli-reference.md +404 -13
  75. package/docs/cli.md +8 -0
  76. package/docs/dev-alpha-candidate-patrol.md +8 -0
  77. package/docs/dev-delivery-qualification-landing-adr.md +251 -0
  78. package/docs/dev-delivery-warrant.md +351 -38
  79. package/docs/next-development-transition.md +118 -0
  80. package/docs/node-api-reference.md +646 -293
  81. package/docs/release-tail-provider-plane.md +30 -0
  82. package/docs/v4-adopter-delivery.md +133 -0
  83. package/docs/v4-compatibility-facts.md +91 -0
  84. package/docs/v4-publication-rehearsal.md +130 -0
  85. package/docs/v4-tail-reseal.md +73 -0
  86. package/docs/versioning.md +4 -3
  87. package/package.json +34 -6
  88. package/packages/core/adopter-delivery-passport.js +259 -0
  89. package/packages/core/adopter-delivery-vectors.js +158 -0
  90. package/packages/core/adopter-delivery-vectors.json +77 -0
  91. package/packages/core/buildchain-agent-manuals.js +3 -0
  92. package/packages/core/buildchain-compatibility-authority.js +336 -0
  93. package/packages/core/buildchain-compatibility-fact.js +394 -0
  94. package/packages/core/buildchain-compatibility-facts.json +217 -0
  95. package/packages/core/buildchain-compatibility-proof.js +566 -0
  96. package/packages/core/buildchain-config.js +84 -0
  97. package/packages/core/buildchain-contract.js +96 -143
  98. package/packages/core/buildchain-delivery-bootstrap.js +240 -0
  99. package/packages/core/buildchain-delivery-infrastructure.js +164 -0
  100. package/packages/core/buildchain-delivery-self-dogfood.js +395 -0
  101. package/packages/core/buildchain-publication-authority.js +1 -0
  102. package/packages/core/channel-candidate.js +8 -0
  103. package/packages/core/channel-promotion-baseline.js +55 -0
  104. package/packages/core/ci-lane-change-budget.js +247 -0
  105. package/packages/core/dev-alpha-candidate-selection.js +10 -2
  106. package/packages/core/dev-delivery-authority-candidate.js +270 -0
  107. package/packages/core/dev-delivery-authority-evidence.js +146 -0
  108. package/packages/core/dev-delivery-authority-landing.js +461 -0
  109. package/packages/core/dev-delivery-authority-observation.js +48 -0
  110. package/packages/core/dev-delivery-authority-qualification.js +591 -0
  111. package/packages/core/dev-delivery-authority-settlement.js +213 -0
  112. package/packages/core/dev-delivery-authority-state.js +583 -0
  113. package/packages/core/dev-delivery-candidate-identity.js +13 -0
  114. package/packages/core/dev-delivery-contract-surface.js +76 -0
  115. package/packages/core/dev-delivery-execution-failure.js +133 -0
  116. package/packages/core/dev-delivery-execution-transfer.js +592 -0
  117. package/packages/core/dev-delivery-landing-admission-core.js +119 -0
  118. package/packages/core/dev-delivery-landing-readback.js +598 -0
  119. package/packages/core/dev-delivery-landing-terminal-evidence.js +271 -0
  120. package/packages/core/dev-delivery-landing-testing-port.js +6 -0
  121. package/packages/core/dev-delivery-native-execution.js +110 -0
  122. package/packages/core/dev-delivery-native-proof.js +562 -0
  123. package/packages/core/dev-delivery-process-boundary.js +563 -0
  124. package/packages/core/dev-delivery-provider-attempt.js +127 -0
  125. package/packages/core/dev-delivery-provider-heartbeat.js +382 -0
  126. package/packages/core/dev-delivery-warrant-cancellation.js +1 -0
  127. package/packages/core/dev-delivery-warrant-qualification.js +145 -0
  128. package/packages/core/dev-delivery-warrant-settlement.js +237 -36
  129. package/packages/core/dev-delivery-warrant-state.js +587 -0
  130. package/packages/core/dev-delivery-warrant.js +328 -370
  131. package/packages/core/github-governance-authority.js +1 -0
  132. package/packages/core/index.js +5 -0
  133. package/packages/core/kungfu-temporal-fact.js +557 -0
  134. package/packages/core/legacy-kfd-adopter-driver.js +167 -0
  135. package/packages/core/next-development-candidate-reservation.js +186 -0
  136. package/packages/core/next-development-controller.js +728 -0
  137. package/packages/core/next-development-projection.js +287 -0
  138. package/packages/core/next-development-transition.js +738 -0
  139. package/packages/core/paper-agent-entry.js +11 -5
  140. package/packages/core/paper.js +16 -5
  141. package/packages/core/published-delivery-authority.js +266 -0
  142. package/packages/core/release-candidate-recovery.js +70 -15
  143. package/packages/core/release-candidate.js +2 -2
  144. package/packages/core/release-passport.js +36 -9
  145. package/packages/core/v4-adopter-delivery-parity.js +3 -3
  146. package/packages/core/v4-adopter-delivery.js +359 -0
  147. package/packages/core/v4-canonical-contracts.js +5 -0
  148. package/packages/core/v4-delivery-warrant-read-candidate.js +10 -3
  149. package/packages/core/v4-floating-consumer-evidence.js +18 -4
  150. package/packages/core/v4-floating-consumer-policy.js +143 -20
  151. package/packages/core/v4-floating-consumer-release-passport.js +32 -5
  152. package/packages/core/v4-publication-rehearsal-capsule.js +468 -0
  153. package/packages/core/v4-publication-rehearsal-provider-bindings.js +212 -0
  154. package/packages/core/v4-publication-rehearsal.js +481 -0
  155. package/packages/core/v4-tail-reseal-contract.js +26 -0
  156. package/packages/core/v4-tail-reseal-github.js +178 -0
  157. package/packages/core/v4-tail-reseal-receipt.js +225 -0
  158. package/packages/core/v4-tail-reseal.js +594 -0
  159. package/packages/core/workflow-yaml-contract.js +24 -2
  160. package/scripts/buildchain-cli-help.mjs +41 -2
  161. package/scripts/check-ci-lane-change-budget.mjs +100 -0
  162. package/scripts/check-inventory.mjs +14 -3
  163. package/scripts/check-v4-floating-consumer-policy-contract.mjs +23 -4
  164. package/scripts/dev-alpha-candidate-patrol.mjs +22 -1
  165. package/scripts/dev-delivery-authority-command-adapters.mjs +206 -0
  166. package/scripts/dev-delivery-authority-provider.mjs +28 -0
  167. package/scripts/dev-delivery-authority.mjs +490 -0
  168. package/scripts/dev-delivery-native-run.mjs +177 -0
  169. package/scripts/dev-delivery-process-boundary.mjs +260 -0
  170. package/scripts/dev-delivery-proof.mjs +67 -2
  171. package/scripts/dev-delivery-provider-heartbeat.mjs +215 -0
  172. package/scripts/dev-delivery-source-proof-replay.mjs +153 -0
  173. package/scripts/dev-delivery-source-proof-reuse.mjs +13 -97
  174. package/scripts/dev-delivery-two-phase-resume.mjs +345 -0
  175. package/scripts/dev-delivery-two-phase.mjs +573 -0
  176. package/scripts/dev-delivery-warrant-options.mjs +266 -0
  177. package/scripts/dev-delivery-warrant-store.mjs +231 -0
  178. package/scripts/dev-delivery-warrant.mjs +232 -194
  179. package/scripts/dev-pr-auto-merge.mjs +1 -1
  180. package/scripts/dev-pr-delivery-warrant.mjs +11 -0
  181. package/scripts/dev-qualification-patrol.mjs +9 -9
  182. package/scripts/dispatch-artifact-signing-authority.mjs +1 -1
  183. package/scripts/generate-channel-promotion-workflow.mjs +2 -2
  184. package/scripts/generate-next-development-guidance.mjs +49 -0
  185. package/scripts/generate-release-candidate-passport.mjs +152 -0
  186. package/scripts/generate-site-bundle.mjs +69 -4
  187. package/scripts/init-repo.mjs +22 -4
  188. package/scripts/next-development-self-dogfood-harness.mjs +460 -0
  189. package/scripts/next-development-self-dogfood.mjs +597 -0
  190. package/scripts/next-development-transition.mjs +47 -0
  191. package/scripts/npm-publish-transaction.mjs +2 -2
  192. package/scripts/promotion-channel-router.mjs +4 -4
  193. package/scripts/release-candidate-anchor-provenance.mjs +99 -0
  194. package/scripts/release-tail.mjs +51 -3
  195. package/scripts/release-train-self-dogfood.mjs +94 -92
  196. package/scripts/resume-from-candidate-run.mjs +557 -30
  197. package/scripts/site-capability-metadata.mjs +41 -0
  198. package/scripts/stable-candidate-qualification.mjs +9 -10
  199. package/scripts/v4-consumer-policy.mjs +5 -5
  200. package/scripts/v4-publication-rehearsal-fixture.mjs +144 -0
  201. package/scripts/v4-tail-reseal-macos-rehearsal.mjs +134 -0
  202. package/scripts/v4-tail-reseal.mjs +416 -0
  203. package/templates/native-dev-delivery.yml +143 -0
@@ -0,0 +1,598 @@
1
+ import {
2
+ devDeliveryExactSha as exactSha,
3
+ devDeliveryPositiveInteger as positiveInteger,
4
+ devDeliveryText as text,
5
+ devDeliveryTimestamp as timestamp,
6
+ } from "./dev-delivery-common.js";
7
+ import { normalizeDevDeliveryProviderAttempt } from "./dev-delivery-provider-attempt.js";
8
+ import { sealLandingTerminalReadback } from "./dev-delivery-landing-terminal-evidence.js";
9
+
10
+ export {
11
+ DEV_DELIVERY_LANDING_TERMINAL_READBACK_SCHEMA,
12
+ verifyLandingSettlementReadback,
13
+ verifyExpiredLandingSettlementReadback,
14
+ } from "./dev-delivery-landing-terminal-evidence.js";
15
+
16
+ function repositoryParts(value) {
17
+ const match = text(value).match(/^([^/\s]+)\/([^/\s]+)$/u);
18
+ if (!match) throw new Error("provider repository must be owner/repo");
19
+ return { owner: match[1], repo: match[2], fullName: value };
20
+ }
21
+
22
+ async function githubRequest({
23
+ apiUrl,
24
+ token,
25
+ path,
26
+ method = "GET",
27
+ fetchImpl = globalThis.fetch,
28
+ }) {
29
+ if (!token) throw new Error("GitHub provider readback requires a token");
30
+ if (typeof fetchImpl !== "function")
31
+ throw new Error("GitHub provider readback requires fetch");
32
+ const response = await fetchImpl(
33
+ `${String(apiUrl).replace(/\/$/u, "")}${path}`,
34
+ {
35
+ method,
36
+ headers: {
37
+ accept: "application/vnd.github+json",
38
+ authorization: `Bearer ${token}`,
39
+ "x-github-api-version": "2022-11-28",
40
+ },
41
+ },
42
+ );
43
+ const bytes = await response.text();
44
+ let body = null;
45
+ if (bytes) {
46
+ try {
47
+ body = JSON.parse(bytes);
48
+ } catch {
49
+ throw new Error(
50
+ `GitHub provider ${method} ${path} returned non-JSON evidence`,
51
+ );
52
+ }
53
+ }
54
+ if (!response.ok) {
55
+ const error = new Error(
56
+ `GitHub provider ${method} ${path} failed with ${response.status}: ${body?.message || "unknown error"}`,
57
+ );
58
+ error.status = response.status;
59
+ throw error;
60
+ }
61
+ return body;
62
+ }
63
+
64
+ async function githubJson(options) {
65
+ const body = await githubRequest(options);
66
+ if (!body || typeof body !== "object") {
67
+ throw new Error(
68
+ `GitHub provider readback ${options.path} returned no JSON`,
69
+ );
70
+ }
71
+ return body;
72
+ }
73
+
74
+ async function readProviderSnapshot({
75
+ apiUrl,
76
+ token,
77
+ owner,
78
+ repo,
79
+ runId,
80
+ runAttempt,
81
+ jobId,
82
+ pullRequestNumber,
83
+ workflowId,
84
+ fetchImpl,
85
+ }) {
86
+ const [run, job, pullRequest, workflow] = await Promise.all([
87
+ githubJson({
88
+ apiUrl,
89
+ token,
90
+ path: `/repos/${owner}/${repo}/actions/runs/${runId}/attempts/${runAttempt}`,
91
+ fetchImpl,
92
+ }),
93
+ githubJson({
94
+ apiUrl,
95
+ token,
96
+ path: `/repos/${owner}/${repo}/actions/jobs/${jobId}`,
97
+ fetchImpl,
98
+ }),
99
+ githubJson({
100
+ apiUrl,
101
+ token,
102
+ path: `/repos/${owner}/${repo}/pulls/${pullRequestNumber}`,
103
+ fetchImpl,
104
+ }),
105
+ githubJson({
106
+ apiUrl,
107
+ token,
108
+ path: `/repos/${owner}/${repo}/actions/workflows/${workflowId}`,
109
+ fetchImpl,
110
+ }),
111
+ ]);
112
+ return { run, job, pullRequest, workflow };
113
+ }
114
+
115
+ const LANDING_AUTHORITY_JOB_NAME = "Landing authority";
116
+
117
+ function currentProviderJob(jobsInput) {
118
+ const entries = Array.isArray(jobsInput) ? jobsInput : jobsInput?.jobs;
119
+ if (!Array.isArray(entries)) {
120
+ throw new Error("GitHub provider current-attempt jobs readback is missing");
121
+ }
122
+ const matches = entries.filter(
123
+ (entry) =>
124
+ entry.status === "in_progress" &&
125
+ (entry.name === LANDING_AUTHORITY_JOB_NAME ||
126
+ String(entry.name || "").endsWith(` / ${LANDING_AUTHORITY_JOB_NAME}`)),
127
+ );
128
+ if (matches.length !== 1) {
129
+ throw new Error(
130
+ "GitHub provider current job cannot be independently identified",
131
+ );
132
+ }
133
+ return matches[0];
134
+ }
135
+
136
+ export function deriveDevDeliveryLandingProviderAttempt({
137
+ state,
138
+ candidate,
139
+ mergeGroupHead,
140
+ providerRunId,
141
+ providerRunAttempt,
142
+ run,
143
+ jobs,
144
+ workflow,
145
+ pullRequest,
146
+ } = {}) {
147
+ const { fullName } = repositoryParts(state?.repository);
148
+ const runId = positiveInteger(providerRunId, "GitHub provider run id");
149
+ const runAttempt = positiveInteger(
150
+ providerRunAttempt,
151
+ "GitHub provider run attempt",
152
+ );
153
+ const exactMergeGroupHead = exactSha(
154
+ mergeGroupHead,
155
+ "provider merge-group head",
156
+ );
157
+ const workflowSha = exactSha(run?.head_sha, "GitHub provider workflow SHA");
158
+ const runHeadBranch = text(run?.head_branch);
159
+ if (
160
+ Number(run?.id) !== runId ||
161
+ Number(run?.run_attempt) !== runAttempt ||
162
+ run?.repository?.full_name !== fullName ||
163
+ run?.event !== "merge_group" ||
164
+ run?.head_sha !== exactMergeGroupHead ||
165
+ !runHeadBranch.startsWith(`gh-readonly-queue/${state.protectedBase}/`)
166
+ ) {
167
+ throw new Error("GitHub provider current execution context mismatch");
168
+ }
169
+ if (Number(workflow?.id) !== Number(run.workflow_id) || !workflow?.path) {
170
+ throw new Error("GitHub provider current workflow context mismatch");
171
+ }
172
+ if (
173
+ Number(pullRequest?.number) !== candidate.pullRequestNumber ||
174
+ pullRequest?.head?.sha !== candidate.sourceHead ||
175
+ pullRequest?.base?.ref !== state.protectedBase ||
176
+ pullRequest?.base?.repo?.full_name !== fullName
177
+ ) {
178
+ throw new Error("GitHub provider pull request readback binding mismatch");
179
+ }
180
+ const providerJob = currentProviderJob(jobs);
181
+ const workflowRef = `${fullName}/${workflow.path}@refs/heads/${runHeadBranch}`;
182
+ return normalizeDevDeliveryProviderAttempt({
183
+ schema: "kungfu.buildchain.github-landing-provider-attempt/v1",
184
+ repository: fullName,
185
+ workflowId: Number(workflow.id),
186
+ workflowPath: workflow.path,
187
+ workflowRef,
188
+ workflowSha,
189
+ event: run.event,
190
+ runId,
191
+ runAttempt,
192
+ jobId: Number(providerJob.id),
193
+ jobName: providerJob.name,
194
+ jobRole: "landing-authority",
195
+ runnerId: Number(providerJob.runner_id),
196
+ runnerName: providerJob.runner_name,
197
+ runnerGroupId: Number(providerJob.runner_group_id),
198
+ runnerGroupName: providerJob.runner_group_name,
199
+ runnerLabels: providerJob.labels,
200
+ sourceHead: candidate.sourceHead,
201
+ mergeGroupHead: exactMergeGroupHead,
202
+ protectedBase: state.protectedBase,
203
+ });
204
+ }
205
+
206
+ export async function readGitHubLandingProviderAttempt({
207
+ state,
208
+ candidate,
209
+ mergeGroupHead,
210
+ providerRunId,
211
+ providerRunAttempt,
212
+ token,
213
+ apiUrl = "https://api.github.com",
214
+ fetchImpl = globalThis.fetch,
215
+ } = {}) {
216
+ const { owner, repo } = repositoryParts(state.repository);
217
+ const runId = positiveInteger(providerRunId, "GitHub provider run id");
218
+ const runAttempt = positiveInteger(
219
+ providerRunAttempt,
220
+ "GitHub provider run attempt",
221
+ );
222
+ const run = await githubJson({
223
+ apiUrl,
224
+ token,
225
+ path: `/repos/${owner}/${repo}/actions/runs/${runId}`,
226
+ fetchImpl,
227
+ });
228
+ const [jobs, workflow, pullRequest] = await Promise.all([
229
+ githubJson({
230
+ apiUrl,
231
+ token,
232
+ path: `/repos/${owner}/${repo}/actions/runs/${runId}/attempts/${runAttempt}/jobs?per_page=100`,
233
+ fetchImpl,
234
+ }),
235
+ githubJson({
236
+ apiUrl,
237
+ token,
238
+ path: `/repos/${owner}/${repo}/actions/workflows/${run.workflow_id}`,
239
+ fetchImpl,
240
+ }),
241
+ githubJson({
242
+ apiUrl,
243
+ token,
244
+ path: `/repos/${owner}/${repo}/pulls/${candidate.pullRequestNumber}`,
245
+ fetchImpl,
246
+ }),
247
+ ]);
248
+ return deriveDevDeliveryLandingProviderAttempt({
249
+ state,
250
+ candidate,
251
+ mergeGroupHead,
252
+ providerRunId: runId,
253
+ providerRunAttempt: runAttempt,
254
+ run,
255
+ jobs,
256
+ workflow,
257
+ pullRequest,
258
+ });
259
+ }
260
+
261
+ function providerAttemptIsTerminal({ run, job }) {
262
+ return run.status === "completed" && job.status === "completed";
263
+ }
264
+
265
+ function providerRunMatches(run, attempt, fullName) {
266
+ return (
267
+ Number(run.id) === attempt.runId &&
268
+ run.repository?.full_name === fullName &&
269
+ Number(run.workflow_id) === attempt.workflowId &&
270
+ Number(run.run_attempt) === attempt.runAttempt &&
271
+ run.event === attempt.event &&
272
+ run.head_sha === attempt.mergeGroupHead
273
+ );
274
+ }
275
+
276
+ function providerJobMatches(job, attempt) {
277
+ return (
278
+ Number(job.id) === attempt.jobId &&
279
+ job.name === attempt.jobName &&
280
+ Number(job.runner_id) === attempt.runnerId &&
281
+ job.runner_name === attempt.runnerName &&
282
+ Number(job.runner_group_id) === attempt.runnerGroupId &&
283
+ job.runner_group_name === attempt.runnerGroupName &&
284
+ JSON.stringify([...(job.labels || [])].sort()) ===
285
+ JSON.stringify(attempt.runnerLabels) &&
286
+ String(job.run_url || "").endsWith(`/actions/runs/${attempt.runId}`)
287
+ );
288
+ }
289
+
290
+ async function readProtectedBaseLanding({
291
+ apiUrl,
292
+ token,
293
+ owner,
294
+ repo,
295
+ protectedBase,
296
+ mergeGroupHead,
297
+ fetchImpl = globalThis.fetch,
298
+ }) {
299
+ const protectedRef = await githubJson({
300
+ apiUrl,
301
+ token,
302
+ path: `/repos/${owner}/${repo}/git/ref/heads/${encodeURIComponent(protectedBase)}`,
303
+ fetchImpl,
304
+ });
305
+ const protectedBaseHead = exactSha(
306
+ protectedRef.object?.sha,
307
+ "GitHub protected base head",
308
+ );
309
+ const comparison = await githubJson({
310
+ apiUrl,
311
+ token,
312
+ path: `/repos/${owner}/${repo}/compare/${protectedBaseHead}...${mergeGroupHead}`,
313
+ fetchImpl,
314
+ });
315
+ const comparisonHead = exactSha(
316
+ comparison.base_commit?.sha,
317
+ "GitHub protected base comparison head",
318
+ );
319
+ if (comparisonHead !== protectedBaseHead) {
320
+ throw new Error("GitHub protected base comparison head drifted");
321
+ }
322
+ const mergeBase = exactSha(
323
+ comparison.merge_base_commit?.sha,
324
+ "GitHub protected base merge-base",
325
+ );
326
+ const status = text(comparison.status);
327
+ if (!new Set(["ahead", "behind", "diverged", "identical"]).has(status)) {
328
+ throw new Error("GitHub protected base comparison status is unsupported");
329
+ }
330
+ return {
331
+ protectedBaseHead,
332
+ providerRunHeadInProtectedBase:
333
+ mergeBase === mergeGroupHead &&
334
+ new Set(["behind", "identical"]).has(status),
335
+ };
336
+ }
337
+
338
+ function providerPullRequestMatches(
339
+ pullRequest,
340
+ candidate,
341
+ attempt,
342
+ fullName,
343
+ requireCurrentSourceHead,
344
+ ) {
345
+ return (
346
+ Number(pullRequest.number) === candidate.pullRequestNumber &&
347
+ (!requireCurrentSourceHead ||
348
+ pullRequest.head?.sha === candidate.sourceHead) &&
349
+ pullRequest.base?.ref === attempt.protectedBase &&
350
+ pullRequest.base?.repo?.full_name === fullName
351
+ );
352
+ }
353
+
354
+ function assertProviderAttemptIdentity({
355
+ snapshot,
356
+ fullName,
357
+ candidate,
358
+ warrant,
359
+ observedAt,
360
+ requireCurrentSourceHead = true,
361
+ }) {
362
+ const { run, job, pullRequest, workflow } = snapshot;
363
+ const attempt = normalizeDevDeliveryProviderAttempt(warrant.providerAttempt, {
364
+ repository: fullName,
365
+ sourceHead: candidate.sourceHead,
366
+ protectedBase: warrant.providerAttempt.protectedBase,
367
+ mergeGroupHead: warrant.mergeGroupHead,
368
+ });
369
+ if (!providerRunMatches(run, attempt, fullName)) {
370
+ throw new Error("GitHub provider run readback binding mismatch");
371
+ }
372
+ if (
373
+ Number(workflow.id) !== attempt.workflowId ||
374
+ workflow.path !== attempt.workflowPath
375
+ ) {
376
+ throw new Error("GitHub provider workflow readback binding mismatch");
377
+ }
378
+ if (!providerJobMatches(job, attempt)) {
379
+ throw new Error("GitHub provider job readback binding mismatch");
380
+ }
381
+ if (
382
+ !providerPullRequestMatches(
383
+ pullRequest,
384
+ candidate,
385
+ attempt,
386
+ fullName,
387
+ requireCurrentSourceHead,
388
+ )
389
+ ) {
390
+ throw new Error("GitHub provider pull request readback binding mismatch");
391
+ }
392
+ const startedAt = timestamp(job.started_at, "provider job start");
393
+ if (
394
+ Date.parse(startedAt) < Date.parse(warrant.issuedAt) ||
395
+ Date.parse(startedAt) > Date.parse(observedAt)
396
+ ) {
397
+ throw new Error(
398
+ "GitHub provider job readback is stale for this Landing fence",
399
+ );
400
+ }
401
+ }
402
+
403
+ export async function readGitHubLandingActiveProviderAttempt({
404
+ state,
405
+ candidate,
406
+ warrant,
407
+ providerAttempt,
408
+ token,
409
+ apiUrl = "https://api.github.com",
410
+ now = new Date().toISOString(),
411
+ fetchImpl = globalThis.fetch,
412
+ } = {}) {
413
+ const observedAt = timestamp(now, "provider heartbeat observation time");
414
+ const persistedAttempt = normalizeDevDeliveryProviderAttempt(
415
+ warrant?.providerAttempt,
416
+ {
417
+ repository: state?.repository,
418
+ sourceHead: candidate?.sourceHead,
419
+ mergeGroupHead: warrant?.mergeGroupHead,
420
+ protectedBase: state?.protectedBase,
421
+ },
422
+ );
423
+ const requestedAttempt = normalizeDevDeliveryProviderAttempt(
424
+ providerAttempt,
425
+ {
426
+ repository: state?.repository,
427
+ sourceHead: candidate?.sourceHead,
428
+ mergeGroupHead: warrant?.mergeGroupHead,
429
+ protectedBase: state?.protectedBase,
430
+ },
431
+ );
432
+ if (JSON.stringify(requestedAttempt) !== JSON.stringify(persistedAttempt)) {
433
+ throw new Error(
434
+ "Landing heartbeat provider attempt does not match persisted admission",
435
+ );
436
+ }
437
+ const { owner, repo, fullName } = repositoryParts(state.repository);
438
+ const snapshot = await readProviderSnapshot({
439
+ apiUrl,
440
+ token,
441
+ owner,
442
+ repo,
443
+ runId: persistedAttempt.runId,
444
+ runAttempt: persistedAttempt.runAttempt,
445
+ jobId: persistedAttempt.jobId,
446
+ pullRequestNumber: candidate.pullRequestNumber,
447
+ workflowId: persistedAttempt.workflowId,
448
+ fetchImpl,
449
+ });
450
+ assertProviderAttemptIdentity({
451
+ snapshot,
452
+ fullName,
453
+ candidate,
454
+ warrant,
455
+ observedAt,
456
+ });
457
+ if (
458
+ snapshot.run.status !== "in_progress" ||
459
+ snapshot.job.status !== "in_progress" ||
460
+ snapshot.run.conclusion ||
461
+ snapshot.job.conclusion
462
+ ) {
463
+ throw new Error(
464
+ "Landing heartbeat requires the exact admitted provider run and job to remain active",
465
+ );
466
+ }
467
+ return persistedAttempt;
468
+ }
469
+
470
+ function outcomeFromProvider(
471
+ run,
472
+ job,
473
+ pullRequest,
474
+ { providerRunHeadInProtectedBase },
475
+ ) {
476
+ if (pullRequest.merged_at) {
477
+ return providerRunHeadInProtectedBase ? "merged" : "dequeued";
478
+ }
479
+ if (pullRequest.state === "closed") return "cancelled";
480
+ if (job.conclusion === "cancelled") return "cancelled";
481
+ if (
482
+ [
483
+ "failure",
484
+ "timed_out",
485
+ "action_required",
486
+ "startup_failure",
487
+ "stale",
488
+ ].includes(job.conclusion)
489
+ ) {
490
+ return "terminal-failure";
491
+ }
492
+ if (job.conclusion === "skipped") return "dequeued";
493
+ throw new Error(
494
+ "GitHub provider terminal state does not prove a durable Landing outcome",
495
+ );
496
+ }
497
+
498
+ export async function readGitHubLandingTerminalState({
499
+ state,
500
+ candidate,
501
+ warrant,
502
+ token,
503
+ apiUrl = "https://api.github.com",
504
+ now = new Date().toISOString(),
505
+ }) {
506
+ const observedAt = timestamp(now, "provider observation time");
507
+ const providerAttempt = normalizeDevDeliveryProviderAttempt(
508
+ warrant.providerAttempt,
509
+ {
510
+ repository: state.repository,
511
+ sourceHead: candidate.sourceHead,
512
+ mergeGroupHead: warrant.mergeGroupHead,
513
+ protectedBase: state.protectedBase,
514
+ },
515
+ );
516
+ const runId = providerAttempt.runId;
517
+ const jobId = providerAttempt.jobId;
518
+ const { owner, repo, fullName } = repositoryParts(state.repository);
519
+ let snapshot = await readProviderSnapshot({
520
+ apiUrl,
521
+ token,
522
+ owner,
523
+ repo,
524
+ runId,
525
+ runAttempt: providerAttempt.runAttempt,
526
+ jobId,
527
+ pullRequestNumber: candidate.pullRequestNumber,
528
+ workflowId: providerAttempt.workflowId,
529
+ });
530
+ assertProviderAttemptIdentity({
531
+ snapshot,
532
+ fullName,
533
+ candidate,
534
+ warrant,
535
+ observedAt,
536
+ requireCurrentSourceHead: false,
537
+ });
538
+ if (!providerAttemptIsTerminal(snapshot)) {
539
+ throw new Error(
540
+ "the exact admitted provider attempt is still active; terminal cleanup refuses run-level cancellation",
541
+ );
542
+ }
543
+ const { run, job, pullRequest } = snapshot;
544
+ if (!run.conclusion || !job.conclusion) {
545
+ throw new Error("GitHub provider terminal conclusion is missing");
546
+ }
547
+ const completedAt = timestamp(job.completed_at, "provider job completion");
548
+ if (
549
+ Date.parse(completedAt) < Date.parse(warrant.issuedAt) ||
550
+ Date.parse(completedAt) > Date.parse(observedAt)
551
+ ) {
552
+ throw new Error(
553
+ "GitHub provider job readback is stale for this Landing fence",
554
+ );
555
+ }
556
+ const protectedBaseLanding = await readProtectedBaseLanding({
557
+ apiUrl,
558
+ token,
559
+ owner,
560
+ repo,
561
+ protectedBase: state.protectedBase,
562
+ mergeGroupHead: providerAttempt.mergeGroupHead,
563
+ });
564
+ const outcome = outcomeFromProvider(
565
+ run,
566
+ job,
567
+ pullRequest,
568
+ protectedBaseLanding,
569
+ );
570
+ return sealLandingTerminalReadback({
571
+ repository: state.repository,
572
+ protectedBase: state.protectedBase,
573
+ stateRoot: state.stateRoot,
574
+ candidateId: candidate.candidateId,
575
+ pullRequestNumber: candidate.pullRequestNumber,
576
+ sourceHead: candidate.sourceHead,
577
+ landingWarrantToken: warrant.token,
578
+ landingWarrantGeneration: warrant.generation,
579
+ providerRunId: runId,
580
+ providerRunAttempt: run.run_attempt,
581
+ providerRunState: run.status,
582
+ providerRunConclusion: run.conclusion,
583
+ providerRunHead: run.head_sha,
584
+ providerJobId: jobId,
585
+ providerJobState: job.status,
586
+ providerJobConclusion: job.conclusion,
587
+ providerJobStartedAt: job.started_at,
588
+ providerJobCompletedAt: completedAt,
589
+ providerAttempt,
590
+ admissionRoot: warrant.mergeGroupAdmissionRoot,
591
+ pullRequestState: pullRequest.state,
592
+ pullRequestMerged: Boolean(pullRequest.merged_at),
593
+ ...protectedBaseLanding,
594
+ outcome,
595
+ reason: `GitHub run ${runId} job ${jobId} concluded ${job.conclusion}`,
596
+ observedAt,
597
+ });
598
+ }