@kungfu-tech/buildchain 3.0.9-alpha.16 → 3.0.9-alpha.17
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.
- package/actions/promote-buildchain-ref/README.md +3 -0
- package/dist/site/buildchain-contract.json +5 -5
- package/dist/site/buildchain-site.json +11 -11
- package/dist/site/kfd-claims.json +2 -2
- package/dist/site/kfd-upstream-aggregate.json +9 -9
- package/dist/site/manual-registry.json +1 -1
- package/dist/site/page-registry.json +6 -6
- package/dist/site/public-surface-audit.json +2 -2
- package/dist/site/publication-registry.json +4 -4
- package/dist/site/site-manifest.json +5 -5
- package/dist/site/workflow-registry.json +4 -4
- package/docs/dev-delivery-warrant.md +9 -3
- package/docs/release-governance.md +3 -0
- package/package.json +2 -2
- package/packages/core/adopter-delivery-vectors.json +2 -2
- package/scripts/dev-pr-auto-merge.mjs +11 -11
- package/scripts/dev-pr-delivery-warrant.mjs +88 -91
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 1,
|
|
3
3
|
"contract": "kungfu-buildchain-site-manifest",
|
|
4
|
-
"generatedAt": "2026-08-
|
|
5
|
-
"publishedAt": "2026-08-
|
|
4
|
+
"generatedAt": "2026-08-14T01:22:22.000Z",
|
|
5
|
+
"publishedAt": "2026-08-14T01:22:22.000Z",
|
|
6
6
|
"reproducible": true,
|
|
7
7
|
"timestampPolicy": "ci-injected",
|
|
8
8
|
"deterministicInputs": [
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"declared Buildchain surface manifest contract"
|
|
20
20
|
],
|
|
21
21
|
"sourceDateEpoch": "0",
|
|
22
|
-
"sourceRevision": "
|
|
22
|
+
"sourceRevision": "17cb4259831b34ba382b0211500cbb1ef26efd11",
|
|
23
23
|
"timestampPolicyDetails": {
|
|
24
24
|
"contract": "kungfu-buildchain-surface-timestamp-policy",
|
|
25
25
|
"timestampFields": [
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
},
|
|
38
38
|
"package": {
|
|
39
39
|
"name": "@kungfu-tech/buildchain",
|
|
40
|
-
"version": "3.0.9-alpha.
|
|
40
|
+
"version": "3.0.9-alpha.17",
|
|
41
41
|
"versionSource": "package.json#version"
|
|
42
42
|
},
|
|
43
43
|
"entrypoint": "buildchain-site.json",
|
|
@@ -333,7 +333,7 @@
|
|
|
333
333
|
"path": "docs/release-governance.md",
|
|
334
334
|
"plane": "why",
|
|
335
335
|
"exists": true,
|
|
336
|
-
"digest": "sha256:
|
|
336
|
+
"digest": "sha256:66eca6c682edd21deeeaae769bcd6882f8fc70b6f9448abec9ed922d2a47ad97"
|
|
337
337
|
},
|
|
338
338
|
{
|
|
339
339
|
"id": "release-flow",
|
|
@@ -1970,7 +1970,7 @@
|
|
|
1970
1970
|
"status": "preview",
|
|
1971
1971
|
"owner": "buildchain-workflows",
|
|
1972
1972
|
"maturity": "preview",
|
|
1973
|
-
"introducedVersion": "3.0.9-alpha.
|
|
1973
|
+
"introducedVersion": "3.0.9-alpha.17",
|
|
1974
1974
|
"compatibilityPromise": "preserved-through-the-v3-major-line",
|
|
1975
1975
|
"deprecationReplacement": "",
|
|
1976
1976
|
"sunsetCondition": "explicit-breaking-change-review-in-a-future-major-line",
|
|
@@ -1991,7 +1991,7 @@
|
|
|
1991
1991
|
"status": "preview",
|
|
1992
1992
|
"owner": "buildchain-workflows",
|
|
1993
1993
|
"maturity": "preview",
|
|
1994
|
-
"introducedVersion": "3.0.9-alpha.
|
|
1994
|
+
"introducedVersion": "3.0.9-alpha.17",
|
|
1995
1995
|
"compatibilityPromise": "preserved-through-the-v3-major-line",
|
|
1996
1996
|
"deprecationReplacement": "",
|
|
1997
1997
|
"sunsetCondition": "explicit-breaking-change-review-in-a-future-major-line",
|
|
@@ -2012,7 +2012,7 @@
|
|
|
2012
2012
|
"status": "preview",
|
|
2013
2013
|
"owner": "buildchain-workflows",
|
|
2014
2014
|
"maturity": "preview",
|
|
2015
|
-
"introducedVersion": "3.0.9-alpha.
|
|
2015
|
+
"introducedVersion": "3.0.9-alpha.17",
|
|
2016
2016
|
"compatibilityPromise": "preserved-through-the-v3-major-line",
|
|
2017
2017
|
"deprecationReplacement": "",
|
|
2018
2018
|
"sunsetCondition": "explicit-breaking-change-review-in-a-future-major-line",
|
|
@@ -2062,7 +2062,7 @@
|
|
|
2062
2062
|
"status": "preview",
|
|
2063
2063
|
"owner": "buildchain-workflows",
|
|
2064
2064
|
"maturity": "preview",
|
|
2065
|
-
"introducedVersion": "3.0.9-alpha.
|
|
2065
|
+
"introducedVersion": "3.0.9-alpha.17",
|
|
2066
2066
|
"compatibilityPromise": "preserved-through-the-v3-major-line",
|
|
2067
2067
|
"deprecationReplacement": "",
|
|
2068
2068
|
"sunsetCondition": "explicit-breaking-change-review-in-a-future-major-line",
|
|
@@ -294,9 +294,15 @@ The reusable `dev-pr-auto-merge.yml` supports three explicit rollout modes:
|
|
|
294
294
|
reuses semantic native proof under heartbeat, atomically qualifies the same
|
|
295
295
|
fence, and refuses GitHub enqueue unless the immutable queue commit, state root, active Warrant, and
|
|
296
296
|
selected candidate all pass exact readback validation. Immediately before
|
|
297
|
-
enqueue, the controller
|
|
298
|
-
|
|
299
|
-
|
|
297
|
+
enqueue, the controller writes and then reads back both the exact-head queue
|
|
298
|
+
admission status and active lease status. Only after those statuses are
|
|
299
|
+
visible at their required states does it reread the pull request head,
|
|
300
|
+
protected base, native merge queue, and current protected Warrant state. The
|
|
301
|
+
final rooted admission transaction binds the frozen base, source head,
|
|
302
|
+
candidate, fencing token, generation, native proof roots, Project Cut proof,
|
|
303
|
+
and both status contexts. Status propagation is retried before enqueue;
|
|
304
|
+
base, head, queue-predecessor, lease, or Warrant drift revokes both statuses
|
|
305
|
+
without attempting enqueue. A previously valid result is not authority after terminal
|
|
300
306
|
closeout. Re-running qualification for the same selected head may regenerate
|
|
301
307
|
timestamped proof bytes, but it retains the immutable active Warrant and its
|
|
302
308
|
originally selected proof instead of rewriting or rejecting that attempt.
|
|
@@ -113,6 +113,9 @@ The implementation is intentionally stricter than a local release script:
|
|
|
113
113
|
`dev/vX/vX.Y` to `alpha/vX/vX.Y`;
|
|
114
114
|
- release promotion must come from a merged same-repository PR from
|
|
115
115
|
`alpha/vX/vX.Y` to `release/vX/vX.Y`;
|
|
116
|
+
- an exact SHA that is itself the merge commit of a reviewed same-repository PR
|
|
117
|
+
into the protected target is equivalent lineage evidence; a commit merely
|
|
118
|
+
contained by an unrelated PR is not;
|
|
116
119
|
- major promotion must come from a merged same-repository PR from
|
|
117
120
|
`release/vX/vX.Y` to `publish-gate/major`;
|
|
118
121
|
- release promotion requires an existing same-patch alpha tag and checks the
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kungfu-tech/buildchain",
|
|
3
|
-
"version": "3.0.9-alpha.
|
|
3
|
+
"version": "3.0.9-alpha.17",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Buildchain Release Passport, release governance, CLI toolkit, and site facts.",
|
|
6
6
|
"repository": "https://github.com/kungfu-systems/buildchain",
|
|
@@ -157,7 +157,7 @@
|
|
|
157
157
|
"typescript": "^5.9.0"
|
|
158
158
|
},
|
|
159
159
|
"dependencies": {
|
|
160
|
-
"@kungfu-tech/kfd": "1.0.0-alpha.
|
|
160
|
+
"@kungfu-tech/kfd": "1.0.0-alpha.65",
|
|
161
161
|
"ajv": "8.20.0",
|
|
162
162
|
"smol-toml": "1.7.0"
|
|
163
163
|
}
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"contract": "kungfu-buildchain-adopter-delivery-vectors/v1",
|
|
4
4
|
"rootAlgorithm": "sha256-buildchain-canonical-json-v1",
|
|
5
5
|
"authorities": {
|
|
6
|
-
"kfdPackage": "@kungfu-tech/kfd@1.0.0-alpha.
|
|
6
|
+
"kfdPackage": "@kungfu-tech/kfd@1.0.0-alpha.65",
|
|
7
7
|
"categoryProfileVectors": "@kungfu-tech/kfd/adopter-conformance/category-profile-vectors.json",
|
|
8
8
|
"categoryInstanceVectors": "@kungfu-tech/kfd/adopter-conformance/category-instance-vectors.json"
|
|
9
9
|
},
|
|
@@ -73,5 +73,5 @@
|
|
|
73
73
|
}
|
|
74
74
|
}
|
|
75
75
|
],
|
|
76
|
-
"suiteRoot": "sha256:
|
|
76
|
+
"suiteRoot": "sha256:c978707556406ffda4ef4192032332c01c2da1cebb6ad8c4f0edfc65d5cef0c7"
|
|
77
77
|
}
|
|
@@ -4,7 +4,7 @@ import { spawnSync } from "node:child_process";
|
|
|
4
4
|
import fs from "node:fs";
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
import { verifyProjectCutReplayProof } from "../packages/core/dev-delivery-warrant.js";
|
|
7
|
-
import { admitExistingQueueEntry, createDevPrAdmissionReceipt,
|
|
7
|
+
import { admitExistingQueueEntry, createDevPrAdmissionReceipt, qualifyAtomicQueueAdmission, readDeliveryWarrantResult, runSourceQualification, runTargetedQueueAdmission, verifyCurrentDeliveryWarrant } from "./dev-pr-delivery-warrant.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/"];
|
|
10
10
|
const DEFAULT_REQUIRED_CHECKS = ["check"];
|
|
@@ -88,6 +88,7 @@ function normalizeOptions(options = {}) {
|
|
|
88
88
|
projectCutProofPath: String(options.projectCutProofPath || "").trim(),
|
|
89
89
|
sourcePatchRoot: String(options.sourcePatchRoot || "").trim().toLowerCase(),
|
|
90
90
|
qualificationOnly: boolOption(options.qualificationOnly, false),
|
|
91
|
+
verifiedDeliveryWarrant: options.verifiedDeliveryWarrant || null,
|
|
91
92
|
};
|
|
92
93
|
}
|
|
93
94
|
|
|
@@ -924,8 +925,9 @@ async function reconcileEnqueueError({ client, options, pr, expectedHeadSha, ent
|
|
|
924
925
|
entry.queueAdmissionStatus = await setQueueAdmissionStatus(client, options.repository, expectedHeadSha, options.queueAdmissionContext, "failure");
|
|
925
926
|
entry.activeLeaseStatus = await setActiveLeaseStatus(client, options.repository, expectedHeadSha, options.activeLeaseContext, "failure");
|
|
926
927
|
entry.action = "skip";
|
|
927
|
-
entry.reason = "enqueue-rejected";
|
|
928
|
+
entry.reason = error.preEnqueue ? (error.code || "pre-enqueue-qualification-failed") : "enqueue-rejected";
|
|
928
929
|
entry.enqueueError = {
|
|
930
|
+
phase: error.preEnqueue ? "pre-enqueue" : "enqueue",
|
|
929
931
|
status: error.status || null,
|
|
930
932
|
message: error.message || "GitHub rejected merge queue admission",
|
|
931
933
|
};
|
|
@@ -1088,17 +1090,15 @@ export async function runDevPrAutoMerge(optionsInput = {}, clientInput) {
|
|
|
1088
1090
|
result.skipped.push(entry);
|
|
1089
1091
|
} else {
|
|
1090
1092
|
try {
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
input: { pullRequestId: decision.pullRequestId, expectedHeadOid: expectedHeadSha },
|
|
1096
|
-
attempts: options.pollMergeableAttempts,
|
|
1097
|
-
delayMs: options.pollMergeableDelayMs,
|
|
1098
|
-
sleep: delay,
|
|
1093
|
+
const atomicAdmission = await qualifyAtomicQueueAdmission({
|
|
1094
|
+
client, options, pullRequest: pr, expectedBaseSha: initialBaseSha, expectedHeadSha, projectCut: decision.projectCut, sleep: delay, mergeableAccepted, root: contentRoot,
|
|
1095
|
+
setActiveLeaseStatus: (state) => setActiveLeaseStatus(client, options.repository, expectedHeadSha, options.activeLeaseContext, state),
|
|
1096
|
+
setQueueAdmissionStatus: (state) => setQueueAdmissionStatus(client, options.repository, expectedHeadSha, options.queueAdmissionContext, state),
|
|
1099
1097
|
});
|
|
1098
|
+
Object.assign(entry, atomicAdmission.entryFields);
|
|
1099
|
+
const queueEntry = atomicAdmission.exactEntry || await client.enqueuePullRequest({ pullRequestId: decision.pullRequestId, expectedHeadOid: expectedHeadSha });
|
|
1100
1100
|
entry.action = "enqueued";
|
|
1101
|
-
entry.reason = "enqueued-with-expected-head";
|
|
1101
|
+
entry.reason = atomicAdmission.exactEntry ? "already-enqueued-exact-head" : "enqueued-with-expected-head";
|
|
1102
1102
|
entry.queueEntry = queueEntry;
|
|
1103
1103
|
entry.admissionReceipt.reason = entry.reason;
|
|
1104
1104
|
result.actions.push(entry);
|
|
@@ -60,44 +60,20 @@ function exactWarrantBinding({ result, warrant, candidate, options, pullRequest
|
|
|
60
60
|
|
|
61
61
|
export function createDevPrAdmissionReceipt({ options, pr = {}, state, reason, readiness, decision = {}, queue = null, warrant = null, labels = [], nextAction }) {
|
|
62
62
|
const observedHeadSha = String(pr.head?.sha || "").toLowerCase();
|
|
63
|
+
const { readyLabel, blockLabels, allowedHeadPrefixes, requiredChecks, requireApproval, sameRepositoryOnly, landingMode, queueAdmissionContext, diagnosticContext } = options;
|
|
63
64
|
return {
|
|
64
|
-
schema: ADMISSION_SCHEMA,
|
|
65
|
-
repository: options.repository.fullName,
|
|
66
|
-
targetBranch: options.targetBranch,
|
|
65
|
+
schema: ADMISSION_SCHEMA, repository: options.repository.fullName, targetBranch: options.targetBranch,
|
|
67
66
|
pullRequestNumber: options.targetPullRequestNumber,
|
|
68
67
|
pullRequestUrl: pr.html_url || `https://github.com/${options.repository.fullName}/pull/${options.targetPullRequestNumber}`,
|
|
69
|
-
expectedHeadSha: options.expectedHeadSha,
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
headRef: pr.head?.ref || "",
|
|
74
|
-
observedLabels: [...labels].sort(),
|
|
75
|
-
policy: {
|
|
76
|
-
readyLabel: options.readyLabel,
|
|
77
|
-
blockLabels: options.blockLabels,
|
|
78
|
-
allowedHeadPrefixes: options.allowedHeadPrefixes,
|
|
79
|
-
requiredChecks: options.requiredChecks,
|
|
80
|
-
requireApproval: options.requireApproval,
|
|
81
|
-
sameRepositoryOnly: options.sameRepositoryOnly,
|
|
82
|
-
landingMode: options.landingMode,
|
|
83
|
-
queueAdmissionContext: options.queueAdmissionContext,
|
|
84
|
-
diagnosticContext: options.diagnosticContext,
|
|
85
|
-
},
|
|
86
|
-
readiness: {
|
|
87
|
-
label: options.readyLabel,
|
|
88
|
-
observed: readiness?.observed === true,
|
|
89
|
-
established: readiness?.established === true,
|
|
90
|
-
mutationAuthorized: !options.dryRun,
|
|
91
|
-
},
|
|
68
|
+
expectedHeadSha: options.expectedHeadSha, observedHeadSha, observedBaseBranch: pr.base?.ref || "",
|
|
69
|
+
headRepository: pr.head?.repo?.full_name || "", headRef: pr.head?.ref || "", observedLabels: [...labels].sort(),
|
|
70
|
+
policy: { readyLabel, blockLabels, allowedHeadPrefixes, requiredChecks, requireApproval, sameRepositoryOnly, landingMode, queueAdmissionContext, diagnosticContext },
|
|
71
|
+
readiness: { label: readyLabel, observed: readiness?.observed === true, established: readiness?.established === true, mutationAuthorized: !options.dryRun },
|
|
92
72
|
approval: decision.approval || { required: options.requireApproval, passed: false },
|
|
93
73
|
checks: decision.checks || { required: options.requiredChecks, entries: [], passed: false },
|
|
94
|
-
projectCut: decision.projectCut || null,
|
|
95
|
-
queue,
|
|
96
|
-
deliveryWarrant: warrant,
|
|
74
|
+
projectCut: decision.projectCut || null, queue, deliveryWarrant: warrant,
|
|
97
75
|
autoMergeEnabled: Boolean(pr.auto_merge || pr.autoMergeRequest),
|
|
98
|
-
state,
|
|
99
|
-
reason,
|
|
100
|
-
decision: state,
|
|
76
|
+
state, reason, decision: state,
|
|
101
77
|
qualification: ["ready", "queued"].includes(state),
|
|
102
78
|
nextAction: nextAction({ options, state, reason, observedHeadSha }),
|
|
103
79
|
};
|
|
@@ -110,35 +86,23 @@ export function readDeliveryWarrantResult(options, pullRequest) {
|
|
|
110
86
|
const { warrant, candidate } = exactActiveReadback(result);
|
|
111
87
|
exactWarrantBinding({ result, warrant, candidate, options, pullRequest });
|
|
112
88
|
return {
|
|
113
|
-
stateRef: result.stateRef || "",
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
fencingToken: warrant.fencingToken,
|
|
119
|
-
generation: warrant.generation,
|
|
120
|
-
issuedAt: warrant.issuedAt,
|
|
121
|
-
expiresAt: warrant.expiresAt,
|
|
89
|
+
stateRef: result.stateRef || "", stateCommit: result.after.commitSha, stateRoot: result.after.stateRoot, receiptRoot: result.receiptRoot,
|
|
90
|
+
candidateId: warrant.candidateId, fencingToken: warrant.fencingToken, generation: warrant.generation,
|
|
91
|
+
issuedAt: warrant.issuedAt, expiresAt: warrant.expiresAt,
|
|
92
|
+
repository: warrant.repository, protectedBase: warrant.protectedBase, pullRequestNumber: warrant.pullRequestNumber, sourceHead: warrant.sourceHead,
|
|
93
|
+
nativeProofRoot: warrant.nativeProofRoot || "", nativeProofReuseRoot: warrant.nativeProofReuseRoot || "",
|
|
122
94
|
};
|
|
123
95
|
}
|
|
124
96
|
|
|
125
97
|
export async function readCurrentDeliveryQueueState(client, repository, targetBranch) {
|
|
126
98
|
if (typeof client.getDevDeliveryQueueState === "function") return client.getDevDeliveryQueueState(targetBranch);
|
|
127
|
-
const store = new GitHubDevDeliveryStore({
|
|
128
|
-
|
|
129
|
-
token: client.token,
|
|
130
|
-
apiUrl: client.apiUrl,
|
|
131
|
-
fetchImpl: client.fetch,
|
|
132
|
-
});
|
|
133
|
-
const { queue } = await store.read({
|
|
134
|
-
stateRef: defaultDevDeliveryStateRef(targetBranch),
|
|
135
|
-
protectedBase: targetBranch,
|
|
136
|
-
});
|
|
99
|
+
const store = new GitHubDevDeliveryStore({ repository: `${repository.owner}/${repository.repo}`, token: client.token, apiUrl: client.apiUrl, fetchImpl: client.fetch });
|
|
100
|
+
const { queue } = await store.read({ stateRef: defaultDevDeliveryStateRef(targetBranch), protectedBase: targetBranch });
|
|
137
101
|
return queue;
|
|
138
102
|
}
|
|
139
103
|
|
|
140
104
|
export async function verifyCurrentDeliveryWarrant(client, options, pullRequest, warrant) {
|
|
141
|
-
if (!warrant) return;
|
|
105
|
+
if (!warrant) return null;
|
|
142
106
|
let queue;
|
|
143
107
|
try {
|
|
144
108
|
queue = await readCurrentDeliveryQueueState(client, options.repository, options.targetBranch);
|
|
@@ -152,23 +116,79 @@ export async function verifyCurrentDeliveryWarrant(client, options, pullRequest,
|
|
|
152
116
|
requireMatch(Number(active?.generation) === Number(warrant.generation), "delivery-warrant-current-generation-mismatch");
|
|
153
117
|
requireMatch(Number(active?.pullRequestNumber) === Number(pullRequest.number), "delivery-warrant-current-pr-mismatch");
|
|
154
118
|
requireMatch(String(active?.sourceHead || "").toLowerCase() === options.expectedHeadSha, "delivery-warrant-current-head-mismatch");
|
|
119
|
+
requireMatch(Number.isFinite(Date.parse(active?.expiresAt)) && Date.parse(active.expiresAt) > Date.now(), "delivery-warrant-current-expired");
|
|
155
120
|
requireMatch(["selected", "proving", "waiting", "blocked", "qualified"].includes(candidate?.status), "delivery-warrant-current-candidate-not-selected");
|
|
156
121
|
requireMatch(candidate?.sourceHead === active.sourceHead, "delivery-warrant-current-candidate-head-mismatch");
|
|
122
|
+
return { activeWarrant: active, activeCandidate: candidate };
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function latestStatusForContext(statuses, context) { return (statuses || []).find((status) => String(status.context || "") === context) || null; }
|
|
126
|
+
|
|
127
|
+
function preEnqueueMismatch(code, details = {}) {
|
|
128
|
+
const error = new Error(code);
|
|
129
|
+
error.code = code;
|
|
130
|
+
Object.assign(error, details);
|
|
131
|
+
throw error;
|
|
157
132
|
}
|
|
158
133
|
|
|
159
|
-
|
|
160
|
-
let
|
|
134
|
+
async function readBackAdmissionStatuses(client, options, expectedHeadSha, sleep) {
|
|
135
|
+
let last = { queueAdmission: null, activeLease: null };
|
|
136
|
+
const attempts = Math.max(1, options.pollMergeableAttempts);
|
|
161
137
|
for (let attempt = 1; attempt <= attempts; attempt += 1) {
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
138
|
+
const checks = await client.listCommitChecks(expectedHeadSha);
|
|
139
|
+
last = { queueAdmission: latestStatusForContext(checks?.statuses, options.queueAdmissionContext), activeLease: latestStatusForContext(checks?.statuses, options.activeLeaseContext) };
|
|
140
|
+
if ((!options.queueAdmissionContext || last.queueAdmission?.state === "success") && (!options.activeLeaseContext || last.activeLease?.state === "pending")) return { ...last, attempts: attempt };
|
|
141
|
+
if (attempt < attempts) await sleep(options.pollMergeableDelayMs);
|
|
142
|
+
}
|
|
143
|
+
preEnqueueMismatch("queue-admission-lease-readback-mismatch", { readback: last });
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export async function qualifyAtomicQueueAdmission({ client, options, pullRequest, expectedBaseSha, expectedHeadSha, projectCut, setActiveLeaseStatus, setQueueAdmissionStatus, sleep, mergeableAccepted, root }) {
|
|
147
|
+
try {
|
|
148
|
+
if (options.warrantMode === "required") {
|
|
149
|
+
if (!options.verifiedDeliveryWarrant) preEnqueueMismatch("delivery-warrant-pre-enqueue-readback-missing");
|
|
150
|
+
if (!options.queueAdmissionContext) preEnqueueMismatch("queue-admission-context-required");
|
|
151
|
+
if (!options.activeLeaseContext) preEnqueueMismatch("active-lease-context-required");
|
|
169
152
|
}
|
|
153
|
+
const activeLeaseStatus = await setActiveLeaseStatus("pending");
|
|
154
|
+
const queueAdmissionStatus = await setQueueAdmissionStatus("success");
|
|
155
|
+
const statusReadback = await readBackAdmissionStatuses(client, options, expectedHeadSha, sleep);
|
|
156
|
+
const finalReadback = await Promise.all([
|
|
157
|
+
client.getPullRequest(pullRequest.number, { attempts: options.pollMergeableAttempts, delayMs: options.pollMergeableDelayMs }), client.getBranchSha(options.targetBranch),
|
|
158
|
+
client.getMergeQueueState(options.targetBranch), verifyCurrentDeliveryWarrant(client, options, pullRequest, options.verifiedDeliveryWarrant),
|
|
159
|
+
]);
|
|
160
|
+
const [observedPullRequest, observedBaseSha, observedQueueState, currentWarrant] = finalReadback;
|
|
161
|
+
const observedHeadSha = String(observedPullRequest.head?.sha || "").toLowerCase();
|
|
162
|
+
if (observedBaseSha !== expectedBaseSha) preEnqueueMismatch("base-sha-drift-after-lease-readback", { observedBaseSha });
|
|
163
|
+
if (observedHeadSha !== expectedHeadSha) preEnqueueMismatch("head-sha-drift-after-lease-readback", { observedHeadSha });
|
|
164
|
+
if (!mergeableAccepted(observedPullRequest, "queue", projectCut?.ok === true)) preEnqueueMismatch("not-mergeable-after-lease-readback");
|
|
165
|
+
const exactEntry = observedQueueState.entries.find((candidate) =>
|
|
166
|
+
candidate.pullRequestNumber === pullRequest.number && candidate.pullRequestHeadSha === expectedHeadSha);
|
|
167
|
+
const predecessor = observedQueueState.entries.find((candidate) => candidate !== exactEntry) || null;
|
|
168
|
+
if (predecessor) preEnqueueMismatch("queue-predecessor-after-lease-readback", { predecessor });
|
|
169
|
+
const transaction = {
|
|
170
|
+
schema: "kungfu.buildchain.dev-queue-admission-transaction/v1", repository: options.repository.fullName,
|
|
171
|
+
protectedBase: options.targetBranch, pullRequestNumber: pullRequest.number,
|
|
172
|
+
sourceHead: expectedHeadSha, frozenBase: expectedBaseSha,
|
|
173
|
+
queueAdmission: { context: options.queueAdmissionContext, state: statusReadback.queueAdmission?.state || "" },
|
|
174
|
+
activeLease: { context: options.activeLeaseContext, state: statusReadback.activeLease?.state || "" },
|
|
175
|
+
warrant: options.verifiedDeliveryWarrant,
|
|
176
|
+
currentWarrant: currentWarrant ? {
|
|
177
|
+
candidateId: currentWarrant.activeWarrant.candidateId, fencingToken: currentWarrant.activeWarrant.fencingToken,
|
|
178
|
+
generation: currentWarrant.activeWarrant.generation, sourceHead: currentWarrant.activeWarrant.sourceHead, expiresAt: currentWarrant.activeWarrant.expiresAt,
|
|
179
|
+
} : null,
|
|
180
|
+
projectCutProofRoot: projectCut?.proofRoot || "", decision: exactEntry ? "already-enqueued" : "qualified",
|
|
181
|
+
};
|
|
182
|
+
const entryFields = { activeLeaseStatus, queueAdmissionStatus, atomicAdmissionReceipt: transaction, atomicAdmissionReceiptRoot: root(transaction), atomicAdmissionReadbackAttempts: statusReadback.attempts };
|
|
183
|
+
return { exactEntry, entryFields };
|
|
184
|
+
} catch (error) {
|
|
185
|
+
error.preEnqueue = true;
|
|
186
|
+
throw error;
|
|
170
187
|
}
|
|
171
|
-
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function admissionResult(options, receipt, root, ok, outcome, controller = null) {
|
|
191
|
+
return { schema: "kungfu.buildchain.dev-pr-admission-result/v1", ok, mode: options.dryRun ? "plan" : "execute", outcome, receipt, receiptRoot: root(receipt), diagnostic: null, ...(controller ? { controller } : {}) };
|
|
172
192
|
}
|
|
173
193
|
|
|
174
194
|
export async function runSourceQualification({ options, pullRequest, readiness, client, evaluate, admissionState, createReceipt, root, publishDiagnostic, reject }) {
|
|
@@ -187,15 +207,7 @@ export async function runSourceQualification({ options, pullRequest, readiness,
|
|
|
187
207
|
queue: null,
|
|
188
208
|
warrant: null,
|
|
189
209
|
});
|
|
190
|
-
const result =
|
|
191
|
-
schema: "kungfu.buildchain.dev-pr-admission-result/v1",
|
|
192
|
-
ok: state === "ready",
|
|
193
|
-
mode: options.dryRun ? "plan" : "execute",
|
|
194
|
-
outcome: state === "ready" ? "source-qualified" : "targeted-admission-failed",
|
|
195
|
-
receipt,
|
|
196
|
-
receiptRoot: root(receipt),
|
|
197
|
-
diagnostic: null,
|
|
198
|
-
};
|
|
210
|
+
const result = admissionResult(options, receipt, root, state === "ready", state === "ready" ? "source-qualified" : "targeted-admission-failed");
|
|
199
211
|
if (!options.dryRun) result.diagnostic = await publishDiagnostic(client, options, receipt, result.receiptRoot);
|
|
200
212
|
return result;
|
|
201
213
|
}
|
|
@@ -211,15 +223,7 @@ export async function admitExistingQueueEntry({ options, pullRequest, readiness,
|
|
|
211
223
|
queue: { enabled: true, entry },
|
|
212
224
|
warrant,
|
|
213
225
|
});
|
|
214
|
-
const result =
|
|
215
|
-
schema: "kungfu.buildchain.dev-pr-admission-result/v1",
|
|
216
|
-
ok: true,
|
|
217
|
-
mode: options.dryRun ? "plan" : "execute",
|
|
218
|
-
outcome: "admitted",
|
|
219
|
-
receipt,
|
|
220
|
-
receiptRoot: root(receipt),
|
|
221
|
-
diagnostic: null,
|
|
222
|
-
};
|
|
226
|
+
const result = admissionResult(options, receipt, root, true, "admitted");
|
|
223
227
|
if (!options.dryRun) result.diagnostic = await publishDiagnostic(client, options, receipt, result.receiptRoot);
|
|
224
228
|
return result;
|
|
225
229
|
}
|
|
@@ -227,7 +231,7 @@ export async function admitExistingQueueEntry({ options, pullRequest, readiness,
|
|
|
227
231
|
export async function runTargetedQueueAdmission({ options, pullRequest, readiness, client, warrant, runController, admissionState, createReceipt, root, publishDiagnostic }) {
|
|
228
232
|
const targetedClient = Object.create(client);
|
|
229
233
|
targetedClient.listPullRequests = async () => [pullRequest];
|
|
230
|
-
const controller = await runController({ ...options, targetPullRequestNumber: 0 }, targetedClient);
|
|
234
|
+
const controller = await runController({ ...options, targetPullRequestNumber: 0, verifiedDeliveryWarrant: warrant }, targetedClient);
|
|
231
235
|
controller.runKind = "targeted-admission-evaluation";
|
|
232
236
|
controller.outcome = controller.actions.length === 0 ? "target-not-admitted" : "target-action-selected";
|
|
233
237
|
controller.qualification = false;
|
|
@@ -245,20 +249,13 @@ export async function runTargetedQueueAdmission({ options, pullRequest, readines
|
|
|
245
249
|
enabled: controller.mergeQueue?.enabled === true,
|
|
246
250
|
predecessor: entry.admissionReceipt?.predecessor || null,
|
|
247
251
|
entry: entry.queueEntry || null,
|
|
252
|
+
admissionTransaction: entry.atomicAdmissionReceipt || null,
|
|
253
|
+
admissionTransactionRoot: entry.atomicAdmissionReceiptRoot || "",
|
|
248
254
|
},
|
|
249
255
|
warrant,
|
|
250
256
|
});
|
|
251
257
|
const admitted = ["ready", "queued"].includes(state);
|
|
252
|
-
const result =
|
|
253
|
-
schema: "kungfu.buildchain.dev-pr-admission-result/v1",
|
|
254
|
-
ok: admitted,
|
|
255
|
-
mode: options.dryRun ? "plan" : "execute",
|
|
256
|
-
outcome: admitted ? "admitted" : "targeted-admission-failed",
|
|
257
|
-
receipt,
|
|
258
|
-
receiptRoot: root(receipt),
|
|
259
|
-
diagnostic: null,
|
|
260
|
-
controller,
|
|
261
|
-
};
|
|
258
|
+
const result = admissionResult(options, receipt, root, admitted, admitted ? "admitted" : "targeted-admission-failed", controller);
|
|
262
259
|
if (!options.dryRun) result.diagnostic = await publishDiagnostic(client, options, receipt, result.receiptRoot);
|
|
263
260
|
return result;
|
|
264
261
|
}
|