@kungfu-tech/buildchain 3.0.2 → 3.0.3-alpha.1

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 (127) hide show
  1. package/AGENTS.md +1 -1
  2. package/CONTRIBUTING.md +1 -1
  3. package/README.md +27 -1
  4. package/actions/promote-buildchain-ref/README.md +90 -19
  5. package/actions/report-buildchain-issue/README.md +20 -2
  6. package/actions/run-lifecycle/README.md +5 -0
  7. package/actions/validate-config/README.md +19 -1
  8. package/bin/buildchain.mjs +56 -2
  9. package/bin/internal/command-registry.mjs +82 -0
  10. package/bin/internal/trust-release-cli.mjs +1 -0
  11. package/contracts/auditable-demo-media-profiles-v1.json +116 -1
  12. package/contracts/buildchain-v2-residuals-v1.json +146 -0
  13. package/contracts/evidence/auditable-demo-responsive-web-delivery-v1.json +143 -0
  14. package/contracts/evidence/auditable-demo-web-delivery-v1.json +7 -3
  15. package/contracts/fixtures/auditable-demo-responsive-web-delivery-v1/complete-transcript.txt +2 -0
  16. package/contracts/fixtures/auditable-demo-responsive-web-delivery-v1/public-projection.json +16 -0
  17. package/contracts/fixtures/auditable-demo-responsive-web-delivery-v1/scene.json +12 -0
  18. package/dist/site/buildchain-contract.json +188 -46
  19. package/dist/site/buildchain-site.json +181 -90
  20. package/dist/site/capability-registry.json +5 -5
  21. package/dist/site/cli-registry.json +860 -98
  22. package/dist/site/controller-registry.json +118 -5
  23. package/dist/site/kfd-claims.json +233 -19
  24. package/dist/site/kfd-upstream-aggregate.json +1 -1
  25. package/dist/site/manual-registry.json +20 -20
  26. package/dist/site/node-api-registry.json +374 -59
  27. package/dist/site/page-registry.json +156 -65
  28. package/dist/site/public-surface-audit.json +231 -22
  29. package/dist/site/publication-registry.json +4 -4
  30. package/dist/site/release-provenance.json +3 -0
  31. package/dist/site/site-manifest.json +24 -24
  32. package/dist/site/workflow-registry.json +575 -79
  33. package/docs/MAP.md +22 -4
  34. package/docs/auditable-demo.md +54 -13
  35. package/docs/aws-us-elastic-runner-burst-plane.md +381 -0
  36. package/docs/cli.md +119 -12
  37. package/docs/consumer-issue-reporting.md +20 -2
  38. package/docs/dev-alpha-candidate-patrol.md +24 -10
  39. package/docs/github-governance-authority.md +24 -31
  40. package/docs/lifecycle-protocol.md +20 -2
  41. package/docs/migration-inventory.md +24 -5
  42. package/docs/observed-evidence-patrol.md +28 -12
  43. package/docs/ownership.md +19 -1
  44. package/docs/publication-artifacts.md +158 -18
  45. package/docs/publication-authority.md +4 -4
  46. package/docs/publish-transaction.md +78 -31
  47. package/docs/release-candidate.md +24 -0
  48. package/docs/release-flow.md +50 -32
  49. package/docs/release-governance.md +39 -32
  50. package/docs/release-passport.md +4 -0
  51. package/docs/release-propagation.md +57 -12
  52. package/docs/reusable-build-surface.md +218 -90
  53. package/docs/runtime-train-validation.md +20 -2
  54. package/docs/shifu-gate-profiles.md +7 -1
  55. package/docs/stable-candidate-patrol.md +6 -6
  56. package/docs/toolkit-observability.md +22 -0
  57. package/docs/versioning.md +46 -24
  58. package/docs/web-surface-deployments.md +82 -1
  59. package/package.json +7 -5
  60. package/packages/core/README.md +60 -4
  61. package/packages/core/artifact-signing.js +1 -0
  62. package/packages/core/buildchain-contract.js +9 -4
  63. package/packages/core/cache-evidence.js +1 -0
  64. package/packages/core/controller-evidence.js +2 -0
  65. package/packages/core/diagnostics.js +105 -30
  66. package/packages/core/github-governance-authority.js +25 -17
  67. package/packages/core/index.js +40 -0
  68. package/packages/core/paper.js +3226 -0
  69. package/packages/core/public-surface-audit.js +3 -0
  70. package/packages/core/publication-artifact.js +1 -6
  71. package/packages/core/publication-authority.js +1 -0
  72. package/packages/core/publication-control-plane-audit.js +28 -0
  73. package/packages/core/publication-reproducibility.js +947 -0
  74. package/packages/core/publication-sealed-bundle.js +190 -0
  75. package/packages/core/publish-transaction.js +147 -17
  76. package/packages/core/release-candidate.js +234 -0
  77. package/packages/core/release-passport.js +519 -228
  78. package/packages/core/release-propagation.js +175 -1
  79. package/packages/core/stable-release-gate.js +4 -1
  80. package/scripts/artifact-signing-delegation.mjs +268 -0
  81. package/scripts/audit-github-governance.mjs +32 -13
  82. package/scripts/audit-publication-control-plane.mjs +17 -8
  83. package/scripts/auditable-demo.mjs +266 -7
  84. package/scripts/aws-codebuild-toolchain.mjs +285 -0
  85. package/scripts/aws-macos-jit-core.mjs +378 -0
  86. package/scripts/aws-macos-jit.mjs +123 -0
  87. package/scripts/aws-runner-burst-core.mjs +248 -0
  88. package/scripts/aws-runner-burst.mjs +79 -0
  89. package/scripts/aws-windows-jit-core.mjs +374 -0
  90. package/scripts/aws-windows-jit.mjs +121 -0
  91. package/scripts/build-contract-core.mjs +69 -6
  92. package/scripts/build-standalone-binary.mjs +23 -0
  93. package/scripts/buildchain-contract-lock.mjs +1 -1
  94. package/scripts/buildchain-patrol.mjs +1 -1
  95. package/scripts/check-internal-architecture.mjs +69 -2
  96. package/scripts/check-inventory.mjs +70 -2
  97. package/scripts/check-javascript-syntax.mjs +31 -0
  98. package/scripts/check-maintainability.mjs +371 -0
  99. package/scripts/compiler-cache-evidence.mjs +221 -0
  100. package/scripts/dev-alpha-candidate-patrol.mjs +288 -43
  101. package/scripts/dispatch-artifact-signing-authority.mjs +7 -1
  102. package/scripts/finalize-native-artifact-signing-result.mjs +185 -31
  103. package/scripts/gate-profile-core.mjs +6 -1
  104. package/scripts/generate-channel-promotion-workflow.mjs +3 -12
  105. package/scripts/generate-release-candidate-passport.mjs +4 -0
  106. package/scripts/generate-site-bundle.mjs +84 -18
  107. package/scripts/import-artifact-signing-results.mjs +64 -5
  108. package/scripts/inspect-artifact-signing-requests.mjs +52 -14
  109. package/scripts/maintainability-metrics.mjs +339 -0
  110. package/scripts/npm-publish-transaction.mjs +57 -6
  111. package/scripts/observed-evidence.mjs +151 -33
  112. package/scripts/paper.mjs +645 -0
  113. package/scripts/promotion-channel-router.mjs +16 -4
  114. package/scripts/publication-reproducibility.mjs +62 -0
  115. package/scripts/reconcile-github-governance.mjs +8 -1
  116. package/scripts/release-propagation.mjs +39 -0
  117. package/scripts/resolve-artifact-signing-upload-route.mjs +55 -0
  118. package/scripts/resolve-build-contract.mjs +6 -0
  119. package/scripts/route-offline-runners.mjs +236 -0
  120. package/scripts/run-candidate-body-prefix-renderer.mjs +187 -0
  121. package/scripts/run-lifecycle-core.mjs +62 -1
  122. package/scripts/runtime-ref-core.mjs +36 -4
  123. package/scripts/seal-artifact-signing-requests.mjs +64 -12
  124. package/scripts/stable-candidate-qualification.mjs +50 -0
  125. package/scripts/verify-artifact-signing-results.mjs +26 -1
  126. package/scripts/web-surface-core.mjs +272 -86
  127. package/scripts/web-surface-production-decision.mjs +19 -3
