@kungfu-tech/buildchain 3.0.6-alpha.1 → 3.0.6-alpha.3
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/README.md +4 -4
- package/actions/promote-buildchain-ref/README.md +8 -0
- package/bin/buildchain.mjs +13 -1
- package/contracts/auditable-demo-scenario-v1.schema.json +52 -0
- package/contracts/release-candidate-recovery-v1.schema.json +102 -0
- package/dist/site/buildchain-contract.json +129 -34
- package/dist/site/buildchain-site.json +171 -45
- package/dist/site/capability-registry.json +5 -5
- package/dist/site/cli-registry.json +40 -4
- package/dist/site/controller-registry.json +56 -4
- package/dist/site/kfd-claims.json +184 -20
- package/dist/site/kfd-upstream-aggregate.json +1 -1
- package/dist/site/manual-registry.json +10 -10
- package/dist/site/node-api-registry.json +1242 -121
- package/dist/site/page-registry.json +157 -31
- package/dist/site/public-surface-audit.json +422 -21
- package/dist/site/publication-authority-registry.json +66 -1
- package/dist/site/publication-registry.json +4 -4
- package/dist/site/release-provenance.json +2 -0
- package/dist/site/site-manifest.json +14 -14
- package/dist/site/workflow-registry.json +197 -15
- package/docs/MAP.md +1 -0
- package/docs/auditable-demo.md +58 -11
- package/docs/aws-us-elastic-runner-burst-plane.md +23 -19
- package/docs/cli-reference.md +154 -0
- package/docs/dev-alpha-candidate-patrol.md +13 -5
- package/docs/dev-delivery-warrant.md +158 -0
- package/docs/node-api-reference.md +96 -38
- package/docs/publication-authority.md +11 -0
- package/docs/publish-transaction.md +10 -1
- package/docs/release-candidate.md +106 -12
- package/docs/release-governance.md +52 -0
- package/docs/reusable-build-surface.md +11 -1
- package/docs/shifu-gate-profiles.md +12 -1
- package/docs/versioning.md +2 -0
- package/package.json +3 -1
- package/packages/core/buildchain-contract.js +30 -2
- package/packages/core/buildchain-publication-authority.js +3 -1
- package/packages/core/channel-candidate.js +2 -21
- package/packages/core/channel-promotion-baseline.js +199 -0
- package/packages/core/dev-delivery-candidate-identity.js +94 -0
- package/packages/core/dev-delivery-common.js +73 -0
- package/packages/core/dev-delivery-proof.js +252 -0
- package/packages/core/dev-delivery-warrant-cancellation.js +94 -0
- package/packages/core/dev-delivery-warrant-settlement.js +73 -0
- package/packages/core/dev-delivery-warrant.js +591 -0
- package/packages/core/index.js +7 -0
- package/packages/core/release-candidate-recovery.js +386 -0
- package/scripts/auditable-demo-bundle-verification.mjs +148 -0
- package/scripts/auditable-demo-platform.mjs +86 -50
- package/scripts/auditable-demo-presentation.mjs +83 -0
- package/scripts/auditable-demo-renditions.mjs +264 -0
- package/scripts/auditable-demo.mjs +24 -30
- package/scripts/build-contract-core.mjs +58 -3
- package/scripts/buildchain-cli-help.mjs +8 -0
- package/scripts/buildchain-patrol.mjs +9 -0
- package/scripts/check-inventory.mjs +13 -2
- package/scripts/dev-alpha-candidate-patrol.mjs +45 -48
- package/scripts/dev-delivery-proof.mjs +193 -0
- package/scripts/dev-delivery-warrant.mjs +426 -0
- package/scripts/dev-pr-auto-merge.mjs +488 -46
- package/scripts/dev-pr-delivery-warrant.mjs +209 -0
- package/scripts/gate-profile-core.mjs +24 -0
- package/scripts/generate-site-bundle.mjs +2 -2
- package/scripts/git-fetch-process-tree.mjs +142 -0
- package/scripts/lifecycle-substage-evidence.mjs +274 -0
- package/scripts/locked-source-checkout.mjs +6 -3
- package/scripts/release-candidate-resolver.mjs +35 -3
- package/scripts/resolve-artifact-transfer-mode.mjs +9 -0
- package/scripts/resolve-build-contract.mjs +7 -0
- package/scripts/resume-from-candidate-run.mjs +498 -0
- package/scripts/route-offline-runners.mjs +1 -0
- package/scripts/run-lifecycle-core.mjs +9 -9
- package/scripts/shifu-gate-profile.mjs +10 -16
- package/scripts/site-capability-metadata.mjs +15 -0
|
@@ -7,10 +7,7 @@ import os from "node:os";
|
|
|
7
7
|
import path from "node:path";
|
|
8
8
|
import { spawnSync } from "node:child_process";
|
|
9
9
|
import { fileURLToPath } from "node:url";
|
|
10
|
-
import {
|
|
11
|
-
validateRenditionSet,
|
|
12
|
-
validateTerminalCapture,
|
|
13
|
-
} from "./auditable-demo-renditions.mjs";
|
|
10
|
+
import { readRendererManifest, validateRendererCompositionInputs, validateRenditionSet, validateTerminalCapture } from "./auditable-demo-renditions.mjs";
|
|
14
11
|
|
|
15
12
|
const UTF8 = new TextDecoder("utf-8", { fatal: true });
|
|
16
13
|
const IMAGE_PATTERN = /^[a-z0-9][a-z0-9./_-]*@sha256:[0-9a-f]{64}$/;
|
|
@@ -130,7 +127,7 @@ function writeChecksums(root, checksumName = "checksums.sha256") {
|
|
|
130
127
|
return sha256(Buffer.from(bytes));
|
|
131
128
|
}
|
|
132
129
|
|
|
133
|
-
function verifyChecksums(root, checksumName = "checksums.sha256") {
|
|
130
|
+
function verifyChecksums(root, checksumName = "checksums.sha256", options = {}) {
|
|
134
131
|
const bytes = readRegular(path.join(root, checksumName), checksumName);
|
|
135
132
|
const text = decodeUtf8(bytes, checksumName);
|
|
136
133
|
invariant(text.endsWith("\n"), `${checksumName} must end with a newline`);
|
|
@@ -143,8 +140,11 @@ function verifyChecksums(root, checksumName = "checksums.sha256") {
|
|
|
143
140
|
const target = resolveInside(root, member, "checksum member");
|
|
144
141
|
invariant(!declared.has(member), `duplicate checksum member: ${member}`);
|
|
145
142
|
declared.add(member);
|
|
143
|
+
const maximumBytes = options.allowLongFormRendererManifest && member === "manifest.json"
|
|
144
|
+
? 32 * 1024 * 1024
|
|
145
|
+
: MAX_BUNDLE_MEMBER_BYTES;
|
|
146
146
|
invariant(
|
|
147
|
-
sha256(readRegular(target, member,
|
|
147
|
+
sha256(readRegular(target, member, maximumBytes)).slice(7) === match[1],
|
|
148
148
|
`checksum mismatch: ${member}`,
|
|
149
149
|
);
|
|
150
150
|
}
|
|
@@ -190,7 +190,7 @@ function validateScene(value) {
|
|
|
190
190
|
exactKeys(
|
|
191
191
|
value,
|
|
192
192
|
["schema", "id", "width", "height", "fps", "durationMs", "title"],
|
|
193
|
-
["durationClass", "commandLabel", "background", "accent"],
|
|
193
|
+
["durationClass", "compositionMode", "commandLabel", "background", "accent"],
|
|
194
194
|
"scene",
|
|
195
195
|
);
|
|
196
196
|
invariant(value.schema === "build-images.demo-scene/v1", "unsupported scene schema");
|
|
@@ -199,6 +199,11 @@ function validateScene(value) {
|
|
|
199
199
|
integer(value.height, 360, 1080, "scene.height");
|
|
200
200
|
const durationClass = value.durationClass ?? "standard";
|
|
201
201
|
invariant(durationClass === "standard" || durationClass === "long-form", "scene.durationClass is invalid");
|
|
202
|
+
const compositionMode = value.compositionMode ?? "presentation-framed";
|
|
203
|
+
invariant(
|
|
204
|
+
compositionMode === "presentation-framed" || compositionMode === "terminal-fill",
|
|
205
|
+
"scene.compositionMode is invalid",
|
|
206
|
+
);
|
|
202
207
|
const maximumDurationMs = durationClass === "long-form" ? LONG_FORM_MAX_DURATION_MS : STANDARD_MAX_DURATION_MS;
|
|
203
208
|
const maximumFps = durationClass === "long-form" ? LONG_FORM_MAX_FPS : 30;
|
|
204
209
|
integer(value.fps, 1, maximumFps, "scene.fps");
|
|
@@ -216,6 +221,7 @@ function validateScene(value) {
|
|
|
216
221
|
height: value.height,
|
|
217
222
|
fps: value.fps,
|
|
218
223
|
...(value.durationClass === undefined ? {} : { durationClass }),
|
|
224
|
+
compositionMode,
|
|
219
225
|
durationMs: value.durationMs,
|
|
220
226
|
title: value.title,
|
|
221
227
|
commandLabel: value.commandLabel ?? "",
|
|
@@ -674,8 +680,7 @@ function inspectRendererMedia(values) {
|
|
|
674
680
|
const rendererImage = required(values, "--renderer-image");
|
|
675
681
|
invariant(IMAGE_PATTERN.test(rendererImage), "renderer image must use an immutable sha256 coordinate");
|
|
676
682
|
invariant(!fs.existsSync(output), "media inspection output must not already exist");
|
|
677
|
-
const manifest =
|
|
678
|
-
invariant(manifest.schema === "build-images.auditable-demo-render/v1", "unexpected renderer manifest schema");
|
|
683
|
+
const { manifest } = readRendererManifest(path.join(renderOutput, "manifest.json"), RENDITION_VALIDATION_HELPERS);
|
|
679
684
|
invariant(manifest.renderer?.image === rendererImage, "renderer manifest image coordinate mismatch");
|
|
680
685
|
const members = Object.keys(manifest.outputs || {})
|
|
681
686
|
.filter((name) => name !== "media-probe.json")
|
|
@@ -739,7 +744,7 @@ function qualifyMediaFixture(values) {
|
|
|
739
744
|
sha256(readRegular(path.join(renderOutput, name), `fixture ${name}`)),
|
|
740
745
|
]),
|
|
741
746
|
),
|
|
742
|
-
rendererManifestRoot: sha256(
|
|
747
|
+
rendererManifestRoot: sha256(readRendererManifest(path.join(renderOutput, "manifest.json"), RENDITION_VALIDATION_HELPERS).bytes),
|
|
743
748
|
qualification: verified.qualification,
|
|
744
749
|
};
|
|
745
750
|
writeJson(output, { ...body, evidenceRoot: semanticRoot(body) });
|
|
@@ -979,9 +984,8 @@ function verifyRendererOutput(renderOutput, expectedImage, expectedInputs, optio
|
|
|
979
984
|
"public-projection.json",
|
|
980
985
|
"scene.json",
|
|
981
986
|
];
|
|
982
|
-
verifyChecksums(renderOutput);
|
|
983
|
-
const manifest =
|
|
984
|
-
invariant(manifest.schema === "build-images.auditable-demo-render/v1", "unexpected renderer manifest schema");
|
|
987
|
+
verifyChecksums(renderOutput, "checksums.sha256", { allowLongFormRendererManifest: true });
|
|
988
|
+
const { manifest } = readRendererManifest(path.join(renderOutput, "manifest.json"), RENDITION_VALIDATION_HELPERS);
|
|
985
989
|
invariant(manifest.renderer?.image === expectedImage, "renderer manifest image coordinate mismatch");
|
|
986
990
|
const outputNames = Object.keys(manifest.outputs || {}).sort();
|
|
987
991
|
invariant(outputNames.includes("media-probe.json"), "renderer manifest must declare media-probe.json");
|
|
@@ -999,21 +1003,11 @@ function verifyRendererOutput(renderOutput, expectedImage, expectedInputs, optio
|
|
|
999
1003
|
const observed = manifest.inputs?.[key]?.root;
|
|
1000
1004
|
invariant(observed === sha256(readRegular(filePath, `${key} input`)), `renderer ${key} input root mismatch`);
|
|
1001
1005
|
}
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
invariant(
|
|
1008
|
-
Array.isArray(manifest.inputs?.renditions)
|
|
1009
|
-
&& manifest.inputs.renditions.length === 2
|
|
1010
|
-
&& manifest.inputs.renditions[0]?.role === "primary"
|
|
1011
|
-
&& manifest.inputs.renditions[1]?.role === "responsive"
|
|
1012
|
-
&& manifest.inputs.renditions[0]?.terminalCapture?.root
|
|
1013
|
-
!== manifest.inputs.renditions[1]?.terminalCapture?.root,
|
|
1014
|
-
"renderer native rendition inputs are not independently bound",
|
|
1015
|
-
);
|
|
1016
|
-
}
|
|
1006
|
+
const composition = validateRendererCompositionInputs(
|
|
1007
|
+
manifest,
|
|
1008
|
+
expectedInputs,
|
|
1009
|
+
RENDITION_VALIDATION_HELPERS,
|
|
1010
|
+
);
|
|
1017
1011
|
const probe = readJson(path.join(renderOutput, "media-probe.json"), "media probe");
|
|
1018
1012
|
invariant(probe.schema === "build-images.demo-media-probe/v1" && probe.passed === true, "renderer media probe failed");
|
|
1019
1013
|
const qualification = qualifyRendererOutput(
|
|
@@ -1024,7 +1018,7 @@ function verifyRendererOutput(renderOutput, expectedImage, expectedInputs, optio
|
|
|
1024
1018
|
options.inspectMedia || inspectMediaFile,
|
|
1025
1019
|
options.inspectionRoot || "",
|
|
1026
1020
|
);
|
|
1027
|
-
return { manifest, probe, qualification };
|
|
1021
|
+
return { manifest, probe, qualification, composition };
|
|
1028
1022
|
}
|
|
1029
1023
|
|
|
1030
1024
|
function renditionInputRoots(root, renditions) {
|
|
@@ -1254,7 +1248,7 @@ function finalizeMedia(values) {
|
|
|
1254
1248
|
sourceSha,
|
|
1255
1249
|
qualifiedGateRoot: gateRoot,
|
|
1256
1250
|
rendererImage,
|
|
1257
|
-
rendererManifestRoot: sha256(
|
|
1251
|
+
rendererManifestRoot: sha256(readRendererManifest(path.join(renderOutput, "manifest.json"), RENDITION_VALIDATION_HELPERS).bytes),
|
|
1258
1252
|
};
|
|
1259
1253
|
const mediaReceipt = selectedProfile.mode === "archive"
|
|
1260
1254
|
? { schema: "buildchain.auditable-demo-media/v1", ...commonReceipt }
|
|
@@ -3,7 +3,6 @@ import fs from "node:fs";
|
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { macosJitRunnerLabel } from "./aws-macos-jit-core.mjs";
|
|
5
5
|
import { windowsJitRunnerLabel } from "./aws-windows-jit-core.mjs";
|
|
6
|
-
|
|
7
6
|
export const RUNNER_PRESETS = Object.freeze({
|
|
8
7
|
"github-hosted": [
|
|
9
8
|
{ id: "linux-x64", name: "Linux x64", platform: "linux", runner: '["ubuntu-24.04"]', capabilities: ["node"] },
|
|
@@ -768,6 +767,55 @@ function platformIsLinux(platform) {
|
|
|
768
767
|
);
|
|
769
768
|
}
|
|
770
769
|
|
|
770
|
+
function platformUsesGitHubHostedRunner(platform, index) {
|
|
771
|
+
if (platform?.githubHosted !== undefined) {
|
|
772
|
+
if (typeof platform.githubHosted !== "boolean") {
|
|
773
|
+
throw new Error(
|
|
774
|
+
`platforms-json[${index}].githubHosted must be a boolean`,
|
|
775
|
+
);
|
|
776
|
+
}
|
|
777
|
+
return platform.githubHosted;
|
|
778
|
+
}
|
|
779
|
+
if (String(platform?.provider || "").trim()) {
|
|
780
|
+
return false;
|
|
781
|
+
}
|
|
782
|
+
const runnerLabels = parseJsonArray(
|
|
783
|
+
String(platform?.runner || "[]"),
|
|
784
|
+
`platforms-json[${index}].runner`,
|
|
785
|
+
).map((label) => String(label || "").toLowerCase());
|
|
786
|
+
if (runnerLabels.includes("self-hosted") || runnerLabels.length !== 1) {
|
|
787
|
+
return false;
|
|
788
|
+
}
|
|
789
|
+
return /^(ubuntu-(latest|20\.04|22\.04|24\.04|24\.04-arm)|windows-(latest|2019|2022|2025|11-arm)|macos-(latest|13|14|15|26)(-intel)?)$/.test(
|
|
790
|
+
runnerLabels[0],
|
|
791
|
+
);
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
function bindRunnerHosting(platforms) {
|
|
795
|
+
return platforms.map((platform, index) => ({
|
|
796
|
+
...platform,
|
|
797
|
+
githubHosted: platformUsesGitHubHostedRunner(platform, index),
|
|
798
|
+
}));
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
function runnerHostingSummary(platforms) {
|
|
802
|
+
const githubHostedPlatforms = platforms.filter(
|
|
803
|
+
(platform) => platform.githubHosted,
|
|
804
|
+
);
|
|
805
|
+
const relayPlatforms = platforms.filter((platform) => !platform.githubHosted);
|
|
806
|
+
return {
|
|
807
|
+
githubHostedPlatforms,
|
|
808
|
+
githubHostedPlatformsJson: JSON.stringify(githubHostedPlatforms),
|
|
809
|
+
githubHostedPlatformIdsJson: JSON.stringify(
|
|
810
|
+
githubHostedPlatforms.map((platform) => platform.id),
|
|
811
|
+
),
|
|
812
|
+
githubHostedPlatformCount: githubHostedPlatforms.length,
|
|
813
|
+
relayPlatforms,
|
|
814
|
+
relayPlatformsJson: JSON.stringify(relayPlatforms),
|
|
815
|
+
relayPlatformCount: relayPlatforms.length,
|
|
816
|
+
};
|
|
817
|
+
}
|
|
818
|
+
|
|
771
819
|
function normalizePlatform(platform, index) {
|
|
772
820
|
const id = String(platform?.id || "").trim();
|
|
773
821
|
const name = String(platform?.name || id).trim();
|
|
@@ -793,7 +841,9 @@ function normalizePlatform(platform, index) {
|
|
|
793
841
|
if (platform?.platform !== undefined) normalized.platform = String(platform.platform || "").trim();
|
|
794
842
|
if (platform?.provider !== undefined) normalized.provider = String(platform.provider || "").trim();
|
|
795
843
|
if (platform?.project !== undefined) normalized.project = String(platform.project || "").trim();
|
|
844
|
+
if (platform?.githubHosted !== undefined) normalized.githubHosted = platform.githubHosted;
|
|
796
845
|
normalized.capabilities = capabilities.sort();
|
|
846
|
+
if (platform?.environment !== undefined) normalized.environment = platform.environment;
|
|
797
847
|
if (platform?.required === false) normalized.required = false;
|
|
798
848
|
return normalized;
|
|
799
849
|
}
|
|
@@ -813,8 +863,10 @@ export function resolveRunnerMatrix({
|
|
|
813
863
|
linuxContainerImage,
|
|
814
864
|
});
|
|
815
865
|
if (customPlatformsJson) {
|
|
816
|
-
const platforms =
|
|
817
|
-
|
|
866
|
+
const platforms = bindRunnerHosting(
|
|
867
|
+
parseJsonArray(customPlatformsJson, "platforms-json").map(
|
|
868
|
+
normalizePlatform,
|
|
869
|
+
),
|
|
818
870
|
);
|
|
819
871
|
if (platforms.length === 0) {
|
|
820
872
|
throw new Error("platforms-json must include at least one platform");
|
|
@@ -838,6 +890,7 @@ export function resolveRunnerMatrix({
|
|
|
838
890
|
containerPlatformsJson: JSON.stringify(containerPlatforms),
|
|
839
891
|
containerPlatformCount: containerPlatforms.length,
|
|
840
892
|
linuxContainer,
|
|
893
|
+
...runnerHostingSummary(platforms),
|
|
841
894
|
};
|
|
842
895
|
}
|
|
843
896
|
|
|
@@ -875,6 +928,7 @@ export function resolveRunnerMatrix({
|
|
|
875
928
|
runner: JSON.stringify(["self-hosted", "macOS", "ARM64", runnerLabel]),
|
|
876
929
|
}));
|
|
877
930
|
}
|
|
931
|
+
resolvedPlatforms = bindRunnerHosting(resolvedPlatforms);
|
|
878
932
|
const containerPlatforms = linuxContainer.enabled
|
|
879
933
|
? resolvedPlatforms.filter(platformIsLinux)
|
|
880
934
|
: [];
|
|
@@ -894,6 +948,7 @@ export function resolveRunnerMatrix({
|
|
|
894
948
|
containerPlatformsJson: JSON.stringify(containerPlatforms),
|
|
895
949
|
containerPlatformCount: containerPlatforms.length,
|
|
896
950
|
linuxContainer,
|
|
951
|
+
...runnerHostingSummary(resolvedPlatforms),
|
|
897
952
|
};
|
|
898
953
|
}
|
|
899
954
|
|
|
@@ -108,10 +108,18 @@ export const BUILDCHAIN_USAGE = `Usage:
|
|
|
108
108
|
buildchain inspect release --passport <file-or-url> [--json]
|
|
109
109
|
buildchain inspect artifact <subject> [--passport <file-or-url>] [--npm-registry <url>] [--json]
|
|
110
110
|
buildchain doctor [--cwd <dir>] [--require-publish-source-lock] [--json]
|
|
111
|
+
buildchain dev pr-admit --repository <owner/repo> --branch <dev/vN/vN.M>
|
|
112
|
+
--pull-request <n> --expected-head <sha>
|
|
113
|
+
[--execute] [--output <file>] [--json]
|
|
111
114
|
buildchain dev merge-queue --repository <owner/repo> --branch <dev/vN/vN.M>
|
|
112
115
|
[--from-config | --workflow <required-workflow.yml>...] [--cwd <dir>]
|
|
113
116
|
[--check-response-timeout-minutes <n>]
|
|
114
117
|
[--max-entries-to-build <n>] [--apply]
|
|
118
|
+
buildchain dev warrant <submit|select|heartbeat|recover|close|cancel-queued|observe>
|
|
119
|
+
--repository <owner/repo> --branch <dev/vN/vN.M>
|
|
120
|
+
[--execute] [--output <file>] [--json]
|
|
121
|
+
buildchain dev proof <source|verify-source|classify|replay|integration|verify-integration>
|
|
122
|
+
[--output <file>] [--json]
|
|
115
123
|
buildchain log <info|warn|error> --event <name> [--phase <phase>]
|
|
116
124
|
[--component <name>] [--source <name>] [--attribute key=value]...
|
|
117
125
|
[--path <jsonl>] [--json]
|
|
@@ -194,6 +194,15 @@ export async function runBuildchainPatrol(optionsInput = {}, clientInput) {
|
|
|
194
194
|
}
|
|
195
195
|
|
|
196
196
|
result.summary.plannedCount = result.planned.length;
|
|
197
|
+
const admissionActions = result.actions.flatMap((action) => action.result?.actions || []);
|
|
198
|
+
result.runKind = "cadence-patrol";
|
|
199
|
+
result.outcome = result.summary.evaluatedCount === 0
|
|
200
|
+
? "no-op-no-candidates"
|
|
201
|
+
: admissionActions.length === 0
|
|
202
|
+
? "no-op-all-skipped"
|
|
203
|
+
: "actions-present";
|
|
204
|
+
result.noOp = admissionActions.length === 0;
|
|
205
|
+
result.qualification = false;
|
|
197
206
|
result.ok = result.inspections.every((inspection) => inspection.ok !== false);
|
|
198
207
|
return result;
|
|
199
208
|
}
|
|
@@ -74,6 +74,7 @@ const requiredPaths = [
|
|
|
74
74
|
"docs/shifu-gate-profiles.md",
|
|
75
75
|
"docs/auditable-demo.md",
|
|
76
76
|
"contracts/auditable-demo-scenario-v1.schema.json",
|
|
77
|
+
"contracts/release-candidate-recovery-v1.schema.json",
|
|
77
78
|
"contracts/auditable-demo-media-profiles-v1.json",
|
|
78
79
|
"contracts/evidence/auditable-demo-web-delivery-v1.json",
|
|
79
80
|
"contracts/evidence/auditable-demo-responsive-web-delivery-v1.json",
|
|
@@ -117,6 +118,7 @@ const requiredPaths = [
|
|
|
117
118
|
"scripts/publication-commit-evidence.mjs",
|
|
118
119
|
"scripts/publication-reproducibility.mjs",
|
|
119
120
|
"scripts/release-candidate-resolver.mjs",
|
|
121
|
+
"scripts/resume-from-candidate-run.mjs",
|
|
120
122
|
"scripts/buildchain-patrol.mjs",
|
|
121
123
|
"scripts/observed-evidence.mjs",
|
|
122
124
|
"scripts/workflow-friction-report.mjs",
|
|
@@ -134,9 +136,11 @@ const requiredPaths = [
|
|
|
134
136
|
".github/actionlint.yaml",
|
|
135
137
|
".github/workflows/self-hosted-runner-smoke.yml",
|
|
136
138
|
".github/workflows/buildchain-ref-promotion.yml",
|
|
139
|
+
".github/workflows/buildchain-candidate-recovery-dogfood-failure.yml",
|
|
137
140
|
".github/workflows/release-line-bootstrap.yml",
|
|
138
141
|
".github/workflows/release-governance-reconcile.yml",
|
|
139
142
|
".github/workflows/dev-pr-auto-merge.yml",
|
|
143
|
+
".github/workflows/buildchain-dev-delivery.yml",
|
|
140
144
|
".github/workflows/buildchain-patrol.yml",
|
|
141
145
|
".github/workflows/patrol-daily.yml",
|
|
142
146
|
".github/workflows/patrol-weekly.yml",
|
|
@@ -983,10 +987,12 @@ for (const requiredSnippet of [
|
|
|
983
987
|
"github.event.workflow_run.event == 'push'",
|
|
984
988
|
"!startsWith(github.event.workflow_run.display_title, 'chore(release): prepare v')",
|
|
985
989
|
"!startsWith(github.event.workflow_run.display_title, 'chore(release): release v')",
|
|
986
|
-
"
|
|
990
|
+
"resume-candidate-run-id:",
|
|
991
|
+
"resume-expected-source-tree:",
|
|
992
|
+
"resume-buildchain-runtime-sha:",
|
|
987
993
|
"github-release: true",
|
|
988
994
|
"release-passport-buildchain-self-kfd: true",
|
|
989
|
-
"
|
|
995
|
+
"artifact-patterns: ${{ inputs['resume-candidate-run-id'] != '' && 'buildchain-package-*' || '' }}",
|
|
990
996
|
"release-passport-impact-json: .buildchain/release-impact.json",
|
|
991
997
|
]) {
|
|
992
998
|
if (!buildchainRefPromotionWorkflow.includes(requiredSnippet)) {
|
|
@@ -1040,6 +1046,11 @@ for (const requiredSnippet of [
|
|
|
1040
1046
|
"BUILDCHAIN_STABLE_RELEASE_POLICY: .buildchain/stable-release-policy.json",
|
|
1041
1047
|
"Consumer has no binary-distribution.yml; standalone binary dispatch is not applicable.",
|
|
1042
1048
|
"gh workflow view binary-distribution.yml",
|
|
1049
|
+
"resume-candidate-run-id:",
|
|
1050
|
+
"node .buildchain/runtime/scripts/resume-from-candidate-run.mjs",
|
|
1051
|
+
"publish-sealed-bundle-root: ${{ steps.rc.outputs.publish-sealed-bundle-root }}",
|
|
1052
|
+
"BUILDCHAIN_EXPECTED_TRANSACTION_ID: ${{ inputs.resume-transaction-id }}",
|
|
1053
|
+
"if: ${{ inputs.resume-candidate-run-id == '' }}",
|
|
1043
1054
|
]) {
|
|
1044
1055
|
if (!releaseCandidatePromoteWorkflow.includes(requiredSnippet)) {
|
|
1045
1056
|
throw new Error(`release candidate promote workflow missing KFD gate pass-through: ${requiredSnippet}`);
|
|
@@ -5,6 +5,10 @@ import fs from "node:fs";
|
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
import crypto from "node:crypto";
|
|
7
7
|
import { decideChannelCandidate } from "../packages/core/channel-candidate.js";
|
|
8
|
+
import {
|
|
9
|
+
resolveManagedPromotionBaseline,
|
|
10
|
+
resolvePatrolSourceInputs,
|
|
11
|
+
} from "../packages/core/channel-promotion-baseline.js";
|
|
8
12
|
|
|
9
13
|
export const DEV_ALPHA_CANDIDATE_STATE_SCHEMA =
|
|
10
14
|
"kungfu-buildchain-dev-alpha-candidate-state/v1";
|
|
@@ -301,8 +305,7 @@ export function normalizeDevAlphaPatrolOptions(options = {}) {
|
|
|
301
305
|
false,
|
|
302
306
|
),
|
|
303
307
|
mergeMethod: autoMergeMethod(
|
|
304
|
-
options.mergeMethod ??
|
|
305
|
-
process.env.BUILDCHAIN_CHANNEL_PATROL_MERGE_METHOD,
|
|
308
|
+
options.mergeMethod ?? process.env.BUILDCHAIN_CHANNEL_PATROL_MERGE_METHOD,
|
|
306
309
|
),
|
|
307
310
|
dryRun: bool(
|
|
308
311
|
options.dryRun ?? process.env.BUILDCHAIN_CHANNEL_PATROL_DRY_RUN,
|
|
@@ -321,7 +324,10 @@ export function normalizeDevAlphaPatrolOptions(options = {}) {
|
|
|
321
324
|
function latestWorkflowEvidence(runs, workflowPathValue, sourceSha) {
|
|
322
325
|
const matching = runs
|
|
323
326
|
.filter(
|
|
324
|
-
(run) =>
|
|
327
|
+
(run) =>
|
|
328
|
+
run.conclusion !== "cancelled" &&
|
|
329
|
+
run.path === workflowPathValue &&
|
|
330
|
+
run.head_sha === sourceSha,
|
|
325
331
|
)
|
|
326
332
|
.sort((left, right) => Number(right.id) - Number(left.id));
|
|
327
333
|
if (matching.length === 0)
|
|
@@ -356,7 +362,9 @@ function workflowEvidenceIsFreshAndSuccessful(run, { now, maxAgeSeconds }) {
|
|
|
356
362
|
|
|
357
363
|
function latestRunsBySha(runs, workflowPathValue) {
|
|
358
364
|
const latest = new Map();
|
|
359
|
-
for (const run of runs.filter(
|
|
365
|
+
for (const run of runs.filter(
|
|
366
|
+
(row) => row.path === workflowPathValue && row.conclusion !== "cancelled",
|
|
367
|
+
)) {
|
|
360
368
|
const current = latest.get(run.head_sha);
|
|
361
369
|
if (!current || Number(run.id) > Number(current.id))
|
|
362
370
|
latest.set(run.head_sha, run);
|
|
@@ -620,52 +628,26 @@ export async function runDevAlphaCandidatePatrol(
|
|
|
620
628
|
repository: options.repository,
|
|
621
629
|
token: process.env.GITHUB_TOKEN,
|
|
622
630
|
});
|
|
623
|
-
const [observedSourceHeadSha, targetSha] = await Promise.all([
|
|
624
|
-
client.resolveBranch(options.sourceBranch),
|
|
625
|
-
client.resolveBranch(options.targetBranch),
|
|
626
|
-
]);
|
|
627
|
-
const headComparison = await client.compare(targetSha, observedSourceHeadSha);
|
|
628
631
|
const requiredWorkflowPaths = [
|
|
629
632
|
options.devWorkflowPath,
|
|
630
633
|
options.alphaWorkflowPath,
|
|
631
634
|
];
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
const selected = selectLatestQualifiedSource({
|
|
649
|
-
sourceHistory,
|
|
650
|
-
workflowRunsByPath: new Map(
|
|
651
|
-
requiredWorkflowPaths.map((workflow, index) => [
|
|
652
|
-
workflow,
|
|
653
|
-
workflowRunSets[index],
|
|
654
|
-
]),
|
|
655
|
-
),
|
|
656
|
-
requiredWorkflowPaths,
|
|
657
|
-
now: options.now,
|
|
658
|
-
maxAgeSeconds: options.maxAgeSeconds,
|
|
659
|
-
});
|
|
660
|
-
sourceSha = selected.sourceSha;
|
|
661
|
-
skippedNewerCommitCount = selected.skippedNewerCommitCount;
|
|
662
|
-
workflowEvidence = selected.workflowEvidence;
|
|
663
|
-
if (sourceSha !== observedSourceHeadSha)
|
|
664
|
-
comparison = await client.compare(targetSha, sourceSha);
|
|
665
|
-
} catch (error) {
|
|
666
|
-
qualificationError = error;
|
|
667
|
-
}
|
|
668
|
-
}
|
|
635
|
+
const {
|
|
636
|
+
observedSourceHeadSha,
|
|
637
|
+
targetSha,
|
|
638
|
+
headComparison,
|
|
639
|
+
targetBaseline,
|
|
640
|
+
sourceSha,
|
|
641
|
+
comparison,
|
|
642
|
+
workflowEvidence,
|
|
643
|
+
skippedNewerCommitCount,
|
|
644
|
+
qualificationError,
|
|
645
|
+
} = await resolvePatrolSourceInputs({
|
|
646
|
+
client,
|
|
647
|
+
options,
|
|
648
|
+
requiredWorkflowPaths,
|
|
649
|
+
selectQualifiedSource: selectLatestQualifiedSource,
|
|
650
|
+
});
|
|
669
651
|
const decision = qualificationError
|
|
670
652
|
? blockedCandidateDecision({
|
|
671
653
|
options,
|
|
@@ -682,9 +664,12 @@ export async function runDevAlphaCandidatePatrol(
|
|
|
682
664
|
targetSha,
|
|
683
665
|
comparison: { status: comparison.status, aheadBy: comparison.ahead_by },
|
|
684
666
|
selection: {
|
|
685
|
-
mode:
|
|
667
|
+
mode: targetBaseline
|
|
668
|
+
? "latest-qualified-source-after-managed-promotion"
|
|
669
|
+
: "latest-qualified-source-ancestor",
|
|
686
670
|
observedSourceHeadSha,
|
|
687
671
|
skippedNewerCommitCount,
|
|
672
|
+
...(targetBaseline ? { targetBaseline } : {}),
|
|
688
673
|
},
|
|
689
674
|
workflowEvidence,
|
|
690
675
|
requiredWorkflowPaths,
|
|
@@ -927,7 +912,12 @@ export async function runDevAlphaCandidatePatrol(
|
|
|
927
912
|
}
|
|
928
913
|
}
|
|
929
914
|
}
|
|
930
|
-
const autoMerge = await armCandidateAutoMerge(
|
|
915
|
+
const autoMerge = await armCandidateAutoMerge(
|
|
916
|
+
options,
|
|
917
|
+
controllerState,
|
|
918
|
+
pullRequest,
|
|
919
|
+
client,
|
|
920
|
+
);
|
|
931
921
|
return {
|
|
932
922
|
schema: "kungfu-buildchain-dev-alpha-candidate-patrol/v1",
|
|
933
923
|
dryRun: options.dryRun,
|
|
@@ -1015,6 +1005,13 @@ export function createGitHubChannelCandidateClient({
|
|
|
1015
1005
|
async compare(baseSha, headSha) {
|
|
1016
1006
|
return api(`/repos/${owner}/${repo}/compare/${baseSha}...${headSha}`);
|
|
1017
1007
|
},
|
|
1008
|
+
resolveManagedPromotionBaseline: (targetSha, targetBranch) =>
|
|
1009
|
+
resolveManagedPromotionBaseline({
|
|
1010
|
+
api: (requestPath) => api(`/repos/${owner}/${repo}${requestPath}`),
|
|
1011
|
+
targetSha,
|
|
1012
|
+
targetBranch,
|
|
1013
|
+
parseCandidate: managedCandidateFromPullRequest,
|
|
1014
|
+
}),
|
|
1018
1015
|
async listCompletedWorkflowRuns(workflowPathValue, sourceBranch) {
|
|
1019
1016
|
const runs = [];
|
|
1020
1017
|
for (let page = 1; page <= 10; page += 1) {
|