@empire-builder-kit/nx 1.0.0-rc.30 → 1.0.0-rc.31

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 (261) hide show
  1. package/executors.json +8 -3
  2. package/package.json +10 -7
  3. package/src/aws/child-environment.d.ts +10 -0
  4. package/src/aws/child-environment.js +77 -0
  5. package/src/aws/child-environment.js.map +1 -0
  6. package/src/aws/identity.d.ts +13 -0
  7. package/src/aws/identity.js +41 -0
  8. package/src/aws/identity.js.map +1 -0
  9. package/src/aws-authority/index.d.ts +1 -0
  10. package/src/aws-authority/index.js +5 -0
  11. package/src/aws-authority/index.js.map +1 -0
  12. package/src/aws-authority/stage-authority.d.ts +238 -0
  13. package/src/aws-authority/stage-authority.js +2836 -0
  14. package/src/aws-authority/stage-authority.js.map +1 -0
  15. package/src/developer-agent/generator-planning.js +4 -4
  16. package/src/developer-agent/generator-planning.js.map +1 -1
  17. package/src/developer-agent/operation-registry.js +14 -6
  18. package/src/developer-agent/operation-registry.js.map +1 -1
  19. package/src/executors/deployment-plan/deployment-plan.js +1 -1
  20. package/src/executors/deployment-plan/deployment-plan.js.map +1 -1
  21. package/src/executors/foundation-preflight/foundation-preflight.d.ts +0 -1
  22. package/src/executors/foundation-preflight/foundation-preflight.js.map +1 -1
  23. package/src/executors/foundation-preflight/schema.json +0 -5
  24. package/src/executors/foundation-profile-preflight/foundation-profile-preflight.d.ts +12 -3
  25. package/src/executors/foundation-profile-preflight/foundation-profile-preflight.js +95 -32
  26. package/src/executors/foundation-profile-preflight/foundation-profile-preflight.js.map +1 -1
  27. package/src/executors/foundation-profile-preflight/production-database-safeguard.d.ts +64 -0
  28. package/src/executors/foundation-profile-preflight/production-database-safeguard.js +221 -0
  29. package/src/executors/foundation-profile-preflight/production-database-safeguard.js.map +1 -0
  30. package/src/executors/foundation-profile-preflight/schema.json +1 -6
  31. package/src/executors/foundation-remove-preflight/foundation-remove-preflight.d.ts +11 -1
  32. package/src/executors/foundation-remove-preflight/foundation-remove-preflight.js +44 -21
  33. package/src/executors/foundation-remove-preflight/foundation-remove-preflight.js.map +1 -1
  34. package/src/executors/foundation-remove-preflight/schema.json +6 -1
  35. package/src/executors/infrastructure-plan/infrastructure-plan.d.ts +25 -369
  36. package/src/executors/infrastructure-plan/infrastructure-plan.js +222 -5472
  37. package/src/executors/infrastructure-plan/infrastructure-plan.js.map +1 -1
  38. package/src/executors/infrastructure-plan/schema.json +8 -64
  39. package/src/executors/local-deploy/federated-credential-transport.d.ts +48 -0
  40. package/src/executors/local-deploy/federated-credential-transport.js +252 -0
  41. package/src/executors/local-deploy/federated-credential-transport.js.map +1 -0
  42. package/src/executors/local-deploy/local-approval.d.ts +4 -9
  43. package/src/executors/local-deploy/local-approval.js +3 -7
  44. package/src/executors/local-deploy/local-approval.js.map +1 -1
  45. package/src/executors/local-deploy/local-deploy.d.ts +58 -43
  46. package/src/executors/local-deploy/local-deploy.js +938 -804
  47. package/src/executors/local-deploy/local-deploy.js.map +1 -1
  48. package/src/executors/local-deploy/schema.json +3 -20
  49. package/src/executors/refresh/refresh.js +2 -2
  50. package/src/executors/refresh/refresh.js.map +1 -1
  51. package/src/executors/remove/remove.js +2 -2
  52. package/src/executors/remove/remove.js.map +1 -1
  53. package/src/executors/router-waf-rehearsal/router-waf-rehearsal.d.ts +25 -1
  54. package/src/executors/router-waf-rehearsal/router-waf-rehearsal.js +905 -22
  55. package/src/executors/router-waf-rehearsal/router-waf-rehearsal.js.map +1 -1
  56. package/src/executors/router-waf-rehearsal/schema.json +17 -2
  57. package/src/executors/secret-initialize/schema.json +42 -0
  58. package/src/executors/secret-initialize/secret-initialize.d.ts +30 -0
  59. package/src/executors/secret-initialize/secret-initialize.js +229 -0
  60. package/src/executors/secret-initialize/secret-initialize.js.map +1 -0
  61. package/src/executors/secret-metadata/secret-metadata.d.ts +2 -1
  62. package/src/executors/secret-metadata/secret-metadata.js +62 -29
  63. package/src/executors/secret-metadata/secret-metadata.js.map +1 -1
  64. package/src/executors/sst-lifecycle/generated-secret-retirement.d.ts +53 -0
  65. package/src/executors/sst-lifecycle/generated-secret-retirement.js +192 -0
  66. package/src/executors/sst-lifecycle/generated-secret-retirement.js.map +1 -0
  67. package/src/executors/sst-lifecycle/governed-native-operation.d.ts +46 -0
  68. package/src/executors/sst-lifecycle/governed-native-operation.js +153 -0
  69. package/src/executors/sst-lifecycle/governed-native-operation.js.map +1 -0
  70. package/src/executors/sst-lifecycle/run-sst-command.d.ts +25 -7
  71. package/src/executors/sst-lifecycle/run-sst-command.js +459 -89
  72. package/src/executors/sst-lifecycle/run-sst-command.js.map +1 -1
  73. package/src/executors/sst-lifecycle/schema.json +0 -5
  74. package/src/executors/sst-lifecycle/secret-preflight.d.ts +2 -0
  75. package/src/executors/sst-lifecycle/secret-preflight.js +13 -7
  76. package/src/executors/sst-lifecycle/secret-preflight.js.map +1 -1
  77. package/src/executors/unlock/unlock.js +2 -2
  78. package/src/executors/unlock/unlock.js.map +1 -1
  79. package/src/foundation/router-waf-rate-rehearsal.d.ts +97 -0
  80. package/src/foundation/router-waf-rate-rehearsal.js +210 -0
  81. package/src/foundation/router-waf-rate-rehearsal.js.map +1 -0
  82. package/src/foundation/router-waf.d.ts +59 -9
  83. package/src/foundation/router-waf.js +427 -32
  84. package/src/foundation/router-waf.js.map +1 -1
  85. package/src/foundation/stages.d.ts +1 -0
  86. package/src/foundation/stages.js +11 -0
  87. package/src/foundation/stages.js.map +1 -1
  88. package/src/generators/formatting.d.ts +8 -0
  89. package/src/generators/formatting.js +118 -0
  90. package/src/generators/formatting.js.map +1 -0
  91. package/src/generators/function/function.js +37 -17
  92. package/src/generators/function/function.js.map +1 -1
  93. package/src/generators/post-generation-install.js +2 -2
  94. package/src/generators/post-generation-install.js.map +1 -1
  95. package/src/generators/preset/generator.js +22 -7
  96. package/src/generators/preset/generator.js.map +1 -1
  97. package/src/generators/preset/workspace-files-developer-agent.js +15 -7
  98. package/src/generators/preset/workspace-files-developer-agent.js.map +1 -1
  99. package/src/generators/preset/workspace-files-ecr-inspector.d.ts +18 -0
  100. package/src/generators/preset/workspace-files-ecr-inspector.js +298 -0
  101. package/src/generators/preset/workspace-files-ecr-inspector.js.map +1 -0
  102. package/src/generators/preset/workspace-files-performance-cost.d.ts +2 -2
  103. package/src/generators/preset/workspace-files-performance-cost.js +8 -8
  104. package/src/generators/preset/workspace-files-performance-cost.js.map +1 -1
  105. package/src/generators/preset/workspace-files-performance-load.d.ts +1 -1
  106. package/src/generators/preset/workspace-files-performance-load.js +8 -12
  107. package/src/generators/preset/workspace-files-performance-load.js.map +1 -1
  108. package/src/generators/preset/workspace-files-stage-authority.d.ts +4 -0
  109. package/src/generators/preset/workspace-files-stage-authority.js +275 -0
  110. package/src/generators/preset/workspace-files-stage-authority.js.map +1 -0
  111. package/src/generators/preset/workspace-files-thin-deploy.d.ts +35 -0
  112. package/src/generators/preset/workspace-files-thin-deploy.js +1434 -0
  113. package/src/generators/preset/workspace-files-thin-deploy.js.map +1 -0
  114. package/src/generators/preset/workspace-files-thin-docs.d.ts +5 -0
  115. package/src/generators/preset/workspace-files-thin-docs.js +224 -0
  116. package/src/generators/preset/workspace-files-thin-docs.js.map +1 -0
  117. package/src/generators/preset/workspace-files-thin-release.d.ts +14 -0
  118. package/src/generators/preset/workspace-files-thin-release.js +26 -0
  119. package/src/generators/preset/workspace-files-thin-release.js.map +1 -0
  120. package/src/generators/preset/workspace-files.d.ts +1 -1
  121. package/src/generators/preset/workspace-files.js +373 -223
  122. package/src/generators/preset/workspace-files.js.map +1 -1
  123. package/src/generators/slice/built-in-plan-api-files.js +1 -0
  124. package/src/generators/slice/built-in-plan-api-files.js.map +1 -1
  125. package/src/generators/slice/built-in-plan-authorization-infrastructure-files.js +4 -0
  126. package/src/generators/slice/built-in-plan-authorization-infrastructure-files.js.map +1 -1
  127. package/src/generators/slice/built-in-plan-data-export-infrastructure-files.js +5 -31
  128. package/src/generators/slice/built-in-plan-data-export-infrastructure-files.js.map +1 -1
  129. package/src/generators/slice/built-in-plan-data-files.d.ts +1 -1
  130. package/src/generators/slice/built-in-plan-data-files.js +89 -30
  131. package/src/generators/slice/built-in-plan-data-files.js.map +1 -1
  132. package/src/generators/slice/built-in-plan-foundation-files.js +3 -5
  133. package/src/generators/slice/built-in-plan-foundation-files.js.map +1 -1
  134. package/src/generators/slice/built-in-plan-frontend-files.js +5 -1
  135. package/src/generators/slice/built-in-plan-frontend-files.js.map +1 -1
  136. package/src/generators/slice/built-in-plan-infrastructure-files.d.ts +1 -1
  137. package/src/generators/slice/built-in-plan-infrastructure-files.js +199 -188
  138. package/src/generators/slice/built-in-plan-infrastructure-files.js.map +1 -1
  139. package/src/generators/slice/built-in-plan-metadata-files.js +13 -0
  140. package/src/generators/slice/built-in-plan-metadata-files.js.map +1 -1
  141. package/src/generators/slice/built-in-plan-native-runtime-authority-files.d.ts +2 -0
  142. package/src/generators/slice/built-in-plan-native-runtime-authority-files.js +307 -0
  143. package/src/generators/slice/built-in-plan-native-runtime-authority-files.js.map +1 -0
  144. package/src/generators/slice/built-in-plan-operations-files.js +43 -49
  145. package/src/generators/slice/built-in-plan-operations-files.js.map +1 -1
  146. package/src/generators/slice/built-in-plan-performance-files.js +37 -19
  147. package/src/generators/slice/built-in-plan-performance-files.js.map +1 -1
  148. package/src/generators/slice/built-in-plan-project-files.js +9 -68
  149. package/src/generators/slice/built-in-plan-project-files.js.map +1 -1
  150. package/src/generators/slice/built-in-plan-secret-runtime-files.d.ts +1 -1
  151. package/src/generators/slice/built-in-plan-secret-runtime-files.js +74 -76
  152. package/src/generators/slice/built-in-plan-secret-runtime-files.js.map +1 -1
  153. package/src/generators/slice/built-in-plan-testing-files.js +74 -47
  154. package/src/generators/slice/built-in-plan-testing-files.js.map +1 -1
  155. package/src/generators/slice/built-in-plan-thin-release-files.d.ts +11 -0
  156. package/src/generators/slice/built-in-plan-thin-release-files.js +129 -0
  157. package/src/generators/slice/built-in-plan-thin-release-files.js.map +1 -0
  158. package/src/generators/slice/built-in-plan-verification-files.js +6 -2
  159. package/src/generators/slice/built-in-plan-verification-files.js.map +1 -1
  160. package/src/generators/slice/built-in-plan-web-files.js +4 -2
  161. package/src/generators/slice/built-in-plan-web-files.js.map +1 -1
  162. package/src/generators/slice/built-in-plan-workload-role-files.js +26 -4
  163. package/src/generators/slice/built-in-plan-workload-role-files.js.map +1 -1
  164. package/src/generators/slice/built-in-plan.d.ts +1 -0
  165. package/src/generators/slice/built-in-plan.js +28 -11
  166. package/src/generators/slice/built-in-plan.js.map +1 -1
  167. package/src/generators/slice/slice.js +27 -29
  168. package/src/generators/slice/slice.js.map +1 -1
  169. package/src/generators/upgrade/upgrade.d.ts +1 -1
  170. package/src/generators/upgrade/upgrade.js +224 -69
  171. package/src/generators/upgrade/upgrade.js.map +1 -1
  172. package/src/generators/workload/performance-registration.d.ts +1 -0
  173. package/src/generators/workload/performance-registration.js +6 -4
  174. package/src/generators/workload/performance-registration.js.map +1 -1
  175. package/src/generators/workload/service-performance-files.d.ts +1 -0
  176. package/src/generators/workload/service-performance-files.js +27 -7
  177. package/src/generators/workload/service-performance-files.js.map +1 -1
  178. package/src/generators/workload/workload.d.ts +1 -1
  179. package/src/generators/workload/workload.js +55 -28
  180. package/src/generators/workload/workload.js.map +1 -1
  181. package/src/git-policy/github-bootstrap.d.ts +19 -6
  182. package/src/git-policy/github-bootstrap.js +103 -157
  183. package/src/git-policy/github-bootstrap.js.map +1 -1
  184. package/src/local-dev/private-state.d.ts +3 -0
  185. package/src/local-dev/private-state.js +17 -5
  186. package/src/local-dev/private-state.js.map +1 -1
  187. package/src/performance/cost-collection.d.ts +1 -1
  188. package/src/performance/cost-collection.js +4 -2
  189. package/src/performance/cost-collection.js.map +1 -1
  190. package/src/performance/cost-controls.d.ts +3 -1
  191. package/src/performance/cost-controls.js +9 -4
  192. package/src/performance/cost-controls.js.map +1 -1
  193. package/src/performance/evidence.d.ts +1 -1
  194. package/src/performance/evidence.js +5 -3
  195. package/src/performance/evidence.js.map +1 -1
  196. package/src/performance/load-safety.js +5 -3
  197. package/src/performance/load-safety.js.map +1 -1
  198. package/src/performance/model.d.ts +2 -2
  199. package/src/performance/model.js +4 -3
  200. package/src/performance/model.js.map +1 -1
  201. package/src/release/index.d.ts +0 -2
  202. package/src/release/index.js +0 -2
  203. package/src/release/index.js.map +1 -1
  204. package/src/release/source-authority.d.ts +4 -5
  205. package/src/release/source-authority.js +13 -123
  206. package/src/release/source-authority.js.map +1 -1
  207. package/src/repository-policy/aurora-preflight.d.ts +1 -1
  208. package/src/repository-policy/aurora-preflight.js +4 -1
  209. package/src/repository-policy/aurora-preflight.js.map +1 -1
  210. package/src/repository-policy/policy.d.ts +5 -1
  211. package/src/repository-policy/policy.js +64 -13
  212. package/src/repository-policy/policy.js.map +1 -1
  213. package/src/schemas/performance-policy-v1.json +3 -1
  214. package/src/secrets/adapters.d.ts +5 -1
  215. package/src/secrets/adapters.js +22 -1
  216. package/src/secrets/adapters.js.map +1 -1
  217. package/src/secrets/aws-control-plane.d.ts +10 -1
  218. package/src/secrets/aws-control-plane.js +67 -0
  219. package/src/secrets/aws-control-plane.js.map +1 -1
  220. package/src/secrets/generated-aws-control-plane.d.ts +23 -0
  221. package/src/secrets/generated-aws-control-plane.js +259 -0
  222. package/src/secrets/generated-aws-control-plane.js.map +1 -0
  223. package/src/secrets/metadata.js +38 -10
  224. package/src/secrets/metadata.js.map +1 -1
  225. package/src/secrets/model.d.ts +8 -1
  226. package/src/secrets/model.js +24 -3
  227. package/src/secrets/model.js.map +1 -1
  228. package/src/secrets/operator.d.ts +89 -3
  229. package/src/secrets/operator.js +422 -9
  230. package/src/secrets/operator.js.map +1 -1
  231. package/src/secrets/registry.d.ts +2 -1
  232. package/src/secrets/registry.js +18 -1
  233. package/src/secrets/registry.js.map +1 -1
  234. package/src/secrets/workload-access.js +10 -2
  235. package/src/secrets/workload-access.js.map +1 -1
  236. package/src/supply-chain/action-pins.js +61 -7
  237. package/src/supply-chain/action-pins.js.map +1 -1
  238. package/src/supply-chain/build-output-bounds.d.ts +2 -0
  239. package/src/supply-chain/build-output-bounds.js +12 -0
  240. package/src/supply-chain/build-output-bounds.js.map +1 -0
  241. package/src/supply-chain/build-output.js +2 -4
  242. package/src/supply-chain/build-output.js.map +1 -1
  243. package/src/supply-chain/ecr-scanning.d.ts +5 -3
  244. package/src/supply-chain/ecr-scanning.js +11 -7
  245. package/src/supply-chain/ecr-scanning.js.map +1 -1
  246. package/src/supply-chain/ecs-deployed-image.d.ts +5 -3
  247. package/src/supply-chain/ecs-deployed-image.js +6 -4
  248. package/src/supply-chain/ecs-deployed-image.js.map +1 -1
  249. package/src/supply-chain/generated-container-release-command.js +816 -34
  250. package/src/supply-chain/generated-container-release-command.js.map +1 -1
  251. package/src/supply-chain/generated-ecr-collector-command.d.ts +0 -1
  252. package/src/supply-chain/generated-ecr-collector-command.js +188 -91
  253. package/src/supply-chain/generated-ecr-collector-command.js.map +1 -1
  254. package/src/supply-chain/generated-files.js +4 -3
  255. package/src/supply-chain/generated-files.js.map +1 -1
  256. package/src/supply-chain/index.d.ts +1 -1
  257. package/src/supply-chain/index.js +2 -1
  258. package/src/supply-chain/index.js.map +1 -1
  259. package/src/workload-roles/index.d.ts +10 -17
  260. package/src/workload-roles/index.js +5 -18
  261. package/src/workload-roles/index.js.map +1 -1
