@kungfu-tech/buildchain 2.12.9-alpha.1 → 2.13.0-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/actions/run-lifecycle/README.md +5 -0
- package/bin/buildchain.mjs +62 -0
- package/dist/site/buildchain-contract.json +33 -23
- package/dist/site/buildchain-site.json +72 -36
- package/dist/site/capability-registry.json +3 -3
- package/dist/site/cli-registry.json +24 -0
- package/dist/site/controller-registry.json +11 -3
- package/dist/site/kfd-claims.json +64 -13
- package/dist/site/kfd-upstream-aggregate.json +1 -1
- package/dist/site/manual-registry.json +9 -9
- package/dist/site/node-api-registry.json +19 -6
- package/dist/site/page-registry.json +59 -23
- package/dist/site/public-surface-audit.json +47 -10
- package/dist/site/publication-registry.json +4 -4
- package/dist/site/release-provenance.json +1 -0
- package/dist/site/site-manifest.json +13 -13
- package/dist/site/workflow-registry.json +7 -4
- package/docs/MAP.md +1 -0
- package/docs/artifact-verification-envelope.md +113 -0
- package/docs/cli.md +22 -0
- package/docs/install.md +1 -3
- package/docs/publish-transaction.md +1 -1
- package/docs/release-candidate.md +5 -0
- package/docs/release-governance.md +7 -5
- package/docs/release-propagation.md +3 -0
- package/docs/reusable-build-surface.md +13 -0
- package/docs/versioning.md +1 -0
- package/package.json +2 -1
- package/packages/core/README.md +13 -0
- package/packages/core/artifact-passport.js +6 -1
- package/packages/core/artifact-verification-envelope.js +386 -0
- package/packages/core/buildchain-config.js +6 -2
- package/packages/core/controller-evidence.js +2 -3
- package/packages/core/index.js +10 -0
- package/packages/core/release-line-bootstrap.js +24 -0
- package/scripts/check-inventory.mjs +5 -0
- package/scripts/generate-site-bundle.mjs +4 -0
- package/scripts/release-candidate-resolver.mjs +52 -29
- package/scripts/run-lifecycle-core.mjs +50 -6
|
@@ -353,6 +353,9 @@ export async function resolveReleaseCandidateArtifacts({
|
|
|
353
353
|
outputDir = ".buildchain/release-candidate",
|
|
354
354
|
fetchImpl = globalThis.fetch,
|
|
355
355
|
download = true,
|
|
356
|
+
waitSeconds = 600,
|
|
357
|
+
pollIntervalMs = 15000,
|
|
358
|
+
sleepImpl = (milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds)),
|
|
356
359
|
} = {}) {
|
|
357
360
|
const repoInfo = splitRepository(repository);
|
|
358
361
|
const sha = assertSha(targetSha, "targetSha");
|
|
@@ -377,45 +380,63 @@ export async function resolveReleaseCandidateArtifacts({
|
|
|
377
380
|
if (!pullRequest) {
|
|
378
381
|
throw new Error(`no same-repository merged channel PR found for ${sha} into ${normalizedTarget}`);
|
|
379
382
|
}
|
|
380
|
-
const
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
runs: Array.isArray(runs.workflow_runs) ? runs.workflow_runs : [],
|
|
388
|
-
pullRequest,
|
|
389
|
-
workflowName,
|
|
390
|
-
});
|
|
391
|
-
if (!candidateRuns.length) {
|
|
392
|
-
throw new Error(`no successful ${workflowName} pull_request run found for channel PR #${pullRequest.number}`);
|
|
383
|
+
const timeoutMs = Number(waitSeconds) * 1000;
|
|
384
|
+
const intervalMs = Number(pollIntervalMs);
|
|
385
|
+
if (!Number.isFinite(timeoutMs) || timeoutMs < 0) {
|
|
386
|
+
throw new Error(`release-candidate waitSeconds must be a non-negative number, got ${waitSeconds}`);
|
|
387
|
+
}
|
|
388
|
+
if (!Number.isFinite(intervalMs) || intervalMs < 0) {
|
|
389
|
+
throw new Error(`release-candidate pollIntervalMs must be a non-negative number, got ${pollIntervalMs}`);
|
|
393
390
|
}
|
|
391
|
+
const deadline = Date.now() + timeoutMs;
|
|
394
392
|
let run;
|
|
395
393
|
let artifactResponse;
|
|
396
394
|
let selected;
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
395
|
+
let selectionErrors = [];
|
|
396
|
+
let candidateRuns = [];
|
|
397
|
+
while (!selected) {
|
|
398
|
+
const runs = await githubJson({
|
|
400
399
|
apiUrl,
|
|
401
400
|
token,
|
|
402
401
|
fetchImpl,
|
|
403
|
-
path: `/repos/${repoInfo.owner}/${repoInfo.repo}/actions/
|
|
402
|
+
path: `/repos/${repoInfo.owner}/${repoInfo.repo}/actions/workflows/${encodeURIComponent(workflowFile)}/runs?event=pull_request&status=success&per_page=100`,
|
|
404
403
|
});
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
404
|
+
candidateRuns = selectReleaseCandidateRuns({
|
|
405
|
+
runs: Array.isArray(runs.workflow_runs) ? runs.workflow_runs : [],
|
|
406
|
+
pullRequest,
|
|
407
|
+
workflowName,
|
|
408
|
+
});
|
|
409
|
+
selectionErrors = [];
|
|
410
|
+
for (const candidateRun of candidateRuns) {
|
|
411
|
+
const candidateArtifactResponse = await githubJson({
|
|
412
|
+
apiUrl,
|
|
413
|
+
token,
|
|
414
|
+
fetchImpl,
|
|
415
|
+
path: `/repos/${repoInfo.owner}/${repoInfo.repo}/actions/runs/${candidateRun.id}/artifacts?per_page=100`,
|
|
409
416
|
});
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
417
|
+
try {
|
|
418
|
+
selected = selectReleaseCandidateArtifacts({
|
|
419
|
+
artifacts: Array.isArray(candidateArtifactResponse.artifacts) ? candidateArtifactResponse.artifacts : [],
|
|
420
|
+
artifactName,
|
|
421
|
+
});
|
|
422
|
+
run = candidateRun;
|
|
423
|
+
artifactResponse = candidateArtifactResponse;
|
|
424
|
+
break;
|
|
425
|
+
} catch (error) {
|
|
426
|
+
selectionErrors.push(`run ${candidateRun.id}: ${error.message}`);
|
|
427
|
+
}
|
|
415
428
|
}
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
429
|
+
if (selected) break;
|
|
430
|
+
if (Date.now() >= deadline) {
|
|
431
|
+
if (!candidateRuns.length) {
|
|
432
|
+
throw new Error(`no successful ${workflowName} pull_request run found for channel PR #${pullRequest.number} within ${waitSeconds}s`);
|
|
433
|
+
}
|
|
434
|
+
throw new Error(`no successful ${workflowName} pull_request run for channel PR #${pullRequest.number} contained release-candidate artifacts within ${waitSeconds}s: ${selectionErrors.join("; ")}`);
|
|
435
|
+
}
|
|
436
|
+
console.log(
|
|
437
|
+
`> waiting for successful ${workflowName} release-candidate evidence for channel PR #${pullRequest.number}`,
|
|
438
|
+
);
|
|
439
|
+
await sleepImpl(intervalMs);
|
|
419
440
|
}
|
|
420
441
|
const payloadArtifacts = selectPayloadArtifacts({
|
|
421
442
|
artifacts: Array.isArray(artifactResponse.artifacts) ? artifactResponse.artifacts : [],
|
|
@@ -551,6 +572,8 @@ export async function resolveReleaseCandidateArtifactsCli() {
|
|
|
551
572
|
publishPackageMain: env("BUILDCHAIN_PUBLISH_PACKAGE_MAIN"),
|
|
552
573
|
outputDir: env("BUILDCHAIN_RC_OUTPUT_DIR", ".buildchain/release-candidate"),
|
|
553
574
|
download: releaseCandidateDownloadEnabled(env("BUILDCHAIN_RC_DOWNLOAD", "true")),
|
|
575
|
+
waitSeconds: env("BUILDCHAIN_RC_WAIT_SECONDS", "600"),
|
|
576
|
+
pollIntervalMs: env("BUILDCHAIN_RC_POLL_INTERVAL_MS", "15000"),
|
|
554
577
|
});
|
|
555
578
|
writeGitHubOutputs({
|
|
556
579
|
"promote-only-release-candidate": String(result.enabled === true),
|
|
@@ -97,6 +97,28 @@ function lifecycleErrorAttributes(error, extra = {}) {
|
|
|
97
97
|
return attributes;
|
|
98
98
|
}
|
|
99
99
|
|
|
100
|
+
function describeLifecycleTimeout(error, {
|
|
101
|
+
timeoutMinutes,
|
|
102
|
+
stageName,
|
|
103
|
+
platformId,
|
|
104
|
+
platformName,
|
|
105
|
+
}) {
|
|
106
|
+
if (
|
|
107
|
+
!timeoutMinutes ||
|
|
108
|
+
(error?.code !== "ETIMEDOUT" && error?.signal !== "SIGTERM")
|
|
109
|
+
) {
|
|
110
|
+
return error;
|
|
111
|
+
}
|
|
112
|
+
const timeoutError = new Error(
|
|
113
|
+
`lifecycle ${stageName || "command"} timed out after ${timeoutMinutes} minute(s) on ${platformName} (${platformId})`,
|
|
114
|
+
{ cause: error },
|
|
115
|
+
);
|
|
116
|
+
timeoutError.name = "LifecycleTimeoutError";
|
|
117
|
+
timeoutError.code = "ETIMEDOUT";
|
|
118
|
+
timeoutError.signal = error?.signal || "";
|
|
119
|
+
return timeoutError;
|
|
120
|
+
}
|
|
121
|
+
|
|
100
122
|
function collectArtifactFiles(root, patterns) {
|
|
101
123
|
const files = new Set();
|
|
102
124
|
for (const pattern of patterns) {
|
|
@@ -245,9 +267,10 @@ function runLifecycleStageWithSampler({
|
|
|
245
267
|
processSamplesPath,
|
|
246
268
|
processSampleIntervalMs,
|
|
247
269
|
requestedParallelism,
|
|
270
|
+
timeoutMinutes,
|
|
248
271
|
}) {
|
|
249
272
|
if (!sampleProcessTree) {
|
|
250
|
-
return runLifecycleStage({ cwd, loadedConfig, name, env });
|
|
273
|
+
return runLifecycleStage({ cwd, loadedConfig, name, env, timeoutMinutes });
|
|
251
274
|
}
|
|
252
275
|
const lifecycle = loadedConfig?.config?.lifecycle || {};
|
|
253
276
|
const stage = lifecycle[name];
|
|
@@ -260,7 +283,8 @@ function runLifecycleStageWithSampler({
|
|
|
260
283
|
...(stage.env || {}),
|
|
261
284
|
...(env || {}),
|
|
262
285
|
};
|
|
263
|
-
const
|
|
286
|
+
const effectiveTimeoutMinutes = stage.timeoutMinutes ?? timeoutMinutes;
|
|
287
|
+
const timeout = effectiveTimeoutMinutes ? effectiveTimeoutMinutes * 60_000 : undefined;
|
|
264
288
|
let lastError;
|
|
265
289
|
for (let attempt = 1; attempt <= stage.retries; attempt += 1) {
|
|
266
290
|
try {
|
|
@@ -362,6 +386,7 @@ export function runLifecycle({
|
|
|
362
386
|
stageName = "",
|
|
363
387
|
command = "",
|
|
364
388
|
required = false,
|
|
389
|
+
timeoutMinutes,
|
|
365
390
|
manifestPath = ".buildchain/artifacts/manifest.json",
|
|
366
391
|
summaryPath = ".buildchain/artifacts/summary.json",
|
|
367
392
|
diagnosticsPath = "",
|
|
@@ -381,6 +406,9 @@ export function runLifecycle({
|
|
|
381
406
|
requestedParallelism = 0,
|
|
382
407
|
processSummaryRequired = true,
|
|
383
408
|
} = {}) {
|
|
409
|
+
if (timeoutMinutes !== undefined && (!Number.isFinite(timeoutMinutes) || timeoutMinutes <= 0)) {
|
|
410
|
+
throw new Error("lifecycle timeoutMinutes must be a positive number");
|
|
411
|
+
}
|
|
384
412
|
const resolvedCwd = path.resolve(cwd);
|
|
385
413
|
const resolvedWorkspace = path.resolve(workspace);
|
|
386
414
|
const resolvedManifestPath = path.resolve(resolvedWorkspace, manifestPath);
|
|
@@ -449,6 +477,7 @@ export function runLifecycle({
|
|
|
449
477
|
const lifecycle = loadedConfig?.config?.lifecycle || {};
|
|
450
478
|
const configuredStage = stageName ? lifecycle[stageName] : undefined;
|
|
451
479
|
const commandShell = configuredStage?.shell || true;
|
|
480
|
+
const effectiveCommandTimeoutMinutes = configuredStage?.timeoutMinutes ?? timeoutMinutes;
|
|
452
481
|
const startedAt = Date.now();
|
|
453
482
|
userLog.info("lifecycle.command.start", {
|
|
454
483
|
attributes: {
|
|
@@ -480,6 +509,7 @@ export function runLifecycle({
|
|
|
480
509
|
processSamplesPath: resolvedProcessSamplesPath,
|
|
481
510
|
processSampleIntervalMs,
|
|
482
511
|
requestedParallelism,
|
|
512
|
+
timeout: effectiveCommandTimeoutMinutes ? effectiveCommandTimeoutMinutes * 60_000 : undefined,
|
|
483
513
|
});
|
|
484
514
|
} else {
|
|
485
515
|
execSync(command, {
|
|
@@ -487,6 +517,7 @@ export function runLifecycle({
|
|
|
487
517
|
env: commandEnv,
|
|
488
518
|
shell: commandShell,
|
|
489
519
|
stdio: "inherit",
|
|
520
|
+
timeout: effectiveCommandTimeoutMinutes ? effectiveCommandTimeoutMinutes * 60_000 : undefined,
|
|
490
521
|
});
|
|
491
522
|
}
|
|
492
523
|
executed = true;
|
|
@@ -498,16 +529,22 @@ export function runLifecycle({
|
|
|
498
529
|
},
|
|
499
530
|
});
|
|
500
531
|
} catch (error) {
|
|
532
|
+
const lifecycleError = describeLifecycleTimeout(error, {
|
|
533
|
+
timeoutMinutes: effectiveCommandTimeoutMinutes,
|
|
534
|
+
stageName,
|
|
535
|
+
platformId,
|
|
536
|
+
platformName,
|
|
537
|
+
});
|
|
501
538
|
userLog.error("lifecycle.command.error", {
|
|
502
539
|
durationMs: Date.now() - startedAt,
|
|
503
540
|
message: "lifecycle command failed",
|
|
504
|
-
attributes: lifecycleErrorAttributes(
|
|
541
|
+
attributes: lifecycleErrorAttributes(lifecycleError, {
|
|
505
542
|
commandSource,
|
|
506
543
|
stage: stageName || "command",
|
|
507
544
|
sampleProcessTree,
|
|
508
545
|
}),
|
|
509
546
|
});
|
|
510
|
-
throw
|
|
547
|
+
throw lifecycleError;
|
|
511
548
|
}
|
|
512
549
|
} else if (stageName) {
|
|
513
550
|
commandSource = "buildchain.toml";
|
|
@@ -535,6 +572,7 @@ export function runLifecycle({
|
|
|
535
572
|
processSamplesPath: resolvedProcessSamplesPath,
|
|
536
573
|
processSampleIntervalMs,
|
|
537
574
|
requestedParallelism,
|
|
575
|
+
timeoutMinutes,
|
|
538
576
|
});
|
|
539
577
|
userLog.info("lifecycle.stage.end", {
|
|
540
578
|
durationMs: Date.now() - startedAt,
|
|
@@ -545,16 +583,22 @@ export function runLifecycle({
|
|
|
545
583
|
},
|
|
546
584
|
});
|
|
547
585
|
} catch (error) {
|
|
586
|
+
const lifecycleError = describeLifecycleTimeout(error, {
|
|
587
|
+
timeoutMinutes: loadedConfig?.config?.lifecycle?.[stageName]?.timeoutMinutes ?? timeoutMinutes,
|
|
588
|
+
stageName,
|
|
589
|
+
platformId,
|
|
590
|
+
platformName,
|
|
591
|
+
});
|
|
548
592
|
userLog.error("lifecycle.stage.error", {
|
|
549
593
|
durationMs: Date.now() - startedAt,
|
|
550
594
|
message: "lifecycle stage failed",
|
|
551
|
-
attributes: lifecycleErrorAttributes(
|
|
595
|
+
attributes: lifecycleErrorAttributes(lifecycleError, {
|
|
552
596
|
commandSource,
|
|
553
597
|
stage: stageName,
|
|
554
598
|
sampleProcessTree,
|
|
555
599
|
}),
|
|
556
600
|
});
|
|
557
|
-
throw
|
|
601
|
+
throw lifecycleError;
|
|
558
602
|
}
|
|
559
603
|
}
|
|
560
604
|
|