@empire-builder-kit/nx 1.0.0-beta.9 → 1.0.0-rc.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (824) hide show
  1. package/NOTICE +6 -0
  2. package/README.md +113 -3
  3. package/executors.json +30 -0
  4. package/package.json +84 -6
  5. package/src/api-contract/baseline-update.d.ts +82 -0
  6. package/src/api-contract/baseline-update.js +225 -0
  7. package/src/api-contract/baseline-update.js.map +1 -0
  8. package/src/api-contract/compatibility.d.ts +30 -0
  9. package/src/api-contract/compatibility.js +643 -0
  10. package/src/api-contract/compatibility.js.map +1 -0
  11. package/src/api-contract/contract-graph.d.ts +35 -0
  12. package/src/api-contract/contract-graph.js +141 -0
  13. package/src/api-contract/contract-graph.js.map +1 -0
  14. package/src/api-contract/evidence.d.ts +23 -0
  15. package/src/api-contract/evidence.js +87 -0
  16. package/src/api-contract/evidence.js.map +1 -0
  17. package/src/api-contract/index.d.ts +6 -0
  18. package/src/api-contract/index.js +10 -0
  19. package/src/api-contract/index.js.map +1 -0
  20. package/src/api-contract/model.d.ts +144 -0
  21. package/src/api-contract/model.fixtures.d.ts +2 -0
  22. package/src/api-contract/model.fixtures.js +81 -0
  23. package/src/api-contract/model.fixtures.js.map +1 -0
  24. package/src/api-contract/model.js +993 -0
  25. package/src/api-contract/model.js.map +1 -0
  26. package/src/api-contract/rollout.d.ts +32 -0
  27. package/src/api-contract/rollout.js +88 -0
  28. package/src/api-contract/rollout.js.map +1 -0
  29. package/src/authorization/compatibility.d.ts +62 -0
  30. package/src/authorization/compatibility.js +175 -0
  31. package/src/authorization/compatibility.js.map +1 -0
  32. package/src/authorization/index.d.ts +1 -0
  33. package/src/authorization/index.js +5 -0
  34. package/src/authorization/index.js.map +1 -0
  35. package/src/blueprints/execution.d.ts +25 -1
  36. package/src/blueprints/execution.js +246 -83
  37. package/src/blueprints/execution.js.map +1 -1
  38. package/src/blueprints/external-sandbox.d.ts +53 -0
  39. package/src/blueprints/external-sandbox.js +1182 -0
  40. package/src/blueprints/external-sandbox.js.map +1 -0
  41. package/src/blueprints/manifest.d.ts +42 -2
  42. package/src/blueprints/manifest.js +82 -27
  43. package/src/blueprints/manifest.js.map +1 -1
  44. package/src/blueprints/resolver.d.ts +14 -5
  45. package/src/blueprints/resolver.js +228 -68
  46. package/src/blueprints/resolver.js.map +1 -1
  47. package/src/cli/agent-command.d.ts +30 -0
  48. package/src/cli/agent-command.js +204 -0
  49. package/src/cli/agent-command.js.map +1 -0
  50. package/src/cli/agent-runtime.d.ts +25 -0
  51. package/src/cli/agent-runtime.js +488 -0
  52. package/src/cli/agent-runtime.js.map +1 -0
  53. package/src/cli/blueprint-authority.d.ts +9 -0
  54. package/src/cli/blueprint-authority.js +43 -0
  55. package/src/cli/blueprint-authority.js.map +1 -0
  56. package/src/cli/blueprint-command.d.ts +24 -0
  57. package/src/cli/blueprint-command.js +63 -0
  58. package/src/cli/blueprint-command.js.map +1 -0
  59. package/src/cli/bootstrap.d.ts +31 -0
  60. package/src/cli/bootstrap.js +165 -0
  61. package/src/cli/bootstrap.js.map +1 -0
  62. package/src/cli/dev-command.d.ts +92 -0
  63. package/src/cli/dev-command.js +561 -0
  64. package/src/cli/dev-command.js.map +1 -0
  65. package/src/cli/main.d.ts +131 -0
  66. package/src/cli/main.js +1199 -0
  67. package/src/cli/main.js.map +1 -0
  68. package/src/cli/output.d.ts +19 -0
  69. package/src/cli/output.js +51 -0
  70. package/src/cli/output.js.map +1 -0
  71. package/src/cli/personal-secret-runtime.d.ts +81 -0
  72. package/src/cli/personal-secret-runtime.js +567 -0
  73. package/src/cli/personal-secret-runtime.js.map +1 -0
  74. package/src/cli/secret-terminal.d.ts +12 -0
  75. package/src/cli/secret-terminal.js +128 -0
  76. package/src/cli/secret-terminal.js.map +1 -0
  77. package/src/container-images.d.ts +1 -0
  78. package/src/container-images.js +5 -0
  79. package/src/container-images.js.map +1 -0
  80. package/src/data-lifecycle/index.d.ts +3 -0
  81. package/src/data-lifecycle/index.js +7 -0
  82. package/src/data-lifecycle/index.js.map +1 -0
  83. package/src/data-lifecycle/model.d.ts +172 -0
  84. package/src/data-lifecycle/model.fixtures.d.ts +2 -0
  85. package/src/data-lifecycle/model.fixtures.js +244 -0
  86. package/src/data-lifecycle/model.fixtures.js.map +1 -0
  87. package/src/data-lifecycle/model.js +95 -0
  88. package/src/data-lifecycle/model.js.map +1 -0
  89. package/src/data-lifecycle/sensitive-artifacts.d.ts +28 -0
  90. package/src/data-lifecycle/sensitive-artifacts.js +146 -0
  91. package/src/data-lifecycle/sensitive-artifacts.js.map +1 -0
  92. package/src/data-lifecycle/validation.d.ts +32 -0
  93. package/src/data-lifecycle/validation.js +837 -0
  94. package/src/data-lifecycle/validation.js.map +1 -0
  95. package/src/database-retirement/contract.d.ts +68 -0
  96. package/src/database-retirement/contract.js +166 -0
  97. package/src/database-retirement/contract.js.map +1 -0
  98. package/src/database-retirement/index.d.ts +1 -0
  99. package/src/database-retirement/index.js +5 -0
  100. package/src/database-retirement/index.js.map +1 -0
  101. package/src/deterministic-order.d.ts +2 -0
  102. package/src/deterministic-order.js +7 -0
  103. package/src/deterministic-order.js.map +1 -0
  104. package/src/developer-agent/context.d.ts +27 -0
  105. package/src/developer-agent/context.js +497 -0
  106. package/src/developer-agent/context.js.map +1 -0
  107. package/src/developer-agent/generator-planning.d.ts +34 -0
  108. package/src/developer-agent/generator-planning.js +645 -0
  109. package/src/developer-agent/generator-planning.js.map +1 -0
  110. package/src/developer-agent/generator-preview.d.ts +8 -0
  111. package/src/developer-agent/generator-preview.js +650 -0
  112. package/src/developer-agent/generator-preview.js.map +1 -0
  113. package/src/developer-agent/git-inspection.d.ts +108 -0
  114. package/src/developer-agent/git-inspection.js +815 -0
  115. package/src/developer-agent/git-inspection.js.map +1 -0
  116. package/src/developer-agent/handoff.d.ts +43 -0
  117. package/src/developer-agent/handoff.js +698 -0
  118. package/src/developer-agent/handoff.js.map +1 -0
  119. package/src/developer-agent/index.d.ts +10 -0
  120. package/src/developer-agent/index.js +14 -0
  121. package/src/developer-agent/index.js.map +1 -0
  122. package/src/developer-agent/model.d.ts +481 -0
  123. package/src/developer-agent/model.js +143 -0
  124. package/src/developer-agent/model.js.map +1 -0
  125. package/src/developer-agent/operation-registry.d.ts +45 -0
  126. package/src/developer-agent/operation-registry.js +1255 -0
  127. package/src/developer-agent/operation-registry.js.map +1 -0
  128. package/src/developer-agent/ordering.d.ts +9 -0
  129. package/src/developer-agent/ordering.js +15 -0
  130. package/src/developer-agent/ordering.js.map +1 -0
  131. package/src/developer-agent/planning.d.ts +18 -0
  132. package/src/developer-agent/planning.js +447 -0
  133. package/src/developer-agent/planning.js.map +1 -0
  134. package/src/developer-agent/preview-changes.d.ts +26 -0
  135. package/src/developer-agent/preview-changes.js +203 -0
  136. package/src/developer-agent/preview-changes.js.map +1 -0
  137. package/src/developer-agent/preview-source.d.ts +2 -0
  138. package/src/developer-agent/preview-source.js +16 -0
  139. package/src/developer-agent/preview-source.js.map +1 -0
  140. package/src/developer-agent/validation.d.ts +18 -0
  141. package/src/developer-agent/validation.js +2617 -0
  142. package/src/developer-agent/validation.js.map +1 -0
  143. package/src/developer-agent/workspace-inspection.d.ts +20 -0
  144. package/src/developer-agent/workspace-inspection.js +1134 -0
  145. package/src/developer-agent/workspace-inspection.js.map +1 -0
  146. package/src/developer-agent/workspace-path.d.ts +6 -0
  147. package/src/developer-agent/workspace-path.js +22 -0
  148. package/src/developer-agent/workspace-path.js.map +1 -0
  149. package/src/executors/blueprint-trust/blueprint-trust.d.ts +29 -0
  150. package/src/executors/blueprint-trust/blueprint-trust.js +115 -0
  151. package/src/executors/blueprint-trust/blueprint-trust.js.map +1 -0
  152. package/src/executors/blueprint-trust/schema.json +27 -0
  153. package/src/executors/blueprint-verification/blueprint-verification.d.ts +13 -0
  154. package/src/executors/blueprint-verification/blueprint-verification.js +110 -0
  155. package/src/executors/blueprint-verification/blueprint-verification.js.map +1 -0
  156. package/src/executors/blueprint-verification/schema.json +44 -0
  157. package/src/executors/deployment-plan/deployment-plan.js +1 -0
  158. package/src/executors/deployment-plan/deployment-plan.js.map +1 -1
  159. package/src/executors/dev-doctor/dev-doctor.d.ts +19 -5
  160. package/src/executors/dev-doctor/dev-doctor.js +186 -32
  161. package/src/executors/dev-doctor/dev-doctor.js.map +1 -1
  162. package/src/executors/dev-doctor/schema.json +1 -6
  163. package/src/executors/dev-setup/dev-setup.d.ts +7 -1
  164. package/src/executors/dev-setup/dev-setup.js +14 -3
  165. package/src/executors/dev-setup/dev-setup.js.map +1 -1
  166. package/src/executors/developer-context/developer-context.d.ts +38 -0
  167. package/src/executors/developer-context/developer-context.js +78 -0
  168. package/src/executors/developer-context/developer-context.js.map +1 -0
  169. package/src/executors/developer-context/schema.json +87 -0
  170. package/src/executors/fleet/fleet.d.ts +12 -43
  171. package/src/executors/fleet/fleet.js +120 -500
  172. package/src/executors/fleet/fleet.js.map +1 -1
  173. package/src/executors/fleet/schema.json +64 -14
  174. package/src/executors/github-bootstrap/github-bootstrap.d.ts +1 -0
  175. package/src/executors/github-bootstrap/github-bootstrap.js +1 -0
  176. package/src/executors/github-bootstrap/github-bootstrap.js.map +1 -1
  177. package/src/executors/github-bootstrap/schema.json +5 -0
  178. package/src/executors/infrastructure-plan/infrastructure-plan.d.ts +144 -0
  179. package/src/executors/infrastructure-plan/infrastructure-plan.js +3320 -0
  180. package/src/executors/infrastructure-plan/infrastructure-plan.js.map +1 -0
  181. package/src/executors/infrastructure-plan/schema.json +78 -0
  182. package/src/executors/secret-guardrails/schema.json +6 -0
  183. package/src/executors/secret-guardrails/secret-guardrails.d.ts +3 -0
  184. package/src/executors/secret-guardrails/secret-guardrails.js +44 -0
  185. package/src/executors/secret-guardrails/secret-guardrails.js.map +1 -0
  186. package/src/executors/secret-metadata/schema.json +31 -0
  187. package/src/executors/secret-metadata/secret-metadata.d.ts +32 -0
  188. package/src/executors/secret-metadata/secret-metadata.js +131 -0
  189. package/src/executors/secret-metadata/secret-metadata.js.map +1 -0
  190. package/src/executors/sst-lifecycle/logging-preflight.d.ts +17 -0
  191. package/src/executors/sst-lifecycle/logging-preflight.js +196 -0
  192. package/src/executors/sst-lifecycle/logging-preflight.js.map +1 -0
  193. package/src/executors/sst-lifecycle/run-sst-command.d.ts +23 -3
  194. package/src/executors/sst-lifecycle/run-sst-command.js +113 -10
  195. package/src/executors/sst-lifecycle/run-sst-command.js.map +1 -1
  196. package/src/executors/sst-lifecycle/schema.json +9 -5
  197. package/src/executors/sst-lifecycle/secret-preflight.d.ts +25 -0
  198. package/src/executors/sst-lifecycle/secret-preflight.js +119 -0
  199. package/src/executors/sst-lifecycle/secret-preflight.js.map +1 -0
  200. package/src/foundation/discovery.d.ts +17 -1
  201. package/src/foundation/discovery.js +16 -1
  202. package/src/foundation/discovery.js.map +1 -1
  203. package/src/foundation/index.d.ts +3 -0
  204. package/src/foundation/index.js +3 -0
  205. package/src/foundation/index.js.map +1 -1
  206. package/src/foundation/local-database-contract.d.ts +1 -0
  207. package/src/foundation/local-database-contract.js +3 -0
  208. package/src/foundation/local-database-contract.js.map +1 -1
  209. package/src/foundation/logging-preflight.d.ts +30 -0
  210. package/src/foundation/logging-preflight.js +138 -0
  211. package/src/foundation/logging-preflight.js.map +1 -0
  212. package/src/foundation/logging.d.ts +70 -0
  213. package/src/foundation/logging.js +133 -0
  214. package/src/foundation/logging.js.map +1 -0
  215. package/src/foundation/preflight.d.ts +9 -0
  216. package/src/foundation/preflight.js +58 -2
  217. package/src/foundation/preflight.js.map +1 -1
  218. package/src/foundation/provision-database-bindings.d.ts +9 -1
  219. package/src/foundation/provision-database-bindings.js +25 -5
  220. package/src/foundation/provision-database-bindings.js.map +1 -1
  221. package/src/foundation/provision-logical-database.d.ts +10 -2
  222. package/src/foundation/provision-logical-database.js +68 -113
  223. package/src/foundation/provision-logical-database.js.map +1 -1
  224. package/src/foundation/router-origin-proof.d.ts +2 -0
  225. package/src/foundation/router-origin-proof.js +25 -0
  226. package/src/foundation/router-origin-proof.js.map +1 -0
  227. package/src/foundation/stages.d.ts +1 -0
  228. package/src/foundation/stages.js +9 -1
  229. package/src/foundation/stages.js.map +1 -1
  230. package/src/frontend-security/index.d.ts +1 -0
  231. package/src/frontend-security/index.js +5 -0
  232. package/src/frontend-security/index.js.map +1 -0
  233. package/src/frontend-security/security-headers.d.ts +50 -0
  234. package/src/frontend-security/security-headers.js +418 -0
  235. package/src/frontend-security/security-headers.js.map +1 -0
  236. package/src/generators/feature-registry.d.ts +13 -0
  237. package/src/generators/feature-registry.js +73 -0
  238. package/src/generators/feature-registry.js.map +1 -0
  239. package/src/generators/function/function.d.ts +1 -1
  240. package/src/generators/function/function.js +51 -6
  241. package/src/generators/function/function.js.map +1 -1
  242. package/src/generators/function/schema.d.ts +1 -0
  243. package/src/generators/function/schema.json +6 -1
  244. package/src/generators/logging-registry.d.ts +13 -0
  245. package/src/generators/logging-registry.js +62 -0
  246. package/src/generators/logging-registry.js.map +1 -0
  247. package/src/generators/post-generation-install.d.ts +67 -0
  248. package/src/generators/post-generation-install.js +3308 -0
  249. package/src/generators/post-generation-install.js.map +1 -0
  250. package/src/generators/preset/generator.js +3 -0
  251. package/src/generators/preset/generator.js.map +1 -1
  252. package/src/generators/preset/tool-versions.d.ts +18 -0
  253. package/src/generators/preset/tool-versions.js +22 -0
  254. package/src/generators/preset/tool-versions.js.map +1 -0
  255. package/src/generators/preset/workspace-files-developer-agent.d.ts +14 -0
  256. package/src/generators/preset/workspace-files-developer-agent.js +511 -0
  257. package/src/generators/preset/workspace-files-developer-agent.js.map +1 -0
  258. package/src/generators/preset/workspace-files-foundation-workload-roles.d.ts +6 -0
  259. package/src/generators/preset/workspace-files-foundation-workload-roles.js +406 -0
  260. package/src/generators/preset/workspace-files-foundation-workload-roles.js.map +1 -0
  261. package/src/generators/preset/workspace-files-performance-cost.d.ts +66 -0
  262. package/src/generators/preset/workspace-files-performance-cost.js +866 -0
  263. package/src/generators/preset/workspace-files-performance-cost.js.map +1 -0
  264. package/src/generators/preset/workspace-files-performance-load.d.ts +73 -0
  265. package/src/generators/preset/workspace-files-performance-load.js +801 -0
  266. package/src/generators/preset/workspace-files-performance-load.js.map +1 -0
  267. package/src/generators/preset/workspace-files-workload-role-baseline.d.ts +9 -0
  268. package/src/generators/preset/workspace-files-workload-role-baseline.js +1102 -0
  269. package/src/generators/preset/workspace-files-workload-role-baseline.js.map +1 -0
  270. package/src/generators/preset/workspace-files.d.ts +14 -1
  271. package/src/generators/preset/workspace-files.js +8189 -245
  272. package/src/generators/preset/workspace-files.js.map +1 -1
  273. package/src/generators/repository-policy/repository-policy.js +3 -47
  274. package/src/generators/repository-policy/repository-policy.js.map +1 -1
  275. package/src/generators/slice/built-in-plan-api-contract-files.d.ts +2 -0
  276. package/src/generators/slice/built-in-plan-api-contract-files.js +2656 -0
  277. package/src/generators/slice/built-in-plan-api-contract-files.js.map +1 -0
  278. package/src/generators/slice/built-in-plan-api-files.d.ts +2 -0
  279. package/src/generators/slice/built-in-plan-api-files.js +1186 -0
  280. package/src/generators/slice/built-in-plan-api-files.js.map +1 -0
  281. package/src/generators/slice/built-in-plan-async-work-files.d.ts +2 -0
  282. package/src/generators/slice/built-in-plan-async-work-files.js +7903 -0
  283. package/src/generators/slice/built-in-plan-async-work-files.js.map +1 -0
  284. package/src/generators/slice/built-in-plan-authorization-files.d.ts +2 -0
  285. package/src/generators/slice/built-in-plan-authorization-files.js +2314 -0
  286. package/src/generators/slice/built-in-plan-authorization-files.js.map +1 -0
  287. package/src/generators/slice/built-in-plan-authorization-infrastructure-files.d.ts +2 -0
  288. package/src/generators/slice/built-in-plan-authorization-infrastructure-files.js +559 -0
  289. package/src/generators/slice/built-in-plan-authorization-infrastructure-files.js.map +1 -0
  290. package/src/generators/slice/built-in-plan-data-deletion-files.d.ts +2 -0
  291. package/src/generators/slice/built-in-plan-data-deletion-files.js +1101 -0
  292. package/src/generators/slice/built-in-plan-data-deletion-files.js.map +1 -0
  293. package/src/generators/slice/built-in-plan-data-export-infrastructure-files.d.ts +2 -0
  294. package/src/generators/slice/built-in-plan-data-export-infrastructure-files.js +249 -0
  295. package/src/generators/slice/built-in-plan-data-export-infrastructure-files.js.map +1 -0
  296. package/src/generators/slice/built-in-plan-data-files.d.ts +2 -0
  297. package/src/generators/slice/built-in-plan-data-files.js +1530 -0
  298. package/src/generators/slice/built-in-plan-data-files.js.map +1 -0
  299. package/src/generators/slice/built-in-plan-data-lifecycle-files.d.ts +2 -0
  300. package/src/generators/slice/built-in-plan-data-lifecycle-files.js +750 -0
  301. package/src/generators/slice/built-in-plan-data-lifecycle-files.js.map +1 -0
  302. package/src/generators/slice/built-in-plan-data-lifecycle-runtime-files.d.ts +2 -0
  303. package/src/generators/slice/built-in-plan-data-lifecycle-runtime-files.js +828 -0
  304. package/src/generators/slice/built-in-plan-data-lifecycle-runtime-files.js.map +1 -0
  305. package/src/generators/slice/built-in-plan-data-retention-job-files.d.ts +2 -0
  306. package/src/generators/slice/built-in-plan-data-retention-job-files.js +2604 -0
  307. package/src/generators/slice/built-in-plan-data-retention-job-files.js.map +1 -0
  308. package/src/generators/slice/built-in-plan-data-retention-launcher-files.d.ts +2 -0
  309. package/src/generators/slice/built-in-plan-data-retention-launcher-files.js +144 -0
  310. package/src/generators/slice/built-in-plan-data-retention-launcher-files.js.map +1 -0
  311. package/src/generators/slice/built-in-plan-data-rights-files.d.ts +2 -0
  312. package/src/generators/slice/built-in-plan-data-rights-files.js +4628 -0
  313. package/src/generators/slice/built-in-plan-data-rights-files.js.map +1 -0
  314. package/src/generators/slice/built-in-plan-database-lifecycle-files.d.ts +2 -0
  315. package/src/generators/slice/built-in-plan-database-lifecycle-files.js +1901 -0
  316. package/src/generators/slice/built-in-plan-database-lifecycle-files.js.map +1 -0
  317. package/src/generators/slice/built-in-plan-database-retirement-rehearsal-files.d.ts +2 -0
  318. package/src/generators/slice/built-in-plan-database-retirement-rehearsal-files.js +485 -0
  319. package/src/generators/slice/built-in-plan-database-retirement-rehearsal-files.js.map +1 -0
  320. package/src/generators/slice/built-in-plan-foundation-files.d.ts +2 -0
  321. package/src/generators/slice/built-in-plan-foundation-files.js +184 -0
  322. package/src/generators/slice/built-in-plan-foundation-files.js.map +1 -0
  323. package/src/generators/slice/built-in-plan-frontend-files.d.ts +2 -0
  324. package/src/generators/slice/built-in-plan-frontend-files.js +2233 -0
  325. package/src/generators/slice/built-in-plan-frontend-files.js.map +1 -0
  326. package/src/generators/slice/built-in-plan-infrastructure-files.d.ts +2 -0
  327. package/src/generators/slice/built-in-plan-infrastructure-files.js +3038 -0
  328. package/src/generators/slice/built-in-plan-infrastructure-files.js.map +1 -0
  329. package/src/generators/slice/built-in-plan-infrastructure-safety-files.d.ts +2 -0
  330. package/src/generators/slice/built-in-plan-infrastructure-safety-files.js +312 -0
  331. package/src/generators/slice/built-in-plan-infrastructure-safety-files.js.map +1 -0
  332. package/src/generators/slice/built-in-plan-metadata-files.d.ts +3 -0
  333. package/src/generators/slice/built-in-plan-metadata-files.js +219 -0
  334. package/src/generators/slice/built-in-plan-metadata-files.js.map +1 -0
  335. package/src/generators/slice/built-in-plan-operations-files.d.ts +2 -0
  336. package/src/generators/slice/built-in-plan-operations-files.js +630 -0
  337. package/src/generators/slice/built-in-plan-operations-files.js.map +1 -0
  338. package/src/generators/slice/built-in-plan-performance-files.d.ts +3 -0
  339. package/src/generators/slice/built-in-plan-performance-files.js +3293 -0
  340. package/src/generators/slice/built-in-plan-performance-files.js.map +1 -0
  341. package/src/generators/slice/built-in-plan-project-files.d.ts +3 -0
  342. package/src/generators/slice/built-in-plan-project-files.js +979 -0
  343. package/src/generators/slice/built-in-plan-project-files.js.map +1 -0
  344. package/src/generators/slice/built-in-plan-release-files.d.ts +2 -0
  345. package/src/generators/slice/built-in-plan-release-files.js +6387 -0
  346. package/src/generators/slice/built-in-plan-release-files.js.map +1 -0
  347. package/src/generators/slice/built-in-plan-reliability-files.d.ts +2 -0
  348. package/src/generators/slice/built-in-plan-reliability-files.js +4499 -0
  349. package/src/generators/slice/built-in-plan-reliability-files.js.map +1 -0
  350. package/src/generators/slice/built-in-plan-secret-runtime-files.d.ts +2 -0
  351. package/src/generators/slice/built-in-plan-secret-runtime-files.js +1004 -0
  352. package/src/generators/slice/built-in-plan-secret-runtime-files.js.map +1 -0
  353. package/src/generators/slice/built-in-plan-shared.d.ts +53 -0
  354. package/src/generators/slice/built-in-plan-shared.js +113 -0
  355. package/src/generators/slice/built-in-plan-shared.js.map +1 -0
  356. package/src/generators/slice/built-in-plan-testing-files.d.ts +2 -0
  357. package/src/generators/slice/built-in-plan-testing-files.js +2356 -0
  358. package/src/generators/slice/built-in-plan-testing-files.js.map +1 -0
  359. package/src/generators/slice/built-in-plan-verification-files.d.ts +2 -0
  360. package/src/generators/slice/built-in-plan-verification-files.js +358 -0
  361. package/src/generators/slice/built-in-plan-verification-files.js.map +1 -0
  362. package/src/generators/slice/built-in-plan-web-files.d.ts +2 -0
  363. package/src/generators/slice/built-in-plan-web-files.js +430 -0
  364. package/src/generators/slice/built-in-plan-web-files.js.map +1 -0
  365. package/src/generators/slice/built-in-plan-workload-role-files.d.ts +2 -0
  366. package/src/generators/slice/built-in-plan-workload-role-files.js +2356 -0
  367. package/src/generators/slice/built-in-plan-workload-role-files.js.map +1 -0
  368. package/src/generators/slice/built-in-plan.d.ts +8 -0
  369. package/src/generators/slice/built-in-plan.js +264 -0
  370. package/src/generators/slice/built-in-plan.js.map +1 -0
  371. package/src/generators/slice/schema.json +1 -1
  372. package/src/generators/slice/slice.d.ts +1 -22
  373. package/src/generators/slice/slice.js +185 -1510
  374. package/src/generators/slice/slice.js.map +1 -1
  375. package/src/generators/upgrade/schema.d.ts +2 -0
  376. package/src/generators/upgrade/schema.json +11 -1
  377. package/src/generators/upgrade/upgrade.d.ts +51 -1
  378. package/src/generators/upgrade/upgrade.js +820 -82
  379. package/src/generators/upgrade/upgrade.js.map +1 -1
  380. package/src/generators/workload/frozen-container.d.ts +8 -0
  381. package/src/generators/workload/frozen-container.js +36 -0
  382. package/src/generators/workload/frozen-container.js.map +1 -0
  383. package/src/generators/workload/performance-registration.d.ts +32 -0
  384. package/src/generators/workload/performance-registration.js +226 -0
  385. package/src/generators/workload/performance-registration.js.map +1 -0
  386. package/src/generators/workload/reliability-registration.d.ts +16 -0
  387. package/src/generators/workload/reliability-registration.js +150 -0
  388. package/src/generators/workload/reliability-registration.js.map +1 -0
  389. package/src/generators/workload/schema.d.ts +1 -0
  390. package/src/generators/workload/schema.json +6 -1
  391. package/src/generators/workload/service-performance-files.d.ts +4 -0
  392. package/src/generators/workload/service-performance-files.js +1846 -0
  393. package/src/generators/workload/service-performance-files.js.map +1 -0
  394. package/src/generators/workload/workload.d.ts +3 -2
  395. package/src/generators/workload/workload.js +1083 -18
  396. package/src/generators/workload/workload.js.map +1 -1
  397. package/src/git-policy/action-pins.d.ts +10 -0
  398. package/src/git-policy/action-pins.js +10 -0
  399. package/src/git-policy/action-pins.js.map +1 -1
  400. package/src/git-policy/github-bootstrap.d.ts +14 -1
  401. package/src/git-policy/github-bootstrap.js +274 -46
  402. package/src/git-policy/github-bootstrap.js.map +1 -1
  403. package/src/index.d.ts +2 -0
  404. package/src/index.js +2 -0
  405. package/src/index.js.map +1 -1
  406. package/src/infrastructure-change/convergence.d.ts +13 -0
  407. package/src/infrastructure-change/convergence.js +85 -0
  408. package/src/infrastructure-change/convergence.js.map +1 -0
  409. package/src/infrastructure-change/drift.d.ts +13 -0
  410. package/src/infrastructure-change/drift.js +112 -0
  411. package/src/infrastructure-change/drift.js.map +1 -0
  412. package/src/infrastructure-change/index.d.ts +7 -0
  413. package/src/infrastructure-change/index.js +11 -0
  414. package/src/infrastructure-change/index.js.map +1 -0
  415. package/src/infrastructure-change/model.d.ts +253 -0
  416. package/src/infrastructure-change/model.js +28 -0
  417. package/src/infrastructure-change/model.js.map +1 -0
  418. package/src/infrastructure-change/normalize.d.ts +12 -0
  419. package/src/infrastructure-change/normalize.js +245 -0
  420. package/src/infrastructure-change/normalize.js.map +1 -0
  421. package/src/infrastructure-change/plan.d.ts +40 -0
  422. package/src/infrastructure-change/plan.js +361 -0
  423. package/src/infrastructure-change/plan.js.map +1 -0
  424. package/src/infrastructure-change/risk.d.ts +7 -0
  425. package/src/infrastructure-change/risk.js +269 -0
  426. package/src/infrastructure-change/risk.js.map +1 -0
  427. package/src/infrastructure-change/summary.d.ts +20 -0
  428. package/src/infrastructure-change/summary.js +130 -0
  429. package/src/infrastructure-change/summary.js.map +1 -0
  430. package/src/local-dev/context-discovery.d.ts +2 -0
  431. package/src/local-dev/context-discovery.js +59 -0
  432. package/src/local-dev/context-discovery.js.map +1 -0
  433. package/src/local-dev/contract-validation.d.ts +15 -0
  434. package/src/local-dev/contract-validation.js +84 -0
  435. package/src/local-dev/contract-validation.js.map +1 -0
  436. package/src/local-dev/contracts.d.ts +10 -1
  437. package/src/local-dev/contracts.js +57 -5
  438. package/src/local-dev/contracts.js.map +1 -1
  439. package/src/local-dev/control-contract.d.ts +67 -0
  440. package/src/local-dev/control-contract.js +180 -0
  441. package/src/local-dev/control-contract.js.map +1 -0
  442. package/src/local-dev/dev-command-core.d.ts +60 -0
  443. package/src/local-dev/dev-command-core.js +89 -0
  444. package/src/local-dev/dev-command-core.js.map +1 -0
  445. package/src/local-dev/effects.d.ts +16 -0
  446. package/src/local-dev/effects.js +63 -0
  447. package/src/local-dev/effects.js.map +1 -0
  448. package/src/local-dev/environment-inspector.d.ts +125 -0
  449. package/src/local-dev/environment-inspector.js +687 -0
  450. package/src/local-dev/environment-inspector.js.map +1 -0
  451. package/src/local-dev/environment-state.d.ts +113 -0
  452. package/src/local-dev/environment-state.js +463 -0
  453. package/src/local-dev/environment-state.js.map +1 -0
  454. package/src/local-dev/errors.d.ts +6 -0
  455. package/src/local-dev/errors.js +41 -0
  456. package/src/local-dev/errors.js.map +1 -0
  457. package/src/local-dev/fleet-client.d.ts +33 -0
  458. package/src/local-dev/fleet-client.js +156 -0
  459. package/src/local-dev/fleet-client.js.map +1 -0
  460. package/src/local-dev/fleet-configuration.d.ts +13 -0
  461. package/src/local-dev/fleet-configuration.js +71 -0
  462. package/src/local-dev/fleet-configuration.js.map +1 -0
  463. package/src/local-dev/fleet-inventory.d.ts +56 -0
  464. package/src/local-dev/fleet-inventory.js +469 -0
  465. package/src/local-dev/fleet-inventory.js.map +1 -0
  466. package/src/local-dev/fleet-ipc.d.ts +28 -0
  467. package/src/local-dev/fleet-ipc.js +267 -0
  468. package/src/local-dev/fleet-ipc.js.map +1 -0
  469. package/src/local-dev/fleet-launcher.d.ts +53 -0
  470. package/src/local-dev/fleet-launcher.js +379 -0
  471. package/src/local-dev/fleet-launcher.js.map +1 -0
  472. package/src/local-dev/fleet-lock.d.ts +41 -0
  473. package/src/local-dev/fleet-lock.js +135 -0
  474. package/src/local-dev/fleet-lock.js.map +1 -0
  475. package/src/local-dev/fleet-logs.d.ts +66 -0
  476. package/src/local-dev/fleet-logs.js +542 -0
  477. package/src/local-dev/fleet-logs.js.map +1 -0
  478. package/src/local-dev/fleet-process.d.ts +50 -0
  479. package/src/local-dev/fleet-process.js +289 -0
  480. package/src/local-dev/fleet-process.js.map +1 -0
  481. package/src/local-dev/fleet-runtime.d.ts +22 -0
  482. package/src/local-dev/fleet-runtime.js +368 -0
  483. package/src/local-dev/fleet-runtime.js.map +1 -0
  484. package/src/local-dev/fleet-session.d.ts +70 -0
  485. package/src/local-dev/fleet-session.js +301 -0
  486. package/src/local-dev/fleet-session.js.map +1 -0
  487. package/src/local-dev/fleet-slice-runner-daemon.d.ts +1 -0
  488. package/src/local-dev/fleet-slice-runner-daemon.js +21 -0
  489. package/src/local-dev/fleet-slice-runner-daemon.js.map +1 -0
  490. package/src/local-dev/fleet-startup.d.ts +22 -0
  491. package/src/local-dev/fleet-startup.js +114 -0
  492. package/src/local-dev/fleet-startup.js.map +1 -0
  493. package/src/local-dev/fleet-state.d.ts +62 -0
  494. package/src/local-dev/fleet-state.js +297 -0
  495. package/src/local-dev/fleet-state.js.map +1 -0
  496. package/src/local-dev/fleet-supervisor-daemon.d.ts +1 -0
  497. package/src/local-dev/fleet-supervisor-daemon.js +41 -0
  498. package/src/local-dev/fleet-supervisor-daemon.js.map +1 -0
  499. package/src/local-dev/fleet-supervisor.d.ts +52 -0
  500. package/src/local-dev/fleet-supervisor.js +384 -0
  501. package/src/local-dev/fleet-supervisor.js.map +1 -0
  502. package/src/local-dev/gateway.js +1 -1
  503. package/src/local-dev/gateway.js.map +1 -1
  504. package/src/local-dev/identity.js +4 -6
  505. package/src/local-dev/identity.js.map +1 -1
  506. package/src/local-dev/index.d.ts +1 -0
  507. package/src/local-dev/index.js +1 -0
  508. package/src/local-dev/index.js.map +1 -1
  509. package/src/local-dev/local-context.d.ts +62 -0
  510. package/src/local-dev/local-context.js +299 -0
  511. package/src/local-dev/local-context.js.map +1 -0
  512. package/src/local-dev/nx-workspace.d.ts +45 -0
  513. package/src/local-dev/nx-workspace.js +228 -0
  514. package/src/local-dev/nx-workspace.js.map +1 -0
  515. package/src/local-dev/personal-secret-ledger.d.ts +75 -0
  516. package/src/local-dev/personal-secret-ledger.js +391 -0
  517. package/src/local-dev/personal-secret-ledger.js.map +1 -0
  518. package/src/local-dev/personal-stage.d.ts +18 -0
  519. package/src/local-dev/personal-stage.js +129 -0
  520. package/src/local-dev/personal-stage.js.map +1 -0
  521. package/src/local-dev/private-state.d.ts +17 -0
  522. package/src/local-dev/private-state.js +254 -0
  523. package/src/local-dev/private-state.js.map +1 -0
  524. package/src/local-dev/process-identity.d.ts +9 -0
  525. package/src/local-dev/process-identity.js +79 -0
  526. package/src/local-dev/process-identity.js.map +1 -0
  527. package/src/local-dev/runtime-paths.d.ts +17 -0
  528. package/src/local-dev/runtime-paths.js +33 -0
  529. package/src/local-dev/runtime-paths.js.map +1 -0
  530. package/src/local-dev/secret-slice-claim.d.ts +17 -0
  531. package/src/local-dev/secret-slice-claim.js +75 -0
  532. package/src/local-dev/secret-slice-claim.js.map +1 -0
  533. package/src/local-dev/slice-claim.d.ts +36 -0
  534. package/src/local-dev/slice-claim.js +199 -0
  535. package/src/local-dev/slice-claim.js.map +1 -0
  536. package/src/local-dev/slice-runner.d.ts +121 -0
  537. package/src/local-dev/slice-runner.js +715 -0
  538. package/src/local-dev/slice-runner.js.map +1 -0
  539. package/src/local-dev/tls.js +51 -17
  540. package/src/local-dev/tls.js.map +1 -1
  541. package/src/ownership/composition.d.ts +5 -2
  542. package/src/ownership/composition.js +26 -3
  543. package/src/ownership/composition.js.map +1 -1
  544. package/src/ownership/hash.d.ts +1 -1
  545. package/src/ownership/hash.js +3 -1
  546. package/src/ownership/hash.js.map +1 -1
  547. package/src/ownership/record.d.ts +32 -1
  548. package/src/ownership/record.js +139 -2
  549. package/src/ownership/record.js.map +1 -1
  550. package/src/performance/cost-collection.d.ts +225 -0
  551. package/src/performance/cost-collection.js +825 -0
  552. package/src/performance/cost-collection.js.map +1 -0
  553. package/src/performance/cost-controls.d.ts +131 -0
  554. package/src/performance/cost-controls.js +336 -0
  555. package/src/performance/cost-controls.js.map +1 -0
  556. package/src/performance/evidence.d.ts +140 -0
  557. package/src/performance/evidence.js +599 -0
  558. package/src/performance/evidence.js.map +1 -0
  559. package/src/performance/index.d.ts +10 -0
  560. package/src/performance/index.js +14 -0
  561. package/src/performance/index.js.map +1 -0
  562. package/src/performance/load-safety.d.ts +84 -0
  563. package/src/performance/load-safety.js +346 -0
  564. package/src/performance/load-safety.js.map +1 -0
  565. package/src/performance/model.d.ts +129 -0
  566. package/src/performance/model.js +452 -0
  567. package/src/performance/model.js.map +1 -0
  568. package/src/performance/pr-artifact-budget.d.ts +42 -0
  569. package/src/performance/pr-artifact-budget.js +151 -0
  570. package/src/performance/pr-artifact-budget.js.map +1 -0
  571. package/src/performance/runner.d.ts +75 -0
  572. package/src/performance/runner.js +227 -0
  573. package/src/performance/runner.js.map +1 -0
  574. package/src/performance/scenario-endpoint.d.ts +1 -0
  575. package/src/performance/scenario-endpoint.js +5 -0
  576. package/src/performance/scenario-endpoint.js.map +1 -0
  577. package/src/performance/scenario.d.ts +193 -0
  578. package/src/performance/scenario.js +736 -0
  579. package/src/performance/scenario.js.map +1 -0
  580. package/src/performance/test-fixtures.d.ts +7 -0
  581. package/src/performance/test-fixtures.js +169 -0
  582. package/src/performance/test-fixtures.js.map +1 -0
  583. package/src/performance/unit-cost.d.ts +105 -0
  584. package/src/performance/unit-cost.js +364 -0
  585. package/src/performance/unit-cost.js.map +1 -0
  586. package/src/release/index.d.ts +4 -0
  587. package/src/release/index.js +8 -0
  588. package/src/release/index.js.map +1 -0
  589. package/src/release/lifecycle.d.ts +132 -0
  590. package/src/release/lifecycle.js +411 -0
  591. package/src/release/lifecycle.js.map +1 -0
  592. package/src/release/manifest.d.ts +148 -0
  593. package/src/release/manifest.js +503 -0
  594. package/src/release/manifest.js.map +1 -0
  595. package/src/release/rollout.d.ts +170 -0
  596. package/src/release/rollout.js +578 -0
  597. package/src/release/rollout.js.map +1 -0
  598. package/src/release/source-authority.d.ts +19 -0
  599. package/src/release/source-authority.js +195 -0
  600. package/src/release/source-authority.js.map +1 -0
  601. package/src/repository-policy/deployment-plan.d.ts +2 -1
  602. package/src/repository-policy/deployment-plan.js +2 -0
  603. package/src/repository-policy/deployment-plan.js.map +1 -1
  604. package/src/repository-policy/policy.d.ts +1 -1
  605. package/src/repository-policy/policy.js +29 -25
  606. package/src/repository-policy/policy.js.map +1 -1
  607. package/src/schemas/aggregate-count-evidence-v1.json +135 -0
  608. package/src/schemas/api-contract-baseline-v1.json +674 -0
  609. package/src/schemas/api-contract-compatibility-report-v1.json +126 -0
  610. package/src/schemas/aws-billing-actual-evidence-v1.json +407 -0
  611. package/src/schemas/blueprint-v1.json +18 -1
  612. package/src/schemas/data-lifecycle-policy-v1.json +517 -0
  613. package/src/schemas/generated-ownership-v2.json +22 -0
  614. package/src/schemas/performance-policy-v1.json +335 -0
  615. package/src/schemas/repository-policy-v1.json +1 -0
  616. package/src/schemas/testing-evidence-quarantine-v1.json +168 -0
  617. package/src/schemas/testing-evidence-result-v1.json +319 -0
  618. package/src/secrets/adapters.d.ts +49 -0
  619. package/src/secrets/adapters.js +184 -0
  620. package/src/secrets/adapters.js.map +1 -0
  621. package/src/secrets/aws-control-plane.d.ts +20 -0
  622. package/src/secrets/aws-control-plane.js +255 -0
  623. package/src/secrets/aws-control-plane.js.map +1 -0
  624. package/src/secrets/declaration-verification.d.ts +11 -0
  625. package/src/secrets/declaration-verification.js +68 -0
  626. package/src/secrets/declaration-verification.js.map +1 -0
  627. package/src/secrets/guardrails.d.ts +26 -0
  628. package/src/secrets/guardrails.js +328 -0
  629. package/src/secrets/guardrails.js.map +1 -0
  630. package/src/secrets/index.d.ts +12 -0
  631. package/src/secrets/index.js +16 -0
  632. package/src/secrets/index.js.map +1 -0
  633. package/src/secrets/metadata.d.ts +48 -0
  634. package/src/secrets/metadata.js +171 -0
  635. package/src/secrets/metadata.js.map +1 -0
  636. package/src/secrets/model.d.ts +49 -0
  637. package/src/secrets/model.js +125 -0
  638. package/src/secrets/model.js.map +1 -0
  639. package/src/secrets/operator.d.ts +101 -0
  640. package/src/secrets/operator.js +229 -0
  641. package/src/secrets/operator.js.map +1 -0
  642. package/src/secrets/permissions.d.ts +34 -0
  643. package/src/secrets/permissions.js +86 -0
  644. package/src/secrets/permissions.js.map +1 -0
  645. package/src/secrets/personal-command.d.ts +83 -0
  646. package/src/secrets/personal-command.js +197 -0
  647. package/src/secrets/personal-command.js.map +1 -0
  648. package/src/secrets/personal-ledger.d.ts +21 -0
  649. package/src/secrets/personal-ledger.js +135 -0
  650. package/src/secrets/personal-ledger.js.map +1 -0
  651. package/src/secrets/registry.d.ts +9 -0
  652. package/src/secrets/registry.js +86 -0
  653. package/src/secrets/registry.js.map +1 -0
  654. package/src/secrets/verification.d.ts +18 -0
  655. package/src/secrets/verification.js +139 -0
  656. package/src/secrets/verification.js.map +1 -0
  657. package/src/secrets/workload-access.d.ts +46 -0
  658. package/src/secrets/workload-access.js +163 -0
  659. package/src/secrets/workload-access.js.map +1 -0
  660. package/src/supply-chain/action-pins.d.ts +26 -0
  661. package/src/supply-chain/action-pins.js +208 -0
  662. package/src/supply-chain/action-pins.js.map +1 -0
  663. package/src/supply-chain/blueprint-trust-git.d.ts +130 -0
  664. package/src/supply-chain/blueprint-trust-git.js +1581 -0
  665. package/src/supply-chain/blueprint-trust-git.js.map +1 -0
  666. package/src/supply-chain/blueprint-trust-process.d.ts +4 -0
  667. package/src/supply-chain/blueprint-trust-process.js +88 -0
  668. package/src/supply-chain/blueprint-trust-process.js.map +1 -0
  669. package/src/supply-chain/blueprint-trust.d.ts +354 -0
  670. package/src/supply-chain/blueprint-trust.js +1558 -0
  671. package/src/supply-chain/blueprint-trust.js.map +1 -0
  672. package/src/supply-chain/blueprint-verification-authority.d.ts +62 -0
  673. package/src/supply-chain/blueprint-verification-authority.js +588 -0
  674. package/src/supply-chain/blueprint-verification-authority.js.map +1 -0
  675. package/src/supply-chain/blueprint-verification-execution.d.ts +68 -0
  676. package/src/supply-chain/blueprint-verification-execution.js +624 -0
  677. package/src/supply-chain/blueprint-verification-execution.js.map +1 -0
  678. package/src/supply-chain/build-output.d.ts +35 -0
  679. package/src/supply-chain/build-output.js +398 -0
  680. package/src/supply-chain/build-output.js.map +1 -0
  681. package/src/supply-chain/common.d.ts +21 -0
  682. package/src/supply-chain/common.js +107 -0
  683. package/src/supply-chain/common.js.map +1 -0
  684. package/src/supply-chain/container-artifact-contract.d.ts +23 -0
  685. package/src/supply-chain/container-artifact-contract.js +208 -0
  686. package/src/supply-chain/container-artifact-contract.js.map +1 -0
  687. package/src/supply-chain/container-release.d.ts +119 -0
  688. package/src/supply-chain/container-release.js +496 -0
  689. package/src/supply-chain/container-release.js.map +1 -0
  690. package/src/supply-chain/ecr-applicability.d.ts +19 -0
  691. package/src/supply-chain/ecr-applicability.js +103 -0
  692. package/src/supply-chain/ecr-applicability.js.map +1 -0
  693. package/src/supply-chain/ecr-scanning.d.ts +83 -0
  694. package/src/supply-chain/ecr-scanning.js +321 -0
  695. package/src/supply-chain/ecr-scanning.js.map +1 -0
  696. package/src/supply-chain/ecs-deployed-image.d.ts +85 -0
  697. package/src/supply-chain/ecs-deployed-image.js +300 -0
  698. package/src/supply-chain/ecs-deployed-image.js.map +1 -0
  699. package/src/supply-chain/errors.d.ts +6 -0
  700. package/src/supply-chain/errors.js +15 -0
  701. package/src/supply-chain/errors.js.map +1 -0
  702. package/src/supply-chain/generated-blueprint-trust-command.d.ts +4 -0
  703. package/src/supply-chain/generated-blueprint-trust-command.js +1362 -0
  704. package/src/supply-chain/generated-blueprint-trust-command.js.map +1 -0
  705. package/src/supply-chain/generated-command.d.ts +1 -0
  706. package/src/supply-chain/generated-command.js +687 -0
  707. package/src/supply-chain/generated-command.js.map +1 -0
  708. package/src/supply-chain/generated-container-release-command.d.ts +16 -0
  709. package/src/supply-chain/generated-container-release-command.js +2087 -0
  710. package/src/supply-chain/generated-container-release-command.js.map +1 -0
  711. package/src/supply-chain/generated-ecr-collector-command.d.ts +21 -0
  712. package/src/supply-chain/generated-ecr-collector-command.js +1338 -0
  713. package/src/supply-chain/generated-ecr-collector-command.js.map +1 -0
  714. package/src/supply-chain/generated-files.d.ts +13 -0
  715. package/src/supply-chain/generated-files.js +401 -0
  716. package/src/supply-chain/generated-files.js.map +1 -0
  717. package/src/supply-chain/generated-scanner-command.d.ts +1 -0
  718. package/src/supply-chain/generated-scanner-command.js +718 -0
  719. package/src/supply-chain/generated-scanner-command.js.map +1 -0
  720. package/src/supply-chain/index.d.ts +28 -0
  721. package/src/supply-chain/index.js +35 -0
  722. package/src/supply-chain/index.js.map +1 -0
  723. package/src/supply-chain/inventory.d.ts +79 -0
  724. package/src/supply-chain/inventory.js +717 -0
  725. package/src/supply-chain/inventory.js.map +1 -0
  726. package/src/supply-chain/license-expression.d.ts +7 -0
  727. package/src/supply-chain/license-expression.js +128 -0
  728. package/src/supply-chain/license-expression.js.map +1 -0
  729. package/src/supply-chain/pnpm-toolchain-authority.d.ts +31 -0
  730. package/src/supply-chain/pnpm-toolchain-authority.js +316 -0
  731. package/src/supply-chain/pnpm-toolchain-authority.js.map +1 -0
  732. package/src/supply-chain/policy.d.ts +105 -0
  733. package/src/supply-chain/policy.js +301 -0
  734. package/src/supply-chain/policy.js.map +1 -0
  735. package/src/supply-chain/publication.d.ts +48 -0
  736. package/src/supply-chain/publication.js +85 -0
  737. package/src/supply-chain/publication.js.map +1 -0
  738. package/src/supply-chain/sbom.d.ts +228 -0
  739. package/src/supply-chain/sbom.js +826 -0
  740. package/src/supply-chain/sbom.js.map +1 -0
  741. package/src/supply-chain/scanner.d.ts +77 -0
  742. package/src/supply-chain/scanner.js +502 -0
  743. package/src/supply-chain/scanner.js.map +1 -0
  744. package/src/supply-chain/vendored-source.d.ts +95 -0
  745. package/src/supply-chain/vendored-source.js +315 -0
  746. package/src/supply-chain/vendored-source.js.map +1 -0
  747. package/src/testing-evidence/fixture-scope.d.ts +52 -0
  748. package/src/testing-evidence/fixture-scope.js +140 -0
  749. package/src/testing-evidence/fixture-scope.js.map +1 -0
  750. package/src/testing-evidence/index.d.ts +5 -0
  751. package/src/testing-evidence/index.js +9 -0
  752. package/src/testing-evidence/index.js.map +1 -0
  753. package/src/testing-evidence/quarantine.d.ts +48 -0
  754. package/src/testing-evidence/quarantine.js +253 -0
  755. package/src/testing-evidence/quarantine.js.map +1 -0
  756. package/src/testing-evidence/result.d.ts +108 -0
  757. package/src/testing-evidence/result.js +571 -0
  758. package/src/testing-evidence/result.js.map +1 -0
  759. package/src/testing-evidence/runner-adapter.d.ts +51 -0
  760. package/src/testing-evidence/runner-adapter.js +288 -0
  761. package/src/testing-evidence/runner-adapter.js.map +1 -0
  762. package/src/testing-evidence/sensitive-artifacts.d.ts +13 -0
  763. package/src/testing-evidence/sensitive-artifacts.js +93 -0
  764. package/src/testing-evidence/sensitive-artifacts.js.map +1 -0
  765. package/src/work-markers/work-markers.js +4 -3
  766. package/src/work-markers/work-markers.js.map +1 -1
  767. package/src/workload-roles/account-baseline.d.ts +132 -0
  768. package/src/workload-roles/account-baseline.js +743 -0
  769. package/src/workload-roles/account-baseline.js.map +1 -0
  770. package/src/workload-roles/authority.d.ts +38 -0
  771. package/src/workload-roles/authority.js +156 -0
  772. package/src/workload-roles/authority.js.map +1 -0
  773. package/src/workload-roles/aws-adapter.d.ts +99 -0
  774. package/src/workload-roles/aws-adapter.js +416 -0
  775. package/src/workload-roles/aws-adapter.js.map +1 -0
  776. package/src/workload-roles/aws-internal-handler.d.ts +27 -0
  777. package/src/workload-roles/aws-internal-handler.js +58 -0
  778. package/src/workload-roles/aws-internal-handler.js.map +1 -0
  779. package/src/workload-roles/aws-s3-state.d.ts +52 -0
  780. package/src/workload-roles/aws-s3-state.js +164 -0
  781. package/src/workload-roles/aws-s3-state.js.map +1 -0
  782. package/src/workload-roles/aws-sdk-control-plane.d.ts +12 -0
  783. package/src/workload-roles/aws-sdk-control-plane.js +291 -0
  784. package/src/workload-roles/aws-sdk-control-plane.js.map +1 -0
  785. package/src/workload-roles/boundary.d.ts +33 -0
  786. package/src/workload-roles/boundary.js +130 -0
  787. package/src/workload-roles/boundary.js.map +1 -0
  788. package/src/workload-roles/convergence.d.ts +66 -0
  789. package/src/workload-roles/convergence.js +186 -0
  790. package/src/workload-roles/convergence.js.map +1 -0
  791. package/src/workload-roles/effective-policy.d.ts +51 -0
  792. package/src/workload-roles/effective-policy.js +183 -0
  793. package/src/workload-roles/effective-policy.js.map +1 -0
  794. package/src/workload-roles/handler.d.ts +43 -0
  795. package/src/workload-roles/handler.js +130 -0
  796. package/src/workload-roles/handler.js.map +1 -0
  797. package/src/workload-roles/history.d.ts +47 -0
  798. package/src/workload-roles/history.js +324 -0
  799. package/src/workload-roles/history.js.map +1 -0
  800. package/src/workload-roles/index.d.ts +17 -0
  801. package/src/workload-roles/index.js +21 -0
  802. package/src/workload-roles/index.js.map +1 -0
  803. package/src/workload-roles/injection.d.ts +119 -0
  804. package/src/workload-roles/injection.js +495 -0
  805. package/src/workload-roles/injection.js.map +1 -0
  806. package/src/workload-roles/manifest.d.ts +54 -0
  807. package/src/workload-roles/manifest.js +343 -0
  808. package/src/workload-roles/manifest.js.map +1 -0
  809. package/src/workload-roles/provisioner.d.ts +70 -0
  810. package/src/workload-roles/provisioner.js +231 -0
  811. package/src/workload-roles/provisioner.js.map +1 -0
  812. package/src/workload-roles/registration.d.ts +101 -0
  813. package/src/workload-roles/registration.js +457 -0
  814. package/src/workload-roles/registration.js.map +1 -0
  815. package/src/workload-roles/stage-boundary.d.ts +31 -0
  816. package/src/workload-roles/stage-boundary.js +208 -0
  817. package/src/workload-roles/stage-boundary.js.map +1 -0
  818. package/src/workload-roles/standard-slice.d.ts +54 -0
  819. package/src/workload-roles/standard-slice.js +252 -0
  820. package/src/workload-roles/standard-slice.js.map +1 -0
  821. package/migrations/standard-pre-v1-to-v1.md +0 -11
  822. package/src/foundation/aurora-logical-database.d.ts +0 -76
  823. package/src/foundation/aurora-logical-database.js +0 -419
  824. package/src/foundation/aurora-logical-database.js.map +0 -1
