@kungfu-tech/buildchain 2.14.3-alpha.10 → 2.14.3-alpha.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/site/buildchain-contract.json +7 -7
- package/dist/site/buildchain-site.json +4 -4
- package/dist/site/kfd-upstream-aggregate.json +1 -1
- package/dist/site/node-api-registry.json +1 -1
- package/dist/site/publication-registry.json +4 -4
- package/dist/site/site-manifest.json +4 -4
- package/package.json +1 -1
- package/packages/core/publication-control-plane-audit.js +0 -2
- package/scripts/audit-publication-control-plane.mjs +1 -6
- package/scripts/generate-channel-promotion-workflow.mjs +26 -41
- package/scripts/promotion-channel-router.mjs +7 -0
- package/scripts/stable-candidate-patrol.mjs +6 -55
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"product": {
|
|
5
5
|
"name": "Buildchain",
|
|
6
6
|
"package": "@kungfu-tech/buildchain",
|
|
7
|
-
"version": "2.14.3-alpha.
|
|
7
|
+
"version": "2.14.3-alpha.2",
|
|
8
8
|
"repository": "https://github.com/kungfu-systems/buildchain"
|
|
9
9
|
},
|
|
10
10
|
"majorLine": "v2",
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
"retryable GitHub fallback fetches use a bounded attempt budget before exact source SHA and tree verification"
|
|
51
51
|
],
|
|
52
52
|
"breakingDigest": "sha256:a6a35370d6b0d0f46e1b2712dcc01961fe53c2febde95409a27a485c10a36650",
|
|
53
|
-
"auditDigest": "sha256:
|
|
53
|
+
"auditDigest": "sha256:46fa80913e6174f34b96c323ed913410b9fea0f3632a0ce9c7d4f3e0b23b7072"
|
|
54
54
|
},
|
|
55
55
|
{
|
|
56
56
|
"contractVersion": 1,
|
|
@@ -168,7 +168,7 @@
|
|
|
168
168
|
"GitHub Release passport and evidence publication is delegated to promote-buildchain-ref after the semver release transaction completes"
|
|
169
169
|
],
|
|
170
170
|
"breakingDigest": "sha256:1f476fb9a4854b362c4b24cef89a3c7675d7d63baf3bc828d7b8ae7d2787cead",
|
|
171
|
-
"auditDigest": "sha256:
|
|
171
|
+
"auditDigest": "sha256:b083a71f80052c013468d48473ec0f8f919161e38a3607feebe99edcd3b32460"
|
|
172
172
|
},
|
|
173
173
|
{
|
|
174
174
|
"contractVersion": 1,
|
|
@@ -320,7 +320,7 @@
|
|
|
320
320
|
"semver GitHub Releases are created or updated only after transaction completion, with prerelease/latest metadata derived from the public release tag"
|
|
321
321
|
],
|
|
322
322
|
"breakingDigest": "sha256:a59f0910e6df842e7699139472e5dd69ac2fdd7f7213bf2cb346d1d622556874",
|
|
323
|
-
"auditDigest": "sha256:
|
|
323
|
+
"auditDigest": "sha256:df58156fe2ae06cc080c2e1a7e70458ab9177365ac7da1a66abe026386ddd548"
|
|
324
324
|
},
|
|
325
325
|
{
|
|
326
326
|
"contractVersion": 1,
|
|
@@ -600,7 +600,7 @@
|
|
|
600
600
|
"agents can discover supported Node APIs without importing internal file paths"
|
|
601
601
|
],
|
|
602
602
|
"breakingDigest": "sha256:48f925608d3e2131d90936b07dc2a30341204cae3e6e785c0f77d61ad755c945",
|
|
603
|
-
"auditDigest": "sha256:
|
|
603
|
+
"auditDigest": "sha256:21b2e8fa81fbeab636fbd6e65ee3f39c05ab5a521b5dc24b95ccbfd06c334cb8"
|
|
604
604
|
},
|
|
605
605
|
{
|
|
606
606
|
"contractVersion": 1,
|
|
@@ -1057,7 +1057,7 @@
|
|
|
1057
1057
|
"missing receipts are non-qualifying and must not be represented as a successful controller run"
|
|
1058
1058
|
],
|
|
1059
1059
|
"breakingDigest": "sha256:e264a79f9f399038c2fcfd21e4168c68c2e1485ee5c651c02242a02b622ac2be",
|
|
1060
|
-
"auditDigest": "sha256:
|
|
1060
|
+
"auditDigest": "sha256:46fa80913e6174f34b96c323ed913410b9fea0f3632a0ce9c7d4f3e0b23b7072"
|
|
1061
1061
|
},
|
|
1062
1062
|
{
|
|
1063
1063
|
"contractVersion": 1,
|
|
@@ -2830,5 +2830,5 @@
|
|
|
2830
2830
|
}
|
|
2831
2831
|
],
|
|
2832
2832
|
"compatibilityDigest": "sha256:edc3653e5cb34efd97065a6941db16140bbf375a565bbcf329d9d795bb07676f",
|
|
2833
|
-
"contractDigest": "sha256:
|
|
2833
|
+
"contractDigest": "sha256:5ee3923ae7dc5e6633b0ef0be62a4f28611946f5333bb28c9c8229e88c149599"
|
|
2834
2834
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 1,
|
|
3
3
|
"contract": "kungfu-buildchain-site-bundle",
|
|
4
|
-
"generatedAt": "2026-07-
|
|
5
|
-
"publishedAt": "2026-07-
|
|
4
|
+
"generatedAt": "2026-07-19T01:05:14.449Z",
|
|
5
|
+
"publishedAt": "2026-07-19T01:05:14.449Z",
|
|
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": "2a19346a43d0a6be49ca4d6e76fda6c7d3b63c9d",
|
|
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": "2.14.3-alpha.
|
|
40
|
+
"version": "2.14.3-alpha.2",
|
|
41
41
|
"versionSource": "package.json#version"
|
|
42
42
|
},
|
|
43
43
|
"source": {
|
|
@@ -281,7 +281,7 @@
|
|
|
281
281
|
"specifier": "@kungfu-tech/buildchain/publication-control-plane-audit",
|
|
282
282
|
"export": "./publication-control-plane-audit",
|
|
283
283
|
"target": "./packages/core/publication-control-plane-audit.js",
|
|
284
|
-
"digest": "sha256:
|
|
284
|
+
"digest": "sha256:58baa05440b86259812f370aa9645ba26a4df8e986dd57598412b479589286dc",
|
|
285
285
|
"summary": "Read-only publication control-plane snapshot evaluation APIs.",
|
|
286
286
|
"capabilityGroup": "release-passport-trust",
|
|
287
287
|
"audience": [
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 1,
|
|
3
3
|
"contract": "kungfu-buildchain-publication-release-registry",
|
|
4
|
-
"generatedAt": "2026-07-
|
|
5
|
-
"publishedAt": "2026-07-
|
|
4
|
+
"generatedAt": "2026-07-19T01:05:14.449Z",
|
|
5
|
+
"publishedAt": "2026-07-19T01:05:14.449Z",
|
|
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": "2a19346a43d0a6be49ca4d6e76fda6c7d3b63c9d",
|
|
23
23
|
"timestampPolicyDetails": {
|
|
24
24
|
"contract": "kungfu-buildchain-surface-timestamp-policy",
|
|
25
25
|
"timestampFields": [
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
},
|
|
33
33
|
"package": {
|
|
34
34
|
"name": "@kungfu-tech/buildchain",
|
|
35
|
-
"version": "2.14.3-alpha.
|
|
35
|
+
"version": "2.14.3-alpha.2",
|
|
36
36
|
"versionSource": "package.json#version"
|
|
37
37
|
},
|
|
38
38
|
"sourceKind": "package-site-bundle",
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 1,
|
|
3
3
|
"contract": "kungfu-buildchain-site-manifest",
|
|
4
|
-
"generatedAt": "2026-07-
|
|
5
|
-
"publishedAt": "2026-07-
|
|
4
|
+
"generatedAt": "2026-07-19T01:05:14.449Z",
|
|
5
|
+
"publishedAt": "2026-07-19T01:05:14.449Z",
|
|
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": "2a19346a43d0a6be49ca4d6e76fda6c7d3b63c9d",
|
|
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": "2.14.3-alpha.
|
|
40
|
+
"version": "2.14.3-alpha.2",
|
|
41
41
|
"versionSource": "package.json#version"
|
|
42
42
|
},
|
|
43
43
|
"entrypoint": "buildchain-site.json",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kungfu-tech/buildchain",
|
|
3
|
-
"version": "2.14.3-alpha.
|
|
3
|
+
"version": "2.14.3-alpha.2",
|
|
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",
|
|
@@ -76,8 +76,6 @@ export function evaluatePublicationControlPlaneSnapshot({
|
|
|
76
76
|
branchPolicy.requiredStatusCheck === requiredStatusCheck &&
|
|
77
77
|
branchPolicy.requiredStatusChecks.includes(requiredStatusCheck) &&
|
|
78
78
|
branchPolicy.requiredCheckPassed === true &&
|
|
79
|
-
/^[0-9a-f]{40}$/i.test(String(branchPolicy.requiredCheckSha || "")) &&
|
|
80
|
-
branchPolicy.requiredCheckSha === branchPolicy.pullRequestHeadSha &&
|
|
81
79
|
branchPolicy.sourceSha === branchPolicy.headSha &&
|
|
82
80
|
branchPolicy.mergedPullRequest === true &&
|
|
83
81
|
branchPolicy.baseRef === branch &&
|
|
@@ -278,10 +278,7 @@ function main() {
|
|
|
278
278
|
const independentApprovals = [...latestReviews.values()].filter((review) =>
|
|
279
279
|
review.state === "APPROVED" && review.user?.login !== mergedPullRequest?.user?.login
|
|
280
280
|
);
|
|
281
|
-
const
|
|
282
|
-
const checkRuns = /^[0-9a-f]{40}$/.test(pullRequestHeadSha)
|
|
283
|
-
? githubJson(`repos/${repository}/commits/${pullRequestHeadSha}/check-runs?per_page=100`, "merged pull-request head check runs")
|
|
284
|
-
: { check_runs: [] };
|
|
281
|
+
const checkRuns = githubJson(`repos/${repository}/commits/${sourceSha}/check-runs?per_page=100`, "source check runs");
|
|
285
282
|
const requiredStatusCheckPolicy = branchState.protection?.required_status_checks || {};
|
|
286
283
|
const requiredStatusChecks = requiredStatusCheckPolicy.contexts || [];
|
|
287
284
|
const requiredCheckSource = (requiredStatusCheckPolicy.checks || []).find((entry) => entry.context === requiredStatusCheck);
|
|
@@ -298,12 +295,10 @@ function main() {
|
|
|
298
295
|
(!requiredCheckSource?.app_id || entry.app?.id === requiredCheckSource.app_id)
|
|
299
296
|
),
|
|
300
297
|
requiredCheckAppId: requiredCheckSource?.app_id || 0,
|
|
301
|
-
requiredCheckSha: pullRequestHeadSha,
|
|
302
298
|
sourceSha,
|
|
303
299
|
headSha: String(branchState.commit?.sha || "").toLowerCase(),
|
|
304
300
|
mergedPullRequest: Boolean(mergedPullRequest),
|
|
305
301
|
pullRequestNumber: mergedPullRequest?.number || 0,
|
|
306
|
-
pullRequestHeadSha,
|
|
307
302
|
baseRef: mergedPullRequest?.base?.ref || "",
|
|
308
303
|
headRepository: mergedPullRequest?.head?.repo?.full_name || "",
|
|
309
304
|
approvalCount: independentApprovals.length,
|
|
@@ -90,34 +90,29 @@ function publicOutputs(outputBlock) {
|
|
|
90
90
|
return [...routed, ...forwarded].join("\n");
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
-
function forwardedInputs(inputNames
|
|
94
|
-
return inputNames
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
}
|
|
117
|
-
return routed
|
|
118
|
-
? ` ${name}: \${{ needs.resolve-promotion.outputs.${routed} }}`
|
|
119
|
-
: ` ${name}: \${{ inputs.${name} }}`;
|
|
120
|
-
}).join("\n");
|
|
93
|
+
function forwardedInputs(inputNames) {
|
|
94
|
+
return inputNames.map((name) => {
|
|
95
|
+
const routed = {
|
|
96
|
+
"buildchain-ref": "runtime-ref",
|
|
97
|
+
"buildchain-contract-lock-path": "contract-lock-path",
|
|
98
|
+
channel: "publication-channel",
|
|
99
|
+
"target-ref": "target-ref",
|
|
100
|
+
"promotion-router-ref": "router-ref",
|
|
101
|
+
"promotion-router-sha": "router-sha",
|
|
102
|
+
"promotion-shell-ref": "shell-ref",
|
|
103
|
+
"promotion-shell-sha": "shell-sha",
|
|
104
|
+
"promotion-runtime-ref": "runtime-ref",
|
|
105
|
+
"promotion-runtime-sha": "runtime-sha",
|
|
106
|
+
"promotion-contract-lock-path": "contract-lock-path",
|
|
107
|
+
"promotion-contract-lock-digest": "contract-lock-digest",
|
|
108
|
+
"promotion-publication-channel": "publication-channel",
|
|
109
|
+
"promotion-target-ref": "target-ref",
|
|
110
|
+
"promotion-override-used": "override-used",
|
|
111
|
+
}[name];
|
|
112
|
+
return routed
|
|
113
|
+
? ` ${name}: \${{ needs.resolve-promotion.outputs.${routed} }}`
|
|
114
|
+
: ` ${name}: \${{ inputs.${name} }}`;
|
|
115
|
+
}).join("\n");
|
|
121
116
|
}
|
|
122
117
|
|
|
123
118
|
function validateWorkflowRoute(name, route, expectedLogicalRef) {
|
|
@@ -131,9 +126,6 @@ function validateWorkflowRoute(name, route, expectedLogicalRef) {
|
|
|
131
126
|
if (!/^(?:v[0-9]+(?:-alpha)?|[0-9a-f]{40})$/.test(route.callRef || "")) {
|
|
132
127
|
throw new Error(`promotion shell ${name} callRef must be an official channel ref or exact SHA`);
|
|
133
128
|
}
|
|
134
|
-
if (typeof route.forwardInternalInputs !== "boolean") {
|
|
135
|
-
throw new Error(`promotion shell ${name} forwardInternalInputs must be boolean`);
|
|
136
|
-
}
|
|
137
129
|
return { ...route };
|
|
138
130
|
}
|
|
139
131
|
|
|
@@ -153,13 +145,11 @@ export function generateChannelPromotionWorkflow(source, { major = 2, shellRouti
|
|
|
153
145
|
logicalRef: `v${major}-alpha`,
|
|
154
146
|
callRef: `v${major}-alpha`,
|
|
155
147
|
workflowPath: ".github/workflows/.release-candidate-promote.yml",
|
|
156
|
-
forwardInternalInputs: true,
|
|
157
148
|
},
|
|
158
149
|
stable: {
|
|
159
150
|
logicalRef: `v${major}`,
|
|
160
151
|
callRef: `v${major}`,
|
|
161
152
|
workflowPath: ".github/workflows/.release-candidate-promote.yml",
|
|
162
|
-
forwardInternalInputs: true,
|
|
163
153
|
},
|
|
164
154
|
};
|
|
165
155
|
const alphaRoute = validateWorkflowRoute("alpha", routes.alpha, `v${major}-alpha`);
|
|
@@ -171,12 +161,7 @@ export function generateChannelPromotionWorkflow(source, { major = 2, shellRouti
|
|
|
171
161
|
for (const required of ["buildchain-ref", "buildchain-contract-lock-path", "channel", "target-ref", ...internalInputs]) {
|
|
172
162
|
if (!inputNames.includes(required)) throw new Error(`advanced promotion workflow missing input: ${required}`);
|
|
173
163
|
}
|
|
174
|
-
const
|
|
175
|
-
includeInternal: alphaRoute.forwardInternalInputs,
|
|
176
|
-
});
|
|
177
|
-
const stableForwarded = forwardedInputs(inputNames, {
|
|
178
|
-
includeInternal: stableRoute.forwardInternalInputs,
|
|
179
|
-
});
|
|
164
|
+
const forwarded = forwardedInputs(inputNames);
|
|
180
165
|
return `# Generated by scripts/generate-channel-promotion-workflow.mjs. Do not edit directly.
|
|
181
166
|
name: Release Candidate Promote
|
|
182
167
|
|
|
@@ -371,7 +356,7 @@ jobs:
|
|
|
371
356
|
id-token: write
|
|
372
357
|
issues: write
|
|
373
358
|
with:
|
|
374
|
-
${
|
|
359
|
+
${forwarded}
|
|
375
360
|
secrets: inherit
|
|
376
361
|
|
|
377
362
|
stable:
|
|
@@ -386,7 +371,7 @@ ${alphaForwarded}
|
|
|
386
371
|
id-token: write
|
|
387
372
|
issues: write
|
|
388
373
|
with:
|
|
389
|
-
${
|
|
374
|
+
${forwarded}
|
|
390
375
|
secrets: inherit
|
|
391
376
|
`;
|
|
392
377
|
}
|
|
@@ -23,10 +23,12 @@ function targetIntent(targetRef, requestedPublicationChannel = "") {
|
|
|
23
23
|
if (requested && requested !== target.publicationChannel) {
|
|
24
24
|
throw new Error(`promotion channel ${requested} does not match target ref ${ref} (${target.publicationChannel})`);
|
|
25
25
|
}
|
|
26
|
+
const match = ref.match(target.pattern);
|
|
26
27
|
return {
|
|
27
28
|
targetRef: ref,
|
|
28
29
|
publicationChannel: target.publicationChannel,
|
|
29
30
|
shellChannel: target.shellChannel,
|
|
31
|
+
targetMajor: match?.[1] ? Number(match[1]) : undefined,
|
|
30
32
|
};
|
|
31
33
|
}
|
|
32
34
|
|
|
@@ -53,6 +55,11 @@ export function resolvePromotionChannel({
|
|
|
53
55
|
`promotion target ${intent.targetRef} requires ${intent.shellChannel} shell/runtime, got ${selected.channel}`,
|
|
54
56
|
);
|
|
55
57
|
}
|
|
58
|
+
if (Number.isInteger(intent.targetMajor) && selected.major !== intent.targetMajor) {
|
|
59
|
+
throw new Error(
|
|
60
|
+
`promotion target ${intent.targetRef} requires Buildchain v${intent.targetMajor}, got v${selected.major}`,
|
|
61
|
+
);
|
|
62
|
+
}
|
|
56
63
|
const shellRef = intent.shellChannel === "alpha" ? `v${selected.major}-alpha` : `v${selected.major}`;
|
|
57
64
|
return {
|
|
58
65
|
targetRef: intent.targetRef,
|
|
@@ -223,7 +223,7 @@ export async function runStableCandidatePatrol(optionsInput = {}, clientInput) {
|
|
|
223
223
|
`Buildchain Stable Candidate Patrol human release-now for ${selection.candidate.version}`,
|
|
224
224
|
);
|
|
225
225
|
}
|
|
226
|
-
await client.ensureBranch(refs.sourceRef, selection.candidate.sha
|
|
226
|
+
await client.ensureBranch(refs.sourceRef, selection.candidate.sha);
|
|
227
227
|
const pullRequest = await client.ensurePromotionPullRequest({
|
|
228
228
|
head: refs.sourceRef,
|
|
229
229
|
base: refs.targetRef,
|
|
@@ -371,64 +371,15 @@ export function createGitHubStableCandidateClient({ repository: repositoryInput,
|
|
|
371
371
|
},
|
|
372
372
|
});
|
|
373
373
|
},
|
|
374
|
-
async ensureBranch(ref, candidateSha
|
|
374
|
+
async ensureBranch(ref, candidateSha) {
|
|
375
375
|
const current = await api(`/repos/${owner}/${repo}/git/ref/heads/${encodeRef(ref)}`, { allow404: true });
|
|
376
|
-
if (!
|
|
377
|
-
if (!current) {
|
|
378
|
-
return api(`/repos/${owner}/${repo}/git/refs`, { method: "POST", body: { ref: `refs/heads/${ref}`, sha: candidateSha } });
|
|
379
|
-
}
|
|
380
|
-
if (current.object.sha !== candidateSha) {
|
|
381
|
-
throw new Error(`source-lock branch ${ref} already points to ${current.object.sha}, not ${candidateSha}`);
|
|
382
|
-
}
|
|
383
|
-
return current;
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
const candidateCommit = await api(`/repos/${owner}/${repo}/git/commits/${candidateSha}`);
|
|
387
|
-
let sourceSha = current?.object?.sha || candidateSha;
|
|
388
|
-
if (sourceSha !== candidateSha) {
|
|
389
|
-
const sourceCommit = await api(`/repos/${owner}/${repo}/git/commits/${sourceSha}`);
|
|
390
|
-
if (sourceCommit.tree?.sha !== candidateCommit.tree?.sha) {
|
|
391
|
-
throw new Error(`source-lock branch ${ref} no longer preserves candidate ${candidateSha} tree`);
|
|
392
|
-
}
|
|
393
|
-
const candidateLineage = await api(`/repos/${owner}/${repo}/compare/${candidateSha}...${sourceSha}`);
|
|
394
|
-
if (!["ahead", "identical"].includes(candidateLineage.status)) {
|
|
395
|
-
throw new Error(`source-lock branch ${ref} no longer descends from candidate ${candidateSha}`);
|
|
396
|
-
}
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
const target = await api(`/repos/${owner}/${repo}/git/ref/heads/${encodeRef(targetRef)}`);
|
|
400
|
-
const targetSha = target.object?.sha || "";
|
|
401
|
-
const targetLineage = await api(`/repos/${owner}/${repo}/compare/${targetSha}...${sourceSha}`);
|
|
402
|
-
if (["ahead", "identical"].includes(targetLineage.status)) {
|
|
403
|
-
if (current) return current;
|
|
376
|
+
if (!current) {
|
|
404
377
|
return api(`/repos/${owner}/${repo}/git/refs`, { method: "POST", body: { ref: `refs/heads/${ref}`, sha: candidateSha } });
|
|
405
378
|
}
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
const reconciliation = await api(`/repos/${owner}/${repo}/git/commits`, {
|
|
409
|
-
method: "POST",
|
|
410
|
-
body: {
|
|
411
|
-
message: [
|
|
412
|
-
"chore(buildchain): reconcile stable source-lock ancestry",
|
|
413
|
-
"",
|
|
414
|
-
`Preserve the exact candidate tree from ${candidateSha} while admitting ${targetRef} at ${targetSha}.`,
|
|
415
|
-
"",
|
|
416
|
-
"Signed-off-by: Buildchain Patrol <buildchain-patrol@kungfu.link>",
|
|
417
|
-
].join("\n"),
|
|
418
|
-
tree: candidateCommit.tree.sha,
|
|
419
|
-
parents: [sourceSha, targetSha],
|
|
420
|
-
author: identity,
|
|
421
|
-
committer: identity,
|
|
422
|
-
},
|
|
423
|
-
});
|
|
424
|
-
sourceSha = reconciliation.sha;
|
|
425
|
-
if (!current) {
|
|
426
|
-
return api(`/repos/${owner}/${repo}/git/refs`, { method: "POST", body: { ref: `refs/heads/${ref}`, sha: sourceSha } });
|
|
379
|
+
if (current.object.sha !== candidateSha) {
|
|
380
|
+
throw new Error(`source-lock branch ${ref} already points to ${current.object.sha}, not ${candidateSha}`);
|
|
427
381
|
}
|
|
428
|
-
return
|
|
429
|
-
method: "PATCH",
|
|
430
|
-
body: { sha: sourceSha, force: false },
|
|
431
|
-
});
|
|
382
|
+
return current;
|
|
432
383
|
},
|
|
433
384
|
async ensurePromotionPullRequest({ head, base, title, body }) {
|
|
434
385
|
const open = await api(`/repos/${owner}/${repo}/pulls?state=open&head=${encodeURIComponent(`${owner}:${head}`)}&base=${encodeURIComponent(base)}&per_page=20`);
|