@kungfu-tech/buildchain 3.0.6-alpha.0 → 3.0.6-alpha.2
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/dist/site/buildchain-contract.json +47 -27
- package/dist/site/buildchain-site.json +165 -44
- package/dist/site/capability-registry.json +3 -3
- package/dist/site/cli-registry.json +40 -4
- package/dist/site/controller-registry.json +20 -4
- package/dist/site/kfd-claims.json +140 -19
- package/dist/site/kfd-upstream-aggregate.json +9 -9
- package/dist/site/manual-registry.json +9 -9
- package/dist/site/node-api-registry.json +1161 -180
- package/dist/site/page-registry.json +152 -31
- package/dist/site/public-surface-audit.json +386 -19
- package/dist/site/publication-authority-registry.json +61 -1
- 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 +151 -13
- package/docs/MAP.md +2 -0
- package/docs/auditable-demo.md +58 -11
- package/docs/aws-us-elastic-runner-burst-plane.md +114 -80
- 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 +54 -15
- package/docs/publication-authority.md +11 -0
- package/docs/release-candidate.md +19 -2
- package/docs/release-governance.md +60 -1
- 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 +4 -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/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/aws-windows-jit-campaign-core.mjs +7 -8
- package/scripts/aws-windows-jit-controller.mjs +1 -0
- package/scripts/aws-windows-jit-core.mjs +1 -1
- 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 +1 -0
- 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 +497 -55
- package/scripts/dev-pr-delivery-warrant.mjs +209 -0
- package/scripts/dispatch-artifact-signing-authority.mjs +2 -4
- 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/resolve-artifact-transfer-mode.mjs +9 -0
- package/scripts/resolve-build-contract.mjs +7 -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 +14 -0
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import crypto from "node:crypto";
|
|
4
|
+
import fs from "node:fs";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
import { fileURLToPath } from "node:url";
|
|
7
|
+
|
|
8
|
+
const ROOT_PATTERN = /^sha256:[0-9a-f]{64}$/u;
|
|
9
|
+
const SHA_PATTERN = /^[0-9a-f]{40}$/u;
|
|
10
|
+
|
|
11
|
+
function canonical(value) {
|
|
12
|
+
if (Array.isArray(value)) return value.map(canonical);
|
|
13
|
+
if (value && typeof value === "object") {
|
|
14
|
+
return Object.fromEntries(
|
|
15
|
+
Object.entries(value)
|
|
16
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
17
|
+
.map(([key, item]) => [key, canonical(item)]),
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
return value;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function digest(value) {
|
|
24
|
+
return `sha256:${crypto
|
|
25
|
+
.createHash("sha256")
|
|
26
|
+
.update(JSON.stringify(canonical(value)))
|
|
27
|
+
.digest("hex")}`;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function withoutRoot(value, field = "evidenceRoot") {
|
|
31
|
+
const body = structuredClone(value);
|
|
32
|
+
Reflect.deleteProperty(body, field);
|
|
33
|
+
return body;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function requireIso(value, label) {
|
|
37
|
+
if (typeof value !== "string" || !Number.isFinite(Date.parse(value))) {
|
|
38
|
+
throw new Error(`${label} must be an ISO timestamp`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function verifyEvidenceHeader(
|
|
43
|
+
evidence,
|
|
44
|
+
{ lifecycleStage, sourceSha, sourceTree, platformId },
|
|
45
|
+
) {
|
|
46
|
+
if (evidence?.schema !== "kungfu.lifecycle-substage-evidence/v1") {
|
|
47
|
+
throw new Error(
|
|
48
|
+
`unsupported lifecycle substage evidence schema: ${evidence?.schema || "missing"}`,
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
if (!ROOT_PATTERN.test(evidence.evidenceRoot || "")) {
|
|
52
|
+
throw new Error("lifecycle substage evidence root is invalid");
|
|
53
|
+
}
|
|
54
|
+
if (evidence.evidenceRoot !== digest(withoutRoot(evidence))) {
|
|
55
|
+
throw new Error("lifecycle substage evidence root mismatch");
|
|
56
|
+
}
|
|
57
|
+
if (lifecycleStage && evidence.lifecycleStage !== lifecycleStage) {
|
|
58
|
+
throw new Error(
|
|
59
|
+
`lifecycle stage mismatch: expected ${lifecycleStage}, got ${evidence.lifecycleStage}`,
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
if (
|
|
63
|
+
!SHA_PATTERN.test(evidence.source?.sha || "") ||
|
|
64
|
+
!SHA_PATTERN.test(evidence.source?.tree || "")
|
|
65
|
+
) {
|
|
66
|
+
throw new Error("lifecycle substage source identity is invalid");
|
|
67
|
+
}
|
|
68
|
+
if (sourceSha && evidence.source.sha !== sourceSha) {
|
|
69
|
+
throw new Error(
|
|
70
|
+
`lifecycle substage source SHA mismatch: expected ${sourceSha}, got ${evidence.source.sha}`,
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
if (sourceTree && evidence.source.tree !== sourceTree) {
|
|
74
|
+
throw new Error(
|
|
75
|
+
`lifecycle substage source tree mismatch: expected ${sourceTree}, got ${evidence.source.tree}`,
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
if (platformId && evidence.platform?.id !== platformId) {
|
|
79
|
+
throw new Error(
|
|
80
|
+
`lifecycle substage platform mismatch: expected ${platformId}, got ${evidence.platform?.id}`,
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
if (!["passed", "failed"].includes(evidence.conclusion)) {
|
|
84
|
+
throw new Error("lifecycle substage conclusion must be passed or failed");
|
|
85
|
+
}
|
|
86
|
+
requireIso(evidence.startedAt, "lifecycle substage startedAt");
|
|
87
|
+
requireIso(evidence.completedAt, "lifecycle substage completedAt");
|
|
88
|
+
if (!Array.isArray(evidence.substages) || evidence.substages.length === 0) {
|
|
89
|
+
throw new Error("lifecycle substage evidence has no substages");
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function verifySubstage(substage, index, names) {
|
|
94
|
+
if (typeof substage.stage !== "string" || !substage.stage) {
|
|
95
|
+
throw new Error(`substage ${index} has no name`);
|
|
96
|
+
}
|
|
97
|
+
if (names.has(substage.stage)) {
|
|
98
|
+
throw new Error(`duplicate lifecycle substage: ${substage.stage}`);
|
|
99
|
+
}
|
|
100
|
+
names.add(substage.stage);
|
|
101
|
+
requireIso(substage.startedAt, `${substage.stage}.startedAt`);
|
|
102
|
+
requireIso(substage.completedAt, `${substage.stage}.completedAt`);
|
|
103
|
+
if (
|
|
104
|
+
!Number.isFinite(substage.durationSeconds) ||
|
|
105
|
+
substage.durationSeconds < 0
|
|
106
|
+
) {
|
|
107
|
+
throw new Error(`${substage.stage}.durationSeconds is invalid`);
|
|
108
|
+
}
|
|
109
|
+
if (
|
|
110
|
+
!Number.isInteger(substage.status) ||
|
|
111
|
+
!["passed", "failed"].includes(substage.conclusion)
|
|
112
|
+
) {
|
|
113
|
+
throw new Error(`${substage.stage} result is invalid`);
|
|
114
|
+
}
|
|
115
|
+
if ((substage.status === 0) !== (substage.conclusion === "passed")) {
|
|
116
|
+
throw new Error(`${substage.stage} status and conclusion disagree`);
|
|
117
|
+
}
|
|
118
|
+
if (
|
|
119
|
+
!["platform-native", "exact-source-reuse"].includes(substage.executionMode)
|
|
120
|
+
) {
|
|
121
|
+
throw new Error(`${substage.stage}.executionMode is invalid`);
|
|
122
|
+
}
|
|
123
|
+
if (
|
|
124
|
+
!ROOT_PATTERN.test(substage.evidenceRoot || "") ||
|
|
125
|
+
substage.evidenceRoot !== digest(withoutRoot(substage))
|
|
126
|
+
) {
|
|
127
|
+
throw new Error(`${substage.stage} evidence root mismatch`);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function verifyAggregate(evidence) {
|
|
132
|
+
const failed = evidence.substages.some((substage) => substage.status !== 0);
|
|
133
|
+
const expectedFailureReason = failed
|
|
134
|
+
? "substage-failed"
|
|
135
|
+
: evidence.conclusion === "failed"
|
|
136
|
+
? "budget-exceeded"
|
|
137
|
+
: undefined;
|
|
138
|
+
if (
|
|
139
|
+
(failed && evidence.conclusion !== "failed") ||
|
|
140
|
+
(!failed && evidence.conclusion === "passed" && evidence.failureReason) ||
|
|
141
|
+
evidence.failureReason !== expectedFailureReason
|
|
142
|
+
) {
|
|
143
|
+
throw new Error("lifecycle substage aggregate conclusion is inconsistent");
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export function verifyLifecycleSubstageEvidence(
|
|
148
|
+
value,
|
|
149
|
+
{
|
|
150
|
+
lifecycleStage = "",
|
|
151
|
+
sourceSha = "",
|
|
152
|
+
sourceTree = "",
|
|
153
|
+
platformId = "",
|
|
154
|
+
} = {},
|
|
155
|
+
) {
|
|
156
|
+
const evidence = value?.substageEvidence || value;
|
|
157
|
+
verifyEvidenceHeader(evidence, {
|
|
158
|
+
lifecycleStage,
|
|
159
|
+
sourceSha,
|
|
160
|
+
sourceTree,
|
|
161
|
+
platformId,
|
|
162
|
+
});
|
|
163
|
+
const names = new Set();
|
|
164
|
+
for (const [index, substage] of evidence.substages.entries()) {
|
|
165
|
+
verifySubstage(substage, index, names);
|
|
166
|
+
}
|
|
167
|
+
verifyAggregate(evidence);
|
|
168
|
+
return structuredClone(evidence);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
export function lifecycleSubstageEvidenceContext({
|
|
172
|
+
substageEvidencePath = "",
|
|
173
|
+
cwd,
|
|
174
|
+
workspace,
|
|
175
|
+
diagnosticsDir,
|
|
176
|
+
lifecycleStage,
|
|
177
|
+
sourceSha = process.env.BUILDCHAIN_SOURCE_SHA || "",
|
|
178
|
+
sourceTree = process.env.BUILDCHAIN_SOURCE_TREE_SHA || "",
|
|
179
|
+
platformId,
|
|
180
|
+
}) {
|
|
181
|
+
if (!substageEvidencePath) {
|
|
182
|
+
return {
|
|
183
|
+
evidence: undefined,
|
|
184
|
+
observability: {},
|
|
185
|
+
lifecycle: {},
|
|
186
|
+
links: {},
|
|
187
|
+
sourcePath: "",
|
|
188
|
+
targetPath: "",
|
|
189
|
+
sidecar: {},
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
const sourcePath = path.resolve(cwd, substageEvidencePath);
|
|
193
|
+
const targetPath = path.join(diagnosticsDir, "verify-substages.json");
|
|
194
|
+
const relativePath = path
|
|
195
|
+
.relative(workspace, targetPath)
|
|
196
|
+
.split(path.sep)
|
|
197
|
+
.join("/");
|
|
198
|
+
const evidence = readLifecycleSubstageEvidence(sourcePath, {
|
|
199
|
+
lifecycleStage,
|
|
200
|
+
sourceSha,
|
|
201
|
+
sourceTree,
|
|
202
|
+
platformId,
|
|
203
|
+
});
|
|
204
|
+
return {
|
|
205
|
+
evidence,
|
|
206
|
+
observability: {
|
|
207
|
+
substages: {
|
|
208
|
+
contract: evidence.schema,
|
|
209
|
+
evidenceRoot: evidence.evidenceRoot,
|
|
210
|
+
conclusion: evidence.conclusion,
|
|
211
|
+
path: relativePath,
|
|
212
|
+
},
|
|
213
|
+
},
|
|
214
|
+
lifecycle: { substageEvidence: evidence },
|
|
215
|
+
links: { lifecycleSubstages: relativePath },
|
|
216
|
+
sourcePath,
|
|
217
|
+
targetPath,
|
|
218
|
+
sidecar: {
|
|
219
|
+
kind: "lifecycle-substages",
|
|
220
|
+
filePath: targetPath,
|
|
221
|
+
required: true,
|
|
222
|
+
},
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
export function readLifecycleSubstageEvidence(file, options = {}) {
|
|
227
|
+
if (!file) return undefined;
|
|
228
|
+
const absolute = path.resolve(file);
|
|
229
|
+
if (!fs.existsSync(absolute))
|
|
230
|
+
throw new Error(`lifecycle substage evidence file not found: ${file}`);
|
|
231
|
+
return verifyLifecycleSubstageEvidence(
|
|
232
|
+
JSON.parse(fs.readFileSync(absolute, "utf8")),
|
|
233
|
+
options,
|
|
234
|
+
);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function parse(argv) {
|
|
238
|
+
const options = {};
|
|
239
|
+
for (let index = 0; index < argv.length; index += 2) {
|
|
240
|
+
const flag = argv[index];
|
|
241
|
+
if (!flag?.startsWith("--") || index + 1 >= argv.length)
|
|
242
|
+
throw new Error(`invalid option: ${flag || "missing"}`);
|
|
243
|
+
options[flag.slice(2)] = argv[index + 1];
|
|
244
|
+
}
|
|
245
|
+
return options;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function main(argv = process.argv.slice(2)) {
|
|
249
|
+
const options = parse(argv);
|
|
250
|
+
const evidence = readLifecycleSubstageEvidence(options.file, {
|
|
251
|
+
lifecycleStage: options.stage || "",
|
|
252
|
+
sourceSha: options["source-sha"] || "",
|
|
253
|
+
sourceTree: options["source-tree"] || "",
|
|
254
|
+
platformId: options["platform-id"] || "",
|
|
255
|
+
});
|
|
256
|
+
process.stdout.write(
|
|
257
|
+
`${JSON.stringify({ ok: true, evidenceRoot: evidence.evidenceRoot, conclusion: evidence.conclusion })}\n`,
|
|
258
|
+
);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
if (
|
|
262
|
+
process.argv[1] &&
|
|
263
|
+
path.basename(process.argv[1]) === "lifecycle-substage-evidence.mjs" &&
|
|
264
|
+
fileURLToPath(import.meta.url) === path.resolve(process.argv[1])
|
|
265
|
+
) {
|
|
266
|
+
try {
|
|
267
|
+
main();
|
|
268
|
+
} catch (error) {
|
|
269
|
+
console.error(
|
|
270
|
+
`[lifecycle-substages] ${error instanceof Error ? error.message : String(error)}`,
|
|
271
|
+
);
|
|
272
|
+
process.exit(1);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
@@ -4,6 +4,7 @@ import crypto from "node:crypto";
|
|
|
4
4
|
import fs from "node:fs";
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
import { pathToFileURL } from "node:url";
|
|
7
|
+
import { runGitFetchSync } from "./git-fetch-process-tree.mjs";
|
|
7
8
|
|
|
8
9
|
export const LOCKED_SOURCE_CHECKOUT_CONTRACT = "kungfu-buildchain-locked-source-checkout-cache";
|
|
9
10
|
export const ISOLATED_GIT_GLOBAL_CONFIG = process.platform === "win32" ? "NUL" : "/dev/null";
|
|
@@ -111,9 +112,10 @@ function ensureCheckoutTarget(targetPath, workspace) {
|
|
|
111
112
|
|
|
112
113
|
function git(args, { cwd, env = {}, timeoutMs = 60000, stdio = ["ignore", "pipe", "pipe"] } = {}) {
|
|
113
114
|
try {
|
|
114
|
-
const
|
|
115
|
+
const commandEnv = { ...process.env, ...env };
|
|
116
|
+
const output = args[0] === "fetch" ? runGitFetchSync({ args, cwd, env: commandEnv, timeoutMs, stdio }) : execFileSync("git", args, {
|
|
115
117
|
cwd,
|
|
116
|
-
env:
|
|
118
|
+
env: commandEnv,
|
|
117
119
|
encoding: "utf8",
|
|
118
120
|
stdio,
|
|
119
121
|
timeout: timeoutMs,
|
|
@@ -233,7 +235,8 @@ function checkoutFetchedCommit(targetPath, sha, timeoutMs) {
|
|
|
233
235
|
function retryableGitFetchError(error) {
|
|
234
236
|
const code = String(error?.code || "").toUpperCase();
|
|
235
237
|
if (["ETIMEDOUT", "ECONNRESET", "ECONNREFUSED", "EAI_AGAIN", "ENETUNREACH", "EPIPE"].includes(code)) return true;
|
|
236
|
-
|
|
238
|
+
const commandOutput = [error?.stderr, error?.stdout].filter(Boolean).map(String).join("\n").trim();
|
|
239
|
+
return /timed?\s*out|timeout|connection (?:reset|refused)|remote end hung up|early eof|rpc failed|http (?:429|5\d\d)|temporary failure|network is unreachable/i.test(commandOutput || String(error?.message || error || ""));
|
|
237
240
|
}
|
|
238
241
|
|
|
239
242
|
export function fetchSourceCommit({
|
|
@@ -26,6 +26,15 @@ function resolveArtifactTransferMode(env = process.env) {
|
|
|
26
26
|
oidcAudience: "",
|
|
27
27
|
};
|
|
28
28
|
}
|
|
29
|
+
if (env.INPUT_RELAY_REQUIRED === "false") {
|
|
30
|
+
return {
|
|
31
|
+
mode: "github-artifacts",
|
|
32
|
+
s3Bucket: "",
|
|
33
|
+
s3Region: "",
|
|
34
|
+
s3Prefix: "",
|
|
35
|
+
oidcAudience: "",
|
|
36
|
+
};
|
|
37
|
+
}
|
|
29
38
|
const s3Bucket = firstValue(env.INPUT_S3_BUCKET, env.VAR_S3_BUCKET);
|
|
30
39
|
const s3Region = firstValue(env.INPUT_S3_REGION, env.VAR_S3_REGION);
|
|
31
40
|
const s3Prefix = firstValue(
|
|
@@ -41,6 +41,13 @@ export function resolveBuildContractCli() {
|
|
|
41
41
|
"native-platform-count": String(resolved.nativePlatformCount),
|
|
42
42
|
"container-platforms-json": resolved.containerPlatformsJson,
|
|
43
43
|
"container-platform-count": String(resolved.containerPlatformCount),
|
|
44
|
+
"github-hosted-platforms-json": resolved.githubHostedPlatformsJson,
|
|
45
|
+
"github-hosted-platform-ids-json": resolved.githubHostedPlatformIdsJson,
|
|
46
|
+
"github-hosted-platform-count": String(
|
|
47
|
+
resolved.githubHostedPlatformCount,
|
|
48
|
+
),
|
|
49
|
+
"relay-platforms-json": resolved.relayPlatformsJson,
|
|
50
|
+
"relay-platform-count": String(resolved.relayPlatformCount),
|
|
44
51
|
"linux-container-enabled": String(resolved.linuxContainer.enabled),
|
|
45
52
|
"linux-container-preset": resolved.linuxContainer.preset,
|
|
46
53
|
"linux-container-image": resolved.linuxContainer.image,
|
|
@@ -29,6 +29,7 @@ import {
|
|
|
29
29
|
validateExpectedArtifacts,
|
|
30
30
|
} from "./build-contract-core.mjs";
|
|
31
31
|
import { verifyCompilerCacheActivity } from "./compiler-cache-evidence.mjs";
|
|
32
|
+
import { lifecycleSubstageEvidenceContext } from "./lifecycle-substage-evidence.mjs";
|
|
32
33
|
|
|
33
34
|
const moduleDir = path.dirname(fileURLToPath(import.meta.url));
|
|
34
35
|
const buildchainCliCandidates = [
|
|
@@ -432,6 +433,7 @@ export function runLifecycle({
|
|
|
432
433
|
processSampleIntervalMs = 15000,
|
|
433
434
|
requestedParallelism = 0,
|
|
434
435
|
processSummaryRequired = true,
|
|
436
|
+
substageEvidencePath = "",
|
|
435
437
|
} = {}) {
|
|
436
438
|
if (timeoutMinutes !== undefined && (!Number.isFinite(timeoutMinutes) || timeoutMinutes <= 0)) {
|
|
437
439
|
throw new Error("lifecycle timeoutMinutes must be a positive number");
|
|
@@ -652,7 +654,6 @@ export function runLifecycle({
|
|
|
652
654
|
throw lifecycleError;
|
|
653
655
|
}
|
|
654
656
|
}
|
|
655
|
-
|
|
656
657
|
if (required && !executed) {
|
|
657
658
|
frameworkLog.error("lifecycle.required-missing", {
|
|
658
659
|
attributes: {
|
|
@@ -663,13 +664,8 @@ export function runLifecycle({
|
|
|
663
664
|
throw new Error(`required lifecycle stage did not run: ${stageName || "command"}`);
|
|
664
665
|
}
|
|
665
666
|
|
|
666
|
-
const compilerCacheActivity = verifyBuildLifecycleCompilerCacheActivity({
|
|
667
|
-
|
|
668
|
-
executed,
|
|
669
|
-
cwd: resolvedCwd,
|
|
670
|
-
frameworkLog,
|
|
671
|
-
});
|
|
672
|
-
|
|
667
|
+
const compilerCacheActivity = verifyBuildLifecycleCompilerCacheActivity({ stageName, executed, cwd: resolvedCwd, frameworkLog });
|
|
668
|
+
const substages = lifecycleSubstageEvidenceContext({ substageEvidencePath, cwd: resolvedCwd, workspace: resolvedWorkspace, diagnosticsDir, lifecycleStage: stageName, platformId });
|
|
673
669
|
const shouldReadProcessSummary = Boolean(
|
|
674
670
|
resolvedProcessSummaryPath
|
|
675
671
|
&& (fs.existsSync(resolvedProcessSummaryPath) || processSummaryRequired),
|
|
@@ -763,7 +759,7 @@ export function runLifecycle({
|
|
|
763
759
|
fileCount: summary.fileCount,
|
|
764
760
|
});
|
|
765
761
|
observability.lifecycle = lifecycleObservability;
|
|
766
|
-
Object.assign(observability, { compilerCacheActivity });
|
|
762
|
+
Object.assign(observability, { compilerCacheActivity }, substages.observability);
|
|
767
763
|
observability.diagnostics = {
|
|
768
764
|
contract: BUILDCHAIN_DIAGNOSTICS_CONTRACT,
|
|
769
765
|
path: relativeDiagnosticsPath,
|
|
@@ -796,6 +792,7 @@ export function runLifecycle({
|
|
|
796
792
|
stage: stageName,
|
|
797
793
|
commandSource,
|
|
798
794
|
executed,
|
|
795
|
+
...substages.lifecycle,
|
|
799
796
|
},
|
|
800
797
|
observability,
|
|
801
798
|
summary: summaryWithObservability,
|
|
@@ -829,6 +826,7 @@ export function runLifecycle({
|
|
|
829
826
|
...(relativeProcessSummaryPath ? { processSummary: relativeProcessSummaryPath } : {}),
|
|
830
827
|
...(processSummaryArtifact ? { diagnosticsProcessSummary: relativeDiagnosticsProcessSummaryPath } : {}),
|
|
831
828
|
...(processSummaryArtifact?.samplesPath ? { diagnosticsProcessSamples: relativeDiagnosticsProcessSamplesPath } : {}),
|
|
829
|
+
...substages.links,
|
|
832
830
|
...(sourceCheckoutArtifact ? { sourceCheckout: relativeDiagnosticsSourceCheckoutPath } : {}),
|
|
833
831
|
...(compilerCachePreparationArtifact
|
|
834
832
|
? { compilerCachePreparation: relativeDiagnosticsCompilerCachePreparationPath }
|
|
@@ -851,6 +849,7 @@ export function runLifecycle({
|
|
|
851
849
|
});
|
|
852
850
|
copyIfExists(resolvedSamplesPath, resolvedDiagnosticsProcessSamplesPath);
|
|
853
851
|
}
|
|
852
|
+
copyIfExists(substages.sourcePath, substages.targetPath);
|
|
854
853
|
if (sourceCheckoutArtifact) {
|
|
855
854
|
copyIfExists(resolvedSourceCheckoutPath, resolvedDiagnosticsSourceCheckoutPath);
|
|
856
855
|
}
|
|
@@ -870,6 +869,7 @@ export function runLifecycle({
|
|
|
870
869
|
{ kind: "events", filePath: resolvedDiagnosticsEventsPath, required: true },
|
|
871
870
|
{ kind: "process-summary", filePath: resolvedDiagnosticsProcessSummaryPath },
|
|
872
871
|
{ kind: "process-samples", filePath: resolvedDiagnosticsProcessSamplesPath },
|
|
872
|
+
substages.sidecar,
|
|
873
873
|
{ kind: "source-checkout", filePath: resolvedDiagnosticsSourceCheckoutPath },
|
|
874
874
|
{
|
|
875
875
|
kind: "compiler-cache-preparation",
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
import {
|
|
11
11
|
createGateAggregate,
|
|
12
12
|
createGateExecutionMatrix,
|
|
13
|
+
normalizeGateEnvironment,
|
|
13
14
|
normalizeGatePlatform,
|
|
14
15
|
} from "./gate-profile-core.mjs";
|
|
15
16
|
|
|
@@ -54,27 +55,20 @@ function commandForPlatform(commandJson, platform) {
|
|
|
54
55
|
return argv;
|
|
55
56
|
}
|
|
56
57
|
|
|
57
|
-
function gateEnvironment() {
|
|
58
|
+
export function gateEnvironment(platformEnvironment = {}) {
|
|
58
59
|
const parsed = parseJson(
|
|
59
60
|
process.env.BUILDCHAIN_GATE_ENVIRONMENT_JSON || "{}",
|
|
60
61
|
"gate-environment-json",
|
|
61
62
|
);
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
throw new Error(`invalid Gate environment name: ${name}`);
|
|
68
|
-
if (!["string", "number", "boolean"].includes(typeof value)) {
|
|
69
|
-
throw new Error(
|
|
70
|
-
`Gate environment ${name} must be a string, number, or boolean`,
|
|
71
|
-
);
|
|
72
|
-
}
|
|
73
|
-
return [name, String(value)];
|
|
74
|
-
});
|
|
63
|
+
const shared = normalizeGateEnvironment(parsed, "gate-environment-json");
|
|
64
|
+
const platform = normalizeGateEnvironment(
|
|
65
|
+
platformEnvironment,
|
|
66
|
+
"gate matrix entry environment",
|
|
67
|
+
);
|
|
75
68
|
return {
|
|
76
69
|
...process.env,
|
|
77
|
-
...
|
|
70
|
+
...shared,
|
|
71
|
+
...platform,
|
|
78
72
|
...(process.env.BUILDCHAIN_SHIFU_CACHE_PROFILE_REF
|
|
79
73
|
? {
|
|
80
74
|
SHIFU_CACHE_PROFILE_REF:
|
|
@@ -237,7 +231,7 @@ function runMode() {
|
|
|
237
231
|
const receiptPath = path.join(outputRoot, "receipt.json");
|
|
238
232
|
const validationPath = path.join(outputRoot, "validation.json");
|
|
239
233
|
const executionPath = path.join(outputRoot, "execution.json");
|
|
240
|
-
const env = gateEnvironment();
|
|
234
|
+
const env = gateEnvironment(entry.environment || {});
|
|
241
235
|
fs.mkdirSync(outputRoot, { recursive: true });
|
|
242
236
|
prepareGateExecutionFiles([receiptPath, validationPath, executionPath]);
|
|
243
237
|
const argv = commandForPlatform(commandJson, entry.platform);
|
|
@@ -87,7 +87,20 @@ export function cliCommandMeta(id) {
|
|
|
87
87
|
"kfd-upstream-roles": { group: "kfd-trust", purpose: "List Buildchain-managed KFD upstream role values and inference policy." },
|
|
88
88
|
lifecycle: { group: "reusable-build", purpose: "Run configured lifecycle commands and write deterministic artifact manifests." },
|
|
89
89
|
dev: { group: "governance-versioning", purpose: "Inspect protected development-channel governance command families." },
|
|
90
|
+
"dev-proof-classify": { group: "governance-versioning", purpose: "Classify source-qualified candidate changes against a current integration base." },
|
|
91
|
+
"dev-proof-integration": { group: "governance-versioning", purpose: "Seal final exact integration delivery proof for a warranted candidate." },
|
|
92
|
+
"dev-proof-replay": { group: "governance-versioning", purpose: "Replay source qualification against a Project Cut without rewriting candidate source." },
|
|
93
|
+
"dev-proof-source": { group: "governance-versioning", purpose: "Seal source qualification proof for an exact candidate head and source tree." },
|
|
94
|
+
"dev-proof-verify-integration": { group: "governance-versioning", purpose: "Verify exact integration delivery proof and its source qualification binding." },
|
|
95
|
+
"dev-proof-verify-source": { group: "governance-versioning", purpose: "Verify source qualification proof against exact candidate roots." },
|
|
90
96
|
"dev-merge-queue": { group: "governance-versioning", purpose: "Plan or apply an exact-branch GitHub merge queue after required workflow event compatibility is verified." },
|
|
97
|
+
"dev-warrant-close": { group: "governance-versioning", purpose: "Close the active fenced Dev Delivery Warrant with terminal proof." },
|
|
98
|
+
"dev-warrant-cancel-queued": { group: "governance-versioning", purpose: "Cancel one exact non-active queued candidate from immutable terminal event evidence." },
|
|
99
|
+
"dev-warrant-heartbeat": { group: "governance-versioning", purpose: "Renew the active fenced Dev Delivery Warrant without changing queue order." },
|
|
100
|
+
"dev-warrant-observe": { group: "governance-versioning", purpose: "Read the durable Dev Delivery Warrant queue without mutating authority state." },
|
|
101
|
+
"dev-warrant-recover": { group: "governance-versioning", purpose: "Recover an expired fenced Warrant and retain candidate age." },
|
|
102
|
+
"dev-warrant-select": { group: "governance-versioning", purpose: "Select the next fair candidate and issue one fenced delivery Warrant." },
|
|
103
|
+
"dev-warrant-submit": { group: "governance-versioning", purpose: "Submit or safely refresh an exact candidate in the durable delivery queue." },
|
|
91
104
|
log: { group: "observability-diagnostics", purpose: "Inspect Buildchain logging command families." },
|
|
92
105
|
logging: { group: "observability-diagnostics", purpose: "Emit timestamped build events, summarize logs, and enforce required phases." },
|
|
93
106
|
mark: { group: "observability-diagnostics", purpose: "Emit a single Buildchain log event." },
|
|
@@ -155,6 +168,7 @@ export function nodeApiMeta(exportName) {
|
|
|
155
168
|
"./build-facts": { group: "observability-diagnostics", summary: "Git source, version, module output, product artifact, and legacy Kungfu build fact APIs." },
|
|
156
169
|
"./candidate-timeline": { group: "observability-diagnostics", summary: "Source-bound candidate event normalization, per-attempt critical-path-safe aggregation, and compact reporting APIs." },
|
|
157
170
|
"./channel-candidate": { group: "governance-versioning", summary: "Exact-source channel candidate decisions, same-SHA workflow evidence validation, and deterministic source-lock reference APIs." },
|
|
171
|
+
"./dev-delivery-warrant": { group: "governance-versioning", summary: "Durable fair Dev Delivery Warrant queue, fenced lease, and split source and integration proof APIs." },
|
|
158
172
|
"./cache-evidence": { group: "observability-diagnostics", summary: "Content-addressed cache operation receipts and source/platform-bound evidence-set verification APIs." },
|
|
159
173
|
"./diagnostics": { group: "observability-diagnostics", summary: "Native diagnostics collection, summarization, cache, compiler, and process-sampler APIs." },
|
|
160
174
|
"./logging": { group: "observability-diagnostics", summary: "Buildchain JSONL logging, span, summary, and verification APIs." },
|