@kungfu-tech/buildchain 3.0.9-alpha.9 → 4.0.0-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 (262) hide show
  1. package/AGENTS.md +39 -0
  2. package/README.md +17 -13
  3. package/actions/promote-buildchain-ref/README.md +0 -10
  4. package/actions/release-tail/README.md +6 -10
  5. package/architecture/internal-capabilities.json +502 -0
  6. package/architecture/maintainability-baseline.json +59 -0
  7. package/architecture/maintainability-policy.json +1384 -0
  8. package/architecture/release-tail-contract-inventory.json +587 -0
  9. package/architecture/v3-core-mechanism-inventory.json +1043 -0
  10. package/architecture/v3-core-mechanism-inventory.md +67 -0
  11. package/architecture/v4-adopter-delivery-parity.json +47 -0
  12. package/architecture/v4-architecture-constitution.md +210 -0
  13. package/architecture/v4-bootstrap-authority.json +56 -0
  14. package/architecture/v4-canonical-contract-fixtures.json +105 -0
  15. package/architecture/v4-capability-state-machine-manifest.json +480 -0
  16. package/architecture/v4-capability-state-machine-manifest.schema.json +202 -0
  17. package/architecture/v4-delivery-warrant-shadow-bootstrap-plan.json +830 -0
  18. package/architecture/v4-delivery-warrant-shadow-bootstrap-plan.md +223 -0
  19. package/architecture/v4-delivery-warrant-shadow-bootstrap-plan.schema.json +318 -0
  20. package/architecture/v4-delivery-warrant-shadow-fixtures.json +227 -0
  21. package/architecture/v4-delivery-warrant-shadow-fixtures.schema.json +67 -0
  22. package/architecture/v4-exception-ledger.json +5 -0
  23. package/architecture/v4-exception-ledger.schema.json +56 -0
  24. package/architecture/v4-partial-mutation-recovery-qualification.json +65 -0
  25. package/architecture/v4-platform-stage-checkpoints.json +97 -0
  26. package/architecture/v4-provider-operation-journal-contract.json +113 -0
  27. package/architecture/v4-release-activation-shadow-domain.json +61 -0
  28. package/architecture/v4-release-train-parity.json +68 -0
  29. package/architecture/v4-rust-libnode-bridge-evaluation.json +107 -0
  30. package/architecture/v4-rust-libnode-bridge-spike.md +85 -0
  31. package/architecture/v4-stable-publication-fence.json +66 -0
  32. package/architecture/v4-stage-capsule-contract.json +90 -0
  33. package/architecture/v4-stage-capsule-qualification.json +99 -0
  34. package/architecture/v4-stage-capsule-resume-planner.json +52 -0
  35. package/architecture/v4-stage-capsule-store-contract.json +72 -0
  36. package/bin/buildchain.mjs +15 -10
  37. package/contracts/auditable-demo-scenario-v1.schema.json +1 -1
  38. package/contracts/buildchain-v2-residuals-v1.json +2 -2
  39. package/contracts/fixtures/v4-delivery-warrant-trace-v1/golden.json +158 -0
  40. package/contracts/fixtures/v4-delivery-warrant-trace-v1/property.json +380 -0
  41. package/contracts/fixtures/v4-delivery-warrant-trace-v1/replay.json +163 -0
  42. package/contracts/fixtures/v4-partial-mutation-recovery-v1/shared.json +155 -0
  43. package/contracts/fixtures/v4-provider-operation-journal-v1/shared.json +457 -0
  44. package/contracts/fixtures/v4-provider-readback-idempotency-v1/shared.json +682 -0
  45. package/contracts/fixtures/v4-release-activation-shadow-v1/shared.json +53 -0
  46. package/contracts/fixtures/v4-stable-publication-fence-v1/shared.json +56 -0
  47. package/contracts/fixtures/v4-stage-capsule-resume-v1/late-platform-failure.json +176 -0
  48. package/contracts/fixtures/v4-stage-capsule-store-v1/shared.json +132 -0
  49. package/contracts/fixtures/v4-stage-capsule-v1/shared.json +111 -0
  50. package/contracts/v4-canonical-contracts-v1.schema.json +182 -0
  51. package/contracts/v4-delivery-warrant-semantic-diff-report-v1.schema.json +284 -0
  52. package/contracts/v4-delivery-warrant-trace-v1.schema.json +215 -0
  53. package/contracts/v4-host-contract-v1.schema.json +135 -0
  54. package/contracts/v4-partial-mutation-recovery-v1.schema.json +168 -0
  55. package/contracts/v4-provider-operation-journal-v1.schema.json +381 -0
  56. package/contracts/v4-release-activation-shadow-v1.schema.json +186 -0
  57. package/contracts/v4-stable-publication-fence-v1.schema.json +125 -0
  58. package/contracts/v4-stage-capsule-resume-v1.schema.json +225 -0
  59. package/contracts/v4-stage-capsule-store-v1.schema.json +154 -0
  60. package/contracts/v4-stage-capsule-v1.schema.json +178 -0
  61. package/crates/buildchain-v4-bridge/Cargo.lock +213 -0
  62. package/crates/buildchain-v4-bridge/Cargo.toml +12 -0
  63. package/crates/buildchain-v4-bridge/src/main.rs +495 -0
  64. package/dist/site/agent-index.json +0 -4
  65. package/dist/site/artifact-schemas.json +0 -8
  66. package/dist/site/buildchain-contract.json +103 -397
  67. package/dist/site/buildchain-site.json +301 -388
  68. package/dist/site/capability-registry.json +9 -11
  69. package/dist/site/cli-registry.json +8 -53
  70. package/dist/site/controller-registry.json +48 -8
  71. package/dist/site/kfd-claims.json +129 -167
  72. package/dist/site/kfd-upstream-aggregate.json +9 -9
  73. package/dist/site/manual-registry.json +18 -49
  74. package/dist/site/node-api-registry.json +4345 -7345
  75. package/dist/site/page-registry.json +278 -346
  76. package/dist/site/public-surface-audit.json +85 -404
  77. package/dist/site/publication-authority-registry.json +11 -1
  78. package/dist/site/publication-registry.json +3 -6
  79. package/dist/site/release-provenance.json +3 -9
  80. package/dist/site/schemas/kfd-support-projection-v1.schema.json +106 -0
  81. package/dist/site/site-manifest.json +21 -40
  82. package/dist/site/workflow-registry.json +78 -35
  83. package/docs/MAP.md +8 -5
  84. package/docs/auditable-demo.md +2 -2
  85. package/docs/aws-us-elastic-runner-burst-plane.md +7 -59
  86. package/docs/cli-reference.md +15 -213
  87. package/docs/cli.md +14 -14
  88. package/docs/dev-alpha-candidate-patrol.md +3 -11
  89. package/docs/dev-delivery-warrant.md +29 -194
  90. package/docs/engineering-housekeeper.md +12 -4
  91. package/docs/getting-started.md +1 -1
  92. package/docs/install.md +5 -5
  93. package/docs/kfd-support.md +2 -7
  94. package/docs/lifecycle-protocol.md +2 -4
  95. package/docs/node-api-reference.md +414 -554
  96. package/docs/release-flow.md +31 -31
  97. package/docs/release-governance.md +16 -28
  98. package/docs/release-passport.md +2 -15
  99. package/docs/release-tail-contract.md +13 -6
  100. package/docs/release-tail-provider-plane.md +13 -19
  101. package/docs/release-train.md +4 -4
  102. package/docs/reusable-build-surface.md +14 -27
  103. package/docs/site-bundle-contract.md +1 -5
  104. package/docs/stable-candidate-patrol.md +7 -7
  105. package/docs/v4-canonical-contracts.md +92 -0
  106. package/docs/v4-delivery-warrant-read-candidate.md +73 -0
  107. package/docs/v4-delivery-warrant-semantic-diff.md +75 -0
  108. package/docs/v4-production-release.md +63 -0
  109. package/docs/v4-stage-capsule.md +193 -0
  110. package/docs/versioning.md +3 -5
  111. package/package.json +19 -19
  112. package/packages/core/adopter-delivery-gate.js +576 -0
  113. package/packages/core/adopter-delivery-json.js +66 -0
  114. package/packages/core/artifact-signing.js +0 -61
  115. package/packages/core/artifact-verification-envelope.js +5 -5
  116. package/packages/core/build-facts.js +12 -4
  117. package/packages/core/buildchain-agent-manuals.js +0 -2
  118. package/packages/core/buildchain-channel-identity.js +3 -2
  119. package/packages/core/buildchain-config.js +4 -69
  120. package/packages/core/buildchain-contract.js +232 -46
  121. package/packages/core/buildchain-kfd-claims.js +1 -1
  122. package/packages/core/channel-candidate.js +0 -8
  123. package/packages/core/channel-promotion-baseline.js +0 -52
  124. package/packages/core/controller-evidence.js +3 -4
  125. package/packages/core/dev-alpha-candidate-selection.js +2 -10
  126. package/packages/core/dev-delivery-warrant-cancellation.js +0 -1
  127. package/packages/core/dev-delivery-warrant-settlement.js +20 -67
  128. package/packages/core/dev-delivery-warrant.js +50 -138
  129. package/packages/core/diagnostics.js +3 -8
  130. package/packages/core/github-governance-authority.js +3 -1
  131. package/packages/core/kfd-adopter-category-driver.js +167 -0
  132. package/packages/core/kfd-adopter-manifest.js +47 -46
  133. package/packages/core/kfd-gate.js +15 -45
  134. package/packages/core/paper-agent-entry.js +7 -16
  135. package/packages/core/paper-fleet.js +2 -2
  136. package/packages/core/paper-repository.js +2 -3
  137. package/packages/core/paper-scaffold-content.js +2 -23
  138. package/packages/core/paper.js +11 -43
  139. package/packages/core/publication-reproducibility.js +4 -4
  140. package/packages/core/release-line-bootstrap.js +21 -8
  141. package/packages/core/release-passport-contract.js +5 -5
  142. package/packages/core/release-passport.js +20 -131
  143. package/packages/core/self-dogfood-version.js +48 -35
  144. package/packages/core/spawn-command.js +232 -8
  145. package/packages/core/v4-adopter-delivery-parity.js +219 -0
  146. package/packages/core/v4-canonical-contracts.js +284 -0
  147. package/packages/core/v4-delivery-warrant-fixture-runner.js +371 -0
  148. package/packages/core/v4-delivery-warrant-read-candidate.js +279 -0
  149. package/packages/core/v4-delivery-warrant-semantic-diff-gate.js +352 -0
  150. package/packages/core/v4-delivery-warrant-shadow-adapter.js +447 -0
  151. package/packages/core/v4-partial-mutation-recovery-qualification.js +395 -0
  152. package/packages/core/v4-platform-stage-checkpoints.js +471 -0
  153. package/packages/core/v4-provider-operation-journal.js +531 -0
  154. package/packages/core/v4-provider-readback-idempotency.js +394 -0
  155. package/packages/core/v4-release-activation-shadow.js +519 -0
  156. package/packages/core/v4-stable-publication-fence.js +357 -0
  157. package/packages/core/v4-stage-capsule-local-store.js +426 -0
  158. package/packages/core/v4-stage-capsule-qualification-campaign.js +512 -0
  159. package/packages/core/v4-stage-capsule-qualification.js +456 -0
  160. package/packages/core/v4-stage-capsule-resume-planner.js +397 -0
  161. package/packages/core/v4-stage-capsule-store.js +401 -0
  162. package/packages/core/v4-stage-capsule.js +319 -0
  163. package/scripts/assemble-self-publication-admission.mjs +1 -1
  164. package/scripts/audit-publication-control-plane.mjs +5 -5
  165. package/scripts/auditable-demo-bundle-verification.mjs +3 -2
  166. package/scripts/auditable-demo-platform.mjs +2 -2
  167. package/scripts/auditable-demo-renditions.mjs +1 -1
  168. package/scripts/auditable-demo.mjs +2 -2
  169. package/scripts/aws-macos-jit-controller-core.mjs +13 -346
  170. package/scripts/aws-macos-jit-controller-runtime.mjs +2 -210
  171. package/scripts/aws-macos-jit-controller.mjs +37 -275
  172. package/scripts/aws-macos-jit-core.mjs +3 -41
  173. package/scripts/aws-macos-jit.mjs +0 -12
  174. package/scripts/aws-windows-jit-campaign.mjs +2 -2
  175. package/scripts/aws-windows-jit-controller-core.mjs +1 -1
  176. package/scripts/aws-windows-jit-controller.mjs +3 -3
  177. package/scripts/build-contract-core.mjs +3 -8
  178. package/scripts/build-standalone-binary.mjs +3 -14
  179. package/scripts/buildchain-channel-router.mjs +4 -0
  180. package/scripts/buildchain-cli-help.mjs +7 -16
  181. package/scripts/buildchain-contract-lock.mjs +0 -6
  182. package/scripts/check-action-bundles.mjs +3 -4
  183. package/scripts/check-internal-architecture.mjs +7 -6
  184. package/scripts/check-inventory.mjs +30 -25
  185. package/scripts/check-v4-public-dogfood-contract.mjs +270 -0
  186. package/scripts/create-release-bundle.mjs +8 -4
  187. package/scripts/dev-alpha-candidate-patrol.mjs +1 -22
  188. package/scripts/dev-delivery-proof.mjs +2 -64
  189. package/scripts/dev-delivery-warrant.mjs +61 -86
  190. package/scripts/dev-pr-auto-merge.mjs +4 -30
  191. package/scripts/dev-pr-delivery-warrant.mjs +0 -55
  192. package/scripts/dispatch-artifact-signing-authority.mjs +7 -4
  193. package/scripts/generate-buildchain-kfd-witnesses.mjs +94 -88
  194. package/scripts/generate-channel-build-workflow.mjs +54 -24
  195. package/scripts/generate-channel-promotion-workflow.mjs +15 -6
  196. package/scripts/generate-site-bundle.mjs +8 -27
  197. package/scripts/git-fetch-process-tree.mjs +34 -1
  198. package/scripts/init-repo.mjs +64 -159
  199. package/scripts/inspect-artifact-signing-requests.mjs +0 -6
  200. package/scripts/locked-source-checkout.mjs +3 -4
  201. package/scripts/maintainability-metrics.mjs +8 -2
  202. package/scripts/npm-publish-dry-run.mjs +2 -2
  203. package/scripts/npm-publish-transaction.mjs +2 -2
  204. package/scripts/publication-commit-evidence.mjs +23 -69
  205. package/scripts/release-candidate-resolver.mjs +11 -17
  206. package/scripts/release-tail.mjs +3 -49
  207. package/scripts/resume-from-candidate-run.mjs +9 -123
  208. package/scripts/run-lifecycle-core.mjs +12 -13
  209. package/scripts/seal-artifact-signing-requests.mjs +17 -21
  210. package/scripts/site-capability-metadata.mjs +3 -20
  211. package/scripts/stable-candidate-qualification.mjs +10 -10
  212. package/scripts/v4-architecture.mjs +12 -2
  213. package/scripts/v4-bridge-bootstrap.mjs +141 -0
  214. package/scripts/v4-bridge-evidence.mjs +141 -0
  215. package/scripts/v4-host-adapter.mjs +182 -0
  216. package/scripts/v4-platform-stage-checkpoint-rehearsal.mjs +207 -0
  217. package/scripts/v4-stage-capsule-qualification.mjs +528 -0
  218. package/scripts/v4-stage-capsule-resume-rehearsal.mjs +57 -0
  219. package/scripts/v4-warrant-shadow-plan.mjs +601 -0
  220. package/scripts/verify-golden-path.mjs +5 -5
  221. package/scripts/web-surface-cloudfront-rewrite.mjs +15 -15
  222. package/scripts/web-surface-core.mjs +2 -8
  223. package/scripts/workflow-call-contract.mjs +5 -184
  224. package/contracts/dev-delivery-authority-v2.schema.json +0 -253
  225. package/contracts/fixtures/kfd-adopter-release-v1/kfd-4-perspective.json +0 -16
  226. package/contracts/fixtures/kfd-adopter-release-v1/kfd-4-replay.json +0 -57
  227. package/contracts/fixtures/next-development-transition-v1/anchored-manual-waiting.json +0 -47
  228. package/contracts/fixtures/next-development-transition-v1/semver-auto-planned.json +0 -47
  229. package/contracts/fixtures/next-development-transition-v1/version-model-cases.json +0 -40
  230. package/contracts/next-development-request-v1.schema.json +0 -66
  231. package/contracts/next-development-transition-v1.schema.json +0 -292
  232. package/contracts/publication-rehearsal-capsule-v1.schema.json +0 -173
  233. package/dist/site/schemas/dev-delivery-authority-v2.schema.json +0 -442
  234. package/dist/site/schemas/publication-rehearsal-capsule-v1.schema.json +0 -269
  235. package/dist/site/schemas/release-tail-capabilities-v1.schema.json +0 -353
  236. package/dist/site/schemas/release-tail-provider-bindings-v1.schema.json +0 -94
  237. package/docs/dev-delivery-qualification-landing-adr.md +0 -137
  238. package/docs/next-development-transition.md +0 -119
  239. package/docs/publication-rehearsal.md +0 -94
  240. package/packages/core/buildchain-compatibility-proof.js +0 -631
  241. package/packages/core/dev-delivery-authority.js +0 -996
  242. package/packages/core/dev-delivery-native-proof.js +0 -418
  243. package/packages/core/dev-delivery-warrant-shadow.js +0 -502
  244. package/packages/core/next-development-candidate-reservation.js +0 -186
  245. package/packages/core/next-development-controller.js +0 -726
  246. package/packages/core/next-development-projection.js +0 -288
  247. package/packages/core/next-development-transition.js +0 -738
  248. package/packages/core/publication-rehearsal-projection.js +0 -173
  249. package/packages/core/publication-rehearsal-runtime.js +0 -921
  250. package/scripts/aws-macos-jit-instance-rehydrate.mjs +0 -260
  251. package/scripts/aws-macos-jit-source-rebind.mjs +0 -724
  252. package/scripts/dev-delivery-authority.mjs +0 -368
  253. package/scripts/dev-delivery-native-run.mjs +0 -235
  254. package/scripts/dev-delivery-two-phase-resume.mjs +0 -90
  255. package/scripts/dev-delivery-two-phase.mjs +0 -577
  256. package/scripts/generate-next-development-guidance.mjs +0 -49
  257. package/scripts/materialize-self-release-candidate-version.mjs +0 -137
  258. package/scripts/next-development-self-dogfood-harness.mjs +0 -409
  259. package/scripts/next-development-self-dogfood.mjs +0 -532
  260. package/scripts/next-development-transition.mjs +0 -47
  261. package/scripts/release-candidate-tail-reseal.mjs +0 -426
  262. package/templates/native-dev-delivery.yml +0 -86
