@kungfu-tech/buildchain 4.0.1-alpha.4 → 4.0.1-alpha.41

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 (172) 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/internal-capabilities.json +117 -8
  5. package/architecture/maintainability-policy.json +43 -25
  6. package/architecture/release-tail-contract-inventory.json +5 -2
  7. package/architecture/v3-core-mechanism-inventory.json +89 -0
  8. package/architecture/v4-adopter-delivery-parity.json +32 -5
  9. package/architecture/v4-capability-state-machine-manifest.json +73 -0
  10. package/architecture/v4-compatibility-facts-parity.json +77 -0
  11. package/architecture/v4-floating-consumer-policy.json +6 -0
  12. package/architecture/v4-next-development-parity.json +192 -0
  13. package/architecture/v4-publication-rehearsal-parity.json +84 -0
  14. package/architecture/v4-release-train-parity.json +1 -0
  15. package/architecture/v4-tail-reseal-parity.json +150 -0
  16. package/bin/buildchain.mjs +23 -2
  17. package/bin/internal/adopter-delivery-cli.mjs +70 -0
  18. package/bin/internal/command-registry.mjs +3 -0
  19. package/bin/internal/compatibility-facts-cli.mjs +101 -0
  20. package/contracts/fixtures/next-development-transition-v1/anchored-manual-waiting.json +47 -0
  21. package/contracts/fixtures/next-development-transition-v1/semver-auto-planned.json +47 -0
  22. package/contracts/fixtures/next-development-transition-v1/version-model-cases.json +40 -0
  23. package/contracts/fixtures/v4-adopter-delivery-v1/archive-template.json +19 -0
  24. package/contracts/fixtures/v4-adopter-delivery-v1/bootstrap-positive.json +93 -0
  25. package/contracts/fixtures/v4-adopter-delivery-v1/gate-positive.json +29 -0
  26. package/contracts/fixtures/v4-adopter-delivery-v1/gate-unknown-selector.json +26 -0
  27. package/contracts/fixtures/v4-adopter-delivery-v1/offline-vectors.json +50 -0
  28. package/contracts/fixtures/v4-compatibility-facts-v1/shared.json +1129 -0
  29. package/contracts/fixtures/v4-floating-consumer-policy-v1/cases.json +7 -1
  30. package/contracts/fixtures/v4-publication-rehearsal-v1/candidate/artifacts/product.bin +1 -0
  31. package/contracts/fixtures/v4-publication-rehearsal-v1/candidate/config/buildchain.toml +5 -0
  32. package/contracts/fixtures/v4-publication-rehearsal-v1/candidate/documents/release-activation.json +1 -0
  33. package/contracts/fixtures/v4-publication-rehearsal-v1/candidate/documents/signed-channel.json +1 -0
  34. package/contracts/fixtures/v4-publication-rehearsal-v1/candidate/evidence/qualification.json +1 -0
  35. package/contracts/fixtures/v4-publication-rehearsal-v1/candidate/manifests/candidate.json +5 -0
  36. package/contracts/fixtures/v4-publication-rehearsal-v1/candidate/manifests/release-passport.json +1 -0
  37. package/contracts/fixtures/v4-publication-rehearsal-v1/capsule.json +707 -0
  38. package/contracts/fixtures/v4-publication-rehearsal-v1/offline-vectors.json +27 -0
  39. package/contracts/fixtures/v4-tail-reseal-v1/valid.json +229 -0
  40. package/contracts/next-development-request-v1.schema.json +66 -0
  41. package/contracts/next-development-transition-v1.schema.json +292 -0
  42. package/contracts/v4-adopter-delivery-v1.schema.json +81 -0
  43. package/contracts/v4-compatibility-facts-v1.schema.json +230 -0
  44. package/contracts/v4-floating-consumer-policy-receipt-v1.schema.json +4 -2
  45. package/contracts/v4-publication-rehearsal-capsule-v1.schema.json +233 -0
  46. package/contracts/v4-tail-reseal-v1.schema.json +276 -0
  47. package/dist/site/agent-index.json +4 -0
  48. package/dist/site/artifact-schemas.json +8 -0
  49. package/dist/site/buildchain-contract.json +1393 -28
  50. package/dist/site/buildchain-site.json +477 -32
  51. package/dist/site/capability-registry.json +17 -14
  52. package/dist/site/cli-registry.json +159 -1
  53. package/dist/site/kfd-claims.json +455 -15
  54. package/dist/site/kfd-upstream-aggregate.json +1 -1
  55. package/dist/site/manual-registry.json +53 -7
  56. package/dist/site/node-api-registry.json +14008 -9602
  57. package/dist/site/page-registry.json +442 -21
  58. package/dist/site/public-surface-audit.json +440 -11
  59. package/dist/site/publication-authority-registry.json +44 -8
  60. package/dist/site/publication-registry.json +4 -4
  61. package/dist/site/release-provenance.json +22 -0
  62. package/dist/site/schemas/release-tail-capabilities-v1.schema.json +199 -0
  63. package/dist/site/schemas/v4-adopter-delivery-v1.schema.json +81 -0
  64. package/dist/site/schemas/v4-compatibility-facts-v1.schema.json +230 -0
  65. package/dist/site/schemas/v4-publication-rehearsal-capsule-v1.schema.json +233 -0
  66. package/dist/site/site-manifest.json +35 -11
  67. package/dist/site/workflow-registry.json +142 -8
  68. package/docs/MAP.md +7 -2
  69. package/docs/cli-reference.md +193 -0
  70. package/docs/cli.md +8 -0
  71. package/docs/dev-alpha-candidate-patrol.md +8 -0
  72. package/docs/dev-delivery-warrant.md +22 -1
  73. package/docs/next-development-transition.md +118 -0
  74. package/docs/node-api-reference.md +583 -288
  75. package/docs/release-candidate.md +7 -0
  76. package/docs/release-tail-provider-plane.md +30 -0
  77. package/docs/v4-adopter-delivery.md +133 -0
  78. package/docs/v4-compatibility-facts.md +91 -0
  79. package/docs/v4-publication-rehearsal.md +130 -0
  80. package/docs/v4-tail-reseal.md +73 -0
  81. package/docs/versioning.md +4 -3
  82. package/package.json +30 -6
  83. package/packages/core/adopter-delivery-passport.js +259 -0
  84. package/packages/core/adopter-delivery-vectors.js +158 -0
  85. package/packages/core/adopter-delivery-vectors.json +77 -0
  86. package/packages/core/buildchain-agent-manuals.js +3 -0
  87. package/packages/core/buildchain-compatibility-authority.js +336 -0
  88. package/packages/core/buildchain-compatibility-fact.js +394 -0
  89. package/packages/core/buildchain-compatibility-facts.json +217 -0
  90. package/packages/core/buildchain-compatibility-proof.js +566 -0
  91. package/packages/core/buildchain-config.js +84 -0
  92. package/packages/core/buildchain-contract.js +93 -141
  93. package/packages/core/buildchain-delivery-bootstrap.js +240 -0
  94. package/packages/core/buildchain-delivery-infrastructure.js +164 -0
  95. package/packages/core/buildchain-delivery-self-dogfood.js +395 -0
  96. package/packages/core/buildchain-publication-authority.js +1 -0
  97. package/packages/core/channel-candidate.js +8 -0
  98. package/packages/core/channel-promotion-baseline.js +55 -0
  99. package/packages/core/ci-lane-change-budget.js +247 -0
  100. package/packages/core/dev-alpha-candidate-selection.js +10 -2
  101. package/packages/core/dev-delivery-execution-transfer.js +21 -1
  102. package/packages/core/dev-delivery-native-proof.js +43 -27
  103. package/packages/core/dev-delivery-process-boundary.js +19 -6
  104. package/packages/core/dev-delivery-provider-heartbeat.js +18 -6
  105. package/packages/core/dev-delivery-warrant-settlement.js +234 -2
  106. package/packages/core/dev-delivery-warrant-state.js +46 -10
  107. package/packages/core/dev-delivery-warrant.js +16 -3
  108. package/packages/core/github-governance-authority.js +1 -0
  109. package/packages/core/index.js +5 -0
  110. package/packages/core/kungfu-temporal-fact.js +557 -0
  111. package/packages/core/legacy-kfd-adopter-driver.js +167 -0
  112. package/packages/core/next-development-candidate-reservation.js +186 -0
  113. package/packages/core/next-development-controller.js +728 -0
  114. package/packages/core/next-development-projection.js +287 -0
  115. package/packages/core/next-development-transition.js +738 -0
  116. package/packages/core/paper-agent-entry.js +11 -5
  117. package/packages/core/paper.js +16 -5
  118. package/packages/core/published-delivery-authority.js +266 -0
  119. package/packages/core/release-candidate-recovery.js +70 -15
  120. package/packages/core/release-candidate.js +2 -2
  121. package/packages/core/release-passport.js +36 -9
  122. package/packages/core/v4-adopter-delivery-parity.js +3 -3
  123. package/packages/core/v4-adopter-delivery.js +359 -0
  124. package/packages/core/v4-canonical-contracts.js +5 -0
  125. package/packages/core/v4-delivery-warrant-read-candidate.js +10 -3
  126. package/packages/core/v4-floating-consumer-evidence.js +18 -4
  127. package/packages/core/v4-floating-consumer-policy.js +143 -20
  128. package/packages/core/v4-floating-consumer-release-passport.js +35 -8
  129. package/packages/core/v4-publication-rehearsal-capsule.js +468 -0
  130. package/packages/core/v4-publication-rehearsal-provider-bindings.js +212 -0
  131. package/packages/core/v4-publication-rehearsal.js +481 -0
  132. package/packages/core/v4-tail-reseal-contract.js +26 -0
  133. package/packages/core/v4-tail-reseal-github.js +178 -0
  134. package/packages/core/v4-tail-reseal-receipt.js +225 -0
  135. package/packages/core/v4-tail-reseal.js +594 -0
  136. package/packages/core/workflow-yaml-contract.js +24 -2
  137. package/scripts/artifact-signing-controller-core.mjs +1 -1
  138. package/scripts/buildchain-cli-help.mjs +30 -0
  139. package/scripts/check-ci-lane-change-budget.mjs +100 -0
  140. package/scripts/check-inventory.mjs +14 -3
  141. package/scripts/check-project-cut-merge-queue-admission.mjs +87 -0
  142. package/scripts/check-v4-floating-consumer-policy-contract.mjs +23 -4
  143. package/scripts/dev-alpha-candidate-patrol.mjs +22 -1
  144. package/scripts/dev-delivery-source-proof-replay.mjs +153 -0
  145. package/scripts/dev-delivery-source-proof-reuse.mjs +13 -97
  146. package/scripts/dev-delivery-warrant-options.mjs +27 -11
  147. package/scripts/dev-delivery-warrant-store.mjs +10 -6
  148. package/scripts/dev-delivery-warrant.mjs +50 -6
  149. package/scripts/dev-pr-auto-merge.mjs +25 -11
  150. package/scripts/dev-pr-delivery-warrant.mjs +45 -0
  151. package/scripts/dev-pr-prequeue-guard.mjs +0 -18
  152. package/scripts/dev-qualification-patrol.mjs +9 -9
  153. package/scripts/dispatch-artifact-signing-authority.mjs +1 -1
  154. package/scripts/generate-channel-promotion-workflow.mjs +2 -2
  155. package/scripts/generate-next-development-guidance.mjs +49 -0
  156. package/scripts/generate-release-candidate-passport.mjs +152 -0
  157. package/scripts/generate-site-bundle.mjs +46 -3
  158. package/scripts/init-repo.mjs +22 -4
  159. package/scripts/next-development-self-dogfood-harness.mjs +460 -0
  160. package/scripts/next-development-self-dogfood.mjs +597 -0
  161. package/scripts/next-development-transition.mjs +47 -0
  162. package/scripts/project-cut-merge-queue-admission.mjs +394 -0
  163. package/scripts/promotion-channel-router.mjs +4 -4
  164. package/scripts/release-candidate-anchor-provenance.mjs +99 -0
  165. package/scripts/release-tail.mjs +51 -3
  166. package/scripts/resume-from-candidate-run.mjs +595 -65
  167. package/scripts/site-capability-metadata.mjs +29 -0
  168. package/scripts/stable-candidate-qualification.mjs +9 -10
  169. package/scripts/v4-consumer-policy.mjs +5 -5
  170. package/scripts/v4-publication-rehearsal-fixture.mjs +144 -0
  171. package/scripts/v4-tail-reseal-macos-rehearsal.mjs +134 -0
  172. package/scripts/v4-tail-reseal.mjs +416 -0