@@ -0,0 +1,285 @@
1
+ #!/usr/bin/env node
2
+ import crypto from "node:crypto";
3
+ import fs from "node:fs";
4
+ import os from "node:os";
5
+ import path from "node:path";
6
+ import { execFileSync } from "node:child_process";
7
+ import { pathToFileURL } from "node:url";
8
+
9
+ export const AWS_CODEBUILD_TOOLCHAIN = Object.freeze({
10
+ schema: "buildchain.aws-codebuild-native-toolchain/v1",
11
+ gccPackages: ["gcc14", "gcc14-c++"],
12
+ gcc: "gcc14-gcc",
13
+ gxx: "gcc14-g++",
14
+ compilerProfiles: Object.freeze({
15
+ amazonLinux2023: Object.freeze({
16
+ packageManager: "dnf",
17
+ packages: ["gcc14", "gcc14-c++"],
18
+ gcc: "gcc14-gcc",
19
+ gxx: "gcc14-g++",
20
+ }),
21
+ ubuntu2404: Object.freeze({
22
+ packageManager: "apt-get",
23
+ packages: ["gcc-14", "g++-14"],
24
+ gcc: "gcc-14",
25
+ gxx: "g++-14",
26
+ }),
27
+ }),
28
+ cmakeVersion: "3.31.6",
29
+ cmakeArchive: "cmake-3.31.6-linux-x86_64.tar.gz",
30
+ cmakeSha256:
31
+ "5a1133ff103c71eb5120e2cc3de922733e7d8a26a98ae716397e8676adb367bf",
32
+ cmakeBaseUrl: "https://github.com/Kitware/CMake/releases/download/v3.31.6",
33
+ });
34
+
35
+ function run(command, args, options = {}) {
36
+ return execFileSync(command, args, {
37
+ encoding: "utf8",
38
+ stdio: options.capture ? ["ignore", "pipe", "pipe"] : "inherit",
39
+ ...options,
40
+ });
41
+ }
42
+
43
+ function commandPath(command) {
44
+ return run("bash", ["-lc", `command -v ${command}`], {
45
+ capture: true,
46
+ }).trim();
47
+ }
48
+
49
+ function commandExists(command) {
50
+ try {
51
+ return Boolean(commandPath(command));
52
+ } catch {
53
+ return false;
54
+ }
55
+ }
56
+
57
+ export function selectAwsCodeBuildCompiler({
58
+ hasDnf = false,
59
+ hasAptGet = false,
60
+ } = {}) {
61
+ if (hasDnf) return AWS_CODEBUILD_TOOLCHAIN.compilerProfiles.amazonLinux2023;
62
+ if (hasAptGet) return AWS_CODEBUILD_TOOLCHAIN.compilerProfiles.ubuntu2404;
63
+ throw new Error(
64
+ "AWS CodeBuild toolchain requires a supported package manager (dnf or apt-get)",
65
+ );
66
+ }
67
+
68
+ function installCompiler(profile, elevated) {
69
+ if (profile.packageManager === "dnf") {
70
+ const command = elevated ? "dnf" : "sudo";
71
+ const args = elevated
72
+ ? ["install", "-y", ...profile.packages]
73
+ : ["dnf", "install", "-y", ...profile.packages];
74
+ run(command, args);
75
+ return;
76
+ }
77
+
78
+ if (profile.packageManager === "apt-get") {
79
+ run(
80
+ elevated ? "apt-get" : "sudo",
81
+ elevated ? ["update"] : ["apt-get", "update"],
82
+ );
83
+ const command = elevated ? "env" : "sudo";
84
+ const args = elevated
85
+ ? [
86
+ "DEBIAN_FRONTEND=noninteractive",
87
+ "apt-get",
88
+ "install",
89
+ "-y",
90
+ ...profile.packages,
91
+ ]
92
+ : [
93
+ "env",
94
+ "DEBIAN_FRONTEND=noninteractive",
95
+ "apt-get",
96
+ "install",
97
+ "-y",
98
+ ...profile.packages,
99
+ ];
100
+ run(command, args);
101
+ return;
102
+ }
103
+
104
+ throw new Error(
105
+ `unsupported AWS CodeBuild package manager: ${profile.packageManager}`,
106
+ );
107
+ }
108
+
109
+ function appendGitHubPath(entry) {
110
+ const githubPath = process.env.GITHUB_PATH;
111
+ if (!githubPath) {
112
+ throw new Error("GITHUB_PATH is required on the CodeBuild GitHub runner");
113
+ }
114
+ fs.appendFileSync(githubPath, `${entry}\n`);
115
+ }
116
+
117
+ function sha256(filePath) {
118
+ return crypto
119
+ .createHash("sha256")
120
+ .update(fs.readFileSync(filePath))
121
+ .digest("hex");
122
+ }
123
+
124
+ function ensureAlias(directory, name, target) {
125
+ const alias = path.join(directory, name);
126
+ if (fs.existsSync(alias)) {
127
+ if (fs.realpathSync(alias) !== fs.realpathSync(target)) {
128
+ throw new Error(`${alias} already exists with a different target`);
129
+ }
130
+ return alias;
131
+ }
132
+ fs.symlinkSync(target, alias);
133
+ return alias;
134
+ }
135
+
136
+ function assertMinimumVersion(output, minimum, label) {
137
+ const match = String(output).match(/(\d+)\.(\d+)(?:\.(\d+))?/);
138
+ if (!match) throw new Error(`${label} did not report a parseable version`);
139
+ const actual = match.slice(1, 4).map((value) => Number(value || 0));
140
+ const required = minimum.split(".").map(Number);
141
+ for (let index = 0; index < required.length; index += 1) {
142
+ if (actual[index] > required[index]) return;
143
+ if (actual[index] < required[index]) {
144
+ throw new Error(`${label} ${actual.join(".")} is below ${minimum}`);
145
+ }
146
+ }
147
+ }
148
+
149
+ function writeEvidence(outputPath, evidence) {
150
+ fs.mkdirSync(path.dirname(outputPath), { recursive: true });
151
+ fs.writeFileSync(outputPath, `${JSON.stringify(evidence, null, 2)}\n`);
152
+ }
153
+
154
+ export function prepareAwsCodeBuildToolchain({
155
+ runnerTemp = process.env.RUNNER_TEMP || os.tmpdir(),
156
+ evidencePath = process.env.BUILDCHAIN_BURST_TOOLCHAIN_EVIDENCE_PATH ||
157
+ ".buildchain/artifacts/linux-x64/aws-native-toolchain.json",
158
+ } = {}) {
159
+ if (!process.env.CODEBUILD_BUILD_ID) {
160
+ throw new Error(
161
+ "AWS CodeBuild toolchain preparation requires CODEBUILD_BUILD_ID",
162
+ );
163
+ }
164
+ if (process.platform !== "linux" || process.arch !== "x64") {
165
+ throw new Error("AWS CodeBuild toolchain preparation requires Linux x64");
166
+ }
167
+
168
+ const elevated =
169
+ typeof process.getuid === "function" && process.getuid() === 0;
170
+ const compiler = selectAwsCodeBuildCompiler({
171
+ hasDnf: commandExists("dnf"),
172
+ hasAptGet: commandExists("apt-get"),
173
+ });
174
+ installCompiler(compiler, elevated);
175
+
176
+ const aliasDirectory = path.join(runnerTemp, "buildchain-gcc14", "bin");
177
+ fs.mkdirSync(aliasDirectory, { recursive: true });
178
+ const gccPath = commandPath(compiler.gcc);
179
+ const gxxPath = commandPath(compiler.gxx);
180
+ ensureAlias(aliasDirectory, "cc", gccPath);
181
+ ensureAlias(aliasDirectory, "gcc", gccPath);
182
+ ensureAlias(aliasDirectory, "c++", gxxPath);
183
+ ensureAlias(aliasDirectory, "g++", gxxPath);
184
+
185
+ const cmakeUrl = `${AWS_CODEBUILD_TOOLCHAIN.cmakeBaseUrl}/${AWS_CODEBUILD_TOOLCHAIN.cmakeArchive}`;
186
+ const cmakeArchivePath = path.join(
187
+ runnerTemp,
188
+ AWS_CODEBUILD_TOOLCHAIN.cmakeArchive,
189
+ );
190
+ run("curl", [
191
+ "--proto",
192
+ "=https",
193
+ "--tlsv1.2",
194
+ "--fail",
195
+ "--location",
196
+ "--retry",
197
+ "5",
198
+ "--retry-all-errors",
199
+ "--output",
200
+ cmakeArchivePath,
201
+ cmakeUrl,
202
+ ]);
203
+ const actualCmakeSha256 = sha256(cmakeArchivePath);
204
+ if (actualCmakeSha256 !== AWS_CODEBUILD_TOOLCHAIN.cmakeSha256) {
205
+ throw new Error(
206
+ `CMake archive SHA256 ${actualCmakeSha256} does not match the reviewed pin`,
207
+ );
208
+ }
209
+
210
+ const cmakeDirectory = path.join(
211
+ runnerTemp,
212
+ `buildchain-cmake-${AWS_CODEBUILD_TOOLCHAIN.cmakeVersion}`,
213
+ );
214
+ fs.mkdirSync(cmakeDirectory, { recursive: true });
215
+ run("tar", [
216
+ "-xzf",
217
+ cmakeArchivePath,
218
+ "-C",
219
+ cmakeDirectory,
220
+ "--strip-components=1",
221
+ ]);
222
+ const cmakeBinDirectory = path.join(cmakeDirectory, "bin");
223
+ const cmakePath = path.join(cmakeBinDirectory, "cmake");
224
+
225
+ const gccVersion = run(gccPath, ["--version"], { capture: true }).trim();
226
+ const gxxVersion = run(gxxPath, ["--version"], { capture: true }).trim();
227
+ const cmakeVersion = run(cmakePath, ["--version"], { capture: true }).trim();
228
+ assertMinimumVersion(gccVersion, "14.0.0", "gcc");
229
+ assertMinimumVersion(gxxVersion, "14.0.0", "g++");
230
+ assertMinimumVersion(cmakeVersion, "3.28.0", "cmake");
231
+
232
+ appendGitHubPath(aliasDirectory);
233
+ appendGitHubPath(cmakeBinDirectory);
234
+
235
+ const evidence = {
236
+ schema: AWS_CODEBUILD_TOOLCHAIN.schema,
237
+ provider: "aws-codebuild",
238
+ buildId: process.env.CODEBUILD_BUILD_ID,
239
+ os: process.platform,
240
+ architecture: process.arch,
241
+ packageManager: compiler.packageManager,
242
+ gcc: {
243
+ packages: compiler.packages,
244
+ executable: compiler.gcc,
245
+ version: gccVersion.split("\n")[0],
246
+ },
247
+ gxx: {
248
+ executable: compiler.gxx,
249
+ version: gxxVersion.split("\n")[0],
250
+ },
251
+ cmake: {
252
+ version: cmakeVersion.split("\n")[0],
253
+ source: cmakeUrl,
254
+ sha256: actualCmakeSha256,
255
+ },
256
+ observedAt: new Date().toISOString(),
257
+ };
258
+ writeEvidence(path.resolve(evidencePath), evidence);
259
+ process.stdout.write(`${JSON.stringify(evidence, null, 2)}\n`);
260
+ return evidence;
261
+ }
262
+
263
+ export function main() {
264
+ const mode = process.argv[2] || "plan";
265
+ if (mode === "plan") {
266
+ process.stdout.write(
267
+ `${JSON.stringify(AWS_CODEBUILD_TOOLCHAIN, null, 2)}\n`,
268
+ );
269
+ return AWS_CODEBUILD_TOOLCHAIN;
270
+ }
271
+ if (mode === "prepare") return prepareAwsCodeBuildToolchain();
272
+ throw new Error(`unsupported aws-codebuild-toolchain mode: ${mode}`);
273
+ }
274
+
275
+ if (
276
+ process.argv[1] &&
277
+ import.meta.url === pathToFileURL(process.argv[1]).href
278
+ ) {
279
+ try {
280
+ main();
281
+ } catch (error) {
282
+ console.error(`::error::${error.message || error}`);
283
+ process.exitCode = 1;
284
+ }
285
+ }
@@ -0,0 +1,378 @@
1
+ import { digest } from "./aws-runner-burst-core.mjs";
2
+
3
+ export const AWS_MACOS_JIT_CONTRACT = "kungfu-buildchain-aws-macos-jit/v1";
4
+
5
+ export const MACOS_EC2_JIT = Object.freeze({
6
+ phase: "macos-ec2-jit",
7
+ region: "us-east-1",
8
+ repository: "kungfu-systems/kungfu",
9
+ stack: "kungfu-buildchain-macos-jit",
10
+ instanceType: "mac2.metal",
11
+ pricePerHourUsd: 0.6498,
12
+ minimumHostAllocationHours: 24,
13
+ maximumHostAllocationHours: 30,
14
+ maxAcceptedHosts: 1,
15
+ budgetLimitUsd: 25,
16
+ minimumJobs: 3,
17
+ minimumFullJobs: 1,
18
+ runnerVersion: "2.336.0",
19
+ runnerArchive: "actions-runner-osx-arm64-2.336.0.tar.gz",
20
+ runnerArchiveSha256:
21
+ "8e8839c49b7060b6b2154f4931f815df330c27f167d53ef2239ee3dfce28b079",
22
+ labelPrefix: "aws-us-ec2-macos-jit-",
23
+ jitParameterPrefix: "/kungfu/burst/macos/",
24
+ });
25
+
26
+ function exactSha(value, label) {
27
+ const normalized = String(value || "")
28
+ .trim()
29
+ .toLowerCase();
30
+ if (!/^[0-9a-f]{40}$/.test(normalized)) {
31
+ throw new Error(`${label} must be an exact 40-character Git SHA`);
32
+ }
33
+ return normalized;
34
+ }
35
+
36
+ function finiteNumber(value, label) {
37
+ const parsed = Number(value);
38
+ if (!Number.isFinite(parsed) || parsed < 0) {
39
+ throw new Error(`${label} must be a non-negative finite number`);
40
+ }
41
+ return parsed;
42
+ }
43
+
44
+ function iso(value, label) {
45
+ const parsed = new Date(value || "");
46
+ if (!Number.isFinite(parsed.getTime())) {
47
+ throw new Error(`${label} must be an ISO timestamp`);
48
+ }
49
+ return parsed.toISOString();
50
+ }
51
+
52
+ function elapsedHours(start, end) {
53
+ return (new Date(end).getTime() - new Date(start).getTime()) / 3_600_000;
54
+ }
55
+
56
+ export function macosJitRunnerLabel(value) {
57
+ const label = String(value || "")
58
+ .trim()
59
+ .toLowerCase();
60
+ const pattern = new RegExp(
61
+ `^${MACOS_EC2_JIT.labelPrefix}[a-z0-9][a-z0-9-]{0,31}$`,
62
+ );
63
+ if (!pattern.test(label)) {
64
+ throw new Error(
65
+ `runner label must match ${MACOS_EC2_JIT.labelPrefix}<bounded-id>`,
66
+ );
67
+ }
68
+ return label;
69
+ }
70
+
71
+ export function macosJitRunnerLabels(value) {
72
+ return ["self-hosted", "macOS", "ARM64", macosJitRunnerLabel(value)];
73
+ }
74
+
75
+ export function renderMacosJitBootstrap(template, values = {}) {
76
+ const runnerLabel = macosJitRunnerLabel(values.runnerLabel);
77
+ const jitParameterName = String(values.jitParameterName || "").trim();
78
+ if (
79
+ !jitParameterName.startsWith(MACOS_EC2_JIT.jitParameterPrefix) ||
80
+ !/^\/[A-Za-z0-9._/-]+$/.test(jitParameterName)
81
+ ) {
82
+ throw new Error("jitParameterName must use the dedicated macOS prefix");
83
+ }
84
+ const bounded = {
85
+ REGION: String(values.region || MACOS_EC2_JIT.region),
86
+ JIT_PARAMETER_NAME: jitParameterName,
87
+ EVIDENCE_BUCKET: String(values.evidenceBucket || ""),
88
+ RUNNER_LABEL: runnerLabel,
89
+ SOURCE_SHA: exactSha(values.sourceSha, "sourceSha"),
90
+ GITHUB_RUN_ID: String(values.githubRunId || ""),
91
+ GITHUB_RUN_ATTEMPT: String(values.githubRunAttempt || ""),
92
+ AMI_ID: String(values.amiId || ""),
93
+ AMI_NAME: String(values.amiName || ""),
94
+ HOST_ID: String(values.hostId || ""),
95
+ INSTANCE_TYPE: String(values.instanceType || MACOS_EC2_JIT.instanceType),
96
+ HOST_ALLOCATED_AT: iso(values.hostAllocatedAt, "hostAllocatedAt"),
97
+ };
98
+ const patterns = {
99
+ REGION: /^us-[a-z]+-\d$/,
100
+ EVIDENCE_BUCKET: /^[a-z0-9][a-z0-9.-]{1,61}[a-z0-9]$/,
101
+ GITHUB_RUN_ID: /^\d+$/,
102
+ GITHUB_RUN_ATTEMPT: /^\d+$/,
103
+ AMI_ID: /^ami-[0-9a-f]+$/,
104
+ AMI_NAME: /^[A-Za-z0-9._-]+$/,
105
+ HOST_ID: /^h-[0-9a-f]+$/,
106
+ INSTANCE_TYPE: /^mac2\.metal$/,
107
+ };
108
+ for (const [name, pattern] of Object.entries(patterns)) {
109
+ if (!pattern.test(bounded[name])) {
110
+ throw new Error(`${name.toLowerCase()} is invalid`);
111
+ }
112
+ }
113
+ let rendered = String(template || "");
114
+ for (const [name, value] of Object.entries(bounded)) {
115
+ rendered = rendered.replaceAll(`__${name}__`, value);
116
+ }
117
+ const unresolved = rendered.match(/__[A-Z0-9_]+__/g) || [];
118
+ if (unresolved.length) {
119
+ throw new Error(
120
+ `bootstrap has unresolved variables: ${unresolved.join(",")}`,
121
+ );
122
+ }
123
+ if (/encoded_jit_config|github_pat_|ghp_|gho_/.test(rendered)) {
124
+ throw new Error("bootstrap must not contain a GitHub credential");
125
+ }
126
+ return rendered;
127
+ }
128
+
129
+ export function macosEc2JitPlan(overrides = {}) {
130
+ const config = { ...MACOS_EC2_JIT, ...overrides };
131
+ const money = (value) => Math.round(value * 100) / 100;
132
+ const minimumCommittedComputeUsd = money(
133
+ config.pricePerHourUsd *
134
+ config.minimumHostAllocationHours *
135
+ config.maxAcceptedHosts,
136
+ );
137
+ const maximumBoundedSpendUsd = money(
138
+ config.pricePerHourUsd *
139
+ config.maximumHostAllocationHours *
140
+ config.maxAcceptedHosts,
141
+ );
142
+ if (maximumBoundedSpendUsd >= config.budgetLimitUsd) {
143
+ throw new Error(
144
+ `bounded macOS EC2 spend ${maximumBoundedSpendUsd.toFixed(2)} must remain below budget ${config.budgetLimitUsd.toFixed(2)}`,
145
+ );
146
+ }
147
+ const plan = {
148
+ schemaVersion: 1,
149
+ contract: AWS_MACOS_JIT_CONTRACT,
150
+ kind: "phase-plan",
151
+ config,
152
+ costEnvelope: {
153
+ currency: "USD",
154
+ minimumCommittedComputeUsd,
155
+ maximumBoundedSpendUsd,
156
+ assumption:
157
+ "One mac2.metal Dedicated Host is retained for at least 24 hours and released no later than the 30-hour fail-closed ceiling.",
158
+ },
159
+ invariants: {
160
+ repositoryScopedJit: true,
161
+ oneJobPerRunner: true,
162
+ oneHostPerCampaign: true,
163
+ oneInstancePerCampaign: true,
164
+ minimumJobsOnHost: config.minimumJobs,
165
+ minimumFullJobsOnHost: config.minimumFullJobs,
166
+ minimumHostAllocationHours: config.minimumHostAllocationHours,
167
+ noInboundSecurityGroupRules: true,
168
+ imdsv2Required: true,
169
+ encryptedDeleteOnTerminationRoot: true,
170
+ jitConfigurationDeletedAfterRead: true,
171
+ jitConfigurationLabels: [
172
+ "self-hosted",
173
+ "macOS",
174
+ "ARM64",
175
+ `${config.labelPrefix}<bounded-id>`,
176
+ ],
177
+ staticAwsCredentialsForbidden: true,
178
+ signingAndPublicationCredentialsForbidden: true,
179
+ zeroWarmCapacityBeforeAndAfterCampaign: true,
180
+ },
181
+ };
182
+ return { ...plan, digest: digest(plan) };
183
+ }
184
+
185
+ export function createMacosJitEvidence({
186
+ repository,
187
+ sourceSha,
188
+ sourceRef,
189
+ githubRunId,
190
+ githubRunAttempt,
191
+ githubJob,
192
+ runnerName,
193
+ runnerLabels = [],
194
+ hostId,
195
+ instanceId,
196
+ instanceType,
197
+ amiId,
198
+ amiName,
199
+ availabilityZone,
200
+ hostAllocatedAt,
201
+ instanceLaunchedAt,
202
+ runnerStartedAt,
203
+ runnerExitedAt,
204
+ cacheMode = "off",
205
+ observedAt = new Date().toISOString(),
206
+ } = {}) {
207
+ if (repository !== MACOS_EC2_JIT.repository) {
208
+ throw new Error(`repository must be ${MACOS_EC2_JIT.repository}`);
209
+ }
210
+ if (instanceType !== MACOS_EC2_JIT.instanceType) {
211
+ throw new Error(`instanceType must be ${MACOS_EC2_JIT.instanceType}`);
212
+ }
213
+ if (!/^h-[0-9a-f]+$/.test(String(hostId || ""))) {
214
+ throw new Error("hostId must be an EC2 Dedicated Host id");
215
+ }
216
+ if (!/^i-[0-9a-f]+$/.test(String(instanceId || ""))) {
217
+ throw new Error("instanceId must be an EC2 instance id");
218
+ }
219
+ if (!/^ami-[0-9a-f]+$/.test(String(amiId || ""))) {
220
+ throw new Error("amiId must be an EC2 AMI id");
221
+ }
222
+ const labels = [...new Set(runnerLabels.map((label) => String(label).trim()))]
223
+ .filter(Boolean)
224
+ .sort();
225
+ const customLabel = labels.find((label) =>
226
+ label.startsWith(MACOS_EC2_JIT.labelPrefix),
227
+ );
228
+ macosJitRunnerLabel(customLabel);
229
+ for (const required of ["self-hosted", "macOS", "ARM64"]) {
230
+ if (!labels.includes(required)) {
231
+ throw new Error(`runnerLabels must include ${required}`);
232
+ }
233
+ }
234
+ const evidence = {
235
+ schemaVersion: 1,
236
+ contract: AWS_MACOS_JIT_CONTRACT,
237
+ kind: "runner-evidence",
238
+ phase: MACOS_EC2_JIT.phase,
239
+ provider: "aws-ec2-macos-jit",
240
+ repository,
241
+ source: {
242
+ sha: exactSha(sourceSha, "sourceSha"),
243
+ ref: String(sourceRef || "").trim(),
244
+ },
245
+ github: {
246
+ runId: String(githubRunId || "").trim(),
247
+ runAttempt: String(githubRunAttempt || "").trim(),
248
+ job: String(githubJob || "").trim(),
249
+ },
250
+ runner: {
251
+ name: String(runnerName || "").trim(),
252
+ labels,
253
+ version: MACOS_EC2_JIT.runnerVersion,
254
+ archiveSha256: MACOS_EC2_JIT.runnerArchiveSha256,
255
+ cacheMode: String(cacheMode || "off"),
256
+ },
257
+ aws: {
258
+ region: MACOS_EC2_JIT.region,
259
+ hostId: String(hostId),
260
+ instanceId: String(instanceId),
261
+ instanceType,
262
+ amiId: String(amiId),
263
+ amiName: String(amiName || "").trim(),
264
+ availabilityZone: String(availabilityZone || "").trim(),
265
+ },
266
+ lifecycle: {
267
+ hostAllocatedAt: iso(hostAllocatedAt, "hostAllocatedAt"),
268
+ instanceLaunchedAt: iso(instanceLaunchedAt, "instanceLaunchedAt"),
269
+ runnerStartedAt: iso(runnerStartedAt, "runnerStartedAt"),
270
+ runnerExitedAt: iso(runnerExitedAt, "runnerExitedAt"),
271
+ },
272
+ observedAt: iso(observedAt, "observedAt"),
273
+ };
274
+ for (const [label, value] of Object.entries({
275
+ sourceRef: evidence.source.ref,
276
+ runId: evidence.github.runId,
277
+ runAttempt: evidence.github.runAttempt,
278
+ job: evidence.github.job,
279
+ runnerName: evidence.runner.name,
280
+ amiName: evidence.aws.amiName,
281
+ availabilityZone: evidence.aws.availabilityZone,
282
+ })) {
283
+ if (!value) throw new Error(`${label} is required`);
284
+ }
285
+ return { ...evidence, digest: digest(evidence) };
286
+ }
287
+
288
+ export function verifyMacosEc2JitQualification({
289
+ jobs = [],
290
+ hostLifecycle = {},
291
+ registeredCloudRunners = [],
292
+ activeInstances = [],
293
+ allocatedHosts = [],
294
+ disposableVolumes = [],
295
+ actualIncrementalSpendUsd,
296
+ observedAt = new Date().toISOString(),
297
+ } = {}) {
298
+ const plan = macosEc2JitPlan();
299
+ const issues = [];
300
+ const accepted = jobs.filter(
301
+ (job) =>
302
+ job?.trusted === true &&
303
+ job?.exactSource === true &&
304
+ job?.status === "succeeded" &&
305
+ job?.oneJobJit === true,
306
+ );
307
+ const fullJobs = accepted.filter((job) => job.kind === "full");
308
+ if (accepted.length < plan.config.minimumJobs) {
309
+ issues.push("insufficient-trusted-macos-jobs");
310
+ }
311
+ if (fullJobs.length < plan.config.minimumFullJobs) {
312
+ issues.push("trusted-full-macos-job-missing");
313
+ }
314
+ const hostIds = new Set(accepted.map((job) => String(job.hostId || "")));
315
+ const instanceIds = new Set(
316
+ accepted.map((job) => String(job.instanceId || "")),
317
+ );
318
+ if (hostIds.size !== 1 || hostIds.has("")) {
319
+ issues.push("jobs-not-bound-to-one-dedicated-host");
320
+ }
321
+ if (instanceIds.size !== 1 || instanceIds.has("")) {
322
+ issues.push("jobs-not-bound-to-one-campaign-instance");
323
+ }
324
+ let allocationHours = 0;
325
+ try {
326
+ const allocatedAt = iso(hostLifecycle.allocatedAt, "hostLifecycle.allocatedAt");
327
+ const releasedAt = iso(hostLifecycle.releasedAt, "hostLifecycle.releasedAt");
328
+ allocationHours = elapsedHours(allocatedAt, releasedAt);
329
+ if (
330
+ hostLifecycle.status !== "passed" ||
331
+ hostLifecycle.instanceTerminated !== true ||
332
+ hostLifecycle.scrubCompleted !== true ||
333
+ hostLifecycle.hostReleased !== true ||
334
+ allocationHours < plan.config.minimumHostAllocationHours ||
335
+ allocationHours > plan.config.maximumHostAllocationHours
336
+ ) {
337
+ issues.push("dedicated-host-lifecycle-not-proven");
338
+ }
339
+ } catch {
340
+ issues.push("dedicated-host-lifecycle-not-proven");
341
+ }
342
+ const spend = finiteNumber(
343
+ actualIncrementalSpendUsd,
344
+ "actualIncrementalSpendUsd",
345
+ );
346
+ if (spend >= plan.config.budgetLimitUsd) {
347
+ issues.push("actual-spend-not-below-budget");
348
+ }
349
+ if (registeredCloudRunners.length) issues.push("registered-runners-remain");
350
+ if (activeInstances.length) issues.push("active-instances-remain");
351
+ if (allocatedHosts.length) issues.push("allocated-hosts-remain");
352
+ if (disposableVolumes.length) issues.push("disposable-volumes-remain");
353
+ const result = {
354
+ schemaVersion: 1,
355
+ contract: AWS_MACOS_JIT_CONTRACT,
356
+ kind: "phase-verification",
357
+ phase: plan.config.phase,
358
+ status: issues.length ? "failed" : "passed",
359
+ qualifying: issues.length === 0,
360
+ metrics: {
361
+ acceptedJobs: accepted.length,
362
+ fullJobs: fullJobs.length,
363
+ distinctHosts: hostIds.size,
364
+ distinctInstances: instanceIds.size,
365
+ hostAllocationHours: Math.round(allocationHours * 1000) / 1000,
366
+ actualIncrementalSpendUsd: spend,
367
+ registeredCloudRunners: registeredCloudRunners.length,
368
+ activeInstances: activeInstances.length,
369
+ allocatedHosts: allocatedHosts.length,
370
+ disposableVolumes: disposableVolumes.length,
371
+ },
372
+ hostLifecycle,
373
+ issues: [...new Set(issues)],
374
+ planDigest: plan.digest,
375
+ observedAt: iso(observedAt, "observedAt"),
376
+ };
377
+ return { ...result, digest: digest(result) };
378
+ }