@@ -5,6 +5,64 @@ exports.renderContainerReleaseCommand = renderContainerReleaseCommand;
5
5
  const generated_ecr_collector_command_js_1 = require("./generated-ecr-collector-command.js");
6
6
  const safe_reference_1 = require("@empire-builder-kit/runtime/safe-reference");
7
7
  exports.CRANE_VERSION = '0.21.7';
8
+ const WINDOWS_HARDEN_PRIVATE_ACL = String.raw `
9
+ $ErrorActionPreference = 'Stop'
10
+ $path = $env:EBK_PRIVATE_PATH
11
+ $kind = $env:EBK_PRIVATE_KIND
12
+ $identity = [System.Security.Principal.WindowsIdentity]::GetCurrent()
13
+ if ($kind -eq 'directory') {
14
+ $info = [System.IO.DirectoryInfo]::new($path)
15
+ } else {
16
+ $info = [System.IO.FileInfo]::new($path)
17
+ }
18
+ $acl = $info.GetAccessControl()
19
+ $acl.SetAccessRuleProtection($true, $false)
20
+ foreach ($rule in @($acl.Access)) {
21
+ [void]$acl.RemoveAccessRuleSpecific($rule)
22
+ }
23
+ $rights = [System.Security.AccessControl.FileSystemRights]::FullControl
24
+ $type = [System.Security.AccessControl.AccessControlType]::Allow
25
+ if ($kind -eq 'directory') {
26
+ $inheritance = [System.Security.AccessControl.InheritanceFlags]'ContainerInherit,ObjectInherit'
27
+ $propagation = [System.Security.AccessControl.PropagationFlags]::None
28
+ $rule = [System.Security.AccessControl.FileSystemAccessRule]::new(
29
+ $identity.User, $rights, $inheritance, $propagation, $type
30
+ )
31
+ } else {
32
+ $rule = [System.Security.AccessControl.FileSystemAccessRule]::new(
33
+ $identity.User, $rights, $type
34
+ )
35
+ }
36
+ $acl.AddAccessRule($rule)
37
+ $info.SetAccessControl($acl)
38
+ `;
39
+ const WINDOWS_ASSERT_PRIVATE_ACL = String.raw `
40
+ $ErrorActionPreference = 'Stop'
41
+ $path = $env:EBK_PRIVATE_PATH
42
+ $kind = $env:EBK_PRIVATE_KIND
43
+ $identity = [System.Security.Principal.WindowsIdentity]::GetCurrent()
44
+ if ($kind -eq 'directory') {
45
+ $info = [System.IO.DirectoryInfo]::new($path)
46
+ } else {
47
+ $info = [System.IO.FileInfo]::new($path)
48
+ }
49
+ $acl = $info.GetAccessControl()
50
+ if (-not $acl.AreAccessRulesProtected) {
51
+ throw 'ACL inheritance is enabled.'
52
+ }
53
+ $allowRules = @($acl.Access | Where-Object {
54
+ $_.AccessControlType -eq [System.Security.AccessControl.AccessControlType]::Allow
55
+ })
56
+ $unsafeRules = @($allowRules | Where-Object {
57
+ $_.IdentityReference.Translate([System.Security.Principal.SecurityIdentifier]).Value -ne $identity.User.Value
58
+ })
59
+ $selfRules = @($allowRules | Where-Object {
60
+ $_.IdentityReference.Translate([System.Security.Principal.SecurityIdentifier]).Value -eq $identity.User.Value
61
+ })
62
+ if ($unsafeRules.Count -ne 0 -or $selfRules.Count -eq 0) {
63
+ throw 'ACL grants access beyond the current user or omits the current user.'
64
+ }
65
+ `;
8
66
  exports.CRANE_RELEASES = Object.freeze({
9
67
  'darwin-arm64': {
10
68
  archiveFormat: 'tar-gzip',
@@ -59,8 +117,8 @@ function renderContainerReleaseCommand(options = {}) {
59
117
  !/^\d+\.\d+\.\d+$/.test(craneVersion)) {
60
118
  throw new Error('The generated container release configuration is invalid.');
61
119
  }
62
- return `import { createHash } from 'node:crypto';
63
- import { execFileSync, spawnSync } from 'node:child_process';
120
+ return `import { createHash, randomBytes } from 'node:crypto';
121
+ import { execFileSync, spawn, spawnSync } from 'node:child_process';
64
122
  import {
65
123
  chmodSync,
66
124
  existsSync,
@@ -71,10 +129,11 @@ import {
71
129
  realpathSync,
72
130
  readdirSync,
73
131
  rmSync,
132
+ unlinkSync,
74
133
  writeFileSync,
75
134
  } from 'node:fs';
76
135
  import { basename, isAbsolute, join, relative, resolve, sep } from 'node:path';
77
- import { tmpdir } from 'node:os';
136
+ import { platform as hostOperatingSystem, tmpdir } from 'node:os';
78
137
  import {
79
138
  CONTAINER_NATIVE_LIFECYCLE_PROOFS,
80
139
  CONTAINER_RELEASE_PLATFORMS,
@@ -82,6 +141,7 @@ import {
82
141
  createContainerNativeRunProof,
83
142
  createContainerPlatformEvidence,
84
143
  createContainerReleaseBinding,
144
+ hasSensitiveSupplyChainShape,
85
145
  resolveContainerReleaseArtifact,
86
146
  supplyChainDigest,
87
147
  validateContainerArtifactIdentity,
@@ -95,6 +155,8 @@ const SYFT_VERSION = ${JSON.stringify(syftVersion)};
95
155
  const SYFT_RELEASES = Object.freeze(${JSON.stringify(releases)});
96
156
  const CRANE_VERSION = ${JSON.stringify(craneVersion)};
97
157
  const CRANE_RELEASES = Object.freeze(${JSON.stringify(craneReleases)});
158
+ const WINDOWS_HARDEN_PRIVATE_ACL = ${JSON.stringify(WINDOWS_HARDEN_PRIVATE_ACL)};
159
+ const WINDOWS_ASSERT_PRIVATE_ACL = ${JSON.stringify(WINDOWS_ASSERT_PRIVATE_ACL)};
98
160
  const SHA256 = /^[a-f0-9]{64}$/;
99
161
  const IMAGE_DIGEST = /^sha256:[a-f0-9]{64}$/;
100
162
  const COMMIT = /^(?:[a-f0-9]{40}|[a-f0-9]{64})$/;
@@ -144,23 +206,32 @@ const packageRegistryInput =
144
206
  const localFederated =
145
207
  process.env.GITHUB_ACTIONS !== 'true' &&
146
208
  process.env.EBK_LOCAL_DEPLOYMENT_MODE === 'federated';
209
+ const localRunId = process.env.EBK_LOCAL_CONTAINER_RUN_ID?.trim() ?? '';
147
210
  if (localFederated) {
148
211
  const policy = JSON.parse(
149
212
  readFileSync(join(repositoryRoot, 'ebk.repository-policy.json'), 'utf8'),
150
213
  );
214
+ const selectedDeploymentProfile =
215
+ process.env.EBK_DEPLOYMENT_PROFILE?.trim() ?? '';
216
+ const admittedStages = {
217
+ starter: ['development', 'production'],
218
+ 'production-isolated': ['development', 'staging'],
219
+ 'fully-isolated': ['development'],
220
+ }[policy.deploymentProfile];
151
221
  if (
152
- policy.deploymentProfile !== 'starter' ||
153
- stage === 'production' ||
222
+ selectedDeploymentProfile !== policy.deploymentProfile ||
223
+ !admittedStages ||
224
+ !admittedStages.includes(stage) ||
154
225
  policy.deploymentAccounts?.[stage] !== accountId ||
155
226
  policy.deploymentRegion !== region
156
227
  ) {
157
228
  throw new Error(
158
- 'Local container release requires a locally admitted exact starter deployment target; production requires protected GitHub authority.',
229
+ 'Local container release requires the exact committed profile and one of its locally admitted stages.',
159
230
  );
160
231
  }
161
232
  }
162
233
  const runId = localFederated
163
- ? (BigInt('0x' + sourceCommit.slice(0, 15)) + 1n).toString()
234
+ ? localRunId
164
235
  : process.env.GITHUB_RUN_ID?.trim() ?? '';
165
236
  const runAttempt = localFederated
166
237
  ? '1'
@@ -359,12 +430,657 @@ const execute = (command, args, label, options = {}) => {
359
430
  stdio: ['pipe', 'pipe', 'pipe'],
360
431
  });
361
432
  } catch (error) {
433
+ if (options.failureDiagnostics === 'bounded-closure') {
434
+ const diagnosticStream = (value) => {
435
+ const raw = value === undefined || value === null ? '' : String(value);
436
+ if (raw.length === 0) return '[empty]';
437
+ if (hasSensitiveSupplyChainShape(raw)) {
438
+ return '[redacted-sensitive-output]';
439
+ }
440
+ const bytes = Buffer.from(raw, 'utf8');
441
+ const truncated = bytes.byteLength > 1_024;
442
+ const tail = truncated
443
+ ? bytes.subarray(bytes.byteLength - 1_024).toString('utf8')
444
+ : raw;
445
+ return JSON.stringify((truncated ? '[truncated]\\n' : '') + tail);
446
+ };
447
+ const status = Number.isSafeInteger(error?.status)
448
+ ? String(error.status)
449
+ : 'unavailable';
450
+ const signal =
451
+ typeof error?.signal === 'string' && /^[A-Z0-9]+$/.test(error.signal)
452
+ ? error.signal
453
+ : 'none';
454
+ throw new Error(
455
+ label +
456
+ ' failed without complete evidence: ' +
457
+ 'status=' +
458
+ status +
459
+ '; signal=' +
460
+ signal +
461
+ '; stdout=' +
462
+ diagnosticStream(error?.stdout) +
463
+ '; stderr=' +
464
+ diagnosticStream(error?.stderr),
465
+ );
466
+ }
362
467
  throw new Error(
363
468
  label + ' failed without complete evidence: ' +
364
469
  String(error?.stderr ?? error?.message ?? error).slice(0, 2_000),
365
470
  );
366
471
  }
367
472
  };
473
+ let releaseBuilder;
474
+ let releaseBuildChild;
475
+ let releaseBuildStopPromise;
476
+ let releaseBuilderSignal;
477
+ const releaseBuilderSignalHandlers = new Map();
478
+ const releaseToolTemporaryDirectories = new Set();
479
+ const yieldToReleaseSignals = () =>
480
+ new Promise((resolve) => setImmediate(resolve));
481
+ const stopExactReleaseBuildChild = async (signal = 'SIGTERM') => {
482
+ if (releaseBuildStopPromise) return releaseBuildStopPromise;
483
+ const observed = releaseBuildChild;
484
+ if (!observed) return;
485
+ releaseBuildStopPromise = (async () => {
486
+ const child = observed.child;
487
+ if (hostOperatingSystem() === 'win32') {
488
+ const taskkill = join(process.env.SystemRoot ?? '', 'System32', 'taskkill.exe');
489
+ if (!isAbsolute(taskkill) || !existsSync(taskkill) || realpathSync(taskkill) !== taskkill) {
490
+ throw new Error('Exact Windows Buildx cleanup requires canonical SystemRoot.');
491
+ }
492
+ const stopped = spawnSync(
493
+ taskkill,
494
+ ['/PID', String(child.pid), '/T', '/F'],
495
+ {
496
+ cwd: repositoryRoot,
497
+ encoding: 'utf8',
498
+ env: process.env,
499
+ maxBuffer: 1024 * 1024,
500
+ stdio: ['ignore', 'pipe', 'pipe'],
501
+ windowsHide: true,
502
+ },
503
+ );
504
+ if (
505
+ child.exitCode === null &&
506
+ (stopped.error || stopped.signal || stopped.status !== 0)
507
+ ) {
508
+ throw new Error('Exact Windows Buildx process-tree cleanup failed.');
509
+ }
510
+ } else if (child.pid) {
511
+ try {
512
+ process.kill(-child.pid, signal);
513
+ } catch (error) {
514
+ if (error?.code !== 'ESRCH') throw error;
515
+ }
516
+ }
517
+ if (child.exitCode === null) {
518
+ const closed = await Promise.race([
519
+ observed.close.then(() => true),
520
+ new Promise((resolve) => setTimeout(() => resolve(false), 5_000)),
521
+ ]);
522
+ if (!closed && hostOperatingSystem() !== 'win32' && child.pid) {
523
+ try {
524
+ process.kill(-child.pid, 'SIGKILL');
525
+ } catch (error) {
526
+ if (error?.code !== 'ESRCH') throw error;
527
+ }
528
+ const killed = await Promise.race([
529
+ observed.close.then(() => true),
530
+ new Promise((resolve) => setTimeout(() => resolve(false), 5_000)),
531
+ ]);
532
+ if (!killed) {
533
+ throw new Error('Exact Buildx process group did not terminate.');
534
+ }
535
+ }
536
+ }
537
+ })().finally(() => {
538
+ if (releaseBuildChild === observed) releaseBuildChild = undefined;
539
+ releaseBuildStopPromise = undefined;
540
+ });
541
+ return releaseBuildStopPromise;
542
+ };
543
+ const executeObservedBuildx = async (args, label) => {
544
+ if (releaseBuildChild) {
545
+ throw new Error('Only one invocation-owned Buildx child may run at a time.');
546
+ }
547
+ const child = spawn('docker', args, {
548
+ cwd: repositoryRoot,
549
+ detached: hostOperatingSystem() !== 'win32',
550
+ env: process.env,
551
+ stdio: ['ignore', 'pipe', 'pipe'],
552
+ windowsHide: true,
553
+ });
554
+ const stdout = [];
555
+ const stderr = [];
556
+ let outputBytes = 0;
557
+ const retain = (chunks, stream, chunk) => {
558
+ outputBytes += chunk.byteLength;
559
+ if (outputBytes > MAX_OUTPUT_BYTES) {
560
+ void stopExactReleaseBuildChild('SIGTERM');
561
+ return;
562
+ }
563
+ chunks.push(chunk);
564
+ stream.write(chunk);
565
+ };
566
+ child.stdout.on('data', (chunk) => retain(stdout, process.stdout, chunk));
567
+ child.stderr.on('data', (chunk) => retain(stderr, process.stderr, chunk));
568
+ const close = new Promise((resolve) => {
569
+ child.once('error', (error) => resolve({ error }));
570
+ child.once('close', (status, signal) => resolve({ signal, status }));
571
+ });
572
+ releaseBuildChild = { child, close };
573
+ const result = await close;
574
+ await stopExactReleaseBuildChild();
575
+ if (
576
+ outputBytes > MAX_OUTPUT_BYTES ||
577
+ result.error ||
578
+ result.signal ||
579
+ result.status !== 0
580
+ ) {
581
+ throw new Error(
582
+ label + ' failed without complete evidence: ' +
583
+ Buffer.concat(stderr).toString('utf8').slice(-2_000),
584
+ );
585
+ }
586
+ return Buffer.concat(stdout).toString('utf8');
587
+ };
588
+ const runWindowsPrivateAcl = (script, path, kind) => {
589
+ const systemRoot = process.env.SystemRoot;
590
+ if (!systemRoot || !isAbsolute(systemRoot)) {
591
+ throw new Error('Windows private-path hardening requires an absolute SystemRoot.');
592
+ }
593
+ const powershell = join(
594
+ systemRoot,
595
+ 'System32',
596
+ 'WindowsPowerShell',
597
+ 'v1.0',
598
+ 'powershell.exe',
599
+ );
600
+ const result = spawnSync(
601
+ powershell,
602
+ [
603
+ '-NoLogo',
604
+ '-NoProfile',
605
+ '-NonInteractive',
606
+ '-EncodedCommand',
607
+ Buffer.from(script, 'utf16le').toString('base64'),
608
+ ],
609
+ {
610
+ cwd: repositoryRoot,
611
+ encoding: 'utf8',
612
+ env: {
613
+ ...process.env,
614
+ EBK_PRIVATE_KIND: kind,
615
+ EBK_PRIVATE_PATH: path,
616
+ },
617
+ maxBuffer: 1024 * 1024,
618
+ stdio: ['ignore', 'pipe', 'pipe'],
619
+ timeout: 15_000,
620
+ windowsHide: true,
621
+ },
622
+ );
623
+ if (result.error || result.signal || result.status !== 0) {
624
+ throw new Error(
625
+ 'Windows private-path ACL operation failed closed: ' +
626
+ String(result.stderr || result.error?.message || result.signal || result.status)
627
+ .slice(-2_000),
628
+ );
629
+ }
630
+ };
631
+ const assertPrivateReleaseBuilderPathIdentity = (path, kind) => {
632
+ const metadata = lstatSync(path);
633
+ if (
634
+ metadata.isSymbolicLink() ||
635
+ (kind === 'directory' ? !metadata.isDirectory() : !metadata.isFile()) ||
636
+ realpathSync(path) !== path
637
+ ) {
638
+ throw new Error(
639
+ 'Container-release builder ownership path failed canonical type validation.',
640
+ );
641
+ }
642
+ return metadata;
643
+ };
644
+ const assertPrivateReleaseBuilderPath = (path, kind) => {
645
+ const metadata = assertPrivateReleaseBuilderPathIdentity(path, kind);
646
+ if (hostOperatingSystem() === 'win32') {
647
+ runWindowsPrivateAcl(WINDOWS_ASSERT_PRIVATE_ACL, path, kind);
648
+ return metadata;
649
+ }
650
+ const expectedMode = kind === 'directory' ? 0o700 : 0o600;
651
+ if (
652
+ (metadata.mode & 0o777) !== expectedMode ||
653
+ (typeof process.getuid === 'function' && metadata.uid !== process.getuid())
654
+ ) {
655
+ throw new Error(
656
+ 'Container-release builder ownership path is not current-user private.',
657
+ );
658
+ }
659
+ return metadata;
660
+ };
661
+ const hardenPrivateReleaseBuilderPath = (path, kind) => {
662
+ assertPrivateReleaseBuilderPathIdentity(path, kind);
663
+ if (hostOperatingSystem() === 'win32') {
664
+ runWindowsPrivateAcl(WINDOWS_HARDEN_PRIVATE_ACL, path, kind);
665
+ } else {
666
+ chmodSync(path, kind === 'directory' ? 0o700 : 0o600);
667
+ }
668
+ return assertPrivateReleaseBuilderPath(path, kind);
669
+ };
670
+ const assertCanonicalAncestorsBeforeCreate = (requested) => {
671
+ const requestedRelative = relative(repositoryRoot, requested);
672
+ if (
673
+ requestedRelative === '' ||
674
+ requestedRelative === '..' ||
675
+ requestedRelative.startsWith('..' + sep) ||
676
+ isAbsolute(requestedRelative)
677
+ ) {
678
+ throw new Error('Container-release builder ownership path escapes its repository.');
679
+ }
680
+ let cursor = repositoryRoot;
681
+ for (const segment of requestedRelative.split(sep)) {
682
+ cursor = join(cursor, segment);
683
+ if (!existsSync(cursor)) break;
684
+ const metadata = lstatSync(cursor);
685
+ if (metadata.isSymbolicLink() || realpathSync(cursor) !== cursor) {
686
+ throw new Error(
687
+ 'Container-release builder ownership path has a symbolic or noncanonical ancestor.',
688
+ );
689
+ }
690
+ }
691
+ };
692
+ const releaseBuilderLeaseRoot = () => {
693
+ const requested = resolve(
694
+ repositoryRoot,
695
+ '.ebk/cache/container-release-builders',
696
+ );
697
+ assertCanonicalAncestorsBeforeCreate(requested);
698
+ mkdirSync(requested, { mode: 0o700, recursive: true });
699
+ hardenPrivateReleaseBuilderPath(requested, 'directory');
700
+ const canonical = realpathSync(requested);
701
+ if (canonical !== requested) {
702
+ throw new Error(
703
+ 'Container-release builder leases require one repository-private owned directory.',
704
+ );
705
+ }
706
+ return canonical;
707
+ };
708
+ const readReleaseBuilderLease = (leasePath, expectedName) => {
709
+ const metadata = assertPrivateReleaseBuilderPath(leasePath, 'file');
710
+ if (
711
+ metadata.nlink !== 1 ||
712
+ metadata.size < 2 ||
713
+ metadata.size > 16 * 1024
714
+ ) {
715
+ throw new Error('Container-release builder lease is not one private file.');
716
+ }
717
+ let lease;
718
+ try {
719
+ lease = JSON.parse(readFileSync(leasePath, 'utf8'));
720
+ } catch {
721
+ throw new Error('Container-release builder lease is invalid JSON.');
722
+ }
723
+ const keys = Object.keys(lease ?? {}).sort();
724
+ if (
725
+ JSON.stringify(keys) !==
726
+ JSON.stringify([
727
+ 'builder',
728
+ 'cleanup',
729
+ 'createdAt',
730
+ 'driver',
731
+ 'kind',
732
+ 'ownerPid',
733
+ 'schemaVersion',
734
+ ]) ||
735
+ lease.kind !== 'ebk-container-release-builder-lease' ||
736
+ lease.schemaVersion !== 1 ||
737
+ lease.builder !== expectedName ||
738
+ !/^ebk-release-[a-f0-9]{32}$/.test(lease.builder) ||
739
+ lease.driver !== 'docker-container' ||
740
+ JSON.stringify(lease.cleanup) !==
741
+ JSON.stringify(['docker', 'buildx', 'rm', lease.builder]) ||
742
+ !Number.isSafeInteger(lease.ownerPid) ||
743
+ lease.ownerPid <= 0 ||
744
+ !Number.isFinite(Date.parse(lease.createdAt)) ||
745
+ new Date(lease.createdAt).toISOString() !== lease.createdAt
746
+ ) {
747
+ throw new Error('Container-release builder lease has an invalid identity.');
748
+ }
749
+ return lease;
750
+ };
751
+ const removeReleaseBuilderSignalHandlers = () => {
752
+ for (const [signal, handler] of releaseBuilderSignalHandlers) {
753
+ process.removeListener(signal, handler);
754
+ }
755
+ releaseBuilderSignalHandlers.clear();
756
+ };
757
+ const ensureReleaseBuilderSignalHandlers = () => {
758
+ if (releaseBuilderSignalHandlers.size > 0) return;
759
+ for (const signal of ['SIGINT', 'SIGTERM']) {
760
+ const handler = () => void handleReleaseBuilderSignal(signal);
761
+ releaseBuilderSignalHandlers.set(signal, handler);
762
+ process.on(signal, handler);
763
+ }
764
+ };
765
+ const removeReleaseBuilderSignalHandlersWhenIdle = () => {
766
+ if (!releaseBuilder && releaseToolTemporaryDirectories.size === 0) {
767
+ removeReleaseBuilderSignalHandlers();
768
+ }
769
+ };
770
+ const createReleaseToolTemporaryDirectory = (prefix) => {
771
+ const created = mkdtempSync(join(tmpdir(), prefix));
772
+ const metadata = lstatSync(created);
773
+ if (
774
+ !metadata.isDirectory() ||
775
+ metadata.isSymbolicLink() ||
776
+ !isAbsolute(created)
777
+ ) {
778
+ throw new Error('Container-release tool temporary directory is not exact.');
779
+ }
780
+ const temporary = realpathSync(created);
781
+ const canonicalMetadata = lstatSync(temporary);
782
+ if (
783
+ !isAbsolute(temporary) ||
784
+ !canonicalMetadata.isDirectory() ||
785
+ canonicalMetadata.isSymbolicLink() ||
786
+ canonicalMetadata.dev !== metadata.dev ||
787
+ canonicalMetadata.ino !== metadata.ino
788
+ ) {
789
+ throw new Error('Container-release tool temporary directory is not canonical.');
790
+ }
791
+ releaseToolTemporaryDirectories.add(temporary);
792
+ ensureReleaseBuilderSignalHandlers();
793
+ return temporary;
794
+ };
795
+ const removeExactReleaseToolTemporaryDirectory = (temporary) => {
796
+ if (!releaseToolTemporaryDirectories.has(temporary)) {
797
+ throw new Error('Container-release tool temporary directory is not owned.');
798
+ }
799
+ const metadata = lstatSync(temporary);
800
+ if (
801
+ !metadata.isDirectory() ||
802
+ metadata.isSymbolicLink() ||
803
+ !isAbsolute(temporary) ||
804
+ realpathSync(temporary) !== temporary
805
+ ) {
806
+ throw new Error(
807
+ 'Container-release tool temporary directory changed identity before cleanup.',
808
+ );
809
+ }
810
+ rmSync(temporary, { force: true, recursive: true });
811
+ if (existsSync(temporary)) {
812
+ throw new Error('Container-release tool temporary directory cleanup failed.');
813
+ }
814
+ releaseToolTemporaryDirectories.delete(temporary);
815
+ removeReleaseBuilderSignalHandlersWhenIdle();
816
+ };
817
+ const removeAllReleaseToolTemporaryDirectories = () => {
818
+ const cleanupErrors = [];
819
+ for (const temporary of [...releaseToolTemporaryDirectories]) {
820
+ try {
821
+ removeExactReleaseToolTemporaryDirectory(temporary);
822
+ } catch (error) {
823
+ cleanupErrors.push(error);
824
+ }
825
+ }
826
+ if (cleanupErrors.length > 0) {
827
+ throw new AggregateError(
828
+ cleanupErrors,
829
+ 'Exact container-release tool temporary cleanup failed.',
830
+ );
831
+ }
832
+ };
833
+ const releaseBuilderCleanupDiagnostic = (builder) =>
834
+ 'docker buildx rm ' + builder.name +
835
+ ' (ownership lease: ' + builder.leasePath + ')';
836
+ const listExactReleaseBuilderNames = ({ signal } = {}) => {
837
+ const inventory = spawnSync(
838
+ 'docker',
839
+ ['buildx', 'ls', '--format', '{{json .Name}}'],
840
+ {
841
+ cwd: repositoryRoot,
842
+ encoding: 'utf8',
843
+ env: process.env,
844
+ maxBuffer: 1024 * 1024,
845
+ stdio: ['ignore', 'pipe', 'pipe'],
846
+ timeout: signal ? 5_000 : undefined,
847
+ },
848
+ );
849
+ if (inventory.error || inventory.signal || inventory.status !== 0) {
850
+ throw new Error(
851
+ 'Invocation-owned Buildx inventory failed while proving exact cleanup: ' +
852
+ String(
853
+ inventory.stderr ||
854
+ inventory.error?.message ||
855
+ inventory.signal ||
856
+ inventory.status,
857
+ ).slice(-2_000),
858
+ );
859
+ }
860
+ const lines = inventory.stdout.trim() === ''
861
+ ? []
862
+ : inventory.stdout.trim().split(/\\r?\\n/u);
863
+ if (lines.length > 256) {
864
+ throw new Error('Invocation-owned Buildx inventory is unbounded.');
865
+ }
866
+ const names = new Set();
867
+ for (const line of lines) {
868
+ let name;
869
+ try {
870
+ name = JSON.parse(line);
871
+ } catch {
872
+ throw new Error('Invocation-owned Buildx inventory is not exact JSON.');
873
+ }
874
+ if (
875
+ typeof name !== 'string' ||
876
+ name.length === 0 ||
877
+ name.length > 256 ||
878
+ names.has(name)
879
+ ) {
880
+ throw new Error('Invocation-owned Buildx inventory has an invalid identity.');
881
+ }
882
+ names.add(name);
883
+ }
884
+ return names;
885
+ };
886
+ const removeExactReleaseBuilder = (builder, { signal } = {}) => {
887
+ readReleaseBuilderLease(builder.leasePath, builder.name);
888
+ const cleanup = spawnSync(
889
+ 'docker',
890
+ ['buildx', 'rm', builder.name],
891
+ {
892
+ cwd: repositoryRoot,
893
+ encoding: 'utf8',
894
+ env: process.env,
895
+ maxBuffer: 1024 * 1024,
896
+ stdio: ['ignore', 'pipe', 'pipe'],
897
+ timeout: signal ? 5_000 : undefined,
898
+ },
899
+ );
900
+ if (cleanup.error || cleanup.signal || cleanup.status !== 0) {
901
+ let builderNames;
902
+ try {
903
+ builderNames = listExactReleaseBuilderNames({ signal });
904
+ } catch (inventoryError) {
905
+ const error = new AggregateError(
906
+ [
907
+ cleanup.error ?? new Error(String(cleanup.stderr || cleanup.status)),
908
+ inventoryError,
909
+ ],
910
+ 'Invocation-owned Buildx cleanup and exact absence proof both failed; ' +
911
+ 'retain ' + releaseBuilderCleanupDiagnostic(builder) + '.',
912
+ );
913
+ if (signal) {
914
+ process.stderr.write(error.message + '\\n');
915
+ return false;
916
+ }
917
+ throw error;
918
+ }
919
+ if (!builderNames.has(builder.name)) {
920
+ unlinkSync(builder.leasePath);
921
+ process.stderr.write(
922
+ 'ebk-release-builder-already-absent ' +
923
+ JSON.stringify({ builder: builder.name, leasePath: builder.leasePath }) +
924
+ '\\n',
925
+ );
926
+ return true;
927
+ }
928
+ const diagnostic = [cleanup.stdout, cleanup.stderr]
929
+ .filter(Boolean)
930
+ .join('\\n')
931
+ .slice(-2_000);
932
+ const error = new Error(
933
+ 'Invocation-owned Buildx cleanup failed; use only the exact retained ' +
934
+ 'hard-kill cleanup command ' + releaseBuilderCleanupDiagnostic(builder) +
935
+ (diagnostic ? ': ' + diagnostic : ''),
936
+ );
937
+ if (signal) {
938
+ process.stderr.write(error.message + '\\n');
939
+ return false;
940
+ }
941
+ throw error;
942
+ }
943
+ unlinkSync(builder.leasePath);
944
+ return true;
945
+ };
946
+ const recoverReleaseBuilders = () => {
947
+ const leaseRoot = releaseBuilderLeaseRoot();
948
+ const entries = readdirSync(leaseRoot, { withFileTypes: true }).sort(
949
+ (left, right) =>
950
+ left.name < right.name ? -1 : left.name > right.name ? 1 : 0,
951
+ );
952
+ if (entries.length > 64) {
953
+ throw new Error('Container-release builder lease inventory is unbounded.');
954
+ }
955
+ for (const entry of entries) {
956
+ const matched = /^(ebk-release-[a-f0-9]{32})\\.json$/.exec(entry.name);
957
+ if (!entry.isFile() || !matched) {
958
+ throw new Error(
959
+ 'Container-release builder lease directory contains an unexpected entry.',
960
+ );
961
+ }
962
+ const leasePath = join(leaseRoot, entry.name);
963
+ const lease = readReleaseBuilderLease(leasePath, matched[1]);
964
+ const builderNames = listExactReleaseBuilderNames();
965
+ if (builderNames.has(lease.builder)) {
966
+ throw new Error(
967
+ 'A retained invocation-owned Buildx builder still exists; inspect it and use only the exact cleanup command ' +
968
+ releaseBuilderCleanupDiagnostic({ leasePath, name: lease.builder }) + '.',
969
+ );
970
+ }
971
+ unlinkSync(leasePath);
972
+ process.stderr.write(
973
+ 'ebk-release-builder-recovered ' +
974
+ JSON.stringify({ builder: lease.builder, leasePath }) +
975
+ '\\n',
976
+ );
977
+ }
978
+ };
979
+ const cleanupReleaseBuilder = ({ signal } = {}) => {
980
+ const builder = releaseBuilder;
981
+ if (!builder) {
982
+ removeReleaseBuilderSignalHandlersWhenIdle();
983
+ return;
984
+ }
985
+ releaseBuilder = undefined;
986
+ const removed = removeExactReleaseBuilder(builder, { signal });
987
+ removeReleaseBuilderSignalHandlersWhenIdle();
988
+ return removed;
989
+ };
990
+ const handleReleaseBuilderSignal = async (signal) => {
991
+ if (releaseBuilderSignal) return;
992
+ releaseBuilderSignal = signal;
993
+ removeReleaseBuilderSignalHandlers();
994
+ const cleanupErrors = [];
995
+ let childStopped = false;
996
+ try {
997
+ await stopExactReleaseBuildChild(signal);
998
+ childStopped = true;
999
+ } catch (error) {
1000
+ cleanupErrors.push(error);
1001
+ }
1002
+ if (childStopped) {
1003
+ try {
1004
+ cleanupReleaseBuilder({ signal });
1005
+ } catch (error) {
1006
+ cleanupErrors.push(error);
1007
+ }
1008
+ }
1009
+ try {
1010
+ removeAllReleaseToolTemporaryDirectories();
1011
+ } catch (error) {
1012
+ cleanupErrors.push(error);
1013
+ }
1014
+ if (cleanupErrors.length > 0) {
1015
+ process.stderr.write(
1016
+ 'Invocation-owned Buildx signal cleanup failed closed; the exact ' +
1017
+ 'ownership lease was retained: ' +
1018
+ cleanupErrors
1019
+ .map((error) => String(error?.message ?? error))
1020
+ .join('; ')
1021
+ .slice(-2_000) + '\\n',
1022
+ );
1023
+ }
1024
+ try {
1025
+ removeReleaseBuilderSignalHandlers();
1026
+ } finally {
1027
+ process.kill(process.pid, signal);
1028
+ }
1029
+ };
1030
+ const ensureReleaseBuilder = () => {
1031
+ if (releaseBuilder) return releaseBuilder;
1032
+ const name = 'ebk-release-' + randomBytes(16).toString('hex');
1033
+ const leaseRoot = releaseBuilderLeaseRoot();
1034
+ const leasePath = join(leaseRoot, name + '.json');
1035
+ const builder = {
1036
+ leasePath,
1037
+ leaseRoot,
1038
+ name,
1039
+ };
1040
+ writeFileSync(
1041
+ leasePath,
1042
+ JSON.stringify({
1043
+ builder: name,
1044
+ cleanup: ['docker', 'buildx', 'rm', name],
1045
+ createdAt: new Date().toISOString(),
1046
+ driver: 'docker-container',
1047
+ kind: 'ebk-container-release-builder-lease',
1048
+ ownerPid: process.pid,
1049
+ schemaVersion: 1,
1050
+ }) + '\\n',
1051
+ { encoding: 'utf8', flag: 'wx', mode: 0o600 },
1052
+ );
1053
+ hardenPrivateReleaseBuilderPath(leasePath, 'file');
1054
+ releaseBuilder = builder;
1055
+ ensureReleaseBuilderSignalHandlers();
1056
+ process.stderr.write(
1057
+ 'ebk-release-builder-ownership ' +
1058
+ JSON.stringify({
1059
+ builder: name,
1060
+ hardKillCleanup: ['docker', 'buildx', 'rm', name],
1061
+ leasePath,
1062
+ }) +
1063
+ '\\n',
1064
+ );
1065
+ try {
1066
+ execute(
1067
+ 'docker',
1068
+ ['buildx', 'create', '--name', name, '--driver', 'docker-container'],
1069
+ 'Invocation-owned native Buildx builder creation',
1070
+ );
1071
+ } catch (error) {
1072
+ try {
1073
+ cleanupReleaseBuilder();
1074
+ } catch (cleanupError) {
1075
+ throw new AggregateError(
1076
+ [error, cleanupError],
1077
+ 'Invocation-owned Buildx creation and cleanup both failed.',
1078
+ );
1079
+ }
1080
+ throw error;
1081
+ }
1082
+ return builder;
1083
+ };
368
1084
  const awsJson = (args, label) => {
369
1085
  const raw = execute('aws', args, label);
370
1086
  try {
@@ -760,7 +1476,8 @@ const acquireSyft = async () => {
760
1476
  if (!release) {
761
1477
  throw new Error('No reviewed Syft release exists for ' + platformKey + '.');
762
1478
  }
763
- const temporary = mkdtempSync(join(tmpdir(), 'ebk-container-syft-'));
1479
+ const temporary = createReleaseToolTemporaryDirectory('ebk-container-syft-');
1480
+ try {
764
1481
  const supplied = process.env.EBK_SYFT_ARCHIVE_PATH?.trim();
765
1482
  const archive = supplied
766
1483
  ? safePath(relative(repositoryRoot, resolve(repositoryRoot, supplied)), 'Supplied Syft archive')
@@ -798,6 +1515,7 @@ const acquireSyft = async () => {
798
1515
  chmodSync(path, 0o700);
799
1516
  }
800
1517
  const version = String(execute(path, ['version'], 'Syft version'));
1518
+ await yieldToReleaseSignals();
801
1519
  if (
802
1520
  !new RegExp('(?:Version|version):?\\\\s*' + SYFT_VERSION.replaceAll('.', '\\\\.')).test(
803
1521
  version,
@@ -805,7 +1523,18 @@ const acquireSyft = async () => {
805
1523
  ) {
806
1524
  throw new Error('Syft runtime version differs from reviewed policy.');
807
1525
  }
808
- return { path, temporary };
1526
+ return { path, temporary };
1527
+ } catch (error) {
1528
+ try {
1529
+ removeExactReleaseToolTemporaryDirectory(temporary);
1530
+ } catch (cleanupError) {
1531
+ throw new AggregateError(
1532
+ [error, cleanupError],
1533
+ 'Syft acquisition and exact temporary cleanup both failed.',
1534
+ );
1535
+ }
1536
+ throw error;
1537
+ }
809
1538
  };
810
1539
  const acquireCrane = async () => {
811
1540
  const platformKey = process.platform + '-' + process.arch;
@@ -813,7 +1542,8 @@ const acquireCrane = async () => {
813
1542
  if (!release) {
814
1543
  throw new Error('No reviewed Crane release exists for ' + platformKey + '.');
815
1544
  }
816
- const temporary = mkdtempSync(join(tmpdir(), 'ebk-container-crane-'));
1545
+ const temporary = createReleaseToolTemporaryDirectory('ebk-container-crane-');
1546
+ try {
817
1547
  const supplied = process.env.EBK_CRANE_ARCHIVE_PATH?.trim();
818
1548
  const archive = supplied
819
1549
  ? safePath(
@@ -850,6 +1580,7 @@ const acquireCrane = async () => {
850
1580
  const path = join(temporary, 'crane');
851
1581
  chmodSync(path, 0o700);
852
1582
  const version = String(execute(path, ['version'], 'Crane version')).trim();
1583
+ await yieldToReleaseSignals();
853
1584
  if (
854
1585
  !new RegExp('(?:^|\\s|v)' + CRANE_VERSION.replaceAll('.', '\\.') + '(?:$|\\s)').test(
855
1586
  version,
@@ -857,7 +1588,18 @@ const acquireCrane = async () => {
857
1588
  ) {
858
1589
  throw new Error('Crane runtime version differs from reviewed policy.');
859
1590
  }
860
- return { path, temporary };
1591
+ return { path, temporary };
1592
+ } catch (error) {
1593
+ try {
1594
+ removeExactReleaseToolTemporaryDirectory(temporary);
1595
+ } catch (cleanupError) {
1596
+ throw new AggregateError(
1597
+ [error, cleanupError],
1598
+ 'Crane acquisition and exact temporary cleanup both failed.',
1599
+ );
1600
+ }
1601
+ throw error;
1602
+ }
861
1603
  };
862
1604
  const parseCycloneDx = (value, label) => {
863
1605
  if (
@@ -957,8 +1699,8 @@ const npmClosureFor = (syftDocument, options = {}) => {
957
1699
  }
958
1700
  return unique;
959
1701
  };
960
- const deriveExpectedProductionClosure = (syft, input) => {
961
- const temporary = mkdtempSync(join(tmpdir(), 'ebk-container-closure-'));
1702
+ const deriveExpectedProductionClosure = async (syft, input) => {
1703
+ const temporary = createReleaseToolTemporaryDirectory('ebk-container-closure-');
962
1704
  const deployed = join(temporary, 'deployed');
963
1705
  try {
964
1706
  execute(
@@ -977,16 +1719,24 @@ const deriveExpectedProductionClosure = (syft, input) => {
977
1719
  deployed,
978
1720
  ],
979
1721
  'Independent production closure deployment',
980
- { env: { COREPACK_ENABLE_DOWNLOAD_PROMPT: '0' } },
1722
+ {
1723
+ env: { COREPACK_ENABLE_DOWNLOAD_PROMPT: '0' },
1724
+ failureDiagnostics: 'bounded-closure',
1725
+ },
981
1726
  );
1727
+ await yieldToReleaseSignals();
982
1728
  const raw = String(
983
1729
  execute(
984
1730
  syft.path,
985
1731
  ['scan', 'dir:' + deployed, '--output', 'syft-json'],
986
1732
  'Independent production closure inventory',
987
- { env: { SYFT_CHECK_FOR_APP_UPDATE: 'false' } },
1733
+ {
1734
+ env: { SYFT_CHECK_FOR_APP_UPDATE: 'false' },
1735
+ failureDiagnostics: 'bounded-closure',
1736
+ },
988
1737
  ),
989
1738
  );
1739
+ await yieldToReleaseSignals();
990
1740
  let inventory;
991
1741
  try {
992
1742
  inventory = JSON.parse(raw);
@@ -1041,7 +1791,7 @@ const deriveExpectedProductionClosure = (syft, input) => {
1041
1791
  }
1042
1792
  return { digest: supplyChainDigest(packages), packages };
1043
1793
  } finally {
1044
- rmSync(temporary, { force: true, recursive: true });
1794
+ removeExactReleaseToolTemporaryDirectory(temporary);
1045
1795
  }
1046
1796
  };
1047
1797
  const validateRetainedSbom = (entry, label) => {
@@ -1719,6 +2469,7 @@ if (mode === 'promote') {
1719
2469
  slice,
1720
2470
  'binding.json',
1721
2471
  );
2472
+ await yieldToReleaseSignals();
1722
2473
  immutableWrite(output, binding);
1723
2474
  process.stdout.write(
1724
2475
  JSON.stringify({
@@ -1731,21 +2482,26 @@ if (mode === 'promote') {
1731
2482
  }) + '\\n',
1732
2483
  );
1733
2484
  } finally {
1734
- rmSync(crane.temporary, { force: true, recursive: true });
2485
+ removeExactReleaseToolTemporaryDirectory(crane.temporary);
1735
2486
  }
1736
2487
  process.exit(0);
1737
2488
  }
1738
2489
 
1739
2490
  if (mode === 'platform') {
1740
- const syft = await acquireSyft();
1741
- const crane = stage === 'development' ? await acquireCrane() : undefined;
2491
+ let syft;
2492
+ let crane;
2493
+ let platformError;
1742
2494
  try {
1743
- const expectedClosures = new Map(
1744
- containerInputs.map((input) => [
2495
+ syft = await acquireSyft();
2496
+ crane = stage === 'development' ? await acquireCrane() : undefined;
2497
+ recoverReleaseBuilders();
2498
+ const expectedClosures = new Map();
2499
+ for (const input of containerInputs) {
2500
+ expectedClosures.set(
1745
2501
  input.declared.id,
1746
- deriveExpectedProductionClosure(syft, input),
1747
- ]),
1748
- );
2502
+ await deriveExpectedProductionClosure(syft, input),
2503
+ );
2504
+ }
1749
2505
  requireReviewedCheckout();
1750
2506
  for (const input of containerInputs) requireCandidateAuthority(input);
1751
2507
  login();
@@ -1788,11 +2544,13 @@ if (mode === 'platform') {
1788
2544
  'Exact staging retirement child promotion',
1789
2545
  );
1790
2546
  } else {
1791
- execute(
1792
- 'docker',
1793
- [
2547
+ const platformBuilder = ensureReleaseBuilder();
2548
+ try {
2549
+ await executeObservedBuildx([
1794
2550
  'buildx',
1795
2551
  'build',
2552
+ '--builder',
2553
+ platformBuilder.name,
1796
2554
  ...(packageRegistry
1797
2555
  ? [
1798
2556
  '--build-arg',
@@ -1814,9 +2572,10 @@ if (mode === 'platform') {
1814
2572
  '--tag',
1815
2573
  taggedReference,
1816
2574
  relative(repositoryRoot, input.context) || '.',
1817
- ],
1818
- 'Native container build and push',
1819
- );
2575
+ ], 'Native container build and push');
2576
+ } finally {
2577
+ cleanupReleaseBuilder();
2578
+ }
1820
2579
  }
1821
2580
  const imageDigest = exactImageByTag(repositoryName, tag);
1822
2581
  const imageReference = registry + '/' + repositoryName + '@' + imageDigest;
@@ -1991,6 +2750,7 @@ if (mode === 'platform') {
1991
2750
  evidence,
1992
2751
  );
1993
2752
  }
2753
+ await yieldToReleaseSignals();
1994
2754
  process.stdout.write(
1995
2755
  JSON.stringify({
1996
2756
  artifactCount: containerInputs.length,
@@ -1999,12 +2759,33 @@ if (mode === 'platform') {
1999
2759
  stage,
2000
2760
  }) + '\\n',
2001
2761
  );
2762
+ } catch (error) {
2763
+ platformError = error;
2002
2764
  } finally {
2003
- rmSync(syft.temporary, { force: true, recursive: true });
2004
- if (crane) {
2005
- rmSync(crane.temporary, { force: true, recursive: true });
2765
+ const cleanupErrors = [];
2766
+ for (const cleanup of [
2767
+ () => cleanupReleaseBuilder(),
2768
+ ...(syft
2769
+ ? [() => removeExactReleaseToolTemporaryDirectory(syft.temporary)]
2770
+ : []),
2771
+ ...(crane
2772
+ ? [() => removeExactReleaseToolTemporaryDirectory(crane.temporary)]
2773
+ : []),
2774
+ ]) {
2775
+ try {
2776
+ cleanup();
2777
+ } catch (error) {
2778
+ cleanupErrors.push(error);
2779
+ }
2780
+ }
2781
+ if (cleanupErrors.length > 0) {
2782
+ throw new AggregateError(
2783
+ [...(platformError ? [platformError] : []), ...cleanupErrors],
2784
+ 'Container platform release and independent owned-resource cleanup failed.',
2785
+ );
2006
2786
  }
2007
2787
  }
2788
+ if (platformError) throw platformError;
2008
2789
  process.exit(0);
2009
2790
  }
2010
2791
 
@@ -2216,6 +2997,7 @@ const output = resolve(
2216
2997
  slice,
2217
2998
  'binding.json',
2218
2999
  );
3000
+ await yieldToReleaseSignals();
2219
3001
  immutableWrite(output, binding);
2220
3002
  process.stdout.write(
2221
3003
  JSON.stringify({
@@ -2228,7 +3010,7 @@ process.stdout.write(
2228
3010
  );
2229
3011
  } finally {
2230
3012
  if (assemblyCrane) {
2231
- rmSync(assemblyCrane.temporary, { force: true, recursive: true });
3013
+ removeExactReleaseToolTemporaryDirectory(assemblyCrane.temporary);
2232
3014
  }
2233
3015
  }
2234
3016
  `;