@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,591 @@
1
+ import {
2
+ devDeliveryClone as clone,
3
+ devDeliveryContentRoot,
4
+ devDeliveryExactRoot as exactRoot,
5
+ devDeliveryPositiveInteger as positiveInteger,
6
+ devDeliveryTimestamp as timestamp,
7
+ } from "./dev-delivery-common.js";
8
+ import { chainedDevDeliveryAttemptInput } from "./dev-delivery-candidate-identity.js";
9
+ import {
10
+ DEV_DELIVERY_AUTHORITY_MODE,
11
+ DEV_DELIVERY_NATIVE_QUALIFICATION_SCHEMA,
12
+ DEV_DELIVERY_QUALIFICATION_LEASE_SCHEMA,
13
+ DEV_DELIVERY_SCHEDULER_REASON_SCHEMA,
14
+ DEV_DELIVERY_SCHEDULER_WAKE_SCHEMA,
15
+ TERMINAL_STATES,
16
+ normalizeCandidate,
17
+ receipt,
18
+ schedulerPolicy,
19
+ transition,
20
+ } from "./dev-delivery-authority-state.js";
21
+ import {
22
+ createDevDeliveryBoundedQualificationReceipt,
23
+ verifyDevDeliveryQualificationEvidence,
24
+ } from "./dev-delivery-authority-evidence.js";
25
+
26
+ export function submitDevDeliveryAuthorityCandidate(
27
+ stateInput,
28
+ input,
29
+ { now = new Date().toISOString() } = {},
30
+ ) {
31
+ const currentTime = timestamp(now, "now");
32
+ const transaction = transition(
33
+ stateInput,
34
+ (state) => {
35
+ const latestSamePullRequest = [...state.candidates]
36
+ .reverse()
37
+ .find(
38
+ (entry) =>
39
+ entry.pullRequestNumber === Number(input.pullRequestNumber),
40
+ );
41
+ const candidateInput = latestSamePullRequest
42
+ ? chainedDevDeliveryAttemptInput({
43
+ queue: state,
44
+ candidate: latestSamePullRequest,
45
+ input,
46
+ currentTime,
47
+ terminalStates: TERMINAL_STATES,
48
+ }) || input
49
+ : input;
50
+ const candidate = normalizeCandidate(
51
+ {
52
+ ...candidateInput,
53
+ status: "queued",
54
+ enqueuedAt: input.enqueuedAt || currentTime,
55
+ updatedAt: currentTime,
56
+ qualification: null,
57
+ terminal: null,
58
+ qualificationDomains: input.qualificationDomains || [],
59
+ qualificationAttempts: 0,
60
+ landingOvertakes: 0,
61
+ },
62
+ state,
63
+ );
64
+ if (
65
+ candidate.deliveryClass !== "non-native-fast" &&
66
+ !candidate.environmentRoot
67
+ ) {
68
+ throw new Error(
69
+ "native delivery authority candidate requires environmentRoot",
70
+ );
71
+ }
72
+ if (
73
+ candidate.deliveryClass !== "non-native-fast" &&
74
+ !candidate.nativeCommandContract
75
+ ) {
76
+ throw new Error(
77
+ "native delivery authority candidate requires nativeCommandContract",
78
+ );
79
+ }
80
+ const conflicting = state.candidates.find(
81
+ (entry) =>
82
+ entry.pullRequestNumber === candidate.pullRequestNumber &&
83
+ !TERMINAL_STATES.has(entry.status),
84
+ );
85
+ if (conflicting) {
86
+ const exactFields = [
87
+ "candidateId",
88
+ "sourceHead",
89
+ "sourcePatchRoot",
90
+ "sourceProofRoot",
91
+ "planRoot",
92
+ "closureRoot",
93
+ "dependencyRoot",
94
+ "toolchainRoot",
95
+ "environmentRoot",
96
+ ];
97
+ const same = exactFields.every(
98
+ (field) => conflicting[field] === candidate[field],
99
+ );
100
+ const sameDomains =
101
+ JSON.stringify(conflicting.qualificationDomains || []) ===
102
+ JSON.stringify(candidate.qualificationDomains || []);
103
+ const sameEvidence =
104
+ conflicting.nativeCommandContract?.commandRoot ===
105
+ candidate.nativeCommandContract?.commandRoot &&
106
+ JSON.stringify(conflicting.shardEvidenceRoots || []) ===
107
+ JSON.stringify(candidate.shardEvidenceRoots || []) &&
108
+ JSON.stringify(conflicting.affectedPaths || []) ===
109
+ JSON.stringify(candidate.affectedPaths || []);
110
+ if (!same || !sameDomains || !sameEvidence)
111
+ throw new Error(
112
+ `PR #${candidate.pullRequestNumber} already has different active authority state`,
113
+ );
114
+ return {
115
+ candidate: conflicting,
116
+ action: "duplicate-noop",
117
+ mutated: false,
118
+ };
119
+ }
120
+ state.candidates.push(candidate);
121
+ return { candidate, action: "submitted" };
122
+ },
123
+ currentTime,
124
+ );
125
+ return receipt(transaction, transaction.result.action, {
126
+ candidateId: transaction.result.candidate.candidateId,
127
+ pullRequestNumber: transaction.result.candidate.pullRequestNumber,
128
+ sourceHead: transaction.result.candidate.sourceHead,
129
+ authorityMode: DEV_DELIVERY_AUTHORITY_MODE,
130
+ nextAction: "Acquire a bounded Qualification Lease.",
131
+ });
132
+ }
133
+
134
+ export function authorityToken(schema, state, candidate, generation, issuedAt) {
135
+ return devDeliveryContentRoot({
136
+ schema,
137
+ repository: state.repository,
138
+ protectedBase: state.protectedBase,
139
+ candidateId: candidate.candidateId,
140
+ generation,
141
+ expectedOldStateRoot: state.stateRoot,
142
+ issuedAt,
143
+ });
144
+ }
145
+
146
+ function rootedSchedulerReason(
147
+ state,
148
+ code,
149
+ candidate,
150
+ conflicts = [],
151
+ message = "",
152
+ ) {
153
+ const body = {
154
+ schema: DEV_DELIVERY_SCHEDULER_REASON_SCHEMA,
155
+ code,
156
+ candidateId: candidate?.candidateId || null,
157
+ conflictingCandidateIds: [
158
+ ...new Set(conflicts.map((entry) => entry.candidateId)),
159
+ ].sort(),
160
+ stateRoot: state.stateRoot,
161
+ message,
162
+ };
163
+ return { ...body, reasonRoot: devDeliveryContentRoot(body) };
164
+ }
165
+
166
+ function activeSafetyCandidates(state, candidateId = "") {
167
+ return state.candidates.filter(
168
+ (entry) =>
169
+ entry.candidateId !== candidateId &&
170
+ ["qualifying", "qualified", "landing"].includes(entry.status),
171
+ );
172
+ }
173
+
174
+ function qualificationEligibility(state, candidate) {
175
+ const active = activeSafetyCandidates(state, candidate.candidateId);
176
+ if (active.length === 0) return { eligible: true, reason: null };
177
+ const domains = candidate.qualificationDomains || [];
178
+ if (domains.length === 0) {
179
+ return {
180
+ eligible: false,
181
+ reason: rootedSchedulerReason(
182
+ state,
183
+ "unknown-qualification-domain",
184
+ candidate,
185
+ active,
186
+ "A candidate without rooted qualification domains cannot bypass an active safety boundary.",
187
+ ),
188
+ };
189
+ }
190
+ const unknown = active.filter(
191
+ (entry) => (entry.qualificationDomains || []).length === 0,
192
+ );
193
+ if (unknown.length > 0) {
194
+ return {
195
+ eligible: false,
196
+ reason: rootedSchedulerReason(
197
+ state,
198
+ "active-unknown-qualification-domain",
199
+ candidate,
200
+ unknown,
201
+ "An active candidate with unknown qualification domains is a global safety boundary.",
202
+ ),
203
+ };
204
+ }
205
+ const domainSet = new Set(domains);
206
+ const overlapping = active.filter((entry) =>
207
+ entry.qualificationDomains.some((domain) => domainSet.has(domain)),
208
+ );
209
+ if (overlapping.length > 0) {
210
+ return {
211
+ eligible: false,
212
+ reason: rootedSchedulerReason(
213
+ state,
214
+ "overlapping-qualification-domain",
215
+ candidate,
216
+ overlapping,
217
+ "Overlapping rooted qualification domains must be serialized.",
218
+ ),
219
+ };
220
+ }
221
+ return { eligible: true, reason: null };
222
+ }
223
+
224
+ function queuedByAge(state) {
225
+ return state.candidates
226
+ .filter((candidate) => candidate.status === "queued")
227
+ .sort(
228
+ (left, right) =>
229
+ left.enqueuedAt.localeCompare(right.enqueuedAt) ||
230
+ left.candidateId.localeCompare(right.candidateId),
231
+ );
232
+ }
233
+
234
+ export function landingEligibility(state, candidate) {
235
+ if (
236
+ candidate.qualification?.schema !==
237
+ DEV_DELIVERY_NATIVE_QUALIFICATION_SCHEMA ||
238
+ candidate.qualification?.authority !== "verified-native-qualification" ||
239
+ candidate.qualification?.nativeProofAuthority !== true
240
+ ) {
241
+ return {
242
+ eligible: false,
243
+ older: [],
244
+ reason: rootedSchedulerReason(
245
+ state,
246
+ "native-qualification-authority-required",
247
+ candidate,
248
+ [],
249
+ "Only complete verified native qualification may acquire new Landing authority.",
250
+ ),
251
+ };
252
+ }
253
+ const { maxLandingOvertakes } = schedulerPolicy(state);
254
+ const older = state.candidates.filter(
255
+ (entry) =>
256
+ !TERMINAL_STATES.has(entry.status) &&
257
+ entry.candidateId !== candidate.candidateId &&
258
+ (entry.enqueuedAt < candidate.enqueuedAt ||
259
+ (entry.enqueuedAt === candidate.enqueuedAt &&
260
+ entry.candidateId < candidate.candidateId)),
261
+ );
262
+ const exhausted = older.filter(
263
+ (entry) => (entry.landingOvertakes || 0) >= maxLandingOvertakes,
264
+ );
265
+ if (exhausted.length === 0) return { eligible: true, older, reason: null };
266
+ return {
267
+ eligible: false,
268
+ older,
269
+ reason: rootedSchedulerReason(
270
+ state,
271
+ "landing-overtake-bound-reached",
272
+ candidate,
273
+ exhausted,
274
+ `A predecessor has exhausted the configured ${maxLandingOvertakes}-landing overtake budget.`,
275
+ ),
276
+ };
277
+ }
278
+
279
+ export function schedulerWake(state) {
280
+ const available = Math.max(
281
+ 0,
282
+ state.policy.maxQualificationLeases - state.qualificationLeases.length,
283
+ );
284
+ const simulated = clone(state);
285
+ const qualificationCandidateIds = [];
286
+ const blockedReasons = [];
287
+ for (const candidate of queuedByAge(state)) {
288
+ if (qualificationCandidateIds.length >= available) break;
289
+ const eligibility = qualificationEligibility(simulated, candidate);
290
+ if (!eligibility.eligible) {
291
+ blockedReasons.push(eligibility.reason);
292
+ continue;
293
+ }
294
+ qualificationCandidateIds.push(candidate.candidateId);
295
+ simulated.candidates.find(
296
+ (entry) => entry.candidateId === candidate.candidateId,
297
+ ).status = "qualifying";
298
+ }
299
+ let landingCandidateId = null;
300
+ let landingBlockedReason = null;
301
+ if (!state.landingWarrant) {
302
+ const qualified = state.candidates
303
+ .filter((candidate) => candidate.status === "qualified")
304
+ .sort(
305
+ (left, right) =>
306
+ left.enqueuedAt.localeCompare(right.enqueuedAt) ||
307
+ left.candidateId.localeCompare(right.candidateId),
308
+ );
309
+ for (const candidate of qualified) {
310
+ const eligibility = landingEligibility(state, candidate);
311
+ if (eligibility.eligible) {
312
+ landingCandidateId = candidate.candidateId;
313
+ break;
314
+ }
315
+ landingBlockedReason ||= eligibility.reason;
316
+ }
317
+ }
318
+ const body = {
319
+ schema: DEV_DELIVERY_SCHEDULER_WAKE_SCHEMA,
320
+ stateRoot: state.stateRoot,
321
+ qualificationCandidateIds,
322
+ landingCandidateId,
323
+ blockedReasonRoots: blockedReasons.map((reason) => reason.reasonRoot),
324
+ landingBlockedReasonRoot: landingBlockedReason?.reasonRoot || null,
325
+ };
326
+ return {
327
+ ...body,
328
+ wakeRoot: devDeliveryContentRoot(body),
329
+ blockedReasons,
330
+ landingBlockedReason,
331
+ };
332
+ }
333
+
334
+ export function recoverExpiredQualificationLeases(state, now) {
335
+ const expired = state.qualificationLeases.filter(
336
+ (lease) => Date.parse(lease.expiresAt) <= Date.parse(now),
337
+ );
338
+ if (expired.length === 0) return [];
339
+ const expiredIds = new Set(expired.map((lease) => lease.candidateId));
340
+ state.qualificationLeases = state.qualificationLeases.filter(
341
+ (lease) => !expiredIds.has(lease.candidateId),
342
+ );
343
+ const { maxQualificationAttempts } = schedulerPolicy(state);
344
+ for (const candidate of state.candidates) {
345
+ if (expiredIds.has(candidate.candidateId)) {
346
+ if ((candidate.qualificationAttempts || 0) >= maxQualificationAttempts) {
347
+ const reason = rootedSchedulerReason(
348
+ state,
349
+ "qualification-heartbeat-expired-terminal",
350
+ candidate,
351
+ [],
352
+ `Qualification heartbeat expired after ${candidate.qualificationAttempts} attempts.`,
353
+ );
354
+ candidate.status = "terminal-failure";
355
+ candidate.terminal = {
356
+ outcome: "terminal-failure",
357
+ evidenceRoot: reason.reasonRoot,
358
+ reason: reason.code,
359
+ settledAt: now,
360
+ };
361
+ } else {
362
+ candidate.status = "queued";
363
+ }
364
+ candidate.updatedAt = now;
365
+ }
366
+ }
367
+ return expired;
368
+ }
369
+
370
+ export function acquireDevDeliveryQualificationLease(
371
+ stateInput,
372
+ { now = new Date().toISOString(), leaseSeconds } = {},
373
+ ) {
374
+ const currentTime = timestamp(now, "now");
375
+ const transaction = transition(
376
+ stateInput,
377
+ (state, before) => {
378
+ const recovered = recoverExpiredQualificationLeases(state, currentTime);
379
+ if (
380
+ state.qualificationLeases.length >= state.policy.maxQualificationLeases
381
+ )
382
+ return {
383
+ lease: null,
384
+ recovered,
385
+ action: "qualification-bound-full-noop",
386
+ mutated: recovered.length > 0,
387
+ };
388
+ const blockedReasons = [];
389
+ let candidate = null;
390
+ for (const queued of queuedByAge(state)) {
391
+ const eligibility = qualificationEligibility(state, queued);
392
+ if (eligibility.eligible) {
393
+ candidate = queued;
394
+ break;
395
+ }
396
+ blockedReasons.push(eligibility.reason);
397
+ }
398
+ if (!candidate) {
399
+ return {
400
+ lease: null,
401
+ recovered,
402
+ blockedReasons,
403
+ action:
404
+ blockedReasons.length > 0
405
+ ? "qualification-safety-boundary-noop"
406
+ : "no-queued-candidate-noop",
407
+ mutated: recovered.length > 0,
408
+ };
409
+ }
410
+ state.qualificationCounter += 1;
411
+ candidate.qualificationAttempts =
412
+ (candidate.qualificationAttempts || 0) + 1;
413
+ const duration = positiveInteger(
414
+ leaseSeconds,
415
+ "qualification leaseSeconds",
416
+ state.policy.qualificationLeaseSeconds,
417
+ );
418
+ const lease = {
419
+ schema: DEV_DELIVERY_QUALIFICATION_LEASE_SCHEMA,
420
+ authority: "qualification-only",
421
+ mergeGroupAdmission: false,
422
+ candidateId: candidate.candidateId,
423
+ generation: state.qualificationCounter,
424
+ issuedAt: currentTime,
425
+ heartbeatAt: currentTime,
426
+ expiresAt: new Date(
427
+ Date.parse(currentTime) + duration * 1000,
428
+ ).toISOString(),
429
+ token: "",
430
+ };
431
+ lease.token = authorityToken(
432
+ lease.schema,
433
+ before,
434
+ candidate,
435
+ lease.generation,
436
+ currentTime,
437
+ );
438
+ state.qualificationLeases.push(lease);
439
+ candidate.status = "qualifying";
440
+ candidate.updatedAt = currentTime;
441
+ return {
442
+ lease,
443
+ recovered,
444
+ candidate,
445
+ blockedReasons,
446
+ action: "qualification-lease-acquired",
447
+ };
448
+ },
449
+ currentTime,
450
+ );
451
+ const changed = receipt(transaction, transaction.result.action, {
452
+ candidateId: transaction.result.candidate?.candidateId || null,
453
+ authority: transaction.result.lease?.authority || null,
454
+ mergeGroupAdmission: false,
455
+ recoveredLeaseCount: transaction.result.recovered.length,
456
+ blockedReasons: transaction.result.blockedReasons || [],
457
+ nextAction: transaction.result.lease
458
+ ? "Run qualification only; this lease cannot admit merge_group."
459
+ : "Wait for a Qualification Lease slot or queued candidate.",
460
+ });
461
+ return {
462
+ ...changed,
463
+ lease: transaction.result.lease,
464
+ wake: schedulerWake(changed.state),
465
+ };
466
+ }
467
+
468
+ export function heartbeatDevDeliveryQualificationLease(
469
+ stateInput,
470
+ leaseInput,
471
+ { now = new Date().toISOString(), leaseSeconds } = {},
472
+ ) {
473
+ const currentTime = timestamp(now, "now");
474
+ const transaction = transition(
475
+ stateInput,
476
+ (state, before) => {
477
+ const lease = assertQualificationLease(before, leaseInput, currentTime);
478
+ if (lease.heartbeatAt === currentTime)
479
+ return {
480
+ lease,
481
+ action: "duplicate-qualification-heartbeat-noop",
482
+ mutated: false,
483
+ };
484
+ const active = state.qualificationLeases.find(
485
+ (entry) => entry.candidateId === lease.candidateId,
486
+ );
487
+ const duration = positiveInteger(
488
+ leaseSeconds,
489
+ "qualification leaseSeconds",
490
+ state.policy.qualificationLeaseSeconds,
491
+ );
492
+ active.heartbeatAt = currentTime;
493
+ active.expiresAt = new Date(
494
+ Date.parse(currentTime) + duration * 1000,
495
+ ).toISOString();
496
+ return { lease: active, action: "qualification-heartbeat" };
497
+ },
498
+ currentTime,
499
+ );
500
+ const changed = receipt(transaction, transaction.result.action, {
501
+ candidateId: transaction.result.lease.candidateId,
502
+ authorityGeneration: transaction.result.lease.generation,
503
+ expiresAt: transaction.result.lease.expiresAt,
504
+ nextAction:
505
+ "Continue qualification while the fenced lease remains current.",
506
+ });
507
+ return {
508
+ ...changed,
509
+ lease: transaction.result.lease,
510
+ wake: schedulerWake(changed.state),
511
+ };
512
+ }
513
+
514
+ function assertQualificationLease(state, input, now) {
515
+ const token = exactRoot(input?.token, "qualification lease token");
516
+ const generation = positiveInteger(
517
+ input?.generation,
518
+ "qualification lease generation",
519
+ );
520
+ const candidateId = exactRoot(input?.candidateId, "candidateId");
521
+ const lease = state.qualificationLeases.find(
522
+ (entry) => entry.candidateId === candidateId,
523
+ );
524
+ if (!lease || lease.token !== token)
525
+ throw new Error("stale qualification lease token");
526
+ if (lease.generation !== generation)
527
+ throw new Error("stale qualification lease generation");
528
+ if (Date.parse(lease.expiresAt) <= Date.parse(now))
529
+ throw new Error("Qualification Lease expired");
530
+ return lease;
531
+ }
532
+
533
+ export function completeDevDeliveryQualification(
534
+ stateInput,
535
+ leaseInput,
536
+ {
537
+ sourceProof,
538
+ nativeProof,
539
+ qualificationContract,
540
+ now = new Date().toISOString(),
541
+ } = {},
542
+ ) {
543
+ const currentTime = timestamp(now, "now");
544
+ const transaction = transition(
545
+ stateInput,
546
+ (state, before) => {
547
+ const lease = assertQualificationLease(before, leaseInput, currentTime);
548
+ const candidate = state.candidates.find(
549
+ (entry) => entry.candidateId === lease.candidateId,
550
+ );
551
+ const evidence = verifyDevDeliveryQualificationEvidence({
552
+ state: before,
553
+ candidate,
554
+ lease,
555
+ sourceProof,
556
+ nativeProof,
557
+ qualificationContract,
558
+ });
559
+ const qualificationReceipt = createDevDeliveryBoundedQualificationReceipt(
560
+ {
561
+ state: before,
562
+ candidate,
563
+ lease,
564
+ evidence,
565
+ qualifiedAt: currentTime,
566
+ },
567
+ );
568
+ state.qualificationLeases = state.qualificationLeases.filter(
569
+ (entry) => entry.candidateId !== lease.candidateId,
570
+ );
571
+ candidate.status = "qualified";
572
+ candidate.qualification = {
573
+ schema: DEV_DELIVERY_NATIVE_QUALIFICATION_SCHEMA,
574
+ authority: "verified-native-qualification",
575
+ nativeProofAuthority: true,
576
+ ...evidence,
577
+ qualificationReceiptRoot: qualificationReceipt.receiptRoot,
578
+ qualifiedAt: currentTime,
579
+ };
580
+ candidate.updatedAt = currentTime;
581
+ return { lease, candidate, qualificationReceipt };
582
+ },
583
+ currentTime,
584
+ );
585
+ return {
586
+ state: transaction.after,
587
+ receipt: transaction.result.qualificationReceipt.receipt,
588
+ receiptRoot: transaction.result.qualificationReceipt.receiptRoot,
589
+ wake: schedulerWake(transaction.after),
590
+ };
591
+ }