@@ -3,7 +3,6 @@
3
3
  import { digest } from "./aws-runner-burst-core.mjs";
4
4
  import {
5
5
  MACOS_EC2_JIT,
6
- macosJitRegionConfig,
7
6
  macosJitRunnerLabel,
8
7
  macosJitRunnerLabels,
9
8
  } from "./aws-macos-jit-core.mjs";
@@ -56,17 +55,6 @@ function source(values) {
56
55
  };
57
56
  }
58
57
 
59
- function previousSource(values) {
60
- return {
61
- sha: exactSha(values.previousSourceSha),
62
- ref: exact(
63
- values.previousSourceRef || values.sourceRef,
64
- /^refs\/heads\/[A-Za-z0-9._/-]+$/,
65
- "previousSourceRef",
66
- ),
67
- };
68
- }
69
-
70
58
  function campaignId(value) {
71
59
  return exact(value, /^[a-z0-9][a-z0-9-]{2,47}$/, "campaignId");
72
60
  }
@@ -75,110 +63,15 @@ function qualificationId(value) {
75
63
  return exact(value, /^mac-(?:smoke-0[12]|full-01)$/, "qualificationId");
76
64
  }
77
65
 
78
- function terminalFailureRunIds(value) {
79
- if (!Array.isArray(value) || value.length === 0) {
80
- throw new Error("terminalFailureRunIds must be a non-empty array");
81
- }
82
- const normalized = value.map((entry) =>
83
- exact(entry, /^[1-9]\d*$/, "terminalFailureRunId"),
84
- );
85
- if (new Set(normalized).size !== normalized.length) {
86
- throw new Error("terminalFailureRunIds must be unique");
87
- }
88
- return [...normalized].sort((left, right) => Number(left) - Number(right));
89
- }
90
-
91
- function preflightFailureRunIds(value) {
92
- if (!Array.isArray(value) || value.length === 0) {
93
- throw new Error("preflightFailureRunIds must be a non-empty array");
94
- }
95
- const normalized = value.map((entry) =>
96
- exact(entry, /^[1-9]\d*$/, "preflightFailureRunId"),
97
- );
98
- if (new Set(normalized).size !== normalized.length) {
99
- throw new Error("preflightFailureRunIds must be unique");
100
- }
101
- return [...normalized].sort((left, right) => Number(left) - Number(right));
102
- }
103
-
104
- function successfulRunIds(value) {
105
- if (value === undefined) return [];
106
- if (!Array.isArray(value)) {
107
- throw new Error("successfulRunIds must be an array");
108
- }
109
- const normalized = value.map((entry) =>
110
- exact(entry, /^[1-9]\d*$/, "successfulRunId"),
111
- );
112
- if (new Set(normalized).size !== normalized.length) {
113
- throw new Error("successfulRunIds must be unique");
114
- }
115
- return [...normalized].sort((left, right) => Number(left) - Number(right));
116
- }
117
-
118
- function startupFailureRuns(value) {
119
- if (value === undefined) return [];
120
- if (!Array.isArray(value)) {
121
- throw new Error("startupFailureRuns must be an array");
122
- }
123
- const normalized = value.map((entry) => ({
124
- runId: exact(entry?.runId, /^[1-9]\d*$/, "startupFailureRunId"),
125
- sourceSha: exactSha(entry?.sourceSha),
126
- }));
127
- if (
128
- new Set(normalized.map(({ runId }) => runId)).size !== normalized.length
129
- ) {
130
- throw new Error("startupFailureRuns must use unique run ids");
131
- }
132
- return [...normalized].sort(
133
- (left, right) => Number(left.runId) - Number(right.runId),
134
- );
135
- }
136
-
137
- function historicalTerminalFailureRuns(value) {
138
- if (value === undefined) return [];
139
- if (!Array.isArray(value)) {
140
- throw new Error("historicalTerminalFailureRuns must be an array");
141
- }
142
- const normalized = value.map((entry) => ({
143
- runId: exact(entry?.runId, /^[1-9]\d*$/, "historicalTerminalFailureRunId"),
144
- sourceSha: exactSha(entry?.sourceSha),
145
- }));
146
- if (
147
- new Set(normalized.map(({ runId }) => runId)).size !== normalized.length
148
- ) {
149
- throw new Error("historicalTerminalFailureRuns must use unique run ids");
150
- }
151
- return [...normalized].sort(
152
- (left, right) => Number(left.runId) - Number(right.runId),
153
- );
154
- }
155
-
156
- function historicalPreflightFailureRuns(value) {
157
- if (value === undefined) return [];
158
- if (!Array.isArray(value)) {
159
- throw new Error("historicalPreflightFailureRuns must be an array");
160
- }
161
- const normalized = value.map((entry) => ({
162
- runId: exact(entry?.runId, /^[1-9]\d*$/, "historicalPreflightFailureRunId"),
163
- sourceSha: exactSha(entry?.sourceSha),
164
- }));
165
- if (
166
- new Set(normalized.map(({ runId }) => runId)).size !== normalized.length
167
- ) {
168
- throw new Error("historicalPreflightFailureRuns must use unique run ids");
169
- }
170
- return [...normalized].sort(
171
- (left, right) => Number(left.runId) - Number(right.runId),
172
- );
173
- }
174
-
175
66
  function commonAws(values) {
176
67
  const region = exact(
177
68
  values.region || MACOS_EC2_JIT.region,
178
69
  /^us-[a-z]+-\d$/,
179
70
  "region",
180
71
  );
181
- const regionConfig = macosJitRegionConfig(region);
72
+ if (region !== MACOS_EC2_JIT.region) {
73
+ throw new Error(`region must be ${MACOS_EC2_JIT.region}`);
74
+ }
182
75
  const instanceType = exact(
183
76
  values.instanceType || MACOS_EC2_JIT.instanceType,
184
77
  /^mac2\.metal$/,
@@ -187,18 +80,13 @@ function commonAws(values) {
187
80
  if (instanceType !== MACOS_EC2_JIT.instanceType) {
188
81
  throw new Error(`instanceType must be ${MACOS_EC2_JIT.instanceType}`);
189
82
  }
190
- const availabilityZone = exact(
191
- values.availabilityZone,
192
- /^us-[a-z]+-\d[a-z]$/,
193
- "availabilityZone",
194
- );
195
- if (!availabilityZone.startsWith(region)) {
196
- throw new Error(`availabilityZone must belong to ${region}`);
197
- }
198
83
  return {
199
84
  region,
200
- availabilityZone,
201
- controlPlaneStack: regionConfig.stack,
85
+ availabilityZone: exact(
86
+ values.availabilityZone,
87
+ /^us-[a-z]+-\d[a-z]$/,
88
+ "availabilityZone",
89
+ ),
202
90
  instanceType,
203
91
  amiId: exact(values.amiId, /^ami-[0-9a-f]+$/, "amiId"),
204
92
  amiName: exact(values.amiName, /^[A-Za-z0-9._-]+$/, "amiName"),
@@ -235,7 +123,6 @@ export function createMacosJitCampaignPlan(values = {}) {
235
123
  const id = campaignId(values.campaignId);
236
124
  const boundSource = source(values);
237
125
  const aws = commonAws(values);
238
- const regionConfig = macosJitRegionConfig(aws.region);
239
126
  const tags = ownershipTags({ id, sourceSha: boundSource.sha });
240
127
  const createdAt = iso(values.createdAt, "createdAt");
241
128
  const plan = {
@@ -243,13 +130,6 @@ export function createMacosJitCampaignPlan(values = {}) {
243
130
  contract: AWS_MACOS_JIT_CONTROLLER_CONTRACT,
244
131
  kind: "campaign-launch-plan",
245
132
  repository: repository(values.repository),
246
- account: {
247
- id: exact(values.accountId, /^\d{12}$/, "accountId"),
248
- },
249
- github: {
250
- workflowId: MACOS_EC2_JIT.workflowId,
251
- requiredState: "disabled_manually",
252
- },
253
133
  campaign: { id, createdAt },
254
134
  source: boundSource,
255
135
  aws: {
@@ -278,232 +158,18 @@ export function createMacosJitCampaignPlan(values = {}) {
278
158
  exactSourceRequired: true,
279
159
  activeHostCeiling: MACOS_EC2_JIT.maxAcceptedHosts,
280
160
  activeInstanceCeiling: 1,
281
- awsPermissionSimulationRequiredBeforeAllocation: true,
161
+ awsDryRunRequiredBeforeAllocation: true,
282
162
  awsDryRunRequiredBeforeLaunch: true,
283
163
  retainHostOnInstanceLaunchFailure: true,
284
164
  minimumHostAllocationHours: MACOS_EC2_JIT.minimumHostAllocationHours,
285
165
  maximumHostAllocationHours: MACOS_EC2_JIT.maximumHostAllocationHours,
286
166
  cleanupOwner: "scheduled-card-scoped-reaper",
287
- budget: {
288
- name: regionConfig.budgetName,
289
- limitUsd: MACOS_EC2_JIT.budgetLimitUsd,
290
- metrics: ["UnblendedCost"],
291
- dimensionFilter: {
292
- usageTypes: regionConfig.budgetUsageTypes,
293
- operation: MACOS_EC2_JIT.budgetOperation,
294
- regions: regionConfig.budgetRegions,
295
- },
296
- requiredActualThresholds: [80, 95],
297
- },
298
- },
299
- };
300
- return { ...plan, digest: digest(plan) };
301
- }
302
-
303
- export function createMacosJitInstanceRehydratePlan(values = {}) {
304
- const id = campaignId(values.campaignId);
305
- const boundSource = source(values);
306
- const aws = commonAws(values);
307
- const regionConfig = macosJitRegionConfig(aws.region);
308
- const tags = ownershipTags({ id, sourceSha: boundSource.sha });
309
- const rehydrationId = exact(
310
- values.rehydrationId,
311
- /^r[1-9]\d{0,2}$/,
312
- "rehydrationId",
313
- );
314
- const instanceClientToken = `kungfu-mac-rehydrate-${id}-${rehydrationId}`;
315
- if (instanceClientToken.length > 64) {
316
- throw new Error("rehydration client token exceeds the EC2 limit");
317
- }
318
- const plan = {
319
- schemaVersion: 1,
320
- contract: AWS_MACOS_JIT_CONTROLLER_CONTRACT,
321
- kind: "instance-rehydrate-plan",
322
- repository: repository(values.repository),
323
- account: {
324
- id: exact(values.accountId, /^\d{12}$/, "accountId"),
325
- },
326
- github: {
327
- workflowId: MACOS_EC2_JIT.workflowId,
328
- requiredState: "disabled_manually",
329
- },
330
- campaign: { id },
331
- source: boundSource,
332
- aws: {
333
- ...aws,
334
- hostId: exact(values.hostId, /^h-[0-9a-f]+$/, "hostId"),
335
- hostAllocatedAt: iso(values.hostAllocatedAt, "hostAllocatedAt"),
336
- replacesInstanceId: exact(
337
- values.replacesInstanceId,
338
- /^i-[0-9a-f]+$/,
339
- "replacesInstanceId",
340
- ),
341
- rehydrationId,
342
- instanceTags: tags,
343
- volumeTags: tags,
344
- instanceClientToken,
345
- rootVolume: {
346
- deviceName: "/dev/sda1",
347
- deleteOnTermination: true,
348
- encrypted: true,
349
- volumeSizeGiB: 200,
350
- volumeType: "gp3",
351
- },
352
- metadata: {
353
- httpEndpoint: "enabled",
354
- httpTokens: "required",
355
- httpPutResponseHopLimit: 1,
356
- instanceMetadataTags: "disabled",
357
- },
358
- },
359
- safety: {
360
- applyMode: values.execute === true ? "execute" : "dry-run",
361
- exactSourceRequired: true,
362
- existingCampaignHostRequired: true,
363
- noHostAllocation: true,
364
- zeroActiveInstanceRequired: true,
365
- zeroRunnerResidueRequired: true,
366
- awsDryRunRequiredBeforeLaunch: true,
367
- cleanupOwner: "scheduled-card-scoped-reaper",
368
- budget: {
369
- name: regionConfig.budgetName,
370
- limitUsd: MACOS_EC2_JIT.budgetLimitUsd,
371
- metrics: ["UnblendedCost"],
372
- dimensionFilter: {
373
- usageTypes: regionConfig.budgetUsageTypes,
374
- operation: MACOS_EC2_JIT.budgetOperation,
375
- regions: regionConfig.budgetRegions,
376
- },
377
- requiredActualThresholds: [80, 95],
378
- },
379
167
  },
380
168
  };
381
169
  return { ...plan, digest: digest(plan) };
382
170
  }
383
171
 
384
- export function createMacosJitSourceRebindPlan(values = {}) {
385
- const id = campaignId(values.campaignId);
386
- const boundSource = source(values);
387
- const priorSource = previousSource(values);
388
- if (boundSource.sha === priorSource.sha) {
389
- throw new Error("sourceSha must differ from previousSourceSha");
390
- }
391
- if (boundSource.ref !== priorSource.ref) {
392
- throw new Error("sourceRef must remain unchanged during campaign rebind");
393
- }
394
- const aws = commonAws(values);
395
- const regionConfig = macosJitRegionConfig(aws.region);
396
- const workflowId = exact(values.workflowId, /^\d+$/, "workflowId");
397
- if (workflowId !== MACOS_EC2_JIT.workflowId) {
398
- throw new Error(`workflowId must be ${MACOS_EC2_JIT.workflowId}`);
399
- }
400
- const priorRunPolicy = values.priorRunPolicy || "unused";
401
- if (
402
- !["unused", "terminal-failure", "preflight-failure"].includes(
403
- priorRunPolicy,
404
- )
405
- ) {
406
- throw new Error(
407
- "priorRunPolicy must be unused, terminal-failure, or preflight-failure",
408
- );
409
- }
410
- const terminalRunIds =
411
- priorRunPolicy === "terminal-failure"
412
- ? terminalFailureRunIds(values.terminalFailureRunIds)
413
- : [];
414
- const preflightRunIds =
415
- priorRunPolicy === "preflight-failure"
416
- ? preflightFailureRunIds(values.preflightFailureRunIds)
417
- : [];
418
- const startupRuns = startupFailureRuns(values.startupFailureRuns);
419
- const historicalTerminalRuns = historicalTerminalFailureRuns(
420
- values.historicalTerminalFailureRuns,
421
- );
422
- const historicalPreflightRuns = historicalPreflightFailureRuns(
423
- values.historicalPreflightFailureRuns,
424
- );
425
- const successfulRuns = successfulRunIds(values.successfulRunIds);
426
- if (priorRunPolicy === "unused" && startupRuns.length !== 0) {
427
- throw new Error("startupFailureRuns require a failed priorRunPolicy");
428
- }
429
- if (priorRunPolicy === "unused" && historicalTerminalRuns.length !== 0) {
430
- throw new Error(
431
- "historicalTerminalFailureRuns require a failed priorRunPolicy",
432
- );
433
- }
434
- if (priorRunPolicy === "unused" && historicalPreflightRuns.length !== 0) {
435
- throw new Error(
436
- "historicalPreflightFailureRuns require a failed priorRunPolicy",
437
- );
438
- }
439
- if (priorRunPolicy === "unused" && successfulRuns.length !== 0) {
440
- throw new Error("successfulRunIds require a failed priorRunPolicy");
441
- }
442
- const plan = {
443
- schemaVersion: 1,
444
- contract: AWS_MACOS_JIT_CONTROLLER_CONTRACT,
445
- kind: "campaign-source-rebind-plan",
446
- repository: repository(values.repository),
447
- account: {
448
- id: exact(values.accountId, /^\d{12}$/, "accountId"),
449
- },
450
- campaign: { id },
451
- previousSource: priorSource,
452
- source: boundSource,
453
- github: {
454
- workflowId,
455
- requiredState: "disabled_manually",
456
- priorRunPolicy,
457
- terminalFailureRunIds: terminalRunIds,
458
- preflightFailureRunIds: preflightRunIds,
459
- startupFailureRuns: startupRuns,
460
- historicalTerminalFailureRuns: historicalTerminalRuns,
461
- historicalPreflightFailureRuns: historicalPreflightRuns,
462
- successfulRunIds: successfulRuns,
463
- },
464
- aws: {
465
- ...aws,
466
- hostId: exact(values.hostId, /^h-[0-9a-f]+$/, "hostId"),
467
- instanceId: exact(values.instanceId, /^i-[0-9a-f]+$/, "instanceId"),
468
- },
469
- safety: {
470
- applyMode: values.execute === true ? "execute" : "dry-run",
471
- noAllocation: true,
472
- noDispatch: true,
473
- forwardOnlySource: true,
474
- sameSourceRefRequired: true,
475
- workflowDisabledRequired: true,
476
- zeroPriorJobsRequired: priorRunPolicy === "unused",
477
- zeroPriorArtifactsRequired: priorRunPolicy === "unused",
478
- terminalFailureEvidencePreserved: priorRunPolicy === "terminal-failure",
479
- preflightFailureEvidencePreserved: priorRunPolicy === "preflight-failure",
480
- startupFailureEvidencePreserved: startupRuns.length !== 0,
481
- historicalTerminalFailureEvidencePreserved:
482
- historicalTerminalRuns.length !== 0,
483
- historicalPreflightFailureEvidencePreserved:
484
- historicalPreflightRuns.length !== 0,
485
- successfulRunEvidencePreserved: successfulRuns.length !== 0,
486
- zeroJitResidueRequired: true,
487
- zeroEvidenceRequired: true,
488
- exactCampaignResourcesRequired: true,
489
- compensatedRollbackRequired: true,
490
- budget: {
491
- name: regionConfig.budgetName,
492
- limitUsd: MACOS_EC2_JIT.budgetLimitUsd,
493
- metrics: ["UnblendedCost"],
494
- dimensionFilter: {
495
- usageTypes: regionConfig.budgetUsageTypes,
496
- operation: MACOS_EC2_JIT.budgetOperation,
497
- regions: regionConfig.budgetRegions,
498
- },
499
- requiredActualThresholds: [80, 95],
500
- },
501
- },
502
- };
503
- return { ...plan, digest: digest(plan) };
504
- }
505
-
506
- export function macosAllocateHostsArgs(plan) {
172
+ export function macosAllocateHostsArgs(plan, { dryRun = false } = {}) {
507
173
  if (
508
174
  plan?.contract !== AWS_MACOS_JIT_CONTROLLER_CONTRACT ||
509
175
  plan.kind !== "campaign-launch-plan"
@@ -530,15 +196,16 @@ export function macosAllocateHostsArgs(plan) {
530
196
  "--output",
531
197
  "json",
532
198
  ];
199
+ if (dryRun) args.splice(2, 0, "--dry-run");
533
200
  return args;
534
201
  }
535
202
 
536
203
  export function macosRunInstancesArgs(plan, { hostId, dryRun = false } = {}) {
537
204
  if (
538
205
  plan?.contract !== AWS_MACOS_JIT_CONTROLLER_CONTRACT ||
539
- !["campaign-launch-plan", "instance-rehydrate-plan"].includes(plan.kind)
206
+ plan.kind !== "campaign-launch-plan"
540
207
  ) {
541
- throw new Error("macOS JIT instance launch plan contract is invalid");
208
+ throw new Error("macOS JIT campaign launch plan contract is invalid");
542
209
  }
543
210
  const resolvedHostId = exact(hostId, /^h-[0-9a-f]+$/, "hostId");
544
211
  const args = [
@@ -1,9 +1,9 @@
1
1
  // SPDX-License-Identifier: Apache-2.0
2
2
 
3
- import { spawnSync } from "node:child_process";
3
+ import { spawnSyncCommand } from "../packages/core/spawn-command.js";
4
4
 
5
5
  export function commandResult(command, args, options = {}) {
6
- const result = spawnSync(command, args, {
6
+ const result = spawnSyncCommand(command, args, {
7
7
  encoding: "utf8",
8
8
  maxBuffer: 16 * 1024 * 1024,
9
9
  stdio: [options.input === undefined ? "ignore" : "pipe", "pipe", "pipe"],
@@ -80,211 +80,3 @@ export function assertDryRun(result, label) {
80
80
  throw new Error(`${label} did not return DryRunOperation`);
81
81
  }
82
82
  }
83
-
84
- export function assertAllowedPolicySimulation(
85
- plan,
86
- profile,
87
- principalArn,
88
- actionName,
89
- ) {
90
- if (!/^arn:aws:iam::\d{12}:(?:user|role)\/.+/.test(principalArn || "")) {
91
- throw new Error(
92
- "AWS allocation policy simulation requires an IAM user or role principal ARN",
93
- );
94
- }
95
- const contextEntries = [
96
- `ContextKeyName=aws:RequestedRegion,ContextKeyValues=${plan.aws.region},ContextKeyType=string`,
97
- ...plan.aws.hostTags.map(
98
- ({ Key, Value }) =>
99
- `ContextKeyName=aws:RequestTag/${Key},ContextKeyValues=${Value},ContextKeyType=string`,
100
- ),
101
- ];
102
- const evaluation = awsJson(
103
- plan,
104
- profile,
105
- [
106
- "iam",
107
- "simulate-principal-policy",
108
- "--policy-source-arn",
109
- principalArn,
110
- "--action-names",
111
- actionName,
112
- "--resource-arns",
113
- "*",
114
- "--context-entries",
115
- ...contextEntries,
116
- "--output",
117
- "json",
118
- ],
119
- `${actionName} IAM policy simulation`,
120
- ).EvaluationResults?.[0];
121
- if (
122
- evaluation?.EvalActionName !== actionName ||
123
- evaluation?.EvalDecision !== "allowed" ||
124
- (evaluation.MissingContextValues || []).length !== 0
125
- ) {
126
- throw new Error(`${actionName} IAM policy simulation did not allow allocation`);
127
- }
128
- return {
129
- actionName,
130
- decision: evaluation.EvalDecision,
131
- principalArn,
132
- };
133
- }
134
-
135
- function sameJson(left, right) {
136
- return JSON.stringify(left) === JSON.stringify(right);
137
- }
138
-
139
- function sameDimensionSet(observed, expected) {
140
- return (
141
- Array.isArray(observed) &&
142
- observed.length === expected.length &&
143
- expected.every((entry) =>
144
- observed.some((candidate) => sameJson(candidate, entry)),
145
- )
146
- );
147
- }
148
-
149
- export function assertMacosBudgetLaunchGate(plan, profile) {
150
- const identity = awsJson(
151
- plan,
152
- profile,
153
- ["sts", "get-caller-identity", "--output", "json"],
154
- "AWS account identity preflight",
155
- );
156
- if (identity.Account !== plan.account.id) {
157
- throw new Error("AWS account identity mismatch");
158
- }
159
- const workflow = ghJson(
160
- [
161
- "api",
162
- `repos/${plan.repository}/actions/workflows/${plan.github.workflowId}`,
163
- ],
164
- undefined,
165
- "GitHub macOS workflow preflight",
166
- );
167
- if (workflow.state !== plan.github.requiredState) {
168
- throw new Error(
169
- `macOS workflow must be ${plan.github.requiredState}; observed ${workflow.state || "unknown"}`,
170
- );
171
- }
172
- const stack = awsJson(
173
- plan,
174
- profile,
175
- [
176
- "cloudformation",
177
- "describe-stacks",
178
- "--stack-name",
179
- plan.aws.controlPlaneStack,
180
- "--output",
181
- "json",
182
- ],
183
- "macOS control-plane stack preflight",
184
- ).Stacks?.[0];
185
- if (!stack || !/(?:CREATE|UPDATE)_COMPLETE$/.test(stack.StackStatus || "")) {
186
- throw new Error("macOS control-plane stack is not complete");
187
- }
188
- const topicArn = (stack.Outputs || []).find(
189
- (entry) => entry.OutputKey === "KillSwitchTopic",
190
- )?.OutputValue;
191
- if (!/^arn:aws:sns:/.test(String(topicArn || ""))) {
192
- throw new Error("macOS Budget kill-switch topic readback failed");
193
- }
194
- const budget = awsJson(
195
- plan,
196
- profile,
197
- [
198
- "budgets",
199
- "describe-budget",
200
- "--account-id",
201
- plan.account.id,
202
- "--budget-name",
203
- plan.safety.budget.name,
204
- "--show-filter-expression",
205
- "--output",
206
- "json",
207
- ],
208
- "macOS Budget preflight",
209
- ).Budget;
210
- const expectedDimensions = [
211
- ["USAGE_TYPE", plan.safety.budget.dimensionFilter.usageTypes],
212
- ["OPERATION", [plan.safety.budget.dimensionFilter.operation]],
213
- ["REGION", plan.safety.budget.dimensionFilter.regions],
214
- ].map(([Key, Values]) => ({
215
- Dimensions: { Key, Values, MatchOptions: ["EQUALS"] },
216
- }));
217
- if (
218
- !budget ||
219
- budget.BudgetName !== plan.safety.budget.name ||
220
- Number(budget.BudgetLimit?.Amount) !== plan.safety.budget.limitUsd ||
221
- budget.BudgetLimit?.Unit !== "USD" ||
222
- budget.BudgetType !== "COST" ||
223
- !sameJson(budget.Metrics, plan.safety.budget.metrics) ||
224
- !sameDimensionSet(budget.FilterExpression?.And, expectedDimensions)
225
- ) {
226
- throw new Error("macOS Budget identity or dimension filter mismatch");
227
- }
228
- const notifications = awsJson(
229
- plan,
230
- profile,
231
- [
232
- "budgets",
233
- "describe-notifications-for-budget",
234
- "--account-id",
235
- plan.account.id,
236
- "--budget-name",
237
- plan.safety.budget.name,
238
- "--output",
239
- "json",
240
- ],
241
- "macOS Budget notifications preflight",
242
- ).Notifications;
243
- const thresholds = (notifications || [])
244
- .filter(
245
- (entry) =>
246
- entry.NotificationType === "ACTUAL" &&
247
- (entry.ThresholdType || "PERCENTAGE") === "PERCENTAGE",
248
- )
249
- .map((entry) => Number(entry.Threshold))
250
- .sort((left, right) => left - right);
251
- if (!sameJson(thresholds, plan.safety.budget.requiredActualThresholds)) {
252
- throw new Error("macOS Budget notification thresholds mismatch");
253
- }
254
- for (const notification of notifications) {
255
- const subscribers = awsJson(
256
- plan,
257
- profile,
258
- [
259
- "budgets",
260
- "describe-subscribers-for-notification",
261
- "--account-id",
262
- plan.account.id,
263
- "--budget-name",
264
- plan.safety.budget.name,
265
- "--notification",
266
- JSON.stringify(notification),
267
- "--output",
268
- "json",
269
- ],
270
- `macOS Budget ${notification.Threshold}% subscribers preflight`,
271
- ).Subscribers;
272
- if (
273
- !(subscribers || []).some(
274
- (entry) =>
275
- entry.SubscriptionType === "SNS" && entry.Address === topicArn,
276
- )
277
- ) {
278
- throw new Error("macOS Budget SNS subscriber mismatch");
279
- }
280
- }
281
- return {
282
- accountId: identity.Account,
283
- principalArn: identity.Arn,
284
- workflowState: workflow.state,
285
- stackStatus: stack.StackStatus,
286
- budgetName: budget.BudgetName,
287
- budgetDimensionFilter: plan.safety.budget.dimensionFilter,
288
- budgetThresholds: thresholds,
289
- };
290
- }