@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.
Files changed (72) hide show
  1. package/README.md +4 -4
  2. package/actions/promote-buildchain-ref/README.md +8 -0
  3. package/bin/buildchain.mjs +13 -1
  4. package/contracts/auditable-demo-scenario-v1.schema.json +52 -0
  5. package/dist/site/buildchain-contract.json +47 -27
  6. package/dist/site/buildchain-site.json +165 -44
  7. package/dist/site/capability-registry.json +3 -3
  8. package/dist/site/cli-registry.json +40 -4
  9. package/dist/site/controller-registry.json +20 -4
  10. package/dist/site/kfd-claims.json +140 -19
  11. package/dist/site/kfd-upstream-aggregate.json +9 -9
  12. package/dist/site/manual-registry.json +9 -9
  13. package/dist/site/node-api-registry.json +1161 -180
  14. package/dist/site/page-registry.json +152 -31
  15. package/dist/site/public-surface-audit.json +386 -19
  16. package/dist/site/publication-authority-registry.json +61 -1
  17. package/dist/site/publication-registry.json +4 -4
  18. package/dist/site/release-provenance.json +1 -0
  19. package/dist/site/site-manifest.json +13 -13
  20. package/dist/site/workflow-registry.json +151 -13
  21. package/docs/MAP.md +2 -0
  22. package/docs/auditable-demo.md +58 -11
  23. package/docs/aws-us-elastic-runner-burst-plane.md +114 -80
  24. package/docs/cli-reference.md +154 -0
  25. package/docs/dev-alpha-candidate-patrol.md +13 -5
  26. package/docs/dev-delivery-warrant.md +158 -0
  27. package/docs/node-api-reference.md +54 -15
  28. package/docs/publication-authority.md +11 -0
  29. package/docs/release-candidate.md +19 -2
  30. package/docs/release-governance.md +60 -1
  31. package/docs/reusable-build-surface.md +11 -1
  32. package/docs/shifu-gate-profiles.md +12 -1
  33. package/docs/versioning.md +2 -0
  34. package/package.json +4 -2
  35. package/packages/core/buildchain-publication-authority.js +3 -1
  36. package/packages/core/channel-candidate.js +2 -21
  37. package/packages/core/channel-promotion-baseline.js +199 -0
  38. package/packages/core/dev-delivery-candidate-identity.js +94 -0
  39. package/packages/core/dev-delivery-common.js +73 -0
  40. package/packages/core/dev-delivery-proof.js +252 -0
  41. package/packages/core/dev-delivery-warrant-cancellation.js +94 -0
  42. package/packages/core/dev-delivery-warrant-settlement.js +73 -0
  43. package/packages/core/dev-delivery-warrant.js +591 -0
  44. package/scripts/auditable-demo-bundle-verification.mjs +148 -0
  45. package/scripts/auditable-demo-platform.mjs +86 -50
  46. package/scripts/auditable-demo-presentation.mjs +83 -0
  47. package/scripts/auditable-demo-renditions.mjs +264 -0
  48. package/scripts/auditable-demo.mjs +24 -30
  49. package/scripts/aws-windows-jit-campaign-core.mjs +7 -8
  50. package/scripts/aws-windows-jit-controller.mjs +1 -0
  51. package/scripts/aws-windows-jit-core.mjs +1 -1
  52. package/scripts/build-contract-core.mjs +58 -3
  53. package/scripts/buildchain-cli-help.mjs +8 -0
  54. package/scripts/buildchain-patrol.mjs +9 -0
  55. package/scripts/check-inventory.mjs +1 -0
  56. package/scripts/dev-alpha-candidate-patrol.mjs +45 -48
  57. package/scripts/dev-delivery-proof.mjs +193 -0
  58. package/scripts/dev-delivery-warrant.mjs +426 -0
  59. package/scripts/dev-pr-auto-merge.mjs +497 -55
  60. package/scripts/dev-pr-delivery-warrant.mjs +209 -0
  61. package/scripts/dispatch-artifact-signing-authority.mjs +2 -4
  62. package/scripts/gate-profile-core.mjs +24 -0
  63. package/scripts/generate-site-bundle.mjs +2 -2
  64. package/scripts/git-fetch-process-tree.mjs +142 -0
  65. package/scripts/lifecycle-substage-evidence.mjs +274 -0
  66. package/scripts/locked-source-checkout.mjs +6 -3
  67. package/scripts/resolve-artifact-transfer-mode.mjs +9 -0
  68. package/scripts/resolve-build-contract.mjs +7 -0
  69. package/scripts/route-offline-runners.mjs +1 -0
  70. package/scripts/run-lifecycle-core.mjs +9 -9
  71. package/scripts/shifu-gate-profile.mjs +10 -16
  72. 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 output = execFileSync("git", args, {
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: { ...process.env, ...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
- 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(String(error?.message || error || ""));
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,
@@ -71,6 +71,7 @@ export function routeOfflineRunners({
71
71
  return {
72
72
  ...platform,
73
73
  runner: fallbackRunner,
74
+ githubHosted: true,
74
75
  };
75
76
  });
76
77
  return {
@@ -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
- stageName,
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
- if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
63
- throw new Error("gate-environment-json must be a JSON object");
64
- }
65
- const entries = Object.entries(parsed).map(([name, value]) => {
66
- if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(name))
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
- ...Object.fromEntries(entries),
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." },