@@ -11,6 +11,7 @@ import {
11
11
  verifySourceQualificationProof,
12
12
  } from "../packages/core/dev-delivery-warrant.js";
13
13
  import { validateControllerReceipt } from "../packages/core/controller-evidence.js";
14
+ import { exactMergeGroupBinding } from "./dev-delivery-source-proof-replay.mjs";
14
15
 
15
16
  const REUSE_DECISION_SCHEMA =
16
17
  "kungfu.buildchain.source-qualification-reuse-decision/v1";
@@ -113,98 +114,6 @@ function gitPathRoot(ref, paths, label, cwd) {
113
114
  });
114
115
  }
115
116
 
116
- function exactMergeGroupBinding(input = {}) {
117
- const cwd = path.resolve(input.cwd || process.cwd());
118
- const mergeGroupHead = exactSha(input.mergeGroupHead, "mergeGroupHead");
119
- const expectedTree = exactSha(input.mergeGroupTree, "mergeGroupTree");
120
- const currentBase = exactSha(input.currentBase, "currentBase");
121
- const sourceHead = exactSha(input.sourceHead, "sourceHead");
122
- const actualTree = exactSha(
123
- git(["rev-parse", `${mergeGroupHead}^{tree}`], { cwd }),
124
- "mergeGroupTree",
125
- );
126
- if (actualTree !== expectedTree)
127
- return { ok: false, reason: "merge-group-tree-mismatch" };
128
- const [commit, ...parents] = git(
129
- ["rev-list", "--parents", "-n", "1", mergeGroupHead],
130
- { cwd },
131
- ).split(/\s+/u);
132
- if (commit !== mergeGroupHead) {
133
- return { ok: false, reason: "merge-group-parent-mismatch" };
134
- }
135
- if (
136
- parents.length === 2 &&
137
- parents[0] === currentBase &&
138
- parents[1] === sourceHead
139
- ) {
140
- return {
141
- ok: true,
142
- mergeGroupHead,
143
- mergeGroupTree: actualTree,
144
- parents,
145
- compositionMode: "two-parent-merge",
146
- replayedCommitTrees: [],
147
- };
148
- }
149
-
150
- const linearRange = (head) => {
151
- try {
152
- git(["merge-base", "--is-ancestor", currentBase, head], { cwd });
153
- } catch {
154
- return null;
155
- }
156
- const commits = git(
157
- ["rev-list", "--reverse", "--topo-order", `${currentBase}..${head}`],
158
- { cwd },
159
- )
160
- .split(/\s+/u)
161
- .filter(Boolean);
162
- if (commits.length === 0) return null;
163
- let expectedParent = currentBase;
164
- const trees = [];
165
- for (const rangeCommit of commits) {
166
- const [observedCommit, ...rangeParents] = git(
167
- ["rev-list", "--parents", "-n", "1", rangeCommit],
168
- { cwd },
169
- ).split(/\s+/u);
170
- if (
171
- observedCommit !== rangeCommit ||
172
- rangeParents.length !== 1 ||
173
- rangeParents[0] !== expectedParent
174
- ) {
175
- return null;
176
- }
177
- trees.push(
178
- exactSha(
179
- git(["rev-parse", `${rangeCommit}^{tree}`], { cwd }),
180
- "replayedCommitTree",
181
- ),
182
- );
183
- expectedParent = rangeCommit;
184
- }
185
- return trees;
186
- };
187
-
188
- const sourceTrees = linearRange(sourceHead);
189
- const mergeGroupTrees = linearRange(mergeGroupHead);
190
- if (
191
- !sourceTrees ||
192
- !mergeGroupTrees ||
193
- sourceTrees.length !== mergeGroupTrees.length ||
194
- sourceTrees.some((tree, index) => tree !== mergeGroupTrees[index])
195
- ) {
196
- return { ok: false, reason: "merge-group-parent-mismatch" };
197
- }
198
- return {
199
- ok: true,
200
- mergeGroupHead,
201
- mergeGroupTree: actualTree,
202
- parents,
203
- compositionMode: "linear-replay",
204
- replayedCommitTrees: mergeGroupTrees,
205
- };
206
- }
207
-
208
117
  export function sourceQualificationPredicates(input = {}) {
209
118
  const cwd = path.resolve(input.cwd || process.cwd());
210
119
  const repository = required(input.repository, "repository");
@@ -397,7 +306,8 @@ export function materializeReuseLifecycleEvidence({
397
306
  decisionRoot: decision.decisionRoot,
398
307
  sourceWorkflowRunId: decision.sourceWorkflowRunId,
399
308
  sourceHead: decision.sourceHead,
400
- qualifiedBase: decision.currentBase,
309
+ qualifiedBase: decision.qualifiedBase,
310
+ currentBase: decision.currentBase,
401
311
  mergeGroupHead: decision.mergeGroupHead,
402
312
  mergeGroupTree: decision.mergeGroupTree,
403
313
  },
@@ -446,7 +356,7 @@ export function verifySourceQualificationReuse(input = {}) {
446
356
  const verification = verifySourceQualificationProof(proof, {
447
357
  repository: required(input.repository, "repository"),
448
358
  protectedBase: required(input.protectedBase, "protectedBase"),
449
- qualifiedBase: exactSha(input.currentBase, "currentBase"),
359
+ qualifiedBase: exactSha(proof.qualifiedBase, "qualifiedBase"),
450
360
  sourceHead: exactSha(input.sourceHead, "sourceHead"),
451
361
  sourceWorkflowRunId: Number(input.sourceWorkflowRunId),
452
362
  controllerReceiptRoot: receipt.digest,
@@ -462,7 +372,7 @@ export function verifySourceQualificationReuse(input = {}) {
462
372
  }
463
373
  const current = sourceQualificationPredicates({
464
374
  ...input,
465
- qualifiedBase: input.currentBase,
375
+ qualifiedBase: proof.qualifiedBase,
466
376
  });
467
377
  const classification = classifyDevDeliveryDelta({
468
378
  proof,
@@ -476,7 +386,11 @@ export function verifySourceQualificationReuse(input = {}) {
476
386
  reason: classification.reason,
477
387
  });
478
388
  }
479
- const mergeGroup = exactMergeGroupBinding(input);
389
+ const mergeGroup = exactMergeGroupBinding({
390
+ ...input,
391
+ qualifiedBase: proof.qualifiedBase,
392
+ sourcePatchRoot: proof.sourcePatchRoot,
393
+ });
480
394
  if (!mergeGroup.ok) {
481
395
  return rootedDecision({
482
396
  schema: REUSE_DECISION_SCHEMA,
@@ -493,12 +407,14 @@ export function verifySourceQualificationReuse(input = {}) {
493
407
  sourceProofRoot: proof.proofRoot,
494
408
  sourceWorkflowRunId: proof.sourceWorkflowRunId,
495
409
  sourceHead: proof.sourceHead,
496
- currentBase: proof.qualifiedBase,
410
+ qualifiedBase: proof.qualifiedBase,
411
+ currentBase: exactSha(input.currentBase, "currentBase"),
497
412
  mergeGroupHead: mergeGroup.mergeGroupHead,
498
413
  mergeGroupTree: mergeGroup.mergeGroupTree,
499
414
  mergeGroupParents: mergeGroup.parents,
500
415
  mergeGroupCompositionMode: mergeGroup.compositionMode,
501
416
  mergeGroupReplayTrees: mergeGroup.replayedCommitTrees,
417
+ mergeGroupReplayPatchRoots: mergeGroup.replayedCommitPatchRoots,
502
418
  predicateRoots: {
503
419
  sourceIdentityRoot: proof.sourceIdentityRoot,
504
420
  sourcePatchRoot: proof.sourcePatchRoot,
@@ -7,6 +7,32 @@ export function hasFlag(args, name) {
7
7
  return args.includes(`--${name}`);
8
8
  }
9
9
 
10
+ function terminalEvidenceOptions(rest, environment) {
11
+ return {
12
+ outcome: flag(rest, "outcome", environment.BUILDCHAIN_DEV_DELIVERY_OUTCOME),
13
+ eventAction: flag(
14
+ rest,
15
+ "event-action",
16
+ environment.BUILDCHAIN_DEV_DELIVERY_EVENT_ACTION,
17
+ ),
18
+ evidenceRoot: flag(
19
+ rest,
20
+ "evidence-root",
21
+ environment.BUILDCHAIN_DEV_DELIVERY_EVIDENCE_ROOT,
22
+ ),
23
+ expectedPriorEvidenceRoot: flag(
24
+ rest,
25
+ "expected-prior-evidence-root",
26
+ environment.BUILDCHAIN_DEV_DELIVERY_EXPECTED_PRIOR_EVIDENCE_ROOT,
27
+ ),
28
+ integrationProofPath: flag(
29
+ rest,
30
+ "integration-proof",
31
+ environment.BUILDCHAIN_DEV_DELIVERY_INTEGRATION_PROOF,
32
+ ),
33
+ };
34
+ }
35
+
10
36
  export function devDeliveryCliOptions(args = [], environment = process.env) {
11
37
  const [command = "", ...rest] = args;
12
38
  return {
@@ -196,17 +222,7 @@ export function devDeliveryCliOptions(args = [], environment = process.env) {
196
222
  "lease-seconds",
197
223
  environment.BUILDCHAIN_DEV_DELIVERY_LEASE_SECONDS,
198
224
  ),
199
- outcome: flag(rest, "outcome", environment.BUILDCHAIN_DEV_DELIVERY_OUTCOME),
200
- eventAction: flag(
201
- rest,
202
- "event-action",
203
- environment.BUILDCHAIN_DEV_DELIVERY_EVENT_ACTION,
204
- ),
205
- evidenceRoot: flag(
206
- rest,
207
- "evidence-root",
208
- environment.BUILDCHAIN_DEV_DELIVERY_EVIDENCE_ROOT,
209
- ),
225
+ ...terminalEvidenceOptions(rest, environment),
210
226
  transferRoot: flag(rest, "transfer-root"),
211
227
  finalizerBoundaryRoot: flag(rest, "finalizer-boundary-root"),
212
228
  nativeJobId: flag(rest, "native-job-id"),
@@ -46,7 +46,7 @@ function decodeBlob(blob) {
46
46
  ).toString("utf8");
47
47
  }
48
48
 
49
- function validateStoredState(queue) {
49
+ function validateStoredState(queue, { allowLegacyV3Readback = false } = {}) {
50
50
  const body = structuredClone(queue || {});
51
51
  const embeddedStateRoot = body.stateRoot;
52
52
  delete body.stateRoot;
@@ -59,7 +59,7 @@ function validateStoredState(queue) {
59
59
  ) {
60
60
  normalizeDevDeliveryAuthorityState(queue);
61
61
  } else {
62
- normalizeDevDeliveryQueue(queue);
62
+ normalizeDevDeliveryQueue(queue, { allowLegacyV3Readback });
63
63
  }
64
64
  return queue;
65
65
  }
@@ -108,7 +108,7 @@ export class GitHubDevDeliveryStore {
108
108
  return data;
109
109
  }
110
110
 
111
- async read({ stateRef, protectedBase, now }) {
111
+ async read({ stateRef, protectedBase, now, allowLegacyV3Readback = false }) {
112
112
  let ref;
113
113
  try {
114
114
  ref = await this.request(
@@ -128,11 +128,13 @@ export class GitHubDevDeliveryStore {
128
128
  };
129
129
  }
130
130
  const commitSha = exactSha(ref?.object?.sha, "state ref commit");
131
- const readback = await this.readCommit(commitSha);
131
+ const readback = await this.readCommit(commitSha, {
132
+ allowLegacyV3Readback,
133
+ });
132
134
  return { exists: true, ...readback };
133
135
  }
134
136
 
135
- async readCommit(commitShaInput) {
137
+ async readCommit(commitShaInput, { allowLegacyV3Readback = false } = {}) {
136
138
  const commitSha = exactSha(commitShaInput, "state commit");
137
139
  const commit = await this.request(
138
140
  "GET",
@@ -152,7 +154,9 @@ export class GitHubDevDeliveryStore {
152
154
  `/repos/${this.repository.fullName}/git/blobs/${entry.sha}`,
153
155
  );
154
156
  const bytes = decodeBlob(blob);
155
- const queue = validateStoredState(JSON.parse(bytes));
157
+ const queue = validateStoredState(JSON.parse(bytes), {
158
+ allowLegacyV3Readback,
159
+ });
156
160
  return { commitSha, queue, bytes };
157
161
  }
158
162
 
@@ -10,6 +10,7 @@ import {
10
10
  observeDevDeliveryQueue,
11
11
  qualifyDevDeliveryWarrant,
12
12
  recoverExpiredDevDeliveryWarrant,
13
+ reconcileDevDeliveryTerminalEvidence,
13
14
  selectDevDeliveryWarrant,
14
15
  settleDevDeliveryTerminalEvent,
15
16
  submitDevDeliveryCandidate,
@@ -52,6 +53,43 @@ function exactSha(value, label) {
52
53
  return normalized;
53
54
  }
54
55
 
56
+ function terminalSourceHead(options) {
57
+ return exactSha(
58
+ options.expectedSourceHead || options.sourceHead,
59
+ "sourceHead",
60
+ );
61
+ }
62
+
63
+ function reconcileTerminalEvidenceCommand(queue, options) {
64
+ return reconcileDevDeliveryTerminalEvidence(
65
+ queue,
66
+ {
67
+ candidateId: exactRoot(options.candidateId, "candidateId"),
68
+ expectedPriorEvidenceRoot: exactRoot(
69
+ options.expectedPriorEvidenceRoot,
70
+ "expectedPriorEvidenceRoot",
71
+ ),
72
+ integrationProof:
73
+ options.integrationProof ||
74
+ jsonFile(options.integrationProofPath, "integration proof"),
75
+ reason: options.reason,
76
+ },
77
+ { now: options.now },
78
+ );
79
+ }
80
+
81
+ function requireTerminalEvidenceCas(options) {
82
+ if (
83
+ options.command === "reconcile-terminal-evidence" &&
84
+ options.execute &&
85
+ !options.expectedOldStateRoot
86
+ ) {
87
+ throw new Error(
88
+ "terminal evidence reconciliation execute requires expected-old CAS",
89
+ );
90
+ }
91
+ }
92
+
55
93
  function normalizeRepository(value) {
56
94
  const normalized = text(value);
57
95
  const match = normalized.match(/^([^/\s]+)\/([^/\s]+)$/);
@@ -245,10 +283,7 @@ function transitionFor(command, queue, options) {
245
283
  options.pullRequestNumber,
246
284
  "pullRequestNumber",
247
285
  ),
248
- sourceHead: exactSha(
249
- options.expectedSourceHead || options.sourceHead,
250
- "sourceHead",
251
- ),
286
+ sourceHead: terminalSourceHead(options),
252
287
  fencingToken: options.fencingToken,
253
288
  leaseGeneration: options.leaseGeneration,
254
289
  outcome: options.outcome,
@@ -263,6 +298,9 @@ function transitionFor(command, queue, options) {
263
298
  { now: options.now },
264
299
  );
265
300
  }
301
+ if (command === "reconcile-terminal-evidence") {
302
+ return reconcileTerminalEvidenceCommand(queue, options);
303
+ }
266
304
  if (command === "cancel-queued") {
267
305
  return cancelQueuedDevDeliveryCandidate(
268
306
  queue,
@@ -295,7 +333,10 @@ async function observeQueue(loaded, options) {
295
333
  const readMode = text(options.readMode || "v3").toLowerCase();
296
334
  if (!["v3", "v4"].includes(readMode))
297
335
  throw new Error("readMode must be v3 or v4");
298
- let observation = observeDevDeliveryQueue(loaded.queue, { now: options.now });
336
+ let observation = observeDevDeliveryQueue(loaded.queue, {
337
+ now: options.now,
338
+ allowLegacyV3Readback: true,
339
+ });
299
340
  let readCandidate;
300
341
  if (readMode === "v4") {
301
342
  const qualification =
@@ -353,6 +394,7 @@ export async function runDevDeliveryCommand(optionsInput = {}, clientInput) {
353
394
  now: new Date(optionsInput.now || Date.now()).toISOString(),
354
395
  execute: bool(optionsInput.execute, false),
355
396
  };
397
+ requireTerminalEvidenceCas(options);
356
398
  if (
357
399
  options.command === "submit" &&
358
400
  options.execute &&
@@ -374,6 +416,7 @@ export async function runDevDeliveryCommand(optionsInput = {}, clientInput) {
374
416
  stateRef: options.stateRef,
375
417
  protectedBase: options.branch,
376
418
  now: options.now,
419
+ allowLegacyV3Readback: options.command === "observe",
377
420
  });
378
421
  if (
379
422
  options.expectedOldStateRoot &&
@@ -453,7 +496,7 @@ export async function runDevDeliveryCommand(optionsInput = {}, clientInput) {
453
496
  }
454
497
 
455
498
  function usage() {
456
- return "Usage:\n buildchain dev warrant <submit|select|heartbeat|qualify|recover|close|settle|cancel-queued|observe> --repository owner/repo --branch dev/vN/vN.M [--execute] [--output FILE] [--json]\n\nRead candidate:\n observe --read-mode v4 --read-qualification FILE --read-qualification-root sha256:... --read-typescript-revision SHA --read-rust-revision SHA --read-validator-version TOKEN [--read-evidence-output FILE]\n";
499
+ return "Usage:\n buildchain dev warrant <submit|select|heartbeat|qualify|recover|close|settle|reconcile-terminal-evidence|cancel-queued|observe> --repository owner/repo --branch dev/vN/vN.M [--execute] [--output FILE] [--json]\n\nRead candidate:\n observe --read-mode v4 --read-qualification FILE --read-qualification-root sha256:... --read-typescript-revision SHA --read-rust-revision SHA --read-validator-version TOKEN [--read-evidence-output FILE]\n";
457
500
  }
458
501
 
459
502
  async function main() {
@@ -472,6 +515,7 @@ async function main() {
472
515
  "recover",
473
516
  "close",
474
517
  "settle",
518
+ "reconcile-terminal-evidence",
475
519
  "cancel-queued",
476
520
  "observe",
477
521
  ].includes(options.command)
@@ -3,7 +3,7 @@ import crypto from "node:crypto";
3
3
  import { spawnSync } from "node:child_process";
4
4
  import fs from "node:fs";
5
5
  import path from "node:path";
6
- import { admitExistingQueueEntry, createDevPrAdmissionReceipt, qualifyAtomicQueueAdmission, readDeliveryWarrantResult, runSourceQualification, runTargetedQueueAdmission, setActiveLeaseStatus, verifyCurrentDeliveryWarrant } from "./dev-pr-delivery-warrant.mjs";
6
+ import { admitExistingQueueEntry, createDevPrAdmissionReceipt, createPreReadinessQueueFence, qualifyAtomicQueueAdmission, readDeliveryWarrantResult, runSourceQualification, runTargetedQueueAdmission, setActiveLeaseStatus, setQueueAdmissionStatus, verifyCurrentDeliveryWarrant } from "./dev-pr-delivery-warrant.mjs";
7
7
  import { projectCutQualification } from "./dev-pr-prequeue-guard.mjs";
8
8
  const DEFAULT_BLOCK_LABELS = ["blocked", "do-not-merge", "work-in-progress"];
9
9
  const DEFAULT_ALLOWED_HEAD_PREFIXES = ["feature/", "fix/", "chore/", "docs/", "ci/", "refactor/"];
@@ -197,12 +197,6 @@ function mergeableAccepted(pr, landingMode = "direct", projectCutQualified = fal
197
197
  function rejectBaseMoveBeforeAtomicReplay(options, initialBaseSha, observedBaseSha) {
198
198
  return observedBaseSha !== initialBaseSha && options.warrantMode !== "required";
199
199
  }
200
- async function setQueueAdmissionStatus(client, repository, sha, context, state) {
201
- if (!context) return null;
202
- await client.request("POST", `/repos/${repository.owner}/${repository.repo}/statuses/${sha}`, { body: { state, context, description: state === "success" ? "Buildchain admitted this exact PR head to the merge queue" : "Buildchain rejected merge queue admission for this exact PR head" } });
203
- return { context, state, sha };
204
- }
205
-
206
200
  function skip(reason, details = {}) {
207
201
  return { action: "skip", reason, ...details };
208
202
  }
@@ -790,8 +784,10 @@ export async function runDevPrAdmission(optionsInput = {}, clientInput) {
790
784
  return targetedFailure({ options, pr: {}, state: "missing", reason: "pull-request-not-found" });
791
785
  }
792
786
 
787
+ const readinessFence = createPreReadinessQueueFence({ client, options, sleep: delay });
793
788
  const reject = async (state, reason, readiness = { observed: hasReadyLabel(pr, options.readyLabel), established: false }) => {
794
789
  const result = targetedFailure({ options, pr, state, reason, readiness });
790
+ await readinessFence.finish(result);
795
791
  if (!options.dryRun) result.diagnostic = await publishAdmissionDiagnostic(client, options, result.receipt, result.receiptRoot);
796
792
  return result;
797
793
  };
@@ -803,6 +799,12 @@ export async function runDevPrAdmission(optionsInput = {}, clientInput) {
803
799
  if (pr.draft) return reject("blocked", "draft");
804
800
  if (!headPrefixAllowed(pr, options.allowedHeadPrefixes)) return reject("blocked", "head-prefix-not-allowed");
805
801
 
802
+ try {
803
+ await readinessFence.establish();
804
+ } catch (error) {
805
+ return reject("blocked", error.code || "pre-readiness-queue-fence-failed");
806
+ }
807
+
806
808
  let readiness = { observed: hasReadyLabel(pr, options.readyLabel), established: false };
807
809
  if (!readiness.observed) {
808
810
  if (options.dryRun) return reject("rejected", "missing-ready-label", readiness);
@@ -825,7 +827,7 @@ export async function runDevPrAdmission(optionsInput = {}, clientInput) {
825
827
  }
826
828
 
827
829
  if (options.qualificationOnly) {
828
- return runSourceQualification({
830
+ const result = await runSourceQualification({
829
831
  options, pullRequest: pr, readiness, client, reject,
830
832
  evaluate: evaluatePullRequest,
831
833
  admissionState: admissionStateFor,
@@ -833,6 +835,7 @@ export async function runDevPrAdmission(optionsInput = {}, clientInput) {
833
835
  root: contentRoot,
834
836
  publishDiagnostic: publishAdmissionDiagnostic,
835
837
  });
838
+ return readinessFence.finish(result);
836
839
  }
837
840
 
838
841
  const initialQueue = await client.getMergeQueueState(options.targetBranch);
@@ -844,6 +847,7 @@ export async function runDevPrAdmission(optionsInput = {}, clientInput) {
844
847
  }
845
848
  const matchingEntry = initialQueue.entries.find((entry) =>
846
849
  entry.pullRequestNumber === pr.number && entry.pullRequestHeadSha === options.expectedHeadSha);
850
+ await readinessFence.qualifyExisting(matchingEntry);
847
851
  const existing = await admitExistingQueueEntry({
848
852
  options, pullRequest: pr, readiness, client, entry: matchingEntry, warrant,
849
853
  createReceipt: createAdmissionReceipt,
@@ -852,7 +856,7 @@ export async function runDevPrAdmission(optionsInput = {}, clientInput) {
852
856
  });
853
857
  if (existing) return existing;
854
858
 
855
- return runTargetedQueueAdmission({
859
+ const result = await runTargetedQueueAdmission({
856
860
  options, pullRequest: pr, readiness, client, warrant,
857
861
  runController: runDevPrAutoMerge,
858
862
  admissionState: admissionStateFor,
@@ -860,6 +864,7 @@ export async function runDevPrAdmission(optionsInput = {}, clientInput) {
860
864
  root: contentRoot,
861
865
  publishDiagnostic: publishAdmissionDiagnostic,
862
866
  });
867
+ return readinessFence.finish(result);
863
868
  }
864
869
 
865
870
  function finalizePatrolResult(result) {
@@ -1132,6 +1137,11 @@ function cliFlag(args, name) {
1132
1137
 
1133
1138
  export function cliOptions(args = [], environment = process.env) {
1134
1139
  const targetPullRequestNumber = cliValue(args, "pull-request", environment.BUILDCHAIN_DEV_PR_EXPECTED_PR_NUMBER);
1140
+ const warrantMode = cliValue(
1141
+ args,
1142
+ "warrant-mode",
1143
+ environment.BUILDCHAIN_DEV_PR_WARRANT_MODE || environment.WARRANT_MODE,
1144
+ );
1135
1145
  return {
1136
1146
  repository: cliValue(args, "repository", environment.BUILDCHAIN_DEV_PR_REPOSITORY || environment.GITHUB_REPOSITORY),
1137
1147
  targetBranch: cliValue(args, "branch", environment.BUILDCHAIN_DEV_PR_TARGET_BRANCH || environment.GITHUB_REF_NAME),
@@ -1141,10 +1151,14 @@ export function cliOptions(args = [], environment = process.env) {
1141
1151
  blockLabels: cliValue(args, "block-labels", environment.BUILDCHAIN_DEV_PR_BLOCK_LABELS),
1142
1152
  allowedHeadPrefixes: cliValue(args, "allowed-head-prefixes", environment.BUILDCHAIN_DEV_PR_ALLOWED_HEAD_PREFIXES),
1143
1153
  requiredChecks: cliValue(args, "required-checks", environment.BUILDCHAIN_DEV_PR_REQUIRED_CHECKS),
1144
- queueAdmissionContext: cliValue(args, "queue-admission-context", environment.BUILDCHAIN_DEV_PR_QUEUE_ADMISSION_CONTEXT),
1154
+ queueAdmissionContext: cliValue(
1155
+ args,
1156
+ "queue-admission-context",
1157
+ environment.BUILDCHAIN_DEV_PR_QUEUE_ADMISSION_CONTEXT || (warrantMode === "required" ? "Queue admission lease" : ""),
1158
+ ),
1145
1159
  activeLeaseContext: cliValue(args, "active-lease-context", environment.BUILDCHAIN_DEV_PR_ACTIVE_LEASE_CONTEXT),
1146
1160
  diagnosticContext: cliValue(args, "diagnostic-context", environment.BUILDCHAIN_DEV_PR_DIAGNOSTIC_CONTEXT),
1147
- warrantMode: cliValue(args, "warrant-mode", environment.BUILDCHAIN_DEV_PR_WARRANT_MODE),
1161
+ warrantMode,
1148
1162
  warrantResultPath: cliValue(args, "warrant-result", environment.BUILDCHAIN_DEV_PR_WARRANT_RESULT_PATH),
1149
1163
  projectCutProofPath: cliValue(args, "project-cut-proof", environment.BUILDCHAIN_DEV_PR_PROJECT_CUT_PROOF_PATH),
1150
1164
  sourceProofPath: cliValue(args, "source-proof", environment.BUILDCHAIN_DEV_PR_SOURCE_PROOF_PATH),
@@ -143,6 +143,7 @@ export async function readCurrentDeliveryQueueState(client, repository, targetBr
143
143
  const { queue } = await store.read({
144
144
  stateRef: defaultDevDeliveryStateRef(targetBranch),
145
145
  protectedBase: targetBranch,
146
+ allowLegacyV3Readback: true,
146
147
  });
147
148
  return queue;
148
149
  }
@@ -205,6 +206,50 @@ export async function setActiveLeaseStatus(client, repository, sha, context, sta
205
206
  return { context, state, sha };
206
207
  }
207
208
 
209
+ export async function setQueueAdmissionStatus(client, repository, sha, context, state) {
210
+ if (!context) return null;
211
+ const descriptions = {
212
+ pending: "Buildchain is awaiting a qualified Warrant for this exact PR head",
213
+ success: "Buildchain admitted this exact PR head to the merge queue",
214
+ failure: "Buildchain rejected merge queue admission for this exact PR head",
215
+ };
216
+ await client.request("POST", `/repos/${repository.owner}/${repository.repo}/statuses/${sha}`, {
217
+ body: { state, context, description: descriptions[state] || descriptions.failure },
218
+ });
219
+ return { context, state, sha };
220
+ }
221
+
222
+ export function createPreReadinessQueueFence({ client, options, sleep }) {
223
+ let status = null;
224
+ const write = async (state) => {
225
+ status = await setQueueAdmissionStatus(client, options.repository, options.expectedHeadSha, options.queueAdmissionContext, state);
226
+ return status;
227
+ };
228
+ return {
229
+ async establish() {
230
+ if (options.dryRun || options.warrantMode !== "required") return null;
231
+ if (!options.queueAdmissionContext) mismatch("queue-admission-context-required-before-readiness");
232
+ await write("pending");
233
+ for (let attempt = 1; attempt <= Math.max(1, options.pollMergeableAttempts); attempt += 1) {
234
+ const checks = await client.listCommitChecks(options.expectedHeadSha);
235
+ const readback = (checks?.statuses || []).find((entry) => entry.context === options.queueAdmissionContext);
236
+ if (readback?.state === "pending") return { ...status, readbackAttempts: attempt };
237
+ if (attempt < options.pollMergeableAttempts) await sleep(options.pollMergeableDelayMs);
238
+ }
239
+ await write("failure");
240
+ mismatch("pre-readiness-queue-fence-readback-mismatch");
241
+ },
242
+ async qualifyExisting(entry) {
243
+ if (entry && status?.state === "pending") await write("success");
244
+ },
245
+ async finish(result) {
246
+ const controllerFailed = result.controller?.evaluated?.some((entry) => entry.queueAdmissionStatus?.state === "failure");
247
+ if (!result.ok && status?.state === "pending" && !controllerFailed) await write("failure");
248
+ return result;
249
+ },
250
+ };
251
+ }
252
+
208
253
  async function readBackAdmissionStatuses(client, options, expectedHeadSha, sleep) {
209
254
  let last = { queueAdmission: null, activeLease: null };
210
255
  const attempts = Math.max(1, options.pollMergeableAttempts);
@@ -164,15 +164,6 @@ async function qualifyProjectCut({
164
164
 
165
165
  let reuseDecision = null;
166
166
  if (currentBaseSha !== previousBaseSha) {
167
- const observedPullRequestBase = String(
168
- observedPullRequest.base?.sha || "",
169
- ).toLowerCase();
170
- if (observedPullRequestBase !== currentBaseSha) {
171
- mismatch("pre-enqueue-project-cut-base-stale", {
172
- observedPullRequestBase,
173
- currentBaseSha,
174
- });
175
- }
176
167
  const proof = readOptionalJson(options.sourceProofPath);
177
168
  if (!proof) mismatch("pre-enqueue-base-attribution-unknown");
178
169
  if (
@@ -342,15 +333,6 @@ export async function qualifyPreEnqueueReadback({
342
333
  ) {
343
334
  mismatch("head-sha-drift-after-project-cut");
344
335
  }
345
- const casPullRequestBase = String(
346
- casPullRequest.base?.sha || "",
347
- ).toLowerCase();
348
- if (casPullRequestBase && casPullRequestBase !== casBaseSha) {
349
- mismatch("pre-enqueue-project-cut-base-stale", {
350
- observedPullRequestBase: casPullRequestBase,
351
- currentBaseSha: casBaseSha,
352
- });
353
- }
354
336
  if (casPullRequest.mergeable === false) {
355
337
  mismatch("pre-enqueue-merge-conflict-after-project-cut");
356
338
  }
@@ -501,16 +501,16 @@ export function createGitHubDevQualificationClient({
501
501
  sourceBranch,
502
502
  { activeOnly = false } = {},
503
503
  ) {
504
- const query = sourceBranch
505
- ? `?branch=${encodeURIComponent(sourceBranch)}`
506
- : "";
507
- const runs = await paged(
508
- `/repos/${owner}/${repo}/actions/workflows/${encodeURIComponent(workflow)}/runs${query}`,
509
- "workflow_runs",
504
+ const query = sourceBranch ? `branch=${encodeRef(sourceBranch)}&` : "";
505
+ const runs = await Promise.all(
506
+ [...(activeOnly ? ACTIVE_STATUSES : [""])].map((status) =>
507
+ paged(
508
+ `/repos/${owner}/${repo}/actions/workflows/${encodeURIComponent(workflow)}/runs?${query}${status ? `status=${status}` : ""}`,
509
+ "workflow_runs",
510
+ ),
511
+ ),
510
512
  );
511
- return activeOnly
512
- ? runs.filter((run) => ACTIVE_STATUSES.has(text(run.status)))
513
- : runs;
513
+ return runs.flat();
514
514
  },
515
515
  async listRunJobs(runId, runAttempt) {
516
516
  return paged(
@@ -24,7 +24,7 @@ const AUTHORITY_WORKFLOW = "artifact-signing-authority.yml";
24
24
 
25
25
  export function resolveAuthorityDispatchRef(value) {
26
26
  const ref = required(value, "authority ref");
27
- return /^[0-9a-f]{40}$/u.test(ref)
27
+ return /^[0-9a-f]{40}$/u.test(ref) || /^v\d+(?:\.\d+)?(?:-alpha)?$/u.test(ref)
28
28
  ? DEFAULT_ARTIFACT_SIGNING_AUTHORITY_REF
29
29
  : ref;
30
30
  }
@@ -159,8 +159,8 @@ function validateWorkflowRoute(name, route, expectedLogicalRef) {
159
159
  if (!/^\.github\/workflows\/[.a-z0-9-]+\.ya?ml$/.test(route.workflowPath || "")) {
160
160
  throw new Error(`promotion shell ${name} workflowPath must name a reusable workflow`);
161
161
  }
162
- if (!/^(?:v[0-9]+(?:-alpha)?|train\/[A-Za-z0-9._+\/-]+|[0-9a-f]{40})$/.test(route.callRef || "")) {
163
- throw new Error(`promotion shell ${name} callRef must be an official channel ref, trusted train, or exact SHA`);
162
+ if (!/^(?:v[0-9]+(?:-alpha)?|(?:train|alpha|release)\/[A-Za-z0-9._+\/-]+|[0-9a-f]{40})$/.test(route.callRef || "")) {
163
+ throw new Error(`promotion shell ${name} callRef must be an official tag, protected channel ref, trusted train, or exact SHA`);
164
164
  }
165
165
  if (typeof route.forwardInternalInputs !== "boolean") {
166
166
  throw new Error(`promotion shell ${name} forwardInternalInputs must be boolean`);