@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
|
@@ -346,6 +346,7 @@ 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",
|
|
@@ -354,7 +355,8 @@ export function buildStandaloneBinary({
|
|
|
354
355
|
platform: triple,
|
|
355
356
|
platformId: process.platform === "linux" && process.arch === "x64" ? "linux-x64" : triple,
|
|
356
357
|
binary: relativePath(cwd, binaryPath),
|
|
357
|
-
sha256:
|
|
358
|
+
sha256: binarySha256,
|
|
359
|
+
executableFiles: [{ path: path.basename(binaryPath), sha256: binarySha256 }],
|
|
358
360
|
sourceSha: sourceSha(cwd),
|
|
359
361
|
runtimeDependencies: [],
|
|
360
362
|
archive: relativePath(cwd, archivePath),
|
|
@@ -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|work|pickup> ...
|
|
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]
|
|
@@ -105,6 +105,7 @@ const requiredPaths = [
|
|
|
105
105
|
"scripts/shifu-gate-profile.mjs",
|
|
106
106
|
"scripts/auditable-demo.mjs",
|
|
107
107
|
"scripts/auditable-demo-platform.mjs",
|
|
108
|
+
"scripts/auditable-demo-transport-smoke.mjs",
|
|
108
109
|
"scripts/auditable-demo-capture.py",
|
|
109
110
|
"scripts/resolve-artifact-coordinates.mjs",
|
|
110
111
|
"scripts/artifact-relay-s3.mjs",
|
|
@@ -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
|
}
|