@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
@@ -1,911 +1,1045 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.prepareLocalSstBackend = prepareLocalSstBackend;
4
- exports.localPlanSummaryDigest = localPlanSummaryDigest;
3
+ exports.localWorkingTreeDigest = localWorkingTreeDigest;
4
+ exports.localDeploymentProjectOrder = localDeploymentProjectOrder;
5
5
  exports.runLocalDeploy = runLocalDeploy;
6
6
  const node_crypto_1 = require("node:crypto");
7
7
  const node_child_process_1 = require("node:child_process");
8
8
  const node_fs_1 = require("node:fs");
9
- const node_os_1 = require("node:os");
10
9
  const node_path_1 = require("node:path");
11
- const client_sts_1 = require("@aws-sdk/client-sts");
12
- const client_ssm_1 = require("@aws-sdk/client-ssm");
13
- const credential_providers_1 = require("@aws-sdk/credential-providers");
14
10
  const devkit_1 = require("@nx/devkit");
15
- const index_js_1 = require("../../infrastructure-change/index.js");
16
- const local_capability_js_1 = require("../../infrastructure-change/local-capability.js");
11
+ const client_sts_1 = require("@aws-sdk/client-sts");
12
+ const index_js_1 = require("../../aws-authority/index.js");
13
+ const child_environment_js_1 = require("../../aws/child-environment.js");
14
+ const identity_js_1 = require("../../aws/identity.js");
17
15
  const index_js_2 = require("../../repository-policy/index.js");
18
- const source_authority_js_1 = require("../../release/source-authority.js");
16
+ const production_database_safeguard_js_1 = require("../foundation-profile-preflight/production-database-safeguard.js");
19
17
  const private_state_js_1 = require("../../local-dev/private-state.js");
20
- const infrastructure_plan_js_1 = require("../infrastructure-plan/infrastructure-plan.js");
21
- const sst_environment_source_js_1 = require("../sst-lifecycle/sst-environment-source.js");
18
+ const federated_credential_transport_js_1 = require("./federated-credential-transport.js");
22
19
  const local_approval_js_1 = require("./local-approval.js");
