@kungfu-tech/buildchain 3.0.5-alpha.8 → 3.0.6-alpha.0
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/auditable-demo-scenario-v1.schema.json +11 -0
- package/dist/site/buildchain-contract.json +42 -22
- package/dist/site/buildchain-site.json +73 -27
- package/dist/site/capability-registry.json +2 -2
- package/dist/site/cli-registry.json +5 -3
- package/dist/site/controller-registry.json +19 -3
- package/dist/site/kfd-claims.json +58 -14
- package/dist/site/kfd-upstream-aggregate.json +1 -1
- package/dist/site/manual-registry.json +7 -6
- package/dist/site/node-api-registry.json +335 -8
- package/dist/site/page-registry.json +63 -17
- package/dist/site/public-surface-audit.json +94 -11
- package/dist/site/publication-authority-registry.json +21 -1
- package/dist/site/publication-registry.json +4 -4
- package/dist/site/site-manifest.json +10 -10
- package/dist/site/workflow-registry.json +53 -4
- package/docs/MAP.md +4 -1
- package/docs/auditable-demo.md +19 -3
- package/docs/cli-reference.md +15 -4
- package/docs/dev-alpha-candidate-patrol.md +14 -5
- package/docs/dev-qualification-patrol.md +108 -0
- package/docs/node-api-reference.md +42 -9
- package/docs/release-propagation.md +93 -0
- package/docs/reusable-build-surface.md +44 -12
- package/docs/stable-candidate-patrol.md +5 -2
- package/package.json +1 -1
- package/packages/core/buildchain-publication-authority.js +1 -0
- package/packages/core/release-propagation-agent-entry.js +185 -0
- package/packages/core/release-propagation-push.js +293 -0
- package/packages/core/release-propagation-stage-evidence.js +36 -0
- package/packages/core/release-propagation-work.js +1 -1
- package/packages/core/release-propagation.js +15 -0
- package/scripts/artifact-signing-controller-core.mjs +525 -0
- package/scripts/artifact-signing-controller.mjs +302 -0
- package/scripts/artifact-signing-delegation.mjs +51 -3
- package/scripts/auditable-demo-platform.mjs +59 -1
- package/scripts/auditable-demo-transport-smoke.mjs +176 -0
- package/scripts/build-standalone-binary.mjs +3 -1
- package/scripts/buildchain-cli-help.mjs +1 -1
- package/scripts/check-inventory.mjs +1 -0
- package/scripts/compiler-cache-evidence.mjs +90 -7
- package/scripts/dev-alpha-candidate-patrol.mjs +80 -5
- package/scripts/dev-pr-auto-merge.mjs +4 -4
- package/scripts/dev-qualification-patrol.mjs +594 -0
- package/scripts/dispatch-artifact-signing-authority.mjs +312 -61
- package/scripts/generate-site-bundle.mjs +3 -0
- package/scripts/inspect-artifact-signing-requests.mjs +12 -0
- package/scripts/release-propagation.mjs +73 -0
- package/scripts/run-lifecycle-core.mjs +25 -0
- package/scripts/site-capability-metadata.mjs +1 -1
- package/scripts/stable-candidate-patrol.mjs +30 -4
|
@@ -12,37 +12,110 @@ function required(value, label) {
|
|
|
12
12
|
|
|
13
13
|
function repository(value, label) {
|
|
14
14
|
const normalized = required(value, label);
|
|
15
|
-
if (!/^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/u.test(normalized))
|
|
15
|
+
if (!/^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/u.test(normalized))
|
|
16
|
+
throw new Error(`${label} must be owner/repository`);
|
|
16
17
|
return normalized;
|
|
17
18
|
}
|
|
18
19
|
|
|
19
20
|
const TRANSIENT_GITHUB_STATUSES = new Set([408, 429, 500, 502, 503, 504]);
|
|
20
|
-
const DEFAULT_ARTIFACT_SIGNING_AUTHORITY_REF =
|
|
21
|
+
const DEFAULT_ARTIFACT_SIGNING_AUTHORITY_REF =
|
|
22
|
+
"authority/v3/v3.0/artifact-signing";
|
|
23
|
+
const AUTHORITY_WORKFLOW = "artifact-signing-authority.yml";
|
|
21
24
|
|
|
22
25
|
export function resolveAuthorityDispatchRef(value) {
|
|
23
26
|
const ref = required(value, "authority ref");
|
|
24
|
-
return /^[0-9a-f]{40}$/u.test(ref)
|
|
27
|
+
return /^[0-9a-f]{40}$/u.test(ref)
|
|
28
|
+
? DEFAULT_ARTIFACT_SIGNING_AUTHORITY_REF
|
|
29
|
+
: ref;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export async function resolveArtifactSigningAuthorityRuntime({
|
|
33
|
+
authorityRepository,
|
|
34
|
+
authorityRef,
|
|
35
|
+
token,
|
|
36
|
+
requestImpl = githubRequest,
|
|
37
|
+
}) {
|
|
38
|
+
const authorityRepo = repository(authorityRepository, "authority repository");
|
|
39
|
+
const ref = resolveAuthorityDispatchRef(authorityRef);
|
|
40
|
+
const encodedRef = ref.split("/").map(encodeURIComponent).join("/");
|
|
41
|
+
const response = await requestImpl(
|
|
42
|
+
`/repos/${authorityRepo}/git/ref/heads/${encodedRef}`,
|
|
43
|
+
{ token: required(token, "Buildchain authority dispatch token") },
|
|
44
|
+
);
|
|
45
|
+
if (response?.object?.type !== "commit") {
|
|
46
|
+
throw new Error(
|
|
47
|
+
"Buildchain signing authority ref does not resolve to a commit",
|
|
48
|
+
);
|
|
49
|
+
}
|
|
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
|
+
return { ref, sha };
|
|
25
55
|
}
|
|
26
56
|
|
|
27
57
|
function retryDelayMs(attempt) {
|
|
28
|
-
return Math.min(1_000 *
|
|
58
|
+
return Math.min(1_000 * 2 ** (attempt - 1), 10_000);
|
|
29
59
|
}
|
|
30
60
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
61
|
+
function authorityError(message, outputs = {}) {
|
|
62
|
+
const error = new Error(message);
|
|
63
|
+
error.authorityOutputs = outputs;
|
|
64
|
+
return error;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function validateArtifactSigningAuthorityRun(
|
|
68
|
+
run,
|
|
69
|
+
{ authorityRepository, authorityRuntimeSha, expectedTitle },
|
|
70
|
+
) {
|
|
71
|
+
if (!run || typeof run !== "object") {
|
|
72
|
+
throw new Error("Buildchain signing authority run is missing");
|
|
73
|
+
}
|
|
74
|
+
if (String(run.display_title || "") !== expectedTitle) {
|
|
75
|
+
throw new Error("Buildchain signing authority correlation mismatch");
|
|
76
|
+
}
|
|
77
|
+
if (String(run.event || "") !== "workflow_dispatch") {
|
|
78
|
+
throw new Error("Buildchain signing authority event mismatch");
|
|
79
|
+
}
|
|
80
|
+
if (String(run.head_sha || "") !== authorityRuntimeSha) {
|
|
81
|
+
throw new Error("Buildchain signing authority runtime SHA mismatch");
|
|
82
|
+
}
|
|
83
|
+
if (
|
|
84
|
+
run.repository?.full_name &&
|
|
85
|
+
String(run.repository.full_name) !== authorityRepository
|
|
86
|
+
) {
|
|
87
|
+
throw new Error("Buildchain signing authority repository mismatch");
|
|
88
|
+
}
|
|
89
|
+
if (
|
|
90
|
+
run.path &&
|
|
91
|
+
!String(run.path).startsWith(`.github/workflows/${AUTHORITY_WORKFLOW}@`)
|
|
92
|
+
) {
|
|
93
|
+
throw new Error("Buildchain signing authority workflow path mismatch");
|
|
94
|
+
}
|
|
95
|
+
return run;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export async function githubRequest(
|
|
99
|
+
url,
|
|
100
|
+
{
|
|
101
|
+
token,
|
|
102
|
+
method = "GET",
|
|
103
|
+
body,
|
|
104
|
+
fetchImpl = fetch,
|
|
105
|
+
delayImpl = delay,
|
|
106
|
+
maxAttempts = 5,
|
|
107
|
+
warnImpl = console.warn,
|
|
108
|
+
} = {},
|
|
109
|
+
) {
|
|
40
110
|
const methodName = String(method).toUpperCase();
|
|
41
111
|
const retrySafe = methodName === "GET";
|
|
42
112
|
const requestedAttempts = Number(maxAttempts);
|
|
43
|
-
const attemptLimit =
|
|
44
|
-
|
|
45
|
-
|
|
113
|
+
const attemptLimit =
|
|
114
|
+
retrySafe &&
|
|
115
|
+
Number.isSafeInteger(requestedAttempts) &&
|
|
116
|
+
requestedAttempts > 0
|
|
117
|
+
? requestedAttempts
|
|
118
|
+
: 1;
|
|
46
119
|
for (let attempt = 1; attempt <= attemptLimit; attempt += 1) {
|
|
47
120
|
let response;
|
|
48
121
|
try {
|
|
@@ -59,7 +132,9 @@ export async function githubRequest(url, {
|
|
|
59
132
|
});
|
|
60
133
|
} catch (error) {
|
|
61
134
|
if (retrySafe && attempt < attemptLimit) {
|
|
62
|
-
warnImpl(
|
|
135
|
+
warnImpl(
|
|
136
|
+
`Buildchain signing authority: GitHub API GET transport failure; retry ${attempt + 1}/${attemptLimit}`,
|
|
137
|
+
);
|
|
63
138
|
await delayImpl(retryDelayMs(attempt));
|
|
64
139
|
continue;
|
|
65
140
|
}
|
|
@@ -67,19 +142,29 @@ export async function githubRequest(url, {
|
|
|
67
142
|
}
|
|
68
143
|
if (!response.ok) {
|
|
69
144
|
const text = await response.text();
|
|
70
|
-
if (
|
|
71
|
-
|
|
145
|
+
if (
|
|
146
|
+
retrySafe &&
|
|
147
|
+
TRANSIENT_GITHUB_STATUSES.has(response.status) &&
|
|
148
|
+
attempt < attemptLimit
|
|
149
|
+
) {
|
|
150
|
+
warnImpl(
|
|
151
|
+
`Buildchain signing authority: GitHub API GET returned ${response.status}; retry ${attempt + 1}/${attemptLimit}`,
|
|
152
|
+
);
|
|
72
153
|
await delayImpl(retryDelayMs(attempt));
|
|
73
154
|
continue;
|
|
74
155
|
}
|
|
75
|
-
throw new Error(
|
|
156
|
+
throw new Error(
|
|
157
|
+
`GitHub API ${methodName} ${url} failed (${response.status}): ${text.slice(0, 500)}`,
|
|
158
|
+
);
|
|
76
159
|
}
|
|
77
160
|
if (response.status === 204) return {};
|
|
78
161
|
try {
|
|
79
162
|
return await response.json();
|
|
80
163
|
} catch (error) {
|
|
81
164
|
if (retrySafe && attempt < attemptLimit) {
|
|
82
|
-
warnImpl(
|
|
165
|
+
warnImpl(
|
|
166
|
+
`Buildchain signing authority: GitHub API GET response failed to decode; retry ${attempt + 1}/${attemptLimit}`,
|
|
167
|
+
);
|
|
83
168
|
await delayImpl(retryDelayMs(attempt));
|
|
84
169
|
continue;
|
|
85
170
|
}
|
|
@@ -89,70 +174,236 @@ export async function githubRequest(url, {
|
|
|
89
174
|
throw new Error("GitHub API retry loop exhausted");
|
|
90
175
|
}
|
|
91
176
|
|
|
177
|
+
async function pollArtifactSigningAuthorityRun({
|
|
178
|
+
authorityRepository,
|
|
179
|
+
token,
|
|
180
|
+
authorityRuntimeSha,
|
|
181
|
+
expectedTitle,
|
|
182
|
+
startedAtMs,
|
|
183
|
+
deadline,
|
|
184
|
+
baseOutputs,
|
|
185
|
+
requestImpl,
|
|
186
|
+
delayImpl,
|
|
187
|
+
nowImpl,
|
|
188
|
+
}) {
|
|
189
|
+
let run;
|
|
190
|
+
while (nowImpl() < deadline) {
|
|
191
|
+
const response = await requestImpl(
|
|
192
|
+
`/repos/${authorityRepository}/actions/workflows/${AUTHORITY_WORKFLOW}/runs?event=workflow_dispatch&per_page=50`,
|
|
193
|
+
{ token },
|
|
194
|
+
);
|
|
195
|
+
const matches = (response.workflow_runs || []).filter(
|
|
196
|
+
(entry) =>
|
|
197
|
+
new Date(entry.created_at).getTime() >= startedAtMs - 30_000 &&
|
|
198
|
+
String(entry.display_title || "") === expectedTitle,
|
|
199
|
+
);
|
|
200
|
+
if (matches.length > 1) {
|
|
201
|
+
throw authorityError(
|
|
202
|
+
"multiple Buildchain signing authority runs share one correlation",
|
|
203
|
+
{ ...baseOutputs, "authority-status": "failed" },
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
if (matches.length === 1) {
|
|
207
|
+
try {
|
|
208
|
+
run = validateArtifactSigningAuthorityRun(matches[0], {
|
|
209
|
+
authorityRepository,
|
|
210
|
+
authorityRuntimeSha,
|
|
211
|
+
expectedTitle,
|
|
212
|
+
});
|
|
213
|
+
} catch (error) {
|
|
214
|
+
throw authorityError(error.message, {
|
|
215
|
+
...baseOutputs,
|
|
216
|
+
"authority-run-id": String(matches[0].id || ""),
|
|
217
|
+
"authority-run-url": String(matches[0].html_url || ""),
|
|
218
|
+
"authority-status": "failed",
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
if (run?.status === "completed") return run;
|
|
223
|
+
await delayImpl(10_000);
|
|
224
|
+
}
|
|
225
|
+
throw authorityError("timed out waiting for Buildchain signing authority", {
|
|
226
|
+
...baseOutputs,
|
|
227
|
+
"authority-run-id": String(run?.id || ""),
|
|
228
|
+
"authority-run-url": String(run?.html_url || ""),
|
|
229
|
+
"authority-status": "timed-out",
|
|
230
|
+
"authority-conclusion": String(run?.conclusion || "timed-out"),
|
|
231
|
+
"controller-completed-at": new Date(nowImpl()).toISOString(),
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function successfulAuthorityResult({
|
|
236
|
+
run,
|
|
237
|
+
baseOutputs,
|
|
238
|
+
resultArtifact,
|
|
239
|
+
correlationId,
|
|
240
|
+
controllerStartedAt,
|
|
241
|
+
completedAt,
|
|
242
|
+
}) {
|
|
243
|
+
if (run.conclusion !== "success") {
|
|
244
|
+
const status = run.conclusion === "cancelled" ? "cancelled" : "failed";
|
|
245
|
+
throw authorityError(
|
|
246
|
+
`Buildchain signing authority failed: ${run.html_url}`,
|
|
247
|
+
{
|
|
248
|
+
...baseOutputs,
|
|
249
|
+
"authority-run-id": String(run.id),
|
|
250
|
+
"authority-run-url": String(run.html_url || ""),
|
|
251
|
+
"authority-status": status,
|
|
252
|
+
"authority-conclusion": String(run.conclusion || status),
|
|
253
|
+
"controller-completed-at": completedAt,
|
|
254
|
+
},
|
|
255
|
+
);
|
|
256
|
+
}
|
|
257
|
+
return {
|
|
258
|
+
outputs: {
|
|
259
|
+
...baseOutputs,
|
|
260
|
+
"authority-run-id": String(run.id),
|
|
261
|
+
"authority-run-url": String(run.html_url || ""),
|
|
262
|
+
"authority-status": "succeeded",
|
|
263
|
+
"authority-conclusion": "success",
|
|
264
|
+
"controller-completed-at": completedAt,
|
|
265
|
+
},
|
|
266
|
+
result: {
|
|
267
|
+
runId: run.id,
|
|
268
|
+
runUrl: run.html_url,
|
|
269
|
+
authorityRuntimeSha: baseOutputs["authority-runtime-sha"],
|
|
270
|
+
resultArtifact,
|
|
271
|
+
correlationId,
|
|
272
|
+
status: "succeeded",
|
|
273
|
+
startedAt: controllerStartedAt,
|
|
274
|
+
completedAt,
|
|
275
|
+
},
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
|
|
92
279
|
export async function dispatchArtifactSigningAuthority({
|
|
93
280
|
token = process.env.BUILDCHAIN_AUTHORITY_DISPATCH_TOKEN,
|
|
94
|
-
authorityRepository = process.env.BUILDCHAIN_AUTHORITY_REPOSITORY ||
|
|
281
|
+
authorityRepository = process.env.BUILDCHAIN_AUTHORITY_REPOSITORY ||
|
|
282
|
+
"kungfu-systems/buildchain",
|
|
95
283
|
authorityRef = process.env.BUILDCHAIN_AUTHORITY_REF,
|
|
96
284
|
sourceRepository = process.env.GITHUB_REPOSITORY,
|
|
97
285
|
sourceRunId = process.env.GITHUB_RUN_ID,
|
|
98
286
|
sourceRunAttempt = process.env.GITHUB_RUN_ATTEMPT || "1",
|
|
99
287
|
requestArtifact = process.env.BUILDCHAIN_SIGNING_REQUEST_ARTIFACT,
|
|
288
|
+
requestRoot = process.env.BUILDCHAIN_SIGNING_REQUEST_ROOT_DIGEST,
|
|
100
289
|
runtimeSha = process.env.BUILDCHAIN_RUNTIME_SHA,
|
|
101
290
|
resultArtifact = process.env.BUILDCHAIN_SIGNING_RESULT_ARTIFACT,
|
|
291
|
+
correlationId = process.env.BUILDCHAIN_AUTHORITY_CORRELATION_ID || "",
|
|
102
292
|
timeoutSeconds = process.env.BUILDCHAIN_SIGNING_TIMEOUT_SECONDS || "7200",
|
|
293
|
+
requestImpl = githubRequest,
|
|
294
|
+
delayImpl = delay,
|
|
295
|
+
nowImpl = () => Date.now(),
|
|
103
296
|
} = {}) {
|
|
104
297
|
const authToken = required(token, "Buildchain authority dispatch token");
|
|
105
298
|
const authorityRepo = repository(authorityRepository, "authority repository");
|
|
106
299
|
const sourceRepo = repository(sourceRepository, "source repository");
|
|
107
300
|
const ref = resolveAuthorityDispatchRef(authorityRef);
|
|
108
301
|
const runtime = required(runtimeSha, "Buildchain runtime SHA");
|
|
109
|
-
if (!/^[0-9a-f]{40}$/u.test(runtime))
|
|
110
|
-
|
|
302
|
+
if (!/^[0-9a-f]{40}$/u.test(runtime))
|
|
303
|
+
throw new Error("Buildchain runtime SHA must be exact");
|
|
304
|
+
const runId = required(sourceRunId, "source run ID");
|
|
305
|
+
const runAttempt = required(sourceRunAttempt, "source run attempt");
|
|
306
|
+
if (!/^[1-9][0-9]*$/u.test(runAttempt)) {
|
|
307
|
+
throw new Error("source run attempt must be a positive integer");
|
|
308
|
+
}
|
|
309
|
+
const requestName = required(requestArtifact, "request artifact");
|
|
310
|
+
const requestRootDigest = required(requestRoot, "request root");
|
|
311
|
+
if (!/^sha256:[0-9a-f]{64}$/u.test(requestRootDigest)) {
|
|
312
|
+
throw new Error("request root must be a canonical sha256 root");
|
|
313
|
+
}
|
|
314
|
+
const correlation = required(
|
|
315
|
+
correlationId ||
|
|
316
|
+
`${runId}-${runAttempt}-${runtime.slice(0, 12)}-${requestName.replace(/[^A-Za-z0-9._-]+/gu, "-")}`,
|
|
317
|
+
"correlation ID",
|
|
318
|
+
);
|
|
111
319
|
const resultName = required(resultArtifact, "result artifact");
|
|
112
|
-
const
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
320
|
+
const timeout = Number(timeoutSeconds);
|
|
321
|
+
if (!Number.isFinite(timeout) || timeout <= 0) {
|
|
322
|
+
throw new Error("Buildchain signing timeout must be positive");
|
|
323
|
+
}
|
|
324
|
+
const controllerStartedAtMs = nowImpl();
|
|
325
|
+
const controllerStartedAt = new Date(controllerStartedAtMs).toISOString();
|
|
326
|
+
const baseOutputs = {
|
|
327
|
+
"result-artifact": resultName,
|
|
328
|
+
"correlation-id": correlation,
|
|
329
|
+
"controller-started-at": controllerStartedAt,
|
|
330
|
+
};
|
|
331
|
+
try {
|
|
332
|
+
const authorityRuntime = await resolveArtifactSigningAuthorityRuntime({
|
|
333
|
+
authorityRepository: authorityRepo,
|
|
334
|
+
authorityRef: ref,
|
|
335
|
+
token: authToken,
|
|
336
|
+
requestImpl,
|
|
337
|
+
});
|
|
338
|
+
baseOutputs["authority-runtime-sha"] = authorityRuntime.sha;
|
|
339
|
+
await requestImpl(
|
|
340
|
+
`/repos/${authorityRepo}/actions/workflows/${AUTHORITY_WORKFLOW}/dispatches`,
|
|
341
|
+
{
|
|
342
|
+
token: authToken,
|
|
343
|
+
method: "POST",
|
|
344
|
+
body: {
|
|
345
|
+
ref: authorityRuntime.ref,
|
|
346
|
+
inputs: {
|
|
347
|
+
"source-repository": sourceRepo,
|
|
348
|
+
"source-run-id": runId,
|
|
349
|
+
"source-run-attempt": runAttempt,
|
|
350
|
+
"request-artifact-pattern": requestName,
|
|
351
|
+
"expected-request-root": requestRootDigest,
|
|
352
|
+
"result-artifact-name": resultName,
|
|
353
|
+
"correlation-id": correlation,
|
|
354
|
+
"expected-runtime-sha": runtime,
|
|
355
|
+
},
|
|
356
|
+
},
|
|
126
357
|
},
|
|
127
|
-
},
|
|
128
|
-
});
|
|
129
|
-
const deadline = Date.now() + Number(timeoutSeconds) * 1000;
|
|
130
|
-
let run;
|
|
131
|
-
while (Date.now() < deadline) {
|
|
132
|
-
const response = await githubRequest(`/repos/${authorityRepo}/actions/workflows/${workflow}/runs?event=workflow_dispatch&per_page=50`, { token: authToken });
|
|
133
|
-
run = (response.workflow_runs || []).find((entry) =>
|
|
134
|
-
new Date(entry.created_at).getTime() >= startedAt &&
|
|
135
|
-
String(entry.display_title || "").includes(correlationId),
|
|
136
358
|
);
|
|
137
|
-
|
|
138
|
-
await
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
359
|
+
const expectedTitle = `Sign ${sourceRepo} run ${runId} (${correlation})`;
|
|
360
|
+
const run = await pollArtifactSigningAuthorityRun({
|
|
361
|
+
authorityRepository: authorityRepo,
|
|
362
|
+
token: authToken,
|
|
363
|
+
authorityRuntimeSha: authorityRuntime.sha,
|
|
364
|
+
expectedTitle,
|
|
365
|
+
startedAtMs: controllerStartedAtMs,
|
|
366
|
+
deadline: controllerStartedAtMs + timeout * 1000,
|
|
367
|
+
baseOutputs,
|
|
368
|
+
requestImpl,
|
|
369
|
+
delayImpl,
|
|
370
|
+
nowImpl,
|
|
371
|
+
});
|
|
372
|
+
const completedAt = new Date(nowImpl()).toISOString();
|
|
373
|
+
const completed = successfulAuthorityResult({
|
|
374
|
+
run,
|
|
375
|
+
baseOutputs,
|
|
376
|
+
resultArtifact: resultName,
|
|
377
|
+
correlationId: correlation,
|
|
378
|
+
controllerStartedAt,
|
|
379
|
+
completedAt,
|
|
380
|
+
});
|
|
381
|
+
writeGitHubOutputs(completed.outputs);
|
|
382
|
+
return completed.result;
|
|
383
|
+
} catch (error) {
|
|
384
|
+
if (!error.authorityOutputs) {
|
|
385
|
+
error.authorityOutputs = {
|
|
386
|
+
...baseOutputs,
|
|
387
|
+
"authority-status": "failed",
|
|
388
|
+
"authority-conclusion": "controller-error",
|
|
389
|
+
"controller-completed-at": new Date(nowImpl()).toISOString(),
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
throw error;
|
|
393
|
+
}
|
|
149
394
|
}
|
|
150
395
|
|
|
151
|
-
if (
|
|
396
|
+
if (
|
|
397
|
+
process.argv[1] &&
|
|
398
|
+
import.meta.url === pathToFileURL(process.argv[1]).href
|
|
399
|
+
) {
|
|
152
400
|
try {
|
|
153
401
|
await dispatchArtifactSigningAuthority();
|
|
154
402
|
} catch (error) {
|
|
155
|
-
|
|
403
|
+
if (error?.authorityOutputs) writeGitHubOutputs(error.authorityOutputs);
|
|
404
|
+
console.error(
|
|
405
|
+
`::error::${String(error?.message || error).replace(/\r?\n/gu, "%0A")}`,
|
|
406
|
+
);
|
|
156
407
|
process.exitCode = 1;
|
|
157
408
|
}
|
|
158
409
|
}
|
|
@@ -364,6 +364,7 @@ const manualMetaById = new Map(Object.entries({
|
|
|
364
364
|
"release-activation-transaction": { capabilityGroup: "release-passport-trust", audience: ["release-operator", "agent"], maturity: "preview", order: 125 },
|
|
365
365
|
"release-candidate": { capabilityGroup: "reusable-build", audience: ["release-operator", "consumer"], maturity: "stable", order: 130 },
|
|
366
366
|
"stable-candidate-patrol": { capabilityGroup: "governance-versioning", audience: ["release-operator", "consumer"], maturity: "preview", order: 135 },
|
|
367
|
+
"dev-qualification-patrol": { capabilityGroup: "governance-versioning", audience: ["release-operator", "consumer", "agent"], maturity: "preview", order: 136 },
|
|
367
368
|
"dev-alpha-candidate-patrol": { capabilityGroup: "governance-versioning", audience: ["release-operator", "consumer", "agent"], maturity: "preview", order: 137 },
|
|
368
369
|
"observed-evidence-patrol": { capabilityGroup: "governance-versioning", audience: ["release-operator", "consumer", "agent"], maturity: "preview", order: 140 },
|
|
369
370
|
"reusable-build-surface": { capabilityGroup: "reusable-build", audience: ["consumer", "release-operator"], maturity: "stable", order: 200 },
|
|
@@ -724,6 +725,7 @@ function buildSiteBundle() {
|
|
|
724
725
|
"docs/reusable-build-surface.md",
|
|
725
726
|
"docs/release-candidate.md",
|
|
726
727
|
"docs/stable-candidate-patrol.md",
|
|
728
|
+
"docs/dev-qualification-patrol.md",
|
|
727
729
|
"docs/dev-alpha-candidate-patrol.md",
|
|
728
730
|
"docs/observed-evidence-patrol.md",
|
|
729
731
|
"docs/release-governance.md",
|
|
@@ -784,6 +786,7 @@ function buildSiteBundle() {
|
|
|
784
786
|
["buildchain-patrol-weekly", "repository-patrol"],
|
|
785
787
|
["buildchain-patrol-monthly", "repository-patrol"],
|
|
786
788
|
["stable-candidate-patrol", "repository-patrol"],
|
|
789
|
+
["dev-qualification-patrol", "repository-patrol"],
|
|
787
790
|
["dev-alpha-candidate-patrol", "repository-patrol"],
|
|
788
791
|
["buildchain-stable-candidate-patrol", "repository-patrol"],
|
|
789
792
|
["buildchain-stable-candidate-qualification", "repository-patrol"],
|
|
@@ -4,6 +4,7 @@ import path from "node:path";
|
|
|
4
4
|
import { pathToFileURL } from "node:url";
|
|
5
5
|
|
|
6
6
|
import { validateArtifactSigningRequest } from "../packages/core/artifact-signing.js";
|
|
7
|
+
import { artifactSigningRequestRoot } from "./artifact-signing-controller-core.mjs";
|
|
7
8
|
import { writeGitHubOutputs } from "./build-contract-core.mjs";
|
|
8
9
|
|
|
9
10
|
function required(value, label) {
|
|
@@ -35,12 +36,14 @@ export function inspectArtifactSigningRequests({
|
|
|
35
36
|
inputRoot = process.env.BUILDCHAIN_SIGNING_REQUEST_ROOT,
|
|
36
37
|
expectedRepository = process.env.BUILDCHAIN_SIGNING_SOURCE_REPOSITORY,
|
|
37
38
|
expectedRuntimeSha = process.env.BUILDCHAIN_RUNTIME_SHA,
|
|
39
|
+
expectedRequestRoot = process.env.BUILDCHAIN_SIGNING_EXPECTED_REQUEST_ROOT,
|
|
38
40
|
} = {}) {
|
|
39
41
|
const root = path.resolve(required(inputRoot, "signing request root"));
|
|
40
42
|
const indexes = walk(root, "index.json");
|
|
41
43
|
if (indexes.length === 0)
|
|
42
44
|
throw new Error("no artifact signing request indexes found");
|
|
43
45
|
const seen = new Set();
|
|
46
|
+
const observedRoots = [];
|
|
44
47
|
const matrices = {
|
|
45
48
|
detached: [],
|
|
46
49
|
macos: [],
|
|
@@ -52,6 +55,7 @@ export function inspectArtifactSigningRequests({
|
|
|
52
55
|
index.contract !== "kungfu-buildchain-artifact-signing-request-index/v1"
|
|
53
56
|
)
|
|
54
57
|
continue;
|
|
58
|
+
observedRoots.push(artifactSigningRequestRoot(index));
|
|
55
59
|
for (const entry of index.requests || []) {
|
|
56
60
|
const requestPath = path.resolve(path.dirname(indexPath), entry.path);
|
|
57
61
|
const relative = path.relative(root, requestPath);
|
|
@@ -113,6 +117,14 @@ export function inspectArtifactSigningRequests({
|
|
|
113
117
|
);
|
|
114
118
|
}
|
|
115
119
|
}
|
|
120
|
+
if (expectedRequestRoot) {
|
|
121
|
+
if (observedRoots.length !== 1) {
|
|
122
|
+
throw new Error("signing authority expected exactly one request root");
|
|
123
|
+
}
|
|
124
|
+
if (observedRoots[0] !== expectedRequestRoot) {
|
|
125
|
+
throw new Error("signing authority request root mismatch");
|
|
126
|
+
}
|
|
127
|
+
}
|
|
116
128
|
for (const entries of Object.values(matrices))
|
|
117
129
|
entries.sort((a, b) => a.request.localeCompare(b.request));
|
|
118
130
|
writeGitHubOutputs({
|
|
@@ -18,7 +18,12 @@ import {
|
|
|
18
18
|
writeReleasePropagationLock,
|
|
19
19
|
createManualUpstreamPickupCapture,
|
|
20
20
|
createManualUpstreamPickupPlan,
|
|
21
|
+
createReleasePropagationPushPlan,
|
|
22
|
+
executeReleasePropagationPush,
|
|
23
|
+
inspectReleasePropagationPushState,
|
|
21
24
|
normalizeManualUpstreamPickupConfig,
|
|
25
|
+
planSiteUpstreamAgentEntry,
|
|
26
|
+
RELEASE_PROPAGATION_FAILURE_MATRIX,
|
|
22
27
|
resolveNpmRegistryRelease,
|
|
23
28
|
} from "../packages/core/release-propagation.js";
|
|
24
29
|
|
|
@@ -66,6 +71,19 @@ function usage() {
|
|
|
66
71
|
--completion-decision <json-or-path>
|
|
67
72
|
--expected-work-root <sha256:...>
|
|
68
73
|
[--output <file>] [--json]
|
|
74
|
+
buildchain release-propagation work push-plan --work <json-or-path>
|
|
75
|
+
--expected-work-root <sha256:...>
|
|
76
|
+
[--cwd <dir>] [--remote <name>]
|
|
77
|
+
[--output <file>] [--json]
|
|
78
|
+
buildchain release-propagation work push-branch --work <json-or-path>
|
|
79
|
+
--expected-work-root <sha256:...>
|
|
80
|
+
[--cwd <dir>] [--remote <name>]
|
|
81
|
+
--execute [--output <file>] [--json]
|
|
82
|
+
buildchain release-propagation entry plan --source-id <paper|kfd|buildchain|kungfu-core>
|
|
83
|
+
[--channel <alpha|release>]
|
|
84
|
+
[--handoff-work <json-or-path>]
|
|
85
|
+
[--output <file>] [--json]
|
|
86
|
+
buildchain release-propagation entry fault-matrix [--json]
|
|
69
87
|
buildchain release-propagation pickup plan --config <json-or-path>
|
|
70
88
|
--source-id <id> --channel <alpha|release>
|
|
71
89
|
--current-version <version>
|
|
@@ -182,9 +200,60 @@ function runWorkCli(args) {
|
|
|
182
200
|
emitWorkResult(workArgs, result, `release propagation work complete: ${result.contentRoot}`);
|
|
183
201
|
return;
|
|
184
202
|
}
|
|
203
|
+
if (mode === "push-plan" || mode === "push-branch") {
|
|
204
|
+
const work = readJsonFlag(workArgs, "work");
|
|
205
|
+
const cwd = readFlag(workArgs, "cwd", process.cwd());
|
|
206
|
+
const remote = readFlag(workArgs, "remote", "origin");
|
|
207
|
+
let result;
|
|
208
|
+
if (mode === "push-plan") {
|
|
209
|
+
const repositoryState = inspectReleasePropagationPushState({ work, cwd, remote });
|
|
210
|
+
result = createReleasePropagationPushPlan({
|
|
211
|
+
work,
|
|
212
|
+
expectedWorkRoot: readFlag(workArgs, "expected-work-root"),
|
|
213
|
+
repositoryState,
|
|
214
|
+
});
|
|
215
|
+
} else {
|
|
216
|
+
if (!hasFlag(workArgs, "execute")) throw new Error("work push-branch requires --execute");
|
|
217
|
+
result = executeReleasePropagationPush({
|
|
218
|
+
work,
|
|
219
|
+
expectedWorkRoot: readFlag(workArgs, "expected-work-root"),
|
|
220
|
+
cwd,
|
|
221
|
+
remote,
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
emitWorkResult(workArgs, result, mode === "push-plan"
|
|
225
|
+
? `release propagation push plan: ${result.planRoot}`
|
|
226
|
+
: `release propagation push result: ${result.resultRoot}`);
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
185
229
|
throw new Error(`unsupported release-propagation work command: ${mode}`);
|
|
186
230
|
}
|
|
187
231
|
|
|
232
|
+
function runEntryCli(args) {
|
|
233
|
+
const [mode = "", ...entryArgs] = args;
|
|
234
|
+
if (!mode || mode === "--help" || mode === "-h") {
|
|
235
|
+
process.stdout.write(usage());
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
let result;
|
|
239
|
+
if (mode === "fault-matrix") {
|
|
240
|
+
result = RELEASE_PROPAGATION_FAILURE_MATRIX;
|
|
241
|
+
} else if (mode === "plan") {
|
|
242
|
+
result = planSiteUpstreamAgentEntry({
|
|
243
|
+
sourceId: readFlag(entryArgs, "source-id"),
|
|
244
|
+
channel: readFlag(entryArgs, "channel"),
|
|
245
|
+
handoffWork: entryArgs.includes("--handoff-work")
|
|
246
|
+
? readJsonFlag(entryArgs, "handoff-work")
|
|
247
|
+
: null,
|
|
248
|
+
});
|
|
249
|
+
} else {
|
|
250
|
+
throw new Error(`unsupported release-propagation entry command: ${mode}`);
|
|
251
|
+
}
|
|
252
|
+
writeOutput(readFlag(entryArgs, "output", ""), result);
|
|
253
|
+
if (hasFlag(entryArgs, "json")) printJson(result);
|
|
254
|
+
else process.stdout.write(`${result.contract}\n`);
|
|
255
|
+
}
|
|
256
|
+
|
|
188
257
|
async function resolvePickupPlan(args) {
|
|
189
258
|
const config = normalizeManualUpstreamPickupConfig(readJsonFlag(args, "config"));
|
|
190
259
|
const sourceId = readFlag(args, "source-id");
|
|
@@ -274,6 +343,10 @@ export async function runReleasePropagationCli(argv = process.argv.slice(2)) {
|
|
|
274
343
|
runWorkCli(args);
|
|
275
344
|
return;
|
|
276
345
|
}
|
|
346
|
+
if (mode === "entry") {
|
|
347
|
+
runEntryCli(args);
|
|
348
|
+
return;
|
|
349
|
+
}
|
|
277
350
|
if (mode === "pickup") {
|
|
278
351
|
await runPickupCli(args);
|
|
279
352
|
return;
|
|
@@ -28,6 +28,7 @@ import {
|
|
|
28
28
|
parseExpectedArtifactsJson,
|
|
29
29
|
validateExpectedArtifacts,
|
|
30
30
|
} from "./build-contract-core.mjs";
|
|
31
|
+
import { verifyCompilerCacheActivity } from "./compiler-cache-evidence.mjs";
|
|
31
32
|
|
|
32
33
|
const moduleDir = path.dirname(fileURLToPath(import.meta.url));
|
|
33
34
|
const buildchainCliCandidates = [
|
|
@@ -391,6 +392,22 @@ function writeDiagnosticsSidecarManifest(filePath, {
|
|
|
391
392
|
return manifest;
|
|
392
393
|
}
|
|
393
394
|
|
|
395
|
+
export function verifyBuildLifecycleCompilerCacheActivity({
|
|
396
|
+
stageName = "",
|
|
397
|
+
executed = false,
|
|
398
|
+
cwd = process.cwd(),
|
|
399
|
+
env = process.env,
|
|
400
|
+
verifier = verifyCompilerCacheActivity,
|
|
401
|
+
frameworkLog,
|
|
402
|
+
} = {}) {
|
|
403
|
+
if (stageName !== "build" || !executed) return undefined;
|
|
404
|
+
const activity = verifier({ cwd, env });
|
|
405
|
+
if (activity) {
|
|
406
|
+
frameworkLog?.info("compiler-cache.activity", { attributes: activity });
|
|
407
|
+
}
|
|
408
|
+
return activity;
|
|
409
|
+
}
|
|
410
|
+
|
|
394
411
|
export function runLifecycle({
|
|
395
412
|
cwd = process.cwd(),
|
|
396
413
|
stageName = "",
|
|
@@ -646,6 +663,13 @@ export function runLifecycle({
|
|
|
646
663
|
throw new Error(`required lifecycle stage did not run: ${stageName || "command"}`);
|
|
647
664
|
}
|
|
648
665
|
|
|
666
|
+
const compilerCacheActivity = verifyBuildLifecycleCompilerCacheActivity({
|
|
667
|
+
stageName,
|
|
668
|
+
executed,
|
|
669
|
+
cwd: resolvedCwd,
|
|
670
|
+
frameworkLog,
|
|
671
|
+
});
|
|
672
|
+
|
|
649
673
|
const shouldReadProcessSummary = Boolean(
|
|
650
674
|
resolvedProcessSummaryPath
|
|
651
675
|
&& (fs.existsSync(resolvedProcessSummaryPath) || processSummaryRequired),
|
|
@@ -739,6 +763,7 @@ export function runLifecycle({
|
|
|
739
763
|
fileCount: summary.fileCount,
|
|
740
764
|
});
|
|
741
765
|
observability.lifecycle = lifecycleObservability;
|
|
766
|
+
Object.assign(observability, { compilerCacheActivity });
|
|
742
767
|
observability.diagnostics = {
|
|
743
768
|
contract: BUILDCHAIN_DIAGNOSTICS_CONTRACT,
|
|
744
769
|
path: relativeDiagnosticsPath,
|