@kungfu-tech/buildchain 3.0.7 → 3.0.8
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/contracts/engineering-housekeeper-v1.schema.json +11 -1
- package/contracts/fixtures/engineering-housekeeper-v1/cases.json +10 -0
- package/contracts/release-cut-v1.schema.json +70 -0
- package/contracts/release-train-transition-v1.schema.json +85 -0
- package/contracts/release-train-v1.schema.json +82 -0
- package/dist/site/buildchain-contract.json +302 -16
- package/dist/site/buildchain-site.json +83 -27
- package/dist/site/capability-registry.json +4 -3
- package/dist/site/kfd-claims.json +45 -8
- package/dist/site/kfd-upstream-aggregate.json +1 -1
- package/dist/site/manual-registry.json +22 -6
- package/dist/site/node-api-registry.json +1218 -489
- package/dist/site/page-registry.json +65 -17
- package/dist/site/public-surface-audit.json +13 -8
- package/dist/site/publication-authority-registry.json +2 -4
- package/dist/site/publication-registry.json +4 -4
- package/dist/site/release-provenance.json +1 -0
- package/dist/site/site-manifest.json +18 -10
- package/dist/site/workflow-registry.json +10 -9
- package/docs/MAP.md +1 -1
- package/docs/aws-us-elastic-runner-burst-plane.md +20 -7
- package/docs/engineering-housekeeper.md +61 -28
- package/docs/node-api-reference.md +109 -77
- package/docs/release-governance.md +13 -10
- package/docs/release-train.md +102 -0
- package/docs/reusable-build-surface.md +16 -6
- package/docs/site-bundle-contract.md +5 -1
- package/docs/versioning.md +1 -0
- package/package.json +2 -1
- package/packages/core/buildchain-agent-manuals.js +1 -0
- package/packages/core/buildchain-compatibility-proof.js +577 -0
- package/packages/core/buildchain-contract.js +46 -181
- package/packages/core/engineering-housekeeper-github-client.js +18 -1
- package/packages/core/engineering-housekeeper-github.js +247 -41
- package/packages/core/engineering-housekeeper.js +15 -0
- package/packages/core/release-train.js +520 -0
- package/scripts/aws-macos-jit-controller-core.mjs +33 -11
- package/scripts/aws-macos-jit-controller-runtime.mjs +208 -0
- package/scripts/aws-macos-jit-controller.mjs +50 -37
- package/scripts/aws-macos-jit-core.mjs +41 -3
- package/scripts/buildchain-contract-lock.mjs +6 -0
- package/scripts/dispatch-artifact-signing-authority.mjs +4 -7
- package/scripts/engineering-housekeeper-workflow.mjs +13 -6
- package/scripts/generate-site-bundle.mjs +2 -0
- package/scripts/site-capability-metadata.mjs +1 -0
|
@@ -80,3 +80,211 @@ export function assertDryRun(result, label) {
|
|
|
80
80
|
throw new Error(`${label} did not return DryRunOperation`);
|
|
81
81
|
}
|
|
82
82
|
}
|
|
83
|
+
|
|
84
|
+
export function assertAllowedPolicySimulation(
|
|
85
|
+
plan,
|
|
86
|
+
profile,
|
|
87
|
+
principalArn,
|
|
88
|
+
actionName,
|
|
89
|
+
) {
|
|
90
|
+
if (!/^arn:aws:iam::\d{12}:(?:user|role)\/.+/.test(principalArn || "")) {
|
|
91
|
+
throw new Error(
|
|
92
|
+
"AWS allocation policy simulation requires an IAM user or role principal ARN",
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
const contextEntries = [
|
|
96
|
+
`ContextKeyName=aws:RequestedRegion,ContextKeyValues=${plan.aws.region},ContextKeyType=string`,
|
|
97
|
+
...plan.aws.hostTags.map(
|
|
98
|
+
({ Key, Value }) =>
|
|
99
|
+
`ContextKeyName=aws:RequestTag/${Key},ContextKeyValues=${Value},ContextKeyType=string`,
|
|
100
|
+
),
|
|
101
|
+
];
|
|
102
|
+
const evaluation = awsJson(
|
|
103
|
+
plan,
|
|
104
|
+
profile,
|
|
105
|
+
[
|
|
106
|
+
"iam",
|
|
107
|
+
"simulate-principal-policy",
|
|
108
|
+
"--policy-source-arn",
|
|
109
|
+
principalArn,
|
|
110
|
+
"--action-names",
|
|
111
|
+
actionName,
|
|
112
|
+
"--resource-arns",
|
|
113
|
+
"*",
|
|
114
|
+
"--context-entries",
|
|
115
|
+
...contextEntries,
|
|
116
|
+
"--output",
|
|
117
|
+
"json",
|
|
118
|
+
],
|
|
119
|
+
`${actionName} IAM policy simulation`,
|
|
120
|
+
).EvaluationResults?.[0];
|
|
121
|
+
if (
|
|
122
|
+
evaluation?.EvalActionName !== actionName ||
|
|
123
|
+
evaluation?.EvalDecision !== "allowed" ||
|
|
124
|
+
(evaluation.MissingContextValues || []).length !== 0
|
|
125
|
+
) {
|
|
126
|
+
throw new Error(`${actionName} IAM policy simulation did not allow allocation`);
|
|
127
|
+
}
|
|
128
|
+
return {
|
|
129
|
+
actionName,
|
|
130
|
+
decision: evaluation.EvalDecision,
|
|
131
|
+
principalArn,
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function sameJson(left, right) {
|
|
136
|
+
return JSON.stringify(left) === JSON.stringify(right);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function sameDimensionSet(observed, expected) {
|
|
140
|
+
return (
|
|
141
|
+
Array.isArray(observed) &&
|
|
142
|
+
observed.length === expected.length &&
|
|
143
|
+
expected.every((entry) =>
|
|
144
|
+
observed.some((candidate) => sameJson(candidate, entry)),
|
|
145
|
+
)
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export function assertMacosBudgetLaunchGate(plan, profile) {
|
|
150
|
+
const identity = awsJson(
|
|
151
|
+
plan,
|
|
152
|
+
profile,
|
|
153
|
+
["sts", "get-caller-identity", "--output", "json"],
|
|
154
|
+
"AWS account identity preflight",
|
|
155
|
+
);
|
|
156
|
+
if (identity.Account !== plan.account.id) {
|
|
157
|
+
throw new Error("AWS account identity mismatch");
|
|
158
|
+
}
|
|
159
|
+
const workflow = ghJson(
|
|
160
|
+
[
|
|
161
|
+
"api",
|
|
162
|
+
`repos/${plan.repository}/actions/workflows/${plan.github.workflowId}`,
|
|
163
|
+
],
|
|
164
|
+
undefined,
|
|
165
|
+
"GitHub macOS workflow preflight",
|
|
166
|
+
);
|
|
167
|
+
if (workflow.state !== plan.github.requiredState) {
|
|
168
|
+
throw new Error(
|
|
169
|
+
`macOS workflow must be ${plan.github.requiredState}; observed ${workflow.state || "unknown"}`,
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
const stack = awsJson(
|
|
173
|
+
plan,
|
|
174
|
+
profile,
|
|
175
|
+
[
|
|
176
|
+
"cloudformation",
|
|
177
|
+
"describe-stacks",
|
|
178
|
+
"--stack-name",
|
|
179
|
+
plan.aws.controlPlaneStack,
|
|
180
|
+
"--output",
|
|
181
|
+
"json",
|
|
182
|
+
],
|
|
183
|
+
"macOS control-plane stack preflight",
|
|
184
|
+
).Stacks?.[0];
|
|
185
|
+
if (!stack || !/(?:CREATE|UPDATE)_COMPLETE$/.test(stack.StackStatus || "")) {
|
|
186
|
+
throw new Error("macOS control-plane stack is not complete");
|
|
187
|
+
}
|
|
188
|
+
const topicArn = (stack.Outputs || []).find(
|
|
189
|
+
(entry) => entry.OutputKey === "KillSwitchTopic",
|
|
190
|
+
)?.OutputValue;
|
|
191
|
+
if (!/^arn:aws:sns:/.test(String(topicArn || ""))) {
|
|
192
|
+
throw new Error("macOS Budget kill-switch topic readback failed");
|
|
193
|
+
}
|
|
194
|
+
const budget = awsJson(
|
|
195
|
+
plan,
|
|
196
|
+
profile,
|
|
197
|
+
[
|
|
198
|
+
"budgets",
|
|
199
|
+
"describe-budget",
|
|
200
|
+
"--account-id",
|
|
201
|
+
plan.account.id,
|
|
202
|
+
"--budget-name",
|
|
203
|
+
plan.safety.budget.name,
|
|
204
|
+
"--show-filter-expression",
|
|
205
|
+
"--output",
|
|
206
|
+
"json",
|
|
207
|
+
],
|
|
208
|
+
"macOS Budget preflight",
|
|
209
|
+
).Budget;
|
|
210
|
+
const expectedDimensions = [
|
|
211
|
+
["USAGE_TYPE", plan.safety.budget.dimensionFilter.usageTypes],
|
|
212
|
+
["OPERATION", [plan.safety.budget.dimensionFilter.operation]],
|
|
213
|
+
["REGION", plan.safety.budget.dimensionFilter.regions],
|
|
214
|
+
].map(([Key, Values]) => ({
|
|
215
|
+
Dimensions: { Key, Values, MatchOptions: ["EQUALS"] },
|
|
216
|
+
}));
|
|
217
|
+
if (
|
|
218
|
+
!budget ||
|
|
219
|
+
budget.BudgetName !== plan.safety.budget.name ||
|
|
220
|
+
Number(budget.BudgetLimit?.Amount) !== plan.safety.budget.limitUsd ||
|
|
221
|
+
budget.BudgetLimit?.Unit !== "USD" ||
|
|
222
|
+
budget.BudgetType !== "COST" ||
|
|
223
|
+
!sameJson(budget.Metrics, plan.safety.budget.metrics) ||
|
|
224
|
+
!sameDimensionSet(budget.FilterExpression?.And, expectedDimensions)
|
|
225
|
+
) {
|
|
226
|
+
throw new Error("macOS Budget identity or dimension filter mismatch");
|
|
227
|
+
}
|
|
228
|
+
const notifications = awsJson(
|
|
229
|
+
plan,
|
|
230
|
+
profile,
|
|
231
|
+
[
|
|
232
|
+
"budgets",
|
|
233
|
+
"describe-notifications-for-budget",
|
|
234
|
+
"--account-id",
|
|
235
|
+
plan.account.id,
|
|
236
|
+
"--budget-name",
|
|
237
|
+
plan.safety.budget.name,
|
|
238
|
+
"--output",
|
|
239
|
+
"json",
|
|
240
|
+
],
|
|
241
|
+
"macOS Budget notifications preflight",
|
|
242
|
+
).Notifications;
|
|
243
|
+
const thresholds = (notifications || [])
|
|
244
|
+
.filter(
|
|
245
|
+
(entry) =>
|
|
246
|
+
entry.NotificationType === "ACTUAL" &&
|
|
247
|
+
(entry.ThresholdType || "PERCENTAGE") === "PERCENTAGE",
|
|
248
|
+
)
|
|
249
|
+
.map((entry) => Number(entry.Threshold))
|
|
250
|
+
.sort((left, right) => left - right);
|
|
251
|
+
if (!sameJson(thresholds, plan.safety.budget.requiredActualThresholds)) {
|
|
252
|
+
throw new Error("macOS Budget notification thresholds mismatch");
|
|
253
|
+
}
|
|
254
|
+
for (const notification of notifications) {
|
|
255
|
+
const subscribers = awsJson(
|
|
256
|
+
plan,
|
|
257
|
+
profile,
|
|
258
|
+
[
|
|
259
|
+
"budgets",
|
|
260
|
+
"describe-subscribers-for-notification",
|
|
261
|
+
"--account-id",
|
|
262
|
+
plan.account.id,
|
|
263
|
+
"--budget-name",
|
|
264
|
+
plan.safety.budget.name,
|
|
265
|
+
"--notification",
|
|
266
|
+
JSON.stringify(notification),
|
|
267
|
+
"--output",
|
|
268
|
+
"json",
|
|
269
|
+
],
|
|
270
|
+
`macOS Budget ${notification.Threshold}% subscribers preflight`,
|
|
271
|
+
).Subscribers;
|
|
272
|
+
if (
|
|
273
|
+
!(subscribers || []).some(
|
|
274
|
+
(entry) =>
|
|
275
|
+
entry.SubscriptionType === "SNS" && entry.Address === topicArn,
|
|
276
|
+
)
|
|
277
|
+
) {
|
|
278
|
+
throw new Error("macOS Budget SNS subscriber mismatch");
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
return {
|
|
282
|
+
accountId: identity.Account,
|
|
283
|
+
principalArn: identity.Arn,
|
|
284
|
+
workflowState: workflow.state,
|
|
285
|
+
stackStatus: stack.StackStatus,
|
|
286
|
+
budgetName: budget.BudgetName,
|
|
287
|
+
budgetDimensionFilter: plan.safety.budget.dimensionFilter,
|
|
288
|
+
budgetThresholds: thresholds,
|
|
289
|
+
};
|
|
290
|
+
}
|
|
@@ -11,9 +11,12 @@ import {
|
|
|
11
11
|
macosReleaseHostsArgs,
|
|
12
12
|
macosRunInstancesArgs,
|
|
13
13
|
} from "./aws-macos-jit-controller-core.mjs";
|
|
14
|
+
import { MACOS_EC2_JIT_REGIONS } from "./aws-macos-jit-core.mjs";
|
|
14
15
|
import { executeMacosJitJob } from "./aws-macos-jit-job-controller.mjs";
|
|
15
16
|
import {
|
|
16
17
|
assertDryRun,
|
|
18
|
+
assertAllowedPolicySimulation,
|
|
19
|
+
assertMacosBudgetLaunchGate,
|
|
17
20
|
assertOwnership,
|
|
18
21
|
awsArgs,
|
|
19
22
|
awsJson,
|
|
@@ -32,6 +35,7 @@ function flag(name) {
|
|
|
32
35
|
}
|
|
33
36
|
|
|
34
37
|
function assertCampaignLaunchPreflight(plan, profile) {
|
|
38
|
+
const launchGate = assertMacosBudgetLaunchGate(plan, profile);
|
|
35
39
|
const commit = ghJson(
|
|
36
40
|
["api", `repos/${plan.repository}/commits/${plan.source.sha}`],
|
|
37
41
|
undefined,
|
|
@@ -40,43 +44,51 @@ function assertCampaignLaunchPreflight(plan, profile) {
|
|
|
40
44
|
if (String(commit.sha || "").toLowerCase() !== plan.source.sha) {
|
|
41
45
|
throw new Error("GitHub did not resolve the exact campaign source SHA");
|
|
42
46
|
}
|
|
43
|
-
const
|
|
44
|
-
|
|
45
|
-
|
|
47
|
+
const regionalPlans = Object.keys(MACOS_EC2_JIT_REGIONS).map((region) => ({
|
|
48
|
+
...plan,
|
|
49
|
+
aws: { ...plan.aws, region },
|
|
50
|
+
}));
|
|
51
|
+
const activeHosts = regionalPlans.flatMap(
|
|
52
|
+
(regionalPlan) =>
|
|
53
|
+
awsJson(
|
|
54
|
+
regionalPlan,
|
|
55
|
+
profile,
|
|
56
|
+
[
|
|
57
|
+
"ec2",
|
|
58
|
+
"describe-hosts",
|
|
59
|
+
"--filter",
|
|
60
|
+
"Name=tag:kungfu:plane,Values=aws-us-elastic-runner-burst",
|
|
61
|
+
"Name=tag:kungfu:provider,Values=macos-ec2-jit",
|
|
62
|
+
"Name=state,Values=available,pending,under-assessment",
|
|
63
|
+
"--output",
|
|
64
|
+
"json",
|
|
65
|
+
],
|
|
66
|
+
`active macOS JIT host preflight in ${regionalPlan.aws.region}`,
|
|
67
|
+
).Hosts || [],
|
|
68
|
+
);
|
|
69
|
+
if (activeHosts.length >= plan.safety.activeHostCeiling) {
|
|
70
|
+
throw new Error("macOS JIT active Dedicated Host ceiling is reached");
|
|
71
|
+
}
|
|
72
|
+
const instances = regionalPlans.flatMap((regionalPlan) => {
|
|
73
|
+
const active = awsJson(
|
|
74
|
+
regionalPlan,
|
|
46
75
|
profile,
|
|
47
76
|
[
|
|
48
77
|
"ec2",
|
|
49
|
-
"describe-
|
|
50
|
-
"--
|
|
78
|
+
"describe-instances",
|
|
79
|
+
"--filters",
|
|
51
80
|
"Name=tag:kungfu:plane,Values=aws-us-elastic-runner-burst",
|
|
52
81
|
"Name=tag:kungfu:provider,Values=macos-ec2-jit",
|
|
53
|
-
"Name=state,Values=
|
|
82
|
+
"Name=instance-state-name,Values=pending,running,stopping,stopped,shutting-down",
|
|
54
83
|
"--output",
|
|
55
84
|
"json",
|
|
56
85
|
],
|
|
57
|
-
|
|
58
|
-
)
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
plan,
|
|
64
|
-
profile,
|
|
65
|
-
[
|
|
66
|
-
"ec2",
|
|
67
|
-
"describe-instances",
|
|
68
|
-
"--filters",
|
|
69
|
-
"Name=tag:kungfu:plane,Values=aws-us-elastic-runner-burst",
|
|
70
|
-
"Name=tag:kungfu:provider,Values=macos-ec2-jit",
|
|
71
|
-
"Name=instance-state-name,Values=pending,running,stopping,stopped,shutting-down",
|
|
72
|
-
"--output",
|
|
73
|
-
"json",
|
|
74
|
-
],
|
|
75
|
-
"active macOS JIT instance preflight",
|
|
76
|
-
);
|
|
77
|
-
const instances = (active.Reservations || []).flatMap(
|
|
78
|
-
(reservation) => reservation.Instances || [],
|
|
79
|
-
);
|
|
86
|
+
`active macOS JIT instance preflight in ${regionalPlan.aws.region}`,
|
|
87
|
+
);
|
|
88
|
+
return (active.Reservations || []).flatMap(
|
|
89
|
+
(reservation) => reservation.Instances || [],
|
|
90
|
+
);
|
|
91
|
+
});
|
|
80
92
|
if (instances.length >= plan.safety.activeInstanceCeiling) {
|
|
81
93
|
throw new Error("macOS JIT active instance ceiling is reached");
|
|
82
94
|
}
|
|
@@ -120,6 +132,7 @@ function assertCampaignLaunchPreflight(plan, profile) {
|
|
|
120
132
|
);
|
|
121
133
|
}
|
|
122
134
|
return {
|
|
135
|
+
...launchGate,
|
|
123
136
|
exactCommit: plan.source.sha,
|
|
124
137
|
activeHosts: activeHosts.length,
|
|
125
138
|
activeInstances: instances.length,
|
|
@@ -136,12 +149,11 @@ export function executeMacosJitCampaignLaunch(plan, { profile = "" } = {}) {
|
|
|
136
149
|
throw new Error("macOS JIT campaign launch plan contract is invalid");
|
|
137
150
|
}
|
|
138
151
|
const preflight = assertCampaignLaunchPreflight(plan, profile);
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
"EC2 AllocateHosts DryRun",
|
|
152
|
+
const allocationPermission = assertAllowedPolicySimulation(
|
|
153
|
+
plan,
|
|
154
|
+
profile,
|
|
155
|
+
preflight.principalArn,
|
|
156
|
+
"ec2:AllocateHosts",
|
|
145
157
|
);
|
|
146
158
|
const hostId = awsJson(
|
|
147
159
|
plan,
|
|
@@ -223,8 +235,8 @@ export function executeMacosJitCampaignLaunch(plan, { profile = "" } = {}) {
|
|
|
223
235
|
imageId: instance.ImageId,
|
|
224
236
|
launchTime: new Date(instance.LaunchTime).toISOString(),
|
|
225
237
|
},
|
|
226
|
-
preflight,
|
|
227
|
-
dryRuns: ["
|
|
238
|
+
preflight: { ...preflight, allocationPermission },
|
|
239
|
+
dryRuns: ["RunInstances:DryRunOperation"],
|
|
228
240
|
planDigest: plan.digest,
|
|
229
241
|
};
|
|
230
242
|
}
|
|
@@ -462,6 +474,7 @@ export function executeMacosJitCampaignClose(plan, { profile = "" } = {}) {
|
|
|
462
474
|
function commonValues(execute) {
|
|
463
475
|
return {
|
|
464
476
|
execute,
|
|
477
|
+
accountId: arg("account-id"),
|
|
465
478
|
repository: arg("repository", "kungfu-systems/kungfu"),
|
|
466
479
|
campaignId: arg("campaign-id"),
|
|
467
480
|
sourceSha: arg("source-sha"),
|
|
@@ -2,13 +2,29 @@ import { digest } from "./aws-runner-burst-core.mjs";
|
|
|
2
2
|
|
|
3
3
|
export const AWS_MACOS_JIT_CONTRACT = "kungfu-buildchain-aws-macos-jit/v1";
|
|
4
4
|
|
|
5
|
+
export const MACOS_EC2_JIT_REGIONS = Object.freeze({
|
|
6
|
+
"us-east-1": Object.freeze({
|
|
7
|
+
stack: "kungfu-buildchain-macos-jit",
|
|
8
|
+
}),
|
|
9
|
+
"us-east-2": Object.freeze({
|
|
10
|
+
stack: "kungfu-buildchain-macos-jit-us-east-2",
|
|
11
|
+
}),
|
|
12
|
+
});
|
|
13
|
+
|
|
5
14
|
export const MACOS_EC2_JIT = Object.freeze({
|
|
6
15
|
phase: "macos-ec2-jit",
|
|
7
16
|
region: "us-east-1",
|
|
8
17
|
repository: "kungfu-systems/kungfu",
|
|
18
|
+
workflowId: "323846928",
|
|
9
19
|
stack: "kungfu-buildchain-macos-jit",
|
|
20
|
+
budgetName: "kungfu-buildchain-macos-jit-actual-spend",
|
|
21
|
+
budgetUsageTypes: Object.freeze([
|
|
22
|
+
"HostUsage:mac2",
|
|
23
|
+
"USE2-HostUsage:mac2",
|
|
24
|
+
]),
|
|
25
|
+
budgetOperation: "RunInstances",
|
|
10
26
|
instanceType: "mac2.metal",
|
|
11
|
-
pricePerHourUsd: 0.
|
|
27
|
+
pricePerHourUsd: 0.65,
|
|
12
28
|
minimumHostAllocationHours: 24,
|
|
13
29
|
maximumHostAllocationHours: 30,
|
|
14
30
|
maxAcceptedHosts: 1,
|
|
@@ -23,6 +39,22 @@ export const MACOS_EC2_JIT = Object.freeze({
|
|
|
23
39
|
jitParameterPrefix: "/kungfu/burst/macos/",
|
|
24
40
|
});
|
|
25
41
|
|
|
42
|
+
export function macosJitRegionConfig(region) {
|
|
43
|
+
const resolved = String(region || MACOS_EC2_JIT.region).trim();
|
|
44
|
+
const config = MACOS_EC2_JIT_REGIONS[resolved];
|
|
45
|
+
if (!config) {
|
|
46
|
+
throw new Error(
|
|
47
|
+
`region must be one of ${Object.keys(MACOS_EC2_JIT_REGIONS).join(", ")}`,
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
return {
|
|
51
|
+
...config,
|
|
52
|
+
budgetName: MACOS_EC2_JIT.budgetName,
|
|
53
|
+
budgetUsageTypes: MACOS_EC2_JIT.budgetUsageTypes,
|
|
54
|
+
budgetRegions: Object.keys(MACOS_EC2_JIT_REGIONS),
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
26
58
|
function exactSha(value, label) {
|
|
27
59
|
const normalized = String(value || "")
|
|
28
60
|
.trim()
|
|
@@ -323,8 +355,14 @@ export function verifyMacosEc2JitQualification({
|
|
|
323
355
|
}
|
|
324
356
|
let allocationHours = 0;
|
|
325
357
|
try {
|
|
326
|
-
const allocatedAt = iso(
|
|
327
|
-
|
|
358
|
+
const allocatedAt = iso(
|
|
359
|
+
hostLifecycle.allocatedAt,
|
|
360
|
+
"hostLifecycle.allocatedAt",
|
|
361
|
+
);
|
|
362
|
+
const releasedAt = iso(
|
|
363
|
+
hostLifecycle.releasedAt,
|
|
364
|
+
"hostLifecycle.releasedAt",
|
|
365
|
+
);
|
|
328
366
|
allocationHours = elapsedHours(allocatedAt, releasedAt);
|
|
329
367
|
if (
|
|
330
368
|
hostLifecycle.status !== "passed" ||
|
|
@@ -106,6 +106,9 @@ export function checkBuildchainContractLock({
|
|
|
106
106
|
`- Runtime SHA: \`${runtimeSha || "(unknown)"}\``,
|
|
107
107
|
`- Contract digest: \`${current.contractDigest}\``,
|
|
108
108
|
`- Compatibility digest: \`${current.compatibilityDigest}\``,
|
|
109
|
+
`- Compatibility proof registry: \`${current.compatibilityProofRegistryRoot || "(legacy)"}\``,
|
|
110
|
+
`- Verification receipt: \`${evaluation.receiptRoot || "(not-required)"}\``,
|
|
111
|
+
evaluation.usedProofRoots?.length ? `- Used compatibility proofs: ${evaluation.usedProofRoots.map((root) => `\`${root}\``).join(", ")}` : "",
|
|
109
112
|
evaluation.reasons?.length ? `- Reasons: ${evaluation.reasons.join("; ")}` : "",
|
|
110
113
|
shouldIssue ? `- Drift issue body: \`${issueBodyPath}\`` : "",
|
|
111
114
|
].filter(Boolean).join("\n"));
|
|
@@ -117,6 +120,9 @@ export function checkBuildchainContractLock({
|
|
|
117
120
|
"contract-lock-issue-body-file": shouldIssue ? issueBodyPath : "",
|
|
118
121
|
"contract-digest": current.contractDigest,
|
|
119
122
|
"contract-compatibility-digest": current.compatibilityDigest,
|
|
123
|
+
"contract-compatibility-proof-registry-root": current.compatibilityProofRegistryRoot || "",
|
|
124
|
+
"contract-compatibility-verification-receipt-root": evaluation.receiptRoot || "",
|
|
125
|
+
"contract-compatibility-proof-roots": (evaluation.usedProofRoots || []).join(","),
|
|
120
126
|
"accepted-contract-digest": evaluation.accepted?.contractDigest || "",
|
|
121
127
|
"accepted-buildchain-sha": evaluation.accepted?.resolvedSha || "",
|
|
122
128
|
"current-buildchain-sha": runtimeSha || "",
|
|
@@ -39,18 +39,15 @@ export async function resolveArtifactSigningAuthorityRuntime({
|
|
|
39
39
|
const ref = resolveAuthorityDispatchRef(authorityRef);
|
|
40
40
|
const encodedRef = ref.split("/").map(encodeURIComponent).join("/");
|
|
41
41
|
const response = await requestImpl(
|
|
42
|
-
`/repos/${authorityRepo}/
|
|
42
|
+
`/repos/${authorityRepo}/commits/${encodedRef}`,
|
|
43
43
|
{ token: required(token, "Buildchain authority dispatch token") },
|
|
44
44
|
);
|
|
45
|
-
|
|
45
|
+
const sha = String(response?.sha || "");
|
|
46
|
+
if (!/^[0-9a-f]{40}$/u.test(sha)) {
|
|
46
47
|
throw new Error(
|
|
47
|
-
"Buildchain signing authority ref
|
|
48
|
+
"Buildchain signing authority ref must resolve to an exact commit SHA",
|
|
48
49
|
);
|
|
49
50
|
}
|
|
50
|
-
const sha = String(response.object.sha || "");
|
|
51
|
-
if (!/^[0-9a-f]{40}$/u.test(sha)) {
|
|
52
|
-
throw new Error("Buildchain signing authority runtime SHA must be exact");
|
|
53
|
-
}
|
|
54
51
|
return { ref, sha };
|
|
55
52
|
}
|
|
56
53
|
|
|
@@ -86,10 +86,11 @@ export function normalizeHousekeeperWorkflowOptions(options = {}) {
|
|
|
86
86
|
process.env.HOUSEKEEPER_REPOSITORY ||
|
|
87
87
|
process.env.GITHUB_REPOSITORY,
|
|
88
88
|
),
|
|
89
|
-
targetBranch:
|
|
90
|
-
options.targetBranch || process.env.HOUSEKEEPER_TARGET_BRANCH,
|
|
91
|
-
|
|
92
|
-
|
|
89
|
+
targetBranch: String(
|
|
90
|
+
options.targetBranch || process.env.HOUSEKEEPER_TARGET_BRANCH || "",
|
|
91
|
+
)
|
|
92
|
+
.trim()
|
|
93
|
+
.replace(/^refs\/heads\//, ""),
|
|
93
94
|
staleDays: positiveInteger(
|
|
94
95
|
options.staleDays ?? process.env.HOUSEKEEPER_STALE_DAYS,
|
|
95
96
|
30,
|
|
@@ -108,6 +109,11 @@ export function normalizeHousekeeperWorkflowOptions(options = {}) {
|
|
|
108
109
|
options.retainedPatterns ?? process.env.HOUSEKEEPER_RETAINED_PATTERNS,
|
|
109
110
|
["train/**", "authority/**"],
|
|
110
111
|
),
|
|
112
|
+
temporaryBranchPatterns: splitPatterns(
|
|
113
|
+
options.temporaryBranchPatterns ??
|
|
114
|
+
process.env.HOUSEKEEPER_TEMPORARY_BRANCH_PATTERNS,
|
|
115
|
+
["feature/**", "fix/**", "chore/**", "docs/**", "ci/**", "refactor/**"],
|
|
116
|
+
),
|
|
111
117
|
stalePullRequestLabel: String(
|
|
112
118
|
options.stalePullRequestLabel ??
|
|
113
119
|
process.env.HOUSEKEEPER_STALE_PR_LABEL ??
|
|
@@ -137,6 +143,7 @@ function workflowPolicy(options) {
|
|
|
137
143
|
return {
|
|
138
144
|
protectedPatterns: options.protectedPatterns,
|
|
139
145
|
retainedPatterns: options.retainedPatterns,
|
|
146
|
+
temporaryBranchPatterns: options.temporaryBranchPatterns,
|
|
140
147
|
pullRequests: {
|
|
141
148
|
reportStale: true,
|
|
142
149
|
label: options.stalePullRequestLabel,
|
|
@@ -167,7 +174,7 @@ export function renderHousekeeperWorkflowReport(
|
|
|
167
174
|
`Mode: \`${mode || "report"}\``,
|
|
168
175
|
`Scope: \`${scope}\``,
|
|
169
176
|
`Repository: \`${plan.repository}\``,
|
|
170
|
-
`
|
|
177
|
+
`Primary mainline: \`${plan.target.name}@${plan.target.headOid}\``,
|
|
171
178
|
`Observed at: \`${plan.observedAt}\``,
|
|
172
179
|
`Plan root: \`${plan.planRoot}\``,
|
|
173
180
|
`Receipt root: \`${receipt.receiptRoot}\``,
|
|
@@ -284,7 +291,7 @@ export async function applyHousekeeperWorkflowScope({
|
|
|
284
291
|
}
|
|
285
292
|
if (
|
|
286
293
|
plan.repository !== options.repository ||
|
|
287
|
-
plan.target.name !== options.targetBranch
|
|
294
|
+
(options.targetBranch && plan.target.name !== options.targetBranch)
|
|
288
295
|
) {
|
|
289
296
|
throw new Error(
|
|
290
297
|
"plan repository or target branch does not match current workflow inputs",
|
|
@@ -370,6 +370,7 @@ const manualMetaById = new Map(Object.entries({
|
|
|
370
370
|
"publication-rehearsal": { capabilityGroup: "release-passport-trust", audience: ["release-operator", "agent", "maintainer"], maturity: "preview", order: 124 },
|
|
371
371
|
"release-activation-transaction": { capabilityGroup: "release-passport-trust", audience: ["release-operator", "agent"], maturity: "preview", order: 125 },
|
|
372
372
|
"release-candidate": { capabilityGroup: "reusable-build", audience: ["release-operator", "consumer"], maturity: "stable", order: 130 },
|
|
373
|
+
"release-train": { capabilityGroup: "governance-versioning", audience: ["release-operator", "consumer", "agent"], maturity: "preview", order: 132 },
|
|
373
374
|
"stable-candidate-patrol": { capabilityGroup: "governance-versioning", audience: ["release-operator", "consumer"], maturity: "preview", order: 135 },
|
|
374
375
|
"dev-qualification-patrol": { capabilityGroup: "governance-versioning", audience: ["release-operator", "consumer", "agent"], maturity: "preview", order: 136 },
|
|
375
376
|
"dev-alpha-candidate-patrol": { capabilityGroup: "governance-versioning", audience: ["release-operator", "consumer", "agent"], maturity: "preview", order: 137 },
|
|
@@ -734,6 +735,7 @@ function buildSiteBundle() {
|
|
|
734
735
|
"docs/kfd-support.md",
|
|
735
736
|
"docs/reusable-build-surface.md",
|
|
736
737
|
"docs/release-candidate.md",
|
|
738
|
+
"docs/release-train.md",
|
|
737
739
|
"docs/stable-candidate-patrol.md",
|
|
738
740
|
"docs/dev-qualification-patrol.md",
|
|
739
741
|
"docs/dev-alpha-candidate-patrol.md",
|
|
@@ -202,6 +202,7 @@ export function nodeApiMeta(exportName) {
|
|
|
202
202
|
"./release-passport-contract": { group: "release-passport-trust", summary: "Standalone release passport JSON Schema, ownership/check manifest, and structural validation APIs." },
|
|
203
203
|
"./release-candidate": { group: "reusable-build", summary: "PR-stage release-candidate artifact, passport, and promote-only resolver APIs." },
|
|
204
204
|
"./release-candidate-recovery": { group: "release-passport-trust", summary: "Fail-closed verification and immutable receipts for reusing a successful sealed candidate run without rebuilding product payloads." },
|
|
205
|
+
"./release-train": { group: "governance-versioning", summary: "Frozen Release Cut identity, rooted Release Train transitions, Dev observations, supersession, replay, and legacy read compatibility APIs." },
|
|
205
206
|
"./stable-candidate-ledger": { group: "governance-versioning", summary: "Immutable alpha candidate ledger, qualification, revocation, selection, and exact stable source-lock APIs." },
|
|
206
207
|
"./release-propagation": { group: "site-and-propagation", summary: "Release propagation graph, plan, and exact upstream lock APIs." },
|
|
207
208
|
"./release-activation-transaction": { group: "release-passport-trust", summary: "Ordered cross-repository activation, exact receipt-set binding, retry, abort, rollback, and shadow-rehearsal APIs." },
|