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