@@ -0,0 +1,3038 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.renderProductInfrastructureFiles = renderProductInfrastructureFiles;
4
+ const built_in_plan_shared_js_1 = require("./built-in-plan-shared.js");
5
+ /** @internal */
6
+ function renderProductInfrastructureFiles(name, pascalName, workspace) {
7
+ const identifierPrefix = (0, built_in_plan_shared_js_1.lowerCamelIdentifier)(pascalName);
8
+ const recordExportWorkflowName = (0, built_in_plan_shared_js_1.stableGeneratedName)(40, name);
9
+ return {
10
+ 'jobs/data-retention-maintenance-launcher.ts': `import {
11
+ ECSClient,
12
+ RunTaskCommand,
13
+ } from '@aws-sdk/client-ecs';
14
+ import { createLogger } from '@empire-builder-kit/runtime/logging';
15
+
16
+ const UUID =
17
+ /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
18
+ const ECS_CLUSTER_ARN =
19
+ new RegExp(
20
+ '^arn:(?:aws|aws-cn|aws-us-gov):ecs:[a-z0-9-]+:' +
21
+ '[0-9]{12}:cluster/[A-Za-z0-9_./-]{1,255}$',
22
+ );
23
+ const ECS_TASK_DEFINITION_ARN =
24
+ /^arn:(?:aws|aws-cn|aws-us-gov):ecs:[a-z0-9-]+:[0-9]{12}:task-definition\\/[A-Za-z0-9_-]{1,255}:[1-9][0-9]*$/;
25
+ const NETWORK_ID = /^(?:sg|subnet)-[0-9a-f]{8,32}$/;
26
+ const CONTAINER_NAME = /^[A-Za-z0-9_-]{1,255}$/;
27
+
28
+ function required(name: string): string {
29
+ const value = process.env[name]?.trim();
30
+ if (!value) throw new Error(name + ' is required.');
31
+ return value;
32
+ }
33
+
34
+ function requiredArn(name: string, pattern: RegExp): string {
35
+ const value = required(name);
36
+ if (!pattern.test(value)) {
37
+ throw new Error(name + ' is not the exact expected ECS ARN.');
38
+ }
39
+ return value;
40
+ }
41
+
42
+ function requiredBoolean(name: string): boolean {
43
+ const value = required(name);
44
+ if (value !== 'true' && value !== 'false') {
45
+ throw new Error(name + ' must be exactly true or false.');
46
+ }
47
+ return value === 'true';
48
+ }
49
+
50
+ function requiredNetworkIds(name: string): string[] {
51
+ let value: unknown;
52
+ try {
53
+ value = JSON.parse(required(name));
54
+ } catch {
55
+ throw new Error(name + ' must be one bounded JSON array.');
56
+ }
57
+ if (
58
+ !Array.isArray(value) ||
59
+ value.length < 1 ||
60
+ value.length > 16 ||
61
+ value.some(
62
+ (entry) => typeof entry !== 'string' || !NETWORK_ID.test(entry),
63
+ )
64
+ ) {
65
+ throw new Error(name + ' must contain only exact AWS network IDs.');
66
+ }
67
+ return value;
68
+ }
69
+
70
+ function requiredScheduledEventId(event: unknown): string {
71
+ const id =
72
+ event && typeof event === 'object' && 'id' in event
73
+ ? (event as { id?: unknown }).id
74
+ : undefined;
75
+ if (typeof id !== 'string' || !UUID.test(id)) {
76
+ throw new Error('Scheduled maintenance requires one exact event ID.');
77
+ }
78
+ return id.toLowerCase();
79
+ }
80
+
81
+ export async function handler(event: unknown) {
82
+ const stage = required('EBK_STAGE');
83
+ const logger = createLogger({
84
+ component: 'job-data-retention-maintenance-launcher',
85
+ feature: 'data-lifecycle',
86
+ operation: 'retention-maintenance-launch',
87
+ slice: '${name}',
88
+ stage,
89
+ workspace: required('EBK_WORKSPACE'),
90
+ });
91
+ const correlationId = requiredScheduledEventId(event);
92
+ try {
93
+ const containerName = required('EBK_MAINTENANCE_CONTAINER_NAME');
94
+ if (!CONTAINER_NAME.test(containerName)) {
95
+ throw new Error('Maintenance container name is invalid.');
96
+ }
97
+ const response = await new ECSClient({}).send(
98
+ new RunTaskCommand({
99
+ cluster: requiredArn('EBK_MAINTENANCE_CLUSTER_ARN', ECS_CLUSTER_ARN),
100
+ count: 1,
101
+ enableExecuteCommand: false,
102
+ launchType: 'FARGATE',
103
+ networkConfiguration: {
104
+ awsvpcConfiguration: {
105
+ assignPublicIp:
106
+ requiredBoolean('EBK_MAINTENANCE_ASSIGN_PUBLIC_IP')
107
+ ? 'ENABLED'
108
+ : 'DISABLED',
109
+ securityGroups: requiredNetworkIds(
110
+ 'EBK_MAINTENANCE_SECURITY_GROUP_IDS',
111
+ ),
112
+ subnets: requiredNetworkIds('EBK_MAINTENANCE_SUBNET_IDS'),
113
+ },
114
+ },
115
+ overrides: {
116
+ containerOverrides: [
117
+ {
118
+ command: [
119
+ 'node',
120
+ 'dist/jobs/data-retention/src/maintenance.js',
121
+ ],
122
+ environment: [
123
+ {
124
+ name: 'EBK_MAINTENANCE_CORRELATION_ID',
125
+ value: correlationId,
126
+ },
127
+ ],
128
+ name: containerName,
129
+ },
130
+ ],
131
+ },
132
+ taskDefinition: requiredArn(
133
+ 'EBK_MAINTENANCE_TASK_DEFINITION_ARN',
134
+ ECS_TASK_DEFINITION_ARN,
135
+ ),
136
+ }),
137
+ );
138
+ if (
139
+ (response.failures?.length ?? 0) !== 0 ||
140
+ response.tasks?.length !== 1 ||
141
+ !response.tasks[0]?.taskArn
142
+ ) {
143
+ throw new Error('ECS did not accept exactly one maintenance task.');
144
+ }
145
+ logger.info('Retention maintenance task launched', {
146
+ attributes: { launchCount: 1 },
147
+ outcome: 'success',
148
+ });
149
+ return { accepted: true };
150
+ } catch (error) {
151
+ logger.error('Retention maintenance task launch failed', {
152
+ attributes: {
153
+ errorCode:
154
+ error instanceof Error
155
+ ? error.name.replace(/[^A-Za-z0-9_.-]/g, '').slice(0, 80)
156
+ : 'RetentionMaintenanceLaunchError',
157
+ },
158
+ outcome: 'failure',
159
+ });
160
+ throw error;
161
+ }
162
+ }
163
+ `,
164
+ 'infra/product.ts': `import {
165
+ createManagedLogGroupContract,
166
+ createRouterOriginProofInjection,
167
+ provisionDatabaseBindings,
168
+ } from '@empire-builder-kit/nx/foundation';
169
+ import { validateReleaseManifest } from '@empire-builder-kit/nx/release';
170
+ import * as random from '@pulumi/random';
171
+ import { createHash } from 'node:crypto';
172
+ import {
173
+ lstatSync,
174
+ readFileSync,
175
+ readdirSync,
176
+ realpathSync,
177
+ } from 'node:fs';
178
+ import { fileURLToPath } from 'node:url';
179
+ import {
180
+ ${identifierPrefix}CleanupRecordExport,
181
+ ${identifierPrefix}DatabaseHealth,
182
+ ${identifierPrefix}DecideRecordExport,
183
+ ${identifierPrefix}DecideRecordDeletion,
184
+ ${identifierPrefix}DeliverRecordExport,
185
+ ${identifierPrefix}DeleteRecord,
186
+ ${identifierPrefix}ExecuteRecordExport,
187
+ ${identifierPrefix}ExpireRecordHold,
188
+ ${identifierPrefix}GetRecord,
189
+ ${identifierPrefix}Health,
190
+ ${identifierPrefix}IssueRecordDelegation,
191
+ ${identifierPrefix}IssueRecordHold,
192
+ ${identifierPrefix}MachineAuthorizationProof,
193
+ ${identifierPrefix}ProtectedExample,
194
+ ${identifierPrefix}PublishCreated,
195
+ ${identifierPrefix}PutRecord,
196
+ ${identifierPrefix}DecideRetentionApproval,
197
+ ${identifierPrefix}ReleaseRecordHold,
198
+ ${identifierPrefix}RequestRecordExport,
199
+ ${identifierPrefix}RequestRecordDeletion,
200
+ ${identifierPrefix}RequestRetentionApproval,
201
+ ${identifierPrefix}ReviewRecordHold,
202
+ ${identifierPrefix}RevokeRecordDelegation,
203
+ ${identifierPrefix}UseRecordDelegation,
204
+ } from '../api/contracts/protected.js';
205
+ import lifecycleConsumers from '../data/lifecycle-consumers.json';
206
+ import databaseBindings from '../database/bindings.json';
207
+ import localConfiguration from '../configuration/local-values.json';
208
+ import { ConfigurationDeclarations } from '../configuration/declarations.js';
209
+ import { assertAsyncAdapterDeploymentReady } from '../async/recovery-adapters.js';
210
+ import {
211
+ getFoundationInfrastructure,
212
+ registerProductMigration,
213
+ registerProductRetention,
214
+ } from './foundation.js';
215
+ import {
216
+ standardSliceDatabaseTaskFamily,
217
+ standardSliceResourceNames,
218
+ } from './workload-roles/effective-intent.js';
219
+ import type { StandardSliceWorkloadRolePreflight } from './workload-roles/preflight.js';
220
+ import { createStandardSliceSecretLinks } from './personal-secrets.js';
221
+ import { create${pascalName}PerformanceCostControls } from './performance-cost-controls.js';
222
+ import {
223
+ createStandardSliceMachineAuthorization,
224
+ standardSliceMachineClientProvisionerPermissions,
225
+ type MachineCredentialGeneration,
226
+ } from './authorization.js';
227
+ import { createStandardDataExportStore } from './data-export.js';
228
+ import { create${pascalName}Reliability } from './reliability.js';
229
+ import { resolveStandardContainerImage } from './container-images.js';
230
+ import {
231
+ defineEventConsumerAlarms,
232
+ } from '@empire-builder-kit/runtime/observability';
233
+ // <ebk:composition-imports:start>
234
+ // <ebk:composition-imports:end>
235
+
236
+ function protectedPerformanceDeploymentAuthority(input: {
237
+ enabled: string;
238
+ releaseSourceCommit: string;
239
+ stage: string;
240
+ }) {
241
+ if (input.enabled !== 'true' && input.enabled !== 'false') {
242
+ throw new Error(
243
+ 'EBK_PERFORMANCE_LOAD_ENABLED must equal true or false.',
244
+ );
245
+ }
246
+ if (input.enabled === 'false') return undefined;
247
+ if (input.stage !== 'staging' && input.stage !== 'production') {
248
+ throw new Error(
249
+ 'Protected performance load can be deployed only to staging or production.',
250
+ );
251
+ }
252
+ const accountId = process.env.AWS_EXPECTED_ACCOUNT_ID?.trim() ?? '';
253
+ const region = process.env.AWS_REGION?.trim() ?? '';
254
+ if (
255
+ !/^[0-9]{12}$/.test(accountId) ||
256
+ !/^(?:(?:af|ap|ca|cn|eu|il|me|mx|sa|us)(?:-(?:gov|iso|isob|isoe|isof))?|eusc-[a-z]{2})-[a-z]+-\\d$/.test(
257
+ region,
258
+ )
259
+ ) {
260
+ throw new Error(
261
+ 'Protected performance load AWS target is absent or malformed.',
262
+ );
263
+ }
264
+ const candidateBytes = readFileSync(
265
+ new URL(
266
+ '../../../.ebk/evidence/release-candidate/packages/${name}/candidate.json',
267
+ import.meta.url,
268
+ ),
269
+ );
270
+ const candidate = JSON.parse(candidateBytes.toString('utf8')) as {
271
+ kind?: unknown;
272
+ schemaVersion?: unknown;
273
+ slice?: unknown;
274
+ sourceCommit?: unknown;
275
+ };
276
+ if (
277
+ candidate.kind !== 'ebk-release-candidate' ||
278
+ candidate.schemaVersion !== 1 ||
279
+ candidate.slice !== '${name}' ||
280
+ candidate.sourceCommit !== input.releaseSourceCommit ||
281
+ !/^(?:[a-f0-9]{40}|[a-f0-9]{64})$/.test(input.releaseSourceCommit)
282
+ ) {
283
+ throw new Error(
284
+ 'Protected performance load requires the exact deployed release candidate.',
285
+ );
286
+ }
287
+ const releaseManifestDirectory = new URL(
288
+ '../.ebk/evidence/release-manifests/',
289
+ import.meta.url,
290
+ );
291
+ const releaseManifestNames = readdirSync(releaseManifestDirectory).filter(
292
+ (entry) => /^release-[a-f0-9]{24}[.]json$/.test(entry),
293
+ );
294
+ if (releaseManifestNames.length !== 1) {
295
+ throw new Error(
296
+ 'Protected performance load requires one exact deployed release manifest.',
297
+ );
298
+ }
299
+ const releaseManifestUrl = new URL(
300
+ releaseManifestNames[0]!,
301
+ releaseManifestDirectory,
302
+ );
303
+ const releaseManifestPath = fileURLToPath(releaseManifestUrl);
304
+ const releaseManifestMetadata = lstatSync(releaseManifestPath);
305
+ if (
306
+ !releaseManifestMetadata.isFile() ||
307
+ releaseManifestMetadata.isSymbolicLink() ||
308
+ releaseManifestMetadata.size <= 0 ||
309
+ releaseManifestMetadata.size > 10 * 1024 * 1024 ||
310
+ realpathSync(releaseManifestPath) !== releaseManifestPath
311
+ ) {
312
+ throw new Error(
313
+ 'Protected performance load release manifest is not one bounded canonical file.',
314
+ );
315
+ }
316
+ const releaseManifest = validateReleaseManifest(
317
+ JSON.parse(readFileSync(releaseManifestPath, 'utf8')),
318
+ {
319
+ requireSupplyChain: true,
320
+ sourceCommit: input.releaseSourceCommit,
321
+ },
322
+ );
323
+ if (releaseManifestNames[0] !== releaseManifest.releaseId + '.json') {
324
+ throw new Error(
325
+ 'Protected performance load release manifest path differs from its identity.',
326
+ );
327
+ }
328
+ return Object.freeze({
329
+ candidateDigest: createHash('sha256')
330
+ .update(candidateBytes)
331
+ .digest('hex'),
332
+ releaseId: releaseManifest.releaseId,
333
+ releaseMaterialDigest: releaseManifest.materialDigest,
334
+ target: Object.freeze({
335
+ accountFingerprint:
336
+ 'sha256:' +
337
+ createHash('sha256').update(accountId, 'utf8').digest('hex'),
338
+ region,
339
+ slice: '${name}',
340
+ stage: input.stage as 'production' | 'staging',
341
+ }),
342
+ });
343
+ }
344
+
345
+ function managedLogGroup(
346
+ logicalName: string,
347
+ component: string,
348
+ alertTopicArn: any,
349
+ resource?: string,
350
+ ) {
351
+ const contract = managedLogContract(component, resource);
352
+ const logGroup = new aws.cloudwatch.LogGroup(logicalName, {
353
+ logGroupClass: contract.logGroupClass,
354
+ name: contract.name,
355
+ retentionInDays: contract.retentionInDays,
356
+ tags: contract.tags,
357
+ });
358
+ const dataProtection = managedLogDataProtection(
359
+ logicalName,
360
+ contract,
361
+ logGroup.name,
362
+ [logGroup],
363
+ alertTopicArn,
364
+ );
365
+ return { contract, dataProtection, logGroup };
366
+ }
367
+
368
+ function managedLogContract(component: string, resource?: string) {
369
+ return createManagedLogGroupContract({
370
+ component,
371
+ ...(resource ? { resource } : {}),
372
+ slice: '${name}',
373
+ stage: $app.stage,
374
+ workspace: '${workspace}',
375
+ });
376
+ }
377
+
378
+ function operationalAlarmDescription(input: {
379
+ businessSymptom: string;
380
+ id: string;
381
+ runbook: string;
382
+ }) {
383
+ return JSON.stringify({
384
+ businessSymptom: input.businessSymptom,
385
+ id: input.id,
386
+ owner: '${name}-product',
387
+ route: '${name}-on-call',
388
+ runbook: input.runbook,
389
+ schemaVersion: 1,
390
+ slice: '${name}',
391
+ });
392
+ }
393
+
394
+ function managedLogDataProtection(
395
+ logicalName: string,
396
+ contract: ReturnType<typeof createManagedLogGroupContract>,
397
+ logGroupName: any,
398
+ dependsOn: any[],
399
+ alertTopicArn: any,
400
+ ) {
401
+ const policy = new aws.cloudwatch.LogDataProtectionPolicy(
402
+ logicalName + 'DataProtection',
403
+ {
404
+ logGroupName,
405
+ policyDocument: JSON.stringify(contract.dataProtectionPolicy),
406
+ },
407
+ { dependsOn },
408
+ );
409
+ const alarm = new aws.cloudwatch.MetricAlarm(
410
+ logicalName + 'DataProtectionFindings',
411
+ {
412
+ alarmActions: [alertTopicArn],
413
+ alarmDescription: operationalAlarmDescription({
414
+ businessSymptom:
415
+ 'A managed log group contains a protected-data finding.',
416
+ id: 'data-protection-findings',
417
+ runbook: 'docs/runbooks/log-investigation.md',
418
+ }),
419
+ comparisonOperator: 'GreaterThanOrEqualToThreshold',
420
+ dimensions: { LogGroupName: logGroupName },
421
+ evaluationPeriods: 1,
422
+ metricName: 'LogEventsWithFindings',
423
+ namespace: 'AWS/Logs',
424
+ period: 300,
425
+ statistic: 'Sum',
426
+ threshold: 1,
427
+ treatMissingData: 'notBreaching',
428
+ },
429
+ { dependsOn: [policy] },
430
+ );
431
+ return { alarm, policy };
432
+ }
433
+
434
+ function lambdaLogging(logGroupName: any, functionName?: any) {
435
+ return {
436
+ logging: { format: 'json' as const, logGroup: logGroupName },
437
+ transform: {
438
+ function: {
439
+ ...(functionName ? { name: functionName } : {}),
440
+ loggingConfig: {
441
+ applicationLogLevel: 'TRACE',
442
+ logFormat: 'JSON',
443
+ logGroup: logGroupName,
444
+ systemLogLevel: 'INFO',
445
+ },
446
+ },
447
+ },
448
+ };
449
+ }
450
+
451
+ export function product(workloadRoles?: StandardSliceWorkloadRolePreflight) {
452
+ if (!$dev && !workloadRoles) {
453
+ throw new Error(
454
+ 'Protected workload-role preflight must complete before infrastructure construction.',
455
+ );
456
+ }
457
+ const workloadRoleBindings = workloadRoles?.bindings;
458
+ const asyncAdaptersConfigured = assertAsyncAdapterDeploymentReady();
459
+ const secretLinks = createStandardSliceSecretLinks({
460
+ protectedAccess: workloadRoles?.secretAccess,
461
+ });
462
+ const performanceHmacLogicalName =
463
+ '${pascalName}PerformanceLoadHmacKeys';
464
+ const secretLinksFor = (
465
+ resource: string,
466
+ excludeLogicalNames: readonly string[] = [],
467
+ ) =>
468
+ secretLinks.linksForWorkload('${name}.' + resource, {
469
+ excludeLogicalNames,
470
+ });
471
+ const secretLinksForWorkloads = (...resources: readonly string[]) => [
472
+ ...new Set(resources.flatMap((resource) => secretLinksFor(resource))),
473
+ ];
474
+ // Workload generators compose multi-role secret bindings through this seam.
475
+ void secretLinksForWorkloads;
476
+ const workloadRoleResourceNames = workloadRoles
477
+ ? standardSliceResourceNames(workloadRoles.intentContext)
478
+ : undefined;
479
+ if (workloadRoleResourceNames) {
480
+ $transform(aws.dynamodb.Table, (args: any, _opts: any, logicalName: string) => {
481
+ if (logicalName === '${pascalName}WebRevalidationTable') {
482
+ args.name = workloadRoleResourceNames.webRevalidationTable;
483
+ }
484
+ });
485
+ $transform(aws.sqs.Queue, (args: any, _opts: any, logicalName: string) => {
486
+ if (logicalName === '${pascalName}WebRevalidationEventsQueue') {
487
+ args.name = workloadRoleResourceNames.webRevalidationQueue;
488
+ }
489
+ });
490
+ }
491
+ const devPortInput = $dev ? (process.env.EBK_DEV_PORT ?? '3000') : '3000';
492
+ if (!/^\\d+$/.test(devPortInput)) {
493
+ throw new Error('EBK_DEV_PORT must be an integer from 1024 through 65535.');
494
+ }
495
+ const devPort = Number(devPortInput);
496
+ if (!Number.isSafeInteger(devPort) || devPort < 1024 || devPort > 65535) {
497
+ throw new Error('EBK_DEV_PORT must be an integer from 1024 through 65535.');
498
+ }
499
+ const protectedStage =
500
+ $app.stage === 'staging' || $app.stage === 'production';
501
+ const sourceCommit =
502
+ process.env.EBK_SOURCE_COMMIT ?? (protectedStage ? '' : 'unresolved');
503
+ const releaseSourceCommit =
504
+ process.env.EBK_RELEASE_SOURCE_COMMIT ?? sourceCommit;
505
+ const exactDevelopmentRetirement =
506
+ $app.stage === 'development' &&
507
+ Object.values(databaseBindings).some((binding) =>
508
+ binding.provisioned.includes('retirement_rehearsal'),
509
+ );
510
+ if (
511
+ protectedStage &&
512
+ (!/^(?:[a-f0-9]{40}|[a-f0-9]{64})$/.test(sourceCommit) ||
513
+ !/^(?:[a-f0-9]{40}|[a-f0-9]{64})$/.test(releaseSourceCommit))
514
+ ) {
515
+ throw new Error(
516
+ 'Protected infrastructure requires exact deployment and release source Git object IDs.',
517
+ );
518
+ }
519
+ const dataRetentionContainer = resolveStandardContainerImage({
520
+ artifactId: '${name}.job.data-retention',
521
+ local: {
522
+ context: '../..',
523
+ dockerfile: 'packages/${name}/jobs/data-retention/Dockerfile',
524
+ },
525
+ sourceCommit: releaseSourceCommit,
526
+ stage: $app.stage,
527
+ });
528
+ const databaseMigrationContainer = resolveStandardContainerImage({
529
+ artifactId: '${name}.job.database-migration',
530
+ local: {
531
+ context: '../..',
532
+ dockerfile: 'packages/${name}/migration/Dockerfile',
533
+ },
534
+ sourceCommit: releaseSourceCommit,
535
+ stage: $app.stage,
536
+ });
537
+ const databaseRetirementContainer = resolveStandardContainerImage({
538
+ artifactId: '${name}.job.database-retirement',
539
+ exactDevelopment: exactDevelopmentRetirement,
540
+ local: {
541
+ context: '../..',
542
+ dockerfile: 'packages/${name}/migration/Dockerfile',
543
+ },
544
+ sourceCommit: releaseSourceCommit,
545
+ stage: $app.stage,
546
+ });
547
+ const performanceDeploymentAuthority = protectedStage
548
+ ? protectedPerformanceDeploymentAuthority({
549
+ enabled: process.env.EBK_PERFORMANCE_LOAD_ENABLED ?? 'false',
550
+ releaseSourceCommit,
551
+ stage: $app.stage,
552
+ })
553
+ : undefined;
554
+ const foundation = getFoundationInfrastructure();
555
+ const performanceCostControls = create${pascalName}PerformanceCostControls({
556
+ accountCostPolicyDigest: foundation.discovery.accountCostPolicyDigest,
557
+ accountMonthlyMaximumUsd:
558
+ foundation.discovery.accountMonthlyMaximumUsd,
559
+ alertTopicArn: foundation.discovery.alertTopicArn,
560
+ workspace: '${workspace}',
561
+ });
562
+ const databaseProvisions = provisionDatabaseBindings(foundation, {
563
+ bindings: databaseBindings,
564
+ slice: '${name}',
565
+ });
566
+ const databaseProvision = databaseProvisions.bindings.primary;
567
+ const { db: database } = databaseProvision;
568
+ const authClient = foundation.userPool.addClient('${pascalName}WebAuthClient', {
569
+ transform: {
570
+ client: {
571
+ explicitAuthFlows: [
572
+ 'ALLOW_USER_PASSWORD_AUTH',
573
+ 'ALLOW_REFRESH_TOKEN_AUTH',
574
+ ],
575
+ preventUserExistenceErrors: 'ENABLED',
576
+ },
577
+ },
578
+ });
579
+ const machineClientProvisionerLogs = managedLogGroup(
580
+ '${pascalName}MachineClientProvisionerLogs',
581
+ 'machine-client-provisioner',
582
+ foundation.discovery.alertTopicArn,
583
+ );
584
+ const machineClientProvisioner = new sst.aws.Function(
585
+ '${pascalName}MachineClientProvisioner',
586
+ {
587
+ ...(workloadRoleBindings?.authorization.provisioner ?? {}),
588
+ ...(workloadRoles
589
+ ? {}
590
+ : {
591
+ permissions:
592
+ standardSliceMachineClientProvisionerPermissions({
593
+ userPoolArn:
594
+ foundation.discovery.identityUserPoolArn,
595
+ }),
596
+ }),
597
+ environment: {
598
+ EBK_SLICE: '${name}',
599
+ EBK_STAGE: $app.stage,
600
+ EBK_WORKSPACE: '${workspace}',
601
+ },
602
+ handler: 'authorization/machine-client-provisioner.handler',
603
+ link: secretLinksFor('machine-client-provisioner'),
604
+ runtime: 'python3.12',
605
+ timeout: '2 minutes',
606
+ ...lambdaLogging(machineClientProvisionerLogs.logGroup.name),
607
+ },
608
+ );
609
+ const machineAuthorization = createStandardSliceMachineAuthorization({
610
+ credentialGeneration:
611
+ (process.env.EBK_MACHINE_CREDENTIAL_GENERATION ??
612
+ 'blue-1') as MachineCredentialGeneration,
613
+ provisionerFunctionArn: machineClientProvisioner.arn,
614
+ retirePreviousGeneration:
615
+ process.env.EBK_MACHINE_RETIRE_PREVIOUS_GENERATION === 'true',
616
+ tokenEndpoint: foundation.discovery.identityTokenEndpoint,
617
+ userPoolId: foundation.userPool.id,
618
+ });
619
+ const dataExportStore = createStandardDataExportStore({
620
+ bucketName: workloadRoleResourceNames?.dataExportBucket,
621
+ keyAliasName: workloadRoleResourceNames?.dataExportKeyAlias,
622
+ workloadRoleArn: workloadRoleBindings?.api.dataRights.role,
623
+ });
624
+
625
+ const apiHealthLogs = managedLogGroup(
626
+ '${pascalName}ApiHealthLogs',
627
+ 'api-health',
628
+ foundation.discovery.alertTopicArn,
629
+ );
630
+ const protectedApiLogs = managedLogGroup(
631
+ '${pascalName}ProtectedApiLogs',
632
+ 'api-protected',
633
+ foundation.discovery.alertTopicArn,
634
+ );
635
+ const machineAuthorizationProofLogs = managedLogGroup(
636
+ '${pascalName}MachineAuthorizationProofLogs',
637
+ 'machine-authorization-proof',
638
+ foundation.discovery.alertTopicArn,
639
+ );
640
+ const machineAuthorizationProofTargetLogs = managedLogGroup(
641
+ '${pascalName}MachineAuthorizationProofTargetLogs',
642
+ 'machine-authorization-proof-target',
643
+ foundation.discovery.alertTopicArn,
644
+ );
645
+ const delegationLogs = managedLogGroup(
646
+ '${pascalName}DelegationLogs',
647
+ 'authorization-delegations',
648
+ foundation.discovery.alertTopicArn,
649
+ );
650
+ const dataRightsLogs = managedLogGroup(
651
+ '${pascalName}DataRightsLogs',
652
+ 'data-rights',
653
+ foundation.discovery.alertTopicArn,
654
+ );
655
+ const dataRetentionReviewLogs = managedLogGroup(
656
+ '${pascalName}DataRetentionReviewLogs',
657
+ 'data-retention-review',
658
+ foundation.discovery.alertTopicArn,
659
+ );
660
+ const dataExportCleanupLogs = managedLogGroup(
661
+ '${pascalName}DataExportCleanupLogs',
662
+ 'job-data-export-cleanup',
663
+ foundation.discovery.alertTopicArn,
664
+ );
665
+ const databaseHealthLogs = managedLogGroup(
666
+ '${pascalName}DatabaseHealthLogs',
667
+ 'database-health',
668
+ foundation.discovery.alertTopicArn,
669
+ );
670
+ const databaseRecordsLogs = managedLogGroup(
671
+ '${pascalName}DatabaseRecordsLogs',
672
+ 'database-records',
673
+ foundation.discovery.alertTopicArn,
674
+ );
675
+ const eventPublisherLogs = managedLogGroup(
676
+ '${pascalName}EventPublisherLogs',
677
+ 'event-publisher',
678
+ foundation.discovery.alertTopicArn,
679
+ );
680
+ const eventConsumerLogs = managedLogGroup(
681
+ '${pascalName}EventConsumerLogs',
682
+ 'event-consumer',
683
+ foundation.discovery.alertTopicArn,
684
+ );
685
+ const asyncOutboxRelayLogs = managedLogGroup(
686
+ '${pascalName}AsyncOutboxRelayLogs',
687
+ 'async-outbox-relay',
688
+ foundation.discovery.alertTopicArn,
689
+ );
690
+ const asyncInboxCleanupLogs = managedLogGroup(
691
+ '${pascalName}AsyncInboxCleanupLogs',
692
+ 'async-inbox-cleanup',
693
+ foundation.discovery.alertTopicArn,
694
+ );
695
+ const asyncProviderWorkerLogs = managedLogGroup(
696
+ '${pascalName}AsyncProviderWorkerLogs',
697
+ 'async-provider-worker',
698
+ foundation.discovery.alertTopicArn,
699
+ );
700
+ const asyncScheduleTargetLogs = managedLogGroup(
701
+ '${pascalName}AsyncScheduleTargetLogs',
702
+ 'async-schedule-target',
703
+ foundation.discovery.alertTopicArn,
704
+ );
705
+ const asyncWebhookIngressLogs = managedLogGroup(
706
+ '${pascalName}AsyncWebhookIngressLogs',
707
+ 'async-webhook-ingress',
708
+ foundation.discovery.alertTopicArn,
709
+ );
710
+ const asyncWorkflowTaskLogs = managedLogGroup(
711
+ '${pascalName}AsyncWorkflowTaskLogs',
712
+ 'async-workflow-task',
713
+ foundation.discovery.alertTopicArn,
714
+ );
715
+ const nextServerLogs = managedLogGroup(
716
+ '${pascalName}NextServerLogs',
717
+ 'next-server',
718
+ foundation.discovery.alertTopicArn,
719
+ );
720
+ const nextImageOptimizerLogs = managedLogGroup(
721
+ '${pascalName}NextImageOptimizerLogs',
722
+ 'next-image-optimizer',
723
+ foundation.discovery.alertTopicArn,
724
+ );
725
+ const nextRevalidationSeederLogs = managedLogGroup(
726
+ '${pascalName}NextRevalidationSeederLogs',
727
+ 'next-revalidation-seeder',
728
+ foundation.discovery.alertTopicArn,
729
+ );
730
+ const nextRevalidationSubscriberLogs = managedLogGroup(
731
+ '${pascalName}NextRevalidationSubscriberLogs',
732
+ 'next-revalidation-subscriber',
733
+ foundation.discovery.alertTopicArn,
734
+ );
735
+
736
+ const api = new sst.aws.ApiGatewayV2('${pascalName}Api', {
737
+ ...(performanceDeploymentAuthority ? { vpc: foundation.vpc } : {}),
738
+ });
739
+ const performanceIngress = performanceDeploymentAuthority
740
+ ? (() => {
741
+ const originProof = new random.RandomPassword(
742
+ '${pascalName}PerformanceRouterOriginProof',
743
+ {
744
+ keepers: { contract: 'ebk-performance-router-origin-proof-v1' },
745
+ length: 43,
746
+ lower: true,
747
+ numeric: true,
748
+ special: false,
749
+ upper: true,
750
+ },
751
+ );
752
+ const router = new sst.aws.Router('${pascalName}PerformanceRouter', {
753
+ edge: {
754
+ viewerRequest: {
755
+ injection: originProof.result.apply(
756
+ (proof: string) =>
757
+ createRouterOriginProofInjection('${name}', proof),
758
+ ),
759
+ },
760
+ },
761
+ });
762
+ return { api, originProof: originProof.result, router };
763
+ })()
764
+ : undefined;
765
+ const performanceDeploymentBinding =
766
+ performanceDeploymentAuthority && performanceIngress
767
+ ? {
768
+ ...performanceDeploymentAuthority,
769
+ target: {
770
+ ...performanceDeploymentAuthority.target,
771
+ origin: performanceIngress.router.url,
772
+ },
773
+ }
774
+ : undefined;
775
+ api.route(${identifierPrefix}Health.method + ' ' + ${identifierPrefix}Health.path, {
776
+ ...(workloadRoleBindings?.api.health ?? {}),
777
+ environment: {
778
+ EBK_SOURCE_COMMIT: releaseSourceCommit,
779
+ EBK_STAGE: $app.stage,
780
+ },
781
+ handler: 'api/health.handler',
782
+ link: secretLinksFor('api-health'),
783
+ ...lambdaLogging(apiHealthLogs.logGroup.name),
784
+ });
785
+ api.route(
786
+ ${identifierPrefix}MachineAuthorizationProof.method +
787
+ ' ' +
788
+ ${identifierPrefix}MachineAuthorizationProof.path,
789
+ {
790
+ ...(workloadRoleBindings?.api.machineAuthorizationProof ?? {}),
791
+ environment: {
792
+ AUTH_CLIENT_ID: authClient.id,
793
+ AUTH_MACHINE_ACCEPTED_CLIENT_IDS:
794
+ machineAuthorization.acceptedClientIdsJson,
795
+ AUTH_MACHINE_CLIENT_ID: machineAuthorization.clientId,
796
+ AUTH_USER_POOL_ID: foundation.userPool.id,
797
+ EBK_STAGE: $app.stage,
798
+ },
799
+ handler: 'api/machine-authorization.handler',
800
+ link: [
801
+ database,
802
+ ...secretLinksFor('api-machine-authorization-proof'),
803
+ ],
804
+ ...lambdaLogging(machineAuthorizationProofTargetLogs.logGroup.name),
805
+ vpc: foundation.vpc,
806
+ },
807
+ );
808
+ for (const route of [
809
+ {
810
+ handler: 'api/delegations.issueHandler',
811
+ operation: ${identifierPrefix}IssueRecordDelegation,
812
+ },
813
+ {
814
+ handler: 'api/delegations.useHandler',
815
+ operation: ${identifierPrefix}UseRecordDelegation,
816
+ },
817
+ {
818
+ handler: 'api/delegations.revokeHandler',
819
+ operation: ${identifierPrefix}RevokeRecordDelegation,
820
+ },
821
+ ]) {
822
+ api.route(route.operation.method + ' ' + route.operation.path, {
823
+ ...(workloadRoleBindings?.api.delegations ?? {}),
824
+ environment: {
825
+ AUTH_CLIENT_ID: authClient.id,
826
+ AUTH_MACHINE_ACCEPTED_CLIENT_IDS:
827
+ machineAuthorization.acceptedClientIdsJson,
828
+ AUTH_MACHINE_CLIENT_ID: machineAuthorization.clientId,
829
+ AUTH_USER_POOL_ID: foundation.userPool.id,
830
+ EBK_STAGE: $app.stage,
831
+ },
832
+ handler: route.handler,
833
+ link: [database, ...secretLinksFor('api-delegations')],
834
+ ...lambdaLogging(delegationLogs.logGroup.name),
835
+ vpc: foundation.vpc,
836
+ });
837
+ }
838
+ for (const route of [
839
+ {
840
+ handler: 'api/data-rights.requestExportHandler',
841
+ operation: ${identifierPrefix}RequestRecordExport,
842
+ },
843
+ {
844
+ handler: 'api/data-rights.decideExportHandler',
845
+ operation: ${identifierPrefix}DecideRecordExport,
846
+ },
847
+ {
848
+ handler: 'api/data-rights.executeExportHandler',
849
+ operation: ${identifierPrefix}ExecuteRecordExport,
850
+ },
851
+ {
852
+ handler: 'api/data-rights.deliverExportHandler',
853
+ operation: ${identifierPrefix}DeliverRecordExport,
854
+ },
855
+ {
856
+ handler: 'api/data-rights.cleanupExportHandler',
857
+ operation: ${identifierPrefix}CleanupRecordExport,
858
+ },
859
+ {
860
+ handler: 'api/data-rights.issueHoldHandler',
861
+ operation: ${identifierPrefix}IssueRecordHold,
862
+ },
863
+ {
864
+ handler: 'api/data-rights.reviewHoldHandler',
865
+ operation: ${identifierPrefix}ReviewRecordHold,
866
+ },
867
+ {
868
+ handler: 'api/data-rights.releaseHoldHandler',
869
+ operation: ${identifierPrefix}ReleaseRecordHold,
870
+ },
871
+ {
872
+ handler: 'api/data-rights.expireHoldHandler',
873
+ operation: ${identifierPrefix}ExpireRecordHold,
874
+ },
875
+ ]) {
876
+ api.route(route.operation.method + ' ' + route.operation.path, {
877
+ ...(workloadRoleBindings?.api.dataRights ?? {}),
878
+ ...(workloadRoles
879
+ ? {}
880
+ : { permissions: dataExportStore.dataRightsPermissions }),
881
+ environment: {
882
+ AUTH_CLIENT_ID: authClient.id,
883
+ AUTH_MACHINE_ACCEPTED_CLIENT_IDS:
884
+ machineAuthorization.acceptedClientIdsJson,
885
+ AUTH_MACHINE_CLIENT_ID: machineAuthorization.clientId,
886
+ AUTH_USER_POOL_ID: foundation.userPool.id,
887
+ DATA_EXPORT_BUCKET_NAME: dataExportStore.bucket.bucket,
888
+ DATA_EXPORT_KMS_KEY_ARN: dataExportStore.encryptionKey.arn,
889
+ EBK_STAGE: $app.stage,
890
+ EVENT_BUS_NAME: foundation.discovery.eventBusName,
891
+ },
892
+ handler: route.handler,
893
+ link: [
894
+ database,
895
+ foundation.eventBus,
896
+ ...secretLinksFor('api-data-rights'),
897
+ ],
898
+ ...lambdaLogging(dataRightsLogs.logGroup.name),
899
+ vpc: foundation.vpc,
900
+ });
901
+ }
902
+ for (const route of [
903
+ {
904
+ handler: 'api/data-retention.requestRetentionApprovalHandler',
905
+ operation: ${identifierPrefix}RequestRetentionApproval,
906
+ },
907
+ {
908
+ handler: 'api/data-retention.decideRetentionApprovalHandler',
909
+ operation: ${identifierPrefix}DecideRetentionApproval,
910
+ },
911
+ ]) {
912
+ api.route(route.operation.method + ' ' + route.operation.path, {
913
+ ...(workloadRoleBindings?.api.databaseRecords ?? {}),
914
+ environment: {
915
+ AUTH_CLIENT_ID: authClient.id,
916
+ AUTH_MACHINE_ACCEPTED_CLIENT_IDS:
917
+ machineAuthorization.acceptedClientIdsJson,
918
+ AUTH_MACHINE_CLIENT_ID: machineAuthorization.clientId,
919
+ AUTH_USER_POOL_ID: foundation.userPool.id,
920
+ EBK_STAGE: $app.stage,
921
+ },
922
+ handler: route.handler,
923
+ link: [database, ...secretLinksFor('api-database-records')],
924
+ ...lambdaLogging(dataRetentionReviewLogs.logGroup.name),
925
+ vpc: foundation.vpc,
926
+ });
927
+ }
928
+ api.route(
929
+ ${identifierPrefix}ProtectedExample.method +
930
+ ' ' +
931
+ ${identifierPrefix}ProtectedExample.path,
932
+ {
933
+ ...(workloadRoleBindings?.api.protected ?? {}),
934
+ environment: {
935
+ AUTH_CLIENT_ID: authClient.id,
936
+ AUTH_MACHINE_ACCEPTED_CLIENT_IDS:
937
+ machineAuthorization.acceptedClientIdsJson,
938
+ AUTH_MACHINE_CLIENT_ID: machineAuthorization.clientId,
939
+ AUTH_USER_POOL_ID: foundation.userPool.id,
940
+ EBK_STAGE: $app.stage,
941
+ },
942
+ handler: 'api/protected.handler',
943
+ link: [database, ...secretLinksFor('api-protected')],
944
+ ...lambdaLogging(protectedApiLogs.logGroup.name),
945
+ vpc: foundation.vpc,
946
+ },
947
+ );
948
+ api.route(
949
+ ${identifierPrefix}DatabaseHealth.method +
950
+ ' ' +
951
+ ${identifierPrefix}DatabaseHealth.path,
952
+ {
953
+ ...(workloadRoleBindings?.api.databaseHealth ?? {}),
954
+ environment: {
955
+ AUTH_CLIENT_ID: authClient.id,
956
+ AUTH_MACHINE_ACCEPTED_CLIENT_IDS:
957
+ machineAuthorization.acceptedClientIdsJson,
958
+ AUTH_MACHINE_CLIENT_ID: machineAuthorization.clientId,
959
+ AUTH_USER_POOL_ID: foundation.userPool.id,
960
+ EBK_STAGE: $app.stage,
961
+ },
962
+ handler: 'database/runtime-health.handler',
963
+ link: [database, ...secretLinksFor('api-database-health')],
964
+ ...lambdaLogging(databaseHealthLogs.logGroup.name),
965
+ vpc: foundation.vpc,
966
+ },
967
+ );
968
+ for (const route of [
969
+ {
970
+ handler: 'database/deletions.deleteHandler',
971
+ operation: ${identifierPrefix}DeleteRecord,
972
+ },
973
+ {
974
+ handler: 'database/records.getHandler',
975
+ operation: ${identifierPrefix}GetRecord,
976
+ },
977
+ {
978
+ handler: 'database/records.putHandler',
979
+ operation: ${identifierPrefix}PutRecord,
980
+ },
981
+ ]) {
982
+ api.route(route.operation.method + ' ' + route.operation.path, {
983
+ ...(workloadRoleBindings?.api.databaseRecords ?? {}),
984
+ environment: {
985
+ AUTH_CLIENT_ID: authClient.id,
986
+ AUTH_MACHINE_ACCEPTED_CLIENT_IDS:
987
+ machineAuthorization.acceptedClientIdsJson,
988
+ AUTH_MACHINE_CLIENT_ID: machineAuthorization.clientId,
989
+ AUTH_USER_POOL_ID: foundation.userPool.id,
990
+ EBK_STAGE: $app.stage,
991
+ EVENT_BUS_NAME: foundation.discovery.eventBusName,
992
+ },
993
+ handler: route.handler,
994
+ link: [
995
+ database,
996
+ foundation.eventBus,
997
+ ...secretLinksFor('api-database-records'),
998
+ ],
999
+ ...lambdaLogging(databaseRecordsLogs.logGroup.name),
1000
+ vpc: foundation.vpc,
1001
+ });
1002
+ }
1003
+ for (const route of [
1004
+ {
1005
+ handler: 'database/deletions.requestDeletionHandler',
1006
+ operation: ${identifierPrefix}RequestRecordDeletion,
1007
+ },
1008
+ {
1009
+ handler: 'database/deletions.decideDeletionHandler',
1010
+ operation: ${identifierPrefix}DecideRecordDeletion,
1011
+ },
1012
+ ]) {
1013
+ api.route(route.operation.method + ' ' + route.operation.path, {
1014
+ ...(workloadRoleBindings?.api.databaseRecords ?? {}),
1015
+ environment: {
1016
+ AUTH_CLIENT_ID: authClient.id,
1017
+ AUTH_MACHINE_ACCEPTED_CLIENT_IDS:
1018
+ machineAuthorization.acceptedClientIdsJson,
1019
+ AUTH_MACHINE_CLIENT_ID: machineAuthorization.clientId,
1020
+ AUTH_USER_POOL_ID: foundation.userPool.id,
1021
+ EBK_STAGE: $app.stage,
1022
+ },
1023
+ handler: route.handler,
1024
+ link: [database, ...secretLinksFor('api-database-records')],
1025
+ ...lambdaLogging(databaseRecordsLogs.logGroup.name),
1026
+ vpc: foundation.vpc,
1027
+ });
1028
+ }
1029
+ api.route(
1030
+ ${identifierPrefix}PublishCreated.method +
1031
+ ' ' +
1032
+ ${identifierPrefix}PublishCreated.path,
1033
+ {
1034
+ ...(workloadRoleBindings?.api.publishCreated ?? {}),
1035
+ environment: {
1036
+ AUTH_CLIENT_ID: authClient.id,
1037
+ AUTH_MACHINE_ACCEPTED_CLIENT_IDS:
1038
+ machineAuthorization.acceptedClientIdsJson,
1039
+ AUTH_MACHINE_CLIENT_ID: machineAuthorization.clientId,
1040
+ AUTH_USER_POOL_ID: foundation.userPool.id,
1041
+ EBK_STAGE: $app.stage,
1042
+ EVENT_BUS_NAME: foundation.discovery.eventBusName,
1043
+ },
1044
+ handler: 'api/publish-created.handler',
1045
+ link: [
1046
+ database,
1047
+ foundation.eventBus,
1048
+ ...secretLinksFor('api-publish-created'),
1049
+ ],
1050
+ ...lambdaLogging(eventPublisherLogs.logGroup.name),
1051
+ vpc: foundation.vpc,
1052
+ },
1053
+ );
1054
+ const machineAuthorizationProof = new sst.aws.Function(
1055
+ '${pascalName}MachineAuthorizationProof',
1056
+ {
1057
+ ...(workloadRoleBindings?.authorization.proof ?? {}),
1058
+ ...(workloadRoles
1059
+ ? {}
1060
+ : { permissions: machineAuthorization.proof.permissions }),
1061
+ environment: {
1062
+ ...machineAuthorization.proof.environment,
1063
+ AUTH_MACHINE_PROBE_URL:
1064
+ $interpolate\`\${api.url}\${${identifierPrefix}MachineAuthorizationProof.path}\`,
1065
+ EBK_STAGE: $app.stage,
1066
+ },
1067
+ handler: 'authorization/machine-authorization-proof.handler',
1068
+ link: secretLinksFor('machine-authorization-proof'),
1069
+ ...lambdaLogging(machineAuthorizationProofLogs.logGroup.name),
1070
+ },
1071
+ );
1072
+ const dataExportCleanup = new sst.aws.Function(
1073
+ '${pascalName}DataExportCleanup',
1074
+ {
1075
+ ...(workloadRoleBindings?.dataExport.cleanup ?? {}),
1076
+ ...(workloadRoles
1077
+ ? {}
1078
+ : { permissions: dataExportStore.cleanupPermissions }),
1079
+ environment: {
1080
+ DATA_EXPORT_BUCKET_NAME: dataExportStore.bucket.bucket,
1081
+ DATA_EXPORT_KMS_KEY_ARN: dataExportStore.encryptionKey.arn,
1082
+ EBK_STAGE: $app.stage,
1083
+ EBK_WORKLOAD_IDENTITY: '${name}:data-export-cleanup',
1084
+ },
1085
+ handler: 'jobs/data-export-cleanup.handler',
1086
+ link: [
1087
+ database,
1088
+ ...secretLinksFor('data-export-cleanup'),
1089
+ ],
1090
+ ...lambdaLogging(dataExportCleanupLogs.logGroup.name),
1091
+ timeout: '5 minutes',
1092
+ vpc: foundation.vpc,
1093
+ },
1094
+ );
1095
+ const dataExportCleanupRule = new aws.cloudwatch.EventRule(
1096
+ '${pascalName}DataExportCleanupSchedule',
1097
+ {
1098
+ ...(workloadRoleResourceNames
1099
+ ? { name: workloadRoleResourceNames.dataExportCleanupRule }
1100
+ : {}),
1101
+ description:
1102
+ 'Hourly authoritative cleanup of expired confidential export versions.',
1103
+ scheduleExpression: 'rate(1 hour)',
1104
+ state: 'ENABLED',
1105
+ tags: {
1106
+ 'ebk:management': 'standard-data-export',
1107
+ 'ebk:slice': '${name}',
1108
+ 'sst:stage': $app.stage,
1109
+ },
1110
+ },
1111
+ );
1112
+ const dataExportCleanupPermission = new aws.lambda.Permission(
1113
+ '${pascalName}DataExportCleanupPermission',
1114
+ {
1115
+ action: 'lambda:InvokeFunction',
1116
+ function: dataExportCleanup.arn,
1117
+ principal: 'events.amazonaws.com',
1118
+ sourceArn: dataExportCleanupRule.arn,
1119
+ },
1120
+ );
1121
+ const dataExportCleanupTarget = new aws.cloudwatch.EventTarget(
1122
+ '${pascalName}DataExportCleanupTarget',
1123
+ {
1124
+ arn: dataExportCleanup.arn,
1125
+ retryPolicy: {
1126
+ maximumEventAgeInSeconds: 3_600,
1127
+ maximumRetryAttempts: 185,
1128
+ },
1129
+ rule: dataExportCleanupRule.name,
1130
+ targetId: 'DataExportCleanup',
1131
+ },
1132
+ { dependsOn: [dataExportCleanupPermission] },
1133
+ );
1134
+ const dataExportCleanupInvocation =
1135
+ new aws.lambda.FunctionEventInvokeConfig(
1136
+ '${pascalName}DataExportCleanupInvocation',
1137
+ {
1138
+ functionName: dataExportCleanup.arn,
1139
+ maximumEventAgeInSeconds: 3_600,
1140
+ maximumRetryAttempts: 2,
1141
+ },
1142
+ );
1143
+ // <ebk:function-registrations:start>
1144
+ // <ebk:function-registrations:end>
1145
+
1146
+ const eventDlq = new sst.aws.Queue('${pascalName}EventDlq', {
1147
+ transform: {
1148
+ queue: {
1149
+ ...(workloadRoleResourceNames
1150
+ ? { name: workloadRoleResourceNames.eventDlq }
1151
+ : {}),
1152
+ messageRetentionSeconds: 14 * 24 * 60 * 60,
1153
+ },
1154
+ },
1155
+ });
1156
+ const eventQueue = new sst.aws.Queue('${pascalName}EventQueue', {
1157
+ dlq: { queue: eventDlq.arn, retry: 5 },
1158
+ transform: {
1159
+ queue: {
1160
+ ...(workloadRoleResourceNames
1161
+ ? { name: workloadRoleResourceNames.eventQueue }
1162
+ : {}),
1163
+ messageRetentionSeconds: 4 * 24 * 60 * 60,
1164
+ visibilityTimeoutSeconds: 120,
1165
+ },
1166
+ },
1167
+ });
1168
+ const asyncRecoveryLedger = new aws.dynamodb.Table(
1169
+ '${pascalName}AsyncRecoveryLedger',
1170
+ {
1171
+ attributes: [{ name: 'report_digest', type: 'S' }],
1172
+ billingMode: 'PAY_PER_REQUEST',
1173
+ hashKey: 'report_digest',
1174
+ ...(workloadRoleResourceNames
1175
+ ? { name: workloadRoleResourceNames.asyncRecoveryLedger }
1176
+ : {}),
1177
+ pointInTimeRecovery: { enabled: true },
1178
+ tags: {
1179
+ 'ebk:async-recovery-ledger': 'true',
1180
+ 'ebk:slice': '${name}',
1181
+ 'ebk:stage': $app.stage,
1182
+ },
1183
+ ttl: {
1184
+ attributeName: 'expires_at_epoch_seconds',
1185
+ enabled: true,
1186
+ },
1187
+ },
1188
+ );
1189
+ eventQueue.subscribe({
1190
+ environment: {
1191
+ EBK_STAGE: $app.stage,
1192
+ EBK_WORKLOAD_IDENTITY: '${name}:event-consumer',
1193
+ },
1194
+ handler: 'api/event-consumer.handler',
1195
+ link: [database, ...secretLinksFor('event-consumer')],
1196
+ ...lambdaLogging(eventConsumerLogs.logGroup.name),
1197
+ timeout: '1 minute',
1198
+ vpc: foundation.vpc,
1199
+ }, {
1200
+ batch: { partialResponses: true },
1201
+ transform: {
1202
+ function: workloadRoleBindings?.queue.eventConsumer,
1203
+ },
1204
+ });
1205
+ foundation.eventBus.subscribeQueue(
1206
+ '${pascalName}CreatedEventRule',
1207
+ eventQueue,
1208
+ {
1209
+ pattern: {
1210
+ detail: { meta: { stage: [$app.stage] } },
1211
+ detailType: ['${pascalName}Created'],
1212
+ source: ['${name}'],
1213
+ },
1214
+ },
1215
+ );
1216
+ foundation.eventBus.subscribeQueue(
1217
+ '${pascalName}DataLifecycleChangedRule',
1218
+ eventQueue,
1219
+ {
1220
+ pattern: {
1221
+ detail: { meta: { stage: [$app.stage] } },
1222
+ detailType: ['DataLifecycleChanged'],
1223
+ source: [...lifecycleConsumers.acceptedSources],
1224
+ },
1225
+ },
1226
+ );
1227
+
1228
+ const eventConsumerAlarmDefinition = defineEventConsumerAlarms({
1229
+ consumer: '${pascalName}Created',
1230
+ dlqName: 'event-dlq',
1231
+ queueName: 'event-queue',
1232
+ slice: '${name}',
1233
+ stage: $app.stage,
1234
+ }).alarms.find(({ severity }) => severity === 'critical');
1235
+ if (!eventConsumerAlarmDefinition) {
1236
+ throw new Error('Generated event-consumer DLQ alarm definition is missing.');
1237
+ }
1238
+ const dlqAlarm = new aws.cloudwatch.MetricAlarm('${pascalName}EventDlqDepth', {
1239
+ alarmActions: [foundation.discovery.alertTopicArn],
1240
+ alarmDescription: operationalAlarmDescription({
1241
+ businessSymptom: 'Domain-event processing has terminal failures.',
1242
+ id: 'event-dlq-depth',
1243
+ runbook: 'docs/runbooks/async-recovery.md',
1244
+ }),
1245
+ name: '${name}-' + $app.stage + '-event-dlq-depth',
1246
+ comparisonOperator: eventConsumerAlarmDefinition.comparisonOperator,
1247
+ dimensions: { QueueName: eventDlq.name },
1248
+ evaluationPeriods: eventConsumerAlarmDefinition.evaluationPeriods,
1249
+ metricName: eventConsumerAlarmDefinition.metricName,
1250
+ namespace: eventConsumerAlarmDefinition.namespace,
1251
+ period: eventConsumerAlarmDefinition.periodSeconds,
1252
+ statistic: 'Maximum',
1253
+ threshold: eventConsumerAlarmDefinition.threshold,
1254
+ treatMissingData: 'notBreaching',
1255
+ });
1256
+ const asyncTargetDlq = new sst.aws.Queue(
1257
+ '${pascalName}AsyncTargetDlq',
1258
+ {
1259
+ transform: {
1260
+ queue: {
1261
+ ...(workloadRoleResourceNames
1262
+ ? { name: workloadRoleResourceNames.asyncTargetDlq }
1263
+ : {}),
1264
+ messageRetentionSeconds: 14 * 24 * 60 * 60,
1265
+ tags: {
1266
+ 'ebk:async-recovery-source': 'async-target-dlq',
1267
+ 'ebk:slice': '${name}',
1268
+ 'ebk:stage': $app.stage,
1269
+ },
1270
+ },
1271
+ },
1272
+ },
1273
+ );
1274
+ const providerDlq = new sst.aws.Queue('${pascalName}ProviderDlq', {
1275
+ transform: {
1276
+ queue: {
1277
+ ...(workloadRoleResourceNames
1278
+ ? { name: workloadRoleResourceNames.providerDlq }
1279
+ : {}),
1280
+ messageRetentionSeconds: 14 * 24 * 60 * 60,
1281
+ tags: {
1282
+ 'ebk:async-recovery-source': 'provider-dlq',
1283
+ 'ebk:slice': '${name}',
1284
+ 'ebk:stage': $app.stage,
1285
+ },
1286
+ },
1287
+ },
1288
+ });
1289
+ const providerQueue = new sst.aws.Queue('${pascalName}ProviderQueue', {
1290
+ dlq: { queue: providerDlq.arn, retry: 5 },
1291
+ transform: {
1292
+ queue: {
1293
+ ...(workloadRoleResourceNames
1294
+ ? { name: workloadRoleResourceNames.providerQueue }
1295
+ : {}),
1296
+ messageRetentionSeconds: 4 * 24 * 60 * 60,
1297
+ tags: {
1298
+ 'ebk:async-recovery-destination': 'provider-queue',
1299
+ 'ebk:slice': '${name}',
1300
+ 'ebk:stage': $app.stage,
1301
+ },
1302
+ visibilityTimeoutSeconds: 120,
1303
+ },
1304
+ },
1305
+ });
1306
+ const reliability = create${pascalName}Reliability({
1307
+ alertTopicArn: foundation.discovery.alertTopicArn,
1308
+ alertTopicName:
1309
+ foundation.discovery.alertTopicArn.split(':').at(-1) ?? '',
1310
+ apiId: api.id,
1311
+ databaseClusterIdentifier: foundation.discovery.databaseClusterId,
1312
+ functionName: dataExportCleanup.name,
1313
+ providerQueueName: providerQueue.name,
1314
+ });
1315
+ const providerDlqAlarm = new aws.cloudwatch.MetricAlarm(
1316
+ '${pascalName}ProviderDlqDepth',
1317
+ {
1318
+ alarmActions: [foundation.discovery.alertTopicArn],
1319
+ alarmDescription: operationalAlarmDescription({
1320
+ businessSymptom: 'Provider deliveries have terminal failures.',
1321
+ id: 'provider-dlq-depth',
1322
+ runbook: 'docs/runbooks/async-recovery.md',
1323
+ }),
1324
+ comparisonOperator: 'GreaterThanOrEqualToThreshold',
1325
+ dimensions: { QueueName: providerDlq.name },
1326
+ evaluationPeriods: 1,
1327
+ metricName: 'ApproximateNumberOfMessagesVisible',
1328
+ name: '${name}-' + $app.stage + '-provider-dlq-depth',
1329
+ namespace: 'AWS/SQS',
1330
+ period: 300,
1331
+ statistic: 'Maximum',
1332
+ threshold: 1,
1333
+ treatMissingData: 'notBreaching',
1334
+ },
1335
+ );
1336
+ const asyncTargetDlqAlarm = new aws.cloudwatch.MetricAlarm(
1337
+ '${pascalName}AsyncTargetDlqDepth',
1338
+ {
1339
+ alarmActions: [foundation.discovery.alertTopicArn],
1340
+ alarmDescription: operationalAlarmDescription({
1341
+ businessSymptom: 'An asynchronous target has terminal failures.',
1342
+ id: 'async-target-dlq-depth',
1343
+ runbook: 'docs/runbooks/async-recovery.md',
1344
+ }),
1345
+ comparisonOperator: 'GreaterThanOrEqualToThreshold',
1346
+ dimensions: { QueueName: asyncTargetDlq.name },
1347
+ evaluationPeriods: 1,
1348
+ metricName: 'ApproximateNumberOfMessagesVisible',
1349
+ name: '${name}-' + $app.stage + '-async-target-dlq-depth',
1350
+ namespace: 'AWS/SQS',
1351
+ period: 300,
1352
+ statistic: 'Maximum',
1353
+ threshold: 1,
1354
+ treatMissingData: 'notBreaching',
1355
+ },
1356
+ );
1357
+ const runtimeConfiguration = protectedStage && !$dev
1358
+ ? (() => {
1359
+ const application = new aws.appconfig.Application(
1360
+ '${pascalName}ConfigurationApplication',
1361
+ {
1362
+ description: '${name} Slice-owned runtime configuration.',
1363
+ name: 'ebk-${name}-' + $app.stage,
1364
+ tags: {
1365
+ 'ebk:slice': '${name}',
1366
+ 'ebk:stage': $app.stage,
1367
+ },
1368
+ },
1369
+ );
1370
+ const environment = new aws.appconfig.Environment(
1371
+ '${pascalName}ConfigurationEnvironment',
1372
+ {
1373
+ applicationId: application.id,
1374
+ description:
1375
+ '${name} ' + $app.stage + ' runtime configuration.',
1376
+ monitors: [
1377
+ reliability.apiFastBurn,
1378
+ asyncTargetDlqAlarm,
1379
+ dlqAlarm,
1380
+ providerDlqAlarm,
1381
+ reliability.providerDeadline,
1382
+ ].map((alarm) => ({
1383
+ alarmArn: alarm.arn,
1384
+ alarmRoleArn:
1385
+ workloadRoleBindings!.configuration.monitor.roleArn,
1386
+ })),
1387
+ name: $app.stage,
1388
+ tags: {
1389
+ 'ebk:slice': '${name}',
1390
+ 'ebk:stage': $app.stage,
1391
+ },
1392
+ },
1393
+ );
1394
+ const profile = new aws.appconfig.ConfigurationProfile(
1395
+ '${pascalName}ConfigurationProfile',
1396
+ {
1397
+ applicationId: application.id,
1398
+ locationUri: 'hosted',
1399
+ name: 'runtime',
1400
+ tags: {
1401
+ 'ebk:slice': '${name}',
1402
+ 'ebk:stage': $app.stage,
1403
+ },
1404
+ type: 'AWS.Freeform',
1405
+ validators: [{
1406
+ content: JSON.stringify({
1407
+ additionalProperties: false,
1408
+ properties: {
1409
+ schemaVersion: { const: 1 },
1410
+ values: {
1411
+ additionalProperties: false,
1412
+ properties: Object.fromEntries(
1413
+ ConfigurationDeclarations.map((declaration) => [
1414
+ declaration.key,
1415
+ declaration.validation.type === 'number'
1416
+ ? {
1417
+ ...(declaration.validation.integer
1418
+ ? { type: 'integer' }
1419
+ : { type: 'number' }),
1420
+ ...(declaration.validation.maximum === undefined
1421
+ ? {}
1422
+ : {
1423
+ maximum:
1424
+ declaration.validation.maximum,
1425
+ }),
1426
+ ...(declaration.validation.minimum === undefined
1427
+ ? {}
1428
+ : {
1429
+ minimum:
1430
+ declaration.validation.minimum,
1431
+ }),
1432
+ }
1433
+ : declaration.validation,
1434
+ ]),
1435
+ ),
1436
+ required: ConfigurationDeclarations.map(
1437
+ ({ key }) => key,
1438
+ ),
1439
+ type: 'object',
1440
+ },
1441
+ },
1442
+ required: ['schemaVersion', 'values'],
1443
+ type: 'object',
1444
+ }),
1445
+ type: 'JSON_SCHEMA',
1446
+ }],
1447
+ },
1448
+ );
1449
+ const version = new aws.appconfig.HostedConfigurationVersion(
1450
+ '${pascalName}ConfigurationVersion',
1451
+ {
1452
+ applicationId: application.id,
1453
+ configurationProfileId: profile.configurationProfileId,
1454
+ content: JSON.stringify(localConfiguration),
1455
+ contentType: 'application/json',
1456
+ description:
1457
+ '${name} immutable generated default configuration.',
1458
+ versionLabel: releaseSourceCommit,
1459
+ },
1460
+ );
1461
+ const deploymentStrategy =
1462
+ new aws.appconfig.DeploymentStrategy(
1463
+ '${pascalName}ConfigurationDeploymentStrategy',
1464
+ {
1465
+ deploymentDurationInMinutes: 10,
1466
+ description:
1467
+ '${name} bounded linear runtime configuration rollout.',
1468
+ finalBakeTimeInMinutes: 10,
1469
+ growthFactor: 10,
1470
+ growthType: 'LINEAR',
1471
+ name:
1472
+ 'ebk-${name}-bounded-linear-' + $app.stage,
1473
+ replicateTo: 'NONE',
1474
+ tags: {
1475
+ 'ebk:slice': '${name}',
1476
+ 'ebk:stage': $app.stage,
1477
+ },
1478
+ },
1479
+ );
1480
+ const deployment = new aws.appconfig.Deployment(
1481
+ '${pascalName}ConfigurationDeployment',
1482
+ {
1483
+ applicationId: application.id,
1484
+ configurationProfileId: profile.configurationProfileId,
1485
+ configurationVersion: version.versionNumber.apply(String),
1486
+ deploymentStrategyId: deploymentStrategy.id,
1487
+ description:
1488
+ '${name} generated default configuration deployment.',
1489
+ environmentId: environment.environmentId,
1490
+ },
1491
+ );
1492
+ return {
1493
+ application,
1494
+ deployment,
1495
+ deploymentStrategy,
1496
+ environment,
1497
+ profile,
1498
+ version,
1499
+ };
1500
+ })()
1501
+ : undefined;
1502
+ const configurationApplication = runtimeConfiguration?.application;
1503
+ const configurationDeployment = runtimeConfiguration?.deployment;
1504
+ const configurationDeploymentStrategy =
1505
+ runtimeConfiguration?.deploymentStrategy;
1506
+ const configurationEnvironment = runtimeConfiguration?.environment;
1507
+ const configurationProfile = runtimeConfiguration?.profile;
1508
+ const configurationVersion = runtimeConfiguration?.version;
1509
+ const runtimeConfigurationEnvironment = runtimeConfiguration
1510
+ ? {
1511
+ EBK_APPCONFIG_APPLICATION_ID:
1512
+ runtimeConfiguration.application.id,
1513
+ EBK_APPCONFIG_ENVIRONMENT_ID:
1514
+ runtimeConfiguration.environment.environmentId,
1515
+ EBK_APPCONFIG_PROFILE_ID:
1516
+ runtimeConfiguration.profile.configurationProfileId,
1517
+ }
1518
+ : {};
1519
+ // <ebk:workload-registrations:start>
1520
+ // <ebk:workload-registrations:end>
1521
+
1522
+ if (asyncAdaptersConfigured) {
1523
+ providerQueue.subscribe(
1524
+ {
1525
+ environment: {
1526
+ EBK_STAGE: $app.stage,
1527
+ EBK_WORKLOAD_IDENTITY: '${name}:async-provider-worker',
1528
+ EBK_WORKSPACE: '${workspace}',
1529
+ ...runtimeConfigurationEnvironment,
1530
+ },
1531
+ handler: 'async/provider-worker.handler',
1532
+ link: [
1533
+ database,
1534
+ providerQueue,
1535
+ ...secretLinksFor('async-provider-worker'),
1536
+ ],
1537
+ ...lambdaLogging(asyncProviderWorkerLogs.logGroup.name),
1538
+ timeout: '1 minute',
1539
+ vpc: foundation.vpc,
1540
+ },
1541
+ {
1542
+ batch: { partialResponses: true },
1543
+ transform: {
1544
+ function: workloadRoleBindings?.async.providerWorker,
1545
+ },
1546
+ },
1547
+ configurationDeployment
1548
+ ? { dependsOn: [configurationDeployment] }
1549
+ : {},
1550
+ );
1551
+ api.route('POST /${name}/webhooks/provider', {
1552
+ ...(workloadRoleBindings?.async.webhookIngress ?? {}),
1553
+ environment: {
1554
+ EBK_ASYNC_LEDGER_TABLE: asyncRecoveryLedger.name,
1555
+ EBK_STAGE: $app.stage,
1556
+ EBK_WORKLOAD_IDENTITY: '${name}:async-webhook-ingress',
1557
+ EBK_WORKSPACE: '${workspace}',
1558
+ },
1559
+ handler: 'async/webhook-ingress.handler',
1560
+ link: [
1561
+ providerQueue,
1562
+ ...secretLinksFor('async-webhook-ingress'),
1563
+ ],
1564
+ ...lambdaLogging(asyncWebhookIngressLogs.logGroup.name),
1565
+ timeout: '10 seconds',
1566
+ });
1567
+ }
1568
+ const asyncOutboxRelay = new sst.aws.Function(
1569
+ '${pascalName}AsyncOutboxRelay',
1570
+ {
1571
+ ...(workloadRoleBindings?.async.outboxRelay ?? {}),
1572
+ environment: {
1573
+ EBK_STAGE: $app.stage,
1574
+ EBK_WORKLOAD_IDENTITY: '${name}:async-outbox-relay',
1575
+ EBK_WORKSPACE: '${workspace}',
1576
+ EVENT_BUS_NAME: foundation.discovery.eventBusName,
1577
+ },
1578
+ handler: 'async/outbox-relay.handler',
1579
+ link: [
1580
+ database,
1581
+ foundation.eventBus,
1582
+ ...secretLinksFor('async-outbox-relay'),
1583
+ ],
1584
+ ...lambdaLogging(asyncOutboxRelayLogs.logGroup.name),
1585
+ timeout: '1 minute',
1586
+ vpc: foundation.vpc,
1587
+ },
1588
+ );
1589
+ const asyncInboxCleanup = new sst.aws.Function(
1590
+ '${pascalName}AsyncInboxCleanup',
1591
+ {
1592
+ ...(workloadRoleBindings?.async.inboxCleanup ?? {}),
1593
+ environment: {
1594
+ EBK_STAGE: $app.stage,
1595
+ EBK_WORKLOAD_IDENTITY: '${name}:async-inbox-cleanup',
1596
+ EBK_WORKSPACE: '${workspace}',
1597
+ },
1598
+ handler: 'async/inbox-cleanup.handler',
1599
+ link: [
1600
+ database,
1601
+ ...secretLinksFor('async-inbox-cleanup'),
1602
+ ],
1603
+ ...lambdaLogging(asyncInboxCleanupLogs.logGroup.name),
1604
+ timeout: '5 minutes',
1605
+ vpc: foundation.vpc,
1606
+ },
1607
+ );
1608
+ const asyncScheduleTarget = new sst.aws.Function(
1609
+ '${pascalName}AsyncScheduleTarget',
1610
+ {
1611
+ ...(workloadRoleBindings?.async.scheduleTarget ?? {}),
1612
+ environment: {
1613
+ EBK_STAGE: $app.stage,
1614
+ EBK_WORKLOAD_IDENTITY: '${name}:async-schedule-target',
1615
+ EBK_WORKSPACE: '${workspace}',
1616
+ },
1617
+ handler: 'async/schedule-target.handler',
1618
+ link: [providerQueue, ...secretLinksFor('async-schedule-target')],
1619
+ ...lambdaLogging(asyncScheduleTargetLogs.logGroup.name),
1620
+ timeout: '30 seconds',
1621
+ },
1622
+ );
1623
+ const asyncWorkflowTask = new sst.aws.Function(
1624
+ '${pascalName}AsyncWorkflowTask',
1625
+ {
1626
+ ...(workloadRoleBindings?.async.workflowTask ?? {}),
1627
+ environment: {
1628
+ EBK_ASYNC_LEDGER_TABLE: asyncRecoveryLedger.name,
1629
+ EBK_STAGE: $app.stage,
1630
+ EBK_WORKLOAD_IDENTITY: '${name}:async-workflow-task',
1631
+ EBK_WORKSPACE: '${workspace}',
1632
+ },
1633
+ handler: 'async/workflow-task.handler',
1634
+ link: secretLinksFor('async-workflow-task'),
1635
+ ...lambdaLogging(
1636
+ asyncWorkflowTaskLogs.logGroup.name,
1637
+ workloadRoleResourceNames?.workflowTaskFunction,
1638
+ ),
1639
+ timeout: '15 minutes',
1640
+ },
1641
+ );
1642
+ const providerReconciliationRule = new aws.cloudwatch.EventRule(
1643
+ '${pascalName}ProviderReconciliationSchedule',
1644
+ {
1645
+ description:
1646
+ 'Versioned provider reconciliation schedule activated only with configured adapters.',
1647
+ scheduleExpression: 'rate(15 minutes)',
1648
+ state: asyncAdaptersConfigured ? 'ENABLED' : 'DISABLED',
1649
+ tags: {
1650
+ 'ebk:slice': '${name}',
1651
+ 'ebk:stage': $app.stage,
1652
+ },
1653
+ },
1654
+ );
1655
+ const outboxRelayRule = new aws.cloudwatch.EventRule(
1656
+ '${pascalName}OutboxRelaySchedule',
1657
+ {
1658
+ description: 'Durable outbox relay for committed domain events.',
1659
+ scheduleExpression: 'rate(1 minute)',
1660
+ state: 'ENABLED',
1661
+ tags: {
1662
+ 'ebk:slice': '${name}',
1663
+ 'ebk:stage': $app.stage,
1664
+ },
1665
+ },
1666
+ );
1667
+ const inboxCleanupRule = new aws.cloudwatch.EventRule(
1668
+ '${pascalName}AsyncInboxCleanupSchedule',
1669
+ {
1670
+ description:
1671
+ 'Bounded cleanup of expired async inbox idempotency records.',
1672
+ scheduleExpression: 'rate(1 hour)',
1673
+ state: 'ENABLED',
1674
+ tags: {
1675
+ 'ebk:slice': '${name}',
1676
+ 'ebk:stage': $app.stage,
1677
+ },
1678
+ },
1679
+ );
1680
+ const providerReconciliationPermission = new aws.lambda.Permission(
1681
+ '${pascalName}ProviderReconciliationPermission',
1682
+ {
1683
+ action: 'lambda:InvokeFunction',
1684
+ function: asyncScheduleTarget.arn,
1685
+ principal: 'events.amazonaws.com',
1686
+ sourceArn: providerReconciliationRule.arn,
1687
+ },
1688
+ );
1689
+ const outboxRelayPermission = new aws.lambda.Permission(
1690
+ '${pascalName}OutboxRelayPermission',
1691
+ {
1692
+ action: 'lambda:InvokeFunction',
1693
+ function: asyncOutboxRelay.arn,
1694
+ principal: 'events.amazonaws.com',
1695
+ sourceArn: outboxRelayRule.arn,
1696
+ },
1697
+ );
1698
+ const inboxCleanupPermission = new aws.lambda.Permission(
1699
+ '${pascalName}AsyncInboxCleanupPermission',
1700
+ {
1701
+ action: 'lambda:InvokeFunction',
1702
+ function: asyncInboxCleanup.arn,
1703
+ principal: 'events.amazonaws.com',
1704
+ sourceArn: inboxCleanupRule.arn,
1705
+ },
1706
+ );
1707
+ const asyncTargetDlqPolicy = new aws.sqs.QueuePolicy(
1708
+ '${pascalName}AsyncTargetDlqPolicy',
1709
+ {
1710
+ policy: aws.iam.getPolicyDocumentOutput({
1711
+ statements: [{
1712
+ actions: ['sqs:SendMessage'],
1713
+ conditions: [{
1714
+ test: 'ArnEquals',
1715
+ values: [
1716
+ inboxCleanupRule.arn,
1717
+ providerReconciliationRule.arn,
1718
+ outboxRelayRule.arn,
1719
+ ],
1720
+ variable: 'aws:SourceArn',
1721
+ }],
1722
+ principals: [{
1723
+ identifiers: ['events.amazonaws.com'],
1724
+ type: 'Service',
1725
+ }],
1726
+ resources: [asyncTargetDlq.arn],
1727
+ }],
1728
+ }).json,
1729
+ queueUrl: asyncTargetDlq.url,
1730
+ },
1731
+ );
1732
+ const providerReconciliationTarget = new aws.cloudwatch.EventTarget(
1733
+ '${pascalName}ProviderReconciliationTarget',
1734
+ {
1735
+ arn: asyncScheduleTarget.arn,
1736
+ deadLetterConfig: { arn: asyncTargetDlq.arn },
1737
+ retryPolicy: {
1738
+ maximumEventAgeInSeconds: 900,
1739
+ maximumRetryAttempts: 2,
1740
+ },
1741
+ rule: providerReconciliationRule.name,
1742
+ targetId: 'ProviderReconciliation',
1743
+ },
1744
+ {
1745
+ dependsOn: [
1746
+ asyncTargetDlqPolicy,
1747
+ providerReconciliationPermission,
1748
+ ],
1749
+ },
1750
+ );
1751
+ const outboxRelayTarget = new aws.cloudwatch.EventTarget(
1752
+ '${pascalName}OutboxRelayTarget',
1753
+ {
1754
+ arn: asyncOutboxRelay.arn,
1755
+ deadLetterConfig: { arn: asyncTargetDlq.arn },
1756
+ retryPolicy: {
1757
+ maximumEventAgeInSeconds: 300,
1758
+ maximumRetryAttempts: 2,
1759
+ },
1760
+ rule: outboxRelayRule.name,
1761
+ targetId: 'OutboxRelay',
1762
+ },
1763
+ { dependsOn: [asyncTargetDlqPolicy, outboxRelayPermission] },
1764
+ );
1765
+ const inboxCleanupTarget = new aws.cloudwatch.EventTarget(
1766
+ '${pascalName}AsyncInboxCleanupTarget',
1767
+ {
1768
+ arn: asyncInboxCleanup.arn,
1769
+ deadLetterConfig: { arn: asyncTargetDlq.arn },
1770
+ retryPolicy: {
1771
+ maximumEventAgeInSeconds: 3600,
1772
+ maximumRetryAttempts: 2,
1773
+ },
1774
+ rule: inboxCleanupRule.name,
1775
+ targetId: 'AsyncInboxCleanup',
1776
+ },
1777
+ { dependsOn: [asyncTargetDlqPolicy, inboxCleanupPermission] },
1778
+ );
1779
+ let recordExportWorkflow: any;
1780
+ if (!$dev && asyncAdaptersConfigured) {
1781
+ recordExportWorkflow = new aws.sfn.StateMachine(
1782
+ '${pascalName}RecordExportWorkflow',
1783
+ {
1784
+ definition: $jsonStringify({
1785
+ Comment: 'Versioned record export with cooperative cancellation and compensation.',
1786
+ StartAt: 'RequireVersionOne',
1787
+ TimeoutSeconds: 86_400,
1788
+ States: {
1789
+ RequireVersionOne: {
1790
+ Choices: [{
1791
+ Next: 'CreateExport',
1792
+ NumericEquals: 1,
1793
+ Variable: '$.workflowVersion',
1794
+ }],
1795
+ Default: 'UnsupportedVersion',
1796
+ Type: 'Choice',
1797
+ },
1798
+ CreateExport: {
1799
+ Catch: [
1800
+ {
1801
+ ErrorEquals: ['WorkflowEffectReconciliationRequired'],
1802
+ Next: 'ReconciliationRequired',
1803
+ ResultPath: '$.failure',
1804
+ },
1805
+ {
1806
+ ErrorEquals: ['WorkflowEffectNotStartedError'],
1807
+ Next: 'RetryExhaustedBeforeEffect',
1808
+ ResultPath: '$.failure',
1809
+ },
1810
+ {
1811
+ ErrorEquals: ['States.ALL'],
1812
+ Next: 'CompensateAfterFailure',
1813
+ ResultPath: '$.failure',
1814
+ },
1815
+ ],
1816
+ Next: 'PostCreateCancellationBoundary',
1817
+ Parameters: {
1818
+ action: 'execute',
1819
+ 'executionId.$': '$$.Execution.Id',
1820
+ 'input.$': '$',
1821
+ stepId: 'create-export',
1822
+ workflowVersion: 1,
1823
+ },
1824
+ Resource: asyncWorkflowTask.arn,
1825
+ Retry: [{
1826
+ BackoffRate: 2,
1827
+ ErrorEquals: ['WorkflowEffectNotStartedError'],
1828
+ IntervalSeconds: 2,
1829
+ MaxAttempts: 2,
1830
+ }],
1831
+ ResultPath: '$.createExport',
1832
+ TimeoutSeconds: 900,
1833
+ Type: 'Task',
1834
+ },
1835
+ PostCreateCancellationBoundary: {
1836
+ Choices: [{
1837
+ And: [
1838
+ {
1839
+ BooleanEquals: true,
1840
+ Variable: '$.createExport.cancellationRequested',
1841
+ },
1842
+ {
1843
+ BooleanEquals: true,
1844
+ Variable: '$.createExport.sideEffectCommitted',
1845
+ },
1846
+ ],
1847
+ Next: 'CompensateAfterCancellation',
1848
+ }, {
1849
+ BooleanEquals: true,
1850
+ Next: 'Cancelled',
1851
+ Variable: '$.createExport.cancellationRequested',
1852
+ }],
1853
+ Default: 'Completed',
1854
+ Type: 'Choice',
1855
+ },
1856
+ CompensateAfterCancellation: {
1857
+ Parameters: {
1858
+ action: 'compensate',
1859
+ 'executionId.$': '$$.Execution.Id',
1860
+ 'input.$': '$',
1861
+ stepId: 'remove-export',
1862
+ workflowVersion: 1,
1863
+ },
1864
+ Resource: asyncWorkflowTask.arn,
1865
+ Retry: [{
1866
+ BackoffRate: 2,
1867
+ ErrorEquals: ['States.TaskFailed'],
1868
+ IntervalSeconds: 2,
1869
+ MaxAttempts: 2,
1870
+ }],
1871
+ TimeoutSeconds: 300,
1872
+ Type: 'Task',
1873
+ Next: 'Cancelled',
1874
+ },
1875
+ CompensateAfterFailure: {
1876
+ Parameters: {
1877
+ action: 'compensate',
1878
+ 'executionId.$': '$$.Execution.Id',
1879
+ 'input.$': '$',
1880
+ stepId: 'remove-export',
1881
+ workflowVersion: 1,
1882
+ },
1883
+ Resource: asyncWorkflowTask.arn,
1884
+ Retry: [{
1885
+ BackoffRate: 2,
1886
+ ErrorEquals: ['States.TaskFailed'],
1887
+ IntervalSeconds: 2,
1888
+ MaxAttempts: 2,
1889
+ }],
1890
+ TimeoutSeconds: 300,
1891
+ Type: 'Task',
1892
+ Next: 'FailedAfterCompensation',
1893
+ },
1894
+ Cancelled: { Type: 'Succeed' },
1895
+ Completed: { Type: 'Succeed' },
1896
+ FailedAfterCompensation: {
1897
+ Cause: 'The record export failed and compensation completed.',
1898
+ Error: 'RecordExportFailed',
1899
+ Type: 'Fail',
1900
+ },
1901
+ ReconciliationRequired: {
1902
+ Cause:
1903
+ 'The workflow effect outcome is ambiguous and requires operator reconciliation.',
1904
+ Error: 'WorkflowEffectReconciliationRequired',
1905
+ Type: 'Fail',
1906
+ },
1907
+ RetryExhaustedBeforeEffect: {
1908
+ Cause:
1909
+ 'The workflow adapter proved no effect began, but its retry budget was exhausted.',
1910
+ Error: 'WorkflowEffectNotStartedError',
1911
+ Type: 'Fail',
1912
+ },
1913
+ UnsupportedVersion: {
1914
+ Cause: 'No exact deployed workflow definition exists.',
1915
+ Error: 'WorkflowVersionUnsupported',
1916
+ Type: 'Fail',
1917
+ },
1918
+ },
1919
+ }),
1920
+ name:
1921
+ 'ebk-${recordExportWorkflowName}-' +
1922
+ $app.stage +
1923
+ '-record-export-v1',
1924
+ roleArn:
1925
+ workloadRoleBindings!.async.workflowOrchestration.roleArn,
1926
+ tags: {
1927
+ 'ebk:slice': '${name}',
1928
+ 'ebk:stage': $app.stage,
1929
+ 'ebk:workflow-version': '1',
1930
+ },
1931
+ type: 'STANDARD',
1932
+ },
1933
+ );
1934
+ }
1935
+
1936
+ const web = new sst.aws.Nextjs('${pascalName}Web', {
1937
+ dev: {
1938
+ command: \`pnpm exec next dev web --hostname 127.0.0.1 --port \${devPort}\`,
1939
+ directory: '.',
1940
+ title: '${pascalName} web',
1941
+ url: \`http://127.0.0.1:\${devPort}/${name}\`,
1942
+ },
1943
+ environment: {
1944
+ AUTH_CLIENT_ID: authClient.id,
1945
+ AUTH_USER_POOL_ID: foundation.userPool.id,
1946
+ EBK_SOURCE_COMMIT: releaseSourceCommit,
1947
+ EBK_STAGE: $app.stage,
1948
+ EBK_FLEET_ORIGIN: process.env.EBK_FLEET_ORIGIN ?? '',
1949
+ EBK_PERFORMANCE_ACCOUNT_FINGERPRINT:
1950
+ performanceDeploymentBinding?.target.accountFingerprint ?? '',
1951
+ EBK_PERFORMANCE_CANDIDATE_DIGEST:
1952
+ performanceDeploymentBinding?.candidateDigest ?? '',
1953
+ EBK_PERFORMANCE_LOAD_ENABLED:
1954
+ performanceDeploymentBinding ? 'true' : 'false',
1955
+ EBK_PERFORMANCE_ORIGIN:
1956
+ performanceDeploymentBinding?.target.origin ?? '',
1957
+ EBK_PERFORMANCE_REGION:
1958
+ performanceDeploymentBinding?.target.region ?? '',
1959
+ EBK_PERFORMANCE_RELEASE_ID:
1960
+ performanceDeploymentBinding?.releaseId ?? '',
1961
+ EBK_PERFORMANCE_RELEASE_MATERIAL_DIGEST:
1962
+ performanceDeploymentBinding?.releaseMaterialDigest ?? '',
1963
+ EBK_PERFORMANCE_SLICE:
1964
+ performanceDeploymentBinding?.target.slice ?? '',
1965
+ EBK_SECURE_COOKIES: process.env.EBK_SECURE_COOKIES ?? '',
1966
+ EVENT_BUS_NAME: foundation.discovery.eventBusName,
1967
+ ...runtimeConfigurationEnvironment,
1968
+ NEXT_PUBLIC_API_URL: api.url,
1969
+ },
1970
+ link: [database, foundation.userPool],
1971
+ path: 'web',
1972
+ router: { instance: foundation.router, path: '/${name}' },
1973
+ transform: {
1974
+ assets: workloadRoleResourceNames
1975
+ ? {
1976
+ transform: {
1977
+ bucket: {
1978
+ bucket: workloadRoleResourceNames.webAssetsBucket,
1979
+ },
1980
+ },
1981
+ }
1982
+ : undefined,
1983
+ imageOptimizer: {
1984
+ ...(workloadRoleBindings?.next.imageOptimizer ?? {}),
1985
+ link: secretLinksFor('web-image-optimizer'),
1986
+ ...lambdaLogging(nextImageOptimizerLogs.logGroup.name),
1987
+ },
1988
+ revalidationEventsSubscriber: {
1989
+ ...(workloadRoleBindings?.next.revalidationSubscriber ?? {}),
1990
+ link: secretLinksFor('web-revalidation-subscriber'),
1991
+ ...lambdaLogging(nextRevalidationSubscriberLogs.logGroup.name),
1992
+ },
1993
+ revalidationSeeder: {
1994
+ ...(workloadRoleBindings?.next.revalidationSeeder ?? {}),
1995
+ link: secretLinksFor('web-revalidation-seeder'),
1996
+ ...lambdaLogging(nextRevalidationSeederLogs.logGroup.name),
1997
+ },
1998
+ server: {
1999
+ ...(workloadRoleBindings?.next.server ?? {}),
2000
+ link: secretLinksFor(
2001
+ 'web-server',
2002
+ performanceDeploymentBinding
2003
+ ? []
2004
+ : [performanceHmacLogicalName],
2005
+ ),
2006
+ ...lambdaLogging(nextServerLogs.logGroup.name),
2007
+ },
2008
+ },
2009
+ vpc: foundation.vpc,
2010
+ }, configurationDeployment
2011
+ ? { dependsOn: [configurationDeployment] }
2012
+ : {});
2013
+ if (performanceIngress) {
2014
+ for (const path of [
2015
+ '/${name}/__ebk/performance/api-lambda',
2016
+ '/${name}/__ebk/performance/async-worker',
2017
+ '/${name}/__ebk/performance/async-worker/cleanup',
2018
+ '/${name}/__ebk/performance/database',
2019
+ '/${name}/__ebk/performance/database/cleanup',
2020
+ ]) {
2021
+ performanceIngress.router.route(path, web.url, {
2022
+ connectionAttempts: 1,
2023
+ connectionTimeout: '3 seconds',
2024
+ readTimeout: '30 seconds',
2025
+ });
2026
+ }
2027
+ }
2028
+
2029
+ const migrationControls: Record<string, any> = {};
2030
+ if (!$dev) {
2031
+ const region = aws.getRegionOutput({}).name;
2032
+ const taskArguments = (task: any) => ({
2033
+ Cluster: task.cluster,
2034
+ LaunchType: 'FARGATE',
2035
+ NetworkConfiguration: {
2036
+ AwsvpcConfiguration: {
2037
+ AssignPublicIp: task.assignPublicIp.apply(
2038
+ (value: boolean) => value ? 'ENABLED' : 'DISABLED',
2039
+ ),
2040
+ SecurityGroups: task.securityGroups,
2041
+ Subnets: task.subnets,
2042
+ },
2043
+ },
2044
+ TaskDefinition: task.taskDefinition,
2045
+ });
2046
+ const lifecycleEnvironment = (task: any, callback: boolean) =>
2047
+ task.containers.apply((containers: string[]) => ({
2048
+ ContainerOverrides: containers.map((containerName) => ({
2049
+ Environment: [
2050
+ {
2051
+ Name: 'EBK_DATABASE_LIFECYCLE_CORRELATION_ID',
2052
+ Value: '{% $states.input.databaseLifecycleCorrelationId %}',
2053
+ },
2054
+ ...(callback
2055
+ ? [
2056
+ {
2057
+ Name: 'EBK_CANDIDATE_IDENTITY',
2058
+ Value: '{% $states.input.candidateIdentity %}',
2059
+ },
2060
+ {
2061
+ Name: 'EBK_EXECUTION_DATABASE_IDENTIFIER',
2062
+ Value: '{% $states.input.databaseIdentifier %}',
2063
+ },
2064
+ {
2065
+ Name: 'EBK_EXECUTION_SLICE',
2066
+ Value: '{% $states.input.slice %}',
2067
+ },
2068
+ {
2069
+ Name: 'EBK_EXECUTION_STAGE',
2070
+ Value: '{% $states.input.stage %}',
2071
+ },
2072
+ {
2073
+ Name: 'EBK_REGISTRATION_DIGEST',
2074
+ Value: '{% $states.input.registrationDigest %}',
2075
+ },
2076
+ {
2077
+ Name: 'EBK_SOURCE_COMMIT',
2078
+ Value: '{% $states.input.sourceCommit %}',
2079
+ },
2080
+ {
2081
+ Name: 'EBK_STEP_FUNCTIONS_TASK_TOKEN',
2082
+ Value: '{% $states.context.Task.Token %}',
2083
+ },
2084
+ ]
2085
+ : []),
2086
+ ],
2087
+ Name: containerName,
2088
+ })),
2089
+ }));
2090
+ const createRawStateMachine = (
2091
+ logicalName: string,
2092
+ definition: unknown,
2093
+ logGroup: any,
2094
+ roleArn: string,
2095
+ ) =>
2096
+ new aws.sfn.StateMachine(logicalName, {
2097
+ definition: $jsonStringify(definition),
2098
+ loggingConfiguration: {
2099
+ includeExecutionData: false,
2100
+ level: 'ERROR',
2101
+ logDestination: $interpolate\`\${logGroup.arn}:*\`,
2102
+ },
2103
+ roleArn,
2104
+ type: 'STANDARD',
2105
+ });
2106
+
2107
+ if (!databaseProvision.readwriteSecretArn) {
2108
+ throw new Error(
2109
+ 'The active primary database is missing its read/write credential reference.',
2110
+ );
2111
+ }
2112
+ const retentionTaskLogs = managedLogContract('job-data-retention');
2113
+ const retentionTask = new sst.aws.Task('${pascalName}DataRetentionTask', {
2114
+ ...workloadRoleBindings!.dataRetention.task,
2115
+ cluster: foundation.cluster,
2116
+ environment: {
2117
+ ...dataRetentionContainer.environment,
2118
+ AWS_REGION: region,
2119
+ EBK_DATABASE_HOST: foundation.database.clusterEndpoint,
2120
+ EBK_DATABASE_NAME: databaseProvision.databaseName,
2121
+ EBK_DATABASE_PORT: $interpolate\`\${foundation.database.port}\`,
2122
+ EBK_DATABASE_USERNAME: databaseProvision.readwriteRole,
2123
+ EBK_RDS_CA_BUNDLE: '/app/rds-global-bundle.pem',
2124
+ EBK_READWRITE_SECRET_ARN: databaseProvision.readwriteSecretArn,
2125
+ EBK_SLICE: '${name}',
2126
+ EBK_STAGE: $app.stage,
2127
+ EBK_WORKLOAD_IDENTITY: '${name}:data-retention-job',
2128
+ EBK_WORKSPACE: '${workspace}',
2129
+ EVENT_BUS_NAME: foundation.eventBus.name,
2130
+ },
2131
+ image: dataRetentionContainer.image,
2132
+ link: secretLinksFor('data-retention-job-task'),
2133
+ logging: {
2134
+ name: retentionTaskLogs.name,
2135
+ retention: '1 month',
2136
+ },
2137
+ transform: {
2138
+ logGroup: {
2139
+ logGroupClass: retentionTaskLogs.logGroupClass,
2140
+ tags: retentionTaskLogs.tags,
2141
+ },
2142
+ taskDefinition: {
2143
+ family: workloadRoleResourceNames!.dataRetentionTaskFamily,
2144
+ },
2145
+ },
2146
+ });
2147
+ const retentionTaskLogDataProtection = managedLogDataProtection(
2148
+ '${pascalName}DataRetentionTaskLog',
2149
+ retentionTaskLogs,
2150
+ retentionTaskLogs.name,
2151
+ [retentionTask.nodes.taskDefinition],
2152
+ foundation.discovery.alertTopicArn,
2153
+ );
2154
+ const retentionWorkflowLogs = managedLogGroup(
2155
+ '${pascalName}DataRetentionWorkflowLogs',
2156
+ 'job-data-retention-orchestration',
2157
+ foundation.discovery.alertTopicArn,
2158
+ );
2159
+ const retentionOverrides = retentionTask.containers.apply(
2160
+ (containers: string[]) => ({
2161
+ ContainerOverrides: containers.map((containerName) => ({
2162
+ Environment: [
2163
+ {
2164
+ Name: 'EBK_RETENTION_APPROVAL_ID',
2165
+ Value: '{% $states.input.approvalRequestId %}',
2166
+ },
2167
+ {
2168
+ Name: 'EBK_RETENTION_CORRELATION_ID',
2169
+ Value: '{% $states.input.correlationId %}',
2170
+ },
2171
+ {
2172
+ Name: 'EBK_RETENTION_AS_OF',
2173
+ Value: '{% $states.input.asOf %}',
2174
+ },
2175
+ {
2176
+ Name: 'EBK_RETENTION_MODE',
2177
+ Value: '{% $states.input.mode %}',
2178
+ },
2179
+ {
2180
+ Name: 'EBK_RETENTION_RUN_ID',
2181
+ Value: '{% $states.input.runId %}',
2182
+ },
2183
+ {
2184
+ Name: 'EBK_STEP_FUNCTIONS_TASK_TOKEN',
2185
+ Value: '{% $states.context.Task.Token %}',
2186
+ },
2187
+ ],
2188
+ Name: containerName,
2189
+ })),
2190
+ }),
2191
+ );
2192
+ const retentionStateMachine = createRawStateMachine(
2193
+ '${pascalName}DataRetention',
2194
+ {
2195
+ StartAt: 'RunDataRetention',
2196
+ States: {
2197
+ DataRetentionSucceeded: {
2198
+ QueryLanguage: 'JSONata',
2199
+ Type: 'Succeed',
2200
+ },
2201
+ RunDataRetention: {
2202
+ Arguments: {
2203
+ ...taskArguments(retentionTask),
2204
+ Overrides: retentionOverrides,
2205
+ },
2206
+ Next: 'DataRetentionSucceeded',
2207
+ QueryLanguage: 'JSONata',
2208
+ Resource: 'arn:aws:states:::ecs:runTask.waitForTaskToken',
2209
+ Retry: [
2210
+ {
2211
+ BackoffRate: 2,
2212
+ ErrorEquals: [
2213
+ 'ECS.AmazonECSException',
2214
+ 'ECS.ECSException',
2215
+ ],
2216
+ IntervalSeconds: 10,
2217
+ MaxAttempts: 2,
2218
+ },
2219
+ ],
2220
+ TimeoutSeconds: 2700,
2221
+ Type: 'Task',
2222
+ },
2223
+ },
2224
+ },
2225
+ retentionWorkflowLogs.logGroup,
2226
+ workloadRoleBindings!.dataRetention.orchestration.roleArn,
2227
+ );
2228
+ const retentionFailureAlarm = new aws.cloudwatch.MetricAlarm(
2229
+ '${pascalName}DataRetentionFailure',
2230
+ {
2231
+ alarmActions: [foundation.discovery.alertTopicArn],
2232
+ alarmDescription: operationalAlarmDescription({
2233
+ businessSymptom:
2234
+ 'The built-in retention Job terminated unsuccessfully.',
2235
+ id: 'retention-job-failure',
2236
+ runbook: 'docs/runbooks/database-recovery.md',
2237
+ }),
2238
+ comparisonOperator: 'GreaterThanOrEqualToThreshold',
2239
+ evaluationPeriods: 1,
2240
+ metricQueries: [
2241
+ ...['ExecutionsFailed', 'ExecutionsTimedOut', 'ExecutionsAborted'].map(
2242
+ (metricName, index) => ({
2243
+ id: 'm' + index,
2244
+ metric: {
2245
+ dimensions: {
2246
+ StateMachineArn: retentionStateMachine.arn,
2247
+ },
2248
+ metricName,
2249
+ namespace: 'AWS/States',
2250
+ period: 60,
2251
+ stat: 'Sum',
2252
+ },
2253
+ returnData: false,
2254
+ }),
2255
+ ),
2256
+ {
2257
+ expression: 'm0+m1+m2',
2258
+ id: 'failures',
2259
+ label: 'Retention terminal execution failures',
2260
+ returnData: true,
2261
+ },
2262
+ ],
2263
+ name: '${name}-' + $app.stage + '-retention-job-failure',
2264
+ threshold: 1,
2265
+ treatMissingData: 'notBreaching',
2266
+ },
2267
+ );
2268
+ const retentionRegistration = registerProductRetention(
2269
+ retentionStateMachine.arn,
2270
+ );
2271
+ migrationControls.dataRetention = {
2272
+ registration: retentionRegistration,
2273
+ retentionFailureAlarm,
2274
+ retentionStateMachine,
2275
+ retentionTask,
2276
+ retentionTaskLogDataProtection,
2277
+ retentionWorkflowLogs,
2278
+ };
2279
+ const retentionMaintenanceTaskLogs = managedLogContract(
2280
+ 'job-data-retention-maintenance',
2281
+ );
2282
+ const retentionMaintenanceTask = new sst.aws.Task(
2283
+ '${pascalName}DataRetentionMaintenanceTask',
2284
+ {
2285
+ ...workloadRoleBindings!.dataRetention.maintenance.task,
2286
+ cluster: foundation.cluster,
2287
+ environment: {
2288
+ ...dataRetentionContainer.environment,
2289
+ AWS_REGION: region,
2290
+ EBK_DATABASE_HOST: foundation.database.clusterEndpoint,
2291
+ EBK_DATABASE_NAME: databaseProvision.databaseName,
2292
+ EBK_DATABASE_PORT: $interpolate\`\${foundation.database.port}\`,
2293
+ EBK_DATABASE_USERNAME: databaseProvision.readwriteRole,
2294
+ EBK_RDS_CA_BUNDLE: '/app/rds-global-bundle.pem',
2295
+ EBK_READWRITE_SECRET_ARN:
2296
+ databaseProvision.readwriteSecretArn,
2297
+ EBK_SLICE: '${name}',
2298
+ EBK_STAGE: $app.stage,
2299
+ EBK_WORKLOAD_IDENTITY: '${name}:data-retention-job',
2300
+ EBK_WORKSPACE: '${workspace}',
2301
+ },
2302
+ image: dataRetentionContainer.image,
2303
+ link: secretLinksFor(
2304
+ 'data-retention-maintenance-job-task',
2305
+ ),
2306
+ logging: {
2307
+ name: retentionMaintenanceTaskLogs.name,
2308
+ retention: '1 month',
2309
+ },
2310
+ transform: {
2311
+ logGroup: {
2312
+ logGroupClass:
2313
+ retentionMaintenanceTaskLogs.logGroupClass,
2314
+ tags: retentionMaintenanceTaskLogs.tags,
2315
+ },
2316
+ taskDefinition: {
2317
+ family:
2318
+ workloadRoleResourceNames!
2319
+ .dataRetentionMaintenanceTaskFamily,
2320
+ },
2321
+ },
2322
+ },
2323
+ );
2324
+ const retentionMaintenanceTaskLogDataProtection =
2325
+ managedLogDataProtection(
2326
+ '${pascalName}DataRetentionMaintenanceTaskLog',
2327
+ retentionMaintenanceTaskLogs,
2328
+ retentionMaintenanceTaskLogs.name,
2329
+ [retentionMaintenanceTask.nodes.taskDefinition],
2330
+ foundation.discovery.alertTopicArn,
2331
+ );
2332
+ const retentionMaintenanceLauncherLogs = managedLogGroup(
2333
+ '${pascalName}DataRetentionMaintenanceLauncherLogs',
2334
+ 'job-data-retention-maintenance-launcher',
2335
+ foundation.discovery.alertTopicArn,
2336
+ );
2337
+ const retentionMaintenanceContainerName =
2338
+ retentionMaintenanceTask.containers.apply(
2339
+ (containers: string[]) => {
2340
+ if (containers.length !== 1 || !containers[0]) {
2341
+ throw new Error(
2342
+ 'Retention maintenance requires exactly one task container.',
2343
+ );
2344
+ }
2345
+ return containers[0];
2346
+ },
2347
+ );
2348
+ const retentionMaintenanceLauncher = new sst.aws.Function(
2349
+ '${pascalName}DataRetentionMaintenanceLauncher',
2350
+ {
2351
+ ...workloadRoleBindings!.dataRetention.maintenance.launcher,
2352
+ environment: {
2353
+ EBK_MAINTENANCE_ASSIGN_PUBLIC_IP:
2354
+ retentionMaintenanceTask.assignPublicIp.apply(
2355
+ (value: boolean) => String(value),
2356
+ ),
2357
+ EBK_MAINTENANCE_CLUSTER_ARN:
2358
+ retentionMaintenanceTask.cluster,
2359
+ EBK_MAINTENANCE_CONTAINER_NAME:
2360
+ retentionMaintenanceContainerName,
2361
+ EBK_MAINTENANCE_SECURITY_GROUP_IDS: $jsonStringify(
2362
+ retentionMaintenanceTask.securityGroups,
2363
+ ),
2364
+ EBK_MAINTENANCE_SUBNET_IDS: $jsonStringify(
2365
+ retentionMaintenanceTask.subnets,
2366
+ ),
2367
+ EBK_MAINTENANCE_TASK_DEFINITION_ARN:
2368
+ retentionMaintenanceTask.taskDefinition,
2369
+ EBK_STAGE: $app.stage,
2370
+ EBK_WORKSPACE: '${workspace}',
2371
+ },
2372
+ handler: 'jobs/data-retention-maintenance-launcher.handler',
2373
+ ...lambdaLogging(
2374
+ retentionMaintenanceLauncherLogs.logGroup.name,
2375
+ ),
2376
+ timeout: '1 minute',
2377
+ },
2378
+ );
2379
+ const retentionMaintenanceRule = new aws.cloudwatch.EventRule(
2380
+ '${pascalName}DataRetentionMaintenanceSchedule',
2381
+ {
2382
+ name:
2383
+ workloadRoleResourceNames!.dataRetentionMaintenanceRule,
2384
+ scheduleExpression: 'rate(1 day)',
2385
+ },
2386
+ );
2387
+ const retentionMaintenancePermission =
2388
+ new aws.lambda.Permission(
2389
+ '${pascalName}DataRetentionMaintenanceSchedulePermission',
2390
+ {
2391
+ action: 'lambda:InvokeFunction',
2392
+ function: retentionMaintenanceLauncher.arn,
2393
+ principal: 'events.amazonaws.com',
2394
+ sourceArn: retentionMaintenanceRule.arn,
2395
+ },
2396
+ );
2397
+ const retentionMaintenanceTarget =
2398
+ new aws.cloudwatch.EventTarget(
2399
+ '${pascalName}DataRetentionMaintenanceTarget',
2400
+ {
2401
+ arn: retentionMaintenanceLauncher.arn,
2402
+ rule: retentionMaintenanceRule.name,
2403
+ targetId: 'DataRetentionMaintenance',
2404
+ },
2405
+ { dependsOn: [retentionMaintenancePermission] },
2406
+ );
2407
+ migrationControls.dataRetentionMaintenance = {
2408
+ retentionMaintenanceLauncher,
2409
+ retentionMaintenanceLauncherLogs,
2410
+ retentionMaintenanceRule,
2411
+ retentionMaintenanceTarget,
2412
+ retentionMaintenanceTask,
2413
+ retentionMaintenanceTaskLogDataProtection,
2414
+ };
2415
+
2416
+ for (const [databaseIdentifier, target] of Object.entries(
2417
+ databaseProvisions.databases,
2418
+ )) {
2419
+ if (!target.readwriteSecretArn || !target.readonlySecretArn) {
2420
+ throw new Error(
2421
+ 'Deployed database target ' + databaseIdentifier +
2422
+ ' is missing its generated credential references.',
2423
+ );
2424
+ }
2425
+ const bootstrapLogContract = managedLogContract(
2426
+ 'database-bootstrap',
2427
+ databaseIdentifier,
2428
+ );
2429
+ const migrationLogContract = managedLogContract(
2430
+ 'database-migration',
2431
+ databaseIdentifier,
2432
+ );
2433
+ const retirementLogContract = managedLogContract(
2434
+ 'database-retirement',
2435
+ databaseIdentifier,
2436
+ );
2437
+ const databaseRoleBindings =
2438
+ workloadRoleBindings!.database(databaseIdentifier);
2439
+ const bootstrapTask = new sst.aws.Task(
2440
+ target.resourceName + 'BootstrapTask',
2441
+ {
2442
+ ...databaseRoleBindings.bootstrap,
2443
+ cluster: foundation.cluster,
2444
+ command: ['node', 'jobs/bootstrap-database.mjs'],
2445
+ environment: {
2446
+ ...databaseMigrationContainer.environment,
2447
+ AWS_REGION: region,
2448
+ EBK_BOOTSTRAP_ID:
2449
+ $app.name + ':' + $app.stage + ':' + databaseIdentifier,
2450
+ EBK_DATABASE_HOST: foundation.database.clusterEndpoint,
2451
+ EBK_DATABASE_MIGRATOR_ROLE: target.migratorRole,
2452
+ EBK_DATABASE_NAME: target.databaseName,
2453
+ EBK_DATABASE_PORT:
2454
+ $interpolate\`\${foundation.database.port}\`,
2455
+ EBK_DATABASE_READONLY_ROLE: target.readonlyRole,
2456
+ EBK_DATABASE_READWRITE_ROLE: target.readwriteRole,
2457
+ EBK_DATABASE_ROTATION_TOKEN: target.rotationToken ?? '',
2458
+ EBK_MASTER_SECRET_ARN: foundation.database.masterSecretArn,
2459
+ EBK_READONLY_SECRET_ARN: target.readonlySecretArn,
2460
+ EBK_READWRITE_SECRET_ARN: target.readwriteSecretArn,
2461
+ EBK_RDS_CA_BUNDLE: '/app/rds-global-bundle.pem',
2462
+ EBK_RUNTIME_DATABASE_HOST: foundation.database.host,
2463
+ EBK_STAGE: $app.stage,
2464
+ EBK_WORKSPACE: '${workspace}',
2465
+ },
2466
+ image: databaseMigrationContainer.image,
2467
+ link: secretLinksFor(
2468
+ 'database-' + databaseIdentifier + '-bootstrap-task',
2469
+ ),
2470
+ logging: {
2471
+ name: bootstrapLogContract.name,
2472
+ retention: '1 month',
2473
+ },
2474
+ transform: {
2475
+ logGroup: {
2476
+ logGroupClass: bootstrapLogContract.logGroupClass,
2477
+ tags: bootstrapLogContract.tags,
2478
+ },
2479
+ taskDefinition: {
2480
+ family: standardSliceDatabaseTaskFamily(
2481
+ $app.stage,
2482
+ databaseIdentifier,
2483
+ 'bootstrap',
2484
+ ),
2485
+ },
2486
+ },
2487
+ },
2488
+ );
2489
+ const bootstrapLogDataProtection = managedLogDataProtection(
2490
+ target.resourceName + 'BootstrapLog',
2491
+ bootstrapLogContract,
2492
+ bootstrapLogContract.name,
2493
+ [bootstrapTask.nodes.taskDefinition],
2494
+ foundation.discovery.alertTopicArn,
2495
+ );
2496
+ const migrationTask = new sst.aws.Task(
2497
+ target.resourceName + 'MigrationTask',
2498
+ {
2499
+ ...databaseRoleBindings.migration,
2500
+ cluster: foundation.cluster,
2501
+ environment: {
2502
+ ...databaseMigrationContainer.environment,
2503
+ AWS_REGION: region,
2504
+ EBK_DATABASE_HOST: foundation.database.clusterEndpoint,
2505
+ EBK_DATABASE_NAME: target.databaseName,
2506
+ EBK_DATABASE_PORT:
2507
+ $interpolate\`\${foundation.database.port}\`,
2508
+ EBK_DATABASE_READWRITE_ROLE: target.readwriteRole,
2509
+ EBK_DATABASE_USERNAME: target.migratorRole,
2510
+ EBK_MIGRATION_ID:
2511
+ $app.name + ':' + $app.stage + ':' + databaseIdentifier,
2512
+ EBK_RDS_CA_BUNDLE: '/app/rds-global-bundle.pem',
2513
+ EBK_STAGE: $app.stage,
2514
+ EBK_WORKSPACE: '${workspace}',
2515
+ },
2516
+ image: databaseMigrationContainer.image,
2517
+ link: secretLinksFor(
2518
+ 'database-' + databaseIdentifier + '-migration-task',
2519
+ ),
2520
+ logging: {
2521
+ name: migrationLogContract.name,
2522
+ retention: '1 month',
2523
+ },
2524
+ transform: {
2525
+ logGroup: {
2526
+ logGroupClass: migrationLogContract.logGroupClass,
2527
+ tags: migrationLogContract.tags,
2528
+ },
2529
+ taskDefinition: {
2530
+ family: standardSliceDatabaseTaskFamily(
2531
+ $app.stage,
2532
+ databaseIdentifier,
2533
+ 'migration',
2534
+ ),
2535
+ },
2536
+ },
2537
+ },
2538
+ );
2539
+ const migrationLogDataProtection = managedLogDataProtection(
2540
+ target.resourceName + 'MigrationLog',
2541
+ migrationLogContract,
2542
+ migrationLogContract.name,
2543
+ [migrationTask.nodes.taskDefinition],
2544
+ foundation.discovery.alertTopicArn,
2545
+ );
2546
+ const migrationWorkflowLogs = managedLogGroup(
2547
+ target.resourceName + 'MigrationWorkflowLogs',
2548
+ 'database-migration-orchestration',
2549
+ foundation.discovery.alertTopicArn,
2550
+ databaseIdentifier,
2551
+ );
2552
+ const stateMachine = createRawStateMachine(
2553
+ target.resourceName + 'Migration',
2554
+ {
2555
+ StartAt: 'BootstrapDatabase',
2556
+ States: {
2557
+ BootstrapDatabase: {
2558
+ Arguments: {
2559
+ ...taskArguments(bootstrapTask),
2560
+ Overrides: lifecycleEnvironment(bootstrapTask, false),
2561
+ },
2562
+ Next: 'RunDatabaseMigration',
2563
+ QueryLanguage: 'JSONata',
2564
+ Resource: 'arn:aws:states:::ecs:runTask.sync',
2565
+ TimeoutSeconds: 2700,
2566
+ Type: 'Task',
2567
+ },
2568
+ DatabaseMigrationSucceeded: {
2569
+ QueryLanguage: 'JSONata',
2570
+ Type: 'Succeed',
2571
+ },
2572
+ RunDatabaseMigration: {
2573
+ Arguments: {
2574
+ ...taskArguments(migrationTask),
2575
+ Overrides: lifecycleEnvironment(migrationTask, false),
2576
+ },
2577
+ Next: 'DatabaseMigrationSucceeded',
2578
+ QueryLanguage: 'JSONata',
2579
+ Resource: 'arn:aws:states:::ecs:runTask.sync',
2580
+ TimeoutSeconds: 2700,
2581
+ Type: 'Task',
2582
+ },
2583
+ },
2584
+ },
2585
+ migrationWorkflowLogs.logGroup,
2586
+ databaseRoleBindings.migrationOrchestration.roleArn,
2587
+ );
2588
+ const registration = registerProductMigration(
2589
+ target.resourceName + 'MigrationRegistration',
2590
+ databaseIdentifier,
2591
+ stateMachine.arn,
2592
+ );
2593
+ const retirementTask =
2594
+ $app.stage === 'development' &&
2595
+ databaseIdentifier === 'retirement_rehearsal' &&
2596
+ databaseRoleBindings.retirement
2597
+ ? new sst.aws.Task(target.resourceName + 'RetirementTask', {
2598
+ ...databaseRoleBindings.retirement.task,
2599
+ cluster: foundation.cluster,
2600
+ command: ['node', 'jobs/retire-database.mjs'],
2601
+ environment: {
2602
+ ...databaseRetirementContainer.environment,
2603
+ AWS_REGION: region,
2604
+ EBK_DATABASE_HOST: foundation.database.clusterEndpoint,
2605
+ EBK_DATABASE_IDENTIFIER: databaseIdentifier,
2606
+ EBK_DATABASE_MIGRATOR_ROLE: target.migratorRole,
2607
+ EBK_DATABASE_NAME: target.databaseName,
2608
+ EBK_DATABASE_PORT:
2609
+ $interpolate\`\${foundation.database.port}\`,
2610
+ EBK_DATABASE_READONLY_ROLE: target.readonlyRole,
2611
+ EBK_DATABASE_READWRITE_ROLE: target.readwriteRole,
2612
+ EBK_MASTER_SECRET_ARN: foundation.database.masterSecretArn,
2613
+ EBK_RDS_CA_BUNDLE: '/app/rds-global-bundle.pem',
2614
+ EBK_READONLY_SECRET_ARN: target.readonlySecretArn,
2615
+ EBK_READWRITE_SECRET_ARN: target.readwriteSecretArn,
2616
+ EBK_RETIREMENT_ID:
2617
+ $app.name + ':' + $app.stage + ':' + databaseIdentifier,
2618
+ EBK_RETAINED_SIBLINGS: $jsonStringify(
2619
+ Object.entries(databaseProvisions.databases)
2620
+ .filter(([identifier]) => identifier !== databaseIdentifier)
2621
+ .sort(([left], [right]) =>
2622
+ left < right ? -1 : left > right ? 1 : 0,
2623
+ )
2624
+ .map(([identifier, sibling]) => ({
2625
+ databaseIdentifier: identifier,
2626
+ databaseName: sibling.databaseName,
2627
+ migratorRole: sibling.migratorRole,
2628
+ readonlyRole: sibling.readonlyRole,
2629
+ readonlySecretArn: sibling.readonlySecretArn,
2630
+ readwriteRole: sibling.readwriteRole,
2631
+ readwriteSecretArn: sibling.readwriteSecretArn,
2632
+ })),
2633
+ ),
2634
+ EBK_SLICE: '${name}',
2635
+ EBK_STAGE: $app.stage,
2636
+ EBK_WORKSPACE: '${workspace}',
2637
+ },
2638
+ image: databaseRetirementContainer.image,
2639
+ link: secretLinksFor(
2640
+ 'database-' + databaseIdentifier + '-retirement-task',
2641
+ ),
2642
+ logging: {
2643
+ name: retirementLogContract.name,
2644
+ retention: '1 month',
2645
+ },
2646
+ transform: {
2647
+ logGroup: {
2648
+ logGroupClass: retirementLogContract.logGroupClass,
2649
+ tags: retirementLogContract.tags,
2650
+ },
2651
+ taskDefinition: {
2652
+ family: standardSliceDatabaseTaskFamily(
2653
+ $app.stage,
2654
+ databaseIdentifier,
2655
+ 'retirement',
2656
+ ),
2657
+ },
2658
+ },
2659
+ })
2660
+ : undefined;
2661
+ const retirementLogDataProtection = retirementTask
2662
+ ? managedLogDataProtection(
2663
+ target.resourceName + 'RetirementLog',
2664
+ retirementLogContract,
2665
+ retirementLogContract.name,
2666
+ [retirementTask.nodes.taskDefinition],
2667
+ foundation.discovery.alertTopicArn,
2668
+ )
2669
+ : undefined;
2670
+ let retirementControl;
2671
+ if (retirementTask) {
2672
+ const retirementWorkflowLogs = managedLogGroup(
2673
+ target.resourceName + 'RetirementWorkflowLogs',
2674
+ 'database-retirement-orchestration',
2675
+ foundation.discovery.alertTopicArn,
2676
+ databaseIdentifier,
2677
+ );
2678
+ const retirementStateMachine = createRawStateMachine(
2679
+ target.resourceName + 'MigrationRetirement',
2680
+ {
2681
+ StartAt: 'RetireDatabase',
2682
+ States: {
2683
+ DatabaseRetirementSucceeded: {
2684
+ QueryLanguage: 'JSONata',
2685
+ Type: 'Succeed',
2686
+ },
2687
+ RetireDatabase: {
2688
+ Arguments: {
2689
+ ...taskArguments(retirementTask),
2690
+ Overrides: lifecycleEnvironment(retirementTask, true),
2691
+ },
2692
+ Next: 'DatabaseRetirementSucceeded',
2693
+ QueryLanguage: 'JSONata',
2694
+ Resource: 'arn:aws:states:::ecs:runTask.waitForTaskToken',
2695
+ Retry: [
2696
+ {
2697
+ BackoffRate: 2,
2698
+ ErrorEquals: [
2699
+ 'ECS.AmazonECSException',
2700
+ 'ECS.ECSException',
2701
+ ],
2702
+ IntervalSeconds: 10,
2703
+ MaxAttempts: 2,
2704
+ },
2705
+ ],
2706
+ TimeoutSeconds: 2700,
2707
+ Type: 'Task',
2708
+ },
2709
+ },
2710
+ },
2711
+ retirementWorkflowLogs.logGroup,
2712
+ databaseRoleBindings.retirement!.orchestration.roleArn,
2713
+ );
2714
+ retirementControl = {
2715
+ retirementWorkflowLogs,
2716
+ retirementStateMachine,
2717
+ retirementTask,
2718
+ };
2719
+ }
2720
+ migrationControls[databaseIdentifier] = {
2721
+ bootstrapLogDataProtection,
2722
+ bootstrapTask,
2723
+ migrationLogDataProtection,
2724
+ migrationTask,
2725
+ migrationWorkflowLogs,
2726
+ registration,
2727
+ retirementLogDataProtection,
2728
+ ...(retirementControl ?? {}),
2729
+ stateMachine,
2730
+ };
2731
+ }
2732
+ }
2733
+
2734
+ const outboxRelayAlarm = new aws.cloudwatch.MetricAlarm(
2735
+ '${pascalName}OutboxRelayErrors',
2736
+ {
2737
+ alarmActions: [foundation.discovery.alertTopicArn],
2738
+ alarmDescription: operationalAlarmDescription({
2739
+ businessSymptom: 'The transactional outbox relay is failing.',
2740
+ id: 'outbox-relay-errors',
2741
+ runbook: 'docs/runbooks/async-recovery.md',
2742
+ }),
2743
+ comparisonOperator: 'GreaterThanOrEqualToThreshold',
2744
+ dimensions: { FunctionName: asyncOutboxRelay.name },
2745
+ evaluationPeriods: 1,
2746
+ metricName: 'Errors',
2747
+ name: '${name}-' + $app.stage + '-outbox-relay-errors',
2748
+ namespace: 'AWS/Lambda',
2749
+ period: 300,
2750
+ statistic: 'Sum',
2751
+ threshold: 1,
2752
+ treatMissingData: 'notBreaching',
2753
+ },
2754
+ );
2755
+ const inboxCleanupErrorAlarm = new aws.cloudwatch.MetricAlarm(
2756
+ '${pascalName}InboxCleanupErrors',
2757
+ {
2758
+ alarmActions: [foundation.discovery.alertTopicArn],
2759
+ alarmDescription: operationalAlarmDescription({
2760
+ businessSymptom: 'The asynchronous inbox cleanup job is failing.',
2761
+ id: 'inbox-cleanup-errors',
2762
+ runbook: 'docs/runbooks/async-recovery.md',
2763
+ }),
2764
+ comparisonOperator: 'GreaterThanOrEqualToThreshold',
2765
+ dimensions: { FunctionName: asyncInboxCleanup.name },
2766
+ evaluationPeriods: 1,
2767
+ metricName: 'Errors',
2768
+ name: '${name}-' + $app.stage + '-inbox-cleanup-errors',
2769
+ namespace: 'AWS/Lambda',
2770
+ period: 300,
2771
+ statistic: 'Sum',
2772
+ threshold: 1,
2773
+ treatMissingData: 'notBreaching',
2774
+ },
2775
+ );
2776
+ const dataExportCleanupErrorAlarm = new aws.cloudwatch.MetricAlarm(
2777
+ '${pascalName}DataExportCleanupErrors',
2778
+ {
2779
+ alarmActions: [foundation.discovery.alertTopicArn],
2780
+ alarmDescription: operationalAlarmDescription({
2781
+ businessSymptom:
2782
+ 'The confidential-export cleanup invocation is failing.',
2783
+ id: 'data-export-cleanup-failure',
2784
+ runbook: 'docs/runbooks/database-recovery.md',
2785
+ }),
2786
+ comparisonOperator: 'GreaterThanOrEqualToThreshold',
2787
+ dimensions: { FunctionName: dataExportCleanup.name },
2788
+ evaluationPeriods: 1,
2789
+ metricName: 'Errors',
2790
+ name: '${name}-' + $app.stage + '-data-export-cleanup-failure',
2791
+ namespace: 'AWS/Lambda',
2792
+ period: 300,
2793
+ statistic: 'Sum',
2794
+ threshold: 1,
2795
+ treatMissingData: 'notBreaching',
2796
+ },
2797
+ );
2798
+ const dataExportCleanupInvocationAlarm =
2799
+ new aws.cloudwatch.MetricAlarm(
2800
+ '${pascalName}DataExportCleanupInvocationMissing',
2801
+ {
2802
+ alarmActions: [foundation.discovery.alertTopicArn],
2803
+ alarmDescription: operationalAlarmDescription({
2804
+ businessSymptom:
2805
+ 'The scheduled confidential-export cleanup is not running.',
2806
+ id: 'data-export-cleanup-invocation-missing',
2807
+ runbook: 'docs/runbooks/database-recovery.md',
2808
+ }),
2809
+ comparisonOperator: 'LessThanThreshold',
2810
+ dimensions: { FunctionName: dataExportCleanup.name },
2811
+ evaluationPeriods: 2,
2812
+ metricName: 'Invocations',
2813
+ name:
2814
+ '${name}-' +
2815
+ $app.stage +
2816
+ '-data-export-cleanup-invocation-missing',
2817
+ namespace: 'AWS/Lambda',
2818
+ period: 3600,
2819
+ statistic: 'Sum',
2820
+ threshold: 1,
2821
+ treatMissingData: 'breaching',
2822
+ },
2823
+ );
2824
+ const outboxBacklogAlarm = new aws.cloudwatch.MetricAlarm(
2825
+ '${pascalName}OutboxBacklog',
2826
+ {
2827
+ alarmActions: [foundation.discovery.alertTopicArn],
2828
+ alarmDescription: operationalAlarmDescription({
2829
+ businessSymptom: 'Transactional outbox work is accumulating.',
2830
+ id: 'outbox-backlog',
2831
+ runbook: 'docs/runbooks/async-recovery.md',
2832
+ }),
2833
+ comparisonOperator: 'GreaterThanOrEqualToThreshold',
2834
+ dimensions: { Slice: '${name}', Stage: $app.stage },
2835
+ evaluationPeriods: 2,
2836
+ metricName: 'OutboxBacklog',
2837
+ name: '${name}-' + $app.stage + '-outbox-backlog',
2838
+ namespace: 'EBK/Async',
2839
+ period: 300,
2840
+ statistic: 'Maximum',
2841
+ threshold: 1,
2842
+ treatMissingData: 'notBreaching',
2843
+ },
2844
+ );
2845
+ const inboxCleanupBacklogAlarm = new aws.cloudwatch.MetricAlarm(
2846
+ '${pascalName}InboxCleanupBacklog',
2847
+ {
2848
+ alarmActions: [foundation.discovery.alertTopicArn],
2849
+ alarmDescription: operationalAlarmDescription({
2850
+ businessSymptom: 'Terminal asynchronous inbox records are accumulating.',
2851
+ id: 'inbox-cleanup-backlog',
2852
+ runbook: 'docs/runbooks/async-recovery.md',
2853
+ }),
2854
+ comparisonOperator: 'GreaterThanOrEqualToThreshold',
2855
+ dimensions: {
2856
+ Component: 'inbox-cleanup',
2857
+ Feature: 'async-work',
2858
+ Operation: 'inbox-cleanup',
2859
+ Slice: '${name}',
2860
+ Stage: $app.stage,
2861
+ },
2862
+ evaluationPeriods: 2,
2863
+ metricName: 'InboxCleanupBacklog',
2864
+ name: '${name}-' + $app.stage + '-inbox-cleanup-backlog',
2865
+ namespace: 'EBK/Async',
2866
+ period: 3600,
2867
+ statistic: 'Maximum',
2868
+ threshold: 1,
2869
+ treatMissingData: 'notBreaching',
2870
+ },
2871
+ );
2872
+ const dataExportCleanupDeadlineAlarm =
2873
+ new aws.cloudwatch.MetricAlarm(
2874
+ '${pascalName}DataExportCleanupDeadline',
2875
+ {
2876
+ alarmActions: [foundation.discovery.alertTopicArn],
2877
+ alarmDescription: operationalAlarmDescription({
2878
+ businessSymptom:
2879
+ 'Confidential-export residue exceeded its deletion deadline.',
2880
+ id: 'data-export-cleanup-deadline',
2881
+ runbook: 'docs/runbooks/database-recovery.md',
2882
+ }),
2883
+ comparisonOperator: 'GreaterThanOrEqualToThreshold',
2884
+ dimensions: {
2885
+ Component: 'data-export-cleanup',
2886
+ Feature: 'data-lifecycle',
2887
+ Operation: 'expiry-cleanup',
2888
+ Slice: '${name}',
2889
+ Stage: $app.stage,
2890
+ },
2891
+ evaluationPeriods: 1,
2892
+ metricName: 'DataExportCleanupDeadlineBreaches',
2893
+ name:
2894
+ '${name}-' +
2895
+ $app.stage +
2896
+ '-data-export-cleanup-deadline',
2897
+ namespace: 'EBK/DataLifecycle',
2898
+ period: 3600,
2899
+ statistic: 'Maximum',
2900
+ threshold: 1,
2901
+ treatMissingData: 'notBreaching',
2902
+ },
2903
+ );
2904
+ const performanceLoadCleanupBacklogAlarm =
2905
+ new aws.cloudwatch.MetricAlarm(
2906
+ '${pascalName}PerformanceLoadCleanupBacklog',
2907
+ {
2908
+ alarmActions: [foundation.discovery.alertTopicArn],
2909
+ alarmDescription: operationalAlarmDescription({
2910
+ businessSymptom:
2911
+ 'Expired protected performance-load evidence is accumulating.',
2912
+ id: 'performance-load-cleanup-backlog',
2913
+ runbook: 'docs/runbooks/performance-capacity-cost.md',
2914
+ }),
2915
+ comparisonOperator: 'GreaterThanOrEqualToThreshold',
2916
+ dimensions: {
2917
+ Component: 'performance-load-cleanup',
2918
+ Feature: 'performance',
2919
+ Operation: 'expiry-cleanup',
2920
+ Slice: '${name}',
2921
+ Stage: $app.stage,
2922
+ },
2923
+ evaluationPeriods: 2,
2924
+ metricName: 'PerformanceLoadCleanupBacklog',
2925
+ name:
2926
+ '${name}-' +
2927
+ $app.stage +
2928
+ '-performance-load-cleanup-backlog',
2929
+ namespace: 'EBK/Performance',
2930
+ period: 3600,
2931
+ statistic: 'Maximum',
2932
+ threshold: 1,
2933
+ treatMissingData: 'notBreaching',
2934
+ },
2935
+ );
2936
+ const performanceLoadCleanupDeadlineAlarm =
2937
+ new aws.cloudwatch.MetricAlarm(
2938
+ '${pascalName}PerformanceLoadCleanupDeadline',
2939
+ {
2940
+ alarmActions: [foundation.discovery.alertTopicArn],
2941
+ alarmDescription: operationalAlarmDescription({
2942
+ businessSymptom:
2943
+ 'Protected performance-load residue exceeded its cleanup deadline.',
2944
+ id: 'performance-load-cleanup-deadline',
2945
+ runbook: 'docs/runbooks/performance-capacity-cost.md',
2946
+ }),
2947
+ comparisonOperator: 'GreaterThanOrEqualToThreshold',
2948
+ dimensions: {
2949
+ Component: 'performance-load-cleanup',
2950
+ Feature: 'performance',
2951
+ Operation: 'expiry-cleanup',
2952
+ Slice: '${name}',
2953
+ Stage: $app.stage,
2954
+ },
2955
+ evaluationPeriods: 1,
2956
+ metricName: 'PerformanceLoadCleanupDeadlineBreaches',
2957
+ name:
2958
+ '${name}-' +
2959
+ $app.stage +
2960
+ '-performance-load-cleanup-deadline',
2961
+ namespace: 'EBK/Performance',
2962
+ period: 3600,
2963
+ statistic: 'Maximum',
2964
+ threshold: 1,
2965
+ treatMissingData: 'notBreaching',
2966
+ },
2967
+ );
2968
+ const dashboard = new aws.cloudwatch.Dashboard('${pascalName}Dashboard', {
2969
+ dashboardName: '${name}-' + $app.stage,
2970
+ dashboardBody: JSON.stringify({
2971
+ widgets: [{
2972
+ type: 'text', x: 0, y: 0, width: 24, height: 3,
2973
+ properties: { markdown: '# ${pascalName}\\nOwner: product' },
2974
+ }],
2975
+ }),
2976
+ });
2977
+ return {
2978
+ api,
2979
+ asyncInboxCleanup,
2980
+ dataExportCleanupDeadlineAlarm,
2981
+ dataExportCleanupErrorAlarm,
2982
+ dataExportCleanupInvocationAlarm,
2983
+ inboxCleanupErrorAlarm,
2984
+ inboxCleanupBacklogAlarm,
2985
+ inboxCleanupRule,
2986
+ inboxCleanupTarget,
2987
+ performanceLoadCleanupBacklogAlarm,
2988
+ performanceLoadCleanupDeadlineAlarm,
2989
+ asyncOutboxRelay,
2990
+ asyncRecoveryLedger,
2991
+ asyncScheduleTarget,
2992
+ asyncTargetDlq,
2993
+ asyncTargetDlqAlarm,
2994
+ asyncTargetDlqPolicy,
2995
+ asyncWorkflowTask,
2996
+ authClient,
2997
+ configurationApplication,
2998
+ configurationDeployment,
2999
+ configurationDeploymentStrategy,
3000
+ configurationEnvironment,
3001
+ configurationProfile,
3002
+ configurationVersion,
3003
+ dashboard,
3004
+ dataExportCleanup,
3005
+ dataExportCleanupInvocation,
3006
+ dataExportCleanupRule,
3007
+ dataExportCleanupTarget,
3008
+ dataExportStore,
3009
+ database,
3010
+ dlqAlarm,
3011
+ eventDlq,
3012
+ eventQueue,
3013
+ machineClientProvisioner,
3014
+ migrationControls,
3015
+ outboxBacklogAlarm,
3016
+ outboxRelayAlarm,
3017
+ outboxRelayRule,
3018
+ outboxRelayTarget,
3019
+ performanceCostControls,
3020
+ performanceEndpoint: performanceIngress?.router.url,
3021
+ providerDlq,
3022
+ providerDlqAlarm,
3023
+ providerQueue,
3024
+ reliability,
3025
+ providerReconciliationRule,
3026
+ providerReconciliationTarget,
3027
+ recordExportWorkflow,
3028
+ machineAuthorization,
3029
+ machineAuthorizationProof,
3030
+ web,
3031
+ // <ebk:composition-outputs:start>
3032
+ // <ebk:composition-outputs:end>
3033
+ };
3034
+ }
3035
+ `,
3036
+ };
3037
+ }
3038
+ //# sourceMappingURL=built-in-plan-infrastructure-files.js.map