@kungfu-tech/buildchain 3.0.5 → 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/README.md +31 -0
- package/actions/macos-credential-island/README.md +8 -0
- package/bin/buildchain.mjs +1 -1
- package/contracts/auditable-demo-media-profiles-v1.json +61 -0
- package/contracts/auditable-demo-scenario-v1.schema.json +164 -0
- package/contracts/evidence/auditable-demo-responsive-web-delivery-v1.json +3 -3
- package/contracts/evidence/auditable-demo-web-delivery-v1.json +3 -3
- package/dist/site/buildchain-contract.json +107 -26
- package/dist/site/buildchain-site.json +121 -49
- package/dist/site/capability-registry.json +3 -3
- package/dist/site/cli-registry.json +9 -3
- package/dist/site/controller-registry.json +27 -3
- package/dist/site/kfd-claims.json +101 -15
- package/dist/site/kfd-upstream-aggregate.json +1 -1
- package/dist/site/manual-registry.json +10 -9
- package/dist/site/node-api-registry.json +540 -31
- package/dist/site/page-registry.json +105 -29
- package/dist/site/public-surface-audit.json +184 -11
- package/dist/site/publication-authority-registry.json +62 -1
- package/dist/site/publication-registry.json +4 -4
- package/dist/site/site-manifest.json +13 -13
- package/dist/site/workflow-registry.json +118 -4
- package/docs/MAP.md +4 -1
- package/docs/auditable-demo.md +90 -7
- package/docs/aws-us-elastic-runner-burst-plane.md +151 -16
- package/docs/cli-reference.md +35 -2
- package/docs/dev-alpha-candidate-patrol.md +14 -5
- package/docs/dev-qualification-patrol.md +108 -0
- package/docs/node-api-reference.md +123 -72
- package/docs/publish-transaction.md +7 -5
- package/docs/release-flow.md +4 -0
- package/docs/release-governance.md +39 -0
- package/docs/release-propagation.md +93 -0
- package/docs/reusable-build-surface.md +53 -12
- package/docs/stable-candidate-patrol.md +5 -2
- package/package.json +1 -1
- package/packages/core/buildchain-contract.js +36 -1
- package/packages/core/buildchain-publication-authority.js +3 -0
- package/packages/core/public-surface-audit.js +4 -35
- package/packages/core/release-propagation-agent-entry.js +185 -0
- package/packages/core/release-propagation-pickup.js +387 -0
- package/packages/core/release-propagation-push.js +293 -0
- package/packages/core/release-propagation-release.js +61 -18
- package/packages/core/release-propagation-stage-evidence.js +36 -0
- package/packages/core/release-propagation-work.js +8 -4
- package/packages/core/release-propagation.js +27 -2
- package/packages/core/workflow-call-contract.js +308 -0
- package/packages/core/workflow-yaml-contract.js +272 -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 +564 -0
- package/scripts/auditable-demo-renditions.mjs +7 -2
- package/scripts/auditable-demo-transport-smoke.mjs +176 -0
- package/scripts/auditable-demo.mjs +27 -21
- package/scripts/aws-macos-jit-controller-core.mjs +389 -0
- package/scripts/aws-macos-jit-controller-runtime.mjs +82 -0
- package/scripts/aws-macos-jit-controller.mjs +558 -0
- package/scripts/aws-macos-jit-job-controller.mjs +401 -0
- package/scripts/aws-windows-jit-campaign-core.mjs +296 -0
- package/scripts/aws-windows-jit-campaign.mjs +202 -0
- package/scripts/aws-windows-jit-controller-core.mjs +20 -2
- package/scripts/aws-windows-jit-controller.mjs +184 -109
- package/scripts/aws-windows-jit-core.mjs +21 -3
- package/scripts/aws-windows-jit.mjs +2 -0
- package/scripts/build-standalone-binary.mjs +6 -0
- package/scripts/buildchain-cli-help.mjs +2 -1
- package/scripts/capture-package-release-propagation.mjs +24 -1
- package/scripts/check-inventory.mjs +6 -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/finalize-native-artifact-signing-result.mjs +37 -15
- package/scripts/generate-site-bundle.mjs +5 -0
- package/scripts/inspect-artifact-signing-requests.mjs +12 -0
- package/scripts/locked-source-checkout.mjs +17 -3
- package/scripts/release-propagation.mjs +150 -2
- package/scripts/run-lifecycle-core.mjs +25 -0
- package/scripts/site-capability-metadata.mjs +1 -1
- package/scripts/stable-candidate-patrol.mjs +30 -4
- package/scripts/workflow-call-contract.mjs +133 -0
|
@@ -10,9 +10,9 @@ export const WINDOWS_EC2_JIT = Object.freeze({
|
|
|
10
10
|
instanceType: "c7i.4xlarge",
|
|
11
11
|
pricePerHourUsd: 1.45,
|
|
12
12
|
maximumInstanceLifetimeMinutes: 180,
|
|
13
|
-
maxConcurrentInstances:
|
|
14
|
-
maxAcceptedInstances:
|
|
15
|
-
budgetLimitUsd:
|
|
13
|
+
maxConcurrentInstances: 1,
|
|
14
|
+
maxAcceptedInstances: 5,
|
|
15
|
+
budgetLimitUsd: 80,
|
|
16
16
|
minimumSmokeJobs: 1,
|
|
17
17
|
minimumFullJobs: 3,
|
|
18
18
|
maximumCleanupLatencySeconds: 900,
|
|
@@ -75,6 +75,14 @@ export function windowsJitRunnerLabels(value) {
|
|
|
75
75
|
return ["self-hosted", "Windows", "X64", windowsJitRunnerLabel(value)];
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
+
export function windowsJitCampaignId(value) {
|
|
79
|
+
const campaign = String(value || "").trim();
|
|
80
|
+
if (!/^win-[a-z0-9][a-z0-9-]{2,15}$/.test(campaign)) {
|
|
81
|
+
throw new Error("campaignId must be a bounded Windows campaign id");
|
|
82
|
+
}
|
|
83
|
+
return campaign;
|
|
84
|
+
}
|
|
85
|
+
|
|
78
86
|
export function renderWindowsJitBootstrap(template, values = {}) {
|
|
79
87
|
const runnerLabel = windowsJitRunnerLabel(values.runnerLabel);
|
|
80
88
|
const jitParameterName = String(values.jitParameterName || "").trim();
|
|
@@ -86,6 +94,7 @@ export function renderWindowsJitBootstrap(template, values = {}) {
|
|
|
86
94
|
}
|
|
87
95
|
const bounded = {
|
|
88
96
|
REGION: String(values.region || WINDOWS_EC2_JIT.region),
|
|
97
|
+
CAMPAIGN_ID: String(values.campaignId || ""),
|
|
89
98
|
JIT_PARAMETER_NAME: jitParameterName,
|
|
90
99
|
EVIDENCE_BUCKET: String(values.evidenceBucket || ""),
|
|
91
100
|
RUNNER_LABEL: runnerLabel,
|
|
@@ -99,6 +108,7 @@ export function renderWindowsJitBootstrap(template, values = {}) {
|
|
|
99
108
|
};
|
|
100
109
|
const patterns = {
|
|
101
110
|
REGION: /^us-[a-z]+-\d$/,
|
|
111
|
+
CAMPAIGN_ID: /^win-[a-z0-9][a-z0-9-]{2,43}$/,
|
|
102
112
|
EVIDENCE_BUCKET: /^[a-z0-9][a-z0-9.-]{1,61}[a-z0-9]$/,
|
|
103
113
|
GITHUB_RUN_ID: /^\d+$/,
|
|
104
114
|
GITHUB_RUN_ATTEMPT: /^\d+$/,
|
|
@@ -184,6 +194,7 @@ export function windowsEc2JitPlan(overrides = {}) {
|
|
|
184
194
|
|
|
185
195
|
export function createWindowsJitEvidence({
|
|
186
196
|
repository,
|
|
197
|
+
campaignId,
|
|
187
198
|
sourceSha,
|
|
188
199
|
sourceRef,
|
|
189
200
|
githubRunId,
|
|
@@ -235,6 +246,7 @@ export function createWindowsJitEvidence({
|
|
|
235
246
|
phase: WINDOWS_EC2_JIT.phase,
|
|
236
247
|
provider: "aws-ec2",
|
|
237
248
|
repository,
|
|
249
|
+
campaign: { id: windowsJitCampaignId(campaignId) },
|
|
238
250
|
source: {
|
|
239
251
|
sha: exactSha(sourceSha, "sourceSha"),
|
|
240
252
|
ref: String(sourceRef || "").trim(),
|
|
@@ -286,6 +298,7 @@ export function createWindowsJitEvidence({
|
|
|
286
298
|
}
|
|
287
299
|
|
|
288
300
|
export function verifyWindowsEc2JitQualification({
|
|
301
|
+
campaignId,
|
|
289
302
|
jobs = [],
|
|
290
303
|
cancellationCleanup = {},
|
|
291
304
|
timeoutCleanup = {},
|
|
@@ -299,6 +312,7 @@ export function verifyWindowsEc2JitQualification({
|
|
|
299
312
|
} = {}) {
|
|
300
313
|
const plan = windowsEc2JitPlan();
|
|
301
314
|
const issues = [];
|
|
315
|
+
const campaign = windowsJitCampaignId(campaignId);
|
|
302
316
|
const accepted = jobs.filter(
|
|
303
317
|
(job) =>
|
|
304
318
|
job?.trusted === true &&
|
|
@@ -308,6 +322,9 @@ export function verifyWindowsEc2JitQualification({
|
|
|
308
322
|
);
|
|
309
323
|
const smokeJobs = accepted.filter((job) => job.kind === "smoke");
|
|
310
324
|
const fullJobs = accepted.filter((job) => job.kind === "full");
|
|
325
|
+
if (accepted.some((job) => job.campaignId !== campaign)) {
|
|
326
|
+
issues.push("accepted-jobs-not-bound-to-campaign");
|
|
327
|
+
}
|
|
311
328
|
if (smokeJobs.length < plan.config.minimumSmokeJobs) {
|
|
312
329
|
issues.push("runner-profile-smoke-missing");
|
|
313
330
|
}
|
|
@@ -349,6 +366,7 @@ export function verifyWindowsEc2JitQualification({
|
|
|
349
366
|
contract: AWS_WINDOWS_JIT_CONTRACT,
|
|
350
367
|
kind: "phase-verification",
|
|
351
368
|
phase: plan.config.phase,
|
|
369
|
+
campaign: { id: campaign },
|
|
352
370
|
status: issues.length ? "failed" : "passed",
|
|
353
371
|
qualifying: issues.length === 0,
|
|
354
372
|
metrics: {
|
|
@@ -35,6 +35,7 @@ export function main() {
|
|
|
35
35
|
if (mode === "evidence") {
|
|
36
36
|
const result = createWindowsJitEvidence({
|
|
37
37
|
repository: process.env.GITHUB_REPOSITORY,
|
|
38
|
+
campaignId: process.env.AWS_EC2_CAMPAIGN_ID,
|
|
38
39
|
sourceSha:
|
|
39
40
|
process.env.BUILDCHAIN_EXPECTED_SOURCE_SHA || process.env.GITHUB_SHA,
|
|
40
41
|
sourceRef:
|
|
@@ -77,6 +78,7 @@ export function main() {
|
|
|
77
78
|
fs.readFileSync(path.resolve(template), "utf8"),
|
|
78
79
|
{
|
|
79
80
|
region: arg("region", "us-east-1"),
|
|
81
|
+
campaignId: arg("campaign-id"),
|
|
80
82
|
jitParameterName: arg("jit-parameter"),
|
|
81
83
|
evidenceBucket: arg("evidence-bucket"),
|
|
82
84
|
runnerLabel: arg("runner-label"),
|
|
@@ -346,13 +346,19 @@ export function buildStandaloneBinary({
|
|
|
346
346
|
phase: "archive",
|
|
347
347
|
});
|
|
348
348
|
}
|
|
349
|
+
const binarySha256 = crypto.createHash("sha256").update(fs.readFileSync(binaryPath)).digest("hex");
|
|
349
350
|
const manifest = {
|
|
350
351
|
schemaVersion: 1,
|
|
351
352
|
contract: "kungfu-buildchain-standalone-binary",
|
|
352
353
|
name,
|
|
353
354
|
version,
|
|
354
355
|
platform: triple,
|
|
356
|
+
platformId: process.platform === "linux" && process.arch === "x64" ? "linux-x64" : triple,
|
|
355
357
|
binary: relativePath(cwd, binaryPath),
|
|
358
|
+
sha256: binarySha256,
|
|
359
|
+
executableFiles: [{ path: path.basename(binaryPath), sha256: binarySha256 }],
|
|
360
|
+
sourceSha: sourceSha(cwd),
|
|
361
|
+
runtimeDependencies: [],
|
|
356
362
|
archive: relativePath(cwd, archivePath),
|
|
357
363
|
node: process.version,
|
|
358
364
|
observability: {
|
|
@@ -208,7 +208,7 @@ export const BUILDCHAIN_USAGE = `Usage:
|
|
|
208
208
|
[--execute] [--json]
|
|
209
209
|
buildchain paper status [--cwd <dir>] [--json]
|
|
210
210
|
buildchain paper resume [--cwd <dir>] [--buildchain-ref <ref>] [--execute] [--json]
|
|
211
|
-
buildchain release-propagation <plan|write-lock> ...
|
|
211
|
+
buildchain release-propagation <plan|write-lock|work|entry|pickup> ...
|
|
212
212
|
buildchain badges readme [--cwd <dir>] [--readme <path>] [--check] [--write] [--json]
|
|
213
213
|
buildchain badges bundle [--cwd <dir>] [--readme <path>] [--claims <csv>] [--check] [--write] [--json]
|
|
214
214
|
buildchain homebrew update-formula --package <name> --release-passport <file-or-url> [--write] [--json]
|
|
@@ -246,6 +246,7 @@ Examples:
|
|
|
246
246
|
buildchain paper preflight --json
|
|
247
247
|
buildchain paper status --json
|
|
248
248
|
buildchain release-propagation plan --graph graph.json --upstream-release release.json --json
|
|
249
|
+
buildchain release-propagation pickup plan --config manual-upstreams.json --source-id buildchain --channel release --current-version 3.0.3 --json
|
|
249
250
|
buildchain kfd status --json
|
|
250
251
|
buildchain kfd schema list --json
|
|
251
252
|
buildchain kfd 1 witness --json
|
|
@@ -49,7 +49,30 @@ function assertSourcePath(value) {
|
|
|
49
49
|
return sourcePath;
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
-
function
|
|
52
|
+
function hasCommit(sourceSha, cwd) {
|
|
53
|
+
try {
|
|
54
|
+
execFileSync("git", ["cat-file", "-e", `${sourceSha}^{commit}`], {
|
|
55
|
+
cwd,
|
|
56
|
+
stdio: "ignore",
|
|
57
|
+
});
|
|
58
|
+
return true;
|
|
59
|
+
} catch {
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function readConfigAtSource(sourceSha, configPath, cwd) {
|
|
65
|
+
if (!hasCommit(sourceSha, cwd)) {
|
|
66
|
+
try {
|
|
67
|
+
execFileSync("git", ["fetch", "--no-tags", "--depth=1", "origin", sourceSha], {
|
|
68
|
+
cwd,
|
|
69
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
70
|
+
});
|
|
71
|
+
} catch (error) {
|
|
72
|
+
const detail = String(error.stderr || error.message || "unknown git fetch failure").trim();
|
|
73
|
+
throw new Error(`exact release source ${sourceSha} is unavailable from origin: ${detail}`);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
53
76
|
const bytes = execFileSync("git", ["show", `${sourceSha}:${configPath}`], {
|
|
54
77
|
cwd,
|
|
55
78
|
encoding: "utf8",
|
|
@@ -73,6 +73,7 @@ const requiredPaths = [
|
|
|
73
73
|
"docs/github-artifact-attestation.md",
|
|
74
74
|
"docs/shifu-gate-profiles.md",
|
|
75
75
|
"docs/auditable-demo.md",
|
|
76
|
+
"contracts/auditable-demo-scenario-v1.schema.json",
|
|
76
77
|
"contracts/auditable-demo-media-profiles-v1.json",
|
|
77
78
|
"contracts/evidence/auditable-demo-web-delivery-v1.json",
|
|
78
79
|
"contracts/evidence/auditable-demo-responsive-web-delivery-v1.json",
|
|
@@ -103,6 +104,9 @@ const requiredPaths = [
|
|
|
103
104
|
"scripts/seal-macos-credential-input.mjs",
|
|
104
105
|
"scripts/shifu-gate-profile.mjs",
|
|
105
106
|
"scripts/auditable-demo.mjs",
|
|
107
|
+
"scripts/auditable-demo-platform.mjs",
|
|
108
|
+
"scripts/auditable-demo-transport-smoke.mjs",
|
|
109
|
+
"scripts/auditable-demo-capture.py",
|
|
106
110
|
"scripts/resolve-artifact-coordinates.mjs",
|
|
107
111
|
"scripts/artifact-relay-s3.mjs",
|
|
108
112
|
"scripts/anchored-version-material.mjs",
|
|
@@ -154,6 +158,8 @@ const requiredPaths = [
|
|
|
154
158
|
".github/workflows/.build.yml",
|
|
155
159
|
".github/workflows/.gate-profile.yml",
|
|
156
160
|
".github/workflows/.auditable-demo.yml",
|
|
161
|
+
".github/workflows/.declarative-auditable-demo.yml",
|
|
162
|
+
".github/workflows/auditable-demo.yml",
|
|
157
163
|
".github/workflows/build.yml",
|
|
158
164
|
".github/workflows/build-surface-fixture.yml",
|
|
159
165
|
".github/workflows/candidate-lab.yml",
|
|
@@ -7,6 +7,12 @@ import { fileURLToPath } from "node:url";
|
|
|
7
7
|
export const BUILDCHAIN_COMPILER_CACHE_PREPARATION_CONTRACT =
|
|
8
8
|
"kungfu-buildchain-compiler-cache-preparation";
|
|
9
9
|
|
|
10
|
+
const SCCACHE_COMPILER_BINDINGS = Object.freeze({
|
|
11
|
+
RUSTC_WRAPPER: "sccache",
|
|
12
|
+
CMAKE_C_COMPILER_LAUNCHER: "sccache",
|
|
13
|
+
CMAKE_CXX_COMPILER_LAUNCHER: "sccache",
|
|
14
|
+
});
|
|
15
|
+
|
|
10
16
|
const DIGEST_RE = /^sha256:[0-9a-f]{64}$/;
|
|
11
17
|
|
|
12
18
|
function stableValue(value) {
|
|
@@ -76,6 +82,33 @@ function appendGithubEnv(filePath, values) {
|
|
|
76
82
|
);
|
|
77
83
|
}
|
|
78
84
|
|
|
85
|
+
function sumCounters(value) {
|
|
86
|
+
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
87
|
+
if (!value || typeof value !== "object") return 0;
|
|
88
|
+
return Object.values(value).reduce((total, entry) => total + sumCounters(entry), 0);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function readSccacheStats({ cwd, env, runCommand }) {
|
|
92
|
+
const result = runCommand(
|
|
93
|
+
"sccache",
|
|
94
|
+
["--show-stats", "--stats-format", "json"],
|
|
95
|
+
{ cwd, env },
|
|
96
|
+
);
|
|
97
|
+
assertCommandSucceeded(result, "sccache current-run stats probe");
|
|
98
|
+
let payload;
|
|
99
|
+
try {
|
|
100
|
+
payload = JSON.parse(cleanText(result.stdout));
|
|
101
|
+
} catch {
|
|
102
|
+
throw new Error("sccache current-run stats probe returned invalid JSON");
|
|
103
|
+
}
|
|
104
|
+
const stats = payload?.stats || payload;
|
|
105
|
+
return {
|
|
106
|
+
compileRequests: sumCounters(stats?.compile_requests),
|
|
107
|
+
cacheHits: sumCounters(stats?.cache_hits),
|
|
108
|
+
cacheMisses: sumCounters(stats?.cache_misses),
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
79
112
|
function readToolEvidence({ cwd, env, expectedRoot }) {
|
|
80
113
|
const configured = cleanText(
|
|
81
114
|
env.BUILDCHAIN_COMPILER_CACHE_TOOL_EVIDENCE_PATH,
|
|
@@ -178,6 +211,7 @@ export function prepareCompilerCacheEvidence({
|
|
|
178
211
|
action: {
|
|
179
212
|
statsReset: true,
|
|
180
213
|
command: ["sccache", "--zero-stats"],
|
|
214
|
+
compilerBindings: SCCACHE_COMPILER_BINDINGS,
|
|
181
215
|
},
|
|
182
216
|
bindings,
|
|
183
217
|
};
|
|
@@ -191,25 +225,74 @@ export function prepareCompilerCacheEvidence({
|
|
|
191
225
|
BUILDCHAIN_COMPILER_CACHE_ACTIVE_PROVIDER: provider,
|
|
192
226
|
BUILDCHAIN_COMPILER_CACHE_PREPARATION_PATH: outputPath,
|
|
193
227
|
BUILDCHAIN_COMPILER_CACHE_PREPARATION_ROOT: receipt.root,
|
|
228
|
+
...SCCACHE_COMPILER_BINDINGS,
|
|
194
229
|
});
|
|
195
230
|
return receipt;
|
|
196
231
|
}
|
|
197
232
|
|
|
233
|
+
export function verifyCompilerCacheActivity({
|
|
234
|
+
cwd = process.cwd(),
|
|
235
|
+
env = process.env,
|
|
236
|
+
runCommand = defaultRunCommand,
|
|
237
|
+
} = {}) {
|
|
238
|
+
const provider = cleanText(
|
|
239
|
+
env.BUILDCHAIN_COMPILER_CACHE_ACTIVE_PROVIDER ||
|
|
240
|
+
env.BUILDCHAIN_COMPILER_CACHE_PROVIDER ||
|
|
241
|
+
"none",
|
|
242
|
+
).toLowerCase();
|
|
243
|
+
const required = cleanText(env.BUILDCHAIN_COMPILER_CACHE_REQUIRED).toLowerCase() === "true";
|
|
244
|
+
if (provider !== "sccache") {
|
|
245
|
+
if (required) {
|
|
246
|
+
throw new Error(`required compiler cache provider must be sccache, received ${provider || "empty"}`);
|
|
247
|
+
}
|
|
248
|
+
return undefined;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
const stats = readSccacheStats({ cwd, env, runCommand });
|
|
252
|
+
const cacheableRequests = stats.cacheHits + stats.cacheMisses;
|
|
253
|
+
if (required && stats.compileRequests < 1) {
|
|
254
|
+
throw new Error(
|
|
255
|
+
"required sccache observed zero compile requests; compiler launcher bindings were not effective",
|
|
256
|
+
);
|
|
257
|
+
}
|
|
258
|
+
if (required && cacheableRequests < 1) {
|
|
259
|
+
throw new Error(
|
|
260
|
+
"required sccache observed no cacheable compiler requests",
|
|
261
|
+
);
|
|
262
|
+
}
|
|
263
|
+
return {
|
|
264
|
+
...stats,
|
|
265
|
+
cacheableRequests,
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
|
|
198
269
|
function main() {
|
|
199
270
|
const command = process.argv[2] || "";
|
|
200
|
-
if (
|
|
201
|
-
throw new Error("usage: node scripts/compiler-cache-evidence.mjs prepare");
|
|
271
|
+
if (!new Set(["prepare", "verify"]).has(command)) {
|
|
272
|
+
throw new Error("usage: node scripts/compiler-cache-evidence.mjs <prepare|verify>");
|
|
202
273
|
}
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
274
|
+
if (command === "prepare") {
|
|
275
|
+
const receipt = prepareCompilerCacheEvidence();
|
|
276
|
+
if (receipt) {
|
|
277
|
+
console.log(`compiler_cache_provider=${receipt.provider}`);
|
|
278
|
+
console.log(`compiler_cache_preparation_root=${receipt.root}`);
|
|
279
|
+
} else {
|
|
280
|
+
console.log("compiler_cache_provider=none");
|
|
281
|
+
}
|
|
207
282
|
} else {
|
|
208
|
-
|
|
283
|
+
const activity = verifyCompilerCacheActivity();
|
|
284
|
+
if (activity) {
|
|
285
|
+
console.log(`compiler_cache_compile_requests=${activity.compileRequests}`);
|
|
286
|
+
console.log(`compiler_cache_hits=${activity.cacheHits}`);
|
|
287
|
+
console.log(`compiler_cache_misses=${activity.cacheMisses}`);
|
|
288
|
+
} else {
|
|
289
|
+
console.log("compiler_cache_activity=not-applicable");
|
|
290
|
+
}
|
|
209
291
|
}
|
|
210
292
|
}
|
|
211
293
|
|
|
212
294
|
const isMain = process.argv[1] &&
|
|
295
|
+
path.basename(process.argv[1]) === "compiler-cache-evidence.mjs" &&
|
|
213
296
|
path.resolve(process.argv[1]) === fileURLToPath(import.meta.url);
|
|
214
297
|
if (isMain) {
|
|
215
298
|
try {
|
|
@@ -24,6 +24,16 @@ function bool(value, fallback = false) {
|
|
|
24
24
|
return ["1", "true", "yes", "on"].includes(text(value).toLowerCase());
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
+
function autoMergeMethod(value, fallback = "merge") {
|
|
28
|
+
const normalized = text(value || fallback).toLowerCase();
|
|
29
|
+
if (!["merge", "squash", "rebase"].includes(normalized)) {
|
|
30
|
+
throw new Error(
|
|
31
|
+
`mergeMethod must be merge, squash, or rebase, got ${value || "<empty>"}`,
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
return normalized;
|
|
35
|
+
}
|
|
36
|
+
|
|
27
37
|
function repository(value) {
|
|
28
38
|
const normalized = text(value);
|
|
29
39
|
if (!/^[^/\s]+\/[^/\s]+$/.test(normalized))
|
|
@@ -169,6 +179,8 @@ export function managedCandidateFromPullRequest(pullRequest, targetBranch) {
|
|
|
169
179
|
}
|
|
170
180
|
return {
|
|
171
181
|
number: Number(pullRequest.number),
|
|
182
|
+
nodeId: text(pullRequest.node_id),
|
|
183
|
+
autoMerge: pullRequest.auto_merge || null,
|
|
172
184
|
url: text(pullRequest.html_url),
|
|
173
185
|
body,
|
|
174
186
|
sourceSha,
|
|
@@ -191,6 +203,8 @@ export function managedCandidateFromPullRequest(pullRequest, targetBranch) {
|
|
|
191
203
|
);
|
|
192
204
|
return {
|
|
193
205
|
number: Number(pullRequest.number),
|
|
206
|
+
nodeId: text(pullRequest.node_id),
|
|
207
|
+
autoMerge: pullRequest.auto_merge || null,
|
|
194
208
|
url: text(pullRequest.html_url),
|
|
195
209
|
body,
|
|
196
210
|
sourceSha,
|
|
@@ -282,6 +296,14 @@ export function normalizeDevAlphaPatrolOptions(options = {}) {
|
|
|
282
296
|
process.env.BUILDCHAIN_CHANNEL_PATROL_SETTLEMENT_AUTHORIZED,
|
|
283
297
|
createPullRequest,
|
|
284
298
|
),
|
|
299
|
+
autoMerge: bool(
|
|
300
|
+
options.autoMerge ?? process.env.BUILDCHAIN_CHANNEL_PATROL_AUTO_MERGE,
|
|
301
|
+
false,
|
|
302
|
+
),
|
|
303
|
+
mergeMethod: autoMergeMethod(
|
|
304
|
+
options.mergeMethod ??
|
|
305
|
+
process.env.BUILDCHAIN_CHANNEL_PATROL_MERGE_METHOD,
|
|
306
|
+
),
|
|
285
307
|
dryRun: bool(
|
|
286
308
|
options.dryRun ?? process.env.BUILDCHAIN_CHANNEL_PATROL_DRY_RUN,
|
|
287
309
|
true,
|
|
@@ -541,12 +563,50 @@ function pullRequestBody({
|
|
|
541
563
|
`- ${row.workflowName}: [run ${row.runId} attempt ${row.runAttempt}](${row.url})`,
|
|
542
564
|
),
|
|
543
565
|
"",
|
|
544
|
-
"The source-lock branch must continue to point at the exact source SHA. This patrol never merges the PR, publishes a package, creates a tag, or creates a release.",
|
|
566
|
+
"The source-lock branch must continue to point at the exact source SHA. This patrol never directly merges the PR, publishes a package, creates a tag, or creates a release. Repository policy may arm GitHub auto-merge while every protected branch gate remains authoritative.",
|
|
545
567
|
"",
|
|
546
568
|
candidateStateMarker(state),
|
|
547
569
|
].join("\n");
|
|
548
570
|
}
|
|
549
571
|
|
|
572
|
+
async function armCandidateAutoMerge(
|
|
573
|
+
options,
|
|
574
|
+
controllerState,
|
|
575
|
+
pullRequest,
|
|
576
|
+
client,
|
|
577
|
+
) {
|
|
578
|
+
if (
|
|
579
|
+
!options.autoMerge ||
|
|
580
|
+
!options.settlementAuthorized ||
|
|
581
|
+
options.dryRun ||
|
|
582
|
+
controllerState !== "active" ||
|
|
583
|
+
!pullRequest
|
|
584
|
+
) {
|
|
585
|
+
return {
|
|
586
|
+
requested: options.autoMerge,
|
|
587
|
+
enabled: false,
|
|
588
|
+
mergeMethod: options.mergeMethod,
|
|
589
|
+
};
|
|
590
|
+
}
|
|
591
|
+
if (!pullRequest.auto_merge) {
|
|
592
|
+
await client.enableAutoMerge(pullRequest, options.mergeMethod);
|
|
593
|
+
}
|
|
594
|
+
return {
|
|
595
|
+
requested: true,
|
|
596
|
+
enabled: true,
|
|
597
|
+
mergeMethod: options.mergeMethod,
|
|
598
|
+
};
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
function activePullRequest(candidate) {
|
|
602
|
+
return {
|
|
603
|
+
number: candidate.number,
|
|
604
|
+
node_id: candidate.nodeId,
|
|
605
|
+
auto_merge: candidate.autoMerge,
|
|
606
|
+
html_url: candidate.url,
|
|
607
|
+
};
|
|
608
|
+
}
|
|
609
|
+
|
|
550
610
|
export async function runDevAlphaCandidatePatrol(
|
|
551
611
|
optionsInput = {},
|
|
552
612
|
clientInput,
|
|
@@ -793,10 +853,7 @@ export async function runDevAlphaCandidatePatrol(
|
|
|
793
853
|
: "retain-next-candidate"
|
|
794
854
|
: "reconcile-active-candidate";
|
|
795
855
|
}
|
|
796
|
-
pullRequest =
|
|
797
|
-
number: activeCandidate.number,
|
|
798
|
-
html_url: activeCandidate.url,
|
|
799
|
-
};
|
|
856
|
+
pullRequest = activePullRequest(activeCandidate);
|
|
800
857
|
} else if (decision.eligible) {
|
|
801
858
|
await client.ensureImmutableBranch(decision.sourceLockRef, sourceSha);
|
|
802
859
|
state = candidateStateBody({
|
|
@@ -870,11 +927,13 @@ export async function runDevAlphaCandidatePatrol(
|
|
|
870
927
|
}
|
|
871
928
|
}
|
|
872
929
|
}
|
|
930
|
+
const autoMerge = await armCandidateAutoMerge(options, controllerState, pullRequest, client);
|
|
873
931
|
return {
|
|
874
932
|
schema: "kungfu-buildchain-dev-alpha-candidate-patrol/v1",
|
|
875
933
|
dryRun: options.dryRun,
|
|
876
934
|
createPullRequest: options.createPullRequest,
|
|
877
935
|
settlementAuthorized: options.settlementAuthorized,
|
|
936
|
+
autoMerge,
|
|
878
937
|
decision,
|
|
879
938
|
controller: {
|
|
880
939
|
schema: DEV_ALPHA_CANDIDATE_STATE_SCHEMA,
|
|
@@ -1031,6 +1090,22 @@ export function createGitHubChannelCandidateClient({
|
|
|
1031
1090
|
body: { head, base, title, body },
|
|
1032
1091
|
});
|
|
1033
1092
|
},
|
|
1093
|
+
async enableAutoMerge(pullRequest, mergeMethod) {
|
|
1094
|
+
if (!text(pullRequest.node_id)) {
|
|
1095
|
+
throw new Error("candidate pull request has no GraphQL node id");
|
|
1096
|
+
}
|
|
1097
|
+
const query = `mutation($id:ID!,$mergeMethod:PullRequestMergeMethod!){enablePullRequestAutoMerge(input:{pullRequestId:$id,mergeMethod:$mergeMethod}){pullRequest{url}}}`;
|
|
1098
|
+
return api("/graphql", {
|
|
1099
|
+
method: "POST",
|
|
1100
|
+
body: {
|
|
1101
|
+
query,
|
|
1102
|
+
variables: {
|
|
1103
|
+
id: pullRequest.node_id,
|
|
1104
|
+
mergeMethod: autoMergeMethod(mergeMethod).toUpperCase(),
|
|
1105
|
+
},
|
|
1106
|
+
},
|
|
1107
|
+
});
|
|
1108
|
+
},
|
|
1034
1109
|
async updatePullRequestBody(
|
|
1035
1110
|
number,
|
|
1036
1111
|
body,
|
|
@@ -152,11 +152,11 @@ function summarizeChecks({ statuses = [], checkRuns = [] } = {}, requiredChecks
|
|
|
152
152
|
};
|
|
153
153
|
}
|
|
154
154
|
|
|
155
|
-
function mergeableAccepted(pr) {
|
|
155
|
+
function mergeableAccepted(pr, landingMode = "direct") {
|
|
156
156
|
if (pr.mergeable === false) return false;
|
|
157
157
|
const state = String(pr.mergeable_state || pr.mergeStateStatus || "").toLowerCase();
|
|
158
158
|
if (!state) return pr.mergeable === true;
|
|
159
|
-
return ["clean", "has_hooks", "unstable", "unknown"].includes(state);
|
|
159
|
+
return ["clean", "has_hooks", "unstable", "unknown", ...(landingMode === "queue" && pr.mergeable === true ? ["blocked"] : [])].includes(state);
|
|
160
160
|
}
|
|
161
161
|
|
|
162
162
|
function skip(reason, details = {}) {
|
|
@@ -189,7 +189,7 @@ export async function evaluatePullRequest(pr, options, client) {
|
|
|
189
189
|
observedBaseRef: detailed.base.ref,
|
|
190
190
|
});
|
|
191
191
|
}
|
|
192
|
-
if (!mergeableAccepted(detailed)) {
|
|
192
|
+
if (!mergeableAccepted(detailed, options.landingMode)) {
|
|
193
193
|
return skip("not-mergeable", {
|
|
194
194
|
mergeable: detailed.mergeable,
|
|
195
195
|
mergeableState: detailed.mergeable_state || detailed.mergeStateStatus || "",
|
|
@@ -629,7 +629,7 @@ export async function runDevPrAutoMerge(optionsInput = {}, clientInput) {
|
|
|
629
629
|
} else if (observedHeadSha !== expectedHeadSha) {
|
|
630
630
|
admissionDecision = "rejected";
|
|
631
631
|
admissionReason = "head-sha-drift";
|
|
632
|
-
} else if (!mergeableAccepted(observedPullRequest)) {
|
|
632
|
+
} else if (!mergeableAccepted(observedPullRequest, landingMode)) {
|
|
633
633
|
admissionDecision = "rejected";
|
|
634
634
|
admissionReason = "not-mergeable-on-admission-recheck";
|
|
635
635
|
}
|