@empire-builder-kit/nx 1.0.0-rc.2 → 1.0.0-rc.21
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/executors.json +10 -0
- package/package.json +4 -4
- package/src/cli/output.js +1 -1
- package/src/developer-agent/model.js +4 -4
- package/src/developer-agent/model.js.map +1 -1
- package/src/developer-agent/operation-registry.js +67 -10
- package/src/developer-agent/operation-registry.js.map +1 -1
- package/src/developer-agent/planning.js +7 -9
- package/src/developer-agent/planning.js.map +1 -1
- package/src/developer-agent/workspace-inspection.js +12 -0
- package/src/developer-agent/workspace-inspection.js.map +1 -1
- package/src/executors/blueprint-trust/blueprint-trust.js +6 -0
- package/src/executors/blueprint-trust/blueprint-trust.js.map +1 -1
- package/src/executors/deployment-plan/deployment-plan.js +5 -0
- package/src/executors/deployment-plan/deployment-plan.js.map +1 -1
- package/src/executors/foundation-preflight/foundation-preflight.d.ts +2 -0
- package/src/executors/foundation-preflight/foundation-preflight.js +6 -2
- package/src/executors/foundation-preflight/foundation-preflight.js.map +1 -1
- package/src/executors/foundation-preflight/schema.json +10 -0
- package/src/executors/foundation-profile-preflight/foundation-profile-preflight.d.ts +2 -0
- package/src/executors/foundation-profile-preflight/foundation-profile-preflight.js +2 -1
- package/src/executors/foundation-profile-preflight/foundation-profile-preflight.js.map +1 -1
- package/src/executors/foundation-profile-preflight/schema.json +19 -3
- package/src/executors/github-bootstrap/github-bootstrap.js +1 -0
- package/src/executors/github-bootstrap/github-bootstrap.js.map +1 -1
- package/src/executors/infrastructure-plan/infrastructure-plan.d.ts +229 -5
- package/src/executors/infrastructure-plan/infrastructure-plan.js +2298 -175
- package/src/executors/infrastructure-plan/infrastructure-plan.js.map +1 -1
- package/src/executors/infrastructure-plan/schema.json +16 -0
- package/src/executors/local-deploy/fixtures/local-deploy-state-machine.mjs +53 -0
- package/src/executors/local-deploy/local-approval.d.ts +23 -0
- package/src/executors/local-deploy/local-approval.js +43 -0
- package/src/executors/local-deploy/local-approval.js.map +1 -0
- package/src/executors/local-deploy/local-deploy.d.ts +69 -0
- package/src/executors/local-deploy/local-deploy.js +911 -0
- package/src/executors/local-deploy/local-deploy.js.map +1 -0
- package/src/executors/local-deploy/schema.json +42 -0
- package/src/executors/router-waf-rehearsal/router-waf-rehearsal.d.ts +3 -0
- package/src/executors/router-waf-rehearsal/router-waf-rehearsal.js +94 -0
- package/src/executors/router-waf-rehearsal/router-waf-rehearsal.js.map +1 -0
- package/src/executors/router-waf-rehearsal/schema.json +9 -0
- package/src/executors/secret-metadata/schema.json +5 -0
- package/src/executors/secret-metadata/secret-metadata.d.ts +12 -1
- package/src/executors/secret-metadata/secret-metadata.js +68 -16
- package/src/executors/secret-metadata/secret-metadata.js.map +1 -1
- 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/logging-preflight.js.map +1 -1
- package/src/executors/sst-lifecycle/run-sst-command.d.ts +8 -1
- package/src/executors/sst-lifecycle/run-sst-command.js +144 -12
- package/src/executors/sst-lifecycle/run-sst-command.js.map +1 -1
- package/src/executors/sst-lifecycle/schema.json +13 -1
- package/src/executors/sst-lifecycle/sst-environment-source.d.ts +25 -0
- package/src/executors/sst-lifecycle/sst-environment-source.js +213 -0
- package/src/executors/sst-lifecycle/sst-environment-source.js.map +1 -0
- package/src/foundation/index.d.ts +2 -0
- package/src/foundation/index.js +2 -0
- package/src/foundation/index.js.map +1 -1
- package/src/foundation/logging-preflight.js +1 -1
- package/src/foundation/logging-preflight.js.map +1 -1
- package/src/foundation/logging.d.ts +3 -1
- package/src/foundation/logging.js +1 -1
- package/src/foundation/logging.js.map +1 -1
- package/src/foundation/partial-state.d.ts +10 -0
- package/src/foundation/partial-state.js +21 -0
- package/src/foundation/partial-state.js.map +1 -0
- package/src/foundation/router-waf.d.ts +98 -0
- package/src/foundation/router-waf.js +401 -0
- package/src/foundation/router-waf.js.map +1 -0
- package/src/generators/function/function.js +2 -1
- package/src/generators/function/function.js.map +1 -1
- package/src/generators/post-generation-install.d.ts +1 -0
- package/src/generators/post-generation-install.js +134 -67
- package/src/generators/post-generation-install.js.map +1 -1
- package/src/generators/preset/generator.js +23 -3
- package/src/generators/preset/generator.js.map +1 -1
- package/src/generators/preset/schema.d.ts +7 -0
- package/src/generators/preset/schema.json +47 -0
- package/src/generators/preset/tool-versions.d.ts +5 -5
- package/src/generators/preset/tool-versions.js +4 -4
- 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-acceptance.js.map +1 -0
- package/src/generators/preset/workspace-files-developer-agent.d.ts +2 -0
- package/src/generators/preset/workspace-files-developer-agent.js +25 -11
- package/src/generators/preset/workspace-files-developer-agent.js.map +1 -1
- package/src/generators/preset/workspace-files-foundation-workload-roles.js +5 -2
- package/src/generators/preset/workspace-files-foundation-workload-roles.js.map +1 -1
- package/src/generators/preset/workspace-files-performance-load.js +2 -1
- package/src/generators/preset/workspace-files-performance-load.js.map +1 -1
- package/src/generators/preset/workspace-files-workload-role-baseline.js +254 -42
- package/src/generators/preset/workspace-files-workload-role-baseline.js.map +1 -1
- package/src/generators/preset/workspace-files.d.ts +8 -4
- package/src/generators/preset/workspace-files.js +1039 -68
- package/src/generators/preset/workspace-files.js.map +1 -1
- package/src/generators/repository-policy/repository-policy.js +65 -3
- package/src/generators/repository-policy/repository-policy.js.map +1 -1
- package/src/generators/repository-policy/schema.d.ts +6 -0
- package/src/generators/repository-policy/schema.json +46 -0
- package/src/generators/slice/built-in-plan-api-files.js +2 -1
- package/src/generators/slice/built-in-plan-api-files.js.map +1 -1
- package/src/generators/slice/built-in-plan-authorization-infrastructure-files.js +19 -1
- package/src/generators/slice/built-in-plan-authorization-infrastructure-files.js.map +1 -1
- package/src/generators/slice/built-in-plan-data-files.js +16 -5
- package/src/generators/slice/built-in-plan-data-files.js.map +1 -1
- package/src/generators/slice/built-in-plan-data-retention-job-files.js +10 -6
- package/src/generators/slice/built-in-plan-data-retention-job-files.js.map +1 -1
- package/src/generators/slice/built-in-plan-database-lifecycle-files.js +25 -10
- package/src/generators/slice/built-in-plan-database-lifecycle-files.js.map +1 -1
- package/src/generators/slice/built-in-plan-foundation-files.js +2 -3
- package/src/generators/slice/built-in-plan-foundation-files.js.map +1 -1
- package/src/generators/slice/built-in-plan-infrastructure-files.js +364 -29
- package/src/generators/slice/built-in-plan-infrastructure-files.js.map +1 -1
- package/src/generators/slice/built-in-plan-infrastructure-safety-files.js +67 -11
- package/src/generators/slice/built-in-plan-infrastructure-safety-files.js.map +1 -1
- package/src/generators/slice/built-in-plan-metadata-files.js +1 -0
- package/src/generators/slice/built-in-plan-metadata-files.js.map +1 -1
- package/src/generators/slice/built-in-plan-operations-files.js +442 -5
- package/src/generators/slice/built-in-plan-operations-files.js.map +1 -1
- package/src/generators/slice/built-in-plan-performance-files.d.ts +2 -1
- package/src/generators/slice/built-in-plan-performance-files.js +41 -47
- package/src/generators/slice/built-in-plan-performance-files.js.map +1 -1
- package/src/generators/slice/built-in-plan-project-files.js +14 -1
- package/src/generators/slice/built-in-plan-project-files.js.map +1 -1
- package/src/generators/slice/built-in-plan-release-files.js +235 -56
- package/src/generators/slice/built-in-plan-release-files.js.map +1 -1
- package/src/generators/slice/built-in-plan-testing-files.js +92 -5
- package/src/generators/slice/built-in-plan-testing-files.js.map +1 -1
- package/src/generators/slice/built-in-plan-web-files.js +13 -0
- package/src/generators/slice/built-in-plan-web-files.js.map +1 -1
- package/src/generators/slice/built-in-plan-workload-role-files.js +5 -2
- package/src/generators/slice/built-in-plan-workload-role-files.js.map +1 -1
- package/src/generators/slice/built-in-plan.d.ts +2 -1
- package/src/generators/slice/built-in-plan.js +8 -5
- package/src/generators/slice/built-in-plan.js.map +1 -1
- package/src/generators/slice/slice.js +11 -3
- package/src/generators/slice/slice.js.map +1 -1
- package/src/generators/upgrade/upgrade.js +35 -0
- package/src/generators/upgrade/upgrade.js.map +1 -1
- package/src/generators/workload/frozen-container.d.ts +3 -2
- package/src/generators/workload/frozen-container.js +6 -4
- package/src/generators/workload/frozen-container.js.map +1 -1
- package/src/generators/workload/workload.js +2 -2
- package/src/generators/workload/workload.js.map +1 -1
- package/src/git-policy/github-bootstrap.d.ts +3 -1
- package/src/git-policy/github-bootstrap.js +22 -5
- package/src/git-policy/github-bootstrap.js.map +1 -1
- package/src/git-policy/routing.d.ts +2 -0
- package/src/git-policy/routing.js +12 -1
- package/src/git-policy/routing.js.map +1 -1
- package/src/infrastructure-change/local-capability.d.ts +73 -0
- package/src/infrastructure-change/local-capability.js +114 -0
- package/src/infrastructure-change/local-capability.js.map +1 -0
- package/src/infrastructure-change/model.d.ts +25 -4
- package/src/infrastructure-change/model.js +1 -0
- package/src/infrastructure-change/model.js.map +1 -1
- package/src/infrastructure-change/normalize.d.ts +4 -0
- package/src/infrastructure-change/normalize.js +47 -7
- package/src/infrastructure-change/normalize.js.map +1 -1
- package/src/infrastructure-change/plan.d.ts +30 -3
- package/src/infrastructure-change/plan.js +323 -13
- package/src/infrastructure-change/plan.js.map +1 -1
- package/src/infrastructure-change/risk.d.ts +3 -1
- package/src/infrastructure-change/risk.js +96 -16
- package/src/infrastructure-change/risk.js.map +1 -1
- package/src/local-dev/environment-inspector.d.ts +1 -0
- package/src/local-dev/environment-inspector.js +21 -9
- package/src/local-dev/environment-inspector.js.map +1 -1
- package/src/local-dev/private-state.d.ts +2 -0
- package/src/local-dev/private-state.js +3 -1
- package/src/local-dev/private-state.js.map +1 -1
- package/src/performance/cost-controls.d.ts +5 -3
- package/src/performance/cost-controls.js +30 -24
- package/src/performance/cost-controls.js.map +1 -1
- package/src/performance/model.d.ts +1 -1
- package/src/performance/model.js +0 -2
- package/src/performance/model.js.map +1 -1
- package/src/performance/test-fixtures.js +0 -2
- package/src/performance/test-fixtures.js.map +1 -1
- package/src/release/source-authority.d.ts +1 -0
- package/src/release/source-authority.js +136 -9
- package/src/release/source-authority.js.map +1 -1
- package/src/repository-policy/deployment-plan.d.ts +4 -1
- package/src/repository-policy/deployment-plan.js +8 -0
- package/src/repository-policy/deployment-plan.js.map +1 -1
- package/src/repository-policy/foundation-profile.js +1 -2
- package/src/repository-policy/foundation-profile.js.map +1 -1
- package/src/repository-policy/policy.d.ts +25 -1
- package/src/repository-policy/policy.js +145 -4
- package/src/repository-policy/policy.js.map +1 -1
- package/src/schemas/performance-policy-v1.json +0 -2
- package/src/schemas/repository-policy-v1.json +41 -0
- package/src/secrets/operator.js +22 -4
- package/src/secrets/operator.js.map +1 -1
- package/src/secrets/verification.js +9 -1
- package/src/secrets/verification.js.map +1 -1
- package/src/sst-environment-source-format.d.ts +3 -0
- package/src/sst-environment-source-format.js +179 -0
- package/src/sst-environment-source-format.js.map +1 -0
- package/src/supply-chain/blueprint-trust-git.js +72 -21
- package/src/supply-chain/blueprint-trust-git.js.map +1 -1
- package/src/supply-chain/blueprint-verification-authority.js +55 -3
- package/src/supply-chain/blueprint-verification-authority.js.map +1 -1
- package/src/supply-chain/blueprint-verification-execution.js +17 -2
- package/src/supply-chain/blueprint-verification-execution.js.map +1 -1
- package/src/supply-chain/build-output.d.ts +7 -1
- package/src/supply-chain/build-output.js +160 -20
- package/src/supply-chain/build-output.js.map +1 -1
- package/src/supply-chain/generated-command.js +131 -43
- package/src/supply-chain/generated-command.js.map +1 -1
- package/src/supply-chain/generated-container-release-command.js +187 -38
- package/src/supply-chain/generated-container-release-command.js.map +1 -1
- package/src/supply-chain/generated-ecr-collector-command.js +12 -2
- package/src/supply-chain/generated-ecr-collector-command.js.map +1 -1
- package/src/supply-chain/generated-scanner-command.js +114 -54
- package/src/supply-chain/generated-scanner-command.js.map +1 -1
- package/src/supply-chain/inventory.d.ts +17 -0
- package/src/supply-chain/inventory.js +114 -3
- package/src/supply-chain/inventory.js.map +1 -1
- package/src/supply-chain/pnpm-toolchain-authority.d.ts +4 -3
- package/src/supply-chain/pnpm-toolchain-authority.js +20 -34
- package/src/supply-chain/pnpm-toolchain-authority.js.map +1 -1
- package/src/supply-chain/policy.js +4 -1
- package/src/supply-chain/policy.js.map +1 -1
- package/src/supply-chain/sbom.d.ts +1 -1
- package/src/supply-chain/sbom.js +30 -5
- package/src/supply-chain/sbom.js.map +1 -1
- package/src/supply-chain/scanner.js +10 -1
- package/src/supply-chain/scanner.js.map +1 -1
- package/src/workload-roles/account-baseline.d.ts +11 -0
- package/src/workload-roles/account-baseline.js +253 -16
- package/src/workload-roles/account-baseline.js.map +1 -1
- package/src/workload-roles/authority.d.ts +3 -1
- package/src/workload-roles/authority.js +48 -2
- package/src/workload-roles/authority.js.map +1 -1
- package/src/workload-roles/aws-s3-state.js +27 -0
- package/src/workload-roles/aws-s3-state.js.map +1 -1
- package/src/workload-roles/boundary.d.ts +2 -1
- package/src/workload-roles/boundary.js +9 -2
- package/src/workload-roles/boundary.js.map +1 -1
- package/src/workload-roles/handler.js +52 -24
- package/src/workload-roles/handler.js.map +1 -1
- package/src/workload-roles/injection.js +1 -1
- package/src/workload-roles/injection.js.map +1 -1
- package/src/workload-roles/provisioner.js +12 -4
- package/src/workload-roles/provisioner.js.map +1 -1
- package/src/workload-roles/registration.d.ts +1 -0
- package/src/workload-roles/registration.js +5 -0
- package/src/workload-roles/registration.js.map +1 -1
- package/src/workload-roles/stage-boundary.js +1 -1
- package/src/workload-roles/stage-boundary.js.map +1 -1
- package/src/workload-roles/standard-slice.js +1 -0
- package/src/workload-roles/standard-slice.js.map +1 -1
|
@@ -16,6 +16,7 @@ const index_js_6 = require("../../supply-chain/index.js");
|
|
|
16
16
|
const deterministic_order_js_1 = require("../../deterministic-order.js");
|
|
17
17
|
const tool_versions_js_1 = require("./tool-versions.js");
|
|
18
18
|
const workspace_files_developer_agent_js_1 = require("./workspace-files-developer-agent.js");
|
|
19
|
+
const workspace_files_ci_acceptance_js_1 = require("./workspace-files-ci-acceptance.js");
|
|
19
20
|
const workspace_files_foundation_workload_roles_js_1 = require("./workspace-files-foundation-workload-roles.js");
|
|
20
21
|
const workspace_files_performance_cost_js_1 = require("./workspace-files-performance-cost.js");
|
|
21
22
|
const workspace_files_performance_load_js_1 = require("./workspace-files-performance-load.js");
|
|
@@ -60,11 +61,12 @@ exports.PLAN_PROVIDER_REFRESH_GLOBAL_READ_ACTIONS = [
|
|
|
60
61
|
'rds:DescribeDBProxies',
|
|
61
62
|
'rds:DescribeDBSubnetGroups',
|
|
62
63
|
];
|
|
63
|
-
function validatePlanProviderRefreshPolicy(value, region, account) {
|
|
64
|
+
function validatePlanProviderRefreshPolicy(value, region, account, stage) {
|
|
64
65
|
if (!Array.isArray(value) ||
|
|
65
66
|
value.length === 0 ||
|
|
66
67
|
!/^[a-z]{2}(?:-gov)?-[a-z]+-\d$/.test(region) ||
|
|
67
|
-
!/^\d{12}$/.test(account)
|
|
68
|
+
!/^\d{12}$/.test(account) ||
|
|
69
|
+
!['production', 'staging'].includes(stage)) {
|
|
68
70
|
throw new Error('Provider refresh policy must be a non-empty exact statement array for one account and region.');
|
|
69
71
|
}
|
|
70
72
|
const allowed = new Set(exports.PLAN_PROVIDER_REFRESH_READ_ACTIONS);
|
|
@@ -117,6 +119,14 @@ function validatePlanProviderRefreshPolicy(value, region, account) {
|
|
|
117
119
|
if (resources.some((resource) => {
|
|
118
120
|
const arn = resource.split(':');
|
|
119
121
|
const target = arn.slice(5).join(':');
|
|
122
|
+
const boundedPathWildcard = target.endsWith('/*') && !/[*?]/.test(target.slice(0, -2));
|
|
123
|
+
const boundedLambdaStagePrefix = arn[2] === 'lambda' &&
|
|
124
|
+
actions.every((action) => action.startsWith('lambda:') && !global.has(action)) &&
|
|
125
|
+
new RegExp('^function:(?:' +
|
|
126
|
+
stage +
|
|
127
|
+
'-|[A-Za-z0-9][A-Za-z0-9_-]*-' +
|
|
128
|
+
stage +
|
|
129
|
+
'-)[A-Za-z0-9_-]*\\*$').test(target);
|
|
120
130
|
return (arn[0] !== 'arn' ||
|
|
121
131
|
arn[1] !== 'aws' ||
|
|
122
132
|
!/^[a-z0-9-]+$/.test(arn[2] ?? '') ||
|
|
@@ -124,7 +134,8 @@ function validatePlanProviderRefreshPolicy(value, region, account) {
|
|
|
124
134
|
!['', account].includes(arn[4] ?? '') ||
|
|
125
135
|
!target ||
|
|
126
136
|
(/[*?]/.test(target) &&
|
|
127
|
-
|
|
137
|
+
!boundedPathWildcard &&
|
|
138
|
+
!boundedLambdaStagePrefix));
|
|
128
139
|
})) {
|
|
129
140
|
throw new Error(`Provider refresh statement ${index} is not bounded to exact targets in the selected account and region.`);
|
|
130
141
|
}
|
|
@@ -474,6 +485,8 @@ function planRoleAuditStep(branch, mode = 'produce') {
|
|
|
474
485
|
EBK_PLAN_ENVIRONMENT: infrastructure-plan-${stage}
|
|
475
486
|
EBK_PLAN_ROLE_ARN: \${{ env.AWS_PLAN_ROLE_ARN }}
|
|
476
487
|
EBK_PLAN_REPOSITORY: \${{ github.repository }}
|
|
488
|
+
EBK_PLAN_REPOSITORY_ID: \${{ github.repository_id }}
|
|
489
|
+
EBK_PLAN_REPOSITORY_OWNER_ID: \${{ github.repository_owner_id }}
|
|
477
490
|
run: |
|
|
478
491
|
role_name="\${EBK_PLAN_ROLE_ARN##*/}"
|
|
479
492
|
test "$(aws sts get-caller-identity --query Account --output text)" = "$AWS_EXPECTED_ACCOUNT_ID" ||
|
|
@@ -506,8 +519,23 @@ function planRoleAuditStep(branch, mode = 'produce') {
|
|
|
506
519
|
const role = JSON.parse(readFileSync(process.argv[2], 'utf8')).Role;
|
|
507
520
|
const policy = JSON.parse(readFileSync(process.argv[3], 'utf8')).PolicyDocument;
|
|
508
521
|
const account = process.env.AWS_EXPECTED_ACCOUNT_ID;
|
|
522
|
+
const repository = process.env.EBK_PLAN_REPOSITORY ?? '';
|
|
523
|
+
const repositoryId = process.env.EBK_PLAN_REPOSITORY_ID ?? '';
|
|
524
|
+
const repositoryOwnerId = process.env.EBK_PLAN_REPOSITORY_OWNER_ID ?? '';
|
|
509
525
|
const stateBucketArn = process.env.AWS_PLAN_STATE_BUCKET_ARN;
|
|
510
526
|
const stateKmsKeyArn = process.env.AWS_PLAN_STATE_KMS_KEY_ARN;
|
|
527
|
+
if (
|
|
528
|
+
!/^[A-Za-z0-9](?:[A-Za-z0-9-]{0,38})\\/[A-Za-z0-9_.-]{1,100}$/.test(
|
|
529
|
+
repository,
|
|
530
|
+
) ||
|
|
531
|
+
!/^[1-9][0-9]*$/.test(repositoryOwnerId) ||
|
|
532
|
+
!/^[1-9][0-9]*$/.test(repositoryId)
|
|
533
|
+
) throw new Error('Exact immutable GitHub repository identity is required.');
|
|
534
|
+
const [repositoryOwner, repositoryName] = repository.split('/');
|
|
535
|
+
const oidcSubject =
|
|
536
|
+
'repo:' + repositoryOwner + '@' + repositoryOwnerId + '/' +
|
|
537
|
+
repositoryName + '@' + repositoryId + ':environment:' +
|
|
538
|
+
process.env.EBK_PLAN_ENVIRONMENT;
|
|
511
539
|
const parseExactArnArray = (name, pattern) => {
|
|
512
540
|
let value;
|
|
513
541
|
try {
|
|
@@ -551,6 +579,7 @@ function planRoleAuditStep(branch, mode = 'produce') {
|
|
|
551
579
|
'AWS_PLAN_GENERATED_SECRET_ARNS_JSON',
|
|
552
580
|
new RegExp('^arn:aws:secretsmanager:' + process.env.AWS_REGION + ':' + account + ':secret:ebk/'),
|
|
553
581
|
);
|
|
582
|
+
const stateStage = process.env.EBK_PLAN_BRANCH === 'main' ? 'production' : 'staging';
|
|
554
583
|
let providerRefreshPolicy;
|
|
555
584
|
try {
|
|
556
585
|
providerRefreshPolicy = JSON.parse(
|
|
@@ -614,6 +643,23 @@ function planRoleAuditStep(branch, mode = 'produce') {
|
|
|
614
643
|
for (const resource of resources) {
|
|
615
644
|
const arn = resource.split(':');
|
|
616
645
|
const target = arn.slice(5).join(':');
|
|
646
|
+
const boundedPathWildcard =
|
|
647
|
+
target.endsWith('/*') &&
|
|
648
|
+
!/[*?]/.test(target.slice(0, -2));
|
|
649
|
+
const boundedLambdaStagePrefix =
|
|
650
|
+
arn[2] === 'lambda' &&
|
|
651
|
+
actions.every(
|
|
652
|
+
(action) =>
|
|
653
|
+
action.startsWith('lambda:') &&
|
|
654
|
+
!globalProviderReadActions.has(action),
|
|
655
|
+
) &&
|
|
656
|
+
new RegExp(
|
|
657
|
+
'^function:(?:' +
|
|
658
|
+
stateStage +
|
|
659
|
+
'-|[A-Za-z0-9][A-Za-z0-9_-]*-' +
|
|
660
|
+
stateStage +
|
|
661
|
+
'-)[A-Za-z0-9_-]*\\\\*$',
|
|
662
|
+
).test(target);
|
|
617
663
|
if (
|
|
618
664
|
arn[0] !== 'arn' ||
|
|
619
665
|
arn[1] !== 'aws' ||
|
|
@@ -622,7 +668,8 @@ function planRoleAuditStep(branch, mode = 'produce') {
|
|
|
622
668
|
!['', account].includes(arn[4] ?? '') ||
|
|
623
669
|
!target ||
|
|
624
670
|
(/[*?]/.test(target) &&
|
|
625
|
-
|
|
671
|
+
!boundedPathWildcard &&
|
|
672
|
+
!boundedLambdaStagePrefix)
|
|
626
673
|
) throw new Error('Provider refresh statement ' + index + ' has a target outside the selected account and region.');
|
|
627
674
|
}
|
|
628
675
|
}
|
|
@@ -637,7 +684,6 @@ function planRoleAuditStep(branch, mode = 'produce') {
|
|
|
637
684
|
).sort((left, right) =>
|
|
638
685
|
left.Sid < right.Sid ? -1 : left.Sid > right.Sid ? 1 : 0,
|
|
639
686
|
);
|
|
640
|
-
const stateStage = process.env.EBK_PLAN_BRANCH === 'main' ? 'production' : 'staging';
|
|
641
687
|
if (
|
|
642
688
|
stateKmsKeyArn &&
|
|
643
689
|
stateKmsKeyArn !==
|
|
@@ -681,7 +727,7 @@ function planRoleAuditStep(branch, mode = 'produce') {
|
|
|
681
727
|
Action: 'sts:AssumeRoleWithWebIdentity',
|
|
682
728
|
Condition: { StringEquals: {
|
|
683
729
|
'token.actions.githubusercontent.com:aud': 'sts.amazonaws.com',
|
|
684
|
-
'token.actions.githubusercontent.com:sub':
|
|
730
|
+
'token.actions.githubusercontent.com:sub': oidcSubject,
|
|
685
731
|
}},
|
|
686
732
|
}],
|
|
687
733
|
};
|
|
@@ -708,6 +754,19 @@ function planRoleAuditStep(branch, mode = 'produce') {
|
|
|
708
754
|
],
|
|
709
755
|
Resource: '*',
|
|
710
756
|
},
|
|
757
|
+
{
|
|
758
|
+
Sid: 'ObserveStageSecretVersionIdentity',
|
|
759
|
+
Effect: 'Allow',
|
|
760
|
+
Action: 'secretsmanager:ListSecretVersionIds',
|
|
761
|
+
Resource:
|
|
762
|
+
'arn:aws:secretsmanager:' + process.env.AWS_REGION + ':' +
|
|
763
|
+
account + ':secret:*',
|
|
764
|
+
Condition: {
|
|
765
|
+
StringEquals: {
|
|
766
|
+
'aws:ResourceTag/ebk:stage': stateStage,
|
|
767
|
+
},
|
|
768
|
+
},
|
|
769
|
+
},
|
|
711
770
|
{
|
|
712
771
|
Sid: 'ValidatePlannedIamPolicies',
|
|
713
772
|
Effect: 'Allow',
|
|
@@ -782,7 +841,10 @@ function planRoleAuditStep(branch, mode = 'produce') {
|
|
|
782
841
|
generatedSecretArns,
|
|
783
842
|
providerRefreshStatements,
|
|
784
843
|
region: process.env.AWS_REGION,
|
|
785
|
-
repository
|
|
844
|
+
repository,
|
|
845
|
+
repositoryId,
|
|
846
|
+
repositoryOwnerId,
|
|
847
|
+
oidcSubject,
|
|
786
848
|
role: {
|
|
787
849
|
arn: role.Arn,
|
|
788
850
|
policy: canonical(policy),
|
|
@@ -826,11 +888,17 @@ function planRoleAuditJob(stage, configureAwsAction) {
|
|
|
826
888
|
const production = stage === 'production';
|
|
827
889
|
const branch = production ? 'main' : 'develop';
|
|
828
890
|
const prefix = production ? 'AWS_PRODUCTION_PLAN' : 'AWS_STAGING_PLAN';
|
|
891
|
+
const profileAdmission = production
|
|
892
|
+
? ''
|
|
893
|
+
: ` (github.event_name == 'workflow_dispatch' ||\n needs.deployment-mode.outputs.profile != 'starter') &&\n`;
|
|
829
894
|
return ` infrastructure-plan-role-audit-${stage}:
|
|
830
895
|
name: audit ${stage} infrastructure plan role
|
|
831
|
-
needs: route
|
|
896
|
+
needs: [route, deployment-mode]
|
|
832
897
|
if: |
|
|
833
|
-
|
|
898
|
+
always() &&
|
|
899
|
+
needs.route.result == 'success' &&
|
|
900
|
+
needs.deployment-mode.result == 'success' &&
|
|
901
|
+
${profileAdmission} github.ref == 'refs/heads/${branch}' &&
|
|
834
902
|
(github.event_name == 'push' && 'deploy' || inputs.action) != 'unlock'
|
|
835
903
|
runs-on: ubuntu-latest
|
|
836
904
|
environment: infrastructure-plan-${stage}
|
|
@@ -953,6 +1021,9 @@ const WORKSPACE_SEEDED_PATHS = new Set([
|
|
|
953
1021
|
exports.WORKSPACE_PACKAGE_MANAGED_JSON_POINTERS = [
|
|
954
1022
|
'/devDependencies/eslint-plugin-jsx-a11y',
|
|
955
1023
|
'/devDependencies/vitest',
|
|
1024
|
+
'/engines/node',
|
|
1025
|
+
'/engines/pnpm',
|
|
1026
|
+
'/packageManager',
|
|
956
1027
|
];
|
|
957
1028
|
exports.WORKSPACE_PROJECT_MANAGED_JSON_POINTERS = [
|
|
958
1029
|
'/name',
|
|
@@ -961,9 +1032,11 @@ exports.WORKSPACE_PROJECT_MANAGED_JSON_POINTERS = [
|
|
|
961
1032
|
'/root',
|
|
962
1033
|
'/tags',
|
|
963
1034
|
'/targets/blueprint-trust-check',
|
|
1035
|
+
'/targets/blueprint-trust-check-main',
|
|
964
1036
|
'/targets/blueprint-trust-check-protected',
|
|
965
1037
|
'/targets/developer-context',
|
|
966
1038
|
'/targets/deployment-plan',
|
|
1039
|
+
'/targets/deploy-local',
|
|
967
1040
|
'/targets/dev',
|
|
968
1041
|
'/targets/dev-doctor',
|
|
969
1042
|
'/targets/dev-setup',
|
|
@@ -973,6 +1046,7 @@ exports.WORKSPACE_PROJECT_MANAGED_JSON_POINTERS = [
|
|
|
973
1046
|
'/targets/supply-chain-check-protected',
|
|
974
1047
|
'/targets/supply-chain-scan',
|
|
975
1048
|
'/targets/verify',
|
|
1049
|
+
'/targets/verify-proposed',
|
|
976
1050
|
'/targets/work-markers',
|
|
977
1051
|
'/targets/workload-role-candidate-report',
|
|
978
1052
|
'/targets/workload-role-provisioner-build',
|
|
@@ -983,6 +1057,10 @@ const FOUNDATION_SEEDED_PATHS = new Set([
|
|
|
983
1057
|
'docs/costs.md',
|
|
984
1058
|
'docs/runbooks.md',
|
|
985
1059
|
'docs/security.md',
|
|
1060
|
+
'performance/account-cost-policy.json',
|
|
1061
|
+
'security/router-waf-blocking-rehearsal.json',
|
|
1062
|
+
'security/router-waf-live-evidence.json',
|
|
1063
|
+
'security/router-waf.json',
|
|
986
1064
|
]);
|
|
987
1065
|
function createFoundationOwnership(files, frameworkVersion) {
|
|
988
1066
|
const paths = Object.keys(files)
|
|
@@ -1045,7 +1123,16 @@ function createWorkspaceOwnership(files, frameworkVersion) {
|
|
|
1045
1123
|
}
|
|
1046
1124
|
function renderWorkspaceFiles(options) {
|
|
1047
1125
|
const { frameworkVersion, name } = options;
|
|
1126
|
+
const deploymentProfile = options.deploymentProfile ?? 'starter';
|
|
1127
|
+
const initialRepositoryPolicy = (0, index_js_4.createIncubationRepositoryPolicy)(deploymentProfile);
|
|
1128
|
+
initialRepositoryPolicy.deploymentAccounts = options.deploymentAccounts ?? {
|
|
1129
|
+
development: null,
|
|
1130
|
+
production: null,
|
|
1131
|
+
staging: null,
|
|
1132
|
+
};
|
|
1133
|
+
initialRepositoryPolicy.deploymentRegion = options.deploymentRegion ?? null;
|
|
1048
1134
|
const developerAgentFilePlan = (0, workspace_files_developer_agent_js_1.renderDeveloperAgentWorkspaceFilePlan)({
|
|
1135
|
+
deploymentProfile,
|
|
1049
1136
|
frameworkVersion,
|
|
1050
1137
|
name,
|
|
1051
1138
|
});
|
|
@@ -1056,7 +1143,13 @@ function renderWorkspaceFiles(options) {
|
|
|
1056
1143
|
const pnpmAction = (0, index_js_3.pinnedAction)(index_js_3.GITHUB_ACTION_PINS.pnpmSetup);
|
|
1057
1144
|
const setupNodeAction = (0, index_js_3.pinnedAction)(index_js_3.GITHUB_ACTION_PINS.setupNode);
|
|
1058
1145
|
const uploadArtifactAction = (0, index_js_3.pinnedAction)(index_js_3.GITHUB_ACTION_PINS.uploadArtifact);
|
|
1146
|
+
const protectedDevelopPromotion = "github.event.pull_request.head.repo.full_name == github.repository && github.event.pull_request.head.ref == 'develop' && github.event.pull_request.base.ref == 'main'";
|
|
1147
|
+
const protectedMainSync = "github.event.pull_request.head.repo.full_name == github.repository && github.event.pull_request.head.ref == 'main' && github.event.pull_request.base.ref == 'develop'";
|
|
1148
|
+
const protectedPush = "github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/develop')";
|
|
1149
|
+
const protectedDeploymentRouteJobName = 'protected deployment route';
|
|
1150
|
+
const stagingApplyJobPrefix = 'apply staging plan ';
|
|
1059
1151
|
const files = {
|
|
1152
|
+
...(0, workspace_files_ci_acceptance_js_1.renderWorkspaceCiAcceptanceFiles)(),
|
|
1060
1153
|
'.node-version': `${tool_versions_js_1.WORKSPACE_NODE_VERSION}\n`,
|
|
1061
1154
|
'.github/CODEOWNERS': `# Replace this placeholder with the GitHub user or team that owns protected source.
|
|
1062
1155
|
# GitHub bootstrap remains blocked until a non-placeholder catch-all owner is committed.
|
|
@@ -1072,15 +1165,77 @@ on:
|
|
|
1072
1165
|
workflow_dispatch:
|
|
1073
1166
|
|
|
1074
1167
|
concurrency:
|
|
1075
|
-
group: verify-\${{ github.ref }}
|
|
1076
|
-
cancel-in-progress:
|
|
1168
|
+
group: verify-\${{ github.event_name == 'push' && github.sha || github.ref }}
|
|
1169
|
+
cancel-in-progress: \${{ github.event_name != 'push' }}
|
|
1077
1170
|
|
|
1078
1171
|
permissions:
|
|
1172
|
+
actions: read
|
|
1079
1173
|
contents: read
|
|
1174
|
+
pull-requests: read
|
|
1175
|
+
|
|
1176
|
+
env:
|
|
1177
|
+
EBK_CI_CANDIDATE_SHA: \${{ github.event.pull_request.head.sha || github.sha }}
|
|
1080
1178
|
|
|
1081
1179
|
jobs:
|
|
1180
|
+
acceptance:
|
|
1181
|
+
name: accepted merge evidence
|
|
1182
|
+
if: ${protectedPush}
|
|
1183
|
+
runs-on: ubuntu-latest
|
|
1184
|
+
outputs:
|
|
1185
|
+
history_only: \${{ steps.resolve.outputs.history_only == 'true' && steps.restore.outcome == 'success' && steps.verify.outcome == 'success' }}
|
|
1186
|
+
reused: \${{ steps.resolve.outputs.reused == 'true' && steps.restore.outcome == 'success' && steps.verify.outcome == 'success' }}
|
|
1187
|
+
route: \${{ steps.resolve.outputs.reused == 'true' && steps.restore.outcome == 'success' && steps.verify.outcome == 'success' && steps.resolve.outputs.route || '' }}
|
|
1188
|
+
steps:
|
|
1189
|
+
- name: Check out immutable merged candidate
|
|
1190
|
+
if: ${protectedPush}
|
|
1191
|
+
uses: ${checkoutAction}
|
|
1192
|
+
with:
|
|
1193
|
+
fetch-depth: 0
|
|
1194
|
+
ref: \${{ github.sha }}
|
|
1195
|
+
- name: Resolve exact protected-route acceptance
|
|
1196
|
+
if: ${protectedPush}
|
|
1197
|
+
id: resolve
|
|
1198
|
+
env:
|
|
1199
|
+
GH_TOKEN: \${{ github.token }}
|
|
1200
|
+
run: node tools/ci/workspace-acceptance.mjs resolve
|
|
1201
|
+
- name: Restore exact protected-route acceptance
|
|
1202
|
+
if: steps.resolve.outputs.reused == 'true'
|
|
1203
|
+
id: restore
|
|
1204
|
+
continue-on-error: true
|
|
1205
|
+
env:
|
|
1206
|
+
EBK_ACCEPTANCE_ARTIFACT_DIGEST: \${{ steps.resolve.outputs.artifact_digest }}
|
|
1207
|
+
EBK_ACCEPTANCE_ARTIFACT_ID: \${{ steps.resolve.outputs.artifact_id }}
|
|
1208
|
+
EBK_ACCEPTANCE_ARTIFACT_SIZE: \${{ steps.resolve.outputs.artifact_size }}
|
|
1209
|
+
GH_TOKEN: \${{ github.token }}
|
|
1210
|
+
run: >-
|
|
1211
|
+
node tools/ci/workspace-acceptance.mjs download
|
|
1212
|
+
.ebk/evidence/reused-acceptance
|
|
1213
|
+
"$EBK_ACCEPTANCE_ARTIFACT_ID"
|
|
1214
|
+
"$EBK_ACCEPTANCE_ARTIFACT_DIGEST"
|
|
1215
|
+
"$EBK_ACCEPTANCE_ARTIFACT_SIZE"
|
|
1216
|
+
- name: Verify exact merged-tree acceptance
|
|
1217
|
+
if: steps.resolve.outputs.reused == 'true' && steps.restore.outcome == 'success'
|
|
1218
|
+
id: verify
|
|
1219
|
+
continue-on-error: true
|
|
1220
|
+
env:
|
|
1221
|
+
EBK_ACCEPTANCE_BASE: \${{ steps.resolve.outputs.base_commit }}
|
|
1222
|
+
EBK_ACCEPTANCE_HEAD: \${{ steps.resolve.outputs.head_commit }}
|
|
1223
|
+
EBK_ACCEPTANCE_ROUTE: \${{ steps.resolve.outputs.route }}
|
|
1224
|
+
EBK_ACCEPTANCE_RUN_ATTEMPT: \${{ steps.resolve.outputs.run_attempt }}
|
|
1225
|
+
EBK_ACCEPTANCE_RUN_ID: \${{ steps.resolve.outputs.run_id }}
|
|
1226
|
+
run: >-
|
|
1227
|
+
node tools/ci/workspace-acceptance.mjs verify
|
|
1228
|
+
.ebk/evidence/reused-acceptance/manifest.json
|
|
1229
|
+
"$EBK_ACCEPTANCE_HEAD"
|
|
1230
|
+
"$EBK_ACCEPTANCE_BASE"
|
|
1231
|
+
"$EBK_ACCEPTANCE_ROUTE"
|
|
1232
|
+
"$EBK_ACCEPTANCE_RUN_ID"
|
|
1233
|
+
"$EBK_ACCEPTANCE_RUN_ATTEMPT"
|
|
1234
|
+
|
|
1082
1235
|
policy:
|
|
1083
1236
|
name: policy
|
|
1237
|
+
needs: acceptance
|
|
1238
|
+
if: always() && needs.acceptance.outputs.reused != 'true'
|
|
1084
1239
|
runs-on: ubuntu-latest
|
|
1085
1240
|
steps:
|
|
1086
1241
|
- uses: ${checkoutAction}
|
|
@@ -1094,22 +1249,100 @@ jobs:
|
|
|
1094
1249
|
cache: pnpm
|
|
1095
1250
|
- run: pnpm install --frozen-lockfile
|
|
1096
1251
|
- run: node tools/ebk-git-policy.mjs
|
|
1252
|
+
env:
|
|
1253
|
+
EBK_HEAD_REPOSITORY: \${{ github.event.pull_request.head.repo.full_name }}
|
|
1097
1254
|
|
|
1098
1255
|
verify:
|
|
1099
1256
|
name: verify
|
|
1257
|
+
needs: acceptance
|
|
1258
|
+
if: always() && needs.acceptance.outputs.reused != 'true'
|
|
1259
|
+
runs-on: ubuntu-latest
|
|
1260
|
+
steps:
|
|
1261
|
+
- uses: ${checkoutAction}
|
|
1262
|
+
with:
|
|
1263
|
+
fetch-depth: 0
|
|
1264
|
+
ref: \${{ github.event.pull_request.head.sha || github.sha }}
|
|
1265
|
+
- uses: ${pnpmAction}
|
|
1266
|
+
- uses: ${setupNodeAction}
|
|
1267
|
+
with:
|
|
1268
|
+
node-version: ${tool_versions_js_1.WORKSPACE_NODE_VERSION}
|
|
1269
|
+
cache: pnpm
|
|
1270
|
+
- run: pnpm install --frozen-lockfile
|
|
1271
|
+
- run: pnpm format:check
|
|
1272
|
+
- name: Verify proposed workspace
|
|
1273
|
+
if: github.event_name == 'pull_request' && !(${protectedDevelopPromotion} || ${protectedMainSync})
|
|
1274
|
+
run: |
|
|
1275
|
+
pnpm nx run ${name}:verify-proposed
|
|
1276
|
+
pnpm nx run-many -t verify --exclude=${name} --parallel=3
|
|
1277
|
+
- name: Verify protected develop promotion
|
|
1278
|
+
if: github.event_name == 'pull_request' && ${protectedDevelopPromotion}
|
|
1279
|
+
run: pnpm nx run-many -t verify --parallel=3
|
|
1280
|
+
- name: Verify protected main sync
|
|
1281
|
+
if: github.event_name == 'pull_request' && ${protectedMainSync}
|
|
1282
|
+
run: |
|
|
1283
|
+
pnpm nx run ${name}:blueprint-trust-check-main
|
|
1284
|
+
pnpm nx run ${name}:verify-proposed
|
|
1285
|
+
pnpm nx run-many -t verify --exclude=${name} --parallel=3
|
|
1286
|
+
- name: Verify adopted workspace
|
|
1287
|
+
if: github.event_name != 'pull_request'
|
|
1288
|
+
run: pnpm nx run-many -t verify --parallel=3
|
|
1289
|
+
|
|
1290
|
+
seal-acceptance:
|
|
1291
|
+
name: seal protected route acceptance
|
|
1292
|
+
needs: [policy, verify]
|
|
1293
|
+
if: >-
|
|
1294
|
+
always() &&
|
|
1295
|
+
needs.policy.result == 'success' &&
|
|
1296
|
+
needs.verify.result == 'success' &&
|
|
1297
|
+
github.event_name == 'pull_request' &&
|
|
1298
|
+
(${protectedDevelopPromotion} || ${protectedMainSync})
|
|
1299
|
+
runs-on: ubuntu-latest
|
|
1300
|
+
steps:
|
|
1301
|
+
- name: Check out immutable accepted candidate
|
|
1302
|
+
uses: ${checkoutAction}
|
|
1303
|
+
with:
|
|
1304
|
+
fetch-depth: 0
|
|
1305
|
+
ref: \${{ env.EBK_CI_CANDIDATE_SHA }}
|
|
1306
|
+
- name: Seal exact protected-route acceptance
|
|
1307
|
+
id: seal
|
|
1308
|
+
env:
|
|
1309
|
+
EBK_BASE_COMMIT: \${{ github.event.pull_request.base.sha }}
|
|
1310
|
+
EBK_HEAD_REPOSITORY: \${{ github.event.pull_request.head.repo.full_name }}
|
|
1311
|
+
run: >-
|
|
1312
|
+
node tools/ci/workspace-acceptance.mjs seal
|
|
1313
|
+
.ebk/evidence/ci/workspace-acceptance/manifest.json
|
|
1314
|
+
- name: Retain exact protected-route acceptance
|
|
1315
|
+
if: steps.seal.outputs.sealed == 'true'
|
|
1316
|
+
uses: ${uploadArtifactAction}
|
|
1317
|
+
with:
|
|
1318
|
+
name: workspace-acceptance-\${{ env.EBK_CI_CANDIDATE_SHA }}-\${{ github.run_id }}-\${{ github.run_attempt }}
|
|
1319
|
+
path: .ebk/evidence/ci/workspace-acceptance
|
|
1320
|
+
if-no-files-found: error
|
|
1321
|
+
include-hidden-files: true
|
|
1322
|
+
retention-days: 30
|
|
1323
|
+
|
|
1324
|
+
release-candidate:
|
|
1325
|
+
name: build immutable release candidates
|
|
1326
|
+
needs: [acceptance, policy, verify]
|
|
1327
|
+
if: >-
|
|
1328
|
+
always() &&
|
|
1329
|
+
github.event_name == 'push' &&
|
|
1330
|
+
needs.acceptance.outputs.history_only != 'true' &&
|
|
1331
|
+
(needs.acceptance.outputs.reused == 'true' ||
|
|
1332
|
+
(needs.policy.result == 'success' && needs.verify.result == 'success'))
|
|
1100
1333
|
runs-on: ubuntu-latest
|
|
1101
1334
|
steps:
|
|
1102
1335
|
- uses: ${checkoutAction}
|
|
1336
|
+
with:
|
|
1337
|
+
fetch-depth: 0
|
|
1338
|
+
ref: \${{ github.sha }}
|
|
1103
1339
|
- uses: ${pnpmAction}
|
|
1104
1340
|
- uses: ${setupNodeAction}
|
|
1105
1341
|
with:
|
|
1106
1342
|
node-version: ${tool_versions_js_1.WORKSPACE_NODE_VERSION}
|
|
1107
1343
|
cache: pnpm
|
|
1108
1344
|
- run: pnpm install --frozen-lockfile
|
|
1109
|
-
- run: pnpm nx format:check
|
|
1110
|
-
- run: pnpm nx run-many -t verify --parallel=3
|
|
1111
1345
|
- name: Build immutable release candidates
|
|
1112
|
-
if: github.event_name == 'push'
|
|
1113
1346
|
env:
|
|
1114
1347
|
EBK_SOURCE_COMMIT: \${{ github.sha }}
|
|
1115
1348
|
run: |
|
|
@@ -1127,7 +1360,6 @@ jobs:
|
|
|
1127
1360
|
".ebk/evidence/release-candidate/packages/$project/"
|
|
1128
1361
|
done
|
|
1129
1362
|
- name: Retain exact immutable release candidate
|
|
1130
|
-
if: github.event_name == 'push'
|
|
1131
1363
|
uses: ${uploadArtifactAction}
|
|
1132
1364
|
with:
|
|
1133
1365
|
name: release-candidate-\${{ github.sha }}
|
|
@@ -1162,7 +1394,97 @@ permissions:
|
|
|
1162
1394
|
contents: read
|
|
1163
1395
|
|
|
1164
1396
|
jobs:
|
|
1397
|
+
history-classification:
|
|
1398
|
+
name: classify protected history-only sync
|
|
1399
|
+
if: github.event_name == 'push' && github.ref == 'refs/heads/develop'
|
|
1400
|
+
runs-on: ubuntu-latest
|
|
1401
|
+
permissions:
|
|
1402
|
+
actions: read
|
|
1403
|
+
contents: read
|
|
1404
|
+
pull-requests: read
|
|
1405
|
+
outputs:
|
|
1406
|
+
history_only: \${{ steps.resolve.outputs.history_only == 'true' && steps.restore.outcome == 'success' && steps.verify.outcome == 'success' }}
|
|
1407
|
+
steps:
|
|
1408
|
+
- name: Check out immutable protected candidate
|
|
1409
|
+
uses: ${checkoutAction}
|
|
1410
|
+
with:
|
|
1411
|
+
fetch-depth: 0
|
|
1412
|
+
ref: \${{ github.sha }}
|
|
1413
|
+
- uses: ${setupNodeAction}
|
|
1414
|
+
with:
|
|
1415
|
+
node-version: ${tool_versions_js_1.WORKSPACE_NODE_VERSION}
|
|
1416
|
+
- name: Resolve exact protected-route acceptance
|
|
1417
|
+
id: resolve
|
|
1418
|
+
env:
|
|
1419
|
+
GH_TOKEN: \${{ github.token }}
|
|
1420
|
+
run: node tools/ci/workspace-acceptance.mjs resolve
|
|
1421
|
+
- name: Restore exact protected-route acceptance
|
|
1422
|
+
if: steps.resolve.outputs.reused == 'true'
|
|
1423
|
+
id: restore
|
|
1424
|
+
continue-on-error: true
|
|
1425
|
+
env:
|
|
1426
|
+
EBK_ACCEPTANCE_ARTIFACT_DIGEST: \${{ steps.resolve.outputs.artifact_digest }}
|
|
1427
|
+
EBK_ACCEPTANCE_ARTIFACT_ID: \${{ steps.resolve.outputs.artifact_id }}
|
|
1428
|
+
EBK_ACCEPTANCE_ARTIFACT_SIZE: \${{ steps.resolve.outputs.artifact_size }}
|
|
1429
|
+
GH_TOKEN: \${{ github.token }}
|
|
1430
|
+
run: >-
|
|
1431
|
+
node tools/ci/workspace-acceptance.mjs download
|
|
1432
|
+
.ebk/evidence/reused-history-acceptance
|
|
1433
|
+
"$EBK_ACCEPTANCE_ARTIFACT_ID"
|
|
1434
|
+
"$EBK_ACCEPTANCE_ARTIFACT_DIGEST"
|
|
1435
|
+
"$EBK_ACCEPTANCE_ARTIFACT_SIZE"
|
|
1436
|
+
- name: Verify exact history-only acceptance
|
|
1437
|
+
if: steps.resolve.outputs.reused == 'true' && steps.restore.outcome == 'success'
|
|
1438
|
+
id: verify
|
|
1439
|
+
continue-on-error: true
|
|
1440
|
+
env:
|
|
1441
|
+
EBK_ACCEPTANCE_BASE: \${{ steps.resolve.outputs.base_commit }}
|
|
1442
|
+
EBK_ACCEPTANCE_HEAD: \${{ steps.resolve.outputs.head_commit }}
|
|
1443
|
+
EBK_ACCEPTANCE_ROUTE: \${{ steps.resolve.outputs.route }}
|
|
1444
|
+
EBK_ACCEPTANCE_RUN_ATTEMPT: \${{ steps.resolve.outputs.run_attempt }}
|
|
1445
|
+
EBK_ACCEPTANCE_RUN_ID: \${{ steps.resolve.outputs.run_id }}
|
|
1446
|
+
run: >-
|
|
1447
|
+
node tools/ci/workspace-acceptance.mjs verify
|
|
1448
|
+
.ebk/evidence/reused-history-acceptance/manifest.json
|
|
1449
|
+
"$EBK_ACCEPTANCE_HEAD"
|
|
1450
|
+
"$EBK_ACCEPTANCE_BASE"
|
|
1451
|
+
"$EBK_ACCEPTANCE_ROUTE"
|
|
1452
|
+
"$EBK_ACCEPTANCE_RUN_ID"
|
|
1453
|
+
"$EBK_ACCEPTANCE_RUN_ATTEMPT"
|
|
1454
|
+
|
|
1455
|
+
deployment-mode:
|
|
1456
|
+
name: classify deployment profile
|
|
1457
|
+
runs-on: ubuntu-latest
|
|
1458
|
+
outputs:
|
|
1459
|
+
profile: \${{ steps.profile.outputs.profile }}
|
|
1460
|
+
steps:
|
|
1461
|
+
- name: Check out immutable deployment policy
|
|
1462
|
+
uses: ${checkoutAction}
|
|
1463
|
+
with:
|
|
1464
|
+
ref: \${{ github.sha }}
|
|
1465
|
+
- id: profile
|
|
1466
|
+
name: Read exact committed deployment profile
|
|
1467
|
+
run: |
|
|
1468
|
+
node - <<'NODE'
|
|
1469
|
+
const { readFileSync } = require('node:fs');
|
|
1470
|
+
const policy = JSON.parse(readFileSync('ebk.repository-policy.json', 'utf8'));
|
|
1471
|
+
const profiles = new Set(['starter', 'production-isolated', 'fully-isolated']);
|
|
1472
|
+
if (!profiles.has(policy.deploymentProfile)) {
|
|
1473
|
+
throw new Error('Committed deployment profile is absent or invalid.');
|
|
1474
|
+
}
|
|
1475
|
+
require('node:fs').appendFileSync(
|
|
1476
|
+
process.env.GITHUB_OUTPUT,
|
|
1477
|
+
'profile=' + policy.deploymentProfile + '\\n',
|
|
1478
|
+
);
|
|
1479
|
+
NODE
|
|
1480
|
+
|
|
1165
1481
|
route:
|
|
1482
|
+
name: ${protectedDeploymentRouteJobName}
|
|
1483
|
+
needs: [history-classification, deployment-mode]
|
|
1484
|
+
if: >-
|
|
1485
|
+
always() &&
|
|
1486
|
+
needs.history-classification.outputs.history_only != 'true' &&
|
|
1487
|
+
needs.deployment-mode.result == 'success'
|
|
1166
1488
|
runs-on: ubuntu-latest
|
|
1167
1489
|
steps:
|
|
1168
1490
|
- name: Validate protected ref
|
|
@@ -1175,14 +1497,25 @@ jobs:
|
|
|
1175
1497
|
esac
|
|
1176
1498
|
|
|
1177
1499
|
plan-staging:
|
|
1178
|
-
needs: route
|
|
1179
|
-
if:
|
|
1500
|
+
needs: [route, deployment-mode]
|
|
1501
|
+
if: >-
|
|
1502
|
+
always() &&
|
|
1503
|
+
needs.route.result == 'success' &&
|
|
1504
|
+
needs.deployment-mode.result == 'success' &&
|
|
1505
|
+
github.ref == 'refs/heads/develop' &&
|
|
1506
|
+
(github.event_name == 'workflow_dispatch' ||
|
|
1507
|
+
needs.deployment-mode.outputs.profile != 'starter')
|
|
1180
1508
|
runs-on: ubuntu-latest
|
|
1181
1509
|
outputs:
|
|
1510
|
+
deployment_profile: \${{ steps.plan.outputs.deployment_profile }}
|
|
1511
|
+
deployment_region: \${{ steps.plan.outputs.deployment_region }}
|
|
1512
|
+
development_account_id: \${{ steps.plan.outputs.development_account_id }}
|
|
1182
1513
|
foundation_project: \${{ steps.plan.outputs.foundation_project }}
|
|
1183
1514
|
has_slices: \${{ steps.plan.outputs.has_slices }}
|
|
1184
1515
|
ordered_projects: \${{ steps.plan.outputs.ordered_projects }}
|
|
1516
|
+
production_account_id: \${{ steps.plan.outputs.production_account_id }}
|
|
1185
1517
|
slice_projects: \${{ steps.plan.outputs.slice_projects }}
|
|
1518
|
+
staging_account_id: \${{ steps.plan.outputs.staging_account_id }}
|
|
1186
1519
|
steps:
|
|
1187
1520
|
- uses: ${checkoutAction}
|
|
1188
1521
|
with:
|
|
@@ -1393,16 +1726,25 @@ jobs:
|
|
|
1393
1726
|
path: .ebk/evidence/staging-release-source/**
|
|
1394
1727
|
|
|
1395
1728
|
plan-production:
|
|
1396
|
-
needs: route
|
|
1397
|
-
if:
|
|
1729
|
+
needs: [route, deployment-mode]
|
|
1730
|
+
if: >-
|
|
1731
|
+
always() &&
|
|
1732
|
+
needs.route.result == 'success' &&
|
|
1733
|
+
needs.deployment-mode.result == 'success' &&
|
|
1734
|
+
github.ref == 'refs/heads/main'
|
|
1398
1735
|
runs-on: ubuntu-latest
|
|
1399
1736
|
outputs:
|
|
1737
|
+
deployment_profile: \${{ steps.plan.outputs.deployment_profile }}
|
|
1738
|
+
deployment_region: \${{ steps.plan.outputs.deployment_region }}
|
|
1739
|
+
development_account_id: \${{ steps.plan.outputs.development_account_id }}
|
|
1400
1740
|
foundation_project: \${{ steps.plan.outputs.foundation_project }}
|
|
1401
1741
|
foundation_profile: \${{ steps.plan.outputs.foundation_profile }}
|
|
1402
1742
|
has_slices: \${{ steps.plan.outputs.has_slices }}
|
|
1403
1743
|
ordered_projects: \${{ steps.plan.outputs.ordered_projects }}
|
|
1404
1744
|
production_approval_mode: \${{ steps.plan.outputs.production_approval_mode }}
|
|
1745
|
+
production_account_id: \${{ steps.plan.outputs.production_account_id }}
|
|
1405
1746
|
slice_projects: \${{ steps.plan.outputs.slice_projects }}
|
|
1747
|
+
staging_account_id: \${{ steps.plan.outputs.staging_account_id }}
|
|
1406
1748
|
steps:
|
|
1407
1749
|
- uses: ${checkoutAction}
|
|
1408
1750
|
with:
|
|
@@ -1723,6 +2065,8 @@ jobs:
|
|
|
1723
2065
|
EBK_RELEASE_SOURCE_COMMIT: \${{ needs.release-source-staging.outputs.source_commit }}
|
|
1724
2066
|
EBK_SOURCE_COMMIT: \${{ needs.release-source-staging.outputs.source_commit }}
|
|
1725
2067
|
EBK_STAGE: staging
|
|
2068
|
+
EBK_POLICY_ACCOUNT_ID: \${{ needs.plan-staging.outputs.staging_account_id }}
|
|
2069
|
+
EBK_POLICY_REGION: \${{ needs.plan-staging.outputs.deployment_region }}
|
|
1726
2070
|
SLICE_PROJECTS: \${{ needs.plan-staging.outputs.slice_projects }}
|
|
1727
2071
|
steps:
|
|
1728
2072
|
- uses: ${checkoutAction}
|
|
@@ -1738,6 +2082,10 @@ jobs:
|
|
|
1738
2082
|
- run: pnpm install --frozen-lockfile --ignore-scripts --ignore-pnpmfile
|
|
1739
2083
|
- name: Verify managed workspace integrity before AWS authority
|
|
1740
2084
|
run: pnpm nx run ${name}:workspace-health
|
|
2085
|
+
- name: Bind staging account to committed deployment topology
|
|
2086
|
+
run: |
|
|
2087
|
+
test "$AWS_EXPECTED_ACCOUNT_ID" = "$EBK_POLICY_ACCOUNT_ID"
|
|
2088
|
+
test "$AWS_REGION" = "$EBK_POLICY_REGION"
|
|
1741
2089
|
${stagingReleaseCandidateDownloadSteps(downloadArtifactAction)} - uses: ${configureAwsAction}
|
|
1742
2090
|
with:
|
|
1743
2091
|
role-to-assume: \${{ secrets.AWS_DEPLOY_ROLE_ARN }}
|
|
@@ -1777,6 +2125,8 @@ ${stagingReleaseCandidateDownloadSteps(downloadArtifactAction)} - uses: ${c
|
|
|
1777
2125
|
EBK_RELEASE_SOURCE_COMMIT: \${{ needs.release-source-staging.outputs.source_commit }}
|
|
1778
2126
|
EBK_SOURCE_COMMIT: \${{ needs.release-source-staging.outputs.source_commit }}
|
|
1779
2127
|
EBK_STAGE: staging
|
|
2128
|
+
EBK_POLICY_ACCOUNT_ID: \${{ needs.plan-staging.outputs.staging_account_id }}
|
|
2129
|
+
EBK_POLICY_REGION: \${{ needs.plan-staging.outputs.deployment_region }}
|
|
1780
2130
|
SLICE_PROJECTS: \${{ needs.plan-staging.outputs.slice_projects }}
|
|
1781
2131
|
steps:
|
|
1782
2132
|
- uses: ${checkoutAction}
|
|
@@ -1792,6 +2142,10 @@ ${stagingReleaseCandidateDownloadSteps(downloadArtifactAction)} - uses: ${c
|
|
|
1792
2142
|
- run: pnpm install --frozen-lockfile --ignore-scripts --ignore-pnpmfile
|
|
1793
2143
|
- name: Verify managed workspace integrity before AWS authority
|
|
1794
2144
|
run: pnpm nx run ${name}:workspace-health
|
|
2145
|
+
- name: Bind staging account to committed deployment topology
|
|
2146
|
+
run: |
|
|
2147
|
+
test "$AWS_EXPECTED_ACCOUNT_ID" = "$EBK_POLICY_ACCOUNT_ID"
|
|
2148
|
+
test "$AWS_REGION" = "$EBK_POLICY_REGION"
|
|
1795
2149
|
${stagingReleaseCandidateDownloadSteps(downloadArtifactAction)} - name: Restore both native platform records
|
|
1796
2150
|
uses: ${downloadArtifactAction}
|
|
1797
2151
|
with:
|
|
@@ -1833,6 +2187,10 @@ ${stagingReleaseCandidateDownloadSteps(downloadArtifactAction)} - name: Res
|
|
|
1833
2187
|
AWS_DEPLOY_ROLE_ARN: \${{ secrets.AWS_DEPLOY_ROLE_ARN }}
|
|
1834
2188
|
AWS_EXPECTED_ACCOUNT_ID: \${{ vars.AWS_EXPECTED_ACCOUNT_ID }}
|
|
1835
2189
|
AWS_REGION: \${{ vars.AWS_REGION }}
|
|
2190
|
+
EBK_DEPLOYMENT_PROFILE: \${{ needs.plan-production.outputs.deployment_profile }}
|
|
2191
|
+
EBK_POLICY_PRODUCTION_ACCOUNT_ID: \${{ needs.plan-production.outputs.production_account_id }}
|
|
2192
|
+
EBK_POLICY_REGION: \${{ needs.plan-production.outputs.deployment_region }}
|
|
2193
|
+
EBK_POLICY_STAGING_ACCOUNT_ID: \${{ needs.plan-production.outputs.staging_account_id }}
|
|
1836
2194
|
EBK_CANDIDATE_RUN_ATTEMPT: \${{ needs.release-source-production.outputs.candidate_run_attempt }}
|
|
1837
2195
|
EBK_CANDIDATE_RUN_ID: \${{ needs.release-source-production.outputs.candidate_run_id }}
|
|
1838
2196
|
EBK_RELEASE_SOURCE_COMMIT: \${{ needs.release-source-production.outputs.source_commit }}
|
|
@@ -1933,11 +2291,13 @@ ${stagingReleaseCandidateDownloadSteps(downloadArtifactAction)} - name: Res
|
|
|
1933
2291
|
cp "$source_sidecar" "$target"
|
|
1934
2292
|
done
|
|
1935
2293
|
- uses: ${configureAwsAction}
|
|
2294
|
+
if: env.EBK_DEPLOYMENT_PROFILE != 'starter'
|
|
1936
2295
|
with:
|
|
1937
2296
|
role-to-assume: \${{ secrets.AWS_STAGING_PROMOTION_PULL_ROLE_ARN }}
|
|
1938
2297
|
role-session-name: ebk-staging-container-pull-\${{ github.run_id }}-\${{ github.run_attempt }}
|
|
1939
2298
|
aws-region: \${{ vars.AWS_STAGING_PROMOTION_REGION }}
|
|
1940
2299
|
- name: Authenticate exact staging source registry
|
|
2300
|
+
if: env.EBK_DEPLOYMENT_PROFILE != 'starter'
|
|
1941
2301
|
env:
|
|
1942
2302
|
EBK_STAGING_ACCOUNT_ID: \${{ vars.AWS_STAGING_PROMOTION_EXPECTED_ACCOUNT_ID }}
|
|
1943
2303
|
EBK_STAGING_REGION: \${{ vars.AWS_STAGING_PROMOTION_REGION }}
|
|
@@ -1956,10 +2316,27 @@ ${stagingReleaseCandidateDownloadSteps(downloadArtifactAction)} - name: Res
|
|
|
1956
2316
|
role-to-assume: \${{ secrets.AWS_DEPLOY_ROLE_ARN }}
|
|
1957
2317
|
role-session-name: ebk-production-container-push-\${{ github.run_id }}-\${{ github.run_attempt }}
|
|
1958
2318
|
aws-region: \${{ vars.AWS_REGION }}
|
|
2319
|
+
- name: Bind production authority to committed deployment topology
|
|
2320
|
+
run: |
|
|
2321
|
+
test "$AWS_EXPECTED_ACCOUNT_ID" = "$EBK_POLICY_PRODUCTION_ACCOUNT_ID" ||
|
|
2322
|
+
{ echo 'Production environment account differs from committed deployment topology'; exit 1; }
|
|
2323
|
+
test "$AWS_REGION" = "$EBK_POLICY_REGION" ||
|
|
2324
|
+
{ echo 'Production environment Region differs from committed deployment topology'; exit 1; }
|
|
2325
|
+
- name: Authenticate shared-account staging source registry
|
|
2326
|
+
if: env.EBK_DEPLOYMENT_PROFILE == 'starter'
|
|
2327
|
+
run: |
|
|
2328
|
+
test "$EBK_POLICY_STAGING_ACCOUNT_ID" = "$EBK_POLICY_PRODUCTION_ACCOUNT_ID" ||
|
|
2329
|
+
{ echo 'Starter promotion requires one shared AWS account'; exit 1; }
|
|
2330
|
+
registry_suffix=.com
|
|
2331
|
+
case "$EBK_POLICY_REGION" in cn-*) registry_suffix=.com.cn ;; esac
|
|
2332
|
+
aws ecr get-login-password --region "$EBK_POLICY_REGION" |
|
|
2333
|
+
docker login --username AWS --password-stdin \
|
|
2334
|
+
"$EBK_POLICY_STAGING_ACCOUNT_ID.dkr.ecr.$EBK_POLICY_REGION.amazonaws$registry_suffix"
|
|
1959
2335
|
- name: Promote exact staging indexes without rebuilding
|
|
1960
2336
|
env:
|
|
1961
|
-
|
|
1962
|
-
|
|
2337
|
+
EBK_DEPLOYMENT_PROFILE: \${{ needs.plan-production.outputs.deployment_profile }}
|
|
2338
|
+
EBK_STAGING_PROMOTION_EXPECTED_ACCOUNT_ID: \${{ needs.plan-production.outputs.deployment_profile == 'starter' && needs.plan-production.outputs.staging_account_id || vars.AWS_STAGING_PROMOTION_EXPECTED_ACCOUNT_ID }}
|
|
2339
|
+
EBK_STAGING_PROMOTION_REGION: \${{ needs.plan-production.outputs.deployment_profile == 'starter' && needs.plan-production.outputs.deployment_region || vars.AWS_STAGING_PROMOTION_REGION }}
|
|
1963
2340
|
run: |
|
|
1964
2341
|
test "$(aws sts get-caller-identity --query Account --output text)" = "$AWS_EXPECTED_ACCOUNT_ID" ||
|
|
1965
2342
|
{ echo 'Production promotion role account differs from policy'; exit 1; }
|
|
@@ -2178,6 +2555,8 @@ ${planRoleAuditStep('main', 'verify')}
|
|
|
2178
2555
|
EBK_RELEASE_SOURCE_COMMIT: \${{ needs.release-source-staging.outputs.source_commit }}
|
|
2179
2556
|
EBK_SOURCE_COMMIT: \${{ github.sha }}
|
|
2180
2557
|
EBK_STAGE: staging
|
|
2558
|
+
EBK_POLICY_ACCOUNT_ID: \${{ needs.plan-staging.outputs.staging_account_id }}
|
|
2559
|
+
EBK_POLICY_REGION: \${{ needs.plan-staging.outputs.deployment_region }}
|
|
2181
2560
|
FOUNDATION_PROJECT: \${{ needs.plan-staging.outputs.foundation_project }}
|
|
2182
2561
|
ORDERED_PROJECTS: \${{ needs.plan-staging.outputs.ordered_projects }}
|
|
2183
2562
|
SLICE_PROJECTS: \${{ needs.plan-staging.outputs.slice_projects }}
|
|
@@ -2285,7 +2664,7 @@ ${planRoleAuditStep('develop', 'verify')}
|
|
|
2285
2664
|
packages/*/.ebk/evidence/infrastructure-plan-command.json
|
|
2286
2665
|
|
|
2287
2666
|
deploy-staging:
|
|
2288
|
-
name:
|
|
2667
|
+
name: ${stagingApplyJobPrefix}\${{ needs.infrastructure-plan-staging.outputs.plan_digest || 'recovery-without-resource-plan' }}
|
|
2289
2668
|
needs: [plan-staging, release-source-staging, container-assemble-staging, infrastructure-plan-staging]
|
|
2290
2669
|
if: |
|
|
2291
2670
|
always() &&
|
|
@@ -2364,6 +2743,10 @@ ${stagingReleaseCandidateDownloadSteps(downloadArtifactAction)} - name: Res
|
|
|
2364
2743
|
test -n "$AWS_DEPLOY_ROLE_ARN" || { echo 'staging AWS_DEPLOY_ROLE_ARN is required'; exit 1; }
|
|
2365
2744
|
test -n "$AWS_EXPECTED_ACCOUNT_ID" || { echo 'staging AWS_EXPECTED_ACCOUNT_ID is required'; exit 1; }
|
|
2366
2745
|
test -n "$AWS_REGION" || { echo 'staging AWS_REGION is required'; exit 1; }
|
|
2746
|
+
test "$AWS_EXPECTED_ACCOUNT_ID" = "$EBK_POLICY_ACCOUNT_ID" ||
|
|
2747
|
+
{ echo 'staging environment account differs from committed deployment topology'; exit 1; }
|
|
2748
|
+
test "$AWS_REGION" = "$EBK_POLICY_REGION" ||
|
|
2749
|
+
{ echo 'staging environment Region differs from committed deployment topology'; exit 1; }
|
|
2367
2750
|
test -n "$EBK_FLEET_ORIGIN" ||
|
|
2368
2751
|
{ echo 'staging environment EBK_FLEET_ORIGIN is required'; exit 1; }
|
|
2369
2752
|
test "$EBK_FLEET_ORIGIN" = "$EBK_PLANNED_FLEET_ORIGIN" ||
|
|
@@ -2375,8 +2758,7 @@ ${stagingReleaseCandidateDownloadSteps(downloadArtifactAction)} - name: Res
|
|
|
2375
2758
|
test -n "$AWS_PLAN_GENERATED_SECRET_ARNS_JSON" &&
|
|
2376
2759
|
test -n "$AWS_PLAN_PROVIDER_REFRESH_POLICY_JSON" ||
|
|
2377
2760
|
{ echo 'staging exact plan-discovery inputs are required'; exit 1; }
|
|
2378
|
-
test -n "$AWS_PLAN_ROLE_ARN"
|
|
2379
|
-
test -n "$AWS_PLAN_STATE_KMS_KEY_ARN" ||
|
|
2761
|
+
test -n "$AWS_PLAN_ROLE_ARN" ||
|
|
2380
2762
|
{ echo 'staging exact plan-authority inputs are required'; exit 1; }
|
|
2381
2763
|
case "$AWS_DEPLOY_ROLE_ARN" in
|
|
2382
2764
|
arn:aws:iam::$AWS_EXPECTED_ACCOUNT_ID:role/*) ;;
|
|
@@ -3000,6 +3382,8 @@ ${stagingReleaseCandidateDownloadSteps(downloadArtifactAction)} - name: Res
|
|
|
3000
3382
|
EBK_APPROVED_MAIN_COMMIT: \${{ github.sha }}
|
|
3001
3383
|
EBK_SOURCE_COMMIT: \${{ github.sha }}
|
|
3002
3384
|
EBK_STAGE: production
|
|
3385
|
+
EBK_POLICY_ACCOUNT_ID: \${{ needs.plan-production.outputs.production_account_id }}
|
|
3386
|
+
EBK_POLICY_REGION: \${{ needs.plan-production.outputs.deployment_region }}
|
|
3003
3387
|
EBK_VERIFIED_STAGING_RUN_ID: \${{ needs.release-source-production.outputs.verified_staging_run_id }}
|
|
3004
3388
|
FOUNDATION_PROJECT: \${{ needs.plan-production.outputs.foundation_project }}
|
|
3005
3389
|
ORDERED_PROJECTS: \${{ needs.plan-production.outputs.ordered_projects }}
|
|
@@ -3096,6 +3480,10 @@ ${stagingReleaseCandidateDownloadSteps(downloadArtifactAction)} - name: Res
|
|
|
3096
3480
|
test -n "$AWS_DEPLOY_ROLE_ARN" || { echo 'production AWS_DEPLOY_ROLE_ARN is required'; exit 1; }
|
|
3097
3481
|
test -n "$AWS_EXPECTED_ACCOUNT_ID" || { echo 'production AWS_EXPECTED_ACCOUNT_ID is required'; exit 1; }
|
|
3098
3482
|
test -n "$AWS_REGION" || { echo 'production AWS_REGION is required'; exit 1; }
|
|
3483
|
+
test "$AWS_EXPECTED_ACCOUNT_ID" = "$EBK_POLICY_ACCOUNT_ID" ||
|
|
3484
|
+
{ echo 'production environment account differs from committed deployment topology'; exit 1; }
|
|
3485
|
+
test "$AWS_REGION" = "$EBK_POLICY_REGION" ||
|
|
3486
|
+
{ echo 'production environment Region differs from committed deployment topology'; exit 1; }
|
|
3099
3487
|
test -n "$EBK_FLEET_ORIGIN" ||
|
|
3100
3488
|
{ echo 'production environment EBK_FLEET_ORIGIN is required'; exit 1; }
|
|
3101
3489
|
test "$EBK_FLEET_ORIGIN" = "$EBK_PLANNED_FLEET_ORIGIN" ||
|
|
@@ -3107,8 +3495,7 @@ ${stagingReleaseCandidateDownloadSteps(downloadArtifactAction)} - name: Res
|
|
|
3107
3495
|
test -n "$AWS_PLAN_GENERATED_SECRET_ARNS_JSON" &&
|
|
3108
3496
|
test -n "$AWS_PLAN_PROVIDER_REFRESH_POLICY_JSON" ||
|
|
3109
3497
|
{ echo 'production exact plan-discovery inputs are required'; exit 1; }
|
|
3110
|
-
test -n "$AWS_PLAN_ROLE_ARN"
|
|
3111
|
-
test -n "$AWS_PLAN_STATE_KMS_KEY_ARN" ||
|
|
3498
|
+
test -n "$AWS_PLAN_ROLE_ARN" ||
|
|
3112
3499
|
{ echo 'production exact plan-authority inputs are required'; exit 1; }
|
|
3113
3500
|
case "$AWS_DEPLOY_ROLE_ARN" in
|
|
3114
3501
|
arn:aws:iam::$AWS_EXPECTED_ACCOUNT_ID:role/*) ;;
|
|
@@ -4762,13 +5149,51 @@ permissions:
|
|
|
4762
5149
|
contents: read
|
|
4763
5150
|
|
|
4764
5151
|
jobs:
|
|
5152
|
+
deployment-classification:
|
|
5153
|
+
name: classify exact staging deployment
|
|
5154
|
+
if: >-
|
|
5155
|
+
github.event_name == 'workflow_run' &&
|
|
5156
|
+
github.event.workflow_run.event == 'push' &&
|
|
5157
|
+
github.event.workflow_run.head_branch == 'develop' &&
|
|
5158
|
+
github.event.workflow_run.conclusion == 'success'
|
|
5159
|
+
permissions:
|
|
5160
|
+
actions: read
|
|
5161
|
+
contents: read
|
|
5162
|
+
outputs:
|
|
5163
|
+
deployed: \${{ steps.classify.outputs.deployed }}
|
|
5164
|
+
runs-on: ubuntu-latest
|
|
5165
|
+
steps:
|
|
5166
|
+
- id: classify
|
|
5167
|
+
name: Require one successful staging deployment job
|
|
5168
|
+
env:
|
|
5169
|
+
EBK_DEPLOYMENT_RUN_ID: \${{ github.event.workflow_run.id }}
|
|
5170
|
+
GH_TOKEN: \${{ github.token }}
|
|
5171
|
+
GH_REPO: \${{ github.repository }}
|
|
5172
|
+
run: |
|
|
5173
|
+
jobs="$(gh api \
|
|
5174
|
+
"/repos/$GH_REPO/actions/runs/$EBK_DEPLOYMENT_RUN_ID/jobs?filter=latest&per_page=100")"
|
|
5175
|
+
apply_count="$(printf '%s' "$jobs" | jq \
|
|
5176
|
+
'[.jobs[] | select(.name | startswith("${stagingApplyJobPrefix}")) | select(.conclusion == "success")] | length')"
|
|
5177
|
+
route_success_count="$(printf '%s' "$jobs" | jq \
|
|
5178
|
+
'[.jobs[] | select(.name == "${protectedDeploymentRouteJobName}" and .conclusion == "success")] | length')"
|
|
5179
|
+
route_skipped_count="$(printf '%s' "$jobs" | jq \
|
|
5180
|
+
'[.jobs[] | select(.name == "${protectedDeploymentRouteJobName}" and .conclusion == "skipped")] | length')"
|
|
5181
|
+
case "$apply_count:$route_success_count:$route_skipped_count" in
|
|
5182
|
+
1:1:0) echo 'deployed=true' >> "$GITHUB_OUTPUT" ;;
|
|
5183
|
+
0:0:1) echo 'deployed=false' >> "$GITHUB_OUTPUT" ;;
|
|
5184
|
+
*) echo 'Deploy route and staging application evidence disagree'; exit 1 ;;
|
|
5185
|
+
esac
|
|
5186
|
+
|
|
4765
5187
|
plan:
|
|
5188
|
+
needs: deployment-classification
|
|
4766
5189
|
if: >-
|
|
4767
|
-
(
|
|
5190
|
+
always() &&
|
|
5191
|
+
((github.event_name == 'workflow_run' &&
|
|
4768
5192
|
github.event.workflow_run.conclusion == 'success' &&
|
|
4769
5193
|
github.event.workflow_run.event == 'push' &&
|
|
4770
|
-
github.event.workflow_run.head_branch == 'develop'
|
|
4771
|
-
|
|
5194
|
+
github.event.workflow_run.head_branch == 'develop' &&
|
|
5195
|
+
needs.deployment-classification.outputs.deployed == 'true') ||
|
|
5196
|
+
github.event_name == 'workflow_dispatch')
|
|
4772
5197
|
outputs:
|
|
4773
5198
|
deployment_run_attempt: \${{ steps.identity.outputs.deployment_run_attempt }}
|
|
4774
5199
|
deployment_run_id: \${{ steps.identity.outputs.deployment_run_id }}
|
|
@@ -4875,9 +5300,10 @@ jobs:
|
|
|
4875
5300
|
EBK_SMOKE_AUTHORITY_VERSION: \${{ vars.EBK_SMOKE_AUTHORITY_VERSION }}
|
|
4876
5301
|
EBK_SMOKE_TARGETS: \${{ vars.EBK_SMOKE_TARGETS }}
|
|
4877
5302
|
EBK_SOURCE_COMMIT: \${{ needs.plan.outputs.source_commit }}
|
|
4878
|
-
PLAYWRIGHT_BROWSERS_PATH: \${{ runner.temp }}/ebk-playwright-browsers
|
|
4879
5303
|
runs-on: ubuntu-latest
|
|
4880
5304
|
steps:
|
|
5305
|
+
- name: Initialize private browser cache path
|
|
5306
|
+
run: echo "PLAYWRIGHT_BROWSERS_PATH=$RUNNER_TEMP/ebk-playwright-browsers" >> "$GITHUB_ENV"
|
|
4881
5307
|
- name: Validate credential-free preparation
|
|
4882
5308
|
run: |
|
|
4883
5309
|
test -n "$AWS_EXPECTED_ACCOUNT_ID" || { echo 'AWS_EXPECTED_ACCOUNT_ID is required'; exit 1; }
|
|
@@ -5261,14 +5687,14 @@ jobs:
|
|
|
5261
5687
|
test "$(node --version | cut -d. -f1)" = v22
|
|
5262
5688
|
- name: Run exact deployed smoke
|
|
5263
5689
|
working-directory: \${{ runner.temp }}/ebk-prepared-workspace
|
|
5264
|
-
run: node node_modules/nx/bin/nx.js run "$EBK_PROJECT:smoke-deployed"
|
|
5690
|
+
run: node node_modules/nx/dist/bin/nx.js run "$EBK_PROJECT:smoke-deployed"
|
|
5265
5691
|
- name: Verify reviewed frontend visual baseline
|
|
5266
5692
|
working-directory: \${{ runner.temp }}/ebk-prepared-workspace
|
|
5267
|
-
run: node node_modules/nx/bin/nx.js run "$EBK_PROJECT:frontend-visual"
|
|
5693
|
+
run: node node_modules/nx/dist/bin/nx.js run "$EBK_PROJECT:frontend-visual"
|
|
5268
5694
|
- name: Verify deployed frontend security headers
|
|
5269
5695
|
working-directory: \${{ runner.temp }}/ebk-prepared-workspace
|
|
5270
5696
|
run: |
|
|
5271
|
-
node node_modules/nx/bin/nx.js run \
|
|
5697
|
+
node node_modules/nx/dist/bin/nx.js run \
|
|
5272
5698
|
"$EBK_PROJECT:frontend-security-headers" -- \
|
|
5273
5699
|
--url="$EBK_SMOKE_URL"
|
|
5274
5700
|
- name: Bind exact alarm and data-integrity evidence
|
|
@@ -6715,11 +7141,11 @@ jobs:
|
|
|
6715
7141
|
if [ "$EBK_ACTION" = apply ]; then args+=("--apply"); fi
|
|
6716
7142
|
pnpm nx run "$EBK_PROJECT:secret-metadata" -- "\${args[@]}"
|
|
6717
7143
|
`,
|
|
6718
|
-
'.github/workflows/sync-back.yml': `name: Sync
|
|
7144
|
+
'.github/workflows/sync-back.yml': `name: Sync protected main history
|
|
6719
7145
|
|
|
6720
7146
|
on:
|
|
6721
7147
|
workflow_run:
|
|
6722
|
-
workflows: [
|
|
7148
|
+
workflows: [Verify]
|
|
6723
7149
|
types: [completed]
|
|
6724
7150
|
|
|
6725
7151
|
permissions:
|
|
@@ -6730,6 +7156,7 @@ jobs:
|
|
|
6730
7156
|
sync-back:
|
|
6731
7157
|
if: >-
|
|
6732
7158
|
github.event.workflow_run.conclusion == 'success' &&
|
|
7159
|
+
github.event.workflow_run.event == 'push' &&
|
|
6733
7160
|
github.event.workflow_run.head_branch == 'main'
|
|
6734
7161
|
runs-on: ubuntu-latest
|
|
6735
7162
|
steps:
|
|
@@ -6743,8 +7170,8 @@ jobs:
|
|
|
6743
7170
|
gh pr create \
|
|
6744
7171
|
--head main \
|
|
6745
7172
|
--base develop \
|
|
6746
|
-
--title 'chore: sync
|
|
6747
|
-
--body 'Managed EBK sync
|
|
7173
|
+
--title 'chore(git): sync protected main to develop' \
|
|
7174
|
+
--body 'Managed EBK source-history sync. Review and merge; this does not claim or waive independent staging or production deployment evidence, and this workflow never pushes or auto-merges.'
|
|
6748
7175
|
number=$(gh pr list --state open --head main --base develop --json number --jq '.[0].number')
|
|
6749
7176
|
fi
|
|
6750
7177
|
echo "Sync-back pull request #$number is ready for review."
|
|
@@ -6989,6 +7416,8 @@ dist
|
|
|
6989
7416
|
.open-next
|
|
6990
7417
|
.sst
|
|
6991
7418
|
**/next-env.d.ts
|
|
7419
|
+
**/api/generated/*.json
|
|
7420
|
+
**/database/migrations/**
|
|
6992
7421
|
pnpm-lock.yaml
|
|
6993
7422
|
tools/blueprint-trust-check.mjs
|
|
6994
7423
|
`,
|
|
@@ -7096,6 +7525,16 @@ use the direct installed \`.bin/ebk blueprint verification-review\` entry docume
|
|
|
7096
7525
|
\`docs/blueprints.md\`.
|
|
7097
7526
|
|
|
7098
7527
|
Workspace creation only writes source files. It does not create or change cloud resources.
|
|
7528
|
+
|
|
7529
|
+
New workspaces default to the one-account \`starter\` deployment profile. Configure the exact
|
|
7530
|
+
shared account and Region in \`ebk.repository-policy.json\`, authenticate through IAM Identity
|
|
7531
|
+
Center, add exact development and staging CIDRs to
|
|
7532
|
+
\`packages/foundation/security/router-waf.json\`, and use
|
|
7533
|
+
\`pnpm nx run ${name}:deploy-local -- --stage=development
|
|
7534
|
+
--profile=<profile> --expectedRoleArn=<role-arn>\`. Never invoke SST directly.
|
|
7535
|
+
For a brand-new stage with generated Product Slices, complete the documented workload-role
|
|
7536
|
+
Foundation publication and pass \`--phase=foundation-bootstrap\` once before publishing and
|
|
7537
|
+
deploying the complete stage.
|
|
7099
7538
|
`,
|
|
7100
7539
|
'docs/architecture.md': `# Workspace architecture
|
|
7101
7540
|
|
|
@@ -7112,6 +7551,11 @@ Foundation implementation files.
|
|
|
7112
7551
|
Canonical consumer stages use the matching \`development\`, \`staging\`, or \`production\`
|
|
7113
7552
|
Foundation. Framework-derived personal stages may use only the \`development\`
|
|
7114
7553
|
Foundation; generic preview stages are unsupported.
|
|
7554
|
+
|
|
7555
|
+
The committed deployment profile changes account and local/GitHub authority only. Every
|
|
7556
|
+
canonical stage always owns a separate Router/domain, Aurora cluster and migration history,
|
|
7557
|
+
Cognito user pool, event bus, secret namespace, IAM boundary, logs, queues, ECR namespace, and
|
|
7558
|
+
stage-qualified SSM discovery contract. Discovery rejects a record for another canonical stage.
|
|
7115
7559
|
`,
|
|
7116
7560
|
'docs/git-workflow.md': `# Protected Git workflow
|
|
7117
7561
|
|
|
@@ -7132,6 +7576,9 @@ GitHub rulesets, environments, and AWS IAM are the authorization boundaries; loc
|
|
|
7132
7576
|
only defense in depth.
|
|
7133
7577
|
|
|
7134
7578
|
The \`staging\` environment accepts only \`develop\`; \`production\` accepts only \`main\`.
|
|
7579
|
+
These workflows exist for every deployment profile. The staging workflow is optional for
|
|
7580
|
+
\`starter\`, whose governed local Nx target may deploy development and staging. Production is
|
|
7581
|
+
GitHub-only for every profile; \`fully-isolated\` also requires GitHub for staging.
|
|
7135
7582
|
The committed \`productionApproval.mode\` selects one of two production authorization
|
|
7136
7583
|
contracts:
|
|
7137
7584
|
|
|
@@ -7154,14 +7601,22 @@ contracts:
|
|
|
7154
7601
|
Both modes retain required checks, a non-author CODEOWNER approval, no ruleset bypass, the
|
|
7155
7602
|
literal production environment, and its branch-scoped credentials. The base \`staging\` and
|
|
7156
7603
|
\`production\` environments own deployment, smoke, and ECR evidence roles plus their
|
|
7157
|
-
variables;
|
|
7604
|
+
variables; isolated profiles also keep the staging-registry promotion pull role in production.
|
|
7605
|
+
Starter promotion authenticates once to the shared account and copies the exact staging digest
|
|
7606
|
+
into a distinct production ECR repository. Dedicated
|
|
7158
7607
|
\`infrastructure-plan-<stage>\` environments gate read-only role use. Separate
|
|
7159
7608
|
\`async-recovery-<stage>\`, \`performance-aggregate-<stage>\`,
|
|
7160
7609
|
\`performance-cost-<stage>\`, \`performance-load-<stage>\`, and
|
|
7161
7610
|
\`reliability-read-<stage>\` environments own only the role and variables required by that
|
|
7162
7611
|
capability. Infrastructure-plan jobs use a repository-variable read-only role/input inventory;
|
|
7163
7612
|
an independent clean audit job assumes that exact environment-subject role and verifies its
|
|
7164
|
-
trust and complete inline policy before any repository-backed plan job may assume it.
|
|
7613
|
+
trust and complete inline policy before any repository-backed plan job may assume it. Plan-role
|
|
7614
|
+
trust uses GitHub's immutable repository identity subject,
|
|
7615
|
+
\`repo:<owner>@<owner-id>/<repository>@<repository-id>:environment:<environment>\`.
|
|
7616
|
+
The audit derives it from the workflow's repository name, owner ID, repository ID, and literal
|
|
7617
|
+
environment and refuses missing IDs or the older mutable name-only subject. Update every
|
|
7618
|
+
adopter-owned AWS OIDC role trust to the immutable identity before protected use; never retain
|
|
7619
|
+
both forms. Every
|
|
7165
7620
|
\`*-staging\` environment accepts only \`develop\`; every
|
|
7166
7621
|
\`*-production\` environment accepts only \`main\`; reviewer, self-review, and wait protection
|
|
7167
7622
|
matches the corresponding base stage. The credential-free \`npm-prerelease\` environment
|
|
@@ -7303,18 +7758,35 @@ materializes, close and reopen the pull request as a human and then approve it.
|
|
|
7303
7758
|
`,
|
|
7304
7759
|
'docs/repository-operating-policy.md': `# Repository Operating Policy
|
|
7305
7760
|
|
|
7306
|
-
\`ebk.repository-policy.json\` is the reviewed source of truth for
|
|
7307
|
-
|
|
7761
|
+
\`ebk.repository-policy.json\` is the reviewed source of truth for deployment profile, exact
|
|
7762
|
+
stage account IDs, deployment Region, production access, production approval, per-Slice
|
|
7763
|
+
readiness, and the one shared production Foundation profile.
|
|
7308
7764
|
New workspaces start with production disabled and explicit Team-compatible
|
|
7309
7765
|
\`productionApproval.mode: "promotion-pr"\`. A missing approval mode or Slice entry fails
|
|
7310
7766
|
closed; no GitHub capability is allowed to choose a mode implicitly.
|
|
7311
7767
|
|
|
7768
|
+
Deployment profiles are proportional to operator maturity:
|
|
7769
|
+
|
|
7770
|
+
- \`starter\` (default) requires one shared account, permits local development and staging
|
|
7771
|
+
through the governed root Nx target, and reserves production for protected GitHub deployment;
|
|
7772
|
+
- \`production-isolated\` requires one development/staging account and a distinct production
|
|
7773
|
+
account; only production is GitHub-only; and
|
|
7774
|
+
- \`fully-isolated\` requires three distinct accounts; staging and production are GitHub-only.
|
|
7775
|
+
|
|
7776
|
+
Equal account IDs never imply shared stage resources. Router, Aurora, Cognito, events, secrets,
|
|
7777
|
+
IAM, logs, queues, resource names, and SSM discovery remain stage-specific. Existing prerelease
|
|
7778
|
+
repositories migrate explicitly to \`fully-isolated\`; upgrades never infer or weaken topology.
|
|
7779
|
+
That profile-only migration retains the former authority model but stays undeployable until exact
|
|
7780
|
+
development, staging, and production account IDs plus \`deploymentRegion\` are committed.
|
|
7781
|
+
|
|
7312
7782
|
The named repository modes are source-writing presets, not runtime state. Report a change
|
|
7313
7783
|
first and inspect the complete current/next policy:
|
|
7314
7784
|
|
|
7315
7785
|
\`\`\`bash
|
|
7316
7786
|
pnpm nx g @empire-builder-kit/nx:repository-policy \\
|
|
7317
|
-
--
|
|
7787
|
+
--deploymentProfile=starter --deploymentRegion=us-east-1 \\
|
|
7788
|
+
--developmentAccountId=111122223333 --stagingAccountId=111122223333 \\
|
|
7789
|
+
--productionAccountId=111122223333
|
|
7318
7790
|
\`\`\`
|
|
7319
7791
|
|
|
7320
7792
|
Apply the reviewed source change separately with \`--apply\`. This command never contacts AWS
|
|
@@ -7324,6 +7796,66 @@ Use \`--productionApprovalMode=promotion-pr|environment-reviewer\` for an explic
|
|
|
7324
7796
|
transition. This changes committed policy only; a separate bootstrap report/apply reconciles
|
|
7325
7797
|
remote GitHub settings.
|
|
7326
7798
|
|
|
7799
|
+
After applying exact topology, authenticate with temporary federated credentials for development
|
|
7800
|
+
or staging and invoke
|
|
7801
|
+
\`pnpm nx run ${name}:deploy-local -- --stage=<stage> --profile=<profile>
|
|
7802
|
+
--expectedRoleArn=<role-arn>\`. Production runs only through the protected main-branch GitHub
|
|
7803
|
+
workflow. Each admitted local invocation plans the complete current stage, displays the stage
|
|
7804
|
+
summary and first material project, and requires a real TTY response of
|
|
7805
|
+
\`approve:<action>:<stage>:<project>:<current-plan-digest>\`. A human or authorized agent may type
|
|
7806
|
+
that acknowledgement; it is active operator control, not a claim of independent human review.
|
|
7807
|
+
The target creates one private ten-minute capability for that project, rechecks the exact source,
|
|
7808
|
+
artifacts, account, Region, role, stage, policy, state, and lock without a second provider preview,
|
|
7809
|
+
consumes the capability once, and removes it on every exit path. Unattended scripts cannot approve.
|
|
7810
|
+
Every material deploy or refresh mutates at most the first material project and ends with a
|
|
7811
|
+
controlled non-success \`replan-required\` handoff, including the last project in the ordered stage.
|
|
7812
|
+
Rerun the complete stage, review the new current summary, and repeat until one invocation proves
|
|
7813
|
+
every project is semantically converged. Only that convergence invocation succeeds. A preview is
|
|
7814
|
+
recent review and audit evidence; it is not represented as a cryptographically replayable Pulumi
|
|
7815
|
+
transaction. A complete, unlocked deploy plan containing only provider reads with no changed
|
|
7816
|
+
properties, replacement, failure, or unmatched risk finding is retained as observation evidence
|
|
7817
|
+
and counts as semantically converged. The standalone project planner may still return its
|
|
7818
|
+
conservative non-success for that drift observation; the root local transaction accepts only a
|
|
7819
|
+
freshly emitted exact provider-read-only record from that child. Repeated reads coalesce
|
|
7820
|
+
only when their logical ID and resource type match exactly. Any stale or ambiguous
|
|
7821
|
+
observation fails closed; a plan with desired changes remains material and requires review. One
|
|
7822
|
+
narrower case stays reviewable instead of becoming
|
|
7823
|
+
drift: when an upstream Foundation apply changes only both external framework-version tag
|
|
7824
|
+
projections (plus SST's already-bounded name hydration), the retained
|
|
7825
|
+
external identity must match one exact non-replacing provider read in the same ordinary plan.
|
|
7826
|
+
Missing read correlation, owned resources, other tag or property changes, incomplete tag pairs,
|
|
7827
|
+
and provider failures remain drift and fail closed. The local target requires the exact SST v5 account bootstrap normally
|
|
7828
|
+
created by the pinned \`ebk dev up\` path, then creates and attests only missing exact
|
|
7829
|
+
per-project/canonical-stage state passphrases before its formal read-only plans. This passphrase
|
|
7830
|
+
bootstrap is the only persistent pre-acknowledgement write; it creates no deployment authority
|
|
7831
|
+
and is reported in bounded local evidence. Clean immutable source, immediate per-project plans,
|
|
7832
|
+
migration and retention preflights, and secret-free result
|
|
7833
|
+
evidence remain mandatory. Direct SST is forbidden.
|
|
7834
|
+
|
|
7835
|
+
After an interrupted apply, use the same target with \`--action=refresh\` and the exact
|
|
7836
|
+
phase, stage, profile, and role. It retains the same plan-summary review boundary, admits only
|
|
7837
|
+
bounded drift-detected refresh evidence, and never turns refresh into an implicit deploy. When
|
|
7838
|
+
the interrupted write leaves a recorded provider output unusable by the ordinary desired preview,
|
|
7839
|
+
the plan stops without mutation. The non-writing refresh preview separates provider refresh
|
|
7840
|
+
records from its later desired preview and cannot replace failed ordinary desired authority;
|
|
7841
|
+
repair the recorded dependency or use the documented
|
|
7842
|
+
operator recovery path before planning again.
|
|
7843
|
+
|
|
7844
|
+
A stage-wide refresh still plans and authorizes every selected project. When one exact retained
|
|
7845
|
+
project plan proves an absent SST state that is unlocked, complete, empty, and drift-free, the
|
|
7846
|
+
root target records that project as a refresh no-op instead of invoking SST against a nonexistent
|
|
7847
|
+
stage. It also retains the pinned provider's exact SSM \`ebk:framework-version\` \`tagsAll\`
|
|
7848
|
+
normalization as a semantically inert observation after the same bounded shape
|
|
7849
|
+
repeats following refresh. Value changes, other tags, security findings, deletes, replacements,
|
|
7850
|
+
sensitive properties, and every other resource type remain material. Locked, incomplete, failed,
|
|
7851
|
+
or otherwise drifted plans are never skipped.
|
|
7852
|
+
|
|
7853
|
+
For a stage with no workload-role account baseline, publish the fixed Foundation-only candidate
|
|
7854
|
+
and invoke this root target with \`--phase=foundation-bootstrap\`. After Foundation outputs exist,
|
|
7855
|
+
publish the complete \`full-stage\` candidate and invoke the target without \`--phase\` (or with
|
|
7856
|
+
\`--phase=full-stage\`). The bootstrap phase selects exactly Foundation; it is not an arbitrary
|
|
7857
|
+
project filter.
|
|
7858
|
+
|
|
7327
7859
|
Validation requires both an explicit production approval mode and an explicit Foundation
|
|
7328
7860
|
profile. It does not infer omitted values. Use \`promotion-pr\` for a private GitHub Team
|
|
7329
7861
|
repository. Use \`environment-reviewer\` only when the repository plan can enforce a separate
|
|
@@ -7337,10 +7869,16 @@ environment reviewer.
|
|
|
7337
7869
|
|
|
7338
7870
|
Protected production deploy and refresh validate the complete Foundation/Slice set before AWS
|
|
7339
7871
|
access. The Foundation is always ordered first. The set is derived from committed policy and
|
|
7340
|
-
cannot be narrowed with an ad hoc project argument.
|
|
7341
|
-
|
|
7342
|
-
|
|
7343
|
-
|
|
7872
|
+
cannot be narrowed with an ad hoc project argument. The retained stage preview remains review
|
|
7873
|
+
evidence. Immediately before each project mutation the workflow computes one fresh plan against
|
|
7874
|
+
current state and applies it without a second redundant preview. Source, artifacts, configuration,
|
|
7875
|
+
policy, environment, and protected identities remain exact. Provider-only read/discard observations
|
|
7876
|
+
may appear or disappear, but any new delete, replacement, import, stateful or security change, IAM
|
|
7877
|
+
expansion, protection reduction, or unclassified resource stops for renewed approval. After an
|
|
7878
|
+
upstream mutation, every downstream project therefore receives a fresh independent risk decision.
|
|
7879
|
+
|
|
7880
|
+
\`standard\` keeps Aurora warm at 2–16 ACU. \`cost-optimized\` keeps a smaller
|
|
7881
|
+
0.5–4 ACU warm floor for starter production traffic. Both keep managed NAT,
|
|
7344
7882
|
30-day backups, deletion protection, retained data, and the protected GitHub/OIDC/AWS path.
|
|
7345
7883
|
Changing policy affects only later lifecycle requests; it never deploys or removes resources.
|
|
7346
7884
|
Changing profile requires a separate reviewed Foundation deployment and retained operational
|
|
@@ -7411,7 +7949,7 @@ deploy or refresh before AWS access. Protected remove and unlock remain usable f
|
|
|
7411
7949
|
There is no local or silent bypass in this prerelease contract; any future waiver mechanism
|
|
7412
7950
|
must be committed, reviewable, and auditable.
|
|
7413
7951
|
`,
|
|
7414
|
-
'ebk.repository-policy.json': json(
|
|
7952
|
+
'ebk.repository-policy.json': json(initialRepositoryPolicy),
|
|
7415
7953
|
'nx.json': json({
|
|
7416
7954
|
$schema: './node_modules/nx/schemas/nx-schema.json',
|
|
7417
7955
|
defaultBase: 'develop',
|
|
@@ -7451,6 +7989,7 @@ must be committed, reviewable, and auditable.
|
|
|
7451
7989
|
inputs: ['default', '^production'],
|
|
7452
7990
|
},
|
|
7453
7991
|
},
|
|
7992
|
+
analytics: false,
|
|
7454
7993
|
neverConnectToCloud: true,
|
|
7455
7994
|
useDaemonProcess: false,
|
|
7456
7995
|
}),
|
|
@@ -7460,29 +7999,31 @@ must be committed, reviewable, and auditable.
|
|
|
7460
7999
|
private: true,
|
|
7461
8000
|
packageManager: tool_versions_js_1.WORKSPACE_PNPM_DESCRIPTOR,
|
|
7462
8001
|
engines: {
|
|
7463
|
-
node: '>=22.
|
|
7464
|
-
pnpm: '>=
|
|
8002
|
+
node: '>=22.13.0',
|
|
8003
|
+
pnpm: '>=11.20.0',
|
|
7465
8004
|
},
|
|
7466
8005
|
scripts: {
|
|
7467
8006
|
build: `nx run-many -t build --exclude=${name}`,
|
|
7468
8007
|
format: 'nx format:write',
|
|
7469
|
-
'format:check': 'nx format:check',
|
|
8008
|
+
'format:check': 'nx format:check --all',
|
|
7470
8009
|
graph: 'nx graph',
|
|
7471
8010
|
health: `nx run ${name}:workspace-health`,
|
|
7472
8011
|
lint: `nx run-many -t lint --exclude=${name}`,
|
|
7473
8012
|
test: `nx run-many -t test --exclude=${name}`,
|
|
7474
8013
|
typecheck: `nx run-many -t typecheck --exclude=${name}`,
|
|
7475
|
-
verify: `nx run ${name}:verify && nx run-many -t verify --exclude=${name}`,
|
|
8014
|
+
verify: `nx format:check --all && nx run ${name}:verify && nx run-many -t verify --exclude=${name}`,
|
|
7476
8015
|
},
|
|
7477
8016
|
dependencies: {
|
|
7478
8017
|
'@empire-builder-kit/runtime': frameworkVersion,
|
|
7479
8018
|
},
|
|
7480
8019
|
devDependencies: {
|
|
7481
8020
|
'@aws-sdk/client-iam': TOOL_VERSIONS.awsSdk,
|
|
8021
|
+
'@aws-sdk/client-rds': TOOL_VERSIONS.awsSdk,
|
|
7482
8022
|
'@aws-sdk/client-s3': TOOL_VERSIONS.awsSdk,
|
|
7483
8023
|
'@aws-sdk/client-secrets-manager': TOOL_VERSIONS.awsSdk,
|
|
7484
8024
|
'@aws-sdk/client-ssm': TOOL_VERSIONS.awsSdk,
|
|
7485
8025
|
'@aws-sdk/client-sts': TOOL_VERSIONS.awsSdk,
|
|
8026
|
+
'@aws-sdk/credential-providers': TOOL_VERSIONS.awsSdk,
|
|
7486
8027
|
'@eslint/js': TOOL_VERSIONS.eslintJs,
|
|
7487
8028
|
'@empire-builder-kit/nx': frameworkVersion,
|
|
7488
8029
|
'@types/node': TOOL_VERSIONS.nodeTypes,
|
|
@@ -7515,6 +8056,15 @@ must be committed, reviewable, and auditable.
|
|
|
7515
8056
|
protectedRef: 'refs/remotes/origin/develop',
|
|
7516
8057
|
},
|
|
7517
8058
|
},
|
|
8059
|
+
'blueprint-trust-check-main': {
|
|
8060
|
+
executor: '@empire-builder-kit/nx:blueprint-trust',
|
|
8061
|
+
options: {
|
|
8062
|
+
operation: 'adopt',
|
|
8063
|
+
output: '.ebk/evidence/supply-chain/blueprint-trust.json',
|
|
8064
|
+
protectedBranch: 'main',
|
|
8065
|
+
protectedRef: 'refs/remotes/origin/main',
|
|
8066
|
+
},
|
|
8067
|
+
},
|
|
7518
8068
|
'blueprint-trust-check-protected': {
|
|
7519
8069
|
executor: '@empire-builder-kit/nx:blueprint-trust',
|
|
7520
8070
|
options: {
|
|
@@ -7557,6 +8107,13 @@ must be committed, reviewable, and auditable.
|
|
|
7557
8107
|
executor: 'nx:run-commands',
|
|
7558
8108
|
options: { command: 'node tools/install-ebk-hooks.mjs' },
|
|
7559
8109
|
},
|
|
8110
|
+
'deploy-local': {
|
|
8111
|
+
cache: false,
|
|
8112
|
+
executor: '@empire-builder-kit/nx:local-deploy',
|
|
8113
|
+
options: {
|
|
8114
|
+
stage: 'development',
|
|
8115
|
+
},
|
|
8116
|
+
},
|
|
7560
8117
|
'supply-chain-check': {
|
|
7561
8118
|
executor: 'nx:run-commands',
|
|
7562
8119
|
options: {
|
|
@@ -7598,6 +8155,11 @@ must be committed, reviewable, and auditable.
|
|
|
7598
8155
|
executor: 'nx:run-commands',
|
|
7599
8156
|
options: { command: 'node tools/workspace-health.mjs' },
|
|
7600
8157
|
},
|
|
8158
|
+
'verify-proposed': {
|
|
8159
|
+
dependsOn: ['supply-chain-check'],
|
|
8160
|
+
executor: 'nx:run-commands',
|
|
8161
|
+
options: { command: 'node tools/workspace-health.mjs' },
|
|
8162
|
+
},
|
|
7601
8163
|
'workspace-health': {
|
|
7602
8164
|
executor: 'nx:run-commands',
|
|
7603
8165
|
options: { command: 'node tools/workspace-health.mjs' },
|
|
@@ -7620,6 +8182,10 @@ Workspace creation does not deploy this project. Review \`docs/costs.md\`,
|
|
|
7620
8182
|
|
|
7621
8183
|
Set \`EBK_ROOT_DOMAIN\` before deploying when Route 53 in the target account owns a public
|
|
7622
8184
|
domain. Without it, the shared Router uses its functional provider URL.
|
|
8185
|
+
|
|
8186
|
+
Before deploying development or staging, add at least one exact operator/probe CIDR to
|
|
8187
|
+
\`security/router-waf.json\`. Production is public by default behind the same managed and
|
|
8188
|
+
rate-limiting baseline; select its allowlist mode explicitly when required.
|
|
7623
8189
|
`,
|
|
7624
8190
|
'packages/foundation/contracts/foundation.json': json({
|
|
7625
8191
|
contractVersion: '1.0.0',
|
|
@@ -7679,8 +8245,8 @@ seeded decision record aligned with organization-specific budgets and alerts.
|
|
|
7679
8245
|
- Development uses EC2 NAT, a development bastion, and Aurora scale-to-zero.
|
|
7680
8246
|
- Staging uses EC2 NAT and Aurora scale-to-zero by default.
|
|
7681
8247
|
- Production always uses managed NAT and retains protected data. Its explicit Repository
|
|
7682
|
-
Operating Policy profile selects either the standard
|
|
7683
|
-
|
|
8248
|
+
Operating Policy profile selects either the standard 2-ACU Aurora floor or the
|
|
8249
|
+
cost-optimized 0.5-ACU warm floor.
|
|
7684
8250
|
- ECS, EventBridge, Cognito, SNS, SSM, and CloudWatch are primarily usage-priced; logs,
|
|
7685
8251
|
metrics, alarms, and retained data still require active cost review.
|
|
7686
8252
|
|
|
@@ -7698,8 +8264,69 @@ Use Nx for every supported lifecycle action:
|
|
|
7698
8264
|
- \`pnpm nx run foundation:unlock --stage=development\`
|
|
7699
8265
|
- \`pnpm nx run foundation:remove --stage=development\`
|
|
7700
8266
|
|
|
7701
|
-
Do not invoke SST lifecycle commands directly.
|
|
7702
|
-
|
|
8267
|
+
Do not invoke SST lifecycle commands directly. The root \`deploy-local\` target is the governed
|
|
8268
|
+
local path for stages admitted by the committed deployment profile. Its fixed
|
|
8269
|
+
\`foundation-bootstrap\` phase is the only local partial-stage path and exists solely for the
|
|
8270
|
+
documented first workload-role publication; isolated protected stages remain on the trusted
|
|
8271
|
+
GitHub path.
|
|
8272
|
+
|
|
8273
|
+
Every local invocation plans the complete selected stage but mutates at most one material
|
|
8274
|
+
project. Type the exact current \`approve:<action>:<stage>:<project>:<plan-digest>\` challenge in a
|
|
8275
|
+
real TTY after reviewing the displayed summary. A human or authorized agent may operate that TTY;
|
|
8276
|
+
unattended input is refused. The acknowledgement creates one ten-minute, one-use, private
|
|
8277
|
+
capability bound to source, artifacts, policy, environment, credentials, and current state. A
|
|
8278
|
+
completed deploy or refresh returns the controlled non-success \`replan-required\`; rerun the
|
|
8279
|
+
complete stage, review the new current summary, and continue one project at a time. Only a
|
|
8280
|
+
semantically converged complete stage reports success. No upstream mutation can authorize a
|
|
8281
|
+
downstream project. A complete, unlocked deploy plan containing only provider reads with no
|
|
8282
|
+
changed properties, replacement, failure, or unmatched risk finding is retained as observation
|
|
8283
|
+
evidence and counts as semantically converged. The standalone project planner may still return
|
|
8284
|
+
its conservative non-success for that drift observation; the root local transaction accepts only
|
|
8285
|
+
a freshly emitted exact provider-read-only record from that child. Repeated reads coalesce
|
|
8286
|
+
only when their logical ID and resource type match exactly. Any stale or ambiguous
|
|
8287
|
+
observation fails closed; a plan with desired changes remains material and requires review. A
|
|
8288
|
+
material downstream plan may also retain an upstream Foundation framework-version tag advance as
|
|
8289
|
+
observation only when both external tag
|
|
8290
|
+
projections, the retained identity, optional bounded SST name hydration, and one non-replacing
|
|
8291
|
+
provider read correlate exactly. Every other external property or uncorroborated read remains
|
|
8292
|
+
drift and blocks before the TTY challenge.
|
|
8293
|
+
|
|
8294
|
+
For an interrupted local apply, run the same root target with \`--action=refresh\`, review its
|
|
8295
|
+
current provider-refresh evidence, and type the displayed TTY challenge. Before mutation the
|
|
8296
|
+
executor writes and verifies a private state recovery point plus a secret-free digest/version
|
|
8297
|
+
manifest. The refresh preview validates its provider and later desired-event partitions; any
|
|
8298
|
+
ordinary preview failure or malformed post-refresh desired event remains blocked. Provider-only
|
|
8299
|
+
observation noise is retained without claiming a replayable preview. Because refresh changes
|
|
8300
|
+
recorded state rather than cloud resources, provider-output security and protection findings
|
|
8301
|
+
remain explicit review requirements under the drift decision. Refresh never continues into deploy
|
|
8302
|
+
or claims desired-state convergence: run the governed deploy target afterward and resolve every
|
|
8303
|
+
remaining source-versus-state change. Exclude the \`.private.json\` recovery point from archives.
|
|
8304
|
+
|
|
8305
|
+
The root refresh target includes every selected project in its exact reviewed plan set. It skips
|
|
8306
|
+
the SST child only when that project's retained plan proves an absent, unlocked, complete, empty,
|
|
8307
|
+
and drift-free state, or when the only retained difference is the pinned provider's exact repeated
|
|
8308
|
+
SSM \`ebk:framework-version\` \`tagsAll\` normalization. It records the project as an explicit refresh no-op
|
|
8309
|
+
in either case while retaining the observation. Value changes, other tags, security findings,
|
|
8310
|
+
deletes, replacements, sensitive
|
|
8311
|
+
properties, and every other resource type remain material; every other plan must execute or fail
|
|
8312
|
+
closed.
|
|
8313
|
+
|
|
8314
|
+
## Router WAF
|
|
8315
|
+
|
|
8316
|
+
Each canonical stage owns one global-scope Web ACL attached to that stage's Foundation Router.
|
|
8317
|
+
The committed \`security/router-waf.json\` policy requires AWS Common Rule Set, Known Bad
|
|
8318
|
+
Inputs, Amazon IP Reputation, an IP rate limit, CloudWatch metrics, and sampled requests.
|
|
8319
|
+
Development and staging fail synthesis until their allowlists contain explicit CIDRs.
|
|
8320
|
+
Production is public by default and may be changed to an explicit allowlist.
|
|
8321
|
+
|
|
8322
|
+
For tuning, set a nonproduction stage to \`count\`, deploy and inspect sampled requests, then
|
|
8323
|
+
return staging to \`block\` and run the live allow/deny, managed-rule, rate-limit, metric, and
|
|
8324
|
+
sampled-request probes. Write their bounded, secret-free result to
|
|
8325
|
+
\`security/router-waf-live-evidence.json\`, then run
|
|
8326
|
+
\`pnpm nx run foundation:router-waf-rehearsal\`. The executor seals the exact policy digest
|
|
8327
|
+
into \`router-waf-blocking-rehearsal.json\`. Production synthesis rejects a missing, stale, or
|
|
8328
|
+
count-mode rehearsal. Full request logging is opt-in, blocked-only, retention-bounded, and
|
|
8329
|
+
redacts query strings plus credential-bearing headers.
|
|
7703
8330
|
|
|
7704
8331
|
Foundation stages are canonical. \`ebk dev\` derives a Product Slice personal
|
|
7705
8332
|
stage from the operating-system account and maps that consumer to the
|
|
@@ -7734,6 +8361,12 @@ connects as the Slice's no-password IAM-authenticated migrator user.
|
|
|
7734
8361
|
stage-scoped SSM/secret path and least-privilege IAM.
|
|
7735
8362
|
- Public domains are configured explicitly through \`EBK_ROOT_DOMAIN\`; generated source never
|
|
7736
8363
|
contains credentials or customer domain assumptions.
|
|
8364
|
+
- Every stage's CloudFront Router has its own global Web ACL. The baseline always includes AWS
|
|
8365
|
+
Common Rule Set, Known Bad Inputs, Amazon IP Reputation, rate blocking, CloudWatch metrics,
|
|
8366
|
+
and sampled requests. Nonproduction is allowlisted by default. Production is public by
|
|
8367
|
+
default, but cannot deploy until the exact policy has passed a staging blocking rehearsal.
|
|
8368
|
+
- Full WAF request logging is disabled by default. Enabling it keeps blocked requests only,
|
|
8369
|
+
uses bounded retention, and redacts query strings, cookies, authorization, and API keys.
|
|
7737
8370
|
|
|
7738
8371
|
The managed \`contracts/frontend-security-headers.json\` file owns the transport
|
|
7739
8372
|
minimum: HTTPS in deployed environments, one-year HSTS there, and the common
|
|
@@ -7766,7 +8399,12 @@ declare const $app: {
|
|
|
7766
8399
|
import {
|
|
7767
8400
|
FOUNDATION_SSM_SCHEMA_VERSION,
|
|
7768
8401
|
STANDARD_FOUNDATION_CAPABILITIES,
|
|
8402
|
+
createRouterWafRules,
|
|
8403
|
+
createRouterWafStagePlan,
|
|
8404
|
+
foundationDatabaseEndpointHost,
|
|
7769
8405
|
foundationSsmPath,
|
|
8406
|
+
normalizeRouterWafPolicy,
|
|
8407
|
+
reconcileRouterWafBlockingRehearsal,
|
|
7770
8408
|
type FoundationSsmKey,
|
|
7771
8409
|
} from '@empire-builder-kit/nx/foundation';
|
|
7772
8410
|
import {
|
|
@@ -7776,6 +8414,9 @@ import {
|
|
|
7776
8414
|
} from '@empire-builder-kit/nx/repository-policy';
|
|
7777
8415
|
import { createFoundationAccountCostControlPlan } from '@empire-builder-kit/nx/performance';
|
|
7778
8416
|
import accountCostPolicy from '../performance/account-cost-policy.json';
|
|
8417
|
+
import routerWafBlockingRehearsal from '../security/router-waf-blocking-rehearsal.json';
|
|
8418
|
+
import routerWafLiveEvidence from '../security/router-waf-live-evidence.json';
|
|
8419
|
+
import routerWafPolicy from '../security/router-waf.json';
|
|
7779
8420
|
import type { FoundationWorkloadRolePreflight } from './workload-roles/preflight.js';
|
|
7780
8421
|
|
|
7781
8422
|
function identityDomainPrefix(stage: string, accountId: string): string {
|
|
@@ -7827,6 +8468,18 @@ export function foundation(
|
|
|
7827
8468
|
)
|
|
7828
8469
|
: undefined;
|
|
7829
8470
|
const natMode = productionConfiguration?.contract.nat ?? 'ec2';
|
|
8471
|
+
const normalizedRouterWafPolicy = normalizeRouterWafPolicy(routerWafPolicy);
|
|
8472
|
+
const wafPlan = createRouterWafStagePlan({
|
|
8473
|
+
policy: normalizedRouterWafPolicy,
|
|
8474
|
+
rehearsal: isProduction
|
|
8475
|
+
? reconcileRouterWafBlockingRehearsal({
|
|
8476
|
+
evidence: routerWafLiveEvidence,
|
|
8477
|
+
policy: normalizedRouterWafPolicy,
|
|
8478
|
+
receipt: routerWafBlockingRehearsal,
|
|
8479
|
+
})
|
|
8480
|
+
: null,
|
|
8481
|
+
stage: $app.stage,
|
|
8482
|
+
});
|
|
7830
8483
|
const natBinding = workloadRoles?.bindings.nat;
|
|
7831
8484
|
if (natMode === 'ec2' && natBinding) {
|
|
7832
8485
|
$transform(
|
|
@@ -7836,6 +8489,9 @@ export function foundation(
|
|
|
7836
8489
|
args.name = natBinding.instanceProfileName;
|
|
7837
8490
|
args.role = natBinding.roleName;
|
|
7838
8491
|
opts.import = natBinding.instanceProfileName;
|
|
8492
|
+
opts.ignoreChanges = [
|
|
8493
|
+
...new Set([...(opts.ignoreChanges ?? []), 'tags', 'tagsAll']),
|
|
8494
|
+
];
|
|
7839
8495
|
},
|
|
7840
8496
|
);
|
|
7841
8497
|
}
|
|
@@ -7857,8 +8513,83 @@ export function foundation(
|
|
|
7857
8513
|
aliases: ['*.' + rootDomain],
|
|
7858
8514
|
dns: sst.aws.dns(),
|
|
7859
8515
|
},
|
|
7860
|
-
|
|
8516
|
+
}
|
|
7861
8517
|
: {}),
|
|
8518
|
+
waf: {
|
|
8519
|
+
logging: wafPlan.logging,
|
|
8520
|
+
managedRules: {
|
|
8521
|
+
coreRuleSet: true,
|
|
8522
|
+
knownBadInputs: true,
|
|
8523
|
+
sqlInjection: false,
|
|
8524
|
+
},
|
|
8525
|
+
rateLimitPerIp: wafPlan.rateLimitPerIp,
|
|
8526
|
+
},
|
|
8527
|
+
transform: {
|
|
8528
|
+
cdn(args: any) {
|
|
8529
|
+
if (!args.webAclArn) {
|
|
8530
|
+
throw new Error(
|
|
8531
|
+
'Foundation Router must attach its exact global Web ACL.',
|
|
8532
|
+
);
|
|
8533
|
+
}
|
|
8534
|
+
},
|
|
8535
|
+
waf(args: any, opts: any) {
|
|
8536
|
+
if (args.scope !== 'CLOUDFRONT') {
|
|
8537
|
+
throw new Error(
|
|
8538
|
+
'Foundation Router Web ACL must use global CloudFront scope.',
|
|
8539
|
+
);
|
|
8540
|
+
}
|
|
8541
|
+
const ipSets: { ipv4Arn?: any; ipv6Arn?: any } = {};
|
|
8542
|
+
if (wafPlan.ipv4Cidrs.length > 0) {
|
|
8543
|
+
ipSets.ipv4Arn = new aws.wafv2.IpSet(
|
|
8544
|
+
'FoundationRouterIpv4Allowlist',
|
|
8545
|
+
{
|
|
8546
|
+
addresses: wafPlan.ipv4Cidrs,
|
|
8547
|
+
ipAddressVersion: 'IPV4',
|
|
8548
|
+
name: 'foundation-' + wafPlan.stage + '-router-ipv4-allowlist',
|
|
8549
|
+
scope: 'CLOUDFRONT',
|
|
8550
|
+
tags: {
|
|
8551
|
+
'ebk:policy-digest': wafPlan.policyDigest,
|
|
8552
|
+
'ebk:stage': wafPlan.stage,
|
|
8553
|
+
},
|
|
8554
|
+
},
|
|
8555
|
+
{ provider: opts.provider },
|
|
8556
|
+
).arn;
|
|
8557
|
+
}
|
|
8558
|
+
if (wafPlan.ipv6Cidrs.length > 0) {
|
|
8559
|
+
ipSets.ipv6Arn = new aws.wafv2.IpSet(
|
|
8560
|
+
'FoundationRouterIpv6Allowlist',
|
|
8561
|
+
{
|
|
8562
|
+
addresses: wafPlan.ipv6Cidrs,
|
|
8563
|
+
ipAddressVersion: 'IPV6',
|
|
8564
|
+
name: 'foundation-' + wafPlan.stage + '-router-ipv6-allowlist',
|
|
8565
|
+
scope: 'CLOUDFRONT',
|
|
8566
|
+
tags: {
|
|
8567
|
+
'ebk:policy-digest': wafPlan.policyDigest,
|
|
8568
|
+
'ebk:stage': wafPlan.stage,
|
|
8569
|
+
},
|
|
8570
|
+
},
|
|
8571
|
+
{ provider: opts.provider },
|
|
8572
|
+
).arn;
|
|
8573
|
+
}
|
|
8574
|
+
args.defaultAction = { allow: {} };
|
|
8575
|
+
args.rules = createRouterWafRules(wafPlan, ipSets);
|
|
8576
|
+
args.scope = 'CLOUDFRONT';
|
|
8577
|
+
args.tags = {
|
|
8578
|
+
...(args.tags ?? {}),
|
|
8579
|
+
'ebk:policy-digest': wafPlan.policyDigest,
|
|
8580
|
+
'ebk:stage': wafPlan.stage,
|
|
8581
|
+
};
|
|
8582
|
+
args.visibilityConfig = {
|
|
8583
|
+
cloudwatchMetricsEnabled: true,
|
|
8584
|
+
metricName:
|
|
8585
|
+
'Ebk' +
|
|
8586
|
+
wafPlan.stage[0].toUpperCase() +
|
|
8587
|
+
wafPlan.stage.slice(1) +
|
|
8588
|
+
'FoundationRouterWaf',
|
|
8589
|
+
sampledRequestsEnabled: true,
|
|
8590
|
+
};
|
|
8591
|
+
},
|
|
8592
|
+
},
|
|
7862
8593
|
});
|
|
7863
8594
|
const database = new sst.aws.Aurora('FoundationDatabase', {
|
|
7864
8595
|
// SQL bootstrap and migrations run inside the VPC. Keep the public HTTPS
|
|
@@ -7882,6 +8613,46 @@ export function foundation(
|
|
|
7882
8613
|
},
|
|
7883
8614
|
vpc,
|
|
7884
8615
|
});
|
|
8616
|
+
const databaseClusterEndpoint = database.nodes.cluster.endpoint.apply(
|
|
8617
|
+
foundationDatabaseEndpointHost,
|
|
8618
|
+
);
|
|
8619
|
+
const databaseReaderEndpoint = database.nodes.cluster.readerEndpoint.apply(
|
|
8620
|
+
foundationDatabaseEndpointHost,
|
|
8621
|
+
);
|
|
8622
|
+
// SST v4.17.1 creates a top-level LinkRef on the next tick and its Aurora
|
|
8623
|
+
// implementation unconditionally splits cluster endpoints. During a
|
|
8624
|
+
// governed refresh after an interrupted first deploy, those provider
|
|
8625
|
+
// outputs are legitimately absent. Preserve the normal link contract while
|
|
8626
|
+
// keeping that read-only recovery program total; a real deploy resolves the
|
|
8627
|
+
// exact endpoints before any discovery resource is created.
|
|
8628
|
+
database.getSSTLink = () => ({
|
|
8629
|
+
include: [
|
|
8630
|
+
sst.aws.permission({
|
|
8631
|
+
actions: ['secretsmanager:GetSecretValue'],
|
|
8632
|
+
resources: [database.secretArn],
|
|
8633
|
+
}),
|
|
8634
|
+
sst.aws.permission({
|
|
8635
|
+
actions: [
|
|
8636
|
+
'rds-data:BatchExecuteStatement',
|
|
8637
|
+
'rds-data:BeginTransaction',
|
|
8638
|
+
'rds-data:CommitTransaction',
|
|
8639
|
+
'rds-data:ExecuteStatement',
|
|
8640
|
+
'rds-data:RollbackTransaction',
|
|
8641
|
+
],
|
|
8642
|
+
resources: [database.clusterArn],
|
|
8643
|
+
}),
|
|
8644
|
+
],
|
|
8645
|
+
properties: {
|
|
8646
|
+
clusterArn: database.clusterArn,
|
|
8647
|
+
database: database.database,
|
|
8648
|
+
host: databaseClusterEndpoint,
|
|
8649
|
+
password: database.password,
|
|
8650
|
+
port: database.port,
|
|
8651
|
+
reader: databaseReaderEndpoint,
|
|
8652
|
+
secretArn: database.secretArn,
|
|
8653
|
+
username: database.username,
|
|
8654
|
+
},
|
|
8655
|
+
});
|
|
7885
8656
|
const foundationAccountId = aws.getCallerIdentityOutput({}).accountId;
|
|
7886
8657
|
const foundationPartition = aws.getPartitionOutput({});
|
|
7887
8658
|
const foundationRegion = aws.getRegionOutput({});
|
|
@@ -7944,6 +8715,7 @@ export function foundation(
|
|
|
7944
8715
|
},
|
|
7945
8716
|
],
|
|
7946
8717
|
resources: [alertTopic.arn],
|
|
8718
|
+
sid: 'AllowBudgetAlerts',
|
|
7947
8719
|
},
|
|
7948
8720
|
{
|
|
7949
8721
|
actions: ['sns:Publish'],
|
|
@@ -7957,6 +8729,7 @@ export function foundation(
|
|
|
7957
8729
|
type: 'Service',
|
|
7958
8730
|
}],
|
|
7959
8731
|
resources: [alertTopic.arn],
|
|
8732
|
+
sid: 'AllowCostAnomalyAlerts',
|
|
7960
8733
|
},
|
|
7961
8734
|
{
|
|
7962
8735
|
actions: ['sns:Publish'],
|
|
@@ -7979,6 +8752,7 @@ export function foundation(
|
|
|
7979
8752
|
type: 'Service',
|
|
7980
8753
|
}],
|
|
7981
8754
|
resources: [alertTopic.arn],
|
|
8755
|
+
sid: 'AllowRdsEventAlerts',
|
|
7982
8756
|
},
|
|
7983
8757
|
{
|
|
7984
8758
|
actions: ['sns:Publish'],
|
|
@@ -8003,6 +8777,7 @@ export function foundation(
|
|
|
8003
8777
|
},
|
|
8004
8778
|
],
|
|
8005
8779
|
resources: [alertTopic.arn],
|
|
8780
|
+
sid: 'AllowCloudWatchAlarmAlerts',
|
|
8006
8781
|
},
|
|
8007
8782
|
],
|
|
8008
8783
|
}).json,
|
|
@@ -8031,6 +8806,41 @@ export function foundation(
|
|
|
8031
8806
|
{ dependsOn: [alertTopicPolicy] },
|
|
8032
8807
|
)
|
|
8033
8808
|
: undefined;
|
|
8809
|
+
const accountCostAnomalyMonitor = isProduction
|
|
8810
|
+
? new aws.costexplorer.AnomalyMonitor(
|
|
8811
|
+
'FoundationAccountCostAnomalyMonitor',
|
|
8812
|
+
{
|
|
8813
|
+
monitorDimension: 'SERVICE',
|
|
8814
|
+
monitorType: 'DIMENSIONAL',
|
|
8815
|
+
name: '${name}-account-service-cost-anomalies',
|
|
8816
|
+
tags: {
|
|
8817
|
+
'ebk:cost-policy-digest': accountCostControl.policyDigest,
|
|
8818
|
+
'ebk:cost-scope': '${name}/account/production',
|
|
8819
|
+
'ebk:owner': accountCostControl.owner,
|
|
8820
|
+
},
|
|
8821
|
+
},
|
|
8822
|
+
)
|
|
8823
|
+
: undefined;
|
|
8824
|
+
const accountCostAnomalySubscription =
|
|
8825
|
+
accountCostControl.anomaly && accountCostAnomalyMonitor
|
|
8826
|
+
? new aws.costexplorer.AnomalySubscription(
|
|
8827
|
+
'FoundationAccountCostAnomalySubscription',
|
|
8828
|
+
{
|
|
8829
|
+
frequency: accountCostControl.anomaly.subscription.frequency,
|
|
8830
|
+
monitorArnLists: [accountCostAnomalyMonitor.arn],
|
|
8831
|
+
name: accountCostControl.anomaly.subscription.name,
|
|
8832
|
+
subscribers: [{ address: alertTopic.arn, type: 'SNS' }],
|
|
8833
|
+
tags: {
|
|
8834
|
+
'ebk:cost-policy-digest': accountCostControl.policyDigest,
|
|
8835
|
+
'ebk:cost-scope': '${name}/account/production',
|
|
8836
|
+
'ebk:owner': accountCostControl.owner,
|
|
8837
|
+
},
|
|
8838
|
+
thresholdExpression:
|
|
8839
|
+
accountCostControl.anomaly.subscription.thresholdExpression,
|
|
8840
|
+
},
|
|
8841
|
+
{ dependsOn: [accountCostAnomalyMonitor, alertTopicPolicy] },
|
|
8842
|
+
)
|
|
8843
|
+
: undefined;
|
|
8034
8844
|
const databaseEvents = isProduction
|
|
8035
8845
|
? new aws.rds.EventSubscription(
|
|
8036
8846
|
'FoundationDatabaseEvents',
|
|
@@ -8133,12 +8943,10 @@ export function foundation(
|
|
|
8133
8943
|
clusterId: cluster.id,
|
|
8134
8944
|
contractVersion: '1.0.0',
|
|
8135
8945
|
databaseClusterArn: database.clusterArn,
|
|
8136
|
-
databaseClusterEndpoint
|
|
8137
|
-
(endpoint: string) => endpoint.split(':')[0],
|
|
8138
|
-
),
|
|
8946
|
+
databaseClusterEndpoint,
|
|
8139
8947
|
databaseClusterId: database.id,
|
|
8140
8948
|
databaseClusterResourceId: database.nodes.cluster.clusterResourceId,
|
|
8141
|
-
databaseHost:
|
|
8949
|
+
databaseHost: databaseClusterEndpoint,
|
|
8142
8950
|
databaseMasterSecretArn: database.secretArn,
|
|
8143
8951
|
databasePort: database.port.apply(String),
|
|
8144
8952
|
eventBusArn: eventBus.arn,
|
|
@@ -8160,6 +8968,7 @@ export function foundation(
|
|
|
8160
8968
|
capabilities: STANDARD_FOUNDATION_CAPABILITIES,
|
|
8161
8969
|
clusterId: cluster.id,
|
|
8162
8970
|
contractVersion: '1.0.0',
|
|
8971
|
+
costAnomalySubscriptionArn: accountCostAnomalySubscription?.arn,
|
|
8163
8972
|
databaseCapacityAlarmName: databaseCapacityAlarm.name,
|
|
8164
8973
|
dashboardName: dashboard.dashboardName,
|
|
8165
8974
|
databaseClusterId: database.id,
|
|
@@ -8191,13 +9000,49 @@ export function app(workloadRoles?: FoundationWorkloadRolePreflight) {
|
|
|
8191
9000
|
actualNotificationThresholdPercent: 80,
|
|
8192
9001
|
forecastNotificationThresholdPercent: 100,
|
|
8193
9002
|
kind: 'ebk-foundation-account-cost-policy',
|
|
8194
|
-
monthlyMaximumUsd: 1_000,
|
|
9003
|
+
monthlyMaximumUsd: deploymentProfile === 'starter' ? 250 : 1_000,
|
|
8195
9004
|
owner: 'replace-with-owner',
|
|
8196
9005
|
reviewAt: null,
|
|
8197
9006
|
schemaVersion: 1,
|
|
8198
9007
|
status: 'example',
|
|
8199
9008
|
workspace: name,
|
|
8200
9009
|
}),
|
|
9010
|
+
'packages/foundation/security/router-waf.json': json({
|
|
9011
|
+
kind: 'ebk-router-waf-policy',
|
|
9012
|
+
logging: {
|
|
9013
|
+
enabled: false,
|
|
9014
|
+
retention: '1 week',
|
|
9015
|
+
},
|
|
9016
|
+
rateLimitPerIp: 2_000,
|
|
9017
|
+
schemaVersion: 1,
|
|
9018
|
+
stages: {
|
|
9019
|
+
development: {
|
|
9020
|
+
access: 'allowlist',
|
|
9021
|
+
allowedCidrs: [],
|
|
9022
|
+
mode: 'block',
|
|
9023
|
+
},
|
|
9024
|
+
production: {
|
|
9025
|
+
access: 'public',
|
|
9026
|
+
allowedCidrs: [],
|
|
9027
|
+
mode: 'block',
|
|
9028
|
+
},
|
|
9029
|
+
staging: {
|
|
9030
|
+
access: 'allowlist',
|
|
9031
|
+
allowedCidrs: [],
|
|
9032
|
+
mode: 'block',
|
|
9033
|
+
},
|
|
9034
|
+
},
|
|
9035
|
+
}),
|
|
9036
|
+
'packages/foundation/security/router-waf-blocking-rehearsal.json': json({
|
|
9037
|
+
kind: 'ebk-router-waf-blocking-rehearsal',
|
|
9038
|
+
schemaVersion: 1,
|
|
9039
|
+
status: 'pending',
|
|
9040
|
+
}),
|
|
9041
|
+
'packages/foundation/security/router-waf-live-evidence.json': json({
|
|
9042
|
+
kind: 'ebk-router-waf-live-evidence',
|
|
9043
|
+
schemaVersion: 1,
|
|
9044
|
+
status: 'pending',
|
|
9045
|
+
}),
|
|
8201
9046
|
'packages/foundation/configuration/declarations.json': json({
|
|
8202
9047
|
project: 'foundation',
|
|
8203
9048
|
schemaVersion: 1,
|
|
@@ -8230,29 +9075,96 @@ export function app(workloadRoles?: FoundationWorkloadRolePreflight) {
|
|
|
8230
9075
|
version: '4.17.1',
|
|
8231
9076
|
},
|
|
8232
9077
|
policy: {
|
|
9078
|
+
adoptedImports: [
|
|
9079
|
+
{
|
|
9080
|
+
authority: 'workload-role-account-baseline',
|
|
9081
|
+
logicalId: 'FoundationVpcNatInstanceProfile',
|
|
9082
|
+
privateIdentifierTemplate: 'ebk-{stage}-foundation-nat-profile',
|
|
9083
|
+
recoveryRunbookRef: 'docs/workload-role-account-baseline.md',
|
|
9084
|
+
removalBehavior: 'retain',
|
|
9085
|
+
resourceType: 'aws:iam/instanceProfile:InstanceProfile',
|
|
9086
|
+
},
|
|
9087
|
+
],
|
|
8233
9088
|
additiveLowRiskTypes: [
|
|
8234
9089
|
'aws:cloudwatch/dashboard:Dashboard',
|
|
8235
9090
|
'aws:cloudwatch/logGroup:LogGroup',
|
|
8236
9091
|
'aws:cloudwatch/metricAlarm:MetricAlarm',
|
|
8237
9092
|
],
|
|
9093
|
+
additiveReviewedTypes: [
|
|
9094
|
+
'aws:cloudwatch/eventArchive:EventArchive',
|
|
9095
|
+
'aws:cloudwatch/eventBus:EventBus',
|
|
9096
|
+
'aws:cognito/userPoolDomain:UserPoolDomain',
|
|
9097
|
+
'aws:ec2/defaultSecurityGroup:DefaultSecurityGroup',
|
|
9098
|
+
'aws:ec2/internetGateway:InternetGateway',
|
|
9099
|
+
'aws:ec2/routeTable:RouteTable',
|
|
9100
|
+
'aws:ec2/routeTableAssociation:RouteTableAssociation',
|
|
9101
|
+
'aws:ec2/securityGroup:SecurityGroup',
|
|
9102
|
+
'aws:ec2/subnet:Subnet',
|
|
9103
|
+
'aws:ec2/vpc:Vpc',
|
|
9104
|
+
'aws:ecs/cluster:Cluster',
|
|
9105
|
+
'aws:ecs/clusterCapacityProviders:ClusterCapacityProviders',
|
|
9106
|
+
'aws:rds/clusterParameterGroup:ClusterParameterGroup',
|
|
9107
|
+
'aws:rds/eventSubscription:EventSubscription',
|
|
9108
|
+
'aws:rds/parameterGroup:ParameterGroup',
|
|
9109
|
+
'aws:rds/subnetGroup:SubnetGroup',
|
|
9110
|
+
'aws:servicediscovery/privateDnsNamespace:PrivateDnsNamespace',
|
|
9111
|
+
'aws:sns/topic:Topic',
|
|
9112
|
+
'aws:sns/topicPolicy:TopicPolicy',
|
|
9113
|
+
'aws:wafv2/webAclLoggingConfiguration:WebAclLoggingConfiguration',
|
|
9114
|
+
'pulumi-nodejs:dynamic:Resource',
|
|
9115
|
+
'pulumi:providers:aws',
|
|
9116
|
+
'pulumi:providers:pulumi-nodejs',
|
|
9117
|
+
'pulumi:providers:random',
|
|
9118
|
+
'pulumi:pulumi:Stack',
|
|
9119
|
+
'sst:aws:Aurora',
|
|
9120
|
+
'sst:aws:Bus',
|
|
9121
|
+
'sst:aws:CDN',
|
|
9122
|
+
'sst:aws:Cluster',
|
|
9123
|
+
'sst:aws:CognitoUserPool',
|
|
9124
|
+
'sst:aws:Router',
|
|
9125
|
+
'sst:aws:SnsTopic',
|
|
9126
|
+
'sst:aws:Vpc',
|
|
9127
|
+
'sst:sst:LinkRef',
|
|
9128
|
+
'sst:sst:Version',
|
|
9129
|
+
],
|
|
8238
9130
|
costOrQuotaTypes: [
|
|
8239
9131
|
'aws:apigatewayv2/api:Api',
|
|
8240
9132
|
'aws:budgets/budget:Budget',
|
|
9133
|
+
'aws:cloudfront/cachePolicy:CachePolicy',
|
|
8241
9134
|
'aws:cloudfront/distribution:Distribution',
|
|
9135
|
+
'aws:cloudfront/function:Function',
|
|
9136
|
+
'aws:cloudfront/keyValueStore:KeyValueStore',
|
|
9137
|
+
'aws:costexplorer/anomalyMonitor:AnomalyMonitor',
|
|
9138
|
+
'aws:costexplorer/anomalySubscription:AnomalySubscription',
|
|
8242
9139
|
'aws:cognito/userPool:UserPool',
|
|
9140
|
+
'aws:ec2/eip:Eip',
|
|
9141
|
+
'aws:ec2/eipAssociation:EipAssociation',
|
|
9142
|
+
'aws:ec2/instance:Instance',
|
|
8243
9143
|
'aws:ec2/natGateway:NatGateway',
|
|
8244
9144
|
'aws:rds/cluster:Cluster',
|
|
9145
|
+
'aws:rds/clusterInstance:ClusterInstance',
|
|
9146
|
+
'aws:wafv2/ipSet:IpSet',
|
|
9147
|
+
'aws:wafv2/webAcl:WebAcl',
|
|
8245
9148
|
],
|
|
8246
9149
|
failClosedOnUnknown: true,
|
|
8247
9150
|
protectedPlanAuthority: 'deploy-role-residual',
|
|
8248
9151
|
standingPolicyRiskClasses: ['no-change', 'additive-low-risk'],
|
|
8249
9152
|
statefulTypes: [
|
|
9153
|
+
'aws:cloudwatch/eventArchive:EventArchive',
|
|
9154
|
+
'aws:cloudwatch/eventBus:EventBus',
|
|
8250
9155
|
'aws:cognito/userPool:UserPool',
|
|
8251
9156
|
'aws:dynamodb/table:Table',
|
|
8252
9157
|
'aws:events/eventBus:EventBus',
|
|
8253
9158
|
'aws:kms/key:Key',
|
|
8254
9159
|
'aws:rds/cluster:Cluster',
|
|
8255
9160
|
'aws:s3/bucket:Bucket',
|
|
9161
|
+
'aws:secretsmanager/secret:Secret',
|
|
9162
|
+
'aws:secretsmanager/secretVersion:SecretVersion',
|
|
9163
|
+
'aws:sns/topic:Topic',
|
|
9164
|
+
'aws:ssm/parameter:Parameter',
|
|
9165
|
+
'aws:wafv2/ipSet:IpSet',
|
|
9166
|
+
'aws:wafv2/webAcl:WebAcl',
|
|
9167
|
+
'random:index/randomPassword:RandomPassword',
|
|
8256
9168
|
],
|
|
8257
9169
|
},
|
|
8258
9170
|
project: 'foundation',
|
|
@@ -8350,6 +9262,13 @@ export function app(workloadRoles?: FoundationWorkloadRolePreflight) {
|
|
|
8350
9262
|
executor: '@empire-builder-kit/nx:foundation-profile-preflight',
|
|
8351
9263
|
options: { stage: 'development' },
|
|
8352
9264
|
},
|
|
9265
|
+
'router-waf-rehearsal': {
|
|
9266
|
+
cache: false,
|
|
9267
|
+
executor: '@empire-builder-kit/nx:router-waf-rehearsal',
|
|
9268
|
+
outputs: [
|
|
9269
|
+
`{projectRoot}/security/router-waf-blocking-rehearsal.json`,
|
|
9270
|
+
],
|
|
9271
|
+
},
|
|
8353
9272
|
typecheck: {
|
|
8354
9273
|
executor: 'nx:run-commands',
|
|
8355
9274
|
options: {
|
|
@@ -8377,6 +9296,11 @@ export function app(workloadRoles?: FoundationWorkloadRolePreflight) {
|
|
|
8377
9296
|
}),
|
|
8378
9297
|
'packages/foundation/scripts/verify-foundation.mjs': `import { readFileSync } from 'node:fs';
|
|
8379
9298
|
|
|
9299
|
+
import {
|
|
9300
|
+
normalizeRouterWafPolicy,
|
|
9301
|
+
reconcileRouterWafBlockingRehearsal,
|
|
9302
|
+
} from '@empire-builder-kit/nx/foundation';
|
|
9303
|
+
|
|
8380
9304
|
const descriptor = JSON.parse(
|
|
8381
9305
|
readFileSync(new URL('../contracts/foundation.json', import.meta.url), 'utf8'),
|
|
8382
9306
|
);
|
|
@@ -8392,6 +9316,33 @@ const frontendSecurityHeaders = JSON.parse(
|
|
|
8392
9316
|
'utf8',
|
|
8393
9317
|
),
|
|
8394
9318
|
);
|
|
9319
|
+
const routerWafPolicy = normalizeRouterWafPolicy(
|
|
9320
|
+
JSON.parse(
|
|
9321
|
+
readFileSync(
|
|
9322
|
+
new URL('../security/router-waf.json', import.meta.url),
|
|
9323
|
+
'utf8',
|
|
9324
|
+
),
|
|
9325
|
+
),
|
|
9326
|
+
);
|
|
9327
|
+
reconcileRouterWafBlockingRehearsal({
|
|
9328
|
+
evidence: JSON.parse(
|
|
9329
|
+
readFileSync(
|
|
9330
|
+
new URL('../security/router-waf-live-evidence.json', import.meta.url),
|
|
9331
|
+
'utf8',
|
|
9332
|
+
),
|
|
9333
|
+
),
|
|
9334
|
+
policy: routerWafPolicy,
|
|
9335
|
+
receipt:
|
|
9336
|
+
JSON.parse(
|
|
9337
|
+
readFileSync(
|
|
9338
|
+
new URL(
|
|
9339
|
+
'../security/router-waf-blocking-rehearsal.json',
|
|
9340
|
+
import.meta.url,
|
|
9341
|
+
),
|
|
9342
|
+
'utf8',
|
|
9343
|
+
),
|
|
9344
|
+
),
|
|
9345
|
+
});
|
|
8395
9346
|
const expectedCapabilities = [
|
|
8396
9347
|
'network',
|
|
8397
9348
|
'routing',
|
|
@@ -8407,6 +9358,13 @@ const expectedCapabilities = [
|
|
|
8407
9358
|
if (descriptor.contractVersion !== '1.0.0') {
|
|
8408
9359
|
throw new Error('Foundation contractVersion must be 1.0.0.');
|
|
8409
9360
|
}
|
|
9361
|
+
if (
|
|
9362
|
+
routerWafPolicy.stages.development.access !== 'allowlist' ||
|
|
9363
|
+
routerWafPolicy.stages.staging.access !== 'allowlist' ||
|
|
9364
|
+
routerWafPolicy.stages.production.mode !== 'block'
|
|
9365
|
+
) {
|
|
9366
|
+
throw new Error('Foundation Router WAF stage defaults are invalid.');
|
|
9367
|
+
}
|
|
8410
9368
|
if (JSON.stringify(descriptor.capabilities) !== JSON.stringify(expectedCapabilities)) {
|
|
8411
9369
|
throw new Error('Foundation capability descriptor does not match the v1 standard.');
|
|
8412
9370
|
}
|
|
@@ -8415,7 +9373,7 @@ if (
|
|
|
8415
9373
|
costs.aurora?.engineVersion !== '${index_js_4.FOUNDATION_AURORA_ENGINE_VERSION}' ||
|
|
8416
9374
|
costs.productionProfileSource !== 'ebk.repository-policy.json' ||
|
|
8417
9375
|
costs.productionProfiles?.standard?.aurora?.min !== '2 ACU' ||
|
|
8418
|
-
costs.productionProfiles?.['cost-optimized']?.aurora?.min !== '0 ACU' ||
|
|
9376
|
+
costs.productionProfiles?.['cost-optimized']?.aurora?.min !== '0.5 ACU' ||
|
|
8419
9377
|
!costs.stages?.development ||
|
|
8420
9378
|
!costs.stages?.staging ||
|
|
8421
9379
|
costs.stages?.production?.dataRetention !== 'retained-and-protected'
|
|
@@ -8471,16 +9429,15 @@ export default $config({
|
|
|
8471
9429
|
providers: {
|
|
8472
9430
|
aws: {
|
|
8473
9431
|
defaultTags: {
|
|
9432
|
+
// SST contributes sst:app and sst:stage. Keep the provider baseline
|
|
9433
|
+
// at eight tags so generated S3 asset objects remain within AWS's
|
|
9434
|
+
// hard ten-tag object limit. Richer performance tags stay explicit.
|
|
8474
9435
|
tags: {
|
|
8475
9436
|
'ebk:cost-allocation': 'foundation',
|
|
8476
9437
|
'ebk:cost-scope': '${name}/foundation/' + stage,
|
|
8477
9438
|
'ebk:framework-version': '${frameworkVersion}',
|
|
8478
9439
|
'ebk:managed-by': 'empire-builder-kit',
|
|
8479
9440
|
'ebk:owner': 'foundation',
|
|
8480
|
-
'ebk:production-profile':
|
|
8481
|
-
process.env.EBK_PRODUCTION_FOUNDATION_PROFILE ?? 'standard',
|
|
8482
|
-
'ebk:repository':
|
|
8483
|
-
process.env.GITHUB_REPOSITORY ?? '${name}',
|
|
8484
9441
|
'ebk:slice': 'foundation',
|
|
8485
9442
|
'ebk:stage': stage,
|
|
8486
9443
|
'ebk:workspace': '${name}',
|
|
@@ -8524,6 +9481,16 @@ export default $config({
|
|
|
8524
9481
|
- 'packages/*/services/*'
|
|
8525
9482
|
|
|
8526
9483
|
autoInstallPeers: true
|
|
9484
|
+
blockExoticSubdeps: true
|
|
9485
|
+
enableGlobalVirtualStore: false
|
|
9486
|
+
ignorePnpmfile: true
|
|
9487
|
+
pnpmfile: []
|
|
9488
|
+
minimumReleaseAge: 1440
|
|
9489
|
+
minimumReleaseAgeExclude:
|
|
9490
|
+
- '@empire-builder-kit/*'
|
|
9491
|
+
minimumReleaseAgeIgnoreMissingTime: false
|
|
9492
|
+
minimumReleaseAgeStrict: true
|
|
9493
|
+
verifyDepsBeforeRun: error
|
|
8527
9494
|
storeDir: .ebk/cache/pnpm-store
|
|
8528
9495
|
supportedArchitectures:
|
|
8529
9496
|
os:
|
|
@@ -9393,18 +10360,22 @@ if (eventName === 'pull_request' || eventName === 'push') {
|
|
|
9393
10360
|
}
|
|
9394
10361
|
if (eventName === 'pull_request') {
|
|
9395
10362
|
const baseRef = process.env.GITHUB_BASE_REF;
|
|
10363
|
+
const headRepository = process.env.EBK_HEAD_REPOSITORY;
|
|
9396
10364
|
const headRef = process.env.GITHUB_HEAD_REF;
|
|
9397
|
-
|
|
9398
|
-
|
|
10365
|
+
const repository = process.env.GITHUB_REPOSITORY;
|
|
10366
|
+
if (!baseRef || !headRepository || !headRef || !repository) {
|
|
10367
|
+
throw new Error('Pull-request branch and repository metadata is missing.');
|
|
9399
10368
|
}
|
|
9400
10369
|
Object.assign(request, {
|
|
9401
10370
|
baseIsAncestorOfHead: isAncestor('refs/remotes/origin/' + baseRef, 'HEAD'),
|
|
9402
10371
|
baseRef,
|
|
10372
|
+
headRepository,
|
|
9403
10373
|
headRef,
|
|
9404
10374
|
mainIsAncestorOfDevelop: isAncestor(
|
|
9405
10375
|
'refs/remotes/origin/main',
|
|
9406
10376
|
'refs/remotes/origin/develop',
|
|
9407
10377
|
),
|
|
10378
|
+
repository,
|
|
9408
10379
|
});
|
|
9409
10380
|
} else if (eventName === 'push') {
|
|
9410
10381
|
Object.assign(request, {
|