23
- const SHA = /^[a-f0-9]{40}$/;
24
- const SHA256 = /^[a-f0-9]{64}$/;
25
- const MAX_PLAN_BYTES = 2 * 1024 * 1024;
26
- const ROLE = /^arn:(aws|aws-us-gov|aws-cn):iam::\d{12}:role\/[A-Za-z0-9+=,.@_/-]{1,512}$/;
27
- const AMBIENT_CREDENTIAL_KEYS = [
28
- 'AWS_ACCESS_KEY_ID',
29
- 'AWS_SECRET_ACCESS_KEY',
30
- 'AWS_SESSION_TOKEN',
31
- 'AWS_SECURITY_TOKEN',
32
- 'AWS_ROLE_ARN',
33
- 'AWS_ROLE_SESSION_NAME',
34
- 'AWS_WEB_IDENTITY_TOKEN_FILE',
35
- 'AWS_CONTAINER_AUTHORIZATION_TOKEN',
36
- 'AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE',
37
- 'AWS_CONTAINER_CREDENTIALS_FULL_URI',
38
- 'AWS_CONTAINER_CREDENTIALS_RELATIVE_URI',
39
- ];
40
- const PROTECTED_PLAN_KEYS = [
41
- 'EBK_LOCAL_MUTATION_CAPABILITY_NONCE',
42
- 'EBK_LOCAL_MUTATION_CAPABILITY_PATH',
43
- 'EBK_LOCAL_PLAN_APPROVER_ARN',
44
- 'EBK_LOCAL_PLAN_MATERIAL_DIGEST',
45
- 'EBK_LOCAL_PLAN_SUMMARY_DIGEST',
46
- 'AWS_PLAN_GENERATED_OBJECT_ARNS_JSON',
47
- 'AWS_PLAN_GENERATED_PARAMETER_ARNS_JSON',
48
- 'AWS_PLAN_GENERATED_SECRET_ARNS_JSON',
49
- 'AWS_PLAN_PROVIDER_REFRESH_POLICY_JSON',
50
- 'AWS_PLAN_STATE_KMS_KEY_ARN',
51
- 'EBK_RELEASE_SOURCE_COMMIT',
52
- ];
53
- function projectEvidencePath(context, project, name) {
54
- const configured = context.projectsConfigurations?.projects[project]?.root;
55
- if (!configured) {
56
- throw new Error(`Local deployment references unknown project ${project}.`);
57
- }
58
- return (0, node_path_1.join)((0, node_path_1.isAbsolute)(configured) ? configured : (0, node_path_1.join)(context.root, configured), '.ebk', 'evidence', name);
59
- }
60
- function writeLocalMutationCapability(input) {
61
- const nonce = (0, node_crypto_1.randomBytes)(32).toString('hex');
62
- const directory = (0, node_fs_1.mkdtempSync)((0, node_path_1.join)((0, node_os_1.tmpdir)(), 'ebk-local-mutation-'));
63
- (0, private_state_js_1.ensurePrivateDirectory)(directory);
64
- const path = (0, node_path_1.join)(directory, 'capability.json');
65
- const planContents = (0, node_fs_1.readFileSync)(projectEvidencePath(input.context, input.project, 'infrastructure-plan.json'), 'utf8');
66
- const authorizationContents = (0, node_fs_1.readFileSync)(projectEvidencePath(input.context, input.project, 'infrastructure-plan-authorization.json'), 'utf8');
67
- const capability = (0, local_capability_js_1.createLocalMutationCapability)({
68
- actorArn: input.actorArn,
69
- authorizationContents,
70
- deploymentProfile: input.deploymentProfile,
71
- deploymentSourceCommit: input.deploymentSourceCommit,
72
- deploymentSourceTree: input.deploymentSourceTree,
73
- expectedRoleArn: input.expectedRoleArn,
74
- issuedAt: input.issuedAt,
75
- nonce,
76
- plan: input.plan,
77
- planContents,
78
- project: input.project,
79
- releaseSourceTree: input.releaseSourceTree,
80
- });
81
- (0, private_state_js_1.atomicWritePrivateFile)(path, `${JSON.stringify(capability, null, 2)}\n`);
82
- return { directory, nonce, path };
83
- }
84
- function digest(value) {
85
- return (0, node_crypto_1.createHash)('sha256').update(value, 'utf8').digest('hex');
86
- }
87
- function ssmParameterAuthority(value, expectedName) {
88
- const parameter = record(value, expectedName);
89
- if (parameter['Name'] !== expectedName ||
90
- parameter['Type'] !== 'SecureString' ||
91
- typeof parameter['Version'] !== 'number' ||
92
- !Number.isSafeInteger(parameter['Version']) ||
93
- parameter['Version'] < 1) {
94
- throw new Error(`Local SST backend parameter ${expectedName} is missing or malformed.`);
95
- }
96
- return {
97
- name: expectedName,
98
- type: parameter['Type'],
99
- version: parameter['Version'],
100
- };
101
- }
102
- /** @internal */
103
- async function prepareLocalSstBackend(input, dependencies = {}) {
104
- if (input.applications.length === 0 ||
105
- new Set(input.applications).size !== input.applications.length ||
106
- input.applications.some((application) => !/^[a-z0-9](?:[a-z0-9-]{0,62}[a-z0-9])?$/.test(application))) {
107
- throw new Error('Local SST backend requires a unique exact application inventory.');
108
- }
109
- const client = new client_ssm_1.SSMClient({
110
- region: input.region,
111
- ...(input.profile
112
- ? { credentials: (0, credential_providers_1.fromIni)({ profile: input.profile }) }
113
- : {}),
114
- });
115
- const send = dependencies.send ??
116
- (async (command) => client.send(command));
117
- const bootstrapResponse = await send(new client_ssm_1.GetParameterCommand({ Name: '/sst/bootstrap' }));
118
- const bootstrapParameter = bootstrapResponse.Parameter;
119
- let bootstrap;
120
- try {
121
- bootstrap = record(JSON.parse(bootstrapParameter?.Value ?? ''), 'Local SST bootstrap value');
122
- }
123
- catch {
124
- throw new Error('Local deployment requires an existing exact SST v5 account bootstrap.');
125
- }
126
- const exactBootstrapKeys = [
127
- 'appsyncHttp',
128
- 'appsyncRealtime',
129
- 'asset',
130
- 'assetEcrRegistryId',
131
- 'assetEcrUrl',
132
- 'state',
133
- 'version',
134
- ];
135
- const state = bootstrap['state'];
136
- if (bootstrapParameter?.Name !== '/sst/bootstrap' ||
137
- bootstrapParameter.Type !== 'String' ||
138
- typeof bootstrapParameter.Version !== 'number' ||
139
- !Number.isSafeInteger(bootstrapParameter.Version) ||
140
- bootstrapParameter.Version < 1 ||
141
- Object.keys(bootstrap).sort().join(',') !== exactBootstrapKeys.join(',') ||
142
- bootstrap['version'] !== 5 ||
143
- typeof state !== 'string' ||
144
- !/^[a-z0-9][a-z0-9.-]{1,61}[a-z0-9]$/.test(state) ||
145
- typeof bootstrap['asset'] !== 'string' ||
146
- !/^[a-z0-9][a-z0-9.-]{1,61}[a-z0-9]$/.test(bootstrap['asset']) ||
147
- bootstrap['assetEcrRegistryId'] !== input.account ||
148
- bootstrap['assetEcrUrl'] !==
149
- `${input.account}.dkr.ecr.${input.region}.amazonaws.com/sst-asset` ||
150
- typeof bootstrap['appsyncHttp'] !== 'string' ||
151
- typeof bootstrap['appsyncRealtime'] !== 'string') {
152
- throw new Error('Local deployment requires an existing exact SST v5 account bootstrap.');
153
- }
154
- const parameterAuthority = new Map();
155
- const missingApplications = [];
156
- const createdApplications = [];
157
- for (const application of input.applications) {
158
- const name = `/sst/passphrase/${application}/${input.stage}`;
159
- try {
160
- const observed = await send(new client_ssm_1.GetParameterCommand({ Name: name }));
161
- parameterAuthority.set(application, ssmParameterAuthority(observed.Parameter, name));
162
- }
163
- catch (error) {
164
- if (!(error instanceof Error) || error.name !== 'ParameterNotFound') {
165
- throw error;
166
- }
167
- missingApplications.push(application);
168
- }
169
- }
170
- for (const application of missingApplications) {
171
- const name = `/sst/passphrase/${application}/${input.stage}`;
172
- await send(new client_ssm_1.PutParameterCommand({
173
- Description: `SST state passphrase for ${application}/${input.stage}`,
174
- Name: name,
175
- Overwrite: false,
176
- Type: 'SecureString',
177
- Value: dependencies.passphrase?.() ?? (0, node_crypto_1.randomBytes)(32).toString('base64'),
178
- }));
179
- createdApplications.push(application);
180
- const observed = await send(new client_ssm_1.GetParameterCommand({ Name: name }));
181
- parameterAuthority.set(application, ssmParameterAuthority(observed.Parameter, name));
182
- }
183
- const parameters = input.applications.map((application) => {
184
- const authority = parameterAuthority.get(application);
185
- if (!authority) {
186
- throw new Error(`Local SST backend parameter authority is absent for ${application}.`);
187
- }
188
- return authority;
189
- });
190
- return {
191
- authorityDigest: digest(JSON.stringify({
192
- applications: [...input.applications],
193
- bootstrap: {
194
- name: bootstrapParameter.Name,
195
- type: bootstrapParameter.Type,
196
- version: bootstrapParameter.Version,
197
- },
198
- parameters,
199
- region: input.region,
200
- stage: input.stage,
201
- state,
202
- })),
203
- createdApplications,
204
- stateBucketArn: `arn:aws:s3:::${state}`,
205
- };
20
+ const container_release_js_1 = require("../../supply-chain/container-release.js");
21
+ const run_sst_command_js_1 = require("../sst-lifecycle/run-sst-command.js");
22
+ const SHA = /^[a-f0-9]{40,64}$/u;
23
+ const ROLE = /^arn:(aws|aws-us-gov|aws-cn):iam::\d{12}:role\/[A-Za-z0-9+=,.@_/-]{1,512}$/u;
24
+ const MAX_LOCAL_RELEASE_CANDIDATE_FILE_BYTES = 64 * 1024;
25
+ const MAX_LOCAL_RELEASE_ARTIFACTS_BYTES = 64 * 1024 * 1024;
26
+ const MAX_LOCAL_RELEASE_CANDIDATE_FILES = 32;
27
+ const MAX_LOCAL_RELEASE_CANDIDATE_BYTES = MAX_LOCAL_RELEASE_ARTIFACTS_BYTES +
28
+ (MAX_LOCAL_RELEASE_CANDIDATE_FILES - 1) *
29
+ MAX_LOCAL_RELEASE_CANDIDATE_FILE_BYTES;
30
+ function partitionForRegion(region) {
31
+ if (region.startsWith('cn-'))
32
+ return 'aws-cn';
33
+ if (region.startsWith('us-gov-'))
34
+ return 'aws-us-gov';
35
+ return 'aws';
206
36
  }
207
37
  function git(root, args, run) {
208
38
  const result = run('git', args, {
209
39
  cwd: root,
210
40
  encoding: 'utf8',
211
41
  env: process.env,
212
- maxBuffer: 1024 * 1024,
42
+ maxBuffer: 32 * 1024 * 1024,
213
43
  shell: false,
214
44
  });
215
45
  if (result.status !== 0) {
216
46
  throw new Error(`Local deployment Git preflight failed: ${String(result.stderr).trim()}`);
217
47
  }
218
- return String(result.stdout).trim();
48
+ return String(result.stdout);
219
49
  }
220
- function gitRaw(root, args, run) {
221
- const result = run('git', args, {
222
- cwd: root,
223
- encoding: 'utf8',
224
- env: process.env,
225
- maxBuffer: 1024 * 1024,
226
- shell: false,
227
- });
228
- if (result.status !== 0 || String(result.stderr) !== '') {
229
- throw new Error(`Local deployment Git reconciliation failed: ${String(result.stderr).trim()}`);
50
+ function confinedWorkspacePath(root, candidate) {
51
+ if (!candidate || (0, node_path_1.isAbsolute)(candidate) || candidate.includes('\0')) {
52
+ throw new Error('Working-tree inventory contains an invalid path.');
230
53
  }
231
- return String(result.stdout);
54
+ const absolute = (0, node_path_1.join)(root, candidate);
55
+ const confined = (0, node_path_1.relative)(root, absolute);
56
+ if (confined === '..' || confined.startsWith('../') || (0, node_path_1.isAbsolute)(confined)) {
57
+ throw new Error('Working-tree inventory escapes the workspace.');
58
+ }
59
+ return absolute;
232
60
  }
233
- function toolOwnedSourceSnapshots(context, projects) {
234
- const workspaceRoot = (0, node_fs_1.realpathSync)(context.root);
235
- return projects.map((project) => {
236
- const configured = context.projectsConfigurations?.projects[project]?.root;
237
- if (!configured) {
238
- throw new Error(`Local deployment tool-owned source references unknown project ${project}.`);
61
+ /** Bind a development deployment to the exact tracked diff and untracked
62
+ * source bytes without requiring a commit. Ignored caches and build output are
63
+ * intentionally outside this identity. */
64
+ function localWorkingTreeDigest(root, run = node_child_process_1.spawnSync) {
65
+ const commit = git(root, ['rev-parse', 'HEAD'], run).trim();
66
+ if (!SHA.test(commit)) {
67
+ throw new Error('Local deployment could not resolve one exact Git HEAD.');
68
+ }
69
+ const status = git(root, ['status', '--porcelain=v1', '--untracked-files=all'], run);
70
+ const trackedDiff = git(root, ['diff', '--binary', 'HEAD'], run);
71
+ const untracked = git(root, ['ls-files', '--others', '--exclude-standard', '-z'], run)
72
+ .split('\0')
73
+ .filter(Boolean)
74
+ .sort();
75
+ const hash = (0, node_crypto_1.createHash)('sha256');
76
+ hash.update('ebk-working-tree-v1\0');
77
+ hash.update(commit);
78
+ hash.update('\0');
79
+ hash.update(status);
80
+ hash.update('\0');
81
+ hash.update(trackedDiff);
82
+ hash.update('\0');
83
+ for (const path of untracked) {
84
+ const absolute = confinedWorkspacePath(root, path);
85
+ const stat = (0, node_fs_1.lstatSync)(absolute);
86
+ hash.update(path);
87
+ hash.update('\0');
88
+ if (stat.isSymbolicLink()) {
89
+ hash.update('symlink\0');
90
+ hash.update((0, node_fs_1.readlinkSync)(absolute));
239
91
  }
240
- return (0, sst_environment_source_js_1.captureSstEnvironmentSourceTree)({
241
- projectRoot: (0, node_path_1.isAbsolute)(configured)
242
- ? configured
243
- : (0, node_path_1.join)(workspaceRoot, configured),
244
- workspaceRoot,
245
- });
246
- });
247
- }
248
- function nulSeparatedPaths(value) {
249
- return value.split('\0').filter((entry) => entry !== '');
250
- }
251
- function reconcileToolOwnedSource(root, snapshots, run) {
252
- for (const snapshot of snapshots) {
253
- (0, sst_environment_source_js_1.reconcileSstEnvironmentSourceTree)(snapshot);
92
+ else if (stat.isFile()) {
93
+ hash.update('file\0');
94
+ hash.update((0, node_fs_1.readFileSync)(absolute));
95
+ }
96
+ else {
97
+ throw new Error(`Untracked deployment source ${path} is not a regular file or symbolic link.`);
98
+ }
99
+ hash.update('\0');
254
100
  }
255
- const remaining = [
256
- ...nulSeparatedPaths(gitRaw(root, ['diff', 'HEAD', '--name-only', '--no-renames', '-z'], run)),
257
- ...nulSeparatedPaths(gitRaw(root, ['ls-files', '--others', '--exclude-standard', '-z'], run)),
258
- ];
259
- if (remaining.length > 0) {
260
- throw new Error(`Local deployment child changed source outside exact tool-owned files: ${remaining.sort().join(', ')}.`);
101
+ return { clean: status === '', commit, digest: hash.digest('hex') };
102
+ }
103
+ function assertDeploymentSourceUnchanged(root, expected, run) {
104
+ const current = localWorkingTreeDigest(root, run);
105
+ if (current.clean !== expected.clean ||
106
+ current.commit !== expected.commit ||
107
+ current.digest !== expected.workingTreeDigest) {
108
+ throw new Error('Deployment source changed after lifecycle authority was established; downstream execution is blocked.');
261
109
  }
262
110
  }
263
- function projectInventory(context, stage, phase, action) {
264
- const projects = Object.entries(context.projectsConfigurations?.projects ?? {}).map(([name, configuration]) => ({
111
+ function projectDescriptors(context) {
112
+ return Object.entries(context.projectsConfigurations?.projects ?? {}).map(([name, configuration]) => ({
265
113
  name,
266
- tags: configuration.tags,
267
- targets: configuration.targets,
114
+ root: configuration.root,
115
+ tags: configuration.tags ?? [],
116
+ targets: configuration.targets ?? {},
268
117
  }));
269
- const policy = (0, index_js_2.readRepositoryOperatingPolicy)(context.root);
270
- const foundations = projects
271
- .filter(({ tags }) => tags?.includes('scope:foundation'))
272
- .map(({ name }) => name);
273
- if (foundations.length !== 1) {
274
- throw new Error('Local deployment requires exactly one Foundation project.');
118
+ }
119
+ function supportsTarget(project, target) {
120
+ return Object.prototype.hasOwnProperty.call(project.targets, target);
121
+ }
122
+ const LOCAL_CONTAINER_TARGETS = Object.freeze([
123
+ 'release-candidate',
124
+ 'container-release-amd64',
125
+ 'container-release-arm64',
126
+ 'container-release-assemble',
127
+ ]);
128
+ function requiresLocalContainerPreparation(profile, stage) {
129
+ return ((profile === 'starter' && stage === 'production') ||
130
+ (profile === 'production-isolated' && stage === 'staging'));
131
+ }
132
+ function confinedEvidenceSegments(root, candidate) {
133
+ const confined = (0, node_path_1.relative)(root, candidate);
134
+ if (confined === '' ||
135
+ confined === '..' ||
136
+ confined.startsWith(`..${node_path_1.sep}`) ||
137
+ (0, node_path_1.isAbsolute)(confined)) {
138
+ throw new Error('Local container evidence path escapes the workspace.');
139
+ }
140
+ const segments = confined.split(node_path_1.sep);
141
+ if (segments.some((segment) => segment === '' ||
142
+ segment === '.' ||
143
+ segment === '..' ||
144
+ segment.includes('\0'))) {
145
+ throw new Error('Local container evidence path is invalid.');
146
+ }
147
+ return segments;
148
+ }
149
+ function assertSafeEvidenceTree(path) {
150
+ let entries = 0;
151
+ const visit = (directory) => {
152
+ for (const entry of (0, node_fs_1.readdirSync)(directory, { withFileTypes: true })) {
153
+ entries += 1;
154
+ if (entries > 4096) {
155
+ throw new Error('Local container evidence tree is unexpectedly large.');
156
+ }
157
+ const child = (0, node_path_1.join)(directory, entry.name);
158
+ const identity = (0, node_fs_1.lstatSync)(child);
159
+ if (identity.isSymbolicLink()) {
160
+ throw new Error('Local container evidence cleanup refuses symbolic links.');
161
+ }
162
+ if (identity.isDirectory()) {
163
+ visit(child);
164
+ }
165
+ else if (!identity.isFile()) {
166
+ throw new Error('Local container evidence cleanup accepts only regular files and directories.');
167
+ }
168
+ }
169
+ };
170
+ visit(path);
171
+ }
172
+ function removeExactLocalEvidenceDirectory(root, path) {
173
+ const segments = confinedEvidenceSegments(root, path);
174
+ let current = root;
175
+ const rootIdentity = (0, node_fs_1.lstatSync)(current);
176
+ if (!rootIdentity.isDirectory() || rootIdentity.isSymbolicLink()) {
177
+ throw new Error('Local deployment workspace root is absent or unsafe.');
275
178
  }
276
- if (phase === 'foundation-bootstrap') {
277
- return foundations;
179
+ for (const segment of segments) {
180
+ current = (0, node_path_1.join)(current, segment);
181
+ if (!(0, node_fs_1.existsSync)(current))
182
+ return;
183
+ const identity = (0, node_fs_1.lstatSync)(current);
184
+ if (!identity.isDirectory() || identity.isSymbolicLink()) {
185
+ throw new Error('Local container evidence cleanup encountered an unsafe path.');
186
+ }
278
187
  }
279
- if (stage !== 'development') {
280
- return (0, index_js_2.createProtectedDeploymentPlan)(policy, {
281
- action,
282
- projects,
283
- stage,
284
- }).orderedProjects;
188
+ assertSafeEvidenceTree(current);
189
+ (0, node_fs_1.rmSync)(current, { recursive: true });
190
+ }
191
+ function ensurePrivateEvidenceDirectory(root, path) {
192
+ const segments = confinedEvidenceSegments(root, path);
193
+ let current = root;
194
+ for (const segment of segments) {
195
+ current = (0, node_path_1.join)(current, segment);
196
+ if ((0, node_fs_1.existsSync)(current)) {
197
+ const identity = (0, node_fs_1.lstatSync)(current);
198
+ if (!identity.isDirectory() || identity.isSymbolicLink()) {
199
+ throw new Error('Local container evidence directory encountered an unsafe path.');
200
+ }
201
+ }
202
+ (0, private_state_js_1.ensurePrivateDirectory)(current);
285
203
  }
286
- const slices = projects
287
- .filter(({ tags }) => tags?.includes('type:slice'))
288
- .map(({ name }) => name)
289
- .sort();
290
- return [foundations[0], ...slices];
291
204
  }
292
- function runNx(context, project, target, forwarded, environment, run) {
293
- const result = run(process.execPath, [
294
- (0, node_path_1.join)(context.root, 'node_modules', 'nx', 'dist', 'bin', 'nx.js'),
295
- 'run',
296
- `${project}:${target}`,
297
- '--',
298
- ...forwarded,
299
- ], {
300
- cwd: context.root,
301
- encoding: 'utf8',
302
- env: environment,
303
- maxBuffer: 16 * 1024 * 1024,
304
- shell: false,
305
- stdio: 'inherit',
306
- });
307
- if (result.status !== 0) {
308
- throw new Error(`Local ${target} failed for ${project}.`);
205
+ function resetLocalContainerWorkingEvidence(input) {
206
+ const projectRoot = confinedWorkspacePath(input.root, input.project.root);
207
+ for (const path of [
208
+ (0, node_path_1.join)(projectRoot, '.ebk', 'evidence', 'release-candidate'),
209
+ (0, node_path_1.join)(input.root, '.ebk', 'evidence', 'release-candidate', 'packages', input.project.name),
210
+ (0, node_path_1.join)(input.root, '.ebk', 'evidence', 'container-release', 'partials', input.stage, input.project.name),
211
+ (0, node_path_1.join)(input.root, '.ebk', 'evidence', 'container-release', input.stage, 'packages', input.project.name),
212
+ ]) {
213
+ removeExactLocalEvidenceDirectory(input.root, path);
309
214
  }
310
215
  }
311
- function runNxWithToolOwnedSourceReconciliation(context, project, target, forwarded, environment, snapshots, run, acceptExecutionFailure) {
312
- let executionError;
313
- try {
314
- runNx(context, project, target, forwarded, environment, run);
216
+ /** Materialize the same bounded workspace-level candidate layout restored by
217
+ * protected GitHub deployment. The generated container helper and SST image
218
+ * resolver both consume this exact path. */
219
+ function materializeLocalReleaseCandidate(input) {
220
+ if (!/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/u.test(input.project.name)) {
221
+ throw new Error(`Local container release project ${input.project.name} has an unsafe evidence identity.`);
315
222
  }
316
- catch (error) {
317
- executionError = error;
223
+ const projectRoot = confinedWorkspacePath(input.root, input.project.root);
224
+ const source = (0, node_path_1.join)(projectRoot, '.ebk', 'evidence', 'release-candidate');
225
+ const sourceIdentity = (0, node_fs_1.lstatSync)(source);
226
+ if (!sourceIdentity.isDirectory() || sourceIdentity.isSymbolicLink()) {
227
+ throw new Error(`Local release candidate for ${input.project.name} is absent or unsafe.`);
228
+ }
229
+ const entries = (0, node_fs_1.readdirSync)(source, { withFileTypes: true }).sort((left, right) => left.name < right.name ? -1 : left.name > right.name ? 1 : 0);
230
+ if (entries.length === 0 ||
231
+ entries.length > MAX_LOCAL_RELEASE_CANDIDATE_FILES ||
232
+ entries.some((entry) => !entry.isFile() ||
233
+ entry.isSymbolicLink() ||
234
+ !/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}\.json$/u.test(entry.name))) {
235
+ throw new Error(`Local release candidate for ${input.project.name} has an invalid file inventory.`);
236
+ }
237
+ let totalBytes = 0;
238
+ const files = entries.map(({ name }) => {
239
+ const path = (0, node_path_1.join)(source, name);
240
+ const identity = (0, node_fs_1.lstatSync)(path);
241
+ const maximumBytes = name === 'release-artifacts.json'
242
+ ? MAX_LOCAL_RELEASE_ARTIFACTS_BYTES
243
+ : MAX_LOCAL_RELEASE_CANDIDATE_FILE_BYTES;
244
+ if (!identity.isFile() ||
245
+ identity.isSymbolicLink() ||
246
+ identity.size <= 0 ||
247
+ identity.size > maximumBytes) {
248
+ throw new Error(`Local release candidate file ${input.project.name}/${name} is absent or unsafe.`);
249
+ }
250
+ totalBytes += identity.size;
251
+ return { name, path };
252
+ });
253
+ if (totalBytes > MAX_LOCAL_RELEASE_CANDIDATE_BYTES) {
254
+ throw new Error(`Local release candidate for ${input.project.name} exceeds the bounded inventory size.`);
318
255
  }
319
- let reconciliationError;
256
+ const candidateFiles = files.map(({ name, path }) => ({
257
+ contents: (0, node_fs_1.readFileSync)(path),
258
+ name,
259
+ }));
260
+ const candidateFile = candidateFiles.find(({ name }) => name === 'candidate.json');
261
+ let candidate;
320
262
  try {
321
- reconcileToolOwnedSource(context.root, snapshots, run);
263
+ candidate = JSON.parse(candidateFile?.contents.toString('utf8') ?? '');
322
264
  }
323
- catch (error) {
324
- reconciliationError = error;
265
+ catch {
266
+ throw new Error(`Local release candidate for ${input.project.name} is invalid JSON.`);
325
267
  }
326
- if (executionError !== undefined && reconciliationError !== undefined) {
327
- throw new Error(`Local ${target} and tool-owned source reconciliation both failed for ${project}. Execution: ${executionError instanceof Error ? executionError.message : String(executionError)} Reconciliation: ${reconciliationError instanceof Error ? reconciliationError.message : String(reconciliationError)}`);
268
+ if (candidate.kind !== 'ebk-release-candidate' ||
269
+ candidate.schemaVersion !== 1 ||
270
+ candidate.slice !== input.project.name ||
271
+ candidate.sourceCommit !== input.sourceCommit) {
272
+ throw new Error(`Local release candidate for ${input.project.name} differs from the exact deployment source.`);
328
273
  }
329
- if (reconciliationError !== undefined)
330
- throw reconciliationError;
331
- if (executionError !== undefined) {
332
- let accepted = false;
333
- try {
334
- accepted = acceptExecutionFailure?.() === true;
274
+ const destination = (0, node_path_1.join)(input.root, '.ebk', 'evidence', 'release-candidate', 'packages', input.project.name);
275
+ removeExactLocalEvidenceDirectory(input.root, destination);
276
+ ensurePrivateEvidenceDirectory(input.root, destination);
277
+ for (const { contents, name } of candidateFiles) {
278
+ if (name === 'release-artifacts.json') {
279
+ (0, private_state_js_1.atomicWritePrivateFileWithMaximumBytes)((0, node_path_1.join)(destination, name), contents, MAX_LOCAL_RELEASE_ARTIFACTS_BYTES);
335
280
  }
336
- catch {
337
- accepted = false;
281
+ else {
282
+ (0, private_state_js_1.atomicWritePrivateFile)((0, node_path_1.join)(destination, name), contents);
338
283
  }
339
- if (!accepted)
340
- throw executionError;
341
284
  }
342
285
  }
343
- function record(value, label) {
344
- if (value === null || typeof value !== 'object' || Array.isArray(value)) {
345
- throw new Error(`${label} must be a JSON object.`);
286
+ function attestLocalContainerBinding(input) {
287
+ const path = (0, node_path_1.join)(input.root, '.ebk', 'evidence', 'container-release', input.stage, 'packages', input.project.name, 'binding.json');
288
+ const identity = (0, node_fs_1.lstatSync)(path);
289
+ if (!identity.isFile() ||
290
+ identity.isSymbolicLink() ||
291
+ identity.size <= 0 ||
292
+ identity.size > 64 * 1024 * 1024) {
293
+ throw new Error(`Local protected container binding for ${input.project.name} is absent or unsafe.`);
294
+ }
295
+ const binding = (0, container_release_js_1.validateContainerReleaseBinding)(JSON.parse((0, node_fs_1.readFileSync)(path, 'utf8')));
296
+ if (binding.accountId !== input.account ||
297
+ binding.region !== input.region ||
298
+ binding.slice !== input.project.name ||
299
+ binding.sourceCommit !== input.sourceCommit ||
300
+ binding.stage !== input.stage) {
301
+ throw new Error(`Local protected container binding for ${input.project.name} differs from the exact deployment authority.`);
346
302
  }
347
- return value;
348
303
  }
349
- function retainedPlan(context, project, deploymentSourceCommit, account, region, stage, action) {
350
- const configured = context.projectsConfigurations?.projects[project]?.root;
351
- if (!configured) {
352
- throw new Error(`Local deployment references unknown project ${project}.`);
353
- }
354
- const workspaceRoot = (0, node_fs_1.realpathSync)(context.root);
355
- const projectRoot = (0, node_fs_1.realpathSync)((0, node_path_1.isAbsolute)(configured) ? configured : (0, node_path_1.join)(workspaceRoot, configured));
356
- const projectRelative = (0, node_path_1.relative)(workspaceRoot, projectRoot);
357
- if (projectRelative === '..' ||
358
- projectRelative.startsWith(`..${process.platform === 'win32' ? '\\' : '/'}`) ||
359
- (0, node_path_1.isAbsolute)(projectRelative)) {
360
- throw new Error(`Local deployment project ${project} escapes the workspace.`);
361
- }
362
- const path = (0, node_path_1.join)(projectRoot, '.ebk', 'evidence', 'infrastructure-plan.json');
363
- const before = (0, node_fs_1.lstatSync)(path, { bigint: true });
364
- if (!before.isFile() ||
365
- before.nlink !== 1n ||
366
- before.size > BigInt(MAX_PLAN_BYTES)) {
367
- throw new Error(`Local deployment plan for ${project} is not one bounded regular file.`);
368
- }
369
- const bytes = (0, node_fs_1.readFileSync)(path);
370
- const after = (0, node_fs_1.lstatSync)(path, { bigint: true });
371
- if (before.dev !== after.dev ||
372
- before.ino !== after.ino ||
373
- before.mode !== after.mode ||
374
- before.nlink !== after.nlink ||
375
- before.size !== after.size ||
376
- before.mtimeNs !== after.mtimeNs ||
377
- before.ctimeNs !== after.ctimeNs ||
378
- BigInt(bytes.byteLength) !== after.size) {
379
- throw new Error(`Local deployment plan for ${project} changed while it was read.`);
380
- }
381
- let parsed;
304
+ /** Read only the bounded public outputs SST writes after a successful native
305
+ * deploy. This is post-operation smoke input, never preview or mutation
306
+ * authority. Stage and project identity prevent a stale output file from
307
+ * selecting another deployment. */
308
+ function readDeployedSmokeTarget(workspaceRoot, project, stage) {
309
+ const projectRoot = confinedWorkspacePath(workspaceRoot, project.root);
310
+ const outputPath = (0, node_path_1.join)(projectRoot, '.sst', 'outputs.json');
311
+ const identity = (0, node_fs_1.lstatSync)(outputPath);
312
+ if (!identity.isFile() ||
313
+ identity.isSymbolicLink() ||
314
+ identity.size < 2 ||
315
+ identity.size > 64 * 1024) {
316
+ throw new Error(`Native SST outputs for ${project.name} are absent or unsafe.`);
317
+ }
318
+ const value = JSON.parse((0, node_fs_1.readFileSync)(outputPath, 'utf8'));
319
+ let api;
320
+ let web;
382
321
  try {
383
- parsed = record(JSON.parse(bytes.toString('utf8')), `${project} plan`);
322
+ api = new URL(String(value.apiUrl ?? ''));
323
+ web = new URL(String(value.webUrl ?? ''));
384
324
  }
385
325
  catch {
386
- throw new Error(`Local deployment plan for ${project} is invalid JSON.`);
387
- }
388
- const plan = parsed;
389
- if (plan.kind !== index_js_1.INFRASTRUCTURE_PLAN_KIND ||
390
- plan.schemaVersion !== index_js_1.INFRASTRUCTURE_CHANGE_SCHEMA_VERSION ||
391
- plan.action !== action ||
392
- !SHA.test(plan.sourceCommit) ||
393
- plan.target?.accountFingerprint !== digest(account) ||
394
- plan.target?.region !== region ||
395
- plan.target?.stage !== stage ||
396
- !Array.isArray(plan.projects) ||
397
- plan.projects.length !== 1 ||
398
- plan.projects[0] !== project ||
399
- !SHA256.test(plan.materialDigest) ||
400
- (0, index_js_1.computeMaterialPlanDigest)(plan) !== plan.materialDigest) {
401
- throw new Error(`Local deployment plan for ${project} is not the exact current deployment authority.`);
402
- }
403
- if (plan.sourceCommit !== deploymentSourceCommit) {
404
- try {
405
- (0, source_authority_js_1.validateReviewedReleaseCheckout)({
406
- deploymentSourceCommit,
407
- releaseSourceCommit: plan.sourceCommit,
408
- repositoryRoot: context.root,
409
- reviewedSourceCommit: deploymentSourceCommit,
410
- });
411
- }
412
- catch {
413
- throw new Error(`Local deployment plan for ${project} is not bound through one exact approval-only source transition.`);
414
- }
326
+ throw new Error(`Native SST outputs for ${project.name} contain invalid smoke URLs.`);
415
327
  }
416
- return plan;
328
+ const credentialFreeHttps = (url) => url.protocol === 'https:' &&
329
+ !url.username &&
330
+ !url.password &&
331
+ !url.search &&
332
+ !url.hash;
333
+ if (value.name !== project.name ||
334
+ value.stage !== stage ||
335
+ !credentialFreeHttps(api) ||
336
+ !credentialFreeHttps(web) ||
337
+ api.pathname !== '/' ||
338
+ web.pathname.replace(/\/$/u, '') !== `/${project.name}` ||
339
+ !/^[a-z0-9]{1,128}$/u.test(String(value.authClientId ?? '')) ||
340
+ !/^[A-Za-z0-9_-]{1,128}_[A-Za-z0-9]{1,128}$/u.test(String(value.authUserPoolId ?? ''))) {
341
+ throw new Error(`Native SST outputs for ${project.name} do not match the deployed stage and smoke target.`);
342
+ }
343
+ return {
344
+ apiUrl: api.href,
345
+ clientId: String(value.authClientId),
346
+ userPoolId: String(value.authUserPoolId),
347
+ webUrl: web.href,
348
+ };
417
349
  }
418
- function hasExactPendingLocalAuthorizationRefusal(context, project, plan) {
419
- const path = projectEvidencePath(context, project, 'infrastructure-plan-authorization.json');
420
- try {
421
- const before = (0, node_fs_1.lstatSync)(path, { bigint: true });
422
- if (!before.isFile() ||
423
- before.nlink !== 1n ||
424
- before.size > BigInt(MAX_PLAN_BYTES)) {
425
- return false;
426
- }
427
- const bytes = (0, node_fs_1.readFileSync)(path);
428
- const after = (0, node_fs_1.lstatSync)(path, { bigint: true });
429
- if (before.dev !== after.dev ||
430
- before.ino !== after.ino ||
431
- before.mode !== after.mode ||
432
- before.nlink !== after.nlink ||
433
- before.size !== after.size ||
434
- before.mtimeNs !== after.mtimeNs ||
435
- before.ctimeNs !== after.ctimeNs ||
436
- BigInt(bytes.byteLength) !== after.size) {
437
- return false;
438
- }
439
- const authorization = record(JSON.parse(bytes.toString('utf8')), `${project} authorization`);
440
- return (authorization.kind === 'ebk-infrastructure-authorization' &&
441
- authorization.schemaVersion === index_js_1.INFRASTRUCTURE_CHANGE_SCHEMA_VERSION &&
442
- authorization.action === 'deploy' &&
443
- authorization.approval === undefined &&
444
- authorization.localGovernance === undefined &&
445
- authorization.governanceEvidenceDigests === undefined &&
446
- authorization.plannedMaterialDigest === plan.materialDigest &&
447
- authorization.recomputedMaterialDigest === plan.materialDigest &&
448
- Array.isArray(authorization.sourceChangesSincePlan) &&
449
- authorization.sourceChangesSincePlan.length === 0 &&
450
- authorization.validation.allowed === false &&
451
- authorization.validation.outcome === 'plan-risk-blocked' &&
452
- authorization.validation.findings.length === 1 &&
453
- authorization.validation.findings[0]?.code === 'approval-missing');
350
+ /** Determine the one native deployment order. Foundation is always first and
351
+ * production Slice admission remains a committed policy decision. */
352
+ function localDeploymentProjectOrder(policy, context, stage) {
353
+ if (!(0, index_js_2.isActiveDeploymentStage)(policy.deploymentProfile, stage)) {
354
+ throw new Error(`Deployment stage ${stage} is inactive for the ${policy.deploymentProfile} deployment profile.`);
454
355
  }
455
- catch {
456
- return false;
356
+ const projects = projectDescriptors(context);
357
+ const foundations = projects.filter((project) => project.tags.includes('scope:foundation') &&
358
+ supportsTarget(project, 'deploy'));
359
+ if (foundations.length !== 1) {
360
+ throw new Error(`Local deployment requires exactly one deployable Foundation; found ${foundations.length}.`);
457
361
  }
458
- }
459
- /** @internal */
460
- function localPlanSummaryDigest(input) {
461
- const action = input.action ?? 'deploy';
462
- if ((action !== 'deploy' && action !== 'refresh') ||
463
- !/^\d{12}$/.test(input.account) ||
464
- !SHA.test(input.sourceCommit) ||
465
- !/^[a-z]{2}(?:-gov)?-[a-z]+-\d$/.test(input.region) ||
466
- input.orderedProjects.length === 0 ||
467
- new Set(input.orderedProjects).size !== input.orderedProjects.length ||
468
- Object.keys(input.plans).length !== input.orderedProjects.length) {
469
- throw new Error('Local deployment plan summary input is invalid.');
470
- }
471
- const plans = input.orderedProjects.map((project) => {
472
- const plan = input.plans[project];
473
- if (!plan ||
474
- plan.action !== action ||
475
- plan.projects.length !== 1 ||
476
- plan.projects[0] !== project ||
477
- !SHA256.test(plan.materialDigest)) {
478
- throw new Error(`Local deployment plan summary omitted exact project ${project}.`);
362
+ const slices = projects
363
+ .filter((project) => project.tags.includes('type:slice') &&
364
+ supportsTarget(project, 'deploy'))
365
+ .sort((left, right) => left.name < right.name ? -1 : left.name > right.name ? 1 : 0);
366
+ if (stage === 'production') {
367
+ const missing = slices
368
+ .filter(({ name }) => policy.slices[name] === undefined)
369
+ .map(({ name }) => name);
370
+ if (missing.length > 0) {
371
+ throw new Error(`Registered Product Slices lack an explicit production policy: ${missing.join(', ')}.`);
479
372
  }
480
- return { materialDigest: plan.materialDigest, project };
373
+ }
374
+ const selectedSlices = stage === 'production'
375
+ ? slices.filter(({ name }) => {
376
+ const status = policy.slices[name]?.productionStatus;
377
+ return status === 'pilot' || status === 'ready';
378
+ })
379
+ : slices;
380
+ const selected = [
381
+ { kind: 'foundation', name: foundations[0]?.name ?? '' },
382
+ ...selectedSlices.map(({ name }) => ({ kind: 'slice', name })),
383
+ ];
384
+ const decision = (0, index_js_2.evaluateRepositoryPolicyRequest)(policy, {
385
+ action: 'deploy',
386
+ projects: selected,
387
+ stage,
481
388
  });
482
- return digest(JSON.stringify({
483
- accountFingerprint: digest(input.account),
484
- action,
485
- orderedProjects: [...input.orderedProjects],
486
- phase: input.phase,
487
- plans,
488
- region: input.region,
489
- sourceCommit: input.sourceCommit,
490
- stage: input.stage,
491
- }));
492
- }
493
- function retainEvidence(root, evidence) {
494
- const directory = (0, node_path_1.join)(root, '.ebk', 'evidence', 'local-deployment');
495
- (0, node_fs_1.mkdirSync)(directory, { recursive: true, mode: 0o700 });
496
- (0, node_fs_1.chmodSync)(directory, 0o700);
497
- (0, node_fs_1.writeFileSync)((0, node_path_1.join)(directory, 'latest.json'), `${JSON.stringify(evidence, null, 2)}\n`, { encoding: 'utf8', mode: 0o600 });
389
+ if (!decision.allowed) {
390
+ throw new Error(`Repository Operating Policy rejected local deployment:\n${decision.reasons.map((reason) => `- ${reason}`).join('\n')}`);
391
+ }
392
+ return selected.map(({ name }) => name);
498
393
  }
499
- const REFRESH_FRAMEWORK_TAG_OBSERVATION_PATHS = new Map([
500
- ['/inputs/tagsAll/ebk:framework-version', true],
501
- ['/outputs/tags/ebk:framework-version', false],
502
- ['/outputs/tagsAll/ebk:framework-version', false],
503
- ]);
504
- function isFrameworkTagProviderObservation(resource) {
505
- if (resource.operation !== 'update' ||
506
- resource.replacement ||
507
- resource.resourceType !== 'aws:ssm/parameter:Parameter' ||
508
- resource.changedProperties.length !==
509
- REFRESH_FRAMEWORK_TAG_OBSERVATION_PATHS.size) {
510
- return false;
511
- }
512
- const observed = new Set();
513
- for (const property of resource.changedProperties) {
514
- const expectedInputDiff = REFRESH_FRAMEWORK_TAG_OBSERVATION_PATHS.get(property.path);
515
- if (expectedInputDiff === undefined ||
516
- property.inputDiff !== expectedInputDiff ||
517
- property.kind !== 'update' ||
518
- property.sensitive ||
519
- observed.has(property.path)) {
520
- return false;
394
+ function frameworkVersion(root) {
395
+ const candidates = [
396
+ (0, node_path_1.join)(root, 'node_modules', '@empire-builder-kit', 'nx', 'package.json'),
397
+ (0, node_path_1.join)(root, 'package.json'),
398
+ ];
399
+ for (const path of candidates) {
400
+ try {
401
+ const value = JSON.parse((0, node_fs_1.readFileSync)(path, 'utf8'));
402
+ if (value.name === '@empire-builder-kit/nx' && value.version) {
403
+ return value.version;
404
+ }
405
+ const dependency = value.devDependencies?.['@empire-builder-kit/nx'] ??
406
+ value.dependencies?.['@empire-builder-kit/nx'];
407
+ if (dependency)
408
+ return dependency;
409
+ }
410
+ catch {
411
+ // Try the next deterministic package identity.
521
412
  }
522
- observed.add(property.path);
523
413
  }
524
- return observed.size === REFRESH_FRAMEWORK_TAG_OBSERVATION_PATHS.size;
414
+ return 'unknown';
525
415
  }
526
- function isSemanticallyConvergedRefresh(plan) {
527
- if (plan.action !== 'refresh' ||
528
- plan.state.locked ||
529
- !plan.diff.complete ||
530
- plan.diff.failures.length > 0) {
531
- return false;
532
- }
533
- if (plan.diff.resources.length === 0) {
534
- return plan.driftStatus === 'no-drift';
535
- }
536
- if (plan.driftStatus !== 'drift-detected' ||
537
- !plan.diff.resources.every(isFrameworkTagProviderObservation) ||
538
- plan.requiredApprovals.some((approval) => ![
539
- 'deployment-review',
540
- 'drift-decision-and-state-recovery',
541
- 'point-of-no-return',
542
- ].includes(approval))) {
543
- return false;
544
- }
545
- const observedResourceIds = new Set(plan.diff.resources.map(({ logicalId }) => logicalId));
546
- return plan.resourceFindings.every((finding) => (finding.resourceId === undefined &&
547
- finding.class === 'stateful-or-destructive' &&
548
- finding.code === 'protected-state-refresh' &&
549
- finding.disposition === 'block') ||
550
- (finding.resourceId !== undefined &&
551
- observedResourceIds.has(finding.resourceId) &&
552
- finding.class === 'in-place-reviewed' &&
553
- finding.code === 'in-place-update' &&
554
- finding.disposition === 'review'));
416
+ function prepareReceiptDestination(root) {
417
+ const directory = (0, node_path_1.join)(root, '.ebk', 'evidence', 'deployment');
418
+ ensurePrivateEvidenceDirectory(root, directory);
419
+ const path = (0, node_path_1.join)(directory, 'latest.json');
420
+ if ((0, node_fs_1.existsSync)(path))
421
+ (0, private_state_js_1.assertPrivatePath)(path, 'file');
422
+ return path;
555
423
  }
556
- function isSemanticallyConvergedDeploy(plan) {
557
- if (plan.action !== 'deploy' ||
558
- plan.state.locked !== false ||
559
- !plan.diff.complete ||
560
- plan.diff.failures.length > 0) {
561
- return false;
562
- }
563
- if (plan.diff.resources.length === 0) {
564
- return (plan.aggregateRisk === 'no-change' &&
565
- plan.driftStatus === 'no-drift' &&
566
- plan.requiredApprovals.length === 0 &&
567
- plan.resourceFindings.length === 0);
568
- }
569
- if (plan.aggregateRisk !== 'in-place-reviewed' ||
570
- (plan.driftStatus !== 'no-drift' &&
571
- plan.driftStatus !== 'drift-detected') ||
572
- plan.requiredApprovals.length !== 1 ||
573
- plan.requiredApprovals[0] !== 'deployment-review') {
574
- return false;
575
- }
576
- const providerReads = new Map();
577
- for (const resource of plan.diff.resources) {
578
- const retainedResourceType = providerReads.get(resource.logicalId);
579
- if (resource.operation !== 'read' ||
580
- resource.providerOperation !== 'read' ||
581
- resource.replacement ||
582
- resource.changedProperties.length > 0 ||
583
- (retainedResourceType !== undefined &&
584
- retainedResourceType !== resource.resourceType)) {
585
- return false;
586
- }
587
- providerReads.set(resource.logicalId, resource.resourceType);
588
- }
589
- if (plan.resourceFindings.length !== providerReads.size) {
590
- return false;
591
- }
592
- const providerReadFindings = new Set();
593
- for (const finding of plan.resourceFindings) {
594
- if (finding.resourceId === undefined ||
595
- !providerReads.has(finding.resourceId) ||
596
- finding.class !== 'in-place-reviewed' ||
597
- finding.code !== 'provider-read' ||
598
- finding.disposition !== 'review' ||
599
- providerReadFindings.has(finding.resourceId)) {
600
- return false;
601
- }
602
- providerReadFindings.add(finding.resourceId);
424
+ function writeReceipt(root, receipt) {
425
+ (0, private_state_js_1.atomicWritePrivateFile)(prepareReceiptDestination(root), `${JSON.stringify(receipt, null, 2)}\n`);
426
+ }
427
+ function invalidatePriorReceipt(root) {
428
+ const path = prepareReceiptDestination(root);
429
+ if (!(0, node_fs_1.existsSync)(path))
430
+ return;
431
+ (0, private_state_js_1.assertPrivatePath)(path, 'file');
432
+ (0, node_fs_1.rmSync)(path, { force: true });
433
+ if ((0, node_fs_1.existsSync)(path)) {
434
+ throw new Error('Prior deployment receipt invalidation failed.');
435
+ }
436
+ }
437
+ async function runNxTarget(input) {
438
+ const forwarded = [
439
+ `--stage=${input.stage}`,
440
+ `--expectedRoleArn=${input.expectedRoleArn}`,
441
+ `--region=${input.region}`,
442
+ ...(input.profile ? [`--profile=${input.profile}`] : []),
443
+ ];
444
+ const args = [
445
+ (0, node_path_1.join)(input.context.root, 'node_modules', 'nx', 'dist', 'bin', 'nx.js'),
446
+ 'run',
447
+ `${input.project}:${input.target}`,
448
+ '--',
449
+ ...forwarded,
450
+ ];
451
+ const result = input.runSync
452
+ ? input.runSync(process.execPath, args, {
453
+ cwd: input.context.root,
454
+ env: input.environment,
455
+ shell: false,
456
+ stdio: 'inherit',
457
+ })
458
+ : await (0, run_sst_command_js_1.runGovernedChildProcess)({
459
+ args,
460
+ cwd: input.context.root,
461
+ environment: input.environment,
462
+ executable: process.execPath,
463
+ ...(input.spawnChild ? { spawnChild: input.spawnChild } : {}),
464
+ });
465
+ if (result.error || result.signal || result.status !== 0) {
466
+ throw new Error(result.error
467
+ ? `${input.target} failed for ${input.project}: ${result.error.message}`
468
+ : result.signal
469
+ ? `${input.target} failed for ${input.project} with signal ${result.signal}.`
470
+ : `${input.target} failed for ${input.project} with status ${result.status ?? 'unknown'}.`);
603
471
  }
604
- return providerReadFindings.size === providerReads.size;
605
472
  }
606
- function isFreshInvocationPlan(plan, invocationStartedAt, observedAt) {
607
- const started = Date.parse(invocationStartedAt);
608
- const generated = Date.parse(plan.generatedAt);
609
- const observed = observedAt.getTime();
610
- const expires = Date.parse(plan.expiresAt);
611
- return (Number.isFinite(started) &&
612
- Number.isFinite(generated) &&
613
- Number.isFinite(observed) &&
614
- Number.isFinite(expires) &&
615
- generated >= started &&
616
- generated <= observed &&
617
- expires > observed);
473
+ function productionConfirmation(account, region, commit) {
474
+ return `deploy:production:${account}:${region}:${commit}`;
475
+ }
476
+ async function defaultIdentifyGithubCaller(region) {
477
+ return new client_sts_1.STSClient({
478
+ endpoint: (0, identity_js_1.canonicalStsEndpoint)(region),
479
+ region,
480
+ }).send(new client_sts_1.GetCallerIdentityCommand({}));
481
+ }
482
+ function assertGithubDeploymentAuthority(input) {
483
+ const repository = process.env.GITHUB_REPOSITORY?.trim() ?? '';
484
+ const ref = process.env.GITHUB_REF?.trim() ?? '';
485
+ const eventName = process.env.GITHUB_EVENT_NAME?.trim() ?? '';
486
+ const callerWorkflow = process.env.GITHUB_WORKFLOW_REF?.trim() ?? '';
487
+ const calledWorkflow = process.env.EBK_GITHUB_JOB_WORKFLOW_REF?.trim() ?? '';
488
+ const expectedBranch = input.stage === 'production' ? 'main' : 'develop';
489
+ const expectedRef = `refs/heads/${expectedBranch}`;
490
+ const expectedCalledWorkflow = `${repository}/.github/workflows/deploy.yml@${expectedRef}`;
491
+ const expectedCallerWorkflow = eventName === 'push'
492
+ ? `${repository}/.github/workflows/ci.yml@${expectedRef}`
493
+ : eventName === 'workflow_dispatch'
494
+ ? expectedCalledWorkflow
495
+ : undefined;
496
+ const allowedEvent = eventName === 'workflow_dispatch' ||
497
+ (eventName === 'push' && input.policy.deploymentProfile !== 'starter');
498
+ if (process.env.GITHUB_ACTIONS !== 'true' ||
499
+ !repository ||
500
+ !allowedEvent ||
501
+ process.env.GITHUB_SHA !== input.commit ||
502
+ ref !== expectedRef ||
503
+ callerWorkflow !== expectedCallerWorkflow ||
504
+ calledWorkflow !== expectedCalledWorkflow) {
505
+ throw new Error('Protected deployment requires the exact generated GitHub caller and reusable deploy workflows, branch, and immutable source commit.');
506
+ }
507
+ if (input.policy.deploymentProfile === 'starter' &&
508
+ input.stage === 'staging') {
509
+ throw new Error('The starter deployment profile has no staging stage.');
510
+ }
511
+ const clean = git(input.root, ['status', '--porcelain=v1', '--untracked-files=all'], input.run);
512
+ if (clean !== '') {
513
+ throw new Error('Protected GitHub deployment requires a clean checkout.');
514
+ }
618
515
  }
619
516
  async function runLocalDeploy(options, context, dependencies = {}) {
620
517
  const run = dependencies.run ?? node_child_process_1.spawnSync;
621
- if (process.env.GITHUB_ACTIONS === 'true') {
622
- throw new Error('Local deployment is not a GitHub Actions authority mode.');
623
- }
624
- const policy = (0, index_js_2.readRepositoryOperatingPolicy)(context.root);
625
- const action = options.action ?? 'deploy';
626
- if (action !== 'deploy' && action !== 'refresh') {
627
- throw new Error('Local infrastructure action is invalid.');
628
- }
629
- const phase = options.phase ?? 'full-stage';
630
- if (phase !== 'foundation-bootstrap' && phase !== 'full-stage') {
631
- throw new Error('Local deployment phase is invalid.');
632
- }
633
- if ((0, index_js_2.requiresGithubDeployment)(policy.deploymentProfile, options.stage)) {
634
- throw new Error(`The ${policy.deploymentProfile} deployment profile requires GitHub deployment authority for ${options.stage}.`);
635
- }
636
- const accounts = (0, index_js_2.configuredDeploymentAccountTopology)(policy.deploymentProfile, policy.deploymentAccounts);
637
- const region = (0, index_js_2.configuredDeploymentRegion)(policy.deploymentRegion);
638
- const expectedAccount = accounts[options.stage];
639
- if (!ROLE.test(options.expectedRoleArn)) {
640
- throw new Error('Local deployment requires one exact expected IAM role ARN.');
641
- }
642
- const status = git(context.root, ['status', '--porcelain=v1', '--untracked-files=all'], run);
643
- if (status !== '') {
644
- throw new Error('Local deployment requires a completely clean source tree.');
645
- }
646
- const sourceCommit = git(context.root, ['rev-parse', 'HEAD'], run);
647
- const sourceTree = git(context.root, ['rev-parse', 'HEAD^{tree}'], run);
648
- if (!SHA.test(sourceCommit) || !SHA.test(sourceTree)) {
649
- throw new Error('Local deployment could not bind one immutable Git source.');
650
- }
651
- const expectedConfirmation = (0, infrastructure_plan_js_1.localProductionConfirmation)({
652
- action,
653
- accountId: expectedAccount,
654
- region,
655
- sourceCommit,
656
- });
657
- if (options.stage === 'production' &&
658
- options.confirmation !== expectedConfirmation) {
659
- throw new Error(`Local production ${action} requires --confirmation=${expectedConfirmation}.`);
660
- }
661
- const caller = await (dependencies.caller ??
662
- (async (targetRegion) => new client_sts_1.STSClient({
663
- region: targetRegion,
664
- ...(options.profile
665
- ? { credentials: (0, credential_providers_1.fromIni)({ profile: options.profile }) }
666
- : {}),
667
- }).send(new client_sts_1.GetCallerIdentityCommand({}))))(region);
668
- const account = caller.Account?.trim() ?? '';
669
- const arn = caller.Arn?.trim() ?? '';
670
- if (account !== expectedAccount ||
671
- !(0, infrastructure_plan_js_1.matchesExpectedAssumedRoleArn)(expectedAccount, arn, options.expectedRoleArn)) {
672
- throw new Error('Local AWS caller does not match the committed account and exact temporary assumed role.');
673
- }
674
- const orderedProjects = projectInventory(context, options.stage, phase, action);
675
- const sourceSnapshots = toolOwnedSourceSnapshots(context, orderedProjects);
676
- const startedAt = (dependencies.now ?? (() => new Date()))().toISOString();
677
- let backend;
678
- let result = 'failed';
679
- let planDigests = {};
680
- let planSummaryDigest = null;
681
- let deployNoOpProjects = [];
682
- const mutatedProjects = [];
683
- let operatorAcknowledgementDigest = null;
684
- let replanRequiredProject = null;
685
- let refreshNoOpProjects = [];
686
- try {
687
- backend = await (dependencies.backend ?? prepareLocalSstBackend)({
688
- account: expectedAccount,
689
- applications: orderedProjects,
690
- ...(options.profile ? { profile: options.profile } : {}),
518
+ const now = dependencies.now ?? (() => new Date());
519
+ const startedAt = now().toISOString();
520
+ const children = [];
521
+ const migrations = {};
522
+ const smoke = {};
523
+ let account = '';
524
+ let actor = '';
525
+ let policy;
526
+ let region = '';
527
+ let source = { clean: false, commit: '', workingTreeDigest: '' };
528
+ let transport;
529
+ let productionDatabaseBefore;
530
+ let productionDatabaseAfterFoundation;
531
+ let outcome = 'failed';
532
+ let receiptDestinationPrepared = false;
533
+ const githubAuthority = process.env.GITHUB_ACTIONS === 'true';
534
+ const retain = () => {
535
+ if (!policy || !receiptDestinationPrepared)
536
+ return;
537
+ writeReceipt(context.root, {
538
+ account,
539
+ actor,
540
+ authority: githubAuthority ? 'github' : 'local',
541
+ children,
542
+ completedAt: now().toISOString(),
543
+ frameworkVersion: frameworkVersion(context.root),
544
+ kind: 'ebk-deployment-receipt',
545
+ migrations,
546
+ outcome,
547
+ profile: policy.deploymentProfile,
691
548
  region,
549
+ schemaVersion: 1,
550
+ smoke,
551
+ source,
692
552
  stage: options.stage,
553
+ startedAt,
693
554
  });
694
- const environment = {
695
- ...process.env,
696
- AWS_EXPECTED_ACCOUNT_ID: expectedAccount,
697
- AWS_PLAN_ROLE_ARN: options.expectedRoleArn,
698
- AWS_PLAN_SST_APPS_JSON: JSON.stringify(orderedProjects),
699
- AWS_PLAN_STATE_BUCKET_ARN: backend.stateBucketArn,
700
- ...(options.profile ? { AWS_PROFILE: options.profile } : {}),
701
- AWS_REGION: region,
702
- // Local deployment's reviewed clean Git commit is the one stage-wide
703
- // workload-role candidate shared by Foundation and every Slice. Never
704
- // accept an ambient candidate identity from the caller.
705
- EBK_ACTION: action,
706
- EBK_CANDIDATE_IDENTITY: sourceCommit,
707
- EBK_LOCAL_DEPLOYMENT_MODE: 'federated',
708
- EBK_LOCAL_SOURCE_TREE: sourceTree,
709
- EBK_SOURCE_COMMIT: sourceCommit,
710
- EBK_STAGE: options.stage,
711
- ORDERED_PROJECTS: orderedProjects.join(','),
712
- };
713
- for (const key of PROTECTED_PLAN_KEYS)
714
- delete environment[key];
715
- if (options.profile) {
716
- for (const key of AMBIENT_CREDENTIAL_KEYS)
717
- delete environment[key];
555
+ };
556
+ try {
557
+ invalidatePriorReceipt(context.root);
558
+ receiptDestinationPrepared = true;
559
+ (0, run_sst_command_js_1.assertGovernedAwsLifecyclePlatform)(dependencies.platform);
560
+ policy = (0, index_js_2.readRepositoryOperatingPolicy)(context.root);
561
+ if (!(0, index_js_2.isActiveDeploymentStage)(policy.deploymentProfile, options.stage)) {
562
+ throw new Error(`Deployment stage ${options.stage} is inactive for the ${policy.deploymentProfile} deployment profile.`);
718
563
  }
719
- const common = [
720
- `--stage=${options.stage}`,
721
- `--expectedRoleArn=${options.expectedRoleArn}`,
722
- `--region=${region}`,
723
- ...(options.profile ? [`--profile=${options.profile}`] : []),
724
- ];
725
- for (const project of orderedProjects) {
726
- runNxWithToolOwnedSourceReconciliation(context, project, 'infrastructure-plan', [`--action=${action}`, ...common], environment, sourceSnapshots, run, action === 'deploy'
727
- ? () => {
728
- const plan = retainedPlan(context, project, sourceCommit, expectedAccount, region, options.stage, action);
729
- const accepted = isFreshInvocationPlan(plan, startedAt, (dependencies.now ?? (() => new Date()))()) &&
730
- isSemanticallyConvergedDeploy(plan) &&
731
- hasExactPendingLocalAuthorizationRefusal(context, project, plan);
732
- if (accepted) {
733
- devkit_1.logger.info(`Local infrastructure-plan for ${project} returned a conservative non-success after emitting one fresh provider-read-only semantic convergence record.`);
734
- }
735
- return accepted;
736
- }
737
- : undefined);
564
+ if (!githubAuthority &&
565
+ (0, index_js_2.requiresGithubDeployment)(policy.deploymentProfile, options.stage)) {
566
+ throw new Error(`The ${policy.deploymentProfile} profile requires GitHub deployment authority for ${options.stage}.`);
567
+ }
568
+ if (!ROLE.test(options.expectedRoleArn)) {
569
+ throw new Error('Local deployment requires one exact IAM role ARN.');
738
570
  }
739
- const plans = Object.fromEntries(orderedProjects.map((project) => [
740
- project,
741
- retainedPlan(context, project, sourceCommit, expectedAccount, region, options.stage, action),
742
- ]));
743
- planDigests = Object.fromEntries(orderedProjects.map((project) => [
744
- project,
745
- plans[project].materialDigest,
746
- ]));
747
- planSummaryDigest = localPlanSummaryDigest({
748
- account: expectedAccount,
749
- action,
750
- orderedProjects,
751
- phase,
752
- plans,
571
+ region = (0, index_js_2.configuredDeploymentRegion)(policy.deploymentRegion);
572
+ account = (0, index_js_2.configuredDeploymentAccountForStage)(policy.deploymentProfile, policy.deploymentAccounts, options.stage);
573
+ const expectedRoleArn = (0, index_js_1.createStageAuthorityIdentity)({
574
+ accountId: account,
575
+ partition: partitionForRegion(region),
753
576
  region,
754
- sourceCommit,
755
577
  stage: options.stage,
756
- });
757
- const approvalEnvironment = {
758
- ...environment,
759
- EBK_LOCAL_PLAN_APPROVER_ARN: arn,
760
- EBK_LOCAL_PLAN_SUMMARY_DIGEST: planSummaryDigest,
578
+ }).deploymentRoleArn;
579
+ if (options.expectedRoleArn !== expectedRoleArn) {
580
+ throw new Error(`Deployment role must equal the committed ${options.stage} stage authority role ${expectedRoleArn}.`);
581
+ }
582
+ const identity = localWorkingTreeDigest(context.root, run);
583
+ source = {
584
+ clean: identity.clean,
585
+ commit: identity.commit,
586
+ workingTreeDigest: identity.digest,
761
587
  };
762
- const planEnvironment = (project) => plans[project].requiredApprovals.length > 0
763
- ? {
764
- ...approvalEnvironment,
765
- EBK_LOCAL_PLAN_MATERIAL_DIGEST: plans[project].materialDigest,
588
+ if ((githubAuthority || options.stage !== 'development') && !source.clean) {
589
+ throw new Error(`Local ${options.stage} deployment requires a completely clean source tree.`);
590
+ }
591
+ if (githubAuthority) {
592
+ if (options.profile) {
593
+ throw new Error('Protected GitHub deployment must use ambient OIDC credentials, not a local AWS profile.');
766
594
  }
767
- : environment;
768
- deployNoOpProjects =
769
- action === 'deploy'
770
- ? orderedProjects.filter((project) => isSemanticallyConvergedDeploy(plans[project]))
771
- : [];
772
- refreshNoOpProjects =
773
- action === 'refresh'
774
- ? orderedProjects.filter((project) => isSemanticallyConvergedRefresh(plans[project]))
775
- : [];
776
- const mutationNoOpProjectSet = new Set([
777
- ...deployNoOpProjects,
778
- ...refreshNoOpProjects,
779
- ]);
780
- const materialProjectIndex = orderedProjects.findIndex((project) => !mutationNoOpProjectSet.has(project));
781
- if (materialProjectIndex >= 0) {
782
- const project = orderedProjects[materialProjectIndex];
783
- const plan = plans[project];
784
- const challenge = `approve:${action}:${options.stage}:${project}:${plan.materialDigest}`;
785
- devkit_1.logger.info(`[ebk-local-deployment-review]\n${JSON.stringify({
786
- action,
787
- challenge,
788
- firstMaterialProject: project,
789
- orderedProjects,
790
- plan: {
791
- aggregateRisk: plan.aggregateRisk,
792
- diff: plan.diff,
793
- materialDigest: plan.materialDigest,
794
- requiredApprovals: plan.requiredApprovals,
795
- resourceFindings: plan.resourceFindings,
796
- stateDigest: plan.state.digest,
797
- },
798
- planSummaryDigest,
799
- sourceCommit,
595
+ assertGithubDeploymentAuthority({
596
+ commit: source.commit,
597
+ policy,
598
+ root: context.root,
599
+ run,
800
600
  stage: options.stage,
801
- }, null, 2)}`);
601
+ });
602
+ const caller = await (dependencies.identifyGithubCaller ?? defaultIdentifyGithubCaller)(region);
603
+ if (caller.Account?.trim() !== account ||
604
+ !(0, identity_js_1.matchesExpectedAssumedRoleArn)(account, caller.Arn?.trim() ?? '', expectedRoleArn)) {
605
+ throw new Error('Protected GitHub deployment caller does not match the committed account and exact assumed role.');
606
+ }
607
+ actor = process.env.GITHUB_ACTOR?.trim() ?? '';
608
+ if (!actor) {
609
+ throw new Error('Protected GitHub deployment actor is absent.');
610
+ }
611
+ }
612
+ else if (options.stage === 'staging') {
613
+ const challenge = `approve:deploy:staging:${source.commit}`;
802
614
  let acknowledged = false;
803
615
  try {
804
616
  acknowledged = await (dependencies.approve ?? (0, local_approval_js_1.createTtyLocalMutationApprover)())({
805
- action,
617
+ action: 'deploy',
806
618
  challenge,
807
- planDigest: plan.materialDigest,
808
- project,
619
+ sourceCommit: source.commit,
809
620
  stage: options.stage,
810
621
  });
811
622
  }
812
623
  catch (error) {
813
624
  if (error instanceof local_approval_js_1.LocalMutationApprovalUnavailableError) {
814
- result = 'review-required';
625
+ outcome = 'review-required';
815
626
  }
816
627
  throw error;
817
628
  }
818
629
  if (!acknowledged) {
819
- result = 'review-required';
820
- throw new Error(`Local ${action} acknowledgement was declined for ${project}.`);
630
+ outcome = 'review-required';
631
+ throw new Error('Local staging deployment acknowledgement was declined.');
821
632
  }
822
- operatorAcknowledgementDigest = digest(challenge);
823
- const issuedAt = (dependencies.now ?? (() => new Date()))();
824
- (0, infrastructure_plan_js_1.authorizeRetainedLocalInfrastructurePlan)({
825
- context: { ...context, projectName: project },
826
- environment: planEnvironment(project),
827
- now: issuedAt,
828
- plan,
829
- });
830
- if (retainedPlan(context, project, sourceCommit, expectedAccount, region, options.stage, action).materialDigest !== planDigests[project]) {
831
- throw new Error(`Local deployment plan for ${project} changed before invocation-scoped authority was established.`);
633
+ }
634
+ if (!githubAuthority && options.stage === 'production') {
635
+ const expected = productionConfirmation(account, region, source.commit);
636
+ let acknowledged = false;
637
+ try {
638
+ acknowledged = await (dependencies.approve ?? (0, local_approval_js_1.createTtyLocalMutationApprover)())({
639
+ action: 'deploy',
640
+ challenge: expected,
641
+ sourceCommit: source.commit,
642
+ stage: options.stage,
643
+ });
644
+ }
645
+ catch (error) {
646
+ if (error instanceof local_approval_js_1.LocalMutationApprovalUnavailableError) {
647
+ outcome = 'review-required';
648
+ }
649
+ throw error;
650
+ }
651
+ if (!acknowledged) {
652
+ outcome = 'review-required';
653
+ throw new Error('Local production deployment acknowledgement was declined.');
832
654
  }
833
- const capability = writeLocalMutationCapability({
834
- actorArn: arn,
835
- context,
836
- deploymentProfile: policy.deploymentProfile,
837
- deploymentSourceCommit: sourceCommit,
838
- deploymentSourceTree: sourceTree,
839
- expectedRoleArn: options.expectedRoleArn,
840
- issuedAt,
841
- plan,
655
+ }
656
+ const orderedProjects = localDeploymentProjectOrder(policy, context, options.stage);
657
+ const descriptors = projectDescriptors(context);
658
+ const localProtectedContainerStage = !githubAuthority &&
659
+ requiresLocalContainerPreparation(policy.deploymentProfile, options.stage)
660
+ ? options.stage
661
+ : undefined;
662
+ const localProtectedContainerProjects = localProtectedContainerStage
663
+ ? orderedProjects
664
+ .map((project) => descriptors.find((candidate) => candidate.name === project))
665
+ .filter((project) => project?.tags.includes('type:slice') === true)
666
+ : [];
667
+ for (const project of localProtectedContainerProjects) {
668
+ const missingTargets = LOCAL_CONTAINER_TARGETS.filter((target) => !supportsTarget(project, target));
669
+ if (missingTargets.length > 0) {
670
+ throw new Error(`Local ${policy.deploymentProfile} ${options.stage} requires the complete immutable OCI target set for ${project.name}: ${missingTargets.join(', ')}.`);
671
+ }
672
+ }
673
+ if (!githubAuthority) {
674
+ transport = await (dependencies.createTransport ?? federated_credential_transport_js_1.createLocalFederatedCredentialTransport)({
675
+ account,
676
+ expectedRoleArn,
677
+ ...(options.profile ? { profile: options.profile } : {}),
678
+ region,
679
+ });
680
+ const firstIdentity = await transport.refresh();
681
+ actor = firstIdentity.actorArn;
682
+ }
683
+ if (options.stage === 'production') {
684
+ if (transport)
685
+ await transport.refresh();
686
+ const boundaryEnvironment = transport
687
+ ? transport.childEnvironment((0, child_environment_js_1.sanitizeGovernedAwsChildEnvironment)(process.env))
688
+ : (0, child_environment_js_1.sanitizeGovernedAwsChildEnvironment)(process.env, {
689
+ preserveVerifiedGithubCredentials: true,
690
+ });
691
+ productionDatabaseBefore = await (dependencies.attestProductionDatabase ??
692
+ production_database_safeguard_js_1.attestProductionDatabaseLifecycle)({
693
+ ...(boundaryEnvironment.AWS_CONFIG_FILE
694
+ ? { configFilepath: boundaryEnvironment.AWS_CONFIG_FILE }
695
+ : {}),
696
+ ...(boundaryEnvironment.AWS_SHARED_CREDENTIALS_FILE
697
+ ? {
698
+ credentialsFilepath: boundaryEnvironment.AWS_SHARED_CREDENTIALS_FILE,
699
+ }
700
+ : {}),
701
+ expectedAccountId: account,
702
+ expectedRegion: region,
703
+ expectedRoleArn,
704
+ now: now(),
705
+ ...(transport ? { profile: transport.profile } : {}),
706
+ requireExisting: false,
707
+ });
708
+ }
709
+ const localContainerRunId = localProtectedContainerProjects.length === 0
710
+ ? ''
711
+ : ((0, node_crypto_1.randomBytes)(8).readBigUInt64BE() + 1n).toString();
712
+ for (const project of localProtectedContainerProjects) {
713
+ resetLocalContainerWorkingEvidence({
842
714
  project,
843
- releaseSourceTree: git(context.root, ['rev-parse', `${plan.sourceCommit}^{tree}`], run),
715
+ root: context.root,
716
+ stage: localProtectedContainerStage,
844
717
  });
718
+ for (const target of LOCAL_CONTAINER_TARGETS) {
719
+ if (transport)
720
+ await transport.refresh();
721
+ const environment = {
722
+ ...(transport
723
+ ? transport.childEnvironment((0, child_environment_js_1.sanitizeGovernedAwsChildEnvironment)(process.env))
724
+ : (0, child_environment_js_1.sanitizeGovernedAwsChildEnvironment)(process.env)),
725
+ AWS_EXPECTED_ACCOUNT_ID: account,
726
+ AWS_REGION: region,
727
+ EBK_LOCAL_CONTAINER_RUN_ID: localContainerRunId,
728
+ EBK_LOCAL_DEPLOYMENT_MODE: 'federated',
729
+ EBK_DEPLOYMENT_PROFILE: policy.deploymentProfile,
730
+ EBK_NX_STAGE_LIFECYCLE: 'local',
731
+ EBK_RELEASE_SOURCE_COMMIT: source.commit,
732
+ EBK_SOURCE_COMMIT: source.commit,
733
+ EBK_STAGE: options.stage,
734
+ EBK_WORKING_TREE_DIRTY: 'false',
735
+ };
736
+ assertDeploymentSourceUnchanged(context.root, source, run);
737
+ const childStartedAt = now().toISOString();
738
+ try {
739
+ await runNxTarget({
740
+ context,
741
+ environment,
742
+ expectedRoleArn,
743
+ ...(transport ? { profile: transport.profile } : {}),
744
+ project: project.name,
745
+ region,
746
+ ...(dependencies.run ? { runSync: dependencies.run } : {}),
747
+ ...(dependencies.spawnChild
748
+ ? { spawnChild: dependencies.spawnChild }
749
+ : {}),
750
+ stage: options.stage,
751
+ target,
752
+ });
753
+ if (target === 'release-candidate') {
754
+ materializeLocalReleaseCandidate({
755
+ project,
756
+ root: context.root,
757
+ sourceCommit: source.commit,
758
+ });
759
+ }
760
+ if (target === 'container-release-assemble') {
761
+ (dependencies.attestContainerBinding ?? attestLocalContainerBinding)({
762
+ account,
763
+ project,
764
+ region,
765
+ root: context.root,
766
+ sourceCommit: source.commit,
767
+ stage: options.stage,
768
+ });
769
+ }
770
+ children.push({
771
+ completedAt: now().toISOString(),
772
+ outcome: 'succeeded',
773
+ project: project.name,
774
+ startedAt: childStartedAt,
775
+ target,
776
+ });
777
+ }
778
+ catch (error) {
779
+ children.push({
780
+ completedAt: now().toISOString(),
781
+ outcome: 'failed',
782
+ project: project.name,
783
+ startedAt: childStartedAt,
784
+ target,
785
+ });
786
+ throw error;
787
+ }
788
+ }
789
+ }
790
+ for (const project of orderedProjects) {
791
+ const descriptor = descriptors.find((candidate) => candidate.name === project);
792
+ if (!descriptor) {
793
+ throw new Error(`Local deployment project ${project} disappeared.`);
794
+ }
795
+ if (transport)
796
+ await transport.refresh();
797
+ const environment = {
798
+ ...(transport
799
+ ? transport.childEnvironment((0, child_environment_js_1.sanitizeGovernedAwsChildEnvironment)(process.env))
800
+ : (0, child_environment_js_1.sanitizeGovernedAwsChildEnvironment)(process.env, {
801
+ preserveVerifiedGithubCredentials: true,
802
+ })),
803
+ AWS_EXPECTED_ACCOUNT_ID: account,
804
+ AWS_REGION: region,
805
+ EBK_LOCAL_SOURCE_TREE: source.workingTreeDigest,
806
+ EBK_NX_STAGE_LIFECYCLE: githubAuthority ? 'github' : 'local',
807
+ EBK_SOURCE_COMMIT: source.commit,
808
+ EBK_WORKING_TREE_DIRTY: source.clean ? 'false' : 'true',
809
+ };
810
+ assertDeploymentSourceUnchanged(context.root, source, run);
811
+ const childStartedAt = now().toISOString();
812
+ try {
813
+ await runNxTarget({
814
+ context,
815
+ environment,
816
+ expectedRoleArn,
817
+ ...(transport ? { profile: transport.profile } : {}),
818
+ project,
819
+ region,
820
+ ...(dependencies.run ? { runSync: dependencies.run } : {}),
821
+ ...(dependencies.spawnChild
822
+ ? { spawnChild: dependencies.spawnChild }
823
+ : {}),
824
+ stage: options.stage,
825
+ target: 'deploy',
826
+ });
827
+ children.push({
828
+ completedAt: now().toISOString(),
829
+ outcome: 'succeeded',
830
+ project,
831
+ startedAt: childStartedAt,
832
+ target: 'deploy',
833
+ });
834
+ }
835
+ catch (error) {
836
+ children.push({
837
+ completedAt: now().toISOString(),
838
+ outcome: 'failed',
839
+ project,
840
+ startedAt: childStartedAt,
841
+ target: 'deploy',
842
+ });
843
+ throw error;
844
+ }
845
+ if (options.stage === 'production' &&
846
+ descriptor.tags.includes('scope:foundation')) {
847
+ if (transport)
848
+ await transport.refresh();
849
+ const boundaryEnvironment = transport
850
+ ? transport.childEnvironment((0, child_environment_js_1.sanitizeGovernedAwsChildEnvironment)(process.env))
851
+ : (0, child_environment_js_1.sanitizeGovernedAwsChildEnvironment)(process.env, {
852
+ preserveVerifiedGithubCredentials: true,
853
+ });
854
+ const postflightStartedAt = now().toISOString();
855
+ const profile = (0, index_js_2.productionFoundationProfileContract)(policy.productionFoundation.profile).contract;
856
+ try {
857
+ productionDatabaseAfterFoundation = await (dependencies.attestProductionDatabase ??
858
+ production_database_safeguard_js_1.attestProductionDatabaseLifecycle)({
859
+ ...(boundaryEnvironment.AWS_CONFIG_FILE
860
+ ? { configFilepath: boundaryEnvironment.AWS_CONFIG_FILE }
861
+ : {}),
862
+ ...(boundaryEnvironment.AWS_SHARED_CREDENTIALS_FILE
863
+ ? {
864
+ credentialsFilepath: boundaryEnvironment.AWS_SHARED_CREDENTIALS_FILE,
865
+ }
866
+ : {}),
867
+ expectedAccountId: account,
868
+ expectedCapacity: {
869
+ max: Number.parseFloat(profile.aurora.max),
870
+ min: Number.parseFloat(profile.aurora.min),
871
+ },
872
+ expectedRegion: region,
873
+ expectedRoleArn,
874
+ now: now(),
875
+ priorDiscovery: productionDatabaseBefore,
876
+ ...(transport ? { profile: transport.profile } : {}),
877
+ requireExisting: true,
878
+ });
879
+ children.push({
880
+ completedAt: now().toISOString(),
881
+ outcome: 'succeeded',
882
+ project,
883
+ startedAt: postflightStartedAt,
884
+ target: 'production-database-postflight',
885
+ });
886
+ }
887
+ catch (error) {
888
+ children.push({
889
+ completedAt: now().toISOString(),
890
+ outcome: 'failed',
891
+ project,
892
+ startedAt: postflightStartedAt,
893
+ target: 'production-database-postflight',
894
+ });
895
+ throw error;
896
+ }
897
+ }
898
+ migrations[project] = supportsTarget(descriptor, 'db-migrate')
899
+ ? 'completed-by-deploy-target'
900
+ : 'not-applicable';
901
+ if (supportsTarget(descriptor, 'smoke-deployed')) {
902
+ if (transport)
903
+ await transport.refresh();
904
+ const smokeTarget = readDeployedSmokeTarget(context.root, descriptor, options.stage);
905
+ assertDeploymentSourceUnchanged(context.root, source, run);
906
+ const smokeStartedAt = now().toISOString();
907
+ try {
908
+ await runNxTarget({
909
+ context,
910
+ environment: {
911
+ ...(transport
912
+ ? transport.childEnvironment((0, child_environment_js_1.sanitizeGovernedAwsChildEnvironment)(process.env))
913
+ : (0, child_environment_js_1.sanitizeGovernedAwsChildEnvironment)(process.env, {
914
+ preserveVerifiedGithubCredentials: true,
915
+ })),
916
+ AWS_EXPECTED_ACCOUNT_ID: account,
917
+ AWS_REGION: region,
918
+ EBK_NX_STAGE_LIFECYCLE: githubAuthority ? 'github' : 'local',
919
+ EBK_SOURCE_COMMIT: source.commit,
920
+ EBK_SMOKE_API_URL: smokeTarget.apiUrl,
921
+ EBK_SMOKE_AUTHORITY_VERSION: 'cognito-admin-cloudwatch-observe-v1',
922
+ EBK_SMOKE_CLIENT_ID: smokeTarget.clientId,
923
+ EBK_SMOKE_URL: smokeTarget.webUrl,
924
+ EBK_SMOKE_USER_POOL_ID: smokeTarget.userPoolId,
925
+ EBK_STAGE: options.stage,
926
+ },
927
+ expectedRoleArn,
928
+ ...(transport ? { profile: transport.profile } : {}),
929
+ project,
930
+ region,
931
+ ...(dependencies.run ? { runSync: dependencies.run } : {}),
932
+ ...(dependencies.spawnChild
933
+ ? { spawnChild: dependencies.spawnChild }
934
+ : {}),
935
+ stage: options.stage,
936
+ target: 'smoke-deployed',
937
+ });
938
+ children.push({
939
+ completedAt: now().toISOString(),
940
+ outcome: 'succeeded',
941
+ project,
942
+ startedAt: smokeStartedAt,
943
+ target: 'smoke-deployed',
944
+ });
945
+ smoke[project] = 'passed';
946
+ }
947
+ catch (error) {
948
+ children.push({
949
+ completedAt: now().toISOString(),
950
+ outcome: 'failed',
951
+ project,
952
+ startedAt: smokeStartedAt,
953
+ target: 'smoke-deployed',
954
+ });
955
+ throw error;
956
+ }
957
+ }
958
+ else {
959
+ smoke[project] = 'not-applicable';
960
+ }
961
+ }
962
+ if (options.stage === 'production') {
963
+ if (!productionDatabaseAfterFoundation) {
964
+ throw new Error('Production Foundation database postflight was not completed before final deployment acceptance.');
965
+ }
966
+ if (transport)
967
+ await transport.refresh();
968
+ const boundaryEnvironment = transport
969
+ ? transport.childEnvironment((0, child_environment_js_1.sanitizeGovernedAwsChildEnvironment)(process.env))
970
+ : (0, child_environment_js_1.sanitizeGovernedAwsChildEnvironment)(process.env, {
971
+ preserveVerifiedGithubCredentials: true,
972
+ });
973
+ const finalPostflightStartedAt = now().toISOString();
974
+ const foundationProject = orderedProjects[0] ?? 'foundation';
975
+ const profile = (0, index_js_2.productionFoundationProfileContract)(policy.productionFoundation.profile).contract;
845
976
  try {
846
- runNxWithToolOwnedSourceReconciliation(context, project, action, [
847
- ...common,
848
- ...(options.stage === 'production'
849
- ? [`--localProductionConfirmation=${expectedConfirmation}`]
850
- : []),
851
- ], {
852
- ...environment,
853
- EBK_LOCAL_MUTATION_CAPABILITY_NONCE: capability.nonce,
854
- EBK_LOCAL_MUTATION_CAPABILITY_PATH: capability.path,
855
- }, sourceSnapshots, run);
977
+ await (dependencies.attestProductionDatabase ??
978
+ production_database_safeguard_js_1.attestProductionDatabaseLifecycle)({
979
+ ...(boundaryEnvironment.AWS_CONFIG_FILE
980
+ ? { configFilepath: boundaryEnvironment.AWS_CONFIG_FILE }
981
+ : {}),
982
+ ...(boundaryEnvironment.AWS_SHARED_CREDENTIALS_FILE
983
+ ? {
984
+ credentialsFilepath: boundaryEnvironment.AWS_SHARED_CREDENTIALS_FILE,
985
+ }
986
+ : {}),
987
+ expectedAccountId: account,
988
+ expectedCapacity: {
989
+ max: Number.parseFloat(profile.aurora.max),
990
+ min: Number.parseFloat(profile.aurora.min),
991
+ },
992
+ expectedRegion: region,
993
+ expectedRoleArn,
994
+ now: now(),
995
+ priorDiscovery: productionDatabaseAfterFoundation,
996
+ ...(transport ? { profile: transport.profile } : {}),
997
+ requireExisting: true,
998
+ });
999
+ children.push({
1000
+ completedAt: now().toISOString(),
1001
+ outcome: 'succeeded',
1002
+ project: foundationProject,
1003
+ startedAt: finalPostflightStartedAt,
1004
+ target: 'production-database-final-postflight',
1005
+ });
856
1006
  }
857
- finally {
858
- (0, node_fs_1.rmSync)(capability.directory, { force: true, recursive: true });
1007
+ catch (error) {
1008
+ children.push({
1009
+ completedAt: now().toISOString(),
1010
+ outcome: 'failed',
1011
+ project: foundationProject,
1012
+ startedAt: finalPostflightStartedAt,
1013
+ target: 'production-database-final-postflight',
1014
+ });
1015
+ throw error;
859
1016
  }
860
- mutatedProjects.push(project);
861
- result = 'replan-required';
862
- replanRequiredProject = orderedProjects[materialProjectIndex + 1] ?? null;
863
- throw new Error(replanRequiredProject
864
- ? `Local deployment applied ${project} and requires a new complete-stage plan before ${replanRequiredProject}. Rerun and acknowledge the new current plan before any further mutation.`
865
- : `Local deployment applied ${project} and requires a new complete-stage plan before accepting stage convergence. Rerun and require every current project to be a semantic no-op.`);
866
1017
  }
867
- result = 'succeeded';
1018
+ assertDeploymentSourceUnchanged(context.root, source, run);
1019
+ if (transport) {
1020
+ transport.cleanup();
1021
+ transport = undefined;
1022
+ }
1023
+ outcome = 'succeeded';
1024
+ retain();
1025
+ devkit_1.logger.info(`${githubAuthority ? 'Protected GitHub' : 'Local'} ${options.stage} deployment completed through native SST for ${children.filter(({ target }) => target === 'deploy').length} project(s).`);
868
1026
  return { success: true };
869
1027
  }
870
- finally {
871
- retainEvidence(context.root, {
872
- account: expectedAccount,
873
- action,
874
- actorArnDigest: digest(arn),
875
- backendAuthorityDigest: backend?.authorityDigest ?? null,
876
- backendCreatedApplications: backend
877
- ? [...backend.createdApplications]
878
- : [],
879
- completedAt: (dependencies.now ?? (() => new Date()))().toISOString(),
880
- deployNoOpProjects,
881
- kind: 'ebk-local-deployment-evidence',
882
- mutatedProjects,
883
- orderedProjects,
884
- operatorAcknowledgementDigest,
885
- planDigests,
886
- planSummaryDigest,
887
- phase,
888
- profile: policy.deploymentProfile,
889
- replanRequiredProject,
890
- refreshNoOpProjects,
891
- region,
892
- result,
893
- schemaVersion: 1,
894
- sourceCommit,
895
- sourceDigest: digest(`${sourceCommit}:${sourceTree}`),
896
- stage: options.stage,
897
- startedAt,
898
- });
899
- }
900
- }
901
- const executor = async (options, context) => {
902
- try {
903
- return await runLocalDeploy(options, context);
904
- }
905
1028
  catch (error) {
906
1029
  devkit_1.logger.error(error instanceof Error ? error.message : String(error));
1030
+ if (transport) {
1031
+ try {
1032
+ transport.cleanup();
1033
+ transport = undefined;
1034
+ }
1035
+ catch {
1036
+ devkit_1.logger.error('Local federated credential transport cleanup failed; deployment acceptance is blocked.');
1037
+ }
1038
+ }
1039
+ retain();
907
1040
  return { success: false };
908
1041
  }
909
- };
1042
+ }
1043
+ const executor = (options, context) => runLocalDeploy(options, context);
910
1044
  exports.default = executor;
911
1045
  //# sourceMappingURL=local-deploy.js.map