@kungfu-tech/buildchain 3.0.8 → 3.0.9-alpha.1
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 +8 -7
- package/bin/buildchain.mjs +34 -33
- package/bin/internal/trust-release-release-handlers.mjs +5 -0
- package/contracts/fixtures/kfd-adopter-release-v1/kfd-4-perspective.json +16 -0
- package/contracts/fixtures/kfd-adopter-release-v1/kfd-4-replay.json +57 -0
- package/contracts/release-cut-v1.schema.json +1 -0
- package/contracts/release-train-transition-v1.schema.json +1 -0
- package/dist/site/agent-index.json +0 -1
- package/dist/site/artifact-schemas.json +0 -2
- package/dist/site/badge-endpoint-registry.json +9 -9
- package/dist/site/badges/v1/kfd-8/aligned.json +1 -1
- package/dist/site/badges/v1/kfd-8/declared.json +1 -1
- package/dist/site/badges/v1/kfd-8/downgraded.json +1 -1
- package/dist/site/badges/v1/kfd-8/draft.json +1 -1
- package/dist/site/badges/v1/kfd-8/failed.json +1 -1
- package/dist/site/badges/v1/kfd-8/missing.json +1 -1
- package/dist/site/badges/v1/kfd-8/passed.json +1 -1
- package/dist/site/badges/v1/kfd-8/planned.json +1 -1
- package/dist/site/buildchain-contract.json +38 -34
- package/dist/site/buildchain-site.json +59 -39
- package/dist/site/capability-registry.json +1 -1
- package/dist/site/cli-registry.json +8 -7
- package/dist/site/controller-registry.json +13 -5
- package/dist/site/kfd-claims.json +35 -8
- package/dist/site/kfd-upstream-aggregate.json +10 -10
- package/dist/site/manual-registry.json +9 -9
- package/dist/site/node-api-registry.json +1143 -320
- package/dist/site/page-registry.json +46 -26
- package/dist/site/public-surface-audit.json +19 -9
- package/dist/site/publication-registry.json +4 -4
- package/dist/site/release-passport-check-manifest.json +33 -5
- package/dist/site/release-provenance.json +2 -1
- package/dist/site/schemas/release-passport-v1.schema.json +3 -0
- package/dist/site/site-manifest.json +13 -13
- package/dist/site/workflow-registry.json +19 -9
- package/docs/aws-us-elastic-runner-burst-plane.md +15 -3
- package/docs/cli-reference.md +6 -6
- package/docs/cli.md +2 -3
- package/docs/dev-alpha-candidate-patrol.md +45 -20
- package/docs/dev-delivery-warrant.md +12 -2
- package/docs/kfd-support.md +7 -7
- package/docs/node-api-reference.md +295 -246
- package/docs/release-passport.md +25 -12
- package/docs/release-train.md +54 -4
- package/docs/reusable-build-surface.md +26 -9
- package/docs/runtime-train-validation.md +6 -0
- package/docs/versioning.md +3 -2
- package/package.json +4 -3
- package/packages/core/artifact-verification-envelope.js +114 -0
- package/packages/core/buildchain-channel-identity.js +119 -0
- package/packages/core/buildchain-compatibility-proof.js +32 -0
- package/packages/core/buildchain-contract.js +7 -0
- package/packages/core/controller-evidence.js +2 -1
- package/packages/core/dev-alpha-active-release-train.js +460 -0
- package/packages/core/dev-alpha-candidate-selection.js +181 -0
- package/packages/core/dev-delivery-warrant.js +15 -4
- package/packages/core/index.js +4 -5
- package/packages/core/kfd-adopter-manifest.js +390 -0
- package/packages/core/kfd-product-gates.js +0 -300
- package/packages/core/release-blocker-priority.js +192 -0
- package/packages/core/release-passport-contract.js +219 -6
- package/packages/core/release-passport.js +139 -242
- package/packages/core/release-train.js +220 -0
- package/scripts/aws-macos-jit-controller-core.mjs +76 -0
- package/scripts/aws-macos-jit-controller.mjs +29 -0
- package/scripts/aws-macos-jit-source-rebind.mjs +503 -0
- package/scripts/buildchain-channel-router.mjs +18 -2
- package/scripts/buildchain-cli-help.mjs +4 -2
- package/scripts/buildchain-contract-lock.mjs +11 -1
- package/scripts/check-inventory.mjs +4 -4
- package/scripts/dev-alpha-candidate-patrol.mjs +196 -201
- package/scripts/generate-buildchain-kfd-witnesses.mjs +88 -94
- package/scripts/generate-channel-build-workflow.mjs +78 -31
- package/scripts/generate-site-bundle.mjs +1 -8
- package/scripts/promotion-channel-router.mjs +5 -3
- package/scripts/release-train-self-dogfood.mjs +579 -0
- package/scripts/runtime-ref-core.mjs +4 -17
- package/scripts/site-capability-metadata.mjs +3 -1
- package/dist/site/schemas/kfd-support-projection-v1.schema.json +0 -106
|
@@ -0,0 +1,503 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
|
|
3
|
+
import { AWS_MACOS_JIT_CONTROLLER_CONTRACT } from "./aws-macos-jit-controller-core.mjs";
|
|
4
|
+
import {
|
|
5
|
+
assertMacosBudgetLaunchGate,
|
|
6
|
+
assertOwnership,
|
|
7
|
+
awsArgs,
|
|
8
|
+
awsJson,
|
|
9
|
+
commandResult,
|
|
10
|
+
ghJson,
|
|
11
|
+
requireSuccess,
|
|
12
|
+
} from "./aws-macos-jit-controller-runtime.mjs";
|
|
13
|
+
|
|
14
|
+
const MACOS_JIT_RUNNER_LABEL_PREFIX = "aws-us-ec2-macos-jit-";
|
|
15
|
+
|
|
16
|
+
function sourceRefName(ref) {
|
|
17
|
+
return ref.replace(/^refs\/heads\//, "");
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function sourceRefApiPath(plan) {
|
|
21
|
+
return `repos/${plan.repository}/git/refs/heads/${sourceRefName(plan.source.ref)}`;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function priorOwnershipPlan(plan) {
|
|
25
|
+
return {
|
|
26
|
+
campaign: plan.campaign,
|
|
27
|
+
source: plan.previousSource,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function campaignRunsWithNoWork(plan) {
|
|
32
|
+
const branch = sourceRefName(plan.source.ref);
|
|
33
|
+
const response = ghJson(
|
|
34
|
+
[
|
|
35
|
+
"api",
|
|
36
|
+
`repos/${plan.repository}/actions/workflows/${plan.github.workflowId}/runs?event=workflow_dispatch&branch=${encodeURIComponent(branch)}&per_page=100`,
|
|
37
|
+
],
|
|
38
|
+
undefined,
|
|
39
|
+
"macOS campaign workflow run lookup",
|
|
40
|
+
);
|
|
41
|
+
const matching = (response.workflow_runs || []).filter(
|
|
42
|
+
(run) => run.head_branch === branch,
|
|
43
|
+
);
|
|
44
|
+
if (Number(response.total_count || matching.length) !== matching.length) {
|
|
45
|
+
throw new Error("macOS campaign workflow run inventory is incomplete");
|
|
46
|
+
}
|
|
47
|
+
const receipts = [];
|
|
48
|
+
for (const run of matching) {
|
|
49
|
+
if (run.head_sha !== plan.previousSource.sha) {
|
|
50
|
+
throw new Error(
|
|
51
|
+
`macOS campaign run ${run.id} is bound to an unexpected source`,
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
const jobs = ghJson(
|
|
55
|
+
[
|
|
56
|
+
"api",
|
|
57
|
+
`repos/${plan.repository}/actions/runs/${run.id}/jobs?filter=all&per_page=100`,
|
|
58
|
+
],
|
|
59
|
+
undefined,
|
|
60
|
+
`macOS campaign run ${run.id} job lookup`,
|
|
61
|
+
);
|
|
62
|
+
const artifacts = ghJson(
|
|
63
|
+
["api", `repos/${plan.repository}/actions/runs/${run.id}/artifacts`],
|
|
64
|
+
undefined,
|
|
65
|
+
`macOS campaign run ${run.id} artifact lookup`,
|
|
66
|
+
);
|
|
67
|
+
const jobCount = Number(jobs.total_count || (jobs.jobs || []).length || 0);
|
|
68
|
+
const artifactCount = Number(
|
|
69
|
+
artifacts.total_count || (artifacts.artifacts || []).length || 0,
|
|
70
|
+
);
|
|
71
|
+
if (jobCount !== 0 || artifactCount !== 0) {
|
|
72
|
+
throw new Error(
|
|
73
|
+
`macOS campaign run ${run.id} already has jobs or artifacts`,
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
receipts.push({
|
|
77
|
+
runId: String(run.id),
|
|
78
|
+
status: String(run.status || "unknown"),
|
|
79
|
+
conclusion: String(run.conclusion || ""),
|
|
80
|
+
jobCount,
|
|
81
|
+
artifactCount,
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
return receipts;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function assertSourceLineage(plan) {
|
|
88
|
+
const currentRef = ghJson(
|
|
89
|
+
["api", sourceRefApiPath(plan)],
|
|
90
|
+
undefined,
|
|
91
|
+
"macOS campaign source ref preflight",
|
|
92
|
+
);
|
|
93
|
+
if (currentRef.object?.sha !== plan.previousSource.sha) {
|
|
94
|
+
throw new Error("macOS campaign source ref is not at previousSourceSha");
|
|
95
|
+
}
|
|
96
|
+
const nextCommit = ghJson(
|
|
97
|
+
["api", `repos/${plan.repository}/commits/${plan.source.sha}`],
|
|
98
|
+
undefined,
|
|
99
|
+
"macOS campaign replacement source preflight",
|
|
100
|
+
);
|
|
101
|
+
if (String(nextCommit.sha || "").toLowerCase() !== plan.source.sha) {
|
|
102
|
+
throw new Error("GitHub did not resolve the replacement source SHA");
|
|
103
|
+
}
|
|
104
|
+
const comparison = ghJson(
|
|
105
|
+
[
|
|
106
|
+
"api",
|
|
107
|
+
`repos/${plan.repository}/compare/${plan.previousSource.sha}...${plan.source.sha}`,
|
|
108
|
+
],
|
|
109
|
+
undefined,
|
|
110
|
+
"macOS campaign forward-source preflight",
|
|
111
|
+
);
|
|
112
|
+
if (
|
|
113
|
+
comparison.status !== "ahead" ||
|
|
114
|
+
comparison.merge_base_commit?.sha !== plan.previousSource.sha
|
|
115
|
+
) {
|
|
116
|
+
throw new Error(
|
|
117
|
+
"replacement source must be a strict descendant of previousSourceSha",
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
return {
|
|
121
|
+
currentRefSha: currentRef.object.sha,
|
|
122
|
+
comparisonStatus: comparison.status,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function readOwnedCampaignResources(plan, profile) {
|
|
127
|
+
const priorPlan = priorOwnershipPlan(plan);
|
|
128
|
+
const host = awsJson(
|
|
129
|
+
plan,
|
|
130
|
+
profile,
|
|
131
|
+
[
|
|
132
|
+
"ec2",
|
|
133
|
+
"describe-hosts",
|
|
134
|
+
"--host-ids",
|
|
135
|
+
plan.aws.hostId,
|
|
136
|
+
"--output",
|
|
137
|
+
"json",
|
|
138
|
+
],
|
|
139
|
+
"macOS campaign source rebind host preflight",
|
|
140
|
+
).Hosts?.[0];
|
|
141
|
+
if (!host || host.State !== "available") {
|
|
142
|
+
throw new Error("macOS campaign source rebind host is not available");
|
|
143
|
+
}
|
|
144
|
+
assertOwnership(host.Tags, priorPlan);
|
|
145
|
+
const instance = awsJson(
|
|
146
|
+
plan,
|
|
147
|
+
profile,
|
|
148
|
+
[
|
|
149
|
+
"ec2",
|
|
150
|
+
"describe-instances",
|
|
151
|
+
"--instance-ids",
|
|
152
|
+
plan.aws.instanceId,
|
|
153
|
+
"--output",
|
|
154
|
+
"json",
|
|
155
|
+
],
|
|
156
|
+
"macOS campaign source rebind instance preflight",
|
|
157
|
+
).Reservations?.[0]?.Instances?.[0];
|
|
158
|
+
if (
|
|
159
|
+
!instance ||
|
|
160
|
+
instance.State?.Name !== "running" ||
|
|
161
|
+
instance.Placement?.HostId !== plan.aws.hostId
|
|
162
|
+
) {
|
|
163
|
+
throw new Error("macOS campaign source rebind instance identity mismatch");
|
|
164
|
+
}
|
|
165
|
+
assertOwnership(instance.Tags, priorPlan);
|
|
166
|
+
if (
|
|
167
|
+
(instance.Tags || []).some(
|
|
168
|
+
(entry) =>
|
|
169
|
+
entry.Key === "kungfu:jit-parameter" && String(entry.Value || ""),
|
|
170
|
+
)
|
|
171
|
+
) {
|
|
172
|
+
throw new Error("macOS campaign instance already has JIT residue");
|
|
173
|
+
}
|
|
174
|
+
const volumeIds = (instance.BlockDeviceMappings || [])
|
|
175
|
+
.map((mapping) => mapping.Ebs?.VolumeId)
|
|
176
|
+
.filter(Boolean);
|
|
177
|
+
if (volumeIds.length !== 1) {
|
|
178
|
+
throw new Error("macOS campaign must have exactly one attached volume");
|
|
179
|
+
}
|
|
180
|
+
const volume = awsJson(
|
|
181
|
+
plan,
|
|
182
|
+
profile,
|
|
183
|
+
[
|
|
184
|
+
"ec2",
|
|
185
|
+
"describe-volumes",
|
|
186
|
+
"--volume-ids",
|
|
187
|
+
volumeIds[0],
|
|
188
|
+
"--output",
|
|
189
|
+
"json",
|
|
190
|
+
],
|
|
191
|
+
"macOS campaign source rebind volume preflight",
|
|
192
|
+
).Volumes?.[0];
|
|
193
|
+
if (!volume || volume.State !== "in-use" || volume.Encrypted !== true) {
|
|
194
|
+
throw new Error("macOS campaign source rebind volume identity mismatch");
|
|
195
|
+
}
|
|
196
|
+
assertOwnership(volume.Tags, priorPlan);
|
|
197
|
+
return { host, instance, volume };
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function assertExactCampaignInventory(plan, profile, resources) {
|
|
201
|
+
const ownedHosts =
|
|
202
|
+
awsJson(
|
|
203
|
+
plan,
|
|
204
|
+
profile,
|
|
205
|
+
[
|
|
206
|
+
"ec2",
|
|
207
|
+
"describe-hosts",
|
|
208
|
+
"--filter",
|
|
209
|
+
`Name=tag:kungfu:campaign-id,Values=${plan.campaign.id}`,
|
|
210
|
+
"--output",
|
|
211
|
+
"json",
|
|
212
|
+
],
|
|
213
|
+
"macOS campaign owned host inventory",
|
|
214
|
+
).Hosts || [];
|
|
215
|
+
const ownedInstances = (
|
|
216
|
+
awsJson(
|
|
217
|
+
plan,
|
|
218
|
+
profile,
|
|
219
|
+
[
|
|
220
|
+
"ec2",
|
|
221
|
+
"describe-instances",
|
|
222
|
+
"--filters",
|
|
223
|
+
`Name=tag:kungfu:campaign-id,Values=${plan.campaign.id}`,
|
|
224
|
+
"Name=instance-state-name,Values=pending,running,stopping,stopped,shutting-down",
|
|
225
|
+
"--output",
|
|
226
|
+
"json",
|
|
227
|
+
],
|
|
228
|
+
"macOS campaign owned instance inventory",
|
|
229
|
+
).Reservations || []
|
|
230
|
+
).flatMap((reservation) => reservation.Instances || []);
|
|
231
|
+
const ownedVolumes =
|
|
232
|
+
awsJson(
|
|
233
|
+
plan,
|
|
234
|
+
profile,
|
|
235
|
+
[
|
|
236
|
+
"ec2",
|
|
237
|
+
"describe-volumes",
|
|
238
|
+
"--filters",
|
|
239
|
+
`Name=tag:kungfu:campaign-id,Values=${plan.campaign.id}`,
|
|
240
|
+
"--output",
|
|
241
|
+
"json",
|
|
242
|
+
],
|
|
243
|
+
"macOS campaign owned volume inventory",
|
|
244
|
+
).Volumes || [];
|
|
245
|
+
if (
|
|
246
|
+
ownedHosts.length !== 1 ||
|
|
247
|
+
ownedHosts[0].HostId !== resources.host.HostId ||
|
|
248
|
+
ownedInstances.length !== 1 ||
|
|
249
|
+
ownedInstances[0].InstanceId !== resources.instance.InstanceId ||
|
|
250
|
+
ownedVolumes.length !== 1 ||
|
|
251
|
+
ownedVolumes[0].VolumeId !== resources.volume.VolumeId
|
|
252
|
+
) {
|
|
253
|
+
throw new Error("macOS campaign resource inventory is not exact");
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
function assertZeroCampaignResidue(plan, profile) {
|
|
258
|
+
const parameters =
|
|
259
|
+
awsJson(
|
|
260
|
+
plan,
|
|
261
|
+
profile,
|
|
262
|
+
[
|
|
263
|
+
"ssm",
|
|
264
|
+
"describe-parameters",
|
|
265
|
+
"--parameter-filters",
|
|
266
|
+
`Key=Name,Option=BeginsWith,Values=/kungfu/burst/macos/${plan.campaign.id}/`,
|
|
267
|
+
"--output",
|
|
268
|
+
"json",
|
|
269
|
+
],
|
|
270
|
+
"macOS campaign JIT parameter inventory",
|
|
271
|
+
).Parameters || [];
|
|
272
|
+
if (parameters.length !== 0) {
|
|
273
|
+
throw new Error("macOS campaign already has SSM JIT parameters");
|
|
274
|
+
}
|
|
275
|
+
const runners =
|
|
276
|
+
ghJson(
|
|
277
|
+
["api", `repos/${plan.repository}/actions/runners?per_page=100`],
|
|
278
|
+
undefined,
|
|
279
|
+
"macOS campaign runner inventory",
|
|
280
|
+
).runners || [];
|
|
281
|
+
const matchingRunners = runners.filter((runner) =>
|
|
282
|
+
(runner.labels || []).some((label) =>
|
|
283
|
+
String(label.name || label).startsWith(MACOS_JIT_RUNNER_LABEL_PREFIX),
|
|
284
|
+
),
|
|
285
|
+
);
|
|
286
|
+
if (matchingRunners.length !== 0) {
|
|
287
|
+
throw new Error("macOS campaign already has a registered JIT runner");
|
|
288
|
+
}
|
|
289
|
+
const evidenceObjectCounts = {};
|
|
290
|
+
for (const sourceSha of [plan.previousSource.sha, plan.source.sha]) {
|
|
291
|
+
const evidence = awsJson(
|
|
292
|
+
plan,
|
|
293
|
+
profile,
|
|
294
|
+
[
|
|
295
|
+
"s3api",
|
|
296
|
+
"list-objects-v2",
|
|
297
|
+
"--bucket",
|
|
298
|
+
plan.aws.evidenceBucket,
|
|
299
|
+
"--prefix",
|
|
300
|
+
`macos/${sourceSha}/`,
|
|
301
|
+
"--max-keys",
|
|
302
|
+
"1",
|
|
303
|
+
"--output",
|
|
304
|
+
"json",
|
|
305
|
+
],
|
|
306
|
+
`macOS campaign evidence inventory for ${sourceSha}`,
|
|
307
|
+
);
|
|
308
|
+
const count = Number(
|
|
309
|
+
evidence.KeyCount || (evidence.Contents || []).length || 0,
|
|
310
|
+
);
|
|
311
|
+
if (count !== 0) {
|
|
312
|
+
throw new Error(
|
|
313
|
+
`macOS campaign source ${sourceSha} already has bootstrap evidence`,
|
|
314
|
+
);
|
|
315
|
+
}
|
|
316
|
+
evidenceObjectCounts[sourceSha] = count;
|
|
317
|
+
}
|
|
318
|
+
return {
|
|
319
|
+
priorRuns: campaignRunsWithNoWork(plan),
|
|
320
|
+
jitParameterCount: parameters.length,
|
|
321
|
+
runnerCount: matchingRunners.length,
|
|
322
|
+
evidenceObjectCounts,
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
function assertMacosJitSourceRebindPreflight(plan, profile) {
|
|
327
|
+
const controlPlane = assertMacosBudgetLaunchGate(plan, profile);
|
|
328
|
+
const source = assertSourceLineage(plan);
|
|
329
|
+
const resources = readOwnedCampaignResources(plan, profile);
|
|
330
|
+
assertExactCampaignInventory(plan, profile, resources);
|
|
331
|
+
const residue = assertZeroCampaignResidue(plan, profile);
|
|
332
|
+
return {
|
|
333
|
+
...controlPlane,
|
|
334
|
+
...source,
|
|
335
|
+
...residue,
|
|
336
|
+
resourceIds: {
|
|
337
|
+
hostId: resources.host.HostId,
|
|
338
|
+
instanceId: resources.instance.InstanceId,
|
|
339
|
+
volumeId: resources.volume.VolumeId,
|
|
340
|
+
},
|
|
341
|
+
};
|
|
342
|
+
}
|
|
343
|
+
function setCampaignSourceTags(plan, profile, sourceSha) {
|
|
344
|
+
requireSuccess(
|
|
345
|
+
commandResult(
|
|
346
|
+
"aws",
|
|
347
|
+
awsArgs(plan, profile, [
|
|
348
|
+
"ec2",
|
|
349
|
+
"create-tags",
|
|
350
|
+
"--resources",
|
|
351
|
+
plan.aws.hostId,
|
|
352
|
+
plan.aws.instanceId,
|
|
353
|
+
plan.aws.volumeId,
|
|
354
|
+
"--tags",
|
|
355
|
+
`Key=kungfu:source-sha,Value=${sourceSha}`,
|
|
356
|
+
]),
|
|
357
|
+
),
|
|
358
|
+
`macOS campaign source tag update to ${sourceSha}`,
|
|
359
|
+
);
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
function updateCampaignSourceRef(plan, sourceSha, force) {
|
|
363
|
+
requireSuccess(
|
|
364
|
+
commandResult("gh", [
|
|
365
|
+
"api",
|
|
366
|
+
"--method",
|
|
367
|
+
"PATCH",
|
|
368
|
+
sourceRefApiPath(plan),
|
|
369
|
+
"-f",
|
|
370
|
+
`sha=${sourceSha}`,
|
|
371
|
+
"-F",
|
|
372
|
+
`force=${force ? "true" : "false"}`,
|
|
373
|
+
]),
|
|
374
|
+
`macOS campaign source ref update to ${sourceSha}`,
|
|
375
|
+
);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
function assertReboundResources(plan, profile) {
|
|
379
|
+
const resources = [
|
|
380
|
+
awsJson(
|
|
381
|
+
plan,
|
|
382
|
+
profile,
|
|
383
|
+
[
|
|
384
|
+
"ec2",
|
|
385
|
+
"describe-hosts",
|
|
386
|
+
"--host-ids",
|
|
387
|
+
plan.aws.hostId,
|
|
388
|
+
"--output",
|
|
389
|
+
"json",
|
|
390
|
+
],
|
|
391
|
+
"macOS campaign rebound host readback",
|
|
392
|
+
).Hosts?.[0],
|
|
393
|
+
awsJson(
|
|
394
|
+
plan,
|
|
395
|
+
profile,
|
|
396
|
+
[
|
|
397
|
+
"ec2",
|
|
398
|
+
"describe-instances",
|
|
399
|
+
"--instance-ids",
|
|
400
|
+
plan.aws.instanceId,
|
|
401
|
+
"--output",
|
|
402
|
+
"json",
|
|
403
|
+
],
|
|
404
|
+
"macOS campaign rebound instance readback",
|
|
405
|
+
).Reservations?.[0]?.Instances?.[0],
|
|
406
|
+
awsJson(
|
|
407
|
+
plan,
|
|
408
|
+
profile,
|
|
409
|
+
[
|
|
410
|
+
"ec2",
|
|
411
|
+
"describe-volumes",
|
|
412
|
+
"--volume-ids",
|
|
413
|
+
plan.aws.volumeId,
|
|
414
|
+
"--output",
|
|
415
|
+
"json",
|
|
416
|
+
],
|
|
417
|
+
"macOS campaign rebound volume readback",
|
|
418
|
+
).Volumes?.[0],
|
|
419
|
+
];
|
|
420
|
+
for (const resource of resources) {
|
|
421
|
+
if (!resource)
|
|
422
|
+
throw new Error("macOS campaign source rebind readback failed");
|
|
423
|
+
assertOwnership(resource.Tags, plan);
|
|
424
|
+
}
|
|
425
|
+
const ref = ghJson(
|
|
426
|
+
["api", sourceRefApiPath(plan)],
|
|
427
|
+
undefined,
|
|
428
|
+
"macOS campaign rebound source ref readback",
|
|
429
|
+
);
|
|
430
|
+
if (ref.object?.sha !== plan.source.sha) {
|
|
431
|
+
throw new Error("macOS campaign rebound source ref readback mismatch");
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
export function executeMacosJitSourceRebind(plan, { profile = "" } = {}) {
|
|
436
|
+
if (
|
|
437
|
+
plan?.contract !== AWS_MACOS_JIT_CONTROLLER_CONTRACT ||
|
|
438
|
+
plan.kind !== "campaign-source-rebind-plan"
|
|
439
|
+
) {
|
|
440
|
+
throw new Error("macOS JIT source rebind plan contract is invalid");
|
|
441
|
+
}
|
|
442
|
+
const preflight = assertMacosJitSourceRebindPreflight(plan, profile);
|
|
443
|
+
const runtimePlan = {
|
|
444
|
+
...plan,
|
|
445
|
+
aws: { ...plan.aws, volumeId: preflight.resourceIds.volumeId },
|
|
446
|
+
};
|
|
447
|
+
let tagsUpdated = false;
|
|
448
|
+
let refUpdated = false;
|
|
449
|
+
try {
|
|
450
|
+
setCampaignSourceTags(runtimePlan, profile, plan.source.sha);
|
|
451
|
+
tagsUpdated = true;
|
|
452
|
+
updateCampaignSourceRef(runtimePlan, plan.source.sha, false);
|
|
453
|
+
refUpdated = true;
|
|
454
|
+
assertReboundResources(runtimePlan, profile);
|
|
455
|
+
} catch (error) {
|
|
456
|
+
const rollbackFailures = [];
|
|
457
|
+
const rollback = (operation) => {
|
|
458
|
+
try {
|
|
459
|
+
operation();
|
|
460
|
+
} catch (rollbackError) {
|
|
461
|
+
rollbackFailures.push(rollbackError.message || String(rollbackError));
|
|
462
|
+
}
|
|
463
|
+
};
|
|
464
|
+
if (refUpdated) {
|
|
465
|
+
rollback(() =>
|
|
466
|
+
updateCampaignSourceRef(runtimePlan, plan.previousSource.sha, true),
|
|
467
|
+
);
|
|
468
|
+
}
|
|
469
|
+
if (tagsUpdated) {
|
|
470
|
+
rollback(() =>
|
|
471
|
+
setCampaignSourceTags(runtimePlan, profile, plan.previousSource.sha),
|
|
472
|
+
);
|
|
473
|
+
}
|
|
474
|
+
const detail = rollbackFailures.length
|
|
475
|
+
? `; compensated rollback failed: ${rollbackFailures.join("; ")}`
|
|
476
|
+
: "; compensated rollback completed";
|
|
477
|
+
throw new Error(`${error.message || error}${detail}`);
|
|
478
|
+
}
|
|
479
|
+
return {
|
|
480
|
+
schemaVersion: 1,
|
|
481
|
+
contract: AWS_MACOS_JIT_CONTROLLER_CONTRACT,
|
|
482
|
+
kind: "campaign-source-rebind-result",
|
|
483
|
+
status: "rebound-zero-allocation",
|
|
484
|
+
repository: plan.repository,
|
|
485
|
+
campaign: plan.campaign,
|
|
486
|
+
previousSource: plan.previousSource,
|
|
487
|
+
source: plan.source,
|
|
488
|
+
github: {
|
|
489
|
+
workflowId: plan.github.workflowId,
|
|
490
|
+
workflowState: preflight.workflowState,
|
|
491
|
+
sourceRefUpdated: true,
|
|
492
|
+
},
|
|
493
|
+
aws: {
|
|
494
|
+
region: plan.aws.region,
|
|
495
|
+
...preflight.resourceIds,
|
|
496
|
+
sourceTagsUpdated: true,
|
|
497
|
+
},
|
|
498
|
+
preflight,
|
|
499
|
+
paidCapacityCreated: false,
|
|
500
|
+
jobDispatched: false,
|
|
501
|
+
planDigest: plan.digest,
|
|
502
|
+
};
|
|
503
|
+
}
|
|
@@ -105,8 +105,24 @@ export function resolveBuildchainChannel({
|
|
|
105
105
|
if (channel !== "auto" && explicitRef.kind !== "override" && channel !== explicitRef.kind) {
|
|
106
106
|
throw new Error(`buildchain-channel=${channel} conflicts with buildchain-ref=${explicitRef.ref}`);
|
|
107
107
|
}
|
|
108
|
-
if (
|
|
109
|
-
|
|
108
|
+
if (explicitRef.kind === "override") {
|
|
109
|
+
const lane = resolveBuildchainChannel({
|
|
110
|
+
requestedChannel: channel,
|
|
111
|
+
requestedRef: "",
|
|
112
|
+
publishChannel,
|
|
113
|
+
eventName,
|
|
114
|
+
gitRef,
|
|
115
|
+
releasePrerelease,
|
|
116
|
+
routerRef: `v${major}`,
|
|
117
|
+
packageVersion,
|
|
118
|
+
});
|
|
119
|
+
return {
|
|
120
|
+
...lane,
|
|
121
|
+
major,
|
|
122
|
+
buildchainRef: explicitRef.ref,
|
|
123
|
+
selectionSource: "explicit-buildchain-ref+channel-evidence",
|
|
124
|
+
reason: `trusted runtime override ${explicitRef.ref} bound to ${lane.channel}`,
|
|
125
|
+
};
|
|
110
126
|
}
|
|
111
127
|
return {
|
|
112
128
|
channel: explicitRef.kind,
|
|
@@ -67,6 +67,7 @@ export const BUILDCHAIN_USAGE = `Usage:
|
|
|
67
67
|
[--kfd-3-prebuild-witness-json <json-or-path>]...
|
|
68
68
|
[--kfd-3-artifact-witness-json <json-or-path>]...
|
|
69
69
|
[--kfd-3-artifact-verify-cmd <command>]
|
|
70
|
+
[--kfd-adopter-manifest-json <json-or-path>]
|
|
70
71
|
[--kfd-support-matrix-json <json-or-path>]
|
|
71
72
|
[--kfd-product-gate-json <json-or-path>]...
|
|
72
73
|
[--invariant-passport-json <json-or-path>]...
|
|
@@ -190,10 +191,11 @@ export const BUILDCHAIN_USAGE = `Usage:
|
|
|
190
191
|
buildchain kfd 7 schema [--schema <name>] [--json]
|
|
191
192
|
buildchain kfd 7 gate --input-json <file-or-json> [--cwd <dir>] [--output <file>] [--json]
|
|
192
193
|
buildchain kfd 7 verify --gate-json <file-or-json> [--expected-source-sha <sha>] [--json]
|
|
193
|
-
buildchain kfd support project --
|
|
194
|
+
buildchain kfd support project --manifest-json <file-or-json> --manifest-gate-json <file-or-json>
|
|
194
195
|
[--expected-source-sha <sha>] [--checked-at <date-time>]
|
|
195
196
|
[--output <file>] [--json]
|
|
196
|
-
buildchain kfd support verify --projection-json <file-or-json>
|
|
197
|
+
buildchain kfd support verify --projection-json <file-or-json> --manifest-json <file-or-json>
|
|
198
|
+
--manifest-gate-json <file-or-json>
|
|
197
199
|
[--expected-source-sha <sha>] [--checked-at <date-time>] [--json]
|
|
198
200
|
buildchain sample process-tree [--interval-ms <n>] [--label <name>]
|
|
199
201
|
[--output <jsonl>] [--summary-output <json>]
|
|
@@ -69,6 +69,10 @@ export function checkBuildchainContractLock({
|
|
|
69
69
|
runtimeSha = env("BUILDCHAIN_RUNTIME_SHA"),
|
|
70
70
|
runtimeClass = env("BUILDCHAIN_RUNTIME_CLASS"),
|
|
71
71
|
compatibilityPolicy = env("BUILDCHAIN_CONTRACT_COMPATIBILITY_POLICY"),
|
|
72
|
+
workflowShellRef = env("BUILDCHAIN_WORKFLOW_SHELL_REF"),
|
|
73
|
+
expectedChannel = env("BUILDCHAIN_EXPECTED_CHANNEL"),
|
|
74
|
+
expectedMajor = env("BUILDCHAIN_EXPECTED_MAJOR"),
|
|
75
|
+
allowOpaqueRuntime = boolEnv("BUILDCHAIN_ALLOW_OPAQUE_RUNTIME"),
|
|
72
76
|
issueMode = env("BUILDCHAIN_CONTRACT_DRIFT_ISSUE_MODE", "compatible-and-breaking"),
|
|
73
77
|
issueBodyPath = env("BUILDCHAIN_CONTRACT_DRIFT_ISSUE_BODY", ".buildchain/contract-drift/issue-body.md"),
|
|
74
78
|
repository = env("GITHUB_REPOSITORY"),
|
|
@@ -84,6 +88,10 @@ export function checkBuildchainContractLock({
|
|
|
84
88
|
runtimeSha,
|
|
85
89
|
runtimeClass,
|
|
86
90
|
compatibilityPolicy,
|
|
91
|
+
workflowShellRef,
|
|
92
|
+
expectedChannel,
|
|
93
|
+
expectedMajor,
|
|
94
|
+
allowOpaqueRuntime,
|
|
87
95
|
});
|
|
88
96
|
const shouldIssue = issueModeAllows(issueMode, evaluation);
|
|
89
97
|
if (shouldIssue) {
|
|
@@ -104,6 +112,8 @@ export function checkBuildchainContractLock({
|
|
|
104
112
|
`- Compatible: \`${evaluation.compatible ? "true" : "false"}\``,
|
|
105
113
|
`- Runtime ref: \`${runtimeRef || "(unknown)"}\``,
|
|
106
114
|
`- Runtime SHA: \`${runtimeSha || "(unknown)"}\``,
|
|
115
|
+
`- Workflow shell ref: \`${workflowShellRef || "(unknown)"}\``,
|
|
116
|
+
evaluation.channelBinding ? `- Bound channel: \`${evaluation.channelBinding.channel || "(unknown)"}\`` : "",
|
|
107
117
|
`- Contract digest: \`${current.contractDigest}\``,
|
|
108
118
|
`- Compatibility digest: \`${current.compatibilityDigest}\``,
|
|
109
119
|
`- Compatibility proof registry: \`${current.compatibilityProofRegistryRoot || "(legacy)"}\``,
|
|
@@ -128,7 +138,7 @@ export function checkBuildchainContractLock({
|
|
|
128
138
|
"current-buildchain-sha": runtimeSha || "",
|
|
129
139
|
});
|
|
130
140
|
if (!evaluation.ok) {
|
|
131
|
-
throw new Error(`Buildchain contract
|
|
141
|
+
throw new Error(`Buildchain contract lock rejected: ${(evaluation.reasons || []).join("; ")}`);
|
|
132
142
|
}
|
|
133
143
|
return { evaluation, current, shouldIssue };
|
|
134
144
|
}
|
|
@@ -215,7 +215,7 @@ const selfDogfoodAlphaEvaluation = evaluateBuildchainContractLock({
|
|
|
215
215
|
}),
|
|
216
216
|
runtimeRef: `v${selfDogfoodMajor}-alpha`,
|
|
217
217
|
runtimeSha: "current-development-contract",
|
|
218
|
-
runtimeClass: "alpha",
|
|
218
|
+
runtimeClass: "alpha", workflowShellRef: `v${selfDogfoodMajor}-alpha`,
|
|
219
219
|
});
|
|
220
220
|
if (
|
|
221
221
|
!canAdmitSelfDogfoodLockEvaluation({
|
|
@@ -304,7 +304,7 @@ if (!promotionOverrideAuthorization.includes("promotion runtime override is only
|
|
|
304
304
|
}
|
|
305
305
|
for (const requiredSnippet of [
|
|
306
306
|
"buildchain-channel:",
|
|
307
|
-
"uses:
|
|
307
|
+
"uses: kungfu-systems/buildchain/.github/workflows/.build.yml@v3-alpha", "uses: kungfu-systems/buildchain/.github/workflows/.build.yml@v3\n",
|
|
308
308
|
"needs.resolve-channel.outputs.buildchain-ref",
|
|
309
309
|
"needs.resolve-channel.outputs.contract-lock-path",
|
|
310
310
|
]) {
|
|
@@ -1019,8 +1019,8 @@ for (const requiredSnippet of [
|
|
|
1019
1019
|
"release-passport-kfd-3-artifact-witness-jsons:",
|
|
1020
1020
|
"release-passport-kfd-3-artifact-witness-jsons: ${{ inputs.release-passport-kfd-3-artifact-witness-jsons }}",
|
|
1021
1021
|
"release-passport-kfd-3-artifact-verify-command:",
|
|
1022
|
-
"release-passport-kfd-
|
|
1023
|
-
"release-passport-kfd-support-matrix-json: ${{ inputs.release-passport-kfd-support-matrix-json }}",
|
|
1022
|
+
"release-passport-kfd-adopter-manifest-json:", "release-passport-kfd-adopter-manifest-json: ${{ inputs.release-passport-kfd-adopter-manifest-json }}",
|
|
1023
|
+
"release-passport-kfd-support-matrix-json:", "release-passport-kfd-support-matrix-json: ${{ inputs.release-passport-kfd-support-matrix-json }}",
|
|
1024
1024
|
"release-passport-kfd-product-gate-jsons:",
|
|
1025
1025
|
"release-passport-kfd-product-gate-jsons: ${{ inputs.release-passport-kfd-product-gate-jsons }}",
|
|
1026
1026
|
"release-passport-invariant-passport-jsons:",
|