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

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