@empire-builder-kit/nx 1.0.0-rc.4 → 1.0.0-rc.41

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 (805) hide show
  1. package/README.md +44 -10
  2. package/executors.json +21 -1
  3. package/generators.json +10 -0
  4. package/package.json +31 -7
  5. package/src/aws/child-environment.d.ts +10 -0
  6. package/src/aws/child-environment.js +77 -0
  7. package/src/aws/identity.d.ts +13 -0
  8. package/src/aws/identity.js +41 -0
  9. package/src/aws-authority/index.d.ts +2 -0
  10. package/src/aws-authority/index.js +6 -0
  11. package/src/aws-authority/lambda-policy-ordering.d.ts +38 -0
  12. package/src/aws-authority/lambda-policy-ordering.js +56 -0
  13. package/src/aws-authority/stage-authority.d.ts +248 -0
  14. package/src/aws-authority/stage-authority.js +3558 -0
  15. package/src/blueprints/external-sandbox.d.ts +1 -1
  16. package/src/blueprints/external-sandbox.js +21 -9
  17. package/src/blueprints/manifest.d.ts +1 -1
  18. package/src/blueprints/resolver.js +28 -15
  19. package/src/cli/bootstrap.d.ts +1 -1
  20. package/src/cli/deploy-runtime.d.ts +30 -0
  21. package/src/cli/deploy-runtime.js +174 -0
  22. package/src/cli/dev-command.d.ts +18 -4
  23. package/src/cli/dev-command.js +71 -0
  24. package/src/cli/main.d.ts +9 -1
  25. package/src/cli/main.js +157 -24
  26. package/src/cli/output.d.ts +1 -1
  27. package/src/cli/output.js +1 -1
  28. package/src/cli/personal-secret-runtime.d.ts +12 -0
  29. package/src/cli/personal-secret-runtime.js +18 -3
  30. package/src/cli/secret-terminal.js +1 -1
  31. package/src/container-images.d.ts +1 -1
  32. package/src/container-images.js +1 -1
  33. package/src/developer-agent/context.js +41 -9
  34. package/src/developer-agent/generator-planning.js +20 -6
  35. package/src/developer-agent/git-inspection.d.ts +6 -2
  36. package/src/developer-agent/git-inspection.js +78 -6
  37. package/src/developer-agent/model.d.ts +5 -3
  38. package/src/developer-agent/model.js +4 -4
  39. package/src/developer-agent/operation-registry.d.ts +15 -0
  40. package/src/developer-agent/operation-registry.js +342 -49
  41. package/src/developer-agent/planning.d.ts +8 -1
  42. package/src/developer-agent/planning.js +39 -16
  43. package/src/developer-agent/preview-changes.d.ts +2 -0
  44. package/src/developer-agent/preview-changes.js +14 -2
  45. package/src/developer-agent/preview-source.js +3 -0
  46. package/src/developer-agent/validation.js +25 -9
  47. package/src/developer-agent/workspace-inspection.js +60 -5
  48. package/src/executors/blueprint-trust/blueprint-trust.js +6 -0
  49. package/src/executors/deployment-plan/deployment-plan.d.ts +2 -0
  50. package/src/executors/deployment-plan/deployment-plan.js +57 -2
  51. package/src/executors/dev-doctor/dev-doctor.js +13 -6
  52. package/src/executors/dev-task-cleanup/dev-task-cleanup.d.ts +23 -0
  53. package/src/executors/dev-task-cleanup/dev-task-cleanup.js +145 -0
  54. package/src/executors/dev-task-cleanup/schema.json +9 -0
  55. package/src/executors/dev-task-cleanup/task-stack-absence.d.ts +27 -0
  56. package/src/executors/dev-task-cleanup/task-stack-absence.js +281 -0
  57. package/src/executors/dev-task-cleanup/task-stack-checkpoint.d.ts +12 -0
  58. package/src/executors/dev-task-cleanup/task-stack-checkpoint.js +92 -0
  59. package/src/executors/fleet/fleet.js +1 -1
  60. package/src/executors/foundation-preflight/foundation-preflight.d.ts +1 -0
  61. package/src/executors/foundation-preflight/foundation-preflight.js +6 -2
  62. package/src/executors/foundation-preflight/schema.json +5 -0
  63. package/src/executors/foundation-profile-preflight/foundation-profile-preflight.d.ts +13 -2
  64. package/src/executors/foundation-profile-preflight/foundation-profile-preflight.js +95 -31
  65. package/src/executors/foundation-profile-preflight/production-database-safeguard.d.ts +65 -0
  66. package/src/executors/foundation-profile-preflight/production-database-safeguard.js +223 -0
  67. package/src/executors/foundation-profile-preflight/schema.json +15 -4
  68. package/src/executors/foundation-remove-preflight/foundation-remove-preflight.d.ts +11 -1
  69. package/src/executors/foundation-remove-preflight/foundation-remove-preflight.js +70 -20
  70. package/src/executors/foundation-remove-preflight/schema.json +6 -1
  71. package/src/executors/github-bootstrap/github-bootstrap.d.ts +1 -0
  72. package/src/executors/github-bootstrap/github-bootstrap.js +3 -0
  73. package/src/executors/github-bootstrap/schema.json +7 -1
  74. package/src/executors/infrastructure-plan/infrastructure-plan.d.ts +29 -136
  75. package/src/executors/infrastructure-plan/infrastructure-plan.js +230 -3282
  76. package/src/executors/infrastructure-plan/schema.json +12 -52
  77. package/src/executors/local-deploy/federated-credential-transport.d.ts +48 -0
  78. package/src/executors/local-deploy/federated-credential-transport.js +252 -0
  79. package/src/executors/local-deploy/local-approval.d.ts +18 -0
  80. package/src/executors/local-deploy/local-approval.js +39 -0
  81. package/src/executors/local-deploy/local-deploy.d.ts +112 -0
  82. package/src/executors/local-deploy/local-deploy.js +1404 -0
  83. package/src/executors/local-deploy/schema.json +70 -0
  84. package/src/executors/refresh/refresh.js +2 -2
  85. package/src/executors/remove/remove.js +2 -2
  86. package/src/executors/router-waf-rehearsal/router-waf-rehearsal.d.ts +42 -0
  87. package/src/executors/router-waf-rehearsal/router-waf-rehearsal.js +1229 -0
  88. package/src/executors/router-waf-rehearsal/schema.json +29 -0
  89. package/src/executors/secret-initialize/schema.json +42 -0
  90. package/src/executors/secret-initialize/secret-initialize.d.ts +34 -0
  91. package/src/executors/secret-initialize/secret-initialize.js +297 -0
  92. package/src/executors/secret-metadata/schema.json +5 -0
  93. package/src/executors/secret-metadata/secret-metadata.d.ts +16 -2
  94. package/src/executors/secret-metadata/secret-metadata.js +129 -30
  95. package/src/executors/sst-lifecycle/generated-secret-retirement.d.ts +53 -0
  96. package/src/executors/sst-lifecycle/generated-secret-retirement.js +192 -0
  97. package/src/executors/sst-lifecycle/governed-native-operation.d.ts +48 -0
  98. package/src/executors/sst-lifecycle/governed-native-operation.js +163 -0
  99. package/src/executors/sst-lifecycle/logging-preflight.d.ts +5 -0
  100. package/src/executors/sst-lifecycle/logging-preflight.js +23 -13
  101. package/src/executors/sst-lifecycle/run-sst-command.d.ts +46 -6
  102. package/src/executors/sst-lifecycle/run-sst-command.js +750 -71
  103. package/src/executors/sst-lifecycle/schema.json +8 -1
  104. package/src/executors/sst-lifecycle/secret-preflight.d.ts +2 -0
  105. package/src/executors/sst-lifecycle/secret-preflight.js +13 -7
  106. package/src/executors/sst-lifecycle/sst-environment-source.d.ts +33 -0
  107. package/src/executors/sst-lifecycle/sst-environment-source.js +267 -0
  108. package/src/executors/sst-lifecycle/task-unlock.d.ts +15 -0
  109. package/src/executors/sst-lifecycle/task-unlock.js +126 -0
  110. package/src/executors/unlock/unlock.js +2 -2
  111. package/src/foundation/capability-plan.d.ts +43 -0
  112. package/src/foundation/capability-plan.js +168 -0
  113. package/src/foundation/cost-estimate.d.ts +170 -0
  114. package/src/foundation/cost-estimate.js +683 -0
  115. package/src/foundation/discovery.d.ts +14 -11
  116. package/src/foundation/discovery.js +32 -6
  117. package/src/foundation/index.d.ts +4 -0
  118. package/src/foundation/index.js +11 -0
  119. package/src/foundation/local-database-contract.d.ts +20 -0
  120. package/src/foundation/local-database-contract.js +262 -13
  121. package/src/foundation/local-private-smoke-access.d.ts +20 -0
  122. package/src/foundation/local-private-smoke-access.js +197 -0
  123. package/src/foundation/logging-preflight.js +1 -1
  124. package/src/foundation/logging.d.ts +3 -1
  125. package/src/foundation/logging.js +1 -1
  126. package/src/foundation/partial-state.d.ts +10 -0
  127. package/src/foundation/partial-state.js +21 -0
  128. package/src/foundation/preflight.js +37 -18
  129. package/src/foundation/private-smoke-access.d.ts +25 -0
  130. package/src/foundation/private-smoke-access.js +135 -0
  131. package/src/foundation/private-smoke-admission.d.ts +45 -0
  132. package/src/foundation/private-smoke-admission.js +180 -0
  133. package/src/foundation/private-smoke-control-plane.d.ts +18 -0
  134. package/src/foundation/private-smoke-control-plane.js +184 -0
  135. package/src/foundation/provision-database-bindings.d.ts +5 -0
  136. package/src/foundation/provision-database-bindings.js +41 -8
  137. package/src/foundation/provision-logical-database.js +15 -8
  138. package/src/foundation/requirements.d.ts +3 -3
  139. package/src/foundation/requirements.js +24 -12
  140. package/src/foundation/router-waf-rate-rehearsal.d.ts +97 -0
  141. package/src/foundation/router-waf-rate-rehearsal.js +210 -0
  142. package/src/foundation/router-waf.d.ts +149 -0
  143. package/src/foundation/router-waf.js +809 -0
  144. package/src/foundation/stages.d.ts +1 -0
  145. package/src/foundation/stages.js +11 -0
  146. package/src/generators/blueprint/blueprint.d.ts +5 -0
  147. package/src/generators/blueprint/blueprint.js +102 -0
  148. package/src/generators/blueprint/files.d.ts +6 -0
  149. package/src/generators/blueprint/files.js +217 -0
  150. package/src/generators/blueprint/schema.d.ts +4 -0
  151. package/src/generators/blueprint/schema.json +18 -0
  152. package/src/generators/formatting.d.ts +8 -0
  153. package/src/generators/formatting.js +141 -0
  154. package/src/generators/function/function.js +38 -17
  155. package/src/generators/logging-registry.d.ts +2 -0
  156. package/src/generators/logging-registry.js +22 -0
  157. package/src/generators/post-generation-install.js +2 -2
  158. package/src/generators/preset/dependency-overrides.d.ts +8 -0
  159. package/src/generators/preset/dependency-overrides.js +55 -0
  160. package/src/generators/preset/generator.js +43 -3
  161. package/src/generators/preset/schema.d.ts +10 -0
  162. package/src/generators/preset/schema.json +47 -0
  163. package/src/generators/preset/workspace-files-adopter-skills.d.ts +7 -0
  164. package/src/generators/preset/workspace-files-adopter-skills.js +324 -0
  165. package/src/generators/preset/workspace-files-ci-acceptance.d.ts +1 -0
  166. package/src/generators/preset/workspace-files-ci-acceptance.js +791 -0
  167. package/src/generators/preset/workspace-files-ci-install.d.ts +2 -0
  168. package/src/generators/preset/workspace-files-ci-install.js +196 -0
  169. package/src/generators/preset/workspace-files-developer-agent.d.ts +2 -0
  170. package/src/generators/preset/workspace-files-developer-agent.js +162 -44
  171. package/src/generators/preset/workspace-files-ecr-inspector.d.ts +18 -0
  172. package/src/generators/preset/workspace-files-ecr-inspector.js +298 -0
  173. package/src/generators/preset/workspace-files-foundation-workload-roles.js +5 -2
  174. package/src/generators/preset/workspace-files-performance-cost.d.ts +2 -2
  175. package/src/generators/preset/workspace-files-performance-cost.js +8 -8
  176. package/src/generators/preset/workspace-files-performance-load.d.ts +1 -1
  177. package/src/generators/preset/workspace-files-performance-load.js +39 -25
  178. package/src/generators/preset/workspace-files-shared-operation-claims.d.ts +10 -0
  179. package/src/generators/preset/workspace-files-shared-operation-claims.js +59 -0
  180. package/src/generators/preset/workspace-files-stage-authority.d.ts +6 -0
  181. package/src/generators/preset/workspace-files-stage-authority.js +529 -0
  182. package/src/generators/preset/workspace-files-thin-deploy.d.ts +65 -0
  183. package/src/generators/preset/workspace-files-thin-deploy.js +2507 -0
  184. package/src/generators/preset/workspace-files-thin-docs.d.ts +7 -0
  185. package/src/generators/preset/workspace-files-thin-docs.js +370 -0
  186. package/src/generators/preset/workspace-files-thin-release.d.ts +14 -0
  187. package/src/generators/preset/workspace-files-thin-release.js +26 -0
  188. package/src/generators/preset/workspace-files-workload-role-baseline.js +254 -42
  189. package/src/generators/preset/workspace-files.d.ts +18 -2
  190. package/src/generators/preset/workspace-files.js +1525 -609
  191. package/src/generators/repository-policy/repository-policy.js +235 -5
  192. package/src/generators/repository-policy/schema.d.ts +10 -0
  193. package/src/generators/repository-policy/schema.json +80 -4
  194. package/src/generators/repository-policy/tree-policy.d.ts +3 -0
  195. package/src/generators/repository-policy/tree-policy.js +25 -0
  196. package/src/generators/slice/built-in-plan-api-contract-files.js +49 -11
  197. package/src/generators/slice/built-in-plan-api-files.d.ts +1 -1
  198. package/src/generators/slice/built-in-plan-api-files.js +130 -414
  199. package/src/generators/slice/built-in-plan-async-work-files.js +166 -1969
  200. package/src/generators/slice/built-in-plan-authorization-files.js +102 -334
  201. package/src/generators/slice/built-in-plan-authorization-infrastructure-files.js +975 -233
  202. package/src/generators/slice/built-in-plan-authorization-storage.d.ts +3 -0
  203. package/src/generators/slice/built-in-plan-authorization-storage.js +119 -0
  204. package/src/generators/slice/built-in-plan-background-work-files.d.ts +2 -0
  205. package/src/generators/slice/built-in-plan-background-work-files.js +99 -0
  206. package/src/generators/slice/built-in-plan-data-deletion-files.js +2 -830
  207. package/src/generators/slice/built-in-plan-data-export-infrastructure-files.js +5 -31
  208. package/src/generators/slice/built-in-plan-data-files.d.ts +1 -1
  209. package/src/generators/slice/built-in-plan-data-files.js +176 -1200
  210. package/src/generators/slice/built-in-plan-data-lifecycle-files.js +53 -28
  211. package/src/generators/slice/built-in-plan-data-lifecycle-runtime-files.js +37 -2
  212. package/src/generators/slice/built-in-plan-data-retention-job-files.js +7 -2443
  213. package/src/generators/slice/built-in-plan-data-retention-launcher-files.js +50 -8
  214. package/src/generators/slice/built-in-plan-data-rights-files.js +6 -4482
  215. package/src/generators/slice/built-in-plan-database-lifecycle-files.js +44 -25
  216. package/src/generators/slice/built-in-plan-database-retirement-rehearsal-files.js +25 -2
  217. package/src/generators/slice/built-in-plan-foundation-files.d.ts +2 -1
  218. package/src/generators/slice/built-in-plan-foundation-files.js +39 -18
  219. package/src/generators/slice/built-in-plan-frontend-files.js +14 -3
  220. package/src/generators/slice/built-in-plan-infrastructure-files.d.ts +1 -1
  221. package/src/generators/slice/built-in-plan-infrastructure-files.js +933 -730
  222. package/src/generators/slice/built-in-plan-infrastructure-safety-files.js +75 -14
  223. package/src/generators/slice/built-in-plan-key-value-cleanup.d.ts +4 -0
  224. package/src/generators/slice/built-in-plan-key-value-cleanup.js +100 -0
  225. package/src/generators/slice/built-in-plan-key-value-consumer.d.ts +4 -0
  226. package/src/generators/slice/built-in-plan-key-value-consumer.js +151 -0
  227. package/src/generators/slice/built-in-plan-key-value-deletion.d.ts +2 -0
  228. package/src/generators/slice/built-in-plan-key-value-deletion.js +377 -0
  229. package/src/generators/slice/built-in-plan-key-value-delivery.d.ts +2 -0
  230. package/src/generators/slice/built-in-plan-key-value-delivery.js +137 -0
  231. package/src/generators/slice/built-in-plan-key-value-export.d.ts +2 -0
  232. package/src/generators/slice/built-in-plan-key-value-export.js +595 -0
  233. package/src/generators/slice/built-in-plan-key-value-files.d.ts +2 -0
  234. package/src/generators/slice/built-in-plan-key-value-files.js +31 -0
  235. package/src/generators/slice/built-in-plan-key-value-holds.d.ts +2 -0
  236. package/src/generators/slice/built-in-plan-key-value-holds.js +189 -0
  237. package/src/generators/slice/built-in-plan-key-value-infrastructure.d.ts +2 -0
  238. package/src/generators/slice/built-in-plan-key-value-infrastructure.js +102 -0
  239. package/src/generators/slice/built-in-plan-key-value-performance.d.ts +5 -0
  240. package/src/generators/slice/built-in-plan-key-value-performance.js +530 -0
  241. package/src/generators/slice/built-in-plan-key-value-provider.d.ts +4 -0
  242. package/src/generators/slice/built-in-plan-key-value-provider.js +366 -0
  243. package/src/generators/slice/built-in-plan-key-value-records.d.ts +3 -0
  244. package/src/generators/slice/built-in-plan-key-value-records.js +232 -0
  245. package/src/generators/slice/built-in-plan-key-value-retention-infrastructure.d.ts +2 -0
  246. package/src/generators/slice/built-in-plan-key-value-retention-infrastructure.js +103 -0
  247. package/src/generators/slice/built-in-plan-key-value-retention-job-files.d.ts +2 -0
  248. package/src/generators/slice/built-in-plan-key-value-retention-job-files.js +160 -0
  249. package/src/generators/slice/built-in-plan-key-value-retention-maintenance.d.ts +2 -0
  250. package/src/generators/slice/built-in-plan-key-value-retention-maintenance.js +329 -0
  251. package/src/generators/slice/built-in-plan-key-value-retention-worker.d.ts +2 -0
  252. package/src/generators/slice/built-in-plan-key-value-retention-worker.js +477 -0
  253. package/src/generators/slice/built-in-plan-key-value-retention.d.ts +2 -0
  254. package/src/generators/slice/built-in-plan-key-value-retention.js +184 -0
  255. package/src/generators/slice/built-in-plan-metadata-files.d.ts +1 -1
  256. package/src/generators/slice/built-in-plan-metadata-files.js +159 -29
  257. package/src/generators/slice/built-in-plan-native-runtime-authority-files.d.ts +2 -0
  258. package/src/generators/slice/built-in-plan-native-runtime-authority-files.js +324 -0
  259. package/src/generators/slice/built-in-plan-operations-files.d.ts +1 -1
  260. package/src/generators/slice/built-in-plan-operations-files.js +585 -97
  261. package/src/generators/slice/built-in-plan-performance-files.d.ts +2 -1
  262. package/src/generators/slice/built-in-plan-performance-files.js +220 -1074
  263. package/src/generators/slice/built-in-plan-project-files.js +141 -135
  264. package/src/generators/slice/built-in-plan-release-files.d.ts +1 -1
  265. package/src/generators/slice/built-in-plan-release-files.js +310 -70
  266. package/src/generators/slice/built-in-plan-reliability-files.d.ts +1 -1
  267. package/src/generators/slice/built-in-plan-reliability-files.js +283 -44
  268. package/src/generators/slice/built-in-plan-secret-runtime-files.d.ts +1 -1
  269. package/src/generators/slice/built-in-plan-secret-runtime-files.js +87 -76
  270. package/src/generators/slice/built-in-plan-shared.d.ts +4 -2
  271. package/src/generators/slice/built-in-plan-shared.js +41 -19
  272. package/src/generators/slice/built-in-plan-sql-events.d.ts +2 -0
  273. package/src/generators/slice/built-in-plan-sql-events.js +276 -0
  274. package/src/generators/slice/built-in-plan-testing-files.d.ts +1 -1
  275. package/src/generators/slice/built-in-plan-testing-files.js +304 -172
  276. package/src/generators/slice/built-in-plan-testing-persistence-event-files.d.ts +7 -0
  277. package/src/generators/slice/built-in-plan-testing-persistence-event-files.js +487 -0
  278. package/src/generators/slice/built-in-plan-testing-persistence-files.d.ts +2 -0
  279. package/src/generators/slice/built-in-plan-testing-persistence-files.js +180 -0
  280. package/src/generators/slice/built-in-plan-thin-release-files.d.ts +11 -0
  281. package/src/generators/slice/built-in-plan-thin-release-files.js +162 -0
  282. package/src/generators/slice/built-in-plan-verification-files.js +76 -2
  283. package/src/generators/slice/built-in-plan-web-files.d.ts +1 -1
  284. package/src/generators/slice/built-in-plan-web-files.js +223 -16
  285. package/src/generators/slice/built-in-plan-workload-role-files.d.ts +1 -1
  286. package/src/generators/slice/built-in-plan-workload-role-files.js +143 -279
  287. package/src/generators/slice/built-in-plan.d.ts +11 -1
  288. package/src/generators/slice/built-in-plan.js +83 -30
  289. package/src/generators/slice/data-policy-review-seed.d.ts +2 -0
  290. package/src/generators/slice/data-policy-review-seed.js +22 -0
  291. package/src/generators/slice/deployed-smoke-readiness.d.ts +2 -0
  292. package/src/generators/slice/deployed-smoke-readiness.js +214 -0
  293. package/src/generators/slice/local-source-identity.d.ts +2 -0
  294. package/src/generators/slice/local-source-identity.js +31 -0
  295. package/src/generators/slice/persistence-contract.d.ts +14 -0
  296. package/src/generators/slice/persistence-contract.js +32 -0
  297. package/src/generators/slice/slice.js +61 -50
  298. package/src/generators/sql/schema.d.ts +7 -0
  299. package/src/generators/sql/schema.json +38 -0
  300. package/src/generators/sql/sql.d.ts +6 -0
  301. package/src/generators/sql/sql.js +53 -0
  302. package/src/generators/upgrade/sql-seeded-reconciliation.d.ts +2 -0
  303. package/src/generators/upgrade/sql-seeded-reconciliation.js +170 -0
  304. package/src/generators/upgrade/upgrade.d.ts +15 -1
  305. package/src/generators/upgrade/upgrade.js +435 -44
  306. package/src/generators/workload/frozen-container.d.ts +1 -1
  307. package/src/generators/workload/performance-registration.d.ts +1 -0
  308. package/src/generators/workload/performance-registration.js +9 -11
  309. package/src/generators/workload/service-performance-files.d.ts +1 -0
  310. package/src/generators/workload/service-performance-files.js +27 -7
  311. package/src/generators/workload/workload.d.ts +1 -1
  312. package/src/generators/workload/workload.js +105 -29
  313. package/src/git-policy/action-pins.d.ts +5 -0
  314. package/src/git-policy/action-pins.js +5 -0
  315. package/src/git-policy/github-bootstrap.d.ts +48 -8
  316. package/src/git-policy/github-bootstrap.js +344 -176
  317. package/src/git-policy/routing.d.ts +7 -1
  318. package/src/git-policy/routing.js +62 -1
  319. package/src/index.d.ts +1 -0
  320. package/src/index.js +1 -0
  321. package/src/infrastructure-change/local-capability.d.ts +73 -0
  322. package/src/infrastructure-change/local-capability.js +114 -0
  323. package/src/infrastructure-change/model.d.ts +25 -4
  324. package/src/infrastructure-change/model.js +1 -0
  325. package/src/infrastructure-change/normalize.d.ts +4 -0
  326. package/src/infrastructure-change/normalize.js +47 -7
  327. package/src/infrastructure-change/plan.d.ts +30 -3
  328. package/src/infrastructure-change/plan.js +323 -13
  329. package/src/infrastructure-change/risk.d.ts +3 -1
  330. package/src/infrastructure-change/risk.js +96 -16
  331. package/src/local-dev/browser-verification.d.ts +37 -0
  332. package/src/local-dev/browser-verification.js +281 -0
  333. package/src/local-dev/contracts.d.ts +10 -1
  334. package/src/local-dev/contracts.js +114 -13
  335. package/src/local-dev/dev-command-core.js +24 -5
  336. package/src/local-dev/docker-credential-health.d.ts +14 -0
  337. package/src/local-dev/docker-credential-health.js +94 -0
  338. package/src/local-dev/environment-inspector.d.ts +7 -0
  339. package/src/local-dev/environment-inspector.js +42 -15
  340. package/src/local-dev/environment-state.js +2 -2
  341. package/src/local-dev/errors.d.ts +1 -1
  342. package/src/local-dev/errors.js +2 -0
  343. package/src/local-dev/fleet-client.js +2 -2
  344. package/src/local-dev/fleet-configuration.js +57 -25
  345. package/src/local-dev/fleet-inventory.js +58 -22
  346. package/src/local-dev/fleet-ipc.d.ts +7 -0
  347. package/src/local-dev/fleet-ipc.js +14 -2
  348. package/src/local-dev/fleet-launcher.d.ts +6 -0
  349. package/src/local-dev/fleet-launcher.js +16 -4
  350. package/src/local-dev/fleet-logs.js +50 -22
  351. package/src/local-dev/fleet-process.d.ts +20 -1
  352. package/src/local-dev/fleet-process.js +197 -41
  353. package/src/local-dev/fleet-runner-evidence.d.ts +5 -0
  354. package/src/local-dev/fleet-runner-evidence.js +56 -0
  355. package/src/local-dev/fleet-runtime.js +268 -229
  356. package/src/local-dev/fleet-session.d.ts +2 -0
  357. package/src/local-dev/fleet-session.js +28 -11
  358. package/src/local-dev/fleet-slice-runner-daemon.js +4 -0
  359. package/src/local-dev/fleet-state.d.ts +5 -2
  360. package/src/local-dev/fleet-state.js +25 -4
  361. package/src/local-dev/fleet-supervisor.d.ts +3 -0
  362. package/src/local-dev/fleet-supervisor.js +66 -11
  363. package/src/local-dev/fleet-web-start.d.ts +12 -0
  364. package/src/local-dev/fleet-web-start.js +48 -0
  365. package/src/local-dev/identity.js +3 -14
  366. package/src/local-dev/index.d.ts +1 -0
  367. package/src/local-dev/index.js +1 -0
  368. package/src/local-dev/native-completion.d.ts +9 -0
  369. package/src/local-dev/native-completion.js +130 -0
  370. package/src/local-dev/nx-workspace.d.ts +4 -2
  371. package/src/local-dev/nx-workspace.js +11 -6
  372. package/src/local-dev/personal-stage.d.ts +4 -5
  373. package/src/local-dev/personal-stage.js +6 -54
  374. package/src/local-dev/port-claims.d.ts +13 -0
  375. package/src/local-dev/port-claims.js +46 -0
  376. package/src/local-dev/ports.d.ts +14 -2
  377. package/src/local-dev/ports.js +78 -3
  378. package/src/local-dev/private-state.d.ts +11 -0
  379. package/src/local-dev/private-state.js +146 -40
  380. package/src/local-dev/process-group.d.ts +4 -0
  381. package/src/local-dev/process-group.js +29 -0
  382. package/src/local-dev/process-identity.d.ts +8 -0
  383. package/src/local-dev/process-identity.js +31 -1
  384. package/src/local-dev/proxy.js +11 -12
  385. package/src/local-dev/runtime-paths.js +1 -1
  386. package/src/local-dev/slice-claim.js +1 -1
  387. package/src/local-dev/slice-runner.d.ts +15 -6
  388. package/src/local-dev/slice-runner.js +189 -54
  389. package/src/local-dev/task-attachment.d.ts +17 -0
  390. package/src/local-dev/task-attachment.js +73 -0
  391. package/src/local-dev/task-authority.d.ts +10 -0
  392. package/src/local-dev/task-authority.js +27 -0
  393. package/src/local-dev/task-identity.d.ts +33 -0
  394. package/src/local-dev/task-identity.js +418 -0
  395. package/src/local-dev/task-native-writer.d.ts +18 -0
  396. package/src/local-dev/task-native-writer.js +329 -0
  397. package/src/local-dev/tls.js +8 -2
  398. package/src/local-dev/web-launch.d.ts +9 -0
  399. package/src/local-dev/web-launch.js +130 -0
  400. package/src/ownership/composition.d.ts +2 -1
  401. package/src/ownership/composition.js +1 -0
  402. package/src/performance/cost-collection.d.ts +1 -1
  403. package/src/performance/cost-collection.js +4 -2
  404. package/src/performance/cost-controls.d.ts +8 -4
  405. package/src/performance/cost-controls.js +39 -28
  406. package/src/performance/evidence.d.ts +1 -1
  407. package/src/performance/evidence.js +5 -3
  408. package/src/performance/load-safety.js +5 -3
  409. package/src/performance/model.d.ts +4 -4
  410. package/src/performance/model.js +4 -5
  411. package/src/performance/scenario.d.ts +22 -0
  412. package/src/performance/scenario.js +100 -92
  413. package/src/release/index.d.ts +0 -2
  414. package/src/release/index.js +0 -2
  415. package/src/release/source-authority.d.ts +5 -5
  416. package/src/release/source-authority.js +141 -124
  417. package/src/repository-policy/aurora-preflight.d.ts +1 -1
  418. package/src/repository-policy/aurora-preflight.js +4 -1
  419. package/src/repository-policy/capacity-migration.d.ts +3 -0
  420. package/src/repository-policy/capacity-migration.js +21 -0
  421. package/src/repository-policy/deployment-plan.d.ts +4 -1
  422. package/src/repository-policy/deployment-plan.js +9 -0
  423. package/src/repository-policy/foundation-profile.js +10 -1
  424. package/src/repository-policy/policy.d.ts +57 -3
  425. package/src/repository-policy/policy.js +497 -11
  426. package/src/schemas/blueprint-v1.json +5 -4
  427. package/src/schemas/foundation-requirement-v1.json +5 -4
  428. package/src/schemas/performance-policy-v1.json +3 -3
  429. package/src/schemas/repository-policy-v1.json +130 -6
  430. package/src/schemas/testing-evidence-result-v1.json +1 -1
  431. package/src/secrets/adapters.d.ts +7 -2
  432. package/src/secrets/adapters.js +34 -3
  433. package/src/secrets/aws-control-plane.d.ts +10 -1
  434. package/src/secrets/aws-control-plane.js +67 -0
  435. package/src/secrets/generated-aws-control-plane.d.ts +23 -0
  436. package/src/secrets/generated-aws-control-plane.js +259 -0
  437. package/src/secrets/metadata.js +38 -10
  438. package/src/secrets/model.d.ts +8 -1
  439. package/src/secrets/model.js +24 -3
  440. package/src/secrets/operator.d.ts +91 -3
  441. package/src/secrets/operator.js +463 -13
  442. package/src/secrets/personal-command.d.ts +18 -2
  443. package/src/secrets/personal-command.js +66 -12
  444. package/src/secrets/registry.d.ts +2 -1
  445. package/src/secrets/registry.js +18 -1
  446. package/src/secrets/verification.js +15 -2
  447. package/src/secrets/workload-access.js +10 -2
  448. package/src/sst-environment-source-format.d.ts +6 -0
  449. package/src/sst-environment-source-format.js +199 -0
  450. package/src/supply-chain/action-pins.js +61 -7
  451. package/src/supply-chain/blueprint-trust-git.js +85 -24
  452. package/src/supply-chain/blueprint-verification-authority.js +55 -3
  453. package/src/supply-chain/build-output-bounds.d.ts +2 -0
  454. package/src/supply-chain/build-output-bounds.js +12 -0
  455. package/src/supply-chain/build-output.d.ts +7 -1
  456. package/src/supply-chain/build-output.js +177 -24
  457. package/src/supply-chain/ecr-scanning.d.ts +5 -3
  458. package/src/supply-chain/ecr-scanning.js +11 -7
  459. package/src/supply-chain/ecs-deployed-image.d.ts +5 -3
  460. package/src/supply-chain/ecs-deployed-image.js +6 -4
  461. package/src/supply-chain/generated-blueprint-trust-command.js +18 -1
  462. package/src/supply-chain/generated-command.js +1 -0
  463. package/src/supply-chain/generated-container-release-command.js +1285 -124
  464. package/src/supply-chain/generated-ecr-collector-command.d.ts +0 -1
  465. package/src/supply-chain/generated-ecr-collector-command.js +200 -93
  466. package/src/supply-chain/generated-files.js +25 -14
  467. package/src/supply-chain/generated-scanner-command.js +2 -1
  468. package/src/supply-chain/index.d.ts +1 -1
  469. package/src/supply-chain/index.js +2 -1
  470. package/src/supply-chain/sbom.d.ts +1 -1
  471. package/src/supply-chain/sbom.js +30 -5
  472. package/src/supply-chain/scanner.js +3 -0
  473. package/src/testing-evidence/fixture-scope.d.ts +2 -1
  474. package/src/testing-evidence/fixture-scope.js +4 -2
  475. package/src/testing-evidence/result.d.ts +2 -1
  476. package/src/testing-evidence/result.js +18 -4
  477. package/src/workload-roles/account-baseline.d.ts +11 -0
  478. package/src/workload-roles/account-baseline.js +253 -16
  479. package/src/workload-roles/authority.d.ts +3 -1
  480. package/src/workload-roles/authority.js +48 -2
  481. package/src/workload-roles/aws-s3-state.js +27 -0
  482. package/src/workload-roles/boundary.d.ts +2 -1
  483. package/src/workload-roles/boundary.js +9 -2
  484. package/src/workload-roles/handler.js +52 -24
  485. package/src/workload-roles/index.d.ts +10 -17
  486. package/src/workload-roles/index.js +5 -18
  487. package/src/workload-roles/injection.js +1 -1
  488. package/src/workload-roles/provisioner.js +12 -4
  489. package/src/workload-roles/registration.d.ts +1 -0
  490. package/src/workload-roles/registration.js +5 -0
  491. package/src/workload-roles/stage-boundary.js +1 -1
  492. package/src/workload-roles/standard-slice.js +3 -3
  493. package/src/workspace-deployment/affected-lifecycle.d.ts +53 -0
  494. package/src/workspace-deployment/affected-lifecycle.js +84 -0
  495. package/src/workspace-deployment/identity.d.ts +3 -0
  496. package/src/workspace-deployment/identity.js +22 -0
  497. package/src/workspace-deployment/index.d.ts +4 -0
  498. package/src/workspace-deployment/index.js +8 -0
  499. package/src/workspace-deployment/migration-evidence.d.ts +29 -0
  500. package/src/workspace-deployment/migration-evidence.js +83 -0
  501. package/src/workspace-deployment/migration.d.ts +27 -0
  502. package/src/workspace-deployment/migration.js +109 -0
  503. package/src/workspace-deployment/ownership.d.ts +81 -0
  504. package/src/workspace-deployment/ownership.js +381 -0
  505. package/src/api-contract/baseline-update.js.map +0 -1
  506. package/src/api-contract/compatibility.js.map +0 -1
  507. package/src/api-contract/contract-graph.js.map +0 -1
  508. package/src/api-contract/evidence.js.map +0 -1
  509. package/src/api-contract/index.js.map +0 -1
  510. package/src/api-contract/model.fixtures.d.ts +0 -2
  511. package/src/api-contract/model.fixtures.js +0 -81
  512. package/src/api-contract/model.fixtures.js.map +0 -1
  513. package/src/api-contract/model.js.map +0 -1
  514. package/src/api-contract/rollout.js.map +0 -1
  515. package/src/authorization/compatibility.js.map +0 -1
  516. package/src/authorization/index.js.map +0 -1
  517. package/src/blueprints/execution.js.map +0 -1
  518. package/src/blueprints/external-sandbox.js.map +0 -1
  519. package/src/blueprints/index.js.map +0 -1
  520. package/src/blueprints/inputs.js.map +0 -1
  521. package/src/blueprints/manifest.js.map +0 -1
  522. package/src/blueprints/resolver.js.map +0 -1
  523. package/src/blueprints/transitions.js.map +0 -1
  524. package/src/cli/agent-command.js.map +0 -1
  525. package/src/cli/agent-runtime.js.map +0 -1
  526. package/src/cli/blueprint-authority.js.map +0 -1
  527. package/src/cli/blueprint-command.js.map +0 -1
  528. package/src/cli/bootstrap.js.map +0 -1
  529. package/src/cli/dev-command.js.map +0 -1
  530. package/src/cli/main.js.map +0 -1
  531. package/src/cli/output.js.map +0 -1
  532. package/src/cli/personal-secret-runtime.js.map +0 -1
  533. package/src/cli/secret-terminal.js.map +0 -1
  534. package/src/container-images.js.map +0 -1
  535. package/src/data-lifecycle/index.js.map +0 -1
  536. package/src/data-lifecycle/model.fixtures.d.ts +0 -2
  537. package/src/data-lifecycle/model.fixtures.js +0 -244
  538. package/src/data-lifecycle/model.fixtures.js.map +0 -1
  539. package/src/data-lifecycle/model.js.map +0 -1
  540. package/src/data-lifecycle/sensitive-artifacts.js.map +0 -1
  541. package/src/data-lifecycle/validation.js.map +0 -1
  542. package/src/database-retirement/contract.js.map +0 -1
  543. package/src/database-retirement/index.js.map +0 -1
  544. package/src/deterministic-order.js.map +0 -1
  545. package/src/developer-agent/context.js.map +0 -1
  546. package/src/developer-agent/generator-planning.js.map +0 -1
  547. package/src/developer-agent/generator-preview.js.map +0 -1
  548. package/src/developer-agent/git-inspection.js.map +0 -1
  549. package/src/developer-agent/handoff.js.map +0 -1
  550. package/src/developer-agent/index.js.map +0 -1
  551. package/src/developer-agent/model.js.map +0 -1
  552. package/src/developer-agent/operation-registry.js.map +0 -1
  553. package/src/developer-agent/ordering.js.map +0 -1
  554. package/src/developer-agent/planning.js.map +0 -1
  555. package/src/developer-agent/preview-changes.js.map +0 -1
  556. package/src/developer-agent/preview-source.js.map +0 -1
  557. package/src/developer-agent/validation.js.map +0 -1
  558. package/src/developer-agent/workspace-inspection.js.map +0 -1
  559. package/src/developer-agent/workspace-path.js.map +0 -1
  560. package/src/executors/blueprint-trust/blueprint-trust.js.map +0 -1
  561. package/src/executors/blueprint-verification/blueprint-verification.js.map +0 -1
  562. package/src/executors/deploy/deploy.js.map +0 -1
  563. package/src/executors/deployment-plan/deployment-plan.js.map +0 -1
  564. package/src/executors/dev/dev.js.map +0 -1
  565. package/src/executors/dev-doctor/dev-doctor.js.map +0 -1
  566. package/src/executors/dev-setup/dev-setup.js.map +0 -1
  567. package/src/executors/developer-context/developer-context.js.map +0 -1
  568. package/src/executors/fleet/fleet.js.map +0 -1
  569. package/src/executors/foundation-preflight/foundation-preflight.js.map +0 -1
  570. package/src/executors/foundation-profile-preflight/foundation-profile-preflight.js.map +0 -1
  571. package/src/executors/foundation-remove-preflight/foundation-remove-preflight.js.map +0 -1
  572. package/src/executors/github-bootstrap/github-bootstrap.js.map +0 -1
  573. package/src/executors/infrastructure-plan/infrastructure-plan.js.map +0 -1
  574. package/src/executors/refresh/refresh.js.map +0 -1
  575. package/src/executors/remove/remove.js.map +0 -1
  576. package/src/executors/secret-guardrails/secret-guardrails.js.map +0 -1
  577. package/src/executors/secret-metadata/secret-metadata.js.map +0 -1
  578. package/src/executors/sst-lifecycle/logging-preflight.js.map +0 -1
  579. package/src/executors/sst-lifecycle/run-sst-command.js.map +0 -1
  580. package/src/executors/sst-lifecycle/secret-preflight.js.map +0 -1
  581. package/src/executors/unlock/unlock.js.map +0 -1
  582. package/src/executors/work-markers/work-markers.js.map +0 -1
  583. package/src/foundation/discovery.js.map +0 -1
  584. package/src/foundation/index.js.map +0 -1
  585. package/src/foundation/local-database-contract.js.map +0 -1
  586. package/src/foundation/logging-preflight.js.map +0 -1
  587. package/src/foundation/logging.js.map +0 -1
  588. package/src/foundation/preflight.js.map +0 -1
  589. package/src/foundation/provision-database-bindings.js.map +0 -1
  590. package/src/foundation/provision-logical-database.js.map +0 -1
  591. package/src/foundation/requirements.js.map +0 -1
  592. package/src/foundation/router-origin-proof.js.map +0 -1
  593. package/src/foundation/stages.js.map +0 -1
  594. package/src/frontend-security/index.js.map +0 -1
  595. package/src/frontend-security/security-headers.js.map +0 -1
  596. package/src/generators/feature-registry.js.map +0 -1
  597. package/src/generators/function/function.js.map +0 -1
  598. package/src/generators/job/job.js.map +0 -1
  599. package/src/generators/logging-registry.js.map +0 -1
  600. package/src/generators/post-generation-install.js.map +0 -1
  601. package/src/generators/preset/generator.js.map +0 -1
  602. package/src/generators/preset/tool-versions.js.map +0 -1
  603. package/src/generators/preset/workspace-files-developer-agent.js.map +0 -1
  604. package/src/generators/preset/workspace-files-foundation-workload-roles.js.map +0 -1
  605. package/src/generators/preset/workspace-files-performance-cost.js.map +0 -1
  606. package/src/generators/preset/workspace-files-performance-load.js.map +0 -1
  607. package/src/generators/preset/workspace-files-workload-role-baseline.js.map +0 -1
  608. package/src/generators/preset/workspace-files.js.map +0 -1
  609. package/src/generators/repository-policy/repository-policy.js.map +0 -1
  610. package/src/generators/service/service.js.map +0 -1
  611. package/src/generators/slice/built-in-plan-api-contract-files.js.map +0 -1
  612. package/src/generators/slice/built-in-plan-api-files.js.map +0 -1
  613. package/src/generators/slice/built-in-plan-async-work-files.js.map +0 -1
  614. package/src/generators/slice/built-in-plan-authorization-files.js.map +0 -1
  615. package/src/generators/slice/built-in-plan-authorization-infrastructure-files.js.map +0 -1
  616. package/src/generators/slice/built-in-plan-data-deletion-files.js.map +0 -1
  617. package/src/generators/slice/built-in-plan-data-export-infrastructure-files.js.map +0 -1
  618. package/src/generators/slice/built-in-plan-data-files.js.map +0 -1
  619. package/src/generators/slice/built-in-plan-data-lifecycle-files.js.map +0 -1
  620. package/src/generators/slice/built-in-plan-data-lifecycle-runtime-files.js.map +0 -1
  621. package/src/generators/slice/built-in-plan-data-retention-job-files.js.map +0 -1
  622. package/src/generators/slice/built-in-plan-data-retention-launcher-files.js.map +0 -1
  623. package/src/generators/slice/built-in-plan-data-rights-files.js.map +0 -1
  624. package/src/generators/slice/built-in-plan-database-lifecycle-files.js.map +0 -1
  625. package/src/generators/slice/built-in-plan-database-retirement-rehearsal-files.js.map +0 -1
  626. package/src/generators/slice/built-in-plan-foundation-files.js.map +0 -1
  627. package/src/generators/slice/built-in-plan-frontend-files.js.map +0 -1
  628. package/src/generators/slice/built-in-plan-infrastructure-files.js.map +0 -1
  629. package/src/generators/slice/built-in-plan-infrastructure-safety-files.js.map +0 -1
  630. package/src/generators/slice/built-in-plan-metadata-files.js.map +0 -1
  631. package/src/generators/slice/built-in-plan-operations-files.js.map +0 -1
  632. package/src/generators/slice/built-in-plan-performance-files.js.map +0 -1
  633. package/src/generators/slice/built-in-plan-project-files.js.map +0 -1
  634. package/src/generators/slice/built-in-plan-release-files.js.map +0 -1
  635. package/src/generators/slice/built-in-plan-reliability-files.js.map +0 -1
  636. package/src/generators/slice/built-in-plan-secret-runtime-files.js.map +0 -1
  637. package/src/generators/slice/built-in-plan-shared.js.map +0 -1
  638. package/src/generators/slice/built-in-plan-testing-files.js.map +0 -1
  639. package/src/generators/slice/built-in-plan-verification-files.js.map +0 -1
  640. package/src/generators/slice/built-in-plan-web-files.js.map +0 -1
  641. package/src/generators/slice/built-in-plan-workload-role-files.js.map +0 -1
  642. package/src/generators/slice/built-in-plan.js.map +0 -1
  643. package/src/generators/slice/slice.js.map +0 -1
  644. package/src/generators/upgrade/upgrade.js.map +0 -1
  645. package/src/generators/workload/frozen-container.js.map +0 -1
  646. package/src/generators/workload/performance-registration.js.map +0 -1
  647. package/src/generators/workload/reliability-registration.js.map +0 -1
  648. package/src/generators/workload/service-performance-files.js.map +0 -1
  649. package/src/generators/workload/workload.js.map +0 -1
  650. package/src/git-policy/action-pins.js.map +0 -1
  651. package/src/git-policy/github-bootstrap.js.map +0 -1
  652. package/src/git-policy/index.js.map +0 -1
  653. package/src/git-policy/routing.js.map +0 -1
  654. package/src/index.js.map +0 -1
  655. package/src/infrastructure-change/convergence.js.map +0 -1
  656. package/src/infrastructure-change/drift.js.map +0 -1
  657. package/src/infrastructure-change/index.js.map +0 -1
  658. package/src/infrastructure-change/model.js.map +0 -1
  659. package/src/infrastructure-change/normalize.js.map +0 -1
  660. package/src/infrastructure-change/plan.js.map +0 -1
  661. package/src/infrastructure-change/risk.js.map +0 -1
  662. package/src/infrastructure-change/summary.js.map +0 -1
  663. package/src/local-dev/context-discovery.js.map +0 -1
  664. package/src/local-dev/contract-validation.js.map +0 -1
  665. package/src/local-dev/contracts.js.map +0 -1
  666. package/src/local-dev/control-contract.js.map +0 -1
  667. package/src/local-dev/dev-command-core.js.map +0 -1
  668. package/src/local-dev/effects.js.map +0 -1
  669. package/src/local-dev/environment-inspector.js.map +0 -1
  670. package/src/local-dev/environment-state.js.map +0 -1
  671. package/src/local-dev/errors.js.map +0 -1
  672. package/src/local-dev/fleet-client.js.map +0 -1
  673. package/src/local-dev/fleet-configuration.js.map +0 -1
  674. package/src/local-dev/fleet-inventory.js.map +0 -1
  675. package/src/local-dev/fleet-ipc.js.map +0 -1
  676. package/src/local-dev/fleet-launcher.js.map +0 -1
  677. package/src/local-dev/fleet-lock.js.map +0 -1
  678. package/src/local-dev/fleet-logs.js.map +0 -1
  679. package/src/local-dev/fleet-process.js.map +0 -1
  680. package/src/local-dev/fleet-runtime.js.map +0 -1
  681. package/src/local-dev/fleet-session.js.map +0 -1
  682. package/src/local-dev/fleet-slice-runner-daemon.js.map +0 -1
  683. package/src/local-dev/fleet-startup.js.map +0 -1
  684. package/src/local-dev/fleet-state.js.map +0 -1
  685. package/src/local-dev/fleet-supervisor-daemon.js.map +0 -1
  686. package/src/local-dev/fleet-supervisor.js.map +0 -1
  687. package/src/local-dev/gateway-daemon.js.map +0 -1
  688. package/src/local-dev/gateway.js.map +0 -1
  689. package/src/local-dev/identity.js.map +0 -1
  690. package/src/local-dev/index.js.map +0 -1
  691. package/src/local-dev/local-context.js.map +0 -1
  692. package/src/local-dev/nx-workspace.js.map +0 -1
  693. package/src/local-dev/personal-secret-ledger.js.map +0 -1
  694. package/src/local-dev/personal-stage.js.map +0 -1
  695. package/src/local-dev/portless.js.map +0 -1
  696. package/src/local-dev/ports.js.map +0 -1
  697. package/src/local-dev/private-state.js.map +0 -1
  698. package/src/local-dev/process-identity.js.map +0 -1
  699. package/src/local-dev/proxy.js.map +0 -1
  700. package/src/local-dev/runtime-paths.js.map +0 -1
  701. package/src/local-dev/secret-slice-claim.js.map +0 -1
  702. package/src/local-dev/slice-claim.js.map +0 -1
  703. package/src/local-dev/slice-runner.js.map +0 -1
  704. package/src/local-dev/tls.js.map +0 -1
  705. package/src/ownership/composition.js.map +0 -1
  706. package/src/ownership/foundation-record.js.map +0 -1
  707. package/src/ownership/hash.js.map +0 -1
  708. package/src/ownership/index.js.map +0 -1
  709. package/src/ownership/json-ownership.js.map +0 -1
  710. package/src/ownership/record.js.map +0 -1
  711. package/src/ownership/workspace-record.js.map +0 -1
  712. package/src/performance/cost-collection.js.map +0 -1
  713. package/src/performance/cost-controls.js.map +0 -1
  714. package/src/performance/evidence.js.map +0 -1
  715. package/src/performance/index.js.map +0 -1
  716. package/src/performance/load-safety.js.map +0 -1
  717. package/src/performance/model.js.map +0 -1
  718. package/src/performance/pr-artifact-budget.js.map +0 -1
  719. package/src/performance/runner.js.map +0 -1
  720. package/src/performance/scenario-endpoint.js.map +0 -1
  721. package/src/performance/scenario.js.map +0 -1
  722. package/src/performance/test-fixtures.d.ts +0 -7
  723. package/src/performance/test-fixtures.js +0 -169
  724. package/src/performance/test-fixtures.js.map +0 -1
  725. package/src/performance/unit-cost.js.map +0 -1
  726. package/src/release/index.js.map +0 -1
  727. package/src/release/lifecycle.js.map +0 -1
  728. package/src/release/manifest.js.map +0 -1
  729. package/src/release/rollout.js.map +0 -1
  730. package/src/release/source-authority.js.map +0 -1
  731. package/src/repository-policy/aurora-preflight.js.map +0 -1
  732. package/src/repository-policy/deployment-plan.js.map +0 -1
  733. package/src/repository-policy/foundation-profile.js.map +0 -1
  734. package/src/repository-policy/index.js.map +0 -1
  735. package/src/repository-policy/policy.js.map +0 -1
  736. package/src/secrets/adapters.js.map +0 -1
  737. package/src/secrets/aws-control-plane.js.map +0 -1
  738. package/src/secrets/declaration-verification.js.map +0 -1
  739. package/src/secrets/guardrails.js.map +0 -1
  740. package/src/secrets/index.js.map +0 -1
  741. package/src/secrets/metadata.js.map +0 -1
  742. package/src/secrets/model.js.map +0 -1
  743. package/src/secrets/operator.js.map +0 -1
  744. package/src/secrets/permissions.js.map +0 -1
  745. package/src/secrets/personal-command.js.map +0 -1
  746. package/src/secrets/personal-ledger.js.map +0 -1
  747. package/src/secrets/registry.js.map +0 -1
  748. package/src/secrets/verification.js.map +0 -1
  749. package/src/secrets/workload-access.js.map +0 -1
  750. package/src/supply-chain/action-pins.js.map +0 -1
  751. package/src/supply-chain/blueprint-trust-git.js.map +0 -1
  752. package/src/supply-chain/blueprint-trust-process.js.map +0 -1
  753. package/src/supply-chain/blueprint-trust.js.map +0 -1
  754. package/src/supply-chain/blueprint-verification-authority.js.map +0 -1
  755. package/src/supply-chain/blueprint-verification-execution.js.map +0 -1
  756. package/src/supply-chain/build-output.js.map +0 -1
  757. package/src/supply-chain/common.js.map +0 -1
  758. package/src/supply-chain/container-artifact-contract.js.map +0 -1
  759. package/src/supply-chain/container-release.js.map +0 -1
  760. package/src/supply-chain/ecr-applicability.js.map +0 -1
  761. package/src/supply-chain/ecr-scanning.js.map +0 -1
  762. package/src/supply-chain/ecs-deployed-image.js.map +0 -1
  763. package/src/supply-chain/errors.js.map +0 -1
  764. package/src/supply-chain/generated-blueprint-trust-command.js.map +0 -1
  765. package/src/supply-chain/generated-command.js.map +0 -1
  766. package/src/supply-chain/generated-container-release-command.js.map +0 -1
  767. package/src/supply-chain/generated-ecr-collector-command.js.map +0 -1
  768. package/src/supply-chain/generated-files.js.map +0 -1
  769. package/src/supply-chain/generated-scanner-command.js.map +0 -1
  770. package/src/supply-chain/index.js.map +0 -1
  771. package/src/supply-chain/inventory.js.map +0 -1
  772. package/src/supply-chain/license-expression.js.map +0 -1
  773. package/src/supply-chain/pnpm-toolchain-authority.js.map +0 -1
  774. package/src/supply-chain/policy.js.map +0 -1
  775. package/src/supply-chain/publication.js.map +0 -1
  776. package/src/supply-chain/sbom.js.map +0 -1
  777. package/src/supply-chain/scanner.js.map +0 -1
  778. package/src/supply-chain/vendored-source.js.map +0 -1
  779. package/src/testing-evidence/fixture-scope.js.map +0 -1
  780. package/src/testing-evidence/index.js.map +0 -1
  781. package/src/testing-evidence/quarantine.js.map +0 -1
  782. package/src/testing-evidence/result.js.map +0 -1
  783. package/src/testing-evidence/runner-adapter.js.map +0 -1
  784. package/src/testing-evidence/sensitive-artifacts.js.map +0 -1
  785. package/src/validation.js.map +0 -1
  786. package/src/work-markers/index.js.map +0 -1
  787. package/src/work-markers/work-markers.js.map +0 -1
  788. package/src/workload-roles/account-baseline.js.map +0 -1
  789. package/src/workload-roles/authority.js.map +0 -1
  790. package/src/workload-roles/aws-adapter.js.map +0 -1
  791. package/src/workload-roles/aws-internal-handler.js.map +0 -1
  792. package/src/workload-roles/aws-s3-state.js.map +0 -1
  793. package/src/workload-roles/aws-sdk-control-plane.js.map +0 -1
  794. package/src/workload-roles/boundary.js.map +0 -1
  795. package/src/workload-roles/convergence.js.map +0 -1
  796. package/src/workload-roles/effective-policy.js.map +0 -1
  797. package/src/workload-roles/handler.js.map +0 -1
  798. package/src/workload-roles/history.js.map +0 -1
  799. package/src/workload-roles/index.js.map +0 -1
  800. package/src/workload-roles/injection.js.map +0 -1
  801. package/src/workload-roles/manifest.js.map +0 -1
  802. package/src/workload-roles/provisioner.js.map +0 -1
  803. package/src/workload-roles/registration.js.map +0 -1
  804. package/src/workload-roles/stage-boundary.js.map +0 -1
  805. package/src/workload-roles/standard-slice.js.map +0 -1
@@ -0,0 +1,2507 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.EBK_RECORD_MAIN_TOKEN_SECRET = exports.THIN_DEPLOY_WORKFLOW_PATH = void 0;
4
+ exports.renderThinDeployVerifyCaller = renderThinDeployVerifyCaller;
5
+ exports.protectedBrowserInstallScript = protectedBrowserInstallScript;
6
+ exports.machineCredentialRequestScript = machineCredentialRequestScript;
7
+ exports.exactStageDeployerGuardScript = exactStageDeployerGuardScript;
8
+ exports.canonicalStageAuthorityDerivationScript = canonicalStageAuthorityDerivationScript;
9
+ exports.exactStagingPromotionPullGuardScript = exactStagingPromotionPullGuardScript;
10
+ exports.stagingPromotionReceiptValidationScript = stagingPromotionReceiptValidationScript;
11
+ exports.restoredCandidateValidationScript = restoredCandidateValidationScript;
12
+ exports.stagingPromotionSealScript = stagingPromotionSealScript;
13
+ exports.postdeploySupplyChainBindingScript = postdeploySupplyChainBindingScript;
14
+ exports.renderThinDeployWorkflowFilePlan = renderThinDeployWorkflowFilePlan;
15
+ exports.THIN_DEPLOY_WORKFLOW_PATH = '.github/workflows/deploy.yml';
16
+ /** The production environment secret that lets record-main fast-forward main. */
17
+ exports.EBK_RECORD_MAIN_TOKEN_SECRET = 'EBK_RECORD_MAIN_TOKEN';
18
+ const EXACT_SEMVER = /^\d+\.\d+\.\d+$/;
19
+ const WORKSPACE_NAME = /^[a-z0-9][a-z0-9-]{0,62}$/;
20
+ const ACTION_PIN = /^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+@[a-f0-9]{40}(?: # [^\r\n]+)?$/;
21
+ function assertOptions(options) {
22
+ if (options.productionApprovalMode === 'trunk-promotion' &&
23
+ (options.deploymentProfile === 'starter' ||
24
+ options.deliveryLane === 'trunk')) {
25
+ throw new Error('trunk-promotion requires an isolated deployment profile on the protected pair.');
26
+ }
27
+ if (!WORKSPACE_NAME.test(options.name)) {
28
+ throw new Error('Workspace name must be a bounded lowercase slug.');
29
+ }
30
+ for (const [name, value] of Object.entries(options.actionPins)) {
31
+ if (!ACTION_PIN.test(value)) {
32
+ throw new Error(`${name} must be an exact 40-character action pin.`);
33
+ }
34
+ }
35
+ for (const [name, value] of Object.entries(options.toolVersions)) {
36
+ if (!EXACT_SEMVER.test(value)) {
37
+ throw new Error(`${name} must be an exact semantic version.`);
38
+ }
39
+ }
40
+ }
41
+ function workflowCallTrigger(profile, deliveryLane = 'protected-pair') {
42
+ return profile === 'starter' && deliveryLane !== 'trunk'
43
+ ? ''
44
+ : ` workflow_call:
45
+ inputs:
46
+ source_branch:
47
+ required: true
48
+ type: string
49
+ source_commit:
50
+ required: true
51
+ type: string
52
+ source_ref:
53
+ required: true
54
+ type: string
55
+ verify_run_attempt:
56
+ required: true
57
+ type: string
58
+ verify_run_id:
59
+ required: true
60
+ type: string
61
+ `;
62
+ }
63
+ function renderThinDeployVerifyCaller(profile, deliveryLane = 'protected-pair', productionApprovalMode = 'promotion-pr') {
64
+ const trunk = deliveryLane === 'trunk';
65
+ if (profile === 'starter' && !trunk)
66
+ return '';
67
+ // GitHub validates every nested deploy.yml job against this caller's
68
+ // permissions on every event, so the ceiling must admit the managed
69
+ // rehearsal, which pushes its sealed receipt branch and opens the pull
70
+ // request with the workflow token (isolated profiles only).
71
+ const nestedWrite = profile !== 'starter';
72
+ // Trunk lane: every merged main push is a production delivery.
73
+ // Trunk promotion: production deploys from the develop run itself and
74
+ // record-main fast-forwards main afterwards, so a main push only verifies.
75
+ const protectedBranchCondition = trunk
76
+ ? "github.ref == 'refs/heads/main'"
77
+ : productionApprovalMode === 'trunk-promotion'
78
+ ? "github.ref == 'refs/heads/develop'"
79
+ : "(github.ref == 'refs/heads/develop' || github.ref == 'refs/heads/main')";
80
+ return `
81
+
82
+ deploy:
83
+ name: deploy exact verified source
84
+ needs: [acceptance, release-candidate]
85
+ if: >-
86
+ always() &&
87
+ github.event_name == 'push' &&
88
+ ${protectedBranchCondition} &&
89
+ needs.acceptance.result == 'success' &&
90
+ needs.acceptance.outputs.history_only != 'true' &&
91
+ (needs.release-candidate.result == 'success' ||
92
+ (github.ref == 'refs/heads/main' && needs.release-candidate.result == 'skipped'))
93
+ # Ceiling for the nested deploy.yml jobs; each nested job declares its
94
+ # own least-privilege set. Isolated profiles admit the rehearse job's
95
+ # receipt push and pull request (EBK-INT-2026-080).
96
+ permissions:
97
+ actions: read
98
+ contents: ${nestedWrite ? 'write' : 'read'}
99
+ id-token: write${nestedWrite ? '\n pull-requests: write' : ''}
100
+ uses: ./.github/workflows/deploy.yml
101
+ with:
102
+ source_branch: \${{ github.ref_name }}
103
+ source_commit: \${{ github.sha }}
104
+ source_ref: \${{ github.ref }}
105
+ verify_run_attempt: \${{ github.run_attempt }}
106
+ verify_run_id: \${{ github.run_id }}
107
+ # The reusable workflow reads the production environment secret
108
+ # AWS_STAGING_PROMOTION_PULL_ROLE_ARN, which only reaches a called
109
+ # workflow when the caller inherits secrets (EBK-INT-2026-075).
110
+ secrets: inherit`;
111
+ }
112
+ function indentedScript(source, spaces) {
113
+ const prefix = ' '.repeat(spaces);
114
+ return source
115
+ .trim()
116
+ .split('\n')
117
+ .map((line) => `${prefix}${line}`)
118
+ .join('\n');
119
+ }
120
+ /** @internal Run exactly this generated script in the protected-runner fixture. */
121
+ function protectedBrowserInstallScript() {
122
+ return `set -euo pipefail
123
+ if [ -z "$EBK_SLICE_PROJECTS" ]; then exit 0; fi
124
+ case "$EBK_SLICE_PROJECTS" in ,*|*,|*,,*) echo 'Invalid smoke project inventory'; exit 1 ;; esac
125
+ IFS=',' read -r -a projects <<< "$EBK_SLICE_PROJECTS"
126
+ seen=,
127
+ for project in "\${projects[@]}"; do
128
+ case "$project" in ''|*[!A-Za-z0-9_-]*) echo 'Invalid smoke project'; exit 1 ;; esac
129
+ case "$seen" in *,"$project",*) echo 'Duplicate smoke project'; exit 1 ;; esac
130
+ seen="$seen$project,"
131
+ done
132
+ for project in "\${projects[@]}"; do
133
+ pnpm --dir "packages/$project" exec playwright install --with-deps chromium
134
+ done`;
135
+ }
136
+ /**
137
+ * @internal Run exactly this generated script in the protected-runner fixture.
138
+ *
139
+ * Reads the deploying stage's GitHub environment variables through request
140
+ * names and exports the canonical machine-credential variables only when they
141
+ * are set. An unset GitHub variable expands to '', which the generated Slice
142
+ * SST config would read as a generation rather than its blue-1 default, so the
143
+ * canonical names never reach the lifecycle when the request is empty.
144
+ */
145
+ function machineCredentialRequestScript() {
146
+ return `set -euo pipefail
147
+ generation="$EBK_REQUESTED_MACHINE_CREDENTIAL_GENERATION"
148
+ retire="$EBK_REQUESTED_MACHINE_RETIRE_PREVIOUS_GENERATION"
149
+ generation_pattern='^(blue|green)-[1-9][0-9]{0,11}$'
150
+ if [ -n "$generation" ] && ! [[ "$generation" =~ $generation_pattern ]]; then
151
+ echo 'EBK_MACHINE_CREDENTIAL_GENERATION must be blue-N or green-N, where N is 1 to 12 digits without a leading zero, or unset'
152
+ exit 1
153
+ fi
154
+ case "$retire" in
155
+ ''|false|true) ;;
156
+ *) echo 'EBK_MACHINE_RETIRE_PREVIOUS_GENERATION must be exactly true or false, or unset'; exit 1 ;;
157
+ esac
158
+ if [ "$retire" = true ]; then
159
+ [ -n "$generation" ] || {
160
+ echo 'Retiring the previous machine credential generation requires EBK_MACHINE_CREDENTIAL_GENERATION to name the current generation'
161
+ exit 1
162
+ }
163
+ if [ "$EBK_ACTION" = deploy ] && [ "$EBK_LIFECYCLE_SCOPE" != full ]; then
164
+ echo 'Retiring the previous machine credential generation requires a Deploy dispatch with lifecycle full; affected scope can skip the Slice'
165
+ exit 1
166
+ fi
167
+ fi
168
+ if [ -n "$generation" ]; then
169
+ echo "EBK_MACHINE_CREDENTIAL_GENERATION=$generation" >> "$GITHUB_ENV"
170
+ echo "Requested machine credential generation: $generation"
171
+ fi
172
+ if [ "$retire" = true ]; then
173
+ echo 'EBK_MACHINE_RETIRE_PREVIOUS_GENERATION=true' >> "$GITHUB_ENV"
174
+ echo 'Requested retirement of the previous machine credential generation'
175
+ fi`;
176
+ }
177
+ function machineCredentialRequestStep() {
178
+ return ` - name: Validate requested machine credential generation
179
+ env:
180
+ ACTIONS_ID_TOKEN_REQUEST_TOKEN: ''
181
+ ACTIONS_ID_TOKEN_REQUEST_URL: ''
182
+ EBK_REQUESTED_MACHINE_CREDENTIAL_GENERATION: \${{ vars.EBK_MACHINE_CREDENTIAL_GENERATION }}
183
+ EBK_REQUESTED_MACHINE_RETIRE_PREVIOUS_GENERATION: \${{ vars.EBK_MACHINE_RETIRE_PREVIOUS_GENERATION }}
184
+ run: |
185
+ ${indentedScript(machineCredentialRequestScript(), 10)}`;
186
+ }
187
+ /** @internal Exported for executable source-contract tests. */
188
+ function exactStageDeployerGuardScript(operation, accountVariable) {
189
+ return `read -r caller_account caller_arn <<< "$(aws sts get-caller-identity --query '[Account,Arn]' --output text)"
190
+ [ "$caller_account" = "${accountVariable}" ] || {
191
+ echo '${operation} caller account differs from committed policy'; exit 1;
192
+ }
193
+ caller_partition="\${caller_arn#arn:}"
194
+ caller_partition="\${caller_partition%%:*}"
195
+ [ "$caller_partition" = "$AWS_PARTITION" ] || {
196
+ echo '${operation} caller partition differs from committed policy'; exit 1;
197
+ }
198
+ expected_role_arn="arn:$AWS_PARTITION:iam::${accountVariable}:role/ebk/$EBK_STAGE/deployment/ebk-$EBK_STAGE-deploy"
199
+ [ "$AWS_DEPLOY_ROLE_ARN" = "$expected_role_arn" ] || {
200
+ echo '${operation} configured role is not the canonical stage deployer'; exit 1;
201
+ }
202
+ expected_caller_prefix="arn:$AWS_PARTITION:sts::${accountVariable}:assumed-role/ebk-$EBK_STAGE-deploy/"
203
+ case "$caller_arn" in
204
+ "$expected_caller_prefix"?*) ;;
205
+ *) echo '${operation} caller is not the exact stage deployer'; exit 1 ;;
206
+ esac`;
207
+ }
208
+ /** @internal Shared verbatim by every generated protected workflow. */
209
+ function canonicalStageAuthorityDerivationScript() {
210
+ return `case "$EBK_STAGE" in
211
+ development) account_id="$EBK_DEVELOPMENT_ACCOUNT" ;;
212
+ staging) account_id="$EBK_STAGING_ACCOUNT" ;;
213
+ production) account_id="$EBK_PRODUCTION_ACCOUNT" ;;
214
+ *) exit 1 ;;
215
+ esac
216
+ case "$account_id" in *[!0-9]*|'') exit 1 ;; esac
217
+ [ "\${#account_id}" -eq 12 ]
218
+ case "$EBK_REGION" in
219
+ cn-*) partition=aws-cn; audience=sts.amazonaws.com.cn ;;
220
+ us-gov-*) partition=aws-us-gov; audience=sts.amazonaws.com ;;
221
+ us-iso-*|us-isob-*|eu-isoe-*|us-isof-*)
222
+ echo 'The committed Region belongs to an unsupported AWS partition.' >&2
223
+ exit 1
224
+ ;;
225
+ *) partition=aws; audience=sts.amazonaws.com ;;
226
+ esac
227
+ role_arn="arn:$partition:iam::$account_id:role/ebk/$EBK_STAGE/deployment/ebk-$EBK_STAGE-deploy"
228
+ echo "account_id=$account_id" >> "$GITHUB_OUTPUT"
229
+ echo "audience=$audience" >> "$GITHUB_OUTPUT"
230
+ echo "partition=$partition" >> "$GITHUB_OUTPUT"
231
+ echo "role_arn=$role_arn" >> "$GITHUB_OUTPUT"`;
232
+ }
233
+ /** @internal Exported for executable source-contract tests. */
234
+ function exactStagingPromotionPullGuardScript() {
235
+ return `read -r caller_account caller_arn <<< "$(aws sts get-caller-identity --query '[Account,Arn]' --output text)"
236
+ [ "$caller_account" = "$EBK_STAGING_ACCOUNT_ID" ] || {
237
+ echo 'Staging registry caller account differs from committed policy'; exit 1;
238
+ }
239
+ caller_partition="\${caller_arn#arn:}"
240
+ caller_partition="\${caller_partition%%:*}"
241
+ [ "$caller_partition" = "$AWS_PARTITION" ] || {
242
+ echo 'Staging registry caller partition differs from committed policy'; exit 1;
243
+ }
244
+ expected_role_arn="arn:$AWS_PARTITION:iam::$EBK_STAGING_ACCOUNT_ID:role/ebk/promotion/ebk-staging-promotion-pull"
245
+ [ "$AWS_STAGING_PROMOTION_PULL_ROLE_ARN" = "$expected_role_arn" ] || {
246
+ echo 'Staging registry configured role is not the canonical promotion pull role'; exit 1;
247
+ }
248
+ expected_caller_prefix="arn:$AWS_PARTITION:sts::$EBK_STAGING_ACCOUNT_ID:assumed-role/ebk-staging-promotion-pull/"
249
+ case "$caller_arn" in
250
+ "$expected_caller_prefix"?*) ;;
251
+ *) echo 'Staging registry caller is not the exact promotion pull role'; exit 1 ;;
252
+ esac`;
253
+ }
254
+ /** @internal Shared verbatim with the generated workflow and executable tests. */
255
+ function stagingPromotionReceiptValidationScript() {
256
+ return `import { createHash } from 'node:crypto';
257
+ import { appendFileSync, readFileSync } from 'node:fs';
258
+ import { join } from 'node:path';
259
+
260
+ const commit = /^[a-f0-9]{40,64}$/;
261
+ const integer = /^[1-9][0-9]*$/;
262
+ const digest = /^sha256:[a-f0-9]{64}$/;
263
+ const required = (name, pattern) => {
264
+ const value = process.env[name]?.trim() ?? '';
265
+ if (!pattern.test(value)) throw new Error(name + ' is absent or malformed.');
266
+ return value;
267
+ };
268
+ const root = process.env.EBK_STAGING_RECEIPT_ROOT?.trim() ?? '';
269
+ if (!root) throw new Error('Staging deployment receipt root is absent.');
270
+ const expectedHasSlices = required('EBK_EXPECTED_HAS_SLICES', /^(?:true|false)$/) === 'true';
271
+ const expectedHasContainers = required('EBK_EXPECTED_HAS_CONTAINERS', /^(?:true|false)$/) === 'true';
272
+ if (expectedHasContainers && !expectedHasSlices) throw new Error('Container work requires a selected Slice.');
273
+ const sourceCommit = required('EBK_STAGING_SOURCE_COMMIT', commit);
274
+ const expectedAccount = required('EBK_EXPECTED_STAGING_ACCOUNT_ID', /^[0-9]{12}$/);
275
+ const expectedProfile = required('EBK_EXPECTED_DEPLOYMENT_PROFILE', /^(?:production-isolated|fully-isolated)$/);
276
+ const expectedRegion = required('EBK_EXPECTED_DEPLOYMENT_REGION', /^[a-z]{2}(?:-gov)?-[a-z]+(?:-[a-z]+)*-[0-9]$/);
277
+ const deploymentRunId = required('EBK_STAGING_DEPLOYMENT_RUN_ID', integer);
278
+ const deploymentRunAttempt = required('EBK_STAGING_DEPLOYMENT_RUN_ATTEMPT', integer);
279
+ const receiptArtifactId = required('EBK_STAGING_RECEIPT_ARTIFACT_ID', integer);
280
+ const receiptArtifactDigest = required('EBK_STAGING_RECEIPT_ARTIFACT_DIGEST', digest);
281
+ const receiptBytes = readFileSync(join(root, 'latest.json'));
282
+ const promotionBytes = readFileSync(join(root, 'promotion.json'));
283
+ const receipt = JSON.parse(receiptBytes.toString('utf8'));
284
+ const promotion = JSON.parse(promotionBytes.toString('utf8'));
285
+ const receiptDigest = createHash('sha256').update(receiptBytes).digest('hex');
286
+ if (
287
+ receipt?.kind !== 'ebk-deployment-receipt' ||
288
+ receipt.schemaVersion !== 1 ||
289
+ receipt.outcome !== 'succeeded' ||
290
+ receipt.authority !== 'github' ||
291
+ receipt.stage !== 'staging' ||
292
+ receipt.account !== expectedAccount ||
293
+ receipt.profile !== expectedProfile ||
294
+ receipt.region !== expectedRegion ||
295
+ receipt.source?.clean !== true ||
296
+ receipt.source?.commit !== sourceCommit ||
297
+ promotion?.kind !== 'ebk-staging-deployment-promotion' ||
298
+ promotion.schemaVersion !== 1 ||
299
+ promotion.hasSlices !== expectedHasSlices ||
300
+ typeof promotion.hasContainers !== 'boolean' ||
301
+ (promotion.hasContainers && !promotion.hasSlices) ||
302
+ (expectedHasContainers && !promotion.hasContainers) ||
303
+ promotion.stage !== 'staging' ||
304
+ promotion.account !== expectedAccount ||
305
+ promotion.deploymentProfile !== expectedProfile ||
306
+ promotion.region !== expectedRegion ||
307
+ promotion.sourceCommit !== sourceCommit ||
308
+ promotion.deployment?.runId !== deploymentRunId ||
309
+ promotion.deployment?.runAttempt !== deploymentRunAttempt ||
310
+ promotion.deployment?.receiptDigest !== receiptDigest ||
311
+ (expectedHasSlices
312
+ ? !integer.test(promotion.candidate?.artifactId ?? '') ||
313
+ !digest.test(promotion.candidate?.artifactDigest ?? '') ||
314
+ !integer.test(promotion.candidate?.producerRunId ?? '') ||
315
+ !integer.test(promotion.candidate?.producerRunAttempt ?? '') ||
316
+ promotion.candidate?.sourceCommit !== sourceCommit ||
317
+ !/^[a-f0-9]{64}$/.test(promotion.candidate?.materialDigest ?? '')
318
+ : promotion.candidate !== null) ||
319
+ (promotion.hasContainers
320
+ ? !integer.test(promotion.container?.artifactId ?? '') ||
321
+ !digest.test(promotion.container?.artifactDigest ?? '')
322
+ : promotion.container !== null)
323
+ ) {
324
+ throw new Error('Staging deployment receipt and candidate promotion binding disagree.');
325
+ }
326
+ const outputs = {
327
+ deployment_run_attempt: deploymentRunAttempt,
328
+ deployment_run_id: deploymentRunId,
329
+ receipt_artifact_digest: receiptArtifactDigest,
330
+ receipt_artifact_id: receiptArtifactId,
331
+ source_commit: sourceCommit,
332
+ };
333
+ if (expectedHasSlices) Object.assign(outputs, {
334
+ artifact_digest: promotion.candidate.artifactDigest,
335
+ artifact_id: promotion.candidate.artifactId,
336
+ candidate_material_digest: promotion.candidate.materialDigest,
337
+ producer_run_attempt: promotion.candidate.producerRunAttempt,
338
+ producer_run_id: promotion.candidate.producerRunId,
339
+ });
340
+ if (expectedHasContainers) Object.assign(outputs, {
341
+ container_artifact_digest: promotion.container.artifactDigest,
342
+ container_artifact_id: promotion.container.artifactId,
343
+ });
344
+ for (const [name, value] of Object.entries(outputs)) appendFileSync(process.env.GITHUB_OUTPUT, name + '=' + value + '\\n');`;
345
+ }
346
+ /** @internal Shared verbatim with the generated workflow and executable tests. */
347
+ function restoredCandidateValidationScript() {
348
+ return `import { createHash } from 'node:crypto';
349
+ import { readFileSync, readdirSync } from 'node:fs';
350
+ import { join } from 'node:path';
351
+
352
+ const root = '.ebk/evidence/release-candidate/packages';
353
+ const sourceCommit = process.env.EBK_RELEASE_SOURCE_COMMIT?.trim() ?? '';
354
+ const producerRunId = process.env.EBK_CANDIDATE_PRODUCER_RUN_ID?.trim() ?? '';
355
+ const producerRunAttempt = process.env.EBK_CANDIDATE_PRODUCER_RUN_ATTEMPT?.trim() ?? '';
356
+ const expected = process.env.EBK_CANDIDATE_MATERIAL_DIGEST?.trim() ?? '';
357
+ if (!/^[a-f0-9]{40,64}$/.test(sourceCommit) || !/^[1-9][0-9]*$/.test(producerRunId) || !/^[1-9][0-9]*$/.test(producerRunAttempt) || !/^[a-f0-9]{64}$/.test(expected)) {
358
+ throw new Error('Restored candidate authority is absent or malformed.');
359
+ }
360
+ const projects = readdirSync(root, { withFileTypes: true })
361
+ .filter((entry) => entry.isDirectory())
362
+ .map((entry) => entry.name)
363
+ .sort();
364
+ if (projects.length === 0 || projects.some((project) => !/^[a-z0-9][a-z0-9-]{0,62}$/.test(project))) {
365
+ throw new Error('Restored candidate project inventory is absent or malformed.');
366
+ }
367
+ const entries = projects.map((project) => {
368
+ const bytes = readFileSync(join(root, project, 'candidate.json'));
369
+ const value = JSON.parse(bytes.toString('utf8'));
370
+ if (
371
+ value?.kind !== 'ebk-release-candidate' ||
372
+ value.schemaVersion !== 1 ||
373
+ value.slice !== project ||
374
+ value.sourceCommit !== sourceCommit ||
375
+ value.provenance?.runId !== producerRunId ||
376
+ value.provenance?.runAttempt !== producerRunAttempt
377
+ ) throw new Error('Restored candidate provenance is stale or cross-paired.');
378
+ return { manifestDigest: createHash('sha256').update(bytes).digest('hex'), project };
379
+ });
380
+ const observed = createHash('sha256').update(JSON.stringify(entries)).digest('hex');
381
+ if (observed !== expected) throw new Error('Restored candidate material digest differs from staging.');`;
382
+ }
383
+ /** @internal Shared verbatim with the generated workflow and executable tests. */
384
+ function stagingPromotionSealScript() {
385
+ return `import { createHash } from 'node:crypto';
386
+ import { mkdirSync, readFileSync, readdirSync, writeFileSync } from 'node:fs';
387
+ import { join } from 'node:path';
388
+
389
+ const required = (name, pattern) => {
390
+ const value = process.env[name]?.trim() ?? '';
391
+ if (!pattern.test(value)) throw new Error(name + ' is absent or malformed.');
392
+ return value;
393
+ };
394
+ const sourceCommit = required('EBK_RELEASE_SOURCE_COMMIT', /^[a-f0-9]{40,64}$/);
395
+ const hasSlices = required('EBK_HAS_SLICES', /^(?:true|false)$/) === 'true';
396
+ const hasContainers = required('EBK_HAS_CONTAINERS', /^(?:true|false)$/) === 'true';
397
+ if (hasContainers && !hasSlices) throw new Error('Container work requires a selected Slice.');
398
+ const account = required('EBK_POLICY_ACCOUNT_ID', /^[0-9]{12}$/);
399
+ const deploymentProfile = required('EBK_DEPLOYMENT_PROFILE', /^(?:production-isolated|fully-isolated)$/);
400
+ const region = required('EBK_POLICY_REGION', /^[a-z]{2}(?:-gov)?-[a-z]+(?:-[a-z]+)*-[0-9]$/);
401
+ const deploymentRunId = required('GITHUB_RUN_ID', /^[1-9][0-9]*$/);
402
+ const deploymentRunAttempt = required('GITHUB_RUN_ATTEMPT', /^[1-9][0-9]*$/);
403
+ const receiptPath = '.ebk/evidence/deployment/latest.json';
404
+ const receiptBytes = readFileSync(receiptPath);
405
+ const receipt = JSON.parse(receiptBytes.toString('utf8'));
406
+ if (receipt?.kind !== 'ebk-deployment-receipt' || receipt.schemaVersion !== 1 || receipt.outcome !== 'succeeded' || receipt.authority !== 'github' || receipt.stage !== 'staging' || receipt.account !== account || receipt.profile !== deploymentProfile || receipt.region !== region || receipt.source?.clean !== true || receipt.source?.commit !== sourceCommit) {
407
+ throw new Error('Successful staging deployment receipt is absent or stale.');
408
+ }
409
+ const candidateEnvironment = {
410
+ artifactDigest: process.env.EBK_CANDIDATE_ARTIFACT_DIGEST?.trim() ?? '',
411
+ artifactId: process.env.EBK_CANDIDATE_ARTIFACT_ID?.trim() ?? '',
412
+ producerRunAttempt: process.env.EBK_CANDIDATE_PRODUCER_RUN_ATTEMPT?.trim() ?? '',
413
+ producerRunId: process.env.EBK_CANDIDATE_PRODUCER_RUN_ID?.trim() ?? '',
414
+ };
415
+ const containerEnvironment = {
416
+ artifactDigest: process.env.EBK_CONTAINER_ARTIFACT_DIGEST?.trim() ?? '',
417
+ artifactId: process.env.EBK_CONTAINER_ARTIFACT_ID?.trim() ?? '',
418
+ };
419
+ let candidate = null;
420
+ let container = null;
421
+ if (hasSlices) {
422
+ const producerRunId = required('EBK_CANDIDATE_PRODUCER_RUN_ID', /^[1-9][0-9]*$/);
423
+ const producerRunAttempt = required('EBK_CANDIDATE_PRODUCER_RUN_ATTEMPT', /^[1-9][0-9]*$/);
424
+ const artifactId = required('EBK_CANDIDATE_ARTIFACT_ID', /^[1-9][0-9]*$/);
425
+ const artifactDigest = required('EBK_CANDIDATE_ARTIFACT_DIGEST', /^sha256:[a-f0-9]{64}$/);
426
+ const root = '.ebk/evidence/release-candidate/packages';
427
+ const projects = readdirSync(root, { withFileTypes: true })
428
+ .filter((entry) => entry.isDirectory())
429
+ .map((entry) => entry.name)
430
+ .sort();
431
+ if (projects.length === 0 || projects.some((project) => !/^[a-z0-9][a-z0-9-]{0,62}$/.test(project))) throw new Error('Staging candidate inventory is absent or malformed.');
432
+ const entries = projects.map((project) => {
433
+ const bytes = readFileSync(join(root, project, 'candidate.json'));
434
+ const value = JSON.parse(bytes.toString('utf8'));
435
+ if (value?.kind !== 'ebk-release-candidate' || value.schemaVersion !== 1 || value.slice !== project || value.sourceCommit !== sourceCommit || value.provenance?.runId !== producerRunId || value.provenance?.runAttempt !== producerRunAttempt) {
436
+ throw new Error('Staging candidate provenance is stale or cross-paired.');
437
+ }
438
+ return { manifestDigest: createHash('sha256').update(bytes).digest('hex'), project };
439
+ });
440
+ candidate = { artifactDigest, artifactId, materialDigest: createHash('sha256').update(JSON.stringify(entries)).digest('hex'), producerRunAttempt, producerRunId, sourceCommit };
441
+ } else if (Object.values(candidateEnvironment).some(Boolean)) {
442
+ throw new Error('A staging promotion without Slices must not carry Slice release material.');
443
+ }
444
+ if (hasContainers) {
445
+ const artifactId = required('EBK_CONTAINER_ARTIFACT_ID', /^[1-9][0-9]*$/);
446
+ // The upload action returns bare hex; the artifacts API uses sha256:<hex>.
447
+ const raw = containerEnvironment.artifactDigest;
448
+ process.env.EBK_CONTAINER_ARTIFACT_DIGEST = /^[a-f0-9]{64}$/.test(raw) ? 'sha256:' + raw : raw;
449
+ const artifactDigest = required('EBK_CONTAINER_ARTIFACT_DIGEST', /^sha256:[a-f0-9]{64}$/);
450
+ container = { artifactDigest, artifactId };
451
+ } else if (Object.values(containerEnvironment).some(Boolean)) {
452
+ throw new Error('A staging promotion without OCI artifacts must not carry container material.');
453
+ }
454
+ const promotion = {
455
+ account,
456
+ candidate,
457
+ container,
458
+ deploymentProfile,
459
+ deployment: { receiptDigest: createHash('sha256').update(receiptBytes).digest('hex'), runAttempt: deploymentRunAttempt, runId: deploymentRunId },
460
+ hasSlices,
461
+ hasContainers,
462
+ kind: 'ebk-staging-deployment-promotion',
463
+ region,
464
+ schemaVersion: 1,
465
+ sourceCommit,
466
+ stage: 'staging',
467
+ };
468
+ mkdirSync('.ebk/evidence/deployment', { recursive: true });
469
+ writeFileSync('.ebk/evidence/deployment/promotion.json', JSON.stringify(promotion, null, 2) + '\\n', { mode: 0o600 });`;
470
+ }
471
+ /** @internal Shared verbatim with the generated workflow and executable tests. */
472
+ function postdeploySupplyChainBindingScript() {
473
+ return `import { createHash } from 'node:crypto';
474
+ import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';
475
+
476
+ const required = (name, pattern) => {
477
+ const value = process.env[name]?.trim() ?? '';
478
+ if (!pattern.test(value)) throw new Error(name + ' is absent or malformed.');
479
+ return value;
480
+ };
481
+ const canonical = (value) => {
482
+ if (value === null || typeof value !== 'object') return JSON.stringify(value);
483
+ if (Array.isArray(value)) return '[' + value.map(canonical).join(',') + ']';
484
+ return '{' + Object.entries(value)
485
+ .sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0)
486
+ .map(([key, child]) => JSON.stringify(key) + ':' + canonical(child))
487
+ .join(',') + '}';
488
+ };
489
+ const accountId = required('EBK_POLICY_ACCOUNT_ID', /^[0-9]{12}$/);
490
+ const deploymentCommit = required('EBK_SOURCE_COMMIT', /^[a-f0-9]{40,64}$/);
491
+ const profile = required('EBK_DEPLOYMENT_PROFILE', /^(?:starter|production-isolated|fully-isolated)$/);
492
+ const region = required('EBK_POLICY_REGION', /^[a-z]{2}(?:-gov)?-[a-z]+(?:-[a-z]+)*-[0-9]$/);
493
+ const releaseCandidateArtifactDigest = required('EBK_CANDIDATE_ARTIFACT_DIGEST', /^sha256:[a-f0-9]{64}$/);
494
+ const releaseCandidateArtifactId = required('EBK_CANDIDATE_ARTIFACT_ID', /^[1-9][0-9]*$/);
495
+ const releaseCandidateProducerRunAttempt = required('EBK_CANDIDATE_PRODUCER_RUN_ATTEMPT', /^[1-9][0-9]*$/);
496
+ const releaseCandidateProducerRunId = required('EBK_CANDIDATE_PRODUCER_RUN_ID', /^[1-9][0-9]*$/);
497
+ const releaseSourceCommit = required('EBK_RELEASE_SOURCE_COMMIT', /^[a-f0-9]{40,64}$/);
498
+ const runAttempt = required('GITHUB_RUN_ATTEMPT', /^[1-9][0-9]*$/);
499
+ const runId = required('GITHUB_RUN_ID', /^[1-9][0-9]*$/);
500
+ const stage = required('EBK_STAGE', /^(?:development|staging|production)$/);
501
+ const receipt = JSON.parse(readFileSync('.ebk/evidence/deployment/latest.json', 'utf8'));
502
+ if (
503
+ receipt?.kind !== 'ebk-deployment-receipt' ||
504
+ receipt.schemaVersion !== 1 ||
505
+ receipt.outcome !== 'succeeded' ||
506
+ receipt.authority !== 'github' ||
507
+ receipt.account !== accountId ||
508
+ receipt.profile !== profile ||
509
+ receipt.region !== region ||
510
+ receipt.stage !== stage ||
511
+ receipt.source?.clean !== true ||
512
+ receipt.source?.commit !== deploymentCommit
513
+ ) throw new Error('Successful deployment receipt is absent or cross-paired.');
514
+ const unsigned = {
515
+ accountId,
516
+ deploymentCommit,
517
+ kind: 'ebk-postdeploy-supply-chain-binding',
518
+ profile,
519
+ region,
520
+ releaseCandidateArtifactDigest,
521
+ releaseCandidateArtifactId,
522
+ releaseCandidateProducerRunAttempt,
523
+ releaseCandidateProducerRunId,
524
+ releaseSourceCommit,
525
+ runAttempt,
526
+ runId,
527
+ schemaVersion: 1,
528
+ stage,
529
+ };
530
+ const binding = {
531
+ ...unsigned,
532
+ materialDigest: createHash('sha256').update(canonical(unsigned)).digest('hex'),
533
+ };
534
+ mkdirSync('.ebk/evidence/deployment', { recursive: true, mode: 0o700 });
535
+ writeFileSync(
536
+ '.ebk/evidence/deployment/postdeploy-supply-chain.json',
537
+ JSON.stringify(binding, null, 2) + '\\n',
538
+ { flag: 'wx', mode: 0o600 },
539
+ );`;
540
+ }
541
+ function renderThinDeployWorkflow(options) {
542
+ const { actionPins, deploymentProfile, name, toolVersions } = options;
543
+ const trunkPromotion = options.productionApprovalMode === 'trunk-promotion';
544
+ return `name: Deploy
545
+ run-name: Deploy \${{ inputs.source_commit || github.sha }}
546
+
547
+ on:
548
+ ${workflowCallTrigger(deploymentProfile, options.deliveryLane)} workflow_dispatch:
549
+ inputs:
550
+ action:
551
+ description: Native lifecycle action
552
+ required: true
553
+ default: deploy
554
+ type: choice
555
+ options: [deploy, refresh, unlock, remove${deploymentProfile === 'starter' ? '' : ', rehearse'}]
556
+ project:
557
+ description: Required registered project for unlock or remove recovery
558
+ required: false
559
+ type: string${deploymentProfile === 'starter'
560
+ ? ''
561
+ : `
562
+ probe_url:
563
+ description: rehearse only; exact HTTPS Router health URL in /<kebab-slice>/api/health form for the staging blocking rehearsal
564
+ required: false
565
+ type: string`}
566
+ lifecycle:
567
+ description: affected skips Slices unchanged since the newest successful deployment; full deploys everything
568
+ required: false
569
+ default: affected
570
+ type: choice
571
+ options: [affected, full]
572
+
573
+ concurrency:
574
+ group: protected-lifecycle-\${{ inputs.source_branch || github.ref_name }}
575
+ cancel-in-progress: false
576
+
577
+ permissions:
578
+ contents: read
579
+
580
+ env:
581
+ EBK_DEPLOYMENT_SOURCE_BRANCH: \${{ inputs.source_branch || github.ref_name }}
582
+ EBK_DEPLOYMENT_SOURCE_COMMIT: \${{ inputs.source_commit || github.sha }}
583
+ EBK_DEPLOYMENT_SOURCE_REF: \${{ inputs.source_ref || github.ref }}
584
+ EBK_EXPECTED_SST_VERSION: ${toolVersions.sst}
585
+
586
+ jobs:
587
+ prepare:
588
+ name: route protected native lifecycle
589
+ runs-on: ubuntu-latest
590
+ outputs:
591
+ action: \${{ steps.route.outputs.action }}
592
+ probe_url: \${{ steps.route.outputs.probe_url }}
593
+ account_id: \${{ steps.account.outputs.account_id }}
594
+ has_slices: \${{ steps.policy.outputs.has_slices }}
595
+ has_containers: \${{ steps.policy.outputs.has_containers }}
596
+ container_projects: \${{ steps.policy.outputs.container_projects }}
597
+ container_matrix: \${{ steps.policy.outputs.container_matrix }}
598
+ ordered_projects: \${{ steps.policy.outputs.ordered_projects }}
599
+ oidc_audience: \${{ steps.account.outputs.audience }}
600
+ partition: \${{ steps.account.outputs.partition }}
601
+ profile: \${{ steps.policy.outputs.deployment_profile }}
602
+ project: \${{ steps.route.outputs.project }}
603
+ region: \${{ steps.policy.outputs.deployment_region }}
604
+ slice_projects: \${{ steps.policy.outputs.slice_projects }}
605
+ slice_matrix: \${{ steps.policy.outputs.slice_matrix }}
606
+ staging_account_id: \${{ steps.policy.outputs.staging_account_id }}
607
+ stage: \${{ steps.route.outputs.stage }}
608
+ stage_deploy_role_arn: \${{ steps.account.outputs.role_arn }}${trunkPromotion
609
+ ? `
610
+ production_account_id: \${{ steps.production-account.outputs.account_id }}
611
+ production_deploy_role_arn: \${{ steps.production-account.outputs.role_arn }}`
612
+ : ''}
613
+ steps:
614
+ - name: Verify exact protected branch tip before setup
615
+ env:
616
+ EBK_CONTEXT_REF: \${{ github.ref }}
617
+ EBK_CONTEXT_REF_NAME: \${{ github.ref_name }}
618
+ EBK_CONTEXT_RUN_ATTEMPT: \${{ github.run_attempt }}
619
+ EBK_CONTEXT_RUN_ID: \${{ github.run_id }}
620
+ EBK_CONTEXT_SHA: \${{ github.sha }}
621
+ EBK_INPUT_RUN_ATTEMPT: \${{ inputs.verify_run_attempt || '' }}
622
+ EBK_INPUT_RUN_ID: \${{ inputs.verify_run_id || '' }}
623
+ EBK_INPUT_SOURCE_BRANCH: \${{ inputs.source_branch || '' }}
624
+ EBK_INPUT_SOURCE_COMMIT: \${{ inputs.source_commit || '' }}
625
+ EBK_INPUT_SOURCE_REF: \${{ inputs.source_ref || '' }}
626
+ EBK_SOURCE_BRANCH: \${{ env.EBK_DEPLOYMENT_SOURCE_BRANCH }}
627
+ EBK_SOURCE_COMMIT: \${{ env.EBK_DEPLOYMENT_SOURCE_COMMIT }}
628
+ EBK_SOURCE_REF: \${{ env.EBK_DEPLOYMENT_SOURCE_REF }}
629
+ EBK_TRIGGER: \${{ github.event_name }}
630
+ GH_REPO: \${{ github.repository }}
631
+ GH_TOKEN: \${{ github.token }}
632
+ run: |
633
+ set -euo pipefail
634
+ case "$EBK_SOURCE_COMMIT" in ''|*[!0-9a-f]*) exit 1 ;; esac
635
+ { [ "\${#EBK_SOURCE_COMMIT}" -eq 40 ] || [ "\${#EBK_SOURCE_COMMIT}" -eq 64 ]; } || exit 1
636
+ case "$EBK_SOURCE_BRANCH:$EBK_SOURCE_REF" in
637
+ develop:refs/heads/develop|main:refs/heads/main) ;;
638
+ *) echo 'Protected lifecycle must bind one exact protected branch ref'; exit 1 ;;
639
+ esac
640
+ if [ -n "$EBK_INPUT_RUN_ID$EBK_INPUT_RUN_ATTEMPT$EBK_INPUT_SOURCE_BRANCH$EBK_INPUT_SOURCE_COMMIT$EBK_INPUT_SOURCE_REF" ]; then
641
+ [ "$EBK_TRIGGER" = push ] || { echo 'Automatic deployment requires one protected push Verify caller'; exit 1; }
642
+ [ -n "$EBK_INPUT_RUN_ID" ] && [ -n "$EBK_INPUT_RUN_ATTEMPT" ] &&
643
+ [ -n "$EBK_INPUT_SOURCE_BRANCH" ] && [ -n "$EBK_INPUT_SOURCE_COMMIT" ] && [ -n "$EBK_INPUT_SOURCE_REF" ] ||
644
+ { echo 'Automatic deployment caller binding is incomplete'; exit 1; }
645
+ [ "$EBK_INPUT_RUN_ID" = "$EBK_CONTEXT_RUN_ID" ] &&
646
+ [ "$EBK_INPUT_RUN_ATTEMPT" = "$EBK_CONTEXT_RUN_ATTEMPT" ] &&
647
+ [ "$EBK_INPUT_SOURCE_COMMIT" = "$EBK_CONTEXT_SHA" ] &&
648
+ [ "$EBK_INPUT_SOURCE_BRANCH" = "$EBK_CONTEXT_REF_NAME" ] &&
649
+ [ "$EBK_INPUT_SOURCE_REF" = "$EBK_CONTEXT_REF" ] ||
650
+ { echo 'Automatic deployment differs from its exact Verify caller'; exit 1; }
651
+ else
652
+ [ "$EBK_TRIGGER" = workflow_dispatch ] || { echo 'Manual deployment requires workflow dispatch'; exit 1; }
653
+ fi
654
+ branch_tip="$(gh api "/repos/$GH_REPO/git/ref/heads/$EBK_SOURCE_BRANCH" --jq '.object.sha')"
655
+ [ "$branch_tip" = "$EBK_SOURCE_COMMIT" ] || {
656
+ echo 'Deployment source is no longer the exact protected branch tip'; exit 1;
657
+ }
658
+ - name: Check out immutable source
659
+ uses: ${actionPins.checkout}
660
+ with:
661
+ fetch-depth: 0
662
+ persist-credentials: false
663
+ ref: \${{ env.EBK_DEPLOYMENT_SOURCE_COMMIT }}
664
+ - uses: ${actionPins.pnpmSetup}
665
+ - uses: ${actionPins.setupNode}
666
+ with:
667
+ node-version: ${toolVersions.node}
668
+ cache: pnpm
669
+ - run: pnpm install --frozen-lockfile --ignore-scripts --ignore-pnpmfile
670
+ - id: route
671
+ name: Resolve protected stage and action
672
+ env:
673
+ EBK_DISPATCH_ACTION: \${{ inputs.action || '' }}
674
+ EBK_DISPATCH_PROBE_URL: \${{ inputs.probe_url || '' }}
675
+ EBK_DISPATCH_PROJECT: \${{ inputs.project || '' }}
676
+ EBK_PROFILE: ${deploymentProfile}
677
+ EBK_SOURCE_BRANCH: \${{ env.EBK_DEPLOYMENT_SOURCE_BRANCH }}
678
+ EBK_SOURCE_COMMIT: \${{ env.EBK_DEPLOYMENT_SOURCE_COMMIT }}
679
+ EBK_TRIGGER: \${{ github.event_name }}
680
+ run: |
681
+ set -euo pipefail
682
+ action=deploy
683
+ project=
684
+ if [ "$EBK_TRIGGER" = workflow_dispatch ]; then
685
+ action="$EBK_DISPATCH_ACTION"
686
+ project="$EBK_DISPATCH_PROJECT"
687
+ fi
688
+ case "$EBK_SOURCE_COMMIT" in ''|*[!0-9a-f]*) exit 1 ;; esac
689
+ { [ "\${#EBK_SOURCE_COMMIT}" -eq 40 ] || [ "\${#EBK_SOURCE_COMMIT}" -eq 64 ]; } || exit 1
690
+ case "$action" in deploy|refresh|unlock|remove|rehearse) ;; *) exit 1 ;; esac
691
+ case "$project" in ''|*[!A-Za-z0-9_-]*) [ -z "$project" ] || exit 1 ;; esac
692
+ case "$action" in
693
+ unlock|remove) [ -n "$project" ] || { echo 'unlock and remove require an explicit project'; exit 1; } ;;
694
+ deploy|refresh) [ -z "$project" ] || { echo 'deploy and refresh operate on the policy-derived project order'; exit 1; } ;;
695
+ rehearse)
696
+ [ -z "$project" ] || { echo 'rehearse targets the foundation Router WAF and takes no project'; exit 1; }
697
+ [ "$EBK_PROFILE" != starter ] || { echo 'starter rehearses development locally; rehearse is for isolated staging'; exit 1; }
698
+ [ "$EBK_SOURCE_BRANCH" = develop ] || { echo 'rehearse runs from develop against staging'; exit 1; }
699
+ case "$EBK_DISPATCH_PROBE_URL" in
700
+ https://*/*/api/health) ;;
701
+ *) echo 'rehearse requires probe_url in https://<router>/<kebab-slice>/api/health form'; exit 1 ;;
702
+ esac
703
+ ;;
704
+ esac
705
+ # The committed deployment plan knows deploy, refresh, unlock, and
706
+ # remove; a rehearsal derives the same project inventory as deploy.
707
+ plan_action="$action"
708
+ [ "$action" != rehearse ] || plan_action=deploy
709
+ case "$EBK_SOURCE_BRANCH" in
710
+ main) stage=production ;;
711
+ develop)
712
+ if [ "$EBK_PROFILE" = starter ]; then
713
+ stage=development
714
+ else
715
+ stage=staging
716
+ fi
717
+ ;;
718
+ *) echo 'Protected lifecycle must run from develop or main'; exit 1 ;;
719
+ esac
720
+ echo "action=$action" >> "$GITHUB_OUTPUT"
721
+ echo "plan_action=$plan_action" >> "$GITHUB_OUTPUT"
722
+ echo "probe_url=$EBK_DISPATCH_PROBE_URL" >> "$GITHUB_OUTPUT"
723
+ echo "project=$project" >> "$GITHUB_OUTPUT"
724
+ echo "stage=$stage" >> "$GITHUB_OUTPUT"
725
+ - id: policy
726
+ name: Derive ordered projects from committed policy
727
+ env:
728
+ EBK_ACTION: \${{ steps.route.outputs.plan_action }}
729
+ EBK_PROJECT: \${{ steps.route.outputs.project }}
730
+ EBK_STAGE: \${{ steps.route.outputs.stage }}
731
+ run: |
732
+ set -euo pipefail
733
+ args=("--action=$EBK_ACTION" "--stage=$EBK_STAGE")
734
+ if [ -n "$EBK_PROJECT" ]; then args+=("--project=$EBK_PROJECT"); fi
735
+ pnpm nx run ${name}:deployment-plan -- "\${args[@]}"
736
+ - name: Bind generated profile
737
+ env:
738
+ EBK_RENDERED_PROFILE: ${deploymentProfile}
739
+ EBK_REPOSITORY_PROFILE: \${{ steps.policy.outputs.deployment_profile }}
740
+ run: test "$EBK_RENDERED_PROFILE" = "$EBK_REPOSITORY_PROFILE"
741
+ - id: account
742
+ name: Derive canonical committed stage authority
743
+ env:
744
+ EBK_DEVELOPMENT_ACCOUNT: \${{ steps.policy.outputs.development_account_id }}
745
+ EBK_PRODUCTION_ACCOUNT: \${{ steps.policy.outputs.production_account_id }}
746
+ EBK_REGION: \${{ steps.policy.outputs.deployment_region }}
747
+ EBK_STAGE: \${{ steps.route.outputs.stage }}
748
+ EBK_STAGING_ACCOUNT: \${{ steps.policy.outputs.staging_account_id }}
749
+ run: |
750
+ ${indentedScript(`set -euo pipefail\n${canonicalStageAuthorityDerivationScript()}`, 10)}${trunkPromotion
751
+ ? `
752
+ # Trunk promotion deploys production from the develop run, so the
753
+ # production authority is derived here beside the staging authority.
754
+ - id: production-account
755
+ name: Derive canonical committed production authority for trunk promotion
756
+ if: steps.route.outputs.stage == 'staging'
757
+ env:
758
+ EBK_DEVELOPMENT_ACCOUNT: \${{ steps.policy.outputs.development_account_id }}
759
+ EBK_PRODUCTION_ACCOUNT: \${{ steps.policy.outputs.production_account_id }}
760
+ EBK_REGION: \${{ steps.policy.outputs.deployment_region }}
761
+ EBK_STAGE: production
762
+ EBK_STAGING_ACCOUNT: \${{ steps.policy.outputs.staging_account_id }}
763
+ run: |
764
+ ${indentedScript(`set -euo pipefail\n${canonicalStageAuthorityDerivationScript()}`, 10)}`
765
+ : ''}
766
+
767
+ immutable-artifacts:
768
+ name: prepare exact immutable release artifacts
769
+ needs: prepare
770
+ if: >-
771
+ needs.prepare.outputs.action == 'deploy' &&
772
+ (
773
+ needs.prepare.outputs.has_slices == 'true' ||
774
+ (needs.prepare.outputs.stage == 'production' && needs.prepare.outputs.profile != 'starter')
775
+ )
776
+ runs-on: ubuntu-latest
777
+ permissions:
778
+ actions: read
779
+ contents: read
780
+ outputs:
781
+ artifact_digest: \${{ steps.current.outputs.artifact_digest || steps.staging.outputs.artifact_digest }}
782
+ artifact_id: \${{ steps.current.outputs.artifact_id || steps.staging.outputs.artifact_id }}
783
+ candidate_material_digest: \${{ steps.staging.outputs.candidate_material_digest }}
784
+ container_artifact_digest: \${{ steps.staging.outputs.container_artifact_digest }}
785
+ container_artifact_id: \${{ steps.staging.outputs.container_artifact_id }}
786
+ staging_deployment_run_attempt: \${{ steps.staging.outputs.deployment_run_attempt }}
787
+ staging_deployment_run_id: \${{ steps.staging.outputs.deployment_run_id }}
788
+ baseline_artifact_digest: \${{ steps.baseline.outputs.artifact_digest }}
789
+ baseline_artifact_id: \${{ steps.baseline.outputs.artifact_id }}
790
+ baseline_commit: \${{ steps.baseline.outputs.commit }}
791
+ baseline_found: \${{ steps.baseline.outputs.found }}
792
+ baseline_run_id: \${{ steps.baseline.outputs.run_id }}
793
+ producer_run_attempt: \${{ steps.current.outputs.producer_run_attempt || steps.staging.outputs.producer_run_attempt }}
794
+ producer_run_id: \${{ steps.current.outputs.producer_run_id || steps.staging.outputs.producer_run_id }}
795
+ source_commit: \${{ steps.current.outputs.source_commit || steps.staging.outputs.source_commit }}
796
+ steps:
797
+ - name: Check out immutable production ancestry
798
+ if: needs.prepare.outputs.stage == 'production' && needs.prepare.outputs.profile != 'starter'
799
+ uses: ${actionPins.checkout}
800
+ with:
801
+ fetch-depth: 0
802
+ persist-credentials: false
803
+ ref: \${{ env.EBK_DEPLOYMENT_SOURCE_COMMIT }}
804
+ - id: current
805
+ name: Select exact successful current-source CI candidate
806
+ if: >-
807
+ needs.prepare.outputs.has_slices == 'true' &&
808
+ (needs.prepare.outputs.stage != 'production' || needs.prepare.outputs.profile == 'starter')
809
+ env:
810
+ EBK_CALLER_RUN_ATTEMPT: \${{ inputs.verify_run_attempt || '' }}
811
+ EBK_CALLER_RUN_ID: \${{ inputs.verify_run_id || '' }}
812
+ EBK_SOURCE_BRANCH: \${{ env.EBK_DEPLOYMENT_SOURCE_BRANCH }}
813
+ EBK_SOURCE_COMMIT: \${{ env.EBK_DEPLOYMENT_SOURCE_COMMIT }}
814
+ GH_REPO: \${{ github.repository }}
815
+ GH_TOKEN: \${{ github.token }}
816
+ run: |
817
+ set -euo pipefail
818
+ if [ -n "$EBK_CALLER_RUN_ID" ]; then
819
+ [ -n "$EBK_CALLER_RUN_ATTEMPT" ] || exit 1
820
+ run_id="$EBK_CALLER_RUN_ID"
821
+ run_attempt="$EBK_CALLER_RUN_ATTEMPT"
822
+ else
823
+ runs="$(gh api "/repos/$GH_REPO/actions/workflows/ci.yml/runs?event=push&branch=$EBK_SOURCE_BRANCH&head_sha=$EBK_SOURCE_COMMIT&status=success&per_page=100")"
824
+ producer="$(printf '%s' "$runs" | jq -c --arg branch "$EBK_SOURCE_BRANCH" --arg source "$EBK_SOURCE_COMMIT" \
825
+ '[.workflow_runs[] | select(.head_sha == $source and .head_branch == $branch and .event == "push" and .conclusion == "success" and .path == ".github/workflows/ci.yml")] | if length == 1 then .[0] else empty end')"
826
+ [ -n "$producer" ] || { echo 'Exact successful CI candidate producer is absent or ambiguous'; exit 1; }
827
+ run_id="$(printf '%s' "$producer" | jq -r '.id // empty')"
828
+ run_attempt="$(printf '%s' "$producer" | jq -r '.run_attempt // empty')"
829
+ fi
830
+ artifacts="$(gh api "/repos/$GH_REPO/actions/runs/$run_id/artifacts?per_page=100")"
831
+ artifact="$(printf '%s' "$artifacts" | jq -c --arg source "$EBK_SOURCE_COMMIT" \
832
+ '[.artifacts[] | select(.name == "release-candidate-" + $source and .expired == false)] | if length == 1 then .[0] else empty end')"
833
+ [ -n "$artifact" ] || { echo 'Exact release candidate artifact is absent or ambiguous'; exit 1; }
834
+ artifact_id="$(printf '%s' "$artifact" | jq -r '.id // empty')"
835
+ artifact_digest="$(printf '%s' "$artifact" | jq -r '.digest // empty')"
836
+ case "$run_id:$run_attempt:$artifact_id" in *[!0-9:]*) exit 1 ;; esac
837
+ case "$artifact_digest" in sha256:????????????????????????????????????????????????????????????????) ;; *) exit 1 ;; esac
838
+ echo "artifact_digest=$artifact_digest" >> "$GITHUB_OUTPUT"
839
+ echo "artifact_id=$artifact_id" >> "$GITHUB_OUTPUT"
840
+ echo "producer_run_attempt=$run_attempt" >> "$GITHUB_OUTPUT"
841
+ echo "producer_run_id=$run_id" >> "$GITHUB_OUTPUT"
842
+ echo "source_commit=$EBK_SOURCE_COMMIT" >> "$GITHUB_OUTPUT"
843
+ - id: baseline
844
+ name: Select the newest successful deployment receipt on this branch
845
+ if: >-
846
+ needs.prepare.outputs.has_slices == 'true' &&
847
+ (needs.prepare.outputs.stage != 'production' || needs.prepare.outputs.profile == 'starter')
848
+ env:
849
+ EBK_SOURCE_BRANCH: \${{ env.EBK_DEPLOYMENT_SOURCE_BRANCH }}
850
+ EBK_SOURCE_COMMIT: \${{ env.EBK_DEPLOYMENT_SOURCE_COMMIT }}
851
+ EBK_STAGE: \${{ needs.prepare.outputs.stage }}
852
+ GH_REPO: \${{ github.repository }}
853
+ GH_TOKEN: \${{ github.token }}
854
+ run: |
855
+ set -euo pipefail
856
+ # A baseline lets unchanged OCI artifacts reuse their retained evidence.
857
+ # Declining is always safe: without one, every artifact is rebuilt.
858
+ echo "found=false" >> "$GITHUB_OUTPUT"
859
+ runs="$(gh api "/repos/$GH_REPO/actions/workflows/ci.yml/runs?event=push&branch=$EBK_SOURCE_BRANCH&status=success&per_page=50")"
860
+ for candidate in $(printf '%s' "$runs" | jq -r --arg branch "$EBK_SOURCE_BRANCH" \\
861
+ '.workflow_runs | sort_by(.run_number) | reverse | .[] | select(.head_branch == $branch and .event == "push" and .conclusion == "success" and .path == ".github/workflows/ci.yml") | "\\(.id):\\(.run_attempt):\\(.head_sha)"'); do
862
+ IFS=':' read -r run_id run_attempt head_sha <<< "$candidate"
863
+ case "$run_id:$run_attempt" in *[!0-9:]*) continue ;; esac
864
+ case "$head_sha" in ????????????????????????????????????????) ;; *) continue ;; esac
865
+ [ "$head_sha" != "$EBK_SOURCE_COMMIT" ] || continue
866
+ # This job has no checkout on the staging path, so ancestry comes
867
+ # from the API rather than a local repository (EBK-INT-2026-083).
868
+ ancestry="$(gh api "/repos/$GH_REPO/compare/$head_sha...$EBK_SOURCE_COMMIT" --jq '.status' 2>/dev/null || echo unknown)"
869
+ [ "$ancestry" = ahead ] || continue
870
+ artifacts="$(gh api "/repos/$GH_REPO/actions/runs/$run_id/artifacts?per_page=100")"
871
+ receipt="$(printf '%s' "$artifacts" | jq -c --arg name "deployment-receipt-$EBK_STAGE-$run_id-$run_attempt" \\
872
+ '[.artifacts[] | select(.name == $name and .expired == false)] | if length == 1 then .[0] else empty end')"
873
+ [ -n "$receipt" ] || continue
874
+ artifact_id="$(printf '%s' "$receipt" | jq -r '.id // empty')"
875
+ artifact_digest="$(printf '%s' "$receipt" | jq -r '.digest // empty')"
876
+ case "$artifact_id" in ''|*[!0-9]*) continue ;; esac
877
+ case "$artifact_digest" in sha256:????????????????????????????????????????????????????????????????) ;; *) continue ;; esac
878
+ echo "artifact_digest=$artifact_digest" >> "$GITHUB_OUTPUT"
879
+ echo "artifact_id=$artifact_id" >> "$GITHUB_OUTPUT"
880
+ echo "commit=$head_sha" >> "$GITHUB_OUTPUT"
881
+ echo "found=true" >> "$GITHUB_OUTPUT"
882
+ echo "run_id=$run_id" >> "$GITHUB_OUTPUT"
883
+ echo "Baseline deployment receipt: run $run_id attempt $run_attempt at $head_sha"
884
+ break
885
+ done
886
+ - id: staging-receipt
887
+ name: Select exact successful staging deployment receipt
888
+ if: needs.prepare.outputs.stage == 'production' && needs.prepare.outputs.profile != 'starter'
889
+ env:
890
+ EBK_DEPLOYMENT_SOURCE_COMMIT: \${{ env.EBK_DEPLOYMENT_SOURCE_COMMIT }}
891
+ GH_REPO: \${{ github.repository }}
892
+ GH_TOKEN: \${{ github.token }}
893
+ run: |
894
+ set -euo pipefail
895
+ parents="$(git rev-list --parents -n 1 "$EBK_DEPLOYMENT_SOURCE_COMMIT")"
896
+ set -- $parents
897
+ [ "$#" -eq 3 ] || { echo 'Isolated production requires one exact two-parent promotion merge'; exit 1; }
898
+ [ "$1" = "$EBK_DEPLOYMENT_SOURCE_COMMIT" ] || exit 1
899
+ staging_source="$3"
900
+ git merge-base --is-ancestor "$staging_source" "$EBK_DEPLOYMENT_SOURCE_COMMIT" || {
901
+ echo 'Staging promotion source is not an ancestor of production'; exit 1;
902
+ }
903
+ [ "$(git rev-parse "$EBK_DEPLOYMENT_SOURCE_COMMIT^{tree}")" = "$(git rev-parse "$staging_source^{tree}")" ] || {
904
+ echo 'Production merge tree differs from the promoted staging source'; exit 1;
905
+ }
906
+ git show-ref --verify --quiet refs/remotes/origin/develop || {
907
+ echo 'Protected develop lineage is absent from the immutable checkout'; exit 1;
908
+ }
909
+ git rev-list --first-parent refs/remotes/origin/develop > "$RUNNER_TEMP/develop-first-parent"
910
+ grep -Fxq "$staging_source" "$RUNNER_TEMP/develop-first-parent" || {
911
+ echo 'Staging promotion source is absent from protected develop first-parent history'; exit 1;
912
+ }
913
+ runs="$(gh api "/repos/$GH_REPO/actions/workflows/ci.yml/runs?event=push&branch=develop&head_sha=$staging_source&status=success&per_page=100")"
914
+ producer="$(printf '%s' "$runs" | jq -c --arg source "$staging_source" \
915
+ '[.workflow_runs[] | select(.head_sha == $source and .head_branch == "develop" and .event == "push" and .conclusion == "success" and .path == ".github/workflows/ci.yml")] | if length == 1 then .[0] else empty end')"
916
+ [ -n "$producer" ] || { echo 'Exact successful staging Verify deployment is absent or ambiguous'; exit 1; }
917
+ run_id="$(printf '%s' "$producer" | jq -r '.id // empty')"
918
+ run_attempt="$(printf '%s' "$producer" | jq -r '.run_attempt // empty')"
919
+ artifacts="$(gh api "/repos/$GH_REPO/actions/runs/$run_id/artifacts?per_page=100")"
920
+ receipt_name="deployment-receipt-staging-$run_id-$run_attempt"
921
+ receipt="$(printf '%s' "$artifacts" | jq -c --arg name "$receipt_name" \
922
+ '[.artifacts[] | select(.name == $name and .expired == false)] | if length == 1 then .[0] else empty end')"
923
+ [ -n "$receipt" ] || { echo 'Exact successful staging deployment receipt is absent or ambiguous'; exit 1; }
924
+ artifact_id="$(printf '%s' "$receipt" | jq -r '.id // empty')"
925
+ artifact_digest="$(printf '%s' "$receipt" | jq -r '.digest // empty')"
926
+ case "$run_id:$run_attempt:$artifact_id" in *[!0-9:]*) exit 1 ;; esac
927
+ case "$artifact_digest" in sha256:????????????????????????????????????????????????????????????????) ;; *) exit 1 ;; esac
928
+ echo "artifact_digest=$artifact_digest" >> "$GITHUB_OUTPUT"
929
+ echo "artifact_id=$artifact_id" >> "$GITHUB_OUTPUT"
930
+ echo "run_attempt=$run_attempt" >> "$GITHUB_OUTPUT"
931
+ echo "run_id=$run_id" >> "$GITHUB_OUTPUT"
932
+ echo "source_commit=$staging_source" >> "$GITHUB_OUTPUT"
933
+ - name: Restore exact staging deployment receipt
934
+ if: needs.prepare.outputs.stage == 'production' && needs.prepare.outputs.profile != 'starter'
935
+ uses: ${actionPins.downloadArtifact}
936
+ with:
937
+ artifact-ids: \${{ steps.staging-receipt.outputs.artifact_id }}
938
+ github-token: \${{ github.token }}
939
+ path: \${{ runner.temp }}/staging-deployment-receipt
940
+ run-id: \${{ steps.staging-receipt.outputs.run_id }}
941
+ - id: staging
942
+ name: Bind exact staging deployment and release evidence
943
+ if: needs.prepare.outputs.stage == 'production' && needs.prepare.outputs.profile != 'starter'
944
+ env:
945
+ EBK_STAGING_DEPLOYMENT_RUN_ATTEMPT: \${{ steps.staging-receipt.outputs.run_attempt }}
946
+ EBK_STAGING_DEPLOYMENT_RUN_ID: \${{ steps.staging-receipt.outputs.run_id }}
947
+ EBK_EXPECTED_HAS_SLICES: \${{ needs.prepare.outputs.has_slices }}
948
+ EBK_EXPECTED_HAS_CONTAINERS: \${{ needs.prepare.outputs.has_containers }}
949
+ EBK_EXPECTED_DEPLOYMENT_PROFILE: \${{ needs.prepare.outputs.profile }}
950
+ EBK_EXPECTED_DEPLOYMENT_REGION: \${{ needs.prepare.outputs.region }}
951
+ EBK_EXPECTED_STAGING_ACCOUNT_ID: \${{ needs.prepare.outputs.staging_account_id }}
952
+ EBK_STAGING_RECEIPT_ARTIFACT_DIGEST: \${{ steps.staging-receipt.outputs.artifact_digest }}
953
+ EBK_STAGING_RECEIPT_ARTIFACT_ID: \${{ steps.staging-receipt.outputs.artifact_id }}
954
+ # The receipt artifact keeps .ebk/evidence as its root, so latest.json
955
+ # and promotion.json live under deployment/ (EBK-INT-2026-074).
956
+ EBK_STAGING_RECEIPT_ROOT: \${{ runner.temp }}/staging-deployment-receipt/deployment
957
+ EBK_STAGING_SOURCE_COMMIT: \${{ steps.staging-receipt.outputs.source_commit }}
958
+ GH_REPO: \${{ github.repository }}
959
+ GH_TOKEN: \${{ github.token }}
960
+ run: |
961
+ node --input-type=module <<'NODE'
962
+ ${indentedScript(stagingPromotionReceiptValidationScript(), 10)}
963
+ NODE
964
+ if [ "$EBK_EXPECTED_HAS_SLICES" = true ]; then
965
+ promotion="$EBK_STAGING_RECEIPT_ROOT/promotion.json"
966
+ candidate_run_id="$(jq -r '.candidate.producerRunId' "$promotion")"
967
+ candidate_run_attempt="$(jq -r '.candidate.producerRunAttempt' "$promotion")"
968
+ candidate_artifact_id="$(jq -r '.candidate.artifactId' "$promotion")"
969
+ candidate_artifact_digest="$(jq -r '.candidate.artifactDigest' "$promotion")"
970
+ candidate_run="$(gh api "/repos/$GH_REPO/actions/runs/$candidate_run_id")"
971
+ printf '%s' "$candidate_run" | jq -e \
972
+ --arg source "$EBK_STAGING_SOURCE_COMMIT" \
973
+ --arg attempt "$candidate_run_attempt" \
974
+ '.head_sha == $source and .head_branch == "develop" and .event == "push" and .path == ".github/workflows/ci.yml" and .conclusion == "success" and (.run_attempt | tostring) == $attempt' >/dev/null
975
+ candidate_artifacts="$(gh api "/repos/$GH_REPO/actions/runs/$candidate_run_id/artifacts?per_page=100")"
976
+ printf '%s' "$candidate_artifacts" | jq -e \
977
+ --arg id "$candidate_artifact_id" \
978
+ --arg digest "$candidate_artifact_digest" \
979
+ --arg name "release-candidate-$EBK_STAGING_SOURCE_COMMIT" \
980
+ '[.artifacts[] | select((.id | tostring) == $id and .name == $name and .digest == $digest and .expired == false)] | length == 1' >/dev/null
981
+ fi
982
+ if [ "$EBK_EXPECTED_HAS_CONTAINERS" = true ]; then
983
+ container_artifact_id="$(jq -r '.container.artifactId' "$promotion")"
984
+ container_artifact_digest="$(jq -r '.container.artifactDigest' "$promotion")"
985
+ staging_artifacts="$(gh api "/repos/$GH_REPO/actions/runs/$EBK_STAGING_DEPLOYMENT_RUN_ID/artifacts?per_page=100")"
986
+ printf '%s' "$staging_artifacts" | jq -e \
987
+ --arg id "$container_artifact_id" \
988
+ --arg digest "$container_artifact_digest" \
989
+ --arg name "container-release-staging-$EBK_STAGING_DEPLOYMENT_RUN_ID-$EBK_STAGING_DEPLOYMENT_RUN_ATTEMPT" \
990
+ '[.artifacts[] | select((.id | tostring) == $id and .name == $name and .digest == $digest and .expired == false)] | length == 1' >/dev/null
991
+ fi
992
+
993
+ container-platform:
994
+ name: native \${{ needs.prepare.outputs.stage }} OCI \${{ matrix.project.project }} \${{ matrix.platform }}
995
+ needs: [prepare, immutable-artifacts]
996
+ if: >-
997
+ always() &&
998
+ needs.prepare.result == 'success' &&
999
+ needs.immutable-artifacts.result == 'success' &&
1000
+ needs.prepare.outputs.action == 'deploy' &&
1001
+ needs.prepare.outputs.has_containers == 'true' &&
1002
+ (
1003
+ needs.prepare.outputs.stage == 'staging' ||
1004
+ (needs.prepare.outputs.stage == 'production' && needs.prepare.outputs.profile == 'starter')
1005
+ )
1006
+ strategy:
1007
+ fail-fast: false
1008
+ # One job per Slice and architecture: Slices build in parallel instead of
1009
+ # looping on one runner per architecture.
1010
+ matrix:
1011
+ project: \${{ fromJson(needs.prepare.outputs.container_matrix) }}
1012
+ # architecture is an original axis: include entries only decorate the
1013
+ # matching combination. Include-only keys would be merged into every
1014
+ # project and the later entry would overwrite the earlier one, so every
1015
+ # Slice would build one architecture (EBK-INT-2026-081).
1016
+ architecture: [amd64, arm64]
1017
+ include:
1018
+ - architecture: amd64
1019
+ platform: linux/amd64
1020
+ runner: ubuntu-latest
1021
+ target: container-release-amd64
1022
+ - architecture: arm64
1023
+ platform: linux/arm64
1024
+ runner: ubuntu-24.04-arm
1025
+ target: container-release-arm64
1026
+ runs-on: \${{ matrix.runner }}
1027
+ environment: \${{ needs.prepare.outputs.stage }}
1028
+ permissions:
1029
+ actions: read
1030
+ contents: read
1031
+ id-token: write
1032
+ env:
1033
+ AWS_DEPLOY_ROLE_ARN: \${{ needs.prepare.outputs.stage_deploy_role_arn }}
1034
+ AWS_EXPECTED_ACCOUNT_ID: \${{ vars.AWS_EXPECTED_ACCOUNT_ID }}
1035
+ AWS_PARTITION: \${{ needs.prepare.outputs.partition }}
1036
+ AWS_REGION: \${{ vars.AWS_REGION }}
1037
+ EBK_ACTION: deploy
1038
+ EBK_DEPLOYMENT_PROFILE: \${{ needs.prepare.outputs.profile }}
1039
+ EBK_POLICY_ACCOUNT_ID: \${{ needs.prepare.outputs.account_id }}
1040
+ EBK_POLICY_REGION: \${{ needs.prepare.outputs.region }}
1041
+ EBK_RELEASE_SOURCE_COMMIT: \${{ needs.immutable-artifacts.outputs.source_commit }}
1042
+ EBK_SOURCE_COMMIT: \${{ inputs.source_commit || github.sha }}
1043
+ EBK_STAGE: \${{ needs.prepare.outputs.stage }}
1044
+ EBK_SLICE_PROJECTS: \${{ needs.prepare.outputs.container_projects }}
1045
+ EBK_SLICE_PROJECT: \${{ matrix.project.project }}
1046
+ EBK_CONTAINER_PROJECTS: \${{ needs.prepare.outputs.container_projects }}
1047
+ steps:
1048
+ - name: Check out immutable container source
1049
+ uses: ${actionPins.checkout}
1050
+ env:
1051
+ ACTIONS_ID_TOKEN_REQUEST_TOKEN: ''
1052
+ ACTIONS_ID_TOKEN_REQUEST_URL: ''
1053
+ with:
1054
+ fetch-depth: 0
1055
+ persist-credentials: false
1056
+ ref: \${{ env.EBK_DEPLOYMENT_SOURCE_COMMIT }}
1057
+ - uses: ${actionPins.pnpmSetup}
1058
+ env:
1059
+ ACTIONS_ID_TOKEN_REQUEST_TOKEN: ''
1060
+ ACTIONS_ID_TOKEN_REQUEST_URL: ''
1061
+ - uses: ${actionPins.setupNode}
1062
+ env:
1063
+ ACTIONS_ID_TOKEN_REQUEST_TOKEN: ''
1064
+ ACTIONS_ID_TOKEN_REQUEST_URL: ''
1065
+ with:
1066
+ node-version: ${toolVersions.node}
1067
+ cache: pnpm
1068
+ - env:
1069
+ ACTIONS_ID_TOKEN_REQUEST_TOKEN: ''
1070
+ ACTIONS_ID_TOKEN_REQUEST_URL: ''
1071
+ run: pnpm install --frozen-lockfile --ignore-scripts --ignore-pnpmfile
1072
+ - name: Verify managed workspace integrity before container authority
1073
+ env:
1074
+ ACTIONS_ID_TOKEN_REQUEST_TOKEN: ''
1075
+ ACTIONS_ID_TOKEN_REQUEST_URL: ''
1076
+ run: pnpm nx run ${name}:workspace-health
1077
+ - name: Restore exact immutable release candidate for container build
1078
+ uses: ${actionPins.downloadArtifact}
1079
+ env:
1080
+ ACTIONS_ID_TOKEN_REQUEST_TOKEN: ''
1081
+ ACTIONS_ID_TOKEN_REQUEST_URL: ''
1082
+ with:
1083
+ artifact-ids: \${{ needs.immutable-artifacts.outputs.artifact_id }}
1084
+ github-token: \${{ github.token }}
1085
+ path: .ebk/evidence/release-candidate
1086
+ run-id: \${{ needs.immutable-artifacts.outputs.producer_run_id }}
1087
+ - name: Restore the baseline deployment receipt for unchanged artifacts
1088
+ if: needs.immutable-artifacts.outputs.baseline_found == 'true'
1089
+ uses: ${actionPins.downloadArtifact}
1090
+ env:
1091
+ ACTIONS_ID_TOKEN_REQUEST_TOKEN: ''
1092
+ ACTIONS_ID_TOKEN_REQUEST_URL: ''
1093
+ with:
1094
+ artifact-ids: \${{ needs.immutable-artifacts.outputs.baseline_artifact_id }}
1095
+ github-token: \${{ github.token }}
1096
+ path: \${{ runner.temp }}/ebk-deployment-baseline
1097
+ run-id: \${{ needs.immutable-artifacts.outputs.baseline_run_id }}
1098
+ - name: Revalidate exact protected branch tip before OIDC
1099
+ env:
1100
+ ACTIONS_ID_TOKEN_REQUEST_TOKEN: ''
1101
+ ACTIONS_ID_TOKEN_REQUEST_URL: ''
1102
+ EBK_SOURCE_BRANCH: \${{ env.EBK_DEPLOYMENT_SOURCE_BRANCH }}
1103
+ EBK_SOURCE_COMMIT: \${{ env.EBK_DEPLOYMENT_SOURCE_COMMIT }}
1104
+ GH_REPO: \${{ github.repository }}
1105
+ GH_TOKEN: \${{ github.token }}
1106
+ run: |
1107
+ set -euo pipefail
1108
+ [ "$(git rev-parse HEAD)" = "$EBK_SOURCE_COMMIT" ] || {
1109
+ echo 'Deployment checkout differs from the exact protected source'; exit 1;
1110
+ }
1111
+ branch_tip="$(gh api "/repos/$GH_REPO/git/ref/heads/$EBK_SOURCE_BRANCH" --jq '.object.sha')"
1112
+ [ "$branch_tip" = "$EBK_SOURCE_COMMIT" ] || {
1113
+ echo 'Deployment source is no longer the exact protected branch tip'; exit 1;
1114
+ }
1115
+ - name: Assume the current stage deployment role for container build
1116
+ uses: ${actionPins.configureAwsCredentials}
1117
+ with:
1118
+ audience: \${{ needs.prepare.outputs.oidc_audience }}
1119
+ aws-region: \${{ vars.AWS_REGION }}
1120
+ role-session-name: ebk-container-\${{ matrix.architecture }}-\${{ github.run_id }}-\${{ github.run_attempt }}
1121
+ role-to-assume: \${{ env.AWS_DEPLOY_ROLE_ARN }}
1122
+ - name: Verify exact container build authority
1123
+ run: |
1124
+ set -euo pipefail
1125
+ [ "$AWS_EXPECTED_ACCOUNT_ID" = "$EBK_POLICY_ACCOUNT_ID" ]
1126
+ [ "$AWS_REGION" = "$EBK_POLICY_REGION" ]
1127
+ [ "$(aws sts get-caller-identity --query Account --output text)" = "$EBK_POLICY_ACCOUNT_ID" ]
1128
+ - name: Restore the container layer and release tool caches
1129
+ uses: ${actionPins.cache}
1130
+ env:
1131
+ ACTIONS_ID_TOKEN_REQUEST_TOKEN: ''
1132
+ ACTIONS_ID_TOKEN_REQUEST_URL: ''
1133
+ with:
1134
+ path: |
1135
+ \${{ runner.temp }}/ebk-buildx-cache
1136
+ \${{ runner.temp }}/ebk-release-tools
1137
+ key: ebk-container-\${{ needs.prepare.outputs.stage }}-\${{ matrix.project.project }}-\${{ matrix.architecture }}-\${{ hashFiles('pnpm-lock.yaml') }}-\${{ github.run_id }}
1138
+ restore-keys: |
1139
+ ebk-container-\${{ needs.prepare.outputs.stage }}-\${{ matrix.project.project }}-\${{ matrix.architecture }}-\${{ hashFiles('pnpm-lock.yaml') }}-
1140
+ ebk-container-\${{ needs.prepare.outputs.stage }}-\${{ matrix.project.project }}-\${{ matrix.architecture }}-
1141
+ - name: Build and run exact native OCI artifacts
1142
+ env:
1143
+ EBK_BUILDX_CACHE_DIR: \${{ runner.temp }}/ebk-buildx-cache
1144
+ EBK_RELEASE_TOOL_CACHE_DIR: \${{ runner.temp }}/ebk-release-tools
1145
+ EBK_CONTAINER_BASELINE_COMMIT: \${{ needs.immutable-artifacts.outputs.baseline_commit }}
1146
+ EBK_CONTAINER_BASELINE_ROOT: \${{ needs.immutable-artifacts.outputs.baseline_found == 'true' && format('{0}/ebk-deployment-baseline', runner.temp) || '' }}
1147
+ run: |
1148
+ set -euo pipefail
1149
+ ${indentedScript(exactStageDeployerGuardScript('Container build', '$EBK_POLICY_ACCOUNT_ID'), 10)}
1150
+ case "$EBK_SLICE_PROJECT" in
1151
+ ''|-*|*-|*[!a-z0-9-]*) echo 'Container build Slice project must be an exact normalized Slice name'; exit 1 ;;
1152
+ esac
1153
+ case ",$EBK_CONTAINER_PROJECTS," in
1154
+ *",$EBK_SLICE_PROJECT,"*) ;;
1155
+ *) echo 'Container build Slice project is not in the committed deployment plan'; exit 1 ;;
1156
+ esac
1157
+ pnpm nx run "$EBK_SLICE_PROJECT:\${{ matrix.target }}"
1158
+ - name: Retain native platform evidence
1159
+ uses: ${actionPins.uploadArtifact}
1160
+ with:
1161
+ name: container-platform-\${{ needs.prepare.outputs.stage }}-\${{ matrix.project.project }}-\${{ matrix.architecture }}-\${{ github.run_id }}-\${{ github.run_attempt }}
1162
+ if-no-files-found: error
1163
+ include-hidden-files: true
1164
+ retention-days: 30
1165
+ # The glob keeps partials/<stage> as the artifact root: the assemble
1166
+ # job merges every platform artifact into that directory and the
1167
+ # container release tool reads <project>/<architecture>/ beneath it.
1168
+ # A literal <project>/<architecture> path would make the architecture
1169
+ # directory the root and flatten the merged restore.
1170
+ path: .ebk/evidence/container-release/partials/\${{ needs.prepare.outputs.stage }}/*/\${{ matrix.architecture }}
1171
+
1172
+ container-assemble:
1173
+ name: assemble \${{ needs.prepare.outputs.stage }} OCI indexes
1174
+ needs: [prepare, immutable-artifacts, container-platform]
1175
+ if: >-
1176
+ always() &&
1177
+ needs.prepare.result == 'success' &&
1178
+ needs.immutable-artifacts.result == 'success' &&
1179
+ needs.container-platform.result == 'success' &&
1180
+ needs.prepare.outputs.action == 'deploy' &&
1181
+ needs.prepare.outputs.has_containers == 'true'
1182
+ runs-on: ubuntu-latest
1183
+ environment: \${{ needs.prepare.outputs.stage }}
1184
+ permissions:
1185
+ actions: read
1186
+ contents: read
1187
+ id-token: write
1188
+ outputs:
1189
+ artifact_digest: \${{ steps.retain.outputs.artifact-digest }}
1190
+ artifact_id: \${{ steps.retain.outputs.artifact-id }}
1191
+ env:
1192
+ AWS_DEPLOY_ROLE_ARN: \${{ needs.prepare.outputs.stage_deploy_role_arn }}
1193
+ AWS_EXPECTED_ACCOUNT_ID: \${{ vars.AWS_EXPECTED_ACCOUNT_ID }}
1194
+ AWS_PARTITION: \${{ needs.prepare.outputs.partition }}
1195
+ AWS_REGION: \${{ vars.AWS_REGION }}
1196
+ EBK_ACTION: deploy
1197
+ EBK_DEPLOYMENT_PROFILE: \${{ needs.prepare.outputs.profile }}
1198
+ EBK_POLICY_ACCOUNT_ID: \${{ needs.prepare.outputs.account_id }}
1199
+ EBK_POLICY_REGION: \${{ needs.prepare.outputs.region }}
1200
+ EBK_RELEASE_SOURCE_COMMIT: \${{ needs.immutable-artifacts.outputs.source_commit }}
1201
+ EBK_SOURCE_COMMIT: \${{ inputs.source_commit || github.sha }}
1202
+ EBK_STAGE: \${{ needs.prepare.outputs.stage }}
1203
+ EBK_SLICE_PROJECTS: \${{ needs.prepare.outputs.container_projects }}
1204
+ steps:
1205
+ - name: Check out immutable container source
1206
+ uses: ${actionPins.checkout}
1207
+ env:
1208
+ ACTIONS_ID_TOKEN_REQUEST_TOKEN: ''
1209
+ ACTIONS_ID_TOKEN_REQUEST_URL: ''
1210
+ with:
1211
+ fetch-depth: 0
1212
+ persist-credentials: false
1213
+ ref: \${{ env.EBK_DEPLOYMENT_SOURCE_COMMIT }}
1214
+ - uses: ${actionPins.pnpmSetup}
1215
+ env:
1216
+ ACTIONS_ID_TOKEN_REQUEST_TOKEN: ''
1217
+ ACTIONS_ID_TOKEN_REQUEST_URL: ''
1218
+ - uses: ${actionPins.setupNode}
1219
+ env:
1220
+ ACTIONS_ID_TOKEN_REQUEST_TOKEN: ''
1221
+ ACTIONS_ID_TOKEN_REQUEST_URL: ''
1222
+ with:
1223
+ node-version: ${toolVersions.node}
1224
+ cache: pnpm
1225
+ - env:
1226
+ ACTIONS_ID_TOKEN_REQUEST_TOKEN: ''
1227
+ ACTIONS_ID_TOKEN_REQUEST_URL: ''
1228
+ run: pnpm install --frozen-lockfile --ignore-scripts --ignore-pnpmfile
1229
+ - name: Verify managed workspace integrity before container authority
1230
+ env:
1231
+ ACTIONS_ID_TOKEN_REQUEST_TOKEN: ''
1232
+ ACTIONS_ID_TOKEN_REQUEST_URL: ''
1233
+ run: pnpm nx run ${name}:workspace-health
1234
+ - name: Restore exact immutable release candidate for container assembly
1235
+ uses: ${actionPins.downloadArtifact}
1236
+ env:
1237
+ ACTIONS_ID_TOKEN_REQUEST_TOKEN: ''
1238
+ ACTIONS_ID_TOKEN_REQUEST_URL: ''
1239
+ with:
1240
+ artifact-ids: \${{ needs.immutable-artifacts.outputs.artifact_id }}
1241
+ github-token: \${{ github.token }}
1242
+ path: .ebk/evidence/release-candidate
1243
+ run-id: \${{ needs.immutable-artifacts.outputs.producer_run_id }}
1244
+ - name: Restore both native platform records
1245
+ uses: ${actionPins.downloadArtifact}
1246
+ env:
1247
+ ACTIONS_ID_TOKEN_REQUEST_TOKEN: ''
1248
+ ACTIONS_ID_TOKEN_REQUEST_URL: ''
1249
+ with:
1250
+ pattern: container-platform-\${{ needs.prepare.outputs.stage }}-*-\${{ github.run_id }}-\${{ github.run_attempt }}
1251
+ merge-multiple: true
1252
+ path: .ebk/evidence/container-release/partials/\${{ needs.prepare.outputs.stage }}
1253
+ - name: Revalidate exact protected branch tip before OIDC
1254
+ env:
1255
+ ACTIONS_ID_TOKEN_REQUEST_TOKEN: ''
1256
+ ACTIONS_ID_TOKEN_REQUEST_URL: ''
1257
+ EBK_SOURCE_BRANCH: \${{ env.EBK_DEPLOYMENT_SOURCE_BRANCH }}
1258
+ EBK_SOURCE_COMMIT: \${{ env.EBK_DEPLOYMENT_SOURCE_COMMIT }}
1259
+ GH_REPO: \${{ github.repository }}
1260
+ GH_TOKEN: \${{ github.token }}
1261
+ run: |
1262
+ set -euo pipefail
1263
+ [ "$(git rev-parse HEAD)" = "$EBK_SOURCE_COMMIT" ] || {
1264
+ echo 'Deployment checkout differs from the exact protected source'; exit 1;
1265
+ }
1266
+ branch_tip="$(gh api "/repos/$GH_REPO/git/ref/heads/$EBK_SOURCE_BRANCH" --jq '.object.sha')"
1267
+ [ "$branch_tip" = "$EBK_SOURCE_COMMIT" ] || {
1268
+ echo 'Deployment source is no longer the exact protected branch tip'; exit 1;
1269
+ }
1270
+ - name: Assume the current stage deployment role for container assembly
1271
+ uses: ${actionPins.configureAwsCredentials}
1272
+ with:
1273
+ audience: \${{ needs.prepare.outputs.oidc_audience }}
1274
+ aws-region: \${{ vars.AWS_REGION }}
1275
+ role-session-name: ebk-container-assemble-\${{ github.run_id }}-\${{ github.run_attempt }}
1276
+ role-to-assume: \${{ env.AWS_DEPLOY_ROLE_ARN }}
1277
+ - name: Verify exact container assembly authority
1278
+ run: |
1279
+ set -euo pipefail
1280
+ [ "$AWS_EXPECTED_ACCOUNT_ID" = "$EBK_POLICY_ACCOUNT_ID" ]
1281
+ [ "$AWS_REGION" = "$EBK_POLICY_REGION" ]
1282
+ [ "$(aws sts get-caller-identity --query Account --output text)" = "$EBK_POLICY_ACCOUNT_ID" ]
1283
+ - name: Assemble exact dual-architecture indexes
1284
+ run: |
1285
+ set -euo pipefail
1286
+ ${indentedScript(exactStageDeployerGuardScript('Container assembly', '$EBK_POLICY_ACCOUNT_ID'), 10)}
1287
+ IFS=',' read -r -a projects <<< "$EBK_SLICE_PROJECTS"
1288
+ for project in "\${projects[@]}"; do
1289
+ test -n "$project" || continue
1290
+ pnpm nx run "$project:container-release-assemble"
1291
+ binding=".ebk/evidence/container-release/$EBK_STAGE/packages/$project/binding.json"
1292
+ sidecar=".ebk/evidence/container-release/$EBK_STAGE/packages/$project/release-bound-binding.json"
1293
+ test -f "$binding" && test ! -e "$sidecar"
1294
+ cp "$binding" "$sidecar"
1295
+ cmp "$binding" "$sidecar"
1296
+ done
1297
+ - id: retain
1298
+ name: Retain exact predeploy container bundle
1299
+ uses: ${actionPins.uploadArtifact}
1300
+ with:
1301
+ name: container-release-\${{ needs.prepare.outputs.stage }}-\${{ github.run_id }}-\${{ github.run_attempt }}
1302
+ if-no-files-found: error
1303
+ include-hidden-files: true
1304
+ retention-days: 30
1305
+ path: .ebk/evidence/container-release
1306
+
1307
+ container-promote-production:
1308
+ name: promote reviewed OCI indexes to production
1309
+ needs: [prepare, immutable-artifacts]
1310
+ if: >-
1311
+ always() &&
1312
+ needs.prepare.result == 'success' &&
1313
+ needs.immutable-artifacts.result == 'success' &&
1314
+ needs.prepare.outputs.action == 'deploy' &&
1315
+ needs.prepare.outputs.stage == 'production' &&
1316
+ needs.prepare.outputs.profile != 'starter' &&
1317
+ needs.prepare.outputs.has_containers == 'true'
1318
+ runs-on: ubuntu-latest
1319
+ environment: production
1320
+ permissions:
1321
+ actions: read
1322
+ contents: read
1323
+ id-token: write
1324
+ outputs:
1325
+ artifact_digest: \${{ steps.retain.outputs.artifact-digest }}
1326
+ artifact_id: \${{ steps.retain.outputs.artifact-id }}
1327
+ env:
1328
+ AWS_DEPLOY_ROLE_ARN: \${{ needs.prepare.outputs.stage_deploy_role_arn }}
1329
+ AWS_EXPECTED_ACCOUNT_ID: \${{ vars.AWS_EXPECTED_ACCOUNT_ID }}
1330
+ AWS_PARTITION: \${{ needs.prepare.outputs.partition }}
1331
+ AWS_REGION: \${{ vars.AWS_REGION }}
1332
+ EBK_ACTION: deploy
1333
+ EBK_DEPLOYMENT_PROFILE: \${{ needs.prepare.outputs.profile }}
1334
+ EBK_POLICY_PRODUCTION_ACCOUNT_ID: \${{ needs.prepare.outputs.account_id }}
1335
+ EBK_POLICY_REGION: \${{ needs.prepare.outputs.region }}
1336
+ EBK_POLICY_STAGING_ACCOUNT_ID: \${{ needs.prepare.outputs.staging_account_id }}
1337
+ EBK_RELEASE_SOURCE_COMMIT: \${{ needs.immutable-artifacts.outputs.source_commit }}
1338
+ EBK_SOURCE_COMMIT: \${{ inputs.source_commit || github.sha }}
1339
+ EBK_STAGE: production
1340
+ EBK_SLICE_PROJECTS: \${{ needs.prepare.outputs.container_projects }}
1341
+ steps:
1342
+ - name: Check out immutable production source
1343
+ uses: ${actionPins.checkout}
1344
+ env:
1345
+ ACTIONS_ID_TOKEN_REQUEST_TOKEN: ''
1346
+ ACTIONS_ID_TOKEN_REQUEST_URL: ''
1347
+ with:
1348
+ fetch-depth: 0
1349
+ persist-credentials: false
1350
+ ref: \${{ env.EBK_DEPLOYMENT_SOURCE_COMMIT }}
1351
+ - uses: ${actionPins.pnpmSetup}
1352
+ env:
1353
+ ACTIONS_ID_TOKEN_REQUEST_TOKEN: ''
1354
+ ACTIONS_ID_TOKEN_REQUEST_URL: ''
1355
+ - uses: ${actionPins.setupNode}
1356
+ env:
1357
+ ACTIONS_ID_TOKEN_REQUEST_TOKEN: ''
1358
+ ACTIONS_ID_TOKEN_REQUEST_URL: ''
1359
+ with:
1360
+ node-version: ${toolVersions.node}
1361
+ cache: pnpm
1362
+ - env:
1363
+ ACTIONS_ID_TOKEN_REQUEST_TOKEN: ''
1364
+ ACTIONS_ID_TOKEN_REQUEST_URL: ''
1365
+ run: pnpm install --frozen-lockfile --ignore-scripts --ignore-pnpmfile
1366
+ - name: Verify managed workspace integrity before container authority
1367
+ env:
1368
+ ACTIONS_ID_TOKEN_REQUEST_TOKEN: ''
1369
+ ACTIONS_ID_TOKEN_REQUEST_URL: ''
1370
+ run: pnpm nx run ${name}:workspace-health
1371
+ - name: Restore exact immutable release candidate for promotion
1372
+ uses: ${actionPins.downloadArtifact}
1373
+ env:
1374
+ ACTIONS_ID_TOKEN_REQUEST_TOKEN: ''
1375
+ ACTIONS_ID_TOKEN_REQUEST_URL: ''
1376
+ with:
1377
+ artifact-ids: \${{ needs.immutable-artifacts.outputs.artifact_id }}
1378
+ github-token: \${{ github.token }}
1379
+ path: .ebk/evidence/release-candidate
1380
+ run-id: \${{ needs.immutable-artifacts.outputs.producer_run_id }}
1381
+ - name: Restore exact staging container bundle
1382
+ uses: ${actionPins.downloadArtifact}
1383
+ env:
1384
+ ACTIONS_ID_TOKEN_REQUEST_TOKEN: ''
1385
+ ACTIONS_ID_TOKEN_REQUEST_URL: ''
1386
+ with:
1387
+ artifact-ids: \${{ needs.immutable-artifacts.outputs.container_artifact_id }}
1388
+ github-token: \${{ github.token }}
1389
+ path: .ebk/evidence/container-release
1390
+ run-id: \${{ needs.immutable-artifacts.outputs.staging_deployment_run_id }}
1391
+ - name: Revalidate exact protected branch tip before OIDC
1392
+ env:
1393
+ ACTIONS_ID_TOKEN_REQUEST_TOKEN: ''
1394
+ ACTIONS_ID_TOKEN_REQUEST_URL: ''
1395
+ EBK_SOURCE_BRANCH: \${{ env.EBK_DEPLOYMENT_SOURCE_BRANCH }}
1396
+ EBK_SOURCE_COMMIT: \${{ env.EBK_DEPLOYMENT_SOURCE_COMMIT }}
1397
+ GH_REPO: \${{ github.repository }}
1398
+ GH_TOKEN: \${{ github.token }}
1399
+ run: |
1400
+ set -euo pipefail
1401
+ [ "$(git rev-parse HEAD)" = "$EBK_SOURCE_COMMIT" ] || {
1402
+ echo 'Deployment checkout differs from the exact protected source'; exit 1;
1403
+ }
1404
+ branch_tip="$(gh api "/repos/$GH_REPO/git/ref/heads/$EBK_SOURCE_BRANCH" --jq '.object.sha')"
1405
+ [ "$branch_tip" = "$EBK_SOURCE_COMMIT" ] || {
1406
+ echo 'Deployment source is no longer the exact protected branch tip'; exit 1;
1407
+ }
1408
+ - name: Assume exact staging registry pull role
1409
+ uses: ${actionPins.configureAwsCredentials}
1410
+ with:
1411
+ audience: \${{ needs.prepare.outputs.oidc_audience }}
1412
+ aws-region: \${{ vars.AWS_STAGING_PROMOTION_REGION }}
1413
+ role-session-name: ebk-staging-container-pull-\${{ github.run_id }}-\${{ github.run_attempt }}
1414
+ role-to-assume: \${{ secrets.AWS_STAGING_PROMOTION_PULL_ROLE_ARN }}
1415
+ - name: Authenticate exact staging source registry
1416
+ env:
1417
+ AWS_STAGING_PROMOTION_PULL_ROLE_ARN: \${{ secrets.AWS_STAGING_PROMOTION_PULL_ROLE_ARN }}
1418
+ EBK_STAGING_ACCOUNT_ID: \${{ vars.AWS_STAGING_PROMOTION_EXPECTED_ACCOUNT_ID }}
1419
+ EBK_STAGING_REGION: \${{ vars.AWS_STAGING_PROMOTION_REGION }}
1420
+ run: |
1421
+ set -euo pipefail
1422
+ [ "$EBK_STAGING_ACCOUNT_ID" = "$EBK_POLICY_STAGING_ACCOUNT_ID" ]
1423
+ [ "$EBK_STAGING_REGION" = "$EBK_POLICY_REGION" ]
1424
+ ${indentedScript(exactStagingPromotionPullGuardScript(), 10)}
1425
+ [ "$EBK_STAGING_ACCOUNT_ID" != "$AWS_EXPECTED_ACCOUNT_ID" ]
1426
+ registry_suffix=.com
1427
+ case "$EBK_STAGING_REGION" in cn-*) registry_suffix=.com.cn ;; esac
1428
+ aws ecr get-login-password --region "$EBK_STAGING_REGION" |
1429
+ docker login --username AWS --password-stdin \
1430
+ "$EBK_STAGING_ACCOUNT_ID.dkr.ecr.$EBK_STAGING_REGION.amazonaws$registry_suffix"
1431
+ - name: Revalidate exact protected branch tip before OIDC
1432
+ env:
1433
+ ACTIONS_ID_TOKEN_REQUEST_TOKEN: ''
1434
+ ACTIONS_ID_TOKEN_REQUEST_URL: ''
1435
+ EBK_SOURCE_BRANCH: \${{ env.EBK_DEPLOYMENT_SOURCE_BRANCH }}
1436
+ EBK_SOURCE_COMMIT: \${{ env.EBK_DEPLOYMENT_SOURCE_COMMIT }}
1437
+ GH_REPO: \${{ github.repository }}
1438
+ GH_TOKEN: \${{ github.token }}
1439
+ run: |
1440
+ set -euo pipefail
1441
+ [ "$(git rev-parse HEAD)" = "$EBK_SOURCE_COMMIT" ] || {
1442
+ echo 'Deployment checkout differs from the exact protected source'; exit 1;
1443
+ }
1444
+ branch_tip="$(gh api "/repos/$GH_REPO/git/ref/heads/$EBK_SOURCE_BRANCH" --jq '.object.sha')"
1445
+ [ "$branch_tip" = "$EBK_SOURCE_COMMIT" ] || {
1446
+ echo 'Deployment source is no longer the exact protected branch tip'; exit 1;
1447
+ }
1448
+ - name: Assume the production deployment role for container promotion
1449
+ uses: ${actionPins.configureAwsCredentials}
1450
+ with:
1451
+ audience: \${{ needs.prepare.outputs.oidc_audience }}
1452
+ aws-region: \${{ vars.AWS_REGION }}
1453
+ role-session-name: ebk-production-container-push-\${{ github.run_id }}-\${{ github.run_attempt }}
1454
+ role-to-assume: \${{ env.AWS_DEPLOY_ROLE_ARN }}
1455
+ - name: Promote exact staging indexes without rebuilding
1456
+ env:
1457
+ EBK_STAGING_PROMOTION_EXPECTED_ACCOUNT_ID: \${{ vars.AWS_STAGING_PROMOTION_EXPECTED_ACCOUNT_ID }}
1458
+ EBK_STAGING_PROMOTION_REGION: \${{ vars.AWS_STAGING_PROMOTION_REGION }}
1459
+ run: |
1460
+ set -euo pipefail
1461
+ [ "$AWS_EXPECTED_ACCOUNT_ID" = "$EBK_POLICY_PRODUCTION_ACCOUNT_ID" ]
1462
+ [ "$AWS_REGION" = "$EBK_POLICY_REGION" ]
1463
+ ${indentedScript(exactStageDeployerGuardScript('Container promotion', '$AWS_EXPECTED_ACCOUNT_ID'), 10)}
1464
+ IFS=',' read -r -a projects <<< "$EBK_SLICE_PROJECTS"
1465
+ for project in "\${projects[@]}"; do
1466
+ test -n "$project" || continue
1467
+ # The container release tool resolves these against the repository
1468
+ # root, so they stay repository-relative (EBK-INT-2026-077).
1469
+ export EBK_CONTAINER_PROMOTION_SOURCE_BINDING_PATH=".ebk/evidence/container-release/staging/packages/$project/binding.json"
1470
+ export EBK_CONTAINER_PROMOTION_SOURCE_SIDECAR_PATH=".ebk/evidence/container-release/staging/packages/$project/release-bound-binding.json"
1471
+ pnpm nx run "$project:container-release-promote"
1472
+ done
1473
+ - id: retain
1474
+ name: Retain production target container binding
1475
+ uses: ${actionPins.uploadArtifact}
1476
+ with:
1477
+ name: container-release-production-\${{ github.run_id }}-\${{ github.run_attempt }}
1478
+ if-no-files-found: error
1479
+ include-hidden-files: true
1480
+ retention-days: 90
1481
+ path: .ebk/evidence/container-release
1482
+
1483
+ execute:
1484
+ name: native \${{ needs.prepare.outputs.action }} \${{ needs.prepare.outputs.stage }}
1485
+ needs: [prepare, immutable-artifacts, container-assemble, container-promote-production]
1486
+ if: >-
1487
+ always() &&
1488
+ needs.prepare.result == 'success' &&
1489
+ needs.prepare.outputs.action != 'rehearse' &&
1490
+ (
1491
+ needs.prepare.outputs.action != 'deploy' ||
1492
+ (
1493
+ needs.prepare.outputs.has_slices != 'true' &&
1494
+ (needs.prepare.outputs.stage != 'production' || needs.prepare.outputs.profile == 'starter')
1495
+ ) || (
1496
+ needs.immutable-artifacts.result == 'success' &&
1497
+ (
1498
+ needs.prepare.outputs.has_containers != 'true' ||
1499
+ needs.prepare.outputs.stage == 'development' ||
1500
+ (
1501
+ (
1502
+ needs.prepare.outputs.stage == 'staging' ||
1503
+ (needs.prepare.outputs.stage == 'production' && needs.prepare.outputs.profile == 'starter')
1504
+ ) &&
1505
+ needs.container-assemble.result == 'success'
1506
+ ) ||
1507
+ (
1508
+ needs.prepare.outputs.stage == 'production' &&
1509
+ needs.prepare.outputs.profile != 'starter' &&
1510
+ needs.container-promote-production.result == 'success'
1511
+ )
1512
+ )
1513
+ )
1514
+ )
1515
+ runs-on: ubuntu-latest
1516
+ environment: \${{ needs.prepare.outputs.stage }}
1517
+ permissions:
1518
+ actions: read
1519
+ contents: read
1520
+ id-token: write
1521
+ env:
1522
+ AWS_DEPLOY_ROLE_ARN: \${{ needs.prepare.outputs.stage_deploy_role_arn }}
1523
+ AWS_EXPECTED_ACCOUNT_ID: \${{ vars.AWS_EXPECTED_ACCOUNT_ID }}
1524
+ AWS_PARTITION: \${{ needs.prepare.outputs.partition }}
1525
+ AWS_REGION: \${{ vars.AWS_REGION }}
1526
+ EBK_ACTION: \${{ needs.prepare.outputs.action }}
1527
+ EBK_ALLOW_PRODUCTION_REMOVE: \${{ vars.EBK_ALLOW_PRODUCTION_REMOVE }}
1528
+ EBK_CANDIDATE_ARTIFACT_DIGEST: \${{ needs.immutable-artifacts.outputs.artifact_digest }}
1529
+ EBK_CANDIDATE_ARTIFACT_ID: \${{ needs.immutable-artifacts.outputs.artifact_id }}
1530
+ EBK_CANDIDATE_MATERIAL_DIGEST: \${{ needs.immutable-artifacts.outputs.candidate_material_digest }}
1531
+ EBK_CANDIDATE_PRODUCER_RUN_ATTEMPT: \${{ needs.immutable-artifacts.outputs.producer_run_attempt }}
1532
+ EBK_CANDIDATE_PRODUCER_RUN_ID: \${{ needs.immutable-artifacts.outputs.producer_run_id }}
1533
+ EBK_DEPLOYMENT_PROFILE: \${{ needs.prepare.outputs.profile }}
1534
+ EBK_ORDERED_PROJECTS: \${{ needs.prepare.outputs.ordered_projects }}
1535
+ EBK_POLICY_ACCOUNT_ID: \${{ needs.prepare.outputs.account_id }}
1536
+ EBK_POLICY_REGION: \${{ needs.prepare.outputs.region }}
1537
+ EBK_RELEASE_SOURCE_COMMIT: \${{ needs.immutable-artifacts.outputs.source_commit || inputs.source_commit || github.sha }}
1538
+ EBK_SLICE_PROJECTS: \${{ needs.prepare.outputs.slice_projects }}
1539
+ EBK_SOURCE_COMMIT: \${{ inputs.source_commit || github.sha }}
1540
+ EBK_STAGE: \${{ needs.prepare.outputs.stage }}
1541
+ EBK_LIFECYCLE_SCOPE: \${{ inputs.lifecycle || 'affected' }}
1542
+ EBK_BASELINE_FOUND: \${{ needs.immutable-artifacts.outputs.baseline_found }}
1543
+ EBK_BASELINE_RUN_ID: \${{ needs.immutable-artifacts.outputs.baseline_run_id }}
1544
+ steps:
1545
+ - name: Check out immutable deployment source
1546
+ uses: ${actionPins.checkout}
1547
+ env:
1548
+ ACTIONS_ID_TOKEN_REQUEST_TOKEN: ''
1549
+ ACTIONS_ID_TOKEN_REQUEST_URL: ''
1550
+ with:
1551
+ fetch-depth: 0
1552
+ persist-credentials: false
1553
+ ref: \${{ env.EBK_DEPLOYMENT_SOURCE_COMMIT }}
1554
+ - uses: ${actionPins.pnpmSetup}
1555
+ env:
1556
+ ACTIONS_ID_TOKEN_REQUEST_TOKEN: ''
1557
+ ACTIONS_ID_TOKEN_REQUEST_URL: ''
1558
+ - uses: ${actionPins.setupNode}
1559
+ env:
1560
+ ACTIONS_ID_TOKEN_REQUEST_TOKEN: ''
1561
+ ACTIONS_ID_TOKEN_REQUEST_URL: ''
1562
+ with:
1563
+ node-version: ${toolVersions.node}
1564
+ cache: pnpm
1565
+ - env:
1566
+ ACTIONS_ID_TOKEN_REQUEST_TOKEN: ''
1567
+ ACTIONS_ID_TOKEN_REQUEST_URL: ''
1568
+ run: pnpm install --frozen-lockfile --ignore-scripts --ignore-pnpmfile
1569
+ - name: Verify managed workspace integrity before AWS authority
1570
+ env:
1571
+ ACTIONS_ID_TOKEN_REQUEST_TOKEN: ''
1572
+ ACTIONS_ID_TOKEN_REQUEST_URL: ''
1573
+ run: pnpm nx run ${name}:workspace-health
1574
+ - name: Restore exact immutable release candidate
1575
+ if: needs.prepare.outputs.action == 'deploy' && needs.prepare.outputs.has_slices == 'true'
1576
+ uses: ${actionPins.downloadArtifact}
1577
+ env:
1578
+ ACTIONS_ID_TOKEN_REQUEST_TOKEN: ''
1579
+ ACTIONS_ID_TOKEN_REQUEST_URL: ''
1580
+ with:
1581
+ artifact-ids: \${{ needs.immutable-artifacts.outputs.artifact_id }}
1582
+ github-token: \${{ github.token }}
1583
+ path: .ebk/evidence/release-candidate
1584
+ run-id: \${{ needs.immutable-artifacts.outputs.producer_run_id }}
1585
+ - name: Restore the baseline deployment receipt for unchanged Slices
1586
+ if: needs.prepare.outputs.action == 'deploy' && needs.immutable-artifacts.outputs.baseline_found == 'true'
1587
+ uses: ${actionPins.downloadArtifact}
1588
+ env:
1589
+ ACTIONS_ID_TOKEN_REQUEST_TOKEN: ''
1590
+ ACTIONS_ID_TOKEN_REQUEST_URL: ''
1591
+ with:
1592
+ artifact-ids: \${{ needs.immutable-artifacts.outputs.baseline_artifact_id }}
1593
+ github-token: \${{ github.token }}
1594
+ path: \${{ runner.temp }}/ebk-deployment-baseline
1595
+ run-id: \${{ needs.immutable-artifacts.outputs.baseline_run_id }}
1596
+ - name: Restore exact current-stage container binding
1597
+ if: >-
1598
+ needs.prepare.outputs.action == 'deploy' &&
1599
+ needs.prepare.outputs.has_containers == 'true' &&
1600
+ (
1601
+ needs.prepare.outputs.stage == 'staging' ||
1602
+ (needs.prepare.outputs.stage == 'production' && needs.prepare.outputs.profile == 'starter')
1603
+ )
1604
+ uses: ${actionPins.downloadArtifact}
1605
+ env:
1606
+ ACTIONS_ID_TOKEN_REQUEST_TOKEN: ''
1607
+ ACTIONS_ID_TOKEN_REQUEST_URL: ''
1608
+ with:
1609
+ name: container-release-\${{ needs.prepare.outputs.stage }}-\${{ github.run_id }}-\${{ github.run_attempt }}
1610
+ path: .ebk/evidence/container-release
1611
+ - name: Restore exact promoted production container binding
1612
+ if: >-
1613
+ needs.prepare.outputs.action == 'deploy' &&
1614
+ needs.prepare.outputs.has_containers == 'true' &&
1615
+ needs.prepare.outputs.stage == 'production' &&
1616
+ needs.prepare.outputs.profile != 'starter'
1617
+ uses: ${actionPins.downloadArtifact}
1618
+ env:
1619
+ ACTIONS_ID_TOKEN_REQUEST_TOKEN: ''
1620
+ ACTIONS_ID_TOKEN_REQUEST_URL: ''
1621
+ with:
1622
+ name: container-release-production-\${{ github.run_id }}-\${{ github.run_attempt }}
1623
+ path: .ebk/evidence/container-release
1624
+ - name: Verify exact predeploy container binding inventory
1625
+ if: >-
1626
+ needs.prepare.outputs.action == 'deploy' &&
1627
+ needs.prepare.outputs.has_containers == 'true' &&
1628
+ needs.prepare.outputs.stage != 'development'
1629
+ env:
1630
+ ACTIONS_ID_TOKEN_REQUEST_TOKEN: ''
1631
+ ACTIONS_ID_TOKEN_REQUEST_URL: ''
1632
+ EBK_CONTAINER_PROJECTS: \${{ needs.prepare.outputs.container_projects }}
1633
+ run: |
1634
+ set -euo pipefail
1635
+ IFS=',' read -r -a projects <<< "$EBK_CONTAINER_PROJECTS"
1636
+ for project in "\${projects[@]}"; do
1637
+ test -n "$project" || continue
1638
+ test -f ".ebk/evidence/container-release/$EBK_STAGE/packages/$project/binding.json" || {
1639
+ echo "Exact predeploy container binding is absent for $project"
1640
+ exit 1
1641
+ }
1642
+ done
1643
+ - name: Verify exact restored staging-tested release candidate
1644
+ if: env.EBK_ACTION == 'deploy' && env.EBK_STAGE == 'production' && needs.prepare.outputs.profile != 'starter' && needs.prepare.outputs.has_slices == 'true'
1645
+ env:
1646
+ ACTIONS_ID_TOKEN_REQUEST_TOKEN: ''
1647
+ ACTIONS_ID_TOKEN_REQUEST_URL: ''
1648
+ run: |
1649
+ node --input-type=module <<'NODE'
1650
+ ${indentedScript(restoredCandidateValidationScript(), 10)}
1651
+ NODE
1652
+ - name: Authorize deployed Blueprint trust before protected mutation
1653
+ if: env.EBK_ACTION == 'deploy'
1654
+ env:
1655
+ ACTIONS_ID_TOKEN_REQUEST_TOKEN: ''
1656
+ ACTIONS_ID_TOKEN_REQUEST_URL: ''
1657
+ EBK_PROTECTED_BLUEPRINT_COMMIT: \${{ env.EBK_DEPLOYMENT_SOURCE_COMMIT }}
1658
+ run: |
1659
+ set -euo pipefail
1660
+ case "$EBK_PROTECTED_BLUEPRINT_COMMIT" in
1661
+ ''|*[!0-9a-f]*) echo 'Protected Blueprint authority must be one full lowercase Git object ID'; exit 1 ;;
1662
+ esac
1663
+ test "\${#EBK_PROTECTED_BLUEPRINT_COMMIT}" -eq 40 ||
1664
+ test "\${#EBK_PROTECTED_BLUEPRINT_COMMIT}" -eq 64 ||
1665
+ { echo 'Protected Blueprint authority must be one full Git object ID'; exit 1; }
1666
+ test "$(git rev-parse HEAD)" = "$EBK_PROTECTED_BLUEPRINT_COMMIT" ||
1667
+ { echo 'Protected Blueprint authority differs from the exact workflow checkout'; exit 1; }
1668
+ case "$EBK_STAGE" in
1669
+ staging) protected_branch=develop ;;
1670
+ production) protected_branch=main ;;
1671
+ *) exit 1 ;;
1672
+ esac
1673
+ node tools/blueprint-trust-check.mjs \
1674
+ --operation=deploy \
1675
+ --protected-branch="$protected_branch" \
1676
+ --protected-ref="$EBK_PROTECTED_BLUEPRINT_COMMIT" \
1677
+ --output=.ebk/evidence/supply-chain/blueprint-trust.json
1678
+ - name: Produce subject-bound protected scanner evidence
1679
+ if: env.EBK_ACTION == 'deploy'
1680
+ env:
1681
+ ACTIONS_ID_TOKEN_REQUEST_TOKEN: ''
1682
+ ACTIONS_ID_TOKEN_REQUEST_URL: ''
1683
+ run: pnpm nx run ${name}:supply-chain-scan
1684
+ - name: Enforce protected supply-chain policy before mutation
1685
+ if: env.EBK_ACTION == 'deploy'
1686
+ env:
1687
+ ACTIONS_ID_TOKEN_REQUEST_TOKEN: ''
1688
+ ACTIONS_ID_TOKEN_REQUEST_URL: ''
1689
+ run: pnpm nx run ${name}:supply-chain-check-protected
1690
+ - name: Install pinned Playwright Chromium for deployed smoke proofs
1691
+ if: env.EBK_ACTION == 'deploy' && needs.prepare.outputs.has_slices == 'true'
1692
+ env:
1693
+ ACTIONS_ID_TOKEN_REQUEST_TOKEN: ''
1694
+ ACTIONS_ID_TOKEN_REQUEST_URL: ''
1695
+ run: |
1696
+ ${indentedScript(protectedBrowserInstallScript(), 10)}
1697
+ ${machineCredentialRequestStep()}
1698
+ - name: Revalidate exact protected branch tip before OIDC
1699
+ env:
1700
+ ACTIONS_ID_TOKEN_REQUEST_TOKEN: ''
1701
+ ACTIONS_ID_TOKEN_REQUEST_URL: ''
1702
+ EBK_SOURCE_BRANCH: \${{ env.EBK_DEPLOYMENT_SOURCE_BRANCH }}
1703
+ EBK_SOURCE_COMMIT: \${{ env.EBK_DEPLOYMENT_SOURCE_COMMIT }}
1704
+ GH_REPO: \${{ github.repository }}
1705
+ GH_TOKEN: \${{ github.token }}
1706
+ run: |
1707
+ set -euo pipefail
1708
+ [ "$(git rev-parse HEAD)" = "$EBK_SOURCE_COMMIT" ] || {
1709
+ echo 'Deployment checkout differs from the exact protected source'; exit 1;
1710
+ }
1711
+ branch_tip="$(gh api "/repos/$GH_REPO/git/ref/heads/$EBK_SOURCE_BRANCH" --jq '.object.sha')"
1712
+ [ "$branch_tip" = "$EBK_SOURCE_COMMIT" ] || {
1713
+ echo 'Deployment source is no longer the exact protected branch tip'; exit 1;
1714
+ }
1715
+ - name: Assume the one stage deployment role
1716
+ uses: ${actionPins.configureAwsCredentials}
1717
+ with:
1718
+ audience: \${{ needs.prepare.outputs.oidc_audience }}
1719
+ aws-region: \${{ vars.AWS_REGION }}
1720
+ role-session-name: ebk-native-\${{ github.run_id }}-\${{ github.run_attempt }}
1721
+ role-to-assume: \${{ env.AWS_DEPLOY_ROLE_ARN }}
1722
+ - name: Verify exact stage authority
1723
+ run: |
1724
+ set -euo pipefail
1725
+ [ "$AWS_EXPECTED_ACCOUNT_ID" = "$EBK_POLICY_ACCOUNT_ID" ]
1726
+ [ "$AWS_REGION" = "$EBK_POLICY_REGION" ]
1727
+ ${indentedScript(exactStageDeployerGuardScript('Native lifecycle', '$EBK_POLICY_ACCOUNT_ID'), 10)}
1728
+ - name: Run ordered native lifecycle targets
1729
+ id: native-lifecycle
1730
+ env:
1731
+ ACTIONS_ID_TOKEN_REQUEST_TOKEN: ''
1732
+ ACTIONS_ID_TOKEN_REQUEST_URL: ''
1733
+ EBK_GITHUB_JOB_WORKFLOW_REF: \${{ job.workflow_ref }}
1734
+ GITHUB_TOKEN: \${{ github.token }}
1735
+ run: |
1736
+ set -euo pipefail
1737
+ test -n "$AWS_DEPLOY_ROLE_ARN" || { echo 'Stage deployment role is absent'; exit 1; }
1738
+ if [ "$EBK_ACTION" = deploy ]; then
1739
+ case "$EBK_LIFECYCLE_SCOPE" in affected|full) ;; *) echo 'Lifecycle scope must be affected or full'; exit 1 ;; esac
1740
+ baseline_arguments=()
1741
+ if [ "$EBK_LIFECYCLE_SCOPE" = affected ] && [ "$EBK_BASELINE_FOUND" = true ]; then
1742
+ case "$EBK_BASELINE_RUN_ID" in ''|*[!0-9]*) echo 'Baseline run id is malformed'; exit 1 ;; esac
1743
+ baseline_arguments=(
1744
+ --baselineReceipt="$RUNNER_TEMP/ebk-deployment-baseline/deployment/latest.json"
1745
+ --baselineRunId="$EBK_BASELINE_RUN_ID"
1746
+ --baselineRunAttempt=1
1747
+ )
1748
+ fi
1749
+ pnpm nx run ${name}:deploy-local -- \
1750
+ --stage="$EBK_STAGE" \
1751
+ --expectedRoleArn="$AWS_DEPLOY_ROLE_ARN" \
1752
+ --lifecycleScope="$EBK_LIFECYCLE_SCOPE" \
1753
+ "\${baseline_arguments[@]}"
1754
+ else
1755
+ [ -n "$EBK_ORDERED_PROJECTS" ] || { echo 'Committed deployment order is empty'; exit 1; }
1756
+ IFS=',' read -r -a projects <<< "$EBK_ORDERED_PROJECTS"
1757
+ seen=,
1758
+ for project in "\${projects[@]}"; do
1759
+ case "$project" in ''|*[!A-Za-z0-9_-]*) exit 1 ;; esac
1760
+ case "$seen" in *,"$project",*) echo "Duplicate lifecycle project: $project"; exit 1 ;; esac
1761
+ seen="$seen$project,"
1762
+ pnpm nx run "$project:$EBK_ACTION" -- --stage="$EBK_STAGE" --region="$AWS_REGION" --expectedRoleArn="$AWS_DEPLOY_ROLE_ARN"
1763
+ done
1764
+ fi
1765
+ - name: Prove temporary smoke admission removed
1766
+ if: always() && env.EBK_ACTION == 'deploy' && steps.native-lifecycle.outcome != 'skipped'
1767
+ env:
1768
+ ACTIONS_ID_TOKEN_REQUEST_TOKEN: ''
1769
+ ACTIONS_ID_TOKEN_REQUEST_URL: ''
1770
+ EBK_GITHUB_JOB_WORKFLOW_REF: \${{ job.workflow_ref }}
1771
+ GITHUB_TOKEN: \${{ github.token }}
1772
+ run: |
1773
+ set -euo pipefail
1774
+ ${indentedScript(exactStageDeployerGuardScript('Smoke access cleanup', '$EBK_POLICY_ACCOUNT_ID'), 10)}
1775
+ [ "$(git rev-parse HEAD)" = "$EBK_SOURCE_COMMIT" ]
1776
+ git diff --quiet HEAD
1777
+ node --input-type=module <<'NODE'
1778
+ import { cleanupGithubPrivateSmoke } from '@empire-builder-kit/nx/foundation';
1779
+ await cleanupGithubPrivateSmoke({
1780
+ account: process.env.EBK_POLICY_ACCOUNT_ID,
1781
+ deploymentProfile: process.env.EBK_DEPLOYMENT_PROFILE,
1782
+ root: process.cwd(),
1783
+ source: process.env.EBK_SOURCE_COMMIT,
1784
+ stage: process.env.EBK_STAGE,
1785
+ workspace: '${name}',
1786
+ });
1787
+ NODE
1788
+ - name: Seal exact staging promotion receipt
1789
+ if: env.EBK_ACTION == 'deploy' && env.EBK_STAGE == 'staging' && needs.prepare.outputs.profile != 'starter'
1790
+ env:
1791
+ ACTIONS_ID_TOKEN_REQUEST_TOKEN: ''
1792
+ ACTIONS_ID_TOKEN_REQUEST_URL: ''
1793
+ EBK_CONTAINER_ARTIFACT_DIGEST: \${{ needs.container-assemble.outputs.artifact_digest }}
1794
+ EBK_CONTAINER_ARTIFACT_ID: \${{ needs.container-assemble.outputs.artifact_id }}
1795
+ EBK_HAS_SLICES: \${{ needs.prepare.outputs.has_slices }}
1796
+ EBK_HAS_CONTAINERS: \${{ needs.prepare.outputs.has_containers }}
1797
+ run: |
1798
+ node --input-type=module <<'NODE'
1799
+ ${indentedScript(stagingPromotionSealScript(), 10)}
1800
+ NODE
1801
+ - name: Seal exact postdeploy supply-chain binding
1802
+ if: env.EBK_ACTION == 'deploy' && env.EBK_STAGE != 'development' && needs.prepare.outputs.has_slices == 'true'
1803
+ env:
1804
+ ACTIONS_ID_TOKEN_REQUEST_TOKEN: ''
1805
+ ACTIONS_ID_TOKEN_REQUEST_URL: ''
1806
+ run: |
1807
+ node --input-type=module <<'NODE'
1808
+ ${indentedScript(postdeploySupplyChainBindingScript(), 10)}
1809
+ NODE
1810
+ - name: Retain bounded deployment receipt
1811
+ if: always() && needs.prepare.outputs.action == 'deploy'
1812
+ uses: ${actionPins.uploadArtifact}
1813
+ with:
1814
+ if-no-files-found: warn
1815
+ name: deployment-receipt-\${{ needs.prepare.outputs.stage }}-\${{ github.run_id }}-\${{ github.run_attempt }}
1816
+ path: |
1817
+ .ebk/evidence/deployment/latest.json
1818
+ .ebk/evidence/deployment/postdeploy-supply-chain.json
1819
+ .ebk/evidence/deployment/promotion.json
1820
+ .ebk/evidence/container-release/**
1821
+ retention-days: 30
1822
+ ${deploymentProfile === 'starter' ? '' : renderRehearseJob(options)}${trunkPromotion ? renderTrunkPromotionJobs(options) : ''}`;
1823
+ }
1824
+ /**
1825
+ * Managed Router WAF blocking rehearsal (EBK-INT-2026-079). The protected
1826
+ * dispatch runs the foundation rehearsal target under the staging deployment
1827
+ * role with seedProbes, so the runner performs the operator probes itself
1828
+ * inside the private-smoke allowlist admission, proves the admission removed,
1829
+ * and opens the pull request that carries the sealed evidence and receipt
1830
+ * back to develop through the ordinary review path.
1831
+ */
1832
+ function renderRehearseJob(options) {
1833
+ const { actionPins, name, toolVersions } = options;
1834
+ return `
1835
+ rehearse:
1836
+ name: rehearse staging Router WAF blocking
1837
+ needs: prepare
1838
+ if: >-
1839
+ needs.prepare.result == 'success' &&
1840
+ needs.prepare.outputs.action == 'rehearse' &&
1841
+ needs.prepare.outputs.stage == 'staging' &&
1842
+ needs.prepare.outputs.profile != 'starter'
1843
+ runs-on: ubuntu-latest
1844
+ environment: \${{ needs.prepare.outputs.stage }}
1845
+ permissions:
1846
+ actions: read
1847
+ contents: write
1848
+ id-token: write
1849
+ pull-requests: write
1850
+ env:
1851
+ AWS_DEPLOY_ROLE_ARN: \${{ needs.prepare.outputs.stage_deploy_role_arn }}
1852
+ AWS_EXPECTED_ACCOUNT_ID: \${{ vars.AWS_EXPECTED_ACCOUNT_ID }}
1853
+ AWS_PARTITION: \${{ needs.prepare.outputs.partition }}
1854
+ AWS_REGION: \${{ vars.AWS_REGION }}
1855
+ EBK_ACTION: rehearse
1856
+ EBK_DEPLOYMENT_PROFILE: \${{ needs.prepare.outputs.profile }}
1857
+ EBK_POLICY_ACCOUNT_ID: \${{ needs.prepare.outputs.account_id }}
1858
+ EBK_POLICY_REGION: \${{ needs.prepare.outputs.region }}
1859
+ EBK_PROBE_URL: \${{ needs.prepare.outputs.probe_url }}
1860
+ EBK_SOURCE_COMMIT: \${{ inputs.source_commit || github.sha }}
1861
+ EBK_STAGE: \${{ needs.prepare.outputs.stage }}
1862
+ steps:
1863
+ - name: Check out immutable deployment source
1864
+ uses: ${actionPins.checkout}
1865
+ env:
1866
+ ACTIONS_ID_TOKEN_REQUEST_TOKEN: ''
1867
+ ACTIONS_ID_TOKEN_REQUEST_URL: ''
1868
+ with:
1869
+ fetch-depth: 0
1870
+ persist-credentials: false
1871
+ ref: \${{ env.EBK_DEPLOYMENT_SOURCE_COMMIT }}
1872
+ - uses: ${actionPins.pnpmSetup}
1873
+ env:
1874
+ ACTIONS_ID_TOKEN_REQUEST_TOKEN: ''
1875
+ ACTIONS_ID_TOKEN_REQUEST_URL: ''
1876
+ - uses: ${actionPins.setupNode}
1877
+ env:
1878
+ ACTIONS_ID_TOKEN_REQUEST_TOKEN: ''
1879
+ ACTIONS_ID_TOKEN_REQUEST_URL: ''
1880
+ with:
1881
+ node-version: ${toolVersions.node}
1882
+ cache: pnpm
1883
+ - env:
1884
+ ACTIONS_ID_TOKEN_REQUEST_TOKEN: ''
1885
+ ACTIONS_ID_TOKEN_REQUEST_URL: ''
1886
+ run: pnpm install --frozen-lockfile --ignore-scripts --ignore-pnpmfile
1887
+ - name: Verify managed workspace integrity before AWS authority
1888
+ env:
1889
+ ACTIONS_ID_TOKEN_REQUEST_TOKEN: ''
1890
+ ACTIONS_ID_TOKEN_REQUEST_URL: ''
1891
+ run: pnpm nx run ${name}:workspace-health
1892
+ - name: Require the exact pending rehearsal seeds
1893
+ env:
1894
+ ACTIONS_ID_TOKEN_REQUEST_TOKEN: ''
1895
+ ACTIONS_ID_TOKEN_REQUEST_URL: ''
1896
+ run: |
1897
+ set -euo pipefail
1898
+ for seed in router-waf-live-evidence router-waf-blocking-rehearsal; do
1899
+ [ "$(jq -r '.status // empty' "packages/foundation/security/$seed.json")" = pending ] || {
1900
+ echo "packages/foundation/security/$seed.json must be reset to its pending seed before another rehearsal"; exit 1;
1901
+ }
1902
+ done
1903
+ - name: Revalidate exact protected branch tip before OIDC
1904
+ env:
1905
+ ACTIONS_ID_TOKEN_REQUEST_TOKEN: ''
1906
+ ACTIONS_ID_TOKEN_REQUEST_URL: ''
1907
+ EBK_SOURCE_BRANCH: \${{ env.EBK_DEPLOYMENT_SOURCE_BRANCH }}
1908
+ EBK_SOURCE_COMMIT: \${{ env.EBK_DEPLOYMENT_SOURCE_COMMIT }}
1909
+ GH_REPO: \${{ github.repository }}
1910
+ GH_TOKEN: \${{ github.token }}
1911
+ run: |
1912
+ set -euo pipefail
1913
+ [ "$(git rev-parse HEAD)" = "$EBK_SOURCE_COMMIT" ] || {
1914
+ echo 'Deployment checkout differs from the exact protected source'; exit 1;
1915
+ }
1916
+ branch_tip="$(gh api "/repos/$GH_REPO/git/ref/heads/$EBK_SOURCE_BRANCH" --jq '.object.sha')"
1917
+ [ "$branch_tip" = "$EBK_SOURCE_COMMIT" ] || {
1918
+ echo 'Deployment source is no longer the exact protected branch tip'; exit 1;
1919
+ }
1920
+ - name: Assume the one stage deployment role
1921
+ uses: ${actionPins.configureAwsCredentials}
1922
+ with:
1923
+ audience: \${{ needs.prepare.outputs.oidc_audience }}
1924
+ aws-region: \${{ vars.AWS_REGION }}
1925
+ role-session-name: ebk-rehearse-\${{ github.run_id }}-\${{ github.run_attempt }}
1926
+ role-to-assume: \${{ env.AWS_DEPLOY_ROLE_ARN }}
1927
+ - name: Verify exact stage authority
1928
+ run: |
1929
+ set -euo pipefail
1930
+ [ "$AWS_EXPECTED_ACCOUNT_ID" = "$EBK_POLICY_ACCOUNT_ID" ]
1931
+ [ "$AWS_REGION" = "$EBK_POLICY_REGION" ]
1932
+ ${indentedScript(exactStageDeployerGuardScript('Router WAF rehearsal', '$EBK_POLICY_ACCOUNT_ID'), 10)}
1933
+ - name: Rehearse Router WAF blocking with runner-seeded probes
1934
+ id: rehearsal
1935
+ env:
1936
+ ACTIONS_ID_TOKEN_REQUEST_TOKEN: ''
1937
+ ACTIONS_ID_TOKEN_REQUEST_URL: ''
1938
+ EBK_GITHUB_JOB_WORKFLOW_REF: \${{ job.workflow_ref }}
1939
+ GITHUB_TOKEN: \${{ github.token }}
1940
+ run: |
1941
+ set -euo pipefail
1942
+ test -n "$AWS_DEPLOY_ROLE_ARN" || { echo 'Stage deployment role is absent'; exit 1; }
1943
+ pnpm nx run foundation:router-waf-rehearsal -- \\
1944
+ --probeUrl="$EBK_PROBE_URL" \\
1945
+ --seedProbes=true
1946
+ - name: Prove temporary smoke admission removed
1947
+ if: always() && steps.rehearsal.outcome != 'skipped'
1948
+ env:
1949
+ ACTIONS_ID_TOKEN_REQUEST_TOKEN: ''
1950
+ ACTIONS_ID_TOKEN_REQUEST_URL: ''
1951
+ EBK_GITHUB_JOB_WORKFLOW_REF: \${{ job.workflow_ref }}
1952
+ GITHUB_TOKEN: \${{ github.token }}
1953
+ run: |
1954
+ set -euo pipefail
1955
+ ${indentedScript(exactStageDeployerGuardScript('Smoke access cleanup', '$EBK_POLICY_ACCOUNT_ID'), 10)}
1956
+ [ "$(git rev-parse HEAD)" = "$EBK_SOURCE_COMMIT" ]
1957
+ node --input-type=module <<'NODE'
1958
+ import { cleanupGithubPrivateSmoke } from '@empire-builder-kit/nx/foundation';
1959
+ await cleanupGithubPrivateSmoke({
1960
+ account: process.env.EBK_POLICY_ACCOUNT_ID,
1961
+ deploymentProfile: process.env.EBK_DEPLOYMENT_PROFILE,
1962
+ root: process.cwd(),
1963
+ source: process.env.EBK_SOURCE_COMMIT,
1964
+ stage: process.env.EBK_STAGE,
1965
+ workspace: '${name}',
1966
+ });
1967
+ NODE
1968
+ - name: Retain the sealed rehearsal evidence
1969
+ uses: ${actionPins.uploadArtifact}
1970
+ with:
1971
+ if-no-files-found: error
1972
+ name: router-waf-rehearsal-\${{ github.run_id }}-\${{ github.run_attempt }}
1973
+ path: |
1974
+ packages/foundation/security/router-waf-live-evidence.json
1975
+ packages/foundation/security/router-waf-blocking-rehearsal.json
1976
+ retention-days: 90
1977
+ - name: Open the rehearsal receipt pull request
1978
+ env:
1979
+ ACTIONS_ID_TOKEN_REQUEST_TOKEN: ''
1980
+ ACTIONS_ID_TOKEN_REQUEST_URL: ''
1981
+ EBK_RUN_ATTEMPT: \${{ github.run_attempt }}
1982
+ EBK_RUN_ID: \${{ github.run_id }}
1983
+ GH_REPO: \${{ github.repository }}
1984
+ GH_TOKEN: \${{ github.token }}
1985
+ run: |
1986
+ set -euo pipefail
1987
+ case "$EBK_RUN_ID:$EBK_RUN_ATTEMPT" in *[!0-9:]*|:*|*:) exit 1 ;; esac
1988
+ branch="ebk/router-waf-rehearsal-$EBK_RUN_ID-$EBK_RUN_ATTEMPT"
1989
+ git diff --quiet -- packages/foundation/security && { echo 'The rehearsal sealed nothing new'; exit 1; }
1990
+ git config user.name 'ebk-router-waf-rehearsal[bot]'
1991
+ git config user.email 'ebk-router-waf-rehearsal[bot]@users.noreply.github.com'
1992
+ git checkout -q -b "$branch"
1993
+ git add packages/foundation/security/router-waf-live-evidence.json packages/foundation/security/router-waf-blocking-rehearsal.json
1994
+ git commit -q -m "chore(security): seal the staging Router WAF blocking rehearsal (run $EBK_RUN_ID)"
1995
+ # git over HTTPS authenticates with basic x-access-token, the same
1996
+ # header actions/checkout persists; a bearer header makes git fall
1997
+ # back to a username prompt (EBK-INT-2026-085).
1998
+ authorization="$(printf 'x-access-token:%s' "$GH_TOKEN" | base64 | tr -d '\\n')"
1999
+ git -c "http.https://github.com/.extraheader=AUTHORIZATION: basic $authorization" push -q origin "HEAD:refs/heads/$branch"
2000
+ gh pr create \\
2001
+ --head "$branch" \\
2002
+ --base develop \\
2003
+ --title 'chore(security): seal the staging Router WAF blocking rehearsal' \\
2004
+ --body "Managed rehearsal run $EBK_RUN_ID attempt $EBK_RUN_ATTEMPT sealed the staging Router WAF blocking rehearsal for the committed policy digest. Merge to unblock the production Router WAF gate; the receipt binds the exact account, Region, and policy digest."
2005
+ `;
2006
+ }
2007
+ /**
2008
+ * Trunk promotion: after a successful staging lifecycle on develop, the same
2009
+ * run binds its own staging receipt, promotes the reviewed OCI indexes into
2010
+ * production, deploys production, and fast-forwards main to the deployed
2011
+ * commit. Every job carries the production environment so environment
2012
+ * reviewers can still gate it, and every step reuses the promotion-pr scripts.
2013
+ */
2014
+ function renderTrunkPromotionJobs(options) {
2015
+ const { actionPins, name, toolVersions } = options;
2016
+ const checkoutAndInstall = (integrityStep) => ` - name: Check out immutable deployment source
2017
+ uses: ${actionPins.checkout}
2018
+ env:
2019
+ ACTIONS_ID_TOKEN_REQUEST_TOKEN: ''
2020
+ ACTIONS_ID_TOKEN_REQUEST_URL: ''
2021
+ with:
2022
+ fetch-depth: 0
2023
+ persist-credentials: false
2024
+ ref: \${{ env.EBK_DEPLOYMENT_SOURCE_COMMIT }}
2025
+ - uses: ${actionPins.pnpmSetup}
2026
+ env:
2027
+ ACTIONS_ID_TOKEN_REQUEST_TOKEN: ''
2028
+ ACTIONS_ID_TOKEN_REQUEST_URL: ''
2029
+ - uses: ${actionPins.setupNode}
2030
+ env:
2031
+ ACTIONS_ID_TOKEN_REQUEST_TOKEN: ''
2032
+ ACTIONS_ID_TOKEN_REQUEST_URL: ''
2033
+ with:
2034
+ node-version: ${toolVersions.node}
2035
+ cache: pnpm
2036
+ - env:
2037
+ ACTIONS_ID_TOKEN_REQUEST_TOKEN: ''
2038
+ ACTIONS_ID_TOKEN_REQUEST_URL: ''
2039
+ run: pnpm install --frozen-lockfile --ignore-scripts --ignore-pnpmfile
2040
+ - name: ${integrityStep}
2041
+ env:
2042
+ ACTIONS_ID_TOKEN_REQUEST_TOKEN: ''
2043
+ ACTIONS_ID_TOKEN_REQUEST_URL: ''
2044
+ run: pnpm nx run ${name}:workspace-health`;
2045
+ const revalidateTip = ` - name: Revalidate exact protected branch tip before OIDC
2046
+ env:
2047
+ ACTIONS_ID_TOKEN_REQUEST_TOKEN: ''
2048
+ ACTIONS_ID_TOKEN_REQUEST_URL: ''
2049
+ EBK_SOURCE_BRANCH: \${{ env.EBK_DEPLOYMENT_SOURCE_BRANCH }}
2050
+ EBK_SOURCE_COMMIT: \${{ env.EBK_DEPLOYMENT_SOURCE_COMMIT }}
2051
+ GH_REPO: \${{ github.repository }}
2052
+ GH_TOKEN: \${{ github.token }}
2053
+ run: |
2054
+ set -euo pipefail
2055
+ [ "$(git rev-parse HEAD)" = "$EBK_SOURCE_COMMIT" ] || {
2056
+ echo 'Deployment checkout differs from the exact protected source'; exit 1;
2057
+ }
2058
+ branch_tip="$(gh api "/repos/$GH_REPO/git/ref/heads/$EBK_SOURCE_BRANCH" --jq '.object.sha')"
2059
+ [ "$branch_tip" = "$EBK_SOURCE_COMMIT" ] || {
2060
+ echo 'Deployment source is no longer the exact protected branch tip'; exit 1;
2061
+ }`;
2062
+ const trunkPromotionGate = ` needs.prepare.outputs.action == 'deploy' &&
2063
+ needs.prepare.outputs.stage == 'staging' &&
2064
+ needs.prepare.outputs.profile != 'starter' &&
2065
+ needs.prepare.outputs.has_slices == 'true'`;
2066
+ return `
2067
+ promote-production:
2068
+ name: promote the staging-tested candidate to production
2069
+ needs: [prepare, immutable-artifacts, container-assemble, execute]
2070
+ if: >-
2071
+ always() &&
2072
+ needs.prepare.result == 'success' &&
2073
+ needs.immutable-artifacts.result == 'success' &&
2074
+ (needs.prepare.outputs.has_containers != 'true' || needs.container-assemble.result == 'success') &&
2075
+ needs.execute.result == 'success' &&
2076
+ ${trunkPromotionGate}
2077
+ runs-on: ubuntu-latest
2078
+ environment: production
2079
+ permissions:
2080
+ actions: read
2081
+ contents: read
2082
+ id-token: write
2083
+ outputs:
2084
+ artifact_digest: \${{ steps.retain.outputs.artifact-digest }}
2085
+ artifact_id: \${{ steps.retain.outputs.artifact-id }}
2086
+ candidate_artifact_digest: \${{ steps.staging.outputs.artifact_digest }}
2087
+ candidate_artifact_id: \${{ steps.staging.outputs.artifact_id }}
2088
+ candidate_material_digest: \${{ steps.staging.outputs.candidate_material_digest }}
2089
+ candidate_producer_run_attempt: \${{ steps.staging.outputs.producer_run_attempt }}
2090
+ candidate_producer_run_id: \${{ steps.staging.outputs.producer_run_id }}
2091
+ env:
2092
+ AWS_DEPLOY_ROLE_ARN: \${{ needs.prepare.outputs.production_deploy_role_arn }}
2093
+ AWS_EXPECTED_ACCOUNT_ID: \${{ vars.AWS_EXPECTED_ACCOUNT_ID }}
2094
+ AWS_PARTITION: \${{ needs.prepare.outputs.partition }}
2095
+ AWS_REGION: \${{ vars.AWS_REGION }}
2096
+ EBK_ACTION: deploy
2097
+ EBK_DEPLOYMENT_PROFILE: \${{ needs.prepare.outputs.profile }}
2098
+ EBK_POLICY_PRODUCTION_ACCOUNT_ID: \${{ needs.prepare.outputs.production_account_id }}
2099
+ EBK_POLICY_REGION: \${{ needs.prepare.outputs.region }}
2100
+ EBK_POLICY_STAGING_ACCOUNT_ID: \${{ needs.prepare.outputs.staging_account_id }}
2101
+ EBK_RELEASE_SOURCE_COMMIT: \${{ needs.immutable-artifacts.outputs.source_commit }}
2102
+ EBK_SOURCE_COMMIT: \${{ inputs.source_commit || github.sha }}
2103
+ EBK_STAGE: production
2104
+ EBK_SLICE_PROJECTS: \${{ needs.prepare.outputs.slice_projects }}
2105
+ EBK_CONTAINER_PROJECTS: \${{ needs.prepare.outputs.container_projects }}
2106
+ steps:
2107
+ ${checkoutAndInstall('Verify managed workspace integrity before container authority')}
2108
+ - id: staging-receipt
2109
+ name: Select this run's staging deployment receipt
2110
+ env:
2111
+ ACTIONS_ID_TOKEN_REQUEST_TOKEN: ''
2112
+ ACTIONS_ID_TOKEN_REQUEST_URL: ''
2113
+ EBK_DEPLOYMENT_RUN_ATTEMPT: \${{ github.run_attempt }}
2114
+ EBK_DEPLOYMENT_RUN_ID: \${{ github.run_id }}
2115
+ GH_REPO: \${{ github.repository }}
2116
+ GH_TOKEN: \${{ github.token }}
2117
+ run: |
2118
+ set -euo pipefail
2119
+ case "$EBK_DEPLOYMENT_RUN_ID:$EBK_DEPLOYMENT_RUN_ATTEMPT" in *[!0-9:]*|:*|*:) exit 1 ;; esac
2120
+ artifacts="$(gh api "/repos/$GH_REPO/actions/runs/$EBK_DEPLOYMENT_RUN_ID/artifacts?per_page=100")"
2121
+ receipt_name="deployment-receipt-staging-$EBK_DEPLOYMENT_RUN_ID-$EBK_DEPLOYMENT_RUN_ATTEMPT"
2122
+ receipt="$(printf '%s' "$artifacts" | jq -c --arg name "$receipt_name" \\
2123
+ '[.artifacts[] | select(.name == $name and .expired == false)] | if length == 1 then .[0] else empty end')"
2124
+ [ -n "$receipt" ] || { echo 'This run retained no exact staging deployment receipt'; exit 1; }
2125
+ artifact_id="$(printf '%s' "$receipt" | jq -r '.id // empty')"
2126
+ artifact_digest="$(printf '%s' "$receipt" | jq -r '.digest // empty')"
2127
+ case "$artifact_id" in ''|*[!0-9]*) exit 1 ;; esac
2128
+ case "$artifact_digest" in sha256:????????????????????????????????????????????????????????????????) ;; *) exit 1 ;; esac
2129
+ echo "artifact_digest=$artifact_digest" >> "$GITHUB_OUTPUT"
2130
+ echo "artifact_id=$artifact_id" >> "$GITHUB_OUTPUT"
2131
+ - name: Restore this run's staging deployment receipt
2132
+ uses: ${actionPins.downloadArtifact}
2133
+ env:
2134
+ ACTIONS_ID_TOKEN_REQUEST_TOKEN: ''
2135
+ ACTIONS_ID_TOKEN_REQUEST_URL: ''
2136
+ with:
2137
+ artifact-ids: \${{ steps.staging-receipt.outputs.artifact_id }}
2138
+ github-token: \${{ github.token }}
2139
+ path: \${{ runner.temp }}/staging-deployment-receipt
2140
+ - id: staging
2141
+ name: Bind exact staging deployment and release evidence
2142
+ env:
2143
+ ACTIONS_ID_TOKEN_REQUEST_TOKEN: ''
2144
+ ACTIONS_ID_TOKEN_REQUEST_URL: ''
2145
+ EBK_STAGING_DEPLOYMENT_RUN_ATTEMPT: \${{ github.run_attempt }}
2146
+ EBK_STAGING_DEPLOYMENT_RUN_ID: \${{ github.run_id }}
2147
+ EBK_EXPECTED_HAS_SLICES: \${{ needs.prepare.outputs.has_slices }}
2148
+ EBK_EXPECTED_HAS_CONTAINERS: \${{ needs.prepare.outputs.has_containers }}
2149
+ EBK_EXPECTED_DEPLOYMENT_PROFILE: \${{ needs.prepare.outputs.profile }}
2150
+ EBK_EXPECTED_DEPLOYMENT_REGION: \${{ needs.prepare.outputs.region }}
2151
+ EBK_EXPECTED_STAGING_ACCOUNT_ID: \${{ needs.prepare.outputs.staging_account_id }}
2152
+ EBK_STAGING_RECEIPT_ARTIFACT_DIGEST: \${{ steps.staging-receipt.outputs.artifact_digest }}
2153
+ EBK_STAGING_RECEIPT_ARTIFACT_ID: \${{ steps.staging-receipt.outputs.artifact_id }}
2154
+ # The receipt artifact keeps .ebk/evidence as its root (EBK-INT-2026-074).
2155
+ EBK_STAGING_RECEIPT_ROOT: \${{ runner.temp }}/staging-deployment-receipt/deployment
2156
+ EBK_STAGING_SOURCE_COMMIT: \${{ env.EBK_DEPLOYMENT_SOURCE_COMMIT }}
2157
+ GH_REPO: \${{ github.repository }}
2158
+ GH_TOKEN: \${{ github.token }}
2159
+ run: |
2160
+ node --input-type=module <<'NODE'
2161
+ ${indentedScript(stagingPromotionReceiptValidationScript(), 10)}
2162
+ NODE
2163
+ promotion="$EBK_STAGING_RECEIPT_ROOT/promotion.json"
2164
+ candidate_run_id="$(jq -r '.candidate.producerRunId' "$promotion")"
2165
+ candidate_run_attempt="$(jq -r '.candidate.producerRunAttempt' "$promotion")"
2166
+ candidate_artifact_id="$(jq -r '.candidate.artifactId' "$promotion")"
2167
+ candidate_artifact_digest="$(jq -r '.candidate.artifactDigest' "$promotion")"
2168
+ candidate_run="$(gh api "/repos/$GH_REPO/actions/runs/$candidate_run_id")"
2169
+ printf '%s' "$candidate_run" | jq -e \\
2170
+ --arg source "$EBK_STAGING_SOURCE_COMMIT" \\
2171
+ --arg attempt "$candidate_run_attempt" \\
2172
+ '.head_sha == $source and .head_branch == "develop" and .event == "push" and .path == ".github/workflows/ci.yml" and .conclusion == "success" and (.run_attempt | tostring) == $attempt' >/dev/null
2173
+ candidate_artifacts="$(gh api "/repos/$GH_REPO/actions/runs/$candidate_run_id/artifacts?per_page=100")"
2174
+ printf '%s' "$candidate_artifacts" | jq -e \\
2175
+ --arg id "$candidate_artifact_id" \\
2176
+ --arg digest "$candidate_artifact_digest" \\
2177
+ --arg name "release-candidate-$EBK_STAGING_SOURCE_COMMIT" \\
2178
+ '[.artifacts[] | select((.id | tostring) == $id and .name == $name and .digest == $digest and .expired == false)] | length == 1' >/dev/null
2179
+ - name: Restore exact immutable release candidate for promotion
2180
+ uses: ${actionPins.downloadArtifact}
2181
+ env:
2182
+ ACTIONS_ID_TOKEN_REQUEST_TOKEN: ''
2183
+ ACTIONS_ID_TOKEN_REQUEST_URL: ''
2184
+ with:
2185
+ artifact-ids: \${{ steps.staging.outputs.artifact_id }}
2186
+ github-token: \${{ github.token }}
2187
+ path: .ebk/evidence/release-candidate
2188
+ run-id: \${{ steps.staging.outputs.producer_run_id }}
2189
+ - name: Restore this run's staging container bundle
2190
+ if: needs.prepare.outputs.has_containers == 'true'
2191
+ uses: ${actionPins.downloadArtifact}
2192
+ env:
2193
+ ACTIONS_ID_TOKEN_REQUEST_TOKEN: ''
2194
+ ACTIONS_ID_TOKEN_REQUEST_URL: ''
2195
+ with:
2196
+ artifact-ids: \${{ steps.staging.outputs.container_artifact_id }}
2197
+ github-token: \${{ github.token }}
2198
+ path: .ebk/evidence/container-release
2199
+ ${revalidateTip}
2200
+ - name: Assume exact staging registry pull role
2201
+ if: needs.prepare.outputs.has_containers == 'true'
2202
+ uses: ${actionPins.configureAwsCredentials}
2203
+ with:
2204
+ audience: \${{ needs.prepare.outputs.oidc_audience }}
2205
+ aws-region: \${{ vars.AWS_STAGING_PROMOTION_REGION }}
2206
+ role-session-name: ebk-staging-container-pull-\${{ github.run_id }}-\${{ github.run_attempt }}
2207
+ role-to-assume: \${{ secrets.AWS_STAGING_PROMOTION_PULL_ROLE_ARN }}
2208
+ - name: Authenticate exact staging source registry
2209
+ if: needs.prepare.outputs.has_containers == 'true'
2210
+ env:
2211
+ AWS_STAGING_PROMOTION_PULL_ROLE_ARN: \${{ secrets.AWS_STAGING_PROMOTION_PULL_ROLE_ARN }}
2212
+ EBK_STAGING_ACCOUNT_ID: \${{ vars.AWS_STAGING_PROMOTION_EXPECTED_ACCOUNT_ID }}
2213
+ EBK_STAGING_REGION: \${{ vars.AWS_STAGING_PROMOTION_REGION }}
2214
+ run: |
2215
+ set -euo pipefail
2216
+ [ "$EBK_STAGING_ACCOUNT_ID" = "$EBK_POLICY_STAGING_ACCOUNT_ID" ]
2217
+ [ "$EBK_STAGING_REGION" = "$EBK_POLICY_REGION" ]
2218
+ ${indentedScript(exactStagingPromotionPullGuardScript(), 10)}
2219
+ [ "$EBK_STAGING_ACCOUNT_ID" != "$AWS_EXPECTED_ACCOUNT_ID" ]
2220
+ registry_suffix=.com
2221
+ case "$EBK_STAGING_REGION" in cn-*) registry_suffix=.com.cn ;; esac
2222
+ aws ecr get-login-password --region "$EBK_STAGING_REGION" |
2223
+ docker login --username AWS --password-stdin \\
2224
+ "$EBK_STAGING_ACCOUNT_ID.dkr.ecr.$EBK_STAGING_REGION.amazonaws$registry_suffix"
2225
+ ${revalidateTip}
2226
+ - name: Assume the production deployment role for container promotion
2227
+ if: needs.prepare.outputs.has_containers == 'true'
2228
+ uses: ${actionPins.configureAwsCredentials}
2229
+ with:
2230
+ audience: \${{ needs.prepare.outputs.oidc_audience }}
2231
+ aws-region: \${{ vars.AWS_REGION }}
2232
+ role-session-name: ebk-production-container-push-\${{ github.run_id }}-\${{ github.run_attempt }}
2233
+ role-to-assume: \${{ env.AWS_DEPLOY_ROLE_ARN }}
2234
+ - name: Promote exact staging indexes without rebuilding
2235
+ if: needs.prepare.outputs.has_containers == 'true'
2236
+ env:
2237
+ EBK_STAGING_PROMOTION_EXPECTED_ACCOUNT_ID: \${{ vars.AWS_STAGING_PROMOTION_EXPECTED_ACCOUNT_ID }}
2238
+ EBK_STAGING_PROMOTION_REGION: \${{ vars.AWS_STAGING_PROMOTION_REGION }}
2239
+ run: |
2240
+ set -euo pipefail
2241
+ [ "$AWS_EXPECTED_ACCOUNT_ID" = "$EBK_POLICY_PRODUCTION_ACCOUNT_ID" ]
2242
+ [ "$AWS_REGION" = "$EBK_POLICY_REGION" ]
2243
+ ${indentedScript(exactStageDeployerGuardScript('Container promotion', '$AWS_EXPECTED_ACCOUNT_ID'), 10)}
2244
+ IFS=',' read -r -a projects <<< "$EBK_CONTAINER_PROJECTS"
2245
+ for project in "\${projects[@]}"; do
2246
+ test -n "$project" || continue
2247
+ # Repository-relative for the container release tool (EBK-INT-2026-077).
2248
+ export EBK_CONTAINER_PROMOTION_SOURCE_BINDING_PATH=".ebk/evidence/container-release/staging/packages/$project/binding.json"
2249
+ export EBK_CONTAINER_PROMOTION_SOURCE_SIDECAR_PATH=".ebk/evidence/container-release/staging/packages/$project/release-bound-binding.json"
2250
+ pnpm nx run "$project:container-release-promote"
2251
+ done
2252
+ - id: retain
2253
+ name: Retain production target container binding
2254
+ if: needs.prepare.outputs.has_containers == 'true'
2255
+ uses: ${actionPins.uploadArtifact}
2256
+ with:
2257
+ name: container-release-production-\${{ github.run_id }}-\${{ github.run_attempt }}
2258
+ if-no-files-found: error
2259
+ include-hidden-files: true
2260
+ retention-days: 90
2261
+ path: .ebk/evidence/container-release
2262
+
2263
+ execute-production:
2264
+ name: native deploy production
2265
+ needs: [prepare, immutable-artifacts, promote-production]
2266
+ if: >-
2267
+ always() &&
2268
+ needs.prepare.result == 'success' &&
2269
+ needs.immutable-artifacts.result == 'success' &&
2270
+ needs.promote-production.result == 'success' &&
2271
+ ${trunkPromotionGate}
2272
+ runs-on: ubuntu-latest
2273
+ environment: production
2274
+ permissions:
2275
+ actions: read
2276
+ contents: read
2277
+ id-token: write
2278
+ env:
2279
+ AWS_DEPLOY_ROLE_ARN: \${{ needs.prepare.outputs.production_deploy_role_arn }}
2280
+ AWS_EXPECTED_ACCOUNT_ID: \${{ vars.AWS_EXPECTED_ACCOUNT_ID }}
2281
+ AWS_PARTITION: \${{ needs.prepare.outputs.partition }}
2282
+ AWS_REGION: \${{ vars.AWS_REGION }}
2283
+ EBK_ACTION: deploy
2284
+ EBK_ALLOW_PRODUCTION_REMOVE: \${{ vars.EBK_ALLOW_PRODUCTION_REMOVE }}
2285
+ EBK_CANDIDATE_ARTIFACT_DIGEST: \${{ needs.promote-production.outputs.candidate_artifact_digest }}
2286
+ EBK_CANDIDATE_ARTIFACT_ID: \${{ needs.promote-production.outputs.candidate_artifact_id }}
2287
+ EBK_CANDIDATE_MATERIAL_DIGEST: \${{ needs.promote-production.outputs.candidate_material_digest }}
2288
+ EBK_CANDIDATE_PRODUCER_RUN_ATTEMPT: \${{ needs.promote-production.outputs.candidate_producer_run_attempt }}
2289
+ EBK_CANDIDATE_PRODUCER_RUN_ID: \${{ needs.promote-production.outputs.candidate_producer_run_id }}
2290
+ EBK_DEPLOYMENT_PROFILE: \${{ needs.prepare.outputs.profile }}
2291
+ EBK_ORDERED_PROJECTS: \${{ needs.prepare.outputs.ordered_projects }}
2292
+ EBK_POLICY_ACCOUNT_ID: \${{ needs.prepare.outputs.production_account_id }}
2293
+ EBK_POLICY_REGION: \${{ needs.prepare.outputs.region }}
2294
+ EBK_RELEASE_SOURCE_COMMIT: \${{ needs.immutable-artifacts.outputs.source_commit || inputs.source_commit || github.sha }}
2295
+ EBK_SLICE_PROJECTS: \${{ needs.prepare.outputs.slice_projects }}
2296
+ EBK_SOURCE_COMMIT: \${{ inputs.source_commit || github.sha }}
2297
+ EBK_STAGE: production
2298
+ # Production has no same-branch baseline receipt in this run; the
2299
+ # full lifecycle keeps its evidence complete.
2300
+ EBK_LIFECYCLE_SCOPE: full
2301
+ EBK_BASELINE_FOUND: 'false'
2302
+ EBK_BASELINE_RUN_ID: ''
2303
+ steps:
2304
+ ${checkoutAndInstall('Verify managed workspace integrity before AWS authority')}
2305
+ - name: Restore exact immutable release candidate
2306
+ uses: ${actionPins.downloadArtifact}
2307
+ env:
2308
+ ACTIONS_ID_TOKEN_REQUEST_TOKEN: ''
2309
+ ACTIONS_ID_TOKEN_REQUEST_URL: ''
2310
+ with:
2311
+ artifact-ids: \${{ needs.promote-production.outputs.candidate_artifact_id }}
2312
+ github-token: \${{ github.token }}
2313
+ path: .ebk/evidence/release-candidate
2314
+ run-id: \${{ needs.promote-production.outputs.candidate_producer_run_id }}
2315
+ - name: Restore exact promoted production container binding
2316
+ if: needs.prepare.outputs.has_containers == 'true'
2317
+ uses: ${actionPins.downloadArtifact}
2318
+ env:
2319
+ ACTIONS_ID_TOKEN_REQUEST_TOKEN: ''
2320
+ ACTIONS_ID_TOKEN_REQUEST_URL: ''
2321
+ with:
2322
+ name: container-release-production-\${{ github.run_id }}-\${{ github.run_attempt }}
2323
+ path: .ebk/evidence/container-release
2324
+ - name: Verify exact predeploy container binding inventory
2325
+ if: needs.prepare.outputs.has_containers == 'true'
2326
+ env:
2327
+ ACTIONS_ID_TOKEN_REQUEST_TOKEN: ''
2328
+ ACTIONS_ID_TOKEN_REQUEST_URL: ''
2329
+ EBK_CONTAINER_PROJECTS: \${{ needs.prepare.outputs.container_projects }}
2330
+ run: |
2331
+ set -euo pipefail
2332
+ IFS=',' read -r -a projects <<< "$EBK_CONTAINER_PROJECTS"
2333
+ for project in "\${projects[@]}"; do
2334
+ test -n "$project" || continue
2335
+ test -f ".ebk/evidence/container-release/$EBK_STAGE/packages/$project/binding.json" || {
2336
+ echo "Exact predeploy container binding is absent for $project"
2337
+ exit 1
2338
+ }
2339
+ done
2340
+ - name: Verify exact restored staging-tested release candidate
2341
+ env:
2342
+ ACTIONS_ID_TOKEN_REQUEST_TOKEN: ''
2343
+ ACTIONS_ID_TOKEN_REQUEST_URL: ''
2344
+ run: |
2345
+ node --input-type=module <<'NODE'
2346
+ ${indentedScript(restoredCandidateValidationScript(), 10)}
2347
+ NODE
2348
+ - name: Authorize deployed Blueprint trust before protected mutation
2349
+ env:
2350
+ ACTIONS_ID_TOKEN_REQUEST_TOKEN: ''
2351
+ ACTIONS_ID_TOKEN_REQUEST_URL: ''
2352
+ EBK_PROTECTED_BLUEPRINT_COMMIT: \${{ env.EBK_DEPLOYMENT_SOURCE_COMMIT }}
2353
+ run: |
2354
+ set -euo pipefail
2355
+ case "$EBK_PROTECTED_BLUEPRINT_COMMIT" in
2356
+ ''|*[!0-9a-f]*) echo 'Protected Blueprint authority must be one full lowercase Git object ID'; exit 1 ;;
2357
+ esac
2358
+ test "\${#EBK_PROTECTED_BLUEPRINT_COMMIT}" -eq 40 ||
2359
+ test "\${#EBK_PROTECTED_BLUEPRINT_COMMIT}" -eq 64 ||
2360
+ { echo 'Protected Blueprint authority must be one full Git object ID'; exit 1; }
2361
+ test "$(git rev-parse HEAD)" = "$EBK_PROTECTED_BLUEPRINT_COMMIT" ||
2362
+ { echo 'Protected Blueprint authority differs from the exact workflow checkout'; exit 1; }
2363
+ node tools/blueprint-trust-check.mjs \\
2364
+ --operation=deploy \\
2365
+ --protected-branch=main \\
2366
+ --protected-ref="$EBK_PROTECTED_BLUEPRINT_COMMIT" \\
2367
+ --output=.ebk/evidence/supply-chain/blueprint-trust.json
2368
+ - name: Produce subject-bound protected scanner evidence
2369
+ env:
2370
+ ACTIONS_ID_TOKEN_REQUEST_TOKEN: ''
2371
+ ACTIONS_ID_TOKEN_REQUEST_URL: ''
2372
+ run: pnpm nx run ${name}:supply-chain-scan
2373
+ - name: Enforce protected supply-chain policy before mutation
2374
+ env:
2375
+ ACTIONS_ID_TOKEN_REQUEST_TOKEN: ''
2376
+ ACTIONS_ID_TOKEN_REQUEST_URL: ''
2377
+ run: pnpm nx run ${name}:supply-chain-check-protected
2378
+ - name: Install pinned Playwright Chromium for deployed smoke proofs
2379
+ env:
2380
+ ACTIONS_ID_TOKEN_REQUEST_TOKEN: ''
2381
+ ACTIONS_ID_TOKEN_REQUEST_URL: ''
2382
+ run: |
2383
+ ${indentedScript(protectedBrowserInstallScript(), 10)}
2384
+ ${machineCredentialRequestStep()}
2385
+ ${revalidateTip}
2386
+ - name: Assume the one stage deployment role
2387
+ uses: ${actionPins.configureAwsCredentials}
2388
+ with:
2389
+ audience: \${{ needs.prepare.outputs.oidc_audience }}
2390
+ aws-region: \${{ vars.AWS_REGION }}
2391
+ role-session-name: ebk-native-\${{ github.run_id }}-\${{ github.run_attempt }}
2392
+ role-to-assume: \${{ env.AWS_DEPLOY_ROLE_ARN }}
2393
+ - name: Verify exact stage authority
2394
+ run: |
2395
+ set -euo pipefail
2396
+ [ "$AWS_EXPECTED_ACCOUNT_ID" = "$EBK_POLICY_ACCOUNT_ID" ]
2397
+ [ "$AWS_REGION" = "$EBK_POLICY_REGION" ]
2398
+ ${indentedScript(exactStageDeployerGuardScript('Native lifecycle', '$EBK_POLICY_ACCOUNT_ID'), 10)}
2399
+ - name: Run ordered native lifecycle targets
2400
+ id: native-lifecycle
2401
+ env:
2402
+ ACTIONS_ID_TOKEN_REQUEST_TOKEN: ''
2403
+ ACTIONS_ID_TOKEN_REQUEST_URL: ''
2404
+ EBK_GITHUB_JOB_WORKFLOW_REF: \${{ job.workflow_ref }}
2405
+ GITHUB_TOKEN: \${{ github.token }}
2406
+ run: |
2407
+ set -euo pipefail
2408
+ test -n "$AWS_DEPLOY_ROLE_ARN" || { echo 'Stage deployment role is absent'; exit 1; }
2409
+ pnpm nx run ${name}:deploy-local -- \\
2410
+ --stage="$EBK_STAGE" \\
2411
+ --expectedRoleArn="$AWS_DEPLOY_ROLE_ARN" \\
2412
+ --lifecycleScope="$EBK_LIFECYCLE_SCOPE"
2413
+ - name: Prove temporary smoke admission removed
2414
+ if: always() && steps.native-lifecycle.outcome != 'skipped'
2415
+ env:
2416
+ ACTIONS_ID_TOKEN_REQUEST_TOKEN: ''
2417
+ ACTIONS_ID_TOKEN_REQUEST_URL: ''
2418
+ EBK_GITHUB_JOB_WORKFLOW_REF: \${{ job.workflow_ref }}
2419
+ GITHUB_TOKEN: \${{ github.token }}
2420
+ run: |
2421
+ set -euo pipefail
2422
+ ${indentedScript(exactStageDeployerGuardScript('Smoke access cleanup', '$EBK_POLICY_ACCOUNT_ID'), 10)}
2423
+ [ "$(git rev-parse HEAD)" = "$EBK_SOURCE_COMMIT" ]
2424
+ git diff --quiet HEAD
2425
+ node --input-type=module <<'NODE'
2426
+ import { cleanupGithubPrivateSmoke } from '@empire-builder-kit/nx/foundation';
2427
+ await cleanupGithubPrivateSmoke({
2428
+ account: process.env.EBK_POLICY_ACCOUNT_ID,
2429
+ deploymentProfile: process.env.EBK_DEPLOYMENT_PROFILE,
2430
+ root: process.cwd(),
2431
+ source: process.env.EBK_SOURCE_COMMIT,
2432
+ stage: process.env.EBK_STAGE,
2433
+ workspace: '${name}',
2434
+ });
2435
+ NODE
2436
+ - name: Seal exact postdeploy supply-chain binding
2437
+ env:
2438
+ ACTIONS_ID_TOKEN_REQUEST_TOKEN: ''
2439
+ ACTIONS_ID_TOKEN_REQUEST_URL: ''
2440
+ run: |
2441
+ node --input-type=module <<'NODE'
2442
+ ${indentedScript(postdeploySupplyChainBindingScript(), 10)}
2443
+ NODE
2444
+ - name: Retain bounded deployment receipt
2445
+ if: always()
2446
+ uses: ${actionPins.uploadArtifact}
2447
+ with:
2448
+ if-no-files-found: warn
2449
+ name: deployment-receipt-production-\${{ github.run_id }}-\${{ github.run_attempt }}
2450
+ path: |
2451
+ .ebk/evidence/deployment/latest.json
2452
+ .ebk/evidence/deployment/postdeploy-supply-chain.json
2453
+ .ebk/evidence/deployment/promotion.json
2454
+ .ebk/evidence/container-release/**
2455
+ retention-days: 30
2456
+
2457
+ record-main:
2458
+ name: record the deployed commit on main
2459
+ needs: [prepare, execute-production]
2460
+ if: >-
2461
+ always() &&
2462
+ needs.prepare.result == 'success' &&
2463
+ needs.execute-production.result == 'success' &&
2464
+ ${trunkPromotionGate}
2465
+ runs-on: ubuntu-latest
2466
+ environment: production
2467
+ # The fast-forward uses the scoped environment token, not the workflow token.
2468
+ permissions:
2469
+ contents: read
2470
+ steps:
2471
+ - name: Fast-forward protected main to the deployed commit
2472
+ env:
2473
+ EBK_RECORD_MAIN_TOKEN: \${{ secrets.${exports.EBK_RECORD_MAIN_TOKEN_SECRET} }}
2474
+ EBK_SOURCE_BRANCH: \${{ env.EBK_DEPLOYMENT_SOURCE_BRANCH }}
2475
+ EBK_SOURCE_COMMIT: \${{ env.EBK_DEPLOYMENT_SOURCE_COMMIT }}
2476
+ GH_REPO: \${{ github.repository }}
2477
+ run: |
2478
+ set -euo pipefail
2479
+ test -n "$EBK_RECORD_MAIN_TOKEN" || { echo '${exports.EBK_RECORD_MAIN_TOKEN_SECRET} is absent from the production environment'; exit 1; }
2480
+ [ "$EBK_SOURCE_BRANCH" = develop ] || { echo 'Trunk promotion records main only from develop'; exit 1; }
2481
+ case "$EBK_SOURCE_COMMIT" in ''|*[!0-9a-f]*) exit 1 ;; esac
2482
+ export GH_TOKEN="$EBK_RECORD_MAIN_TOKEN"
2483
+ develop_tip="$(gh api "/repos/$GH_REPO/git/ref/heads/develop" --jq '.object.sha')"
2484
+ [ "$develop_tip" = "$EBK_SOURCE_COMMIT" ] || { echo 'Deployed commit is no longer the develop tip; leaving main unchanged'; exit 1; }
2485
+ main_tip="$(gh api "/repos/$GH_REPO/git/ref/heads/main" --jq '.object.sha')"
2486
+ if [ "$main_tip" = "$EBK_SOURCE_COMMIT" ]; then
2487
+ echo 'main already records the deployed commit'
2488
+ exit 0
2489
+ fi
2490
+ # GitHub refuses a non-fast-forward reference update unless force is
2491
+ # requested; it is never requested here.
2492
+ gh api -X PATCH "/repos/$GH_REPO/git/refs/heads/main" -f sha="$EBK_SOURCE_COMMIT" -F force=false >/dev/null
2493
+ recorded="$(gh api "/repos/$GH_REPO/git/ref/heads/main" --jq '.object.sha')"
2494
+ [ "$recorded" = "$EBK_SOURCE_COMMIT" ] || { echo 'main did not record the deployed commit'; exit 1; }
2495
+ echo "main now records $EBK_SOURCE_COMMIT (previously $main_tip)"
2496
+ `;
2497
+ }
2498
+ function renderThinDeployWorkflowFilePlan(options) {
2499
+ assertOptions(options);
2500
+ return {
2501
+ files: {
2502
+ [exports.THIN_DEPLOY_WORKFLOW_PATH]: renderThinDeployWorkflow(options),
2503
+ },
2504
+ managedPaths: [exports.THIN_DEPLOY_WORKFLOW_PATH],
2505
+ };
2506
+ }
2507
+ //# sourceMappingURL=workspace-files-thin-deploy.js.map