@empire-builder-kit/nx 1.0.0-rc.2 → 1.0.0-rc.20
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 +66 -9
- 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 +1020 -62
- 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 +359 -27
- 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 +34 -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),
|
|
@@ -953,6 +1015,9 @@ const WORKSPACE_SEEDED_PATHS = new Set([
|
|
|
953
1015
|
exports.WORKSPACE_PACKAGE_MANAGED_JSON_POINTERS = [
|
|
954
1016
|
'/devDependencies/eslint-plugin-jsx-a11y',
|
|
955
1017
|
'/devDependencies/vitest',
|
|
1018
|
+
'/engines/node',
|
|
1019
|
+
'/engines/pnpm',
|
|
1020
|
+
'/packageManager',
|
|
956
1021
|
];
|
|
957
1022
|
exports.WORKSPACE_PROJECT_MANAGED_JSON_POINTERS = [
|
|
958
1023
|
'/name',
|
|
@@ -961,9 +1026,11 @@ exports.WORKSPACE_PROJECT_MANAGED_JSON_POINTERS = [
|
|
|
961
1026
|
'/root',
|
|
962
1027
|
'/tags',
|
|
963
1028
|
'/targets/blueprint-trust-check',
|
|
1029
|
+
'/targets/blueprint-trust-check-main',
|
|
964
1030
|
'/targets/blueprint-trust-check-protected',
|
|
965
1031
|
'/targets/developer-context',
|
|
966
1032
|
'/targets/deployment-plan',
|
|
1033
|
+
'/targets/deploy-local',
|
|
967
1034
|
'/targets/dev',
|
|
968
1035
|
'/targets/dev-doctor',
|
|
969
1036
|
'/targets/dev-setup',
|
|
@@ -973,6 +1040,7 @@ exports.WORKSPACE_PROJECT_MANAGED_JSON_POINTERS = [
|
|
|
973
1040
|
'/targets/supply-chain-check-protected',
|
|
974
1041
|
'/targets/supply-chain-scan',
|
|
975
1042
|
'/targets/verify',
|
|
1043
|
+
'/targets/verify-proposed',
|
|
976
1044
|
'/targets/work-markers',
|
|
977
1045
|
'/targets/workload-role-candidate-report',
|
|
978
1046
|
'/targets/workload-role-provisioner-build',
|
|
@@ -983,6 +1051,10 @@ const FOUNDATION_SEEDED_PATHS = new Set([
|
|
|
983
1051
|
'docs/costs.md',
|
|
984
1052
|
'docs/runbooks.md',
|
|
985
1053
|
'docs/security.md',
|
|
1054
|
+
'performance/account-cost-policy.json',
|
|
1055
|
+
'security/router-waf-blocking-rehearsal.json',
|
|
1056
|
+
'security/router-waf-live-evidence.json',
|
|
1057
|
+
'security/router-waf.json',
|
|
986
1058
|
]);
|
|
987
1059
|
function createFoundationOwnership(files, frameworkVersion) {
|
|
988
1060
|
const paths = Object.keys(files)
|
|
@@ -1045,7 +1117,16 @@ function createWorkspaceOwnership(files, frameworkVersion) {
|
|
|
1045
1117
|
}
|
|
1046
1118
|
function renderWorkspaceFiles(options) {
|
|
1047
1119
|
const { frameworkVersion, name } = options;
|
|
1120
|
+
const deploymentProfile = options.deploymentProfile ?? 'starter';
|
|
1121
|
+
const initialRepositoryPolicy = (0, index_js_4.createIncubationRepositoryPolicy)(deploymentProfile);
|
|
1122
|
+
initialRepositoryPolicy.deploymentAccounts = options.deploymentAccounts ?? {
|
|
1123
|
+
development: null,
|
|
1124
|
+
production: null,
|
|
1125
|
+
staging: null,
|
|
1126
|
+
};
|
|
1127
|
+
initialRepositoryPolicy.deploymentRegion = options.deploymentRegion ?? null;
|
|
1048
1128
|
const developerAgentFilePlan = (0, workspace_files_developer_agent_js_1.renderDeveloperAgentWorkspaceFilePlan)({
|
|
1129
|
+
deploymentProfile,
|
|
1049
1130
|
frameworkVersion,
|
|
1050
1131
|
name,
|
|
1051
1132
|
});
|
|
@@ -1056,7 +1137,13 @@ function renderWorkspaceFiles(options) {
|
|
|
1056
1137
|
const pnpmAction = (0, index_js_3.pinnedAction)(index_js_3.GITHUB_ACTION_PINS.pnpmSetup);
|
|
1057
1138
|
const setupNodeAction = (0, index_js_3.pinnedAction)(index_js_3.GITHUB_ACTION_PINS.setupNode);
|
|
1058
1139
|
const uploadArtifactAction = (0, index_js_3.pinnedAction)(index_js_3.GITHUB_ACTION_PINS.uploadArtifact);
|
|
1140
|
+
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'";
|
|
1141
|
+
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'";
|
|
1142
|
+
const protectedPush = "github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/develop')";
|
|
1143
|
+
const protectedDeploymentRouteJobName = 'protected deployment route';
|
|
1144
|
+
const stagingApplyJobPrefix = 'apply staging plan ';
|
|
1059
1145
|
const files = {
|
|
1146
|
+
...(0, workspace_files_ci_acceptance_js_1.renderWorkspaceCiAcceptanceFiles)(),
|
|
1060
1147
|
'.node-version': `${tool_versions_js_1.WORKSPACE_NODE_VERSION}\n`,
|
|
1061
1148
|
'.github/CODEOWNERS': `# Replace this placeholder with the GitHub user or team that owns protected source.
|
|
1062
1149
|
# GitHub bootstrap remains blocked until a non-placeholder catch-all owner is committed.
|
|
@@ -1072,15 +1159,77 @@ on:
|
|
|
1072
1159
|
workflow_dispatch:
|
|
1073
1160
|
|
|
1074
1161
|
concurrency:
|
|
1075
|
-
group: verify-\${{ github.ref }}
|
|
1076
|
-
cancel-in-progress:
|
|
1162
|
+
group: verify-\${{ github.event_name == 'push' && github.sha || github.ref }}
|
|
1163
|
+
cancel-in-progress: \${{ github.event_name != 'push' }}
|
|
1077
1164
|
|
|
1078
1165
|
permissions:
|
|
1166
|
+
actions: read
|
|
1079
1167
|
contents: read
|
|
1168
|
+
pull-requests: read
|
|
1169
|
+
|
|
1170
|
+
env:
|
|
1171
|
+
EBK_CI_CANDIDATE_SHA: \${{ github.event.pull_request.head.sha || github.sha }}
|
|
1080
1172
|
|
|
1081
1173
|
jobs:
|
|
1174
|
+
acceptance:
|
|
1175
|
+
name: accepted merge evidence
|
|
1176
|
+
if: ${protectedPush}
|
|
1177
|
+
runs-on: ubuntu-latest
|
|
1178
|
+
outputs:
|
|
1179
|
+
history_only: \${{ steps.resolve.outputs.history_only == 'true' && steps.restore.outcome == 'success' && steps.verify.outcome == 'success' }}
|
|
1180
|
+
reused: \${{ steps.resolve.outputs.reused == 'true' && steps.restore.outcome == 'success' && steps.verify.outcome == 'success' }}
|
|
1181
|
+
route: \${{ steps.resolve.outputs.reused == 'true' && steps.restore.outcome == 'success' && steps.verify.outcome == 'success' && steps.resolve.outputs.route || '' }}
|
|
1182
|
+
steps:
|
|
1183
|
+
- name: Check out immutable merged candidate
|
|
1184
|
+
if: ${protectedPush}
|
|
1185
|
+
uses: ${checkoutAction}
|
|
1186
|
+
with:
|
|
1187
|
+
fetch-depth: 0
|
|
1188
|
+
ref: \${{ github.sha }}
|
|
1189
|
+
- name: Resolve exact protected-route acceptance
|
|
1190
|
+
if: ${protectedPush}
|
|
1191
|
+
id: resolve
|
|
1192
|
+
env:
|
|
1193
|
+
GH_TOKEN: \${{ github.token }}
|
|
1194
|
+
run: node tools/ci/workspace-acceptance.mjs resolve
|
|
1195
|
+
- name: Restore exact protected-route acceptance
|
|
1196
|
+
if: steps.resolve.outputs.reused == 'true'
|
|
1197
|
+
id: restore
|
|
1198
|
+
continue-on-error: true
|
|
1199
|
+
env:
|
|
1200
|
+
EBK_ACCEPTANCE_ARTIFACT_DIGEST: \${{ steps.resolve.outputs.artifact_digest }}
|
|
1201
|
+
EBK_ACCEPTANCE_ARTIFACT_ID: \${{ steps.resolve.outputs.artifact_id }}
|
|
1202
|
+
EBK_ACCEPTANCE_ARTIFACT_SIZE: \${{ steps.resolve.outputs.artifact_size }}
|
|
1203
|
+
GH_TOKEN: \${{ github.token }}
|
|
1204
|
+
run: >-
|
|
1205
|
+
node tools/ci/workspace-acceptance.mjs download
|
|
1206
|
+
.ebk/evidence/reused-acceptance
|
|
1207
|
+
"$EBK_ACCEPTANCE_ARTIFACT_ID"
|
|
1208
|
+
"$EBK_ACCEPTANCE_ARTIFACT_DIGEST"
|
|
1209
|
+
"$EBK_ACCEPTANCE_ARTIFACT_SIZE"
|
|
1210
|
+
- name: Verify exact merged-tree acceptance
|
|
1211
|
+
if: steps.resolve.outputs.reused == 'true' && steps.restore.outcome == 'success'
|
|
1212
|
+
id: verify
|
|
1213
|
+
continue-on-error: true
|
|
1214
|
+
env:
|
|
1215
|
+
EBK_ACCEPTANCE_BASE: \${{ steps.resolve.outputs.base_commit }}
|
|
1216
|
+
EBK_ACCEPTANCE_HEAD: \${{ steps.resolve.outputs.head_commit }}
|
|
1217
|
+
EBK_ACCEPTANCE_ROUTE: \${{ steps.resolve.outputs.route }}
|
|
1218
|
+
EBK_ACCEPTANCE_RUN_ATTEMPT: \${{ steps.resolve.outputs.run_attempt }}
|
|
1219
|
+
EBK_ACCEPTANCE_RUN_ID: \${{ steps.resolve.outputs.run_id }}
|
|
1220
|
+
run: >-
|
|
1221
|
+
node tools/ci/workspace-acceptance.mjs verify
|
|
1222
|
+
.ebk/evidence/reused-acceptance/manifest.json
|
|
1223
|
+
"$EBK_ACCEPTANCE_HEAD"
|
|
1224
|
+
"$EBK_ACCEPTANCE_BASE"
|
|
1225
|
+
"$EBK_ACCEPTANCE_ROUTE"
|
|
1226
|
+
"$EBK_ACCEPTANCE_RUN_ID"
|
|
1227
|
+
"$EBK_ACCEPTANCE_RUN_ATTEMPT"
|
|
1228
|
+
|
|
1082
1229
|
policy:
|
|
1083
1230
|
name: policy
|
|
1231
|
+
needs: acceptance
|
|
1232
|
+
if: always() && needs.acceptance.outputs.reused != 'true'
|
|
1084
1233
|
runs-on: ubuntu-latest
|
|
1085
1234
|
steps:
|
|
1086
1235
|
- uses: ${checkoutAction}
|
|
@@ -1094,12 +1243,19 @@ jobs:
|
|
|
1094
1243
|
cache: pnpm
|
|
1095
1244
|
- run: pnpm install --frozen-lockfile
|
|
1096
1245
|
- run: node tools/ebk-git-policy.mjs
|
|
1246
|
+
env:
|
|
1247
|
+
EBK_HEAD_REPOSITORY: \${{ github.event.pull_request.head.repo.full_name }}
|
|
1097
1248
|
|
|
1098
1249
|
verify:
|
|
1099
1250
|
name: verify
|
|
1251
|
+
needs: acceptance
|
|
1252
|
+
if: always() && needs.acceptance.outputs.reused != 'true'
|
|
1100
1253
|
runs-on: ubuntu-latest
|
|
1101
1254
|
steps:
|
|
1102
1255
|
- uses: ${checkoutAction}
|
|
1256
|
+
with:
|
|
1257
|
+
fetch-depth: 0
|
|
1258
|
+
ref: \${{ github.event.pull_request.head.sha || github.sha }}
|
|
1103
1259
|
- uses: ${pnpmAction}
|
|
1104
1260
|
- uses: ${setupNodeAction}
|
|
1105
1261
|
with:
|
|
@@ -1107,9 +1263,80 @@ jobs:
|
|
|
1107
1263
|
cache: pnpm
|
|
1108
1264
|
- run: pnpm install --frozen-lockfile
|
|
1109
1265
|
- run: pnpm nx format:check
|
|
1110
|
-
-
|
|
1266
|
+
- name: Verify proposed workspace
|
|
1267
|
+
if: github.event_name == 'pull_request' && !(${protectedDevelopPromotion} || ${protectedMainSync})
|
|
1268
|
+
run: |
|
|
1269
|
+
pnpm nx run ${name}:verify-proposed
|
|
1270
|
+
pnpm nx run-many -t verify --exclude=${name} --parallel=3
|
|
1271
|
+
- name: Verify protected develop promotion
|
|
1272
|
+
if: github.event_name == 'pull_request' && ${protectedDevelopPromotion}
|
|
1273
|
+
run: pnpm nx run-many -t verify --parallel=3
|
|
1274
|
+
- name: Verify protected main sync
|
|
1275
|
+
if: github.event_name == 'pull_request' && ${protectedMainSync}
|
|
1276
|
+
run: |
|
|
1277
|
+
pnpm nx run ${name}:blueprint-trust-check-main
|
|
1278
|
+
pnpm nx run ${name}:verify-proposed
|
|
1279
|
+
pnpm nx run-many -t verify --exclude=${name} --parallel=3
|
|
1280
|
+
- name: Verify adopted workspace
|
|
1281
|
+
if: github.event_name != 'pull_request'
|
|
1282
|
+
run: pnpm nx run-many -t verify --parallel=3
|
|
1283
|
+
|
|
1284
|
+
seal-acceptance:
|
|
1285
|
+
name: seal protected route acceptance
|
|
1286
|
+
needs: [policy, verify]
|
|
1287
|
+
if: >-
|
|
1288
|
+
always() &&
|
|
1289
|
+
needs.policy.result == 'success' &&
|
|
1290
|
+
needs.verify.result == 'success' &&
|
|
1291
|
+
github.event_name == 'pull_request' &&
|
|
1292
|
+
(${protectedDevelopPromotion} || ${protectedMainSync})
|
|
1293
|
+
runs-on: ubuntu-latest
|
|
1294
|
+
steps:
|
|
1295
|
+
- name: Check out immutable accepted candidate
|
|
1296
|
+
uses: ${checkoutAction}
|
|
1297
|
+
with:
|
|
1298
|
+
fetch-depth: 0
|
|
1299
|
+
ref: \${{ env.EBK_CI_CANDIDATE_SHA }}
|
|
1300
|
+
- name: Seal exact protected-route acceptance
|
|
1301
|
+
id: seal
|
|
1302
|
+
env:
|
|
1303
|
+
EBK_BASE_COMMIT: \${{ github.event.pull_request.base.sha }}
|
|
1304
|
+
EBK_HEAD_REPOSITORY: \${{ github.event.pull_request.head.repo.full_name }}
|
|
1305
|
+
run: >-
|
|
1306
|
+
node tools/ci/workspace-acceptance.mjs seal
|
|
1307
|
+
.ebk/evidence/ci/workspace-acceptance/manifest.json
|
|
1308
|
+
- name: Retain exact protected-route acceptance
|
|
1309
|
+
if: steps.seal.outputs.sealed == 'true'
|
|
1310
|
+
uses: ${uploadArtifactAction}
|
|
1311
|
+
with:
|
|
1312
|
+
name: workspace-acceptance-\${{ env.EBK_CI_CANDIDATE_SHA }}-\${{ github.run_id }}-\${{ github.run_attempt }}
|
|
1313
|
+
path: .ebk/evidence/ci/workspace-acceptance
|
|
1314
|
+
if-no-files-found: error
|
|
1315
|
+
include-hidden-files: true
|
|
1316
|
+
retention-days: 30
|
|
1317
|
+
|
|
1318
|
+
release-candidate:
|
|
1319
|
+
name: build immutable release candidates
|
|
1320
|
+
needs: [acceptance, policy, verify]
|
|
1321
|
+
if: >-
|
|
1322
|
+
always() &&
|
|
1323
|
+
github.event_name == 'push' &&
|
|
1324
|
+
needs.acceptance.outputs.history_only != 'true' &&
|
|
1325
|
+
(needs.acceptance.outputs.reused == 'true' ||
|
|
1326
|
+
(needs.policy.result == 'success' && needs.verify.result == 'success'))
|
|
1327
|
+
runs-on: ubuntu-latest
|
|
1328
|
+
steps:
|
|
1329
|
+
- uses: ${checkoutAction}
|
|
1330
|
+
with:
|
|
1331
|
+
fetch-depth: 0
|
|
1332
|
+
ref: \${{ github.sha }}
|
|
1333
|
+
- uses: ${pnpmAction}
|
|
1334
|
+
- uses: ${setupNodeAction}
|
|
1335
|
+
with:
|
|
1336
|
+
node-version: ${tool_versions_js_1.WORKSPACE_NODE_VERSION}
|
|
1337
|
+
cache: pnpm
|
|
1338
|
+
- run: pnpm install --frozen-lockfile
|
|
1111
1339
|
- name: Build immutable release candidates
|
|
1112
|
-
if: github.event_name == 'push'
|
|
1113
1340
|
env:
|
|
1114
1341
|
EBK_SOURCE_COMMIT: \${{ github.sha }}
|
|
1115
1342
|
run: |
|
|
@@ -1127,7 +1354,6 @@ jobs:
|
|
|
1127
1354
|
".ebk/evidence/release-candidate/packages/$project/"
|
|
1128
1355
|
done
|
|
1129
1356
|
- name: Retain exact immutable release candidate
|
|
1130
|
-
if: github.event_name == 'push'
|
|
1131
1357
|
uses: ${uploadArtifactAction}
|
|
1132
1358
|
with:
|
|
1133
1359
|
name: release-candidate-\${{ github.sha }}
|
|
@@ -1162,7 +1388,97 @@ permissions:
|
|
|
1162
1388
|
contents: read
|
|
1163
1389
|
|
|
1164
1390
|
jobs:
|
|
1391
|
+
history-classification:
|
|
1392
|
+
name: classify protected history-only sync
|
|
1393
|
+
if: github.event_name == 'push' && github.ref == 'refs/heads/develop'
|
|
1394
|
+
runs-on: ubuntu-latest
|
|
1395
|
+
permissions:
|
|
1396
|
+
actions: read
|
|
1397
|
+
contents: read
|
|
1398
|
+
pull-requests: read
|
|
1399
|
+
outputs:
|
|
1400
|
+
history_only: \${{ steps.resolve.outputs.history_only == 'true' && steps.restore.outcome == 'success' && steps.verify.outcome == 'success' }}
|
|
1401
|
+
steps:
|
|
1402
|
+
- name: Check out immutable protected candidate
|
|
1403
|
+
uses: ${checkoutAction}
|
|
1404
|
+
with:
|
|
1405
|
+
fetch-depth: 0
|
|
1406
|
+
ref: \${{ github.sha }}
|
|
1407
|
+
- uses: ${setupNodeAction}
|
|
1408
|
+
with:
|
|
1409
|
+
node-version: ${tool_versions_js_1.WORKSPACE_NODE_VERSION}
|
|
1410
|
+
- name: Resolve exact protected-route acceptance
|
|
1411
|
+
id: resolve
|
|
1412
|
+
env:
|
|
1413
|
+
GH_TOKEN: \${{ github.token }}
|
|
1414
|
+
run: node tools/ci/workspace-acceptance.mjs resolve
|
|
1415
|
+
- name: Restore exact protected-route acceptance
|
|
1416
|
+
if: steps.resolve.outputs.reused == 'true'
|
|
1417
|
+
id: restore
|
|
1418
|
+
continue-on-error: true
|
|
1419
|
+
env:
|
|
1420
|
+
EBK_ACCEPTANCE_ARTIFACT_DIGEST: \${{ steps.resolve.outputs.artifact_digest }}
|
|
1421
|
+
EBK_ACCEPTANCE_ARTIFACT_ID: \${{ steps.resolve.outputs.artifact_id }}
|
|
1422
|
+
EBK_ACCEPTANCE_ARTIFACT_SIZE: \${{ steps.resolve.outputs.artifact_size }}
|
|
1423
|
+
GH_TOKEN: \${{ github.token }}
|
|
1424
|
+
run: >-
|
|
1425
|
+
node tools/ci/workspace-acceptance.mjs download
|
|
1426
|
+
.ebk/evidence/reused-history-acceptance
|
|
1427
|
+
"$EBK_ACCEPTANCE_ARTIFACT_ID"
|
|
1428
|
+
"$EBK_ACCEPTANCE_ARTIFACT_DIGEST"
|
|
1429
|
+
"$EBK_ACCEPTANCE_ARTIFACT_SIZE"
|
|
1430
|
+
- name: Verify exact history-only acceptance
|
|
1431
|
+
if: steps.resolve.outputs.reused == 'true' && steps.restore.outcome == 'success'
|
|
1432
|
+
id: verify
|
|
1433
|
+
continue-on-error: true
|
|
1434
|
+
env:
|
|
1435
|
+
EBK_ACCEPTANCE_BASE: \${{ steps.resolve.outputs.base_commit }}
|
|
1436
|
+
EBK_ACCEPTANCE_HEAD: \${{ steps.resolve.outputs.head_commit }}
|
|
1437
|
+
EBK_ACCEPTANCE_ROUTE: \${{ steps.resolve.outputs.route }}
|
|
1438
|
+
EBK_ACCEPTANCE_RUN_ATTEMPT: \${{ steps.resolve.outputs.run_attempt }}
|
|
1439
|
+
EBK_ACCEPTANCE_RUN_ID: \${{ steps.resolve.outputs.run_id }}
|
|
1440
|
+
run: >-
|
|
1441
|
+
node tools/ci/workspace-acceptance.mjs verify
|
|
1442
|
+
.ebk/evidence/reused-history-acceptance/manifest.json
|
|
1443
|
+
"$EBK_ACCEPTANCE_HEAD"
|
|
1444
|
+
"$EBK_ACCEPTANCE_BASE"
|
|
1445
|
+
"$EBK_ACCEPTANCE_ROUTE"
|
|
1446
|
+
"$EBK_ACCEPTANCE_RUN_ID"
|
|
1447
|
+
"$EBK_ACCEPTANCE_RUN_ATTEMPT"
|
|
1448
|
+
|
|
1449
|
+
deployment-mode:
|
|
1450
|
+
name: classify deployment profile
|
|
1451
|
+
runs-on: ubuntu-latest
|
|
1452
|
+
outputs:
|
|
1453
|
+
profile: \${{ steps.profile.outputs.profile }}
|
|
1454
|
+
steps:
|
|
1455
|
+
- name: Check out immutable deployment policy
|
|
1456
|
+
uses: ${checkoutAction}
|
|
1457
|
+
with:
|
|
1458
|
+
ref: \${{ github.sha }}
|
|
1459
|
+
- id: profile
|
|
1460
|
+
name: Read exact committed deployment profile
|
|
1461
|
+
run: |
|
|
1462
|
+
node - <<'NODE'
|
|
1463
|
+
const { readFileSync } = require('node:fs');
|
|
1464
|
+
const policy = JSON.parse(readFileSync('ebk.repository-policy.json', 'utf8'));
|
|
1465
|
+
const profiles = new Set(['starter', 'production-isolated', 'fully-isolated']);
|
|
1466
|
+
if (!profiles.has(policy.deploymentProfile)) {
|
|
1467
|
+
throw new Error('Committed deployment profile is absent or invalid.');
|
|
1468
|
+
}
|
|
1469
|
+
require('node:fs').appendFileSync(
|
|
1470
|
+
process.env.GITHUB_OUTPUT,
|
|
1471
|
+
'profile=' + policy.deploymentProfile + '\\n',
|
|
1472
|
+
);
|
|
1473
|
+
NODE
|
|
1474
|
+
|
|
1165
1475
|
route:
|
|
1476
|
+
name: ${protectedDeploymentRouteJobName}
|
|
1477
|
+
needs: [history-classification, deployment-mode]
|
|
1478
|
+
if: >-
|
|
1479
|
+
always() &&
|
|
1480
|
+
needs.history-classification.outputs.history_only != 'true' &&
|
|
1481
|
+
needs.deployment-mode.result == 'success'
|
|
1166
1482
|
runs-on: ubuntu-latest
|
|
1167
1483
|
steps:
|
|
1168
1484
|
- name: Validate protected ref
|
|
@@ -1175,14 +1491,22 @@ jobs:
|
|
|
1175
1491
|
esac
|
|
1176
1492
|
|
|
1177
1493
|
plan-staging:
|
|
1178
|
-
needs: route
|
|
1179
|
-
if:
|
|
1494
|
+
needs: [route, deployment-mode]
|
|
1495
|
+
if: >-
|
|
1496
|
+
github.ref == 'refs/heads/develop' &&
|
|
1497
|
+
(github.event_name == 'workflow_dispatch' ||
|
|
1498
|
+
needs.deployment-mode.outputs.profile != 'starter')
|
|
1180
1499
|
runs-on: ubuntu-latest
|
|
1181
1500
|
outputs:
|
|
1501
|
+
deployment_profile: \${{ steps.plan.outputs.deployment_profile }}
|
|
1502
|
+
deployment_region: \${{ steps.plan.outputs.deployment_region }}
|
|
1503
|
+
development_account_id: \${{ steps.plan.outputs.development_account_id }}
|
|
1182
1504
|
foundation_project: \${{ steps.plan.outputs.foundation_project }}
|
|
1183
1505
|
has_slices: \${{ steps.plan.outputs.has_slices }}
|
|
1184
1506
|
ordered_projects: \${{ steps.plan.outputs.ordered_projects }}
|
|
1507
|
+
production_account_id: \${{ steps.plan.outputs.production_account_id }}
|
|
1185
1508
|
slice_projects: \${{ steps.plan.outputs.slice_projects }}
|
|
1509
|
+
staging_account_id: \${{ steps.plan.outputs.staging_account_id }}
|
|
1186
1510
|
steps:
|
|
1187
1511
|
- uses: ${checkoutAction}
|
|
1188
1512
|
with:
|
|
@@ -1393,16 +1717,21 @@ jobs:
|
|
|
1393
1717
|
path: .ebk/evidence/staging-release-source/**
|
|
1394
1718
|
|
|
1395
1719
|
plan-production:
|
|
1396
|
-
needs: route
|
|
1720
|
+
needs: [route, deployment-mode]
|
|
1397
1721
|
if: github.ref == 'refs/heads/main'
|
|
1398
1722
|
runs-on: ubuntu-latest
|
|
1399
1723
|
outputs:
|
|
1724
|
+
deployment_profile: \${{ steps.plan.outputs.deployment_profile }}
|
|
1725
|
+
deployment_region: \${{ steps.plan.outputs.deployment_region }}
|
|
1726
|
+
development_account_id: \${{ steps.plan.outputs.development_account_id }}
|
|
1400
1727
|
foundation_project: \${{ steps.plan.outputs.foundation_project }}
|
|
1401
1728
|
foundation_profile: \${{ steps.plan.outputs.foundation_profile }}
|
|
1402
1729
|
has_slices: \${{ steps.plan.outputs.has_slices }}
|
|
1403
1730
|
ordered_projects: \${{ steps.plan.outputs.ordered_projects }}
|
|
1404
1731
|
production_approval_mode: \${{ steps.plan.outputs.production_approval_mode }}
|
|
1732
|
+
production_account_id: \${{ steps.plan.outputs.production_account_id }}
|
|
1405
1733
|
slice_projects: \${{ steps.plan.outputs.slice_projects }}
|
|
1734
|
+
staging_account_id: \${{ steps.plan.outputs.staging_account_id }}
|
|
1406
1735
|
steps:
|
|
1407
1736
|
- uses: ${checkoutAction}
|
|
1408
1737
|
with:
|
|
@@ -1723,6 +2052,8 @@ jobs:
|
|
|
1723
2052
|
EBK_RELEASE_SOURCE_COMMIT: \${{ needs.release-source-staging.outputs.source_commit }}
|
|
1724
2053
|
EBK_SOURCE_COMMIT: \${{ needs.release-source-staging.outputs.source_commit }}
|
|
1725
2054
|
EBK_STAGE: staging
|
|
2055
|
+
EBK_POLICY_ACCOUNT_ID: \${{ needs.plan-staging.outputs.staging_account_id }}
|
|
2056
|
+
EBK_POLICY_REGION: \${{ needs.plan-staging.outputs.deployment_region }}
|
|
1726
2057
|
SLICE_PROJECTS: \${{ needs.plan-staging.outputs.slice_projects }}
|
|
1727
2058
|
steps:
|
|
1728
2059
|
- uses: ${checkoutAction}
|
|
@@ -1738,6 +2069,10 @@ jobs:
|
|
|
1738
2069
|
- run: pnpm install --frozen-lockfile --ignore-scripts --ignore-pnpmfile
|
|
1739
2070
|
- name: Verify managed workspace integrity before AWS authority
|
|
1740
2071
|
run: pnpm nx run ${name}:workspace-health
|
|
2072
|
+
- name: Bind staging account to committed deployment topology
|
|
2073
|
+
run: |
|
|
2074
|
+
test "$AWS_EXPECTED_ACCOUNT_ID" = "$EBK_POLICY_ACCOUNT_ID"
|
|
2075
|
+
test "$AWS_REGION" = "$EBK_POLICY_REGION"
|
|
1741
2076
|
${stagingReleaseCandidateDownloadSteps(downloadArtifactAction)} - uses: ${configureAwsAction}
|
|
1742
2077
|
with:
|
|
1743
2078
|
role-to-assume: \${{ secrets.AWS_DEPLOY_ROLE_ARN }}
|
|
@@ -1777,6 +2112,8 @@ ${stagingReleaseCandidateDownloadSteps(downloadArtifactAction)} - uses: ${c
|
|
|
1777
2112
|
EBK_RELEASE_SOURCE_COMMIT: \${{ needs.release-source-staging.outputs.source_commit }}
|
|
1778
2113
|
EBK_SOURCE_COMMIT: \${{ needs.release-source-staging.outputs.source_commit }}
|
|
1779
2114
|
EBK_STAGE: staging
|
|
2115
|
+
EBK_POLICY_ACCOUNT_ID: \${{ needs.plan-staging.outputs.staging_account_id }}
|
|
2116
|
+
EBK_POLICY_REGION: \${{ needs.plan-staging.outputs.deployment_region }}
|
|
1780
2117
|
SLICE_PROJECTS: \${{ needs.plan-staging.outputs.slice_projects }}
|
|
1781
2118
|
steps:
|
|
1782
2119
|
- uses: ${checkoutAction}
|
|
@@ -1792,6 +2129,10 @@ ${stagingReleaseCandidateDownloadSteps(downloadArtifactAction)} - uses: ${c
|
|
|
1792
2129
|
- run: pnpm install --frozen-lockfile --ignore-scripts --ignore-pnpmfile
|
|
1793
2130
|
- name: Verify managed workspace integrity before AWS authority
|
|
1794
2131
|
run: pnpm nx run ${name}:workspace-health
|
|
2132
|
+
- name: Bind staging account to committed deployment topology
|
|
2133
|
+
run: |
|
|
2134
|
+
test "$AWS_EXPECTED_ACCOUNT_ID" = "$EBK_POLICY_ACCOUNT_ID"
|
|
2135
|
+
test "$AWS_REGION" = "$EBK_POLICY_REGION"
|
|
1795
2136
|
${stagingReleaseCandidateDownloadSteps(downloadArtifactAction)} - name: Restore both native platform records
|
|
1796
2137
|
uses: ${downloadArtifactAction}
|
|
1797
2138
|
with:
|
|
@@ -1833,6 +2174,10 @@ ${stagingReleaseCandidateDownloadSteps(downloadArtifactAction)} - name: Res
|
|
|
1833
2174
|
AWS_DEPLOY_ROLE_ARN: \${{ secrets.AWS_DEPLOY_ROLE_ARN }}
|
|
1834
2175
|
AWS_EXPECTED_ACCOUNT_ID: \${{ vars.AWS_EXPECTED_ACCOUNT_ID }}
|
|
1835
2176
|
AWS_REGION: \${{ vars.AWS_REGION }}
|
|
2177
|
+
EBK_DEPLOYMENT_PROFILE: \${{ needs.plan-production.outputs.deployment_profile }}
|
|
2178
|
+
EBK_POLICY_PRODUCTION_ACCOUNT_ID: \${{ needs.plan-production.outputs.production_account_id }}
|
|
2179
|
+
EBK_POLICY_REGION: \${{ needs.plan-production.outputs.deployment_region }}
|
|
2180
|
+
EBK_POLICY_STAGING_ACCOUNT_ID: \${{ needs.plan-production.outputs.staging_account_id }}
|
|
1836
2181
|
EBK_CANDIDATE_RUN_ATTEMPT: \${{ needs.release-source-production.outputs.candidate_run_attempt }}
|
|
1837
2182
|
EBK_CANDIDATE_RUN_ID: \${{ needs.release-source-production.outputs.candidate_run_id }}
|
|
1838
2183
|
EBK_RELEASE_SOURCE_COMMIT: \${{ needs.release-source-production.outputs.source_commit }}
|
|
@@ -1933,11 +2278,13 @@ ${stagingReleaseCandidateDownloadSteps(downloadArtifactAction)} - name: Res
|
|
|
1933
2278
|
cp "$source_sidecar" "$target"
|
|
1934
2279
|
done
|
|
1935
2280
|
- uses: ${configureAwsAction}
|
|
2281
|
+
if: env.EBK_DEPLOYMENT_PROFILE != 'starter'
|
|
1936
2282
|
with:
|
|
1937
2283
|
role-to-assume: \${{ secrets.AWS_STAGING_PROMOTION_PULL_ROLE_ARN }}
|
|
1938
2284
|
role-session-name: ebk-staging-container-pull-\${{ github.run_id }}-\${{ github.run_attempt }}
|
|
1939
2285
|
aws-region: \${{ vars.AWS_STAGING_PROMOTION_REGION }}
|
|
1940
2286
|
- name: Authenticate exact staging source registry
|
|
2287
|
+
if: env.EBK_DEPLOYMENT_PROFILE != 'starter'
|
|
1941
2288
|
env:
|
|
1942
2289
|
EBK_STAGING_ACCOUNT_ID: \${{ vars.AWS_STAGING_PROMOTION_EXPECTED_ACCOUNT_ID }}
|
|
1943
2290
|
EBK_STAGING_REGION: \${{ vars.AWS_STAGING_PROMOTION_REGION }}
|
|
@@ -1956,10 +2303,27 @@ ${stagingReleaseCandidateDownloadSteps(downloadArtifactAction)} - name: Res
|
|
|
1956
2303
|
role-to-assume: \${{ secrets.AWS_DEPLOY_ROLE_ARN }}
|
|
1957
2304
|
role-session-name: ebk-production-container-push-\${{ github.run_id }}-\${{ github.run_attempt }}
|
|
1958
2305
|
aws-region: \${{ vars.AWS_REGION }}
|
|
2306
|
+
- name: Bind production authority to committed deployment topology
|
|
2307
|
+
run: |
|
|
2308
|
+
test "$AWS_EXPECTED_ACCOUNT_ID" = "$EBK_POLICY_PRODUCTION_ACCOUNT_ID" ||
|
|
2309
|
+
{ echo 'Production environment account differs from committed deployment topology'; exit 1; }
|
|
2310
|
+
test "$AWS_REGION" = "$EBK_POLICY_REGION" ||
|
|
2311
|
+
{ echo 'Production environment Region differs from committed deployment topology'; exit 1; }
|
|
2312
|
+
- name: Authenticate shared-account staging source registry
|
|
2313
|
+
if: env.EBK_DEPLOYMENT_PROFILE == 'starter'
|
|
2314
|
+
run: |
|
|
2315
|
+
test "$EBK_POLICY_STAGING_ACCOUNT_ID" = "$EBK_POLICY_PRODUCTION_ACCOUNT_ID" ||
|
|
2316
|
+
{ echo 'Starter promotion requires one shared AWS account'; exit 1; }
|
|
2317
|
+
registry_suffix=.com
|
|
2318
|
+
case "$EBK_POLICY_REGION" in cn-*) registry_suffix=.com.cn ;; esac
|
|
2319
|
+
aws ecr get-login-password --region "$EBK_POLICY_REGION" |
|
|
2320
|
+
docker login --username AWS --password-stdin \
|
|
2321
|
+
"$EBK_POLICY_STAGING_ACCOUNT_ID.dkr.ecr.$EBK_POLICY_REGION.amazonaws$registry_suffix"
|
|
1959
2322
|
- name: Promote exact staging indexes without rebuilding
|
|
1960
2323
|
env:
|
|
1961
|
-
|
|
1962
|
-
|
|
2324
|
+
EBK_DEPLOYMENT_PROFILE: \${{ needs.plan-production.outputs.deployment_profile }}
|
|
2325
|
+
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 }}
|
|
2326
|
+
EBK_STAGING_PROMOTION_REGION: \${{ needs.plan-production.outputs.deployment_profile == 'starter' && needs.plan-production.outputs.deployment_region || vars.AWS_STAGING_PROMOTION_REGION }}
|
|
1963
2327
|
run: |
|
|
1964
2328
|
test "$(aws sts get-caller-identity --query Account --output text)" = "$AWS_EXPECTED_ACCOUNT_ID" ||
|
|
1965
2329
|
{ echo 'Production promotion role account differs from policy'; exit 1; }
|
|
@@ -2178,6 +2542,8 @@ ${planRoleAuditStep('main', 'verify')}
|
|
|
2178
2542
|
EBK_RELEASE_SOURCE_COMMIT: \${{ needs.release-source-staging.outputs.source_commit }}
|
|
2179
2543
|
EBK_SOURCE_COMMIT: \${{ github.sha }}
|
|
2180
2544
|
EBK_STAGE: staging
|
|
2545
|
+
EBK_POLICY_ACCOUNT_ID: \${{ needs.plan-staging.outputs.staging_account_id }}
|
|
2546
|
+
EBK_POLICY_REGION: \${{ needs.plan-staging.outputs.deployment_region }}
|
|
2181
2547
|
FOUNDATION_PROJECT: \${{ needs.plan-staging.outputs.foundation_project }}
|
|
2182
2548
|
ORDERED_PROJECTS: \${{ needs.plan-staging.outputs.ordered_projects }}
|
|
2183
2549
|
SLICE_PROJECTS: \${{ needs.plan-staging.outputs.slice_projects }}
|
|
@@ -2285,7 +2651,7 @@ ${planRoleAuditStep('develop', 'verify')}
|
|
|
2285
2651
|
packages/*/.ebk/evidence/infrastructure-plan-command.json
|
|
2286
2652
|
|
|
2287
2653
|
deploy-staging:
|
|
2288
|
-
name:
|
|
2654
|
+
name: ${stagingApplyJobPrefix}\${{ needs.infrastructure-plan-staging.outputs.plan_digest || 'recovery-without-resource-plan' }}
|
|
2289
2655
|
needs: [plan-staging, release-source-staging, container-assemble-staging, infrastructure-plan-staging]
|
|
2290
2656
|
if: |
|
|
2291
2657
|
always() &&
|
|
@@ -2364,6 +2730,10 @@ ${stagingReleaseCandidateDownloadSteps(downloadArtifactAction)} - name: Res
|
|
|
2364
2730
|
test -n "$AWS_DEPLOY_ROLE_ARN" || { echo 'staging AWS_DEPLOY_ROLE_ARN is required'; exit 1; }
|
|
2365
2731
|
test -n "$AWS_EXPECTED_ACCOUNT_ID" || { echo 'staging AWS_EXPECTED_ACCOUNT_ID is required'; exit 1; }
|
|
2366
2732
|
test -n "$AWS_REGION" || { echo 'staging AWS_REGION is required'; exit 1; }
|
|
2733
|
+
test "$AWS_EXPECTED_ACCOUNT_ID" = "$EBK_POLICY_ACCOUNT_ID" ||
|
|
2734
|
+
{ echo 'staging environment account differs from committed deployment topology'; exit 1; }
|
|
2735
|
+
test "$AWS_REGION" = "$EBK_POLICY_REGION" ||
|
|
2736
|
+
{ echo 'staging environment Region differs from committed deployment topology'; exit 1; }
|
|
2367
2737
|
test -n "$EBK_FLEET_ORIGIN" ||
|
|
2368
2738
|
{ echo 'staging environment EBK_FLEET_ORIGIN is required'; exit 1; }
|
|
2369
2739
|
test "$EBK_FLEET_ORIGIN" = "$EBK_PLANNED_FLEET_ORIGIN" ||
|
|
@@ -2375,8 +2745,7 @@ ${stagingReleaseCandidateDownloadSteps(downloadArtifactAction)} - name: Res
|
|
|
2375
2745
|
test -n "$AWS_PLAN_GENERATED_SECRET_ARNS_JSON" &&
|
|
2376
2746
|
test -n "$AWS_PLAN_PROVIDER_REFRESH_POLICY_JSON" ||
|
|
2377
2747
|
{ 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" ||
|
|
2748
|
+
test -n "$AWS_PLAN_ROLE_ARN" ||
|
|
2380
2749
|
{ echo 'staging exact plan-authority inputs are required'; exit 1; }
|
|
2381
2750
|
case "$AWS_DEPLOY_ROLE_ARN" in
|
|
2382
2751
|
arn:aws:iam::$AWS_EXPECTED_ACCOUNT_ID:role/*) ;;
|
|
@@ -3000,6 +3369,8 @@ ${stagingReleaseCandidateDownloadSteps(downloadArtifactAction)} - name: Res
|
|
|
3000
3369
|
EBK_APPROVED_MAIN_COMMIT: \${{ github.sha }}
|
|
3001
3370
|
EBK_SOURCE_COMMIT: \${{ github.sha }}
|
|
3002
3371
|
EBK_STAGE: production
|
|
3372
|
+
EBK_POLICY_ACCOUNT_ID: \${{ needs.plan-production.outputs.production_account_id }}
|
|
3373
|
+
EBK_POLICY_REGION: \${{ needs.plan-production.outputs.deployment_region }}
|
|
3003
3374
|
EBK_VERIFIED_STAGING_RUN_ID: \${{ needs.release-source-production.outputs.verified_staging_run_id }}
|
|
3004
3375
|
FOUNDATION_PROJECT: \${{ needs.plan-production.outputs.foundation_project }}
|
|
3005
3376
|
ORDERED_PROJECTS: \${{ needs.plan-production.outputs.ordered_projects }}
|
|
@@ -3096,6 +3467,10 @@ ${stagingReleaseCandidateDownloadSteps(downloadArtifactAction)} - name: Res
|
|
|
3096
3467
|
test -n "$AWS_DEPLOY_ROLE_ARN" || { echo 'production AWS_DEPLOY_ROLE_ARN is required'; exit 1; }
|
|
3097
3468
|
test -n "$AWS_EXPECTED_ACCOUNT_ID" || { echo 'production AWS_EXPECTED_ACCOUNT_ID is required'; exit 1; }
|
|
3098
3469
|
test -n "$AWS_REGION" || { echo 'production AWS_REGION is required'; exit 1; }
|
|
3470
|
+
test "$AWS_EXPECTED_ACCOUNT_ID" = "$EBK_POLICY_ACCOUNT_ID" ||
|
|
3471
|
+
{ echo 'production environment account differs from committed deployment topology'; exit 1; }
|
|
3472
|
+
test "$AWS_REGION" = "$EBK_POLICY_REGION" ||
|
|
3473
|
+
{ echo 'production environment Region differs from committed deployment topology'; exit 1; }
|
|
3099
3474
|
test -n "$EBK_FLEET_ORIGIN" ||
|
|
3100
3475
|
{ echo 'production environment EBK_FLEET_ORIGIN is required'; exit 1; }
|
|
3101
3476
|
test "$EBK_FLEET_ORIGIN" = "$EBK_PLANNED_FLEET_ORIGIN" ||
|
|
@@ -3107,8 +3482,7 @@ ${stagingReleaseCandidateDownloadSteps(downloadArtifactAction)} - name: Res
|
|
|
3107
3482
|
test -n "$AWS_PLAN_GENERATED_SECRET_ARNS_JSON" &&
|
|
3108
3483
|
test -n "$AWS_PLAN_PROVIDER_REFRESH_POLICY_JSON" ||
|
|
3109
3484
|
{ 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" ||
|
|
3485
|
+
test -n "$AWS_PLAN_ROLE_ARN" ||
|
|
3112
3486
|
{ echo 'production exact plan-authority inputs are required'; exit 1; }
|
|
3113
3487
|
case "$AWS_DEPLOY_ROLE_ARN" in
|
|
3114
3488
|
arn:aws:iam::$AWS_EXPECTED_ACCOUNT_ID:role/*) ;;
|
|
@@ -4762,13 +5136,51 @@ permissions:
|
|
|
4762
5136
|
contents: read
|
|
4763
5137
|
|
|
4764
5138
|
jobs:
|
|
5139
|
+
deployment-classification:
|
|
5140
|
+
name: classify exact staging deployment
|
|
5141
|
+
if: >-
|
|
5142
|
+
github.event_name == 'workflow_run' &&
|
|
5143
|
+
github.event.workflow_run.event == 'push' &&
|
|
5144
|
+
github.event.workflow_run.head_branch == 'develop' &&
|
|
5145
|
+
github.event.workflow_run.conclusion == 'success'
|
|
5146
|
+
permissions:
|
|
5147
|
+
actions: read
|
|
5148
|
+
contents: read
|
|
5149
|
+
outputs:
|
|
5150
|
+
deployed: \${{ steps.classify.outputs.deployed }}
|
|
5151
|
+
runs-on: ubuntu-latest
|
|
5152
|
+
steps:
|
|
5153
|
+
- id: classify
|
|
5154
|
+
name: Require one successful staging deployment job
|
|
5155
|
+
env:
|
|
5156
|
+
EBK_DEPLOYMENT_RUN_ID: \${{ github.event.workflow_run.id }}
|
|
5157
|
+
GH_TOKEN: \${{ github.token }}
|
|
5158
|
+
GH_REPO: \${{ github.repository }}
|
|
5159
|
+
run: |
|
|
5160
|
+
jobs="$(gh api \
|
|
5161
|
+
"/repos/$GH_REPO/actions/runs/$EBK_DEPLOYMENT_RUN_ID/jobs?filter=latest&per_page=100")"
|
|
5162
|
+
apply_count="$(printf '%s' "$jobs" | jq \
|
|
5163
|
+
'[.jobs[] | select(.name | startswith("${stagingApplyJobPrefix}")) | select(.conclusion == "success")] | length')"
|
|
5164
|
+
route_success_count="$(printf '%s' "$jobs" | jq \
|
|
5165
|
+
'[.jobs[] | select(.name == "${protectedDeploymentRouteJobName}" and .conclusion == "success")] | length')"
|
|
5166
|
+
route_skipped_count="$(printf '%s' "$jobs" | jq \
|
|
5167
|
+
'[.jobs[] | select(.name == "${protectedDeploymentRouteJobName}" and .conclusion == "skipped")] | length')"
|
|
5168
|
+
case "$apply_count:$route_success_count:$route_skipped_count" in
|
|
5169
|
+
1:1:0) echo 'deployed=true' >> "$GITHUB_OUTPUT" ;;
|
|
5170
|
+
0:0:1) echo 'deployed=false' >> "$GITHUB_OUTPUT" ;;
|
|
5171
|
+
*) echo 'Deploy route and staging application evidence disagree'; exit 1 ;;
|
|
5172
|
+
esac
|
|
5173
|
+
|
|
4765
5174
|
plan:
|
|
5175
|
+
needs: deployment-classification
|
|
4766
5176
|
if: >-
|
|
4767
|
-
(
|
|
5177
|
+
always() &&
|
|
5178
|
+
((github.event_name == 'workflow_run' &&
|
|
4768
5179
|
github.event.workflow_run.conclusion == 'success' &&
|
|
4769
5180
|
github.event.workflow_run.event == 'push' &&
|
|
4770
|
-
github.event.workflow_run.head_branch == 'develop'
|
|
4771
|
-
|
|
5181
|
+
github.event.workflow_run.head_branch == 'develop' &&
|
|
5182
|
+
needs.deployment-classification.outputs.deployed == 'true') ||
|
|
5183
|
+
github.event_name == 'workflow_dispatch')
|
|
4772
5184
|
outputs:
|
|
4773
5185
|
deployment_run_attempt: \${{ steps.identity.outputs.deployment_run_attempt }}
|
|
4774
5186
|
deployment_run_id: \${{ steps.identity.outputs.deployment_run_id }}
|
|
@@ -4875,9 +5287,10 @@ jobs:
|
|
|
4875
5287
|
EBK_SMOKE_AUTHORITY_VERSION: \${{ vars.EBK_SMOKE_AUTHORITY_VERSION }}
|
|
4876
5288
|
EBK_SMOKE_TARGETS: \${{ vars.EBK_SMOKE_TARGETS }}
|
|
4877
5289
|
EBK_SOURCE_COMMIT: \${{ needs.plan.outputs.source_commit }}
|
|
4878
|
-
PLAYWRIGHT_BROWSERS_PATH: \${{ runner.temp }}/ebk-playwright-browsers
|
|
4879
5290
|
runs-on: ubuntu-latest
|
|
4880
5291
|
steps:
|
|
5292
|
+
- name: Initialize private browser cache path
|
|
5293
|
+
run: echo "PLAYWRIGHT_BROWSERS_PATH=$RUNNER_TEMP/ebk-playwright-browsers" >> "$GITHUB_ENV"
|
|
4881
5294
|
- name: Validate credential-free preparation
|
|
4882
5295
|
run: |
|
|
4883
5296
|
test -n "$AWS_EXPECTED_ACCOUNT_ID" || { echo 'AWS_EXPECTED_ACCOUNT_ID is required'; exit 1; }
|
|
@@ -5261,14 +5674,14 @@ jobs:
|
|
|
5261
5674
|
test "$(node --version | cut -d. -f1)" = v22
|
|
5262
5675
|
- name: Run exact deployed smoke
|
|
5263
5676
|
working-directory: \${{ runner.temp }}/ebk-prepared-workspace
|
|
5264
|
-
run: node node_modules/nx/bin/nx.js run "$EBK_PROJECT:smoke-deployed"
|
|
5677
|
+
run: node node_modules/nx/dist/bin/nx.js run "$EBK_PROJECT:smoke-deployed"
|
|
5265
5678
|
- name: Verify reviewed frontend visual baseline
|
|
5266
5679
|
working-directory: \${{ runner.temp }}/ebk-prepared-workspace
|
|
5267
|
-
run: node node_modules/nx/bin/nx.js run "$EBK_PROJECT:frontend-visual"
|
|
5680
|
+
run: node node_modules/nx/dist/bin/nx.js run "$EBK_PROJECT:frontend-visual"
|
|
5268
5681
|
- name: Verify deployed frontend security headers
|
|
5269
5682
|
working-directory: \${{ runner.temp }}/ebk-prepared-workspace
|
|
5270
5683
|
run: |
|
|
5271
|
-
node node_modules/nx/bin/nx.js run \
|
|
5684
|
+
node node_modules/nx/dist/bin/nx.js run \
|
|
5272
5685
|
"$EBK_PROJECT:frontend-security-headers" -- \
|
|
5273
5686
|
--url="$EBK_SMOKE_URL"
|
|
5274
5687
|
- name: Bind exact alarm and data-integrity evidence
|
|
@@ -6715,11 +7128,11 @@ jobs:
|
|
|
6715
7128
|
if [ "$EBK_ACTION" = apply ]; then args+=("--apply"); fi
|
|
6716
7129
|
pnpm nx run "$EBK_PROJECT:secret-metadata" -- "\${args[@]}"
|
|
6717
7130
|
`,
|
|
6718
|
-
'.github/workflows/sync-back.yml': `name: Sync
|
|
7131
|
+
'.github/workflows/sync-back.yml': `name: Sync protected main history
|
|
6719
7132
|
|
|
6720
7133
|
on:
|
|
6721
7134
|
workflow_run:
|
|
6722
|
-
workflows: [
|
|
7135
|
+
workflows: [Verify]
|
|
6723
7136
|
types: [completed]
|
|
6724
7137
|
|
|
6725
7138
|
permissions:
|
|
@@ -6730,6 +7143,7 @@ jobs:
|
|
|
6730
7143
|
sync-back:
|
|
6731
7144
|
if: >-
|
|
6732
7145
|
github.event.workflow_run.conclusion == 'success' &&
|
|
7146
|
+
github.event.workflow_run.event == 'push' &&
|
|
6733
7147
|
github.event.workflow_run.head_branch == 'main'
|
|
6734
7148
|
runs-on: ubuntu-latest
|
|
6735
7149
|
steps:
|
|
@@ -6743,8 +7157,8 @@ jobs:
|
|
|
6743
7157
|
gh pr create \
|
|
6744
7158
|
--head main \
|
|
6745
7159
|
--base develop \
|
|
6746
|
-
--title 'chore: sync
|
|
6747
|
-
--body 'Managed EBK sync
|
|
7160
|
+
--title 'chore(git): sync protected main to develop' \
|
|
7161
|
+
--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
7162
|
number=$(gh pr list --state open --head main --base develop --json number --jq '.[0].number')
|
|
6749
7163
|
fi
|
|
6750
7164
|
echo "Sync-back pull request #$number is ready for review."
|
|
@@ -6989,6 +7403,8 @@ dist
|
|
|
6989
7403
|
.open-next
|
|
6990
7404
|
.sst
|
|
6991
7405
|
**/next-env.d.ts
|
|
7406
|
+
**/api/generated/*.json
|
|
7407
|
+
**/database/migrations/**
|
|
6992
7408
|
pnpm-lock.yaml
|
|
6993
7409
|
tools/blueprint-trust-check.mjs
|
|
6994
7410
|
`,
|
|
@@ -7096,6 +7512,16 @@ use the direct installed \`.bin/ebk blueprint verification-review\` entry docume
|
|
|
7096
7512
|
\`docs/blueprints.md\`.
|
|
7097
7513
|
|
|
7098
7514
|
Workspace creation only writes source files. It does not create or change cloud resources.
|
|
7515
|
+
|
|
7516
|
+
New workspaces default to the one-account \`starter\` deployment profile. Configure the exact
|
|
7517
|
+
shared account and Region in \`ebk.repository-policy.json\`, authenticate through IAM Identity
|
|
7518
|
+
Center, add exact development and staging CIDRs to
|
|
7519
|
+
\`packages/foundation/security/router-waf.json\`, and use
|
|
7520
|
+
\`pnpm nx run ${name}:deploy-local -- --stage=development
|
|
7521
|
+
--profile=<profile> --expectedRoleArn=<role-arn>\`. Never invoke SST directly.
|
|
7522
|
+
For a brand-new stage with generated Product Slices, complete the documented workload-role
|
|
7523
|
+
Foundation publication and pass \`--phase=foundation-bootstrap\` once before publishing and
|
|
7524
|
+
deploying the complete stage.
|
|
7099
7525
|
`,
|
|
7100
7526
|
'docs/architecture.md': `# Workspace architecture
|
|
7101
7527
|
|
|
@@ -7112,6 +7538,11 @@ Foundation implementation files.
|
|
|
7112
7538
|
Canonical consumer stages use the matching \`development\`, \`staging\`, or \`production\`
|
|
7113
7539
|
Foundation. Framework-derived personal stages may use only the \`development\`
|
|
7114
7540
|
Foundation; generic preview stages are unsupported.
|
|
7541
|
+
|
|
7542
|
+
The committed deployment profile changes account and local/GitHub authority only. Every
|
|
7543
|
+
canonical stage always owns a separate Router/domain, Aurora cluster and migration history,
|
|
7544
|
+
Cognito user pool, event bus, secret namespace, IAM boundary, logs, queues, ECR namespace, and
|
|
7545
|
+
stage-qualified SSM discovery contract. Discovery rejects a record for another canonical stage.
|
|
7115
7546
|
`,
|
|
7116
7547
|
'docs/git-workflow.md': `# Protected Git workflow
|
|
7117
7548
|
|
|
@@ -7132,6 +7563,9 @@ GitHub rulesets, environments, and AWS IAM are the authorization boundaries; loc
|
|
|
7132
7563
|
only defense in depth.
|
|
7133
7564
|
|
|
7134
7565
|
The \`staging\` environment accepts only \`develop\`; \`production\` accepts only \`main\`.
|
|
7566
|
+
These workflows exist for every deployment profile. The staging workflow is optional for
|
|
7567
|
+
\`starter\`, whose governed local Nx target may deploy development and staging. Production is
|
|
7568
|
+
GitHub-only for every profile; \`fully-isolated\` also requires GitHub for staging.
|
|
7135
7569
|
The committed \`productionApproval.mode\` selects one of two production authorization
|
|
7136
7570
|
contracts:
|
|
7137
7571
|
|
|
@@ -7154,14 +7588,22 @@ contracts:
|
|
|
7154
7588
|
Both modes retain required checks, a non-author CODEOWNER approval, no ruleset bypass, the
|
|
7155
7589
|
literal production environment, and its branch-scoped credentials. The base \`staging\` and
|
|
7156
7590
|
\`production\` environments own deployment, smoke, and ECR evidence roles plus their
|
|
7157
|
-
variables;
|
|
7591
|
+
variables; isolated profiles also keep the staging-registry promotion pull role in production.
|
|
7592
|
+
Starter promotion authenticates once to the shared account and copies the exact staging digest
|
|
7593
|
+
into a distinct production ECR repository. Dedicated
|
|
7158
7594
|
\`infrastructure-plan-<stage>\` environments gate read-only role use. Separate
|
|
7159
7595
|
\`async-recovery-<stage>\`, \`performance-aggregate-<stage>\`,
|
|
7160
7596
|
\`performance-cost-<stage>\`, \`performance-load-<stage>\`, and
|
|
7161
7597
|
\`reliability-read-<stage>\` environments own only the role and variables required by that
|
|
7162
7598
|
capability. Infrastructure-plan jobs use a repository-variable read-only role/input inventory;
|
|
7163
7599
|
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.
|
|
7600
|
+
trust and complete inline policy before any repository-backed plan job may assume it. Plan-role
|
|
7601
|
+
trust uses GitHub's immutable repository identity subject,
|
|
7602
|
+
\`repo:<owner>@<owner-id>/<repository>@<repository-id>:environment:<environment>\`.
|
|
7603
|
+
The audit derives it from the workflow's repository name, owner ID, repository ID, and literal
|
|
7604
|
+
environment and refuses missing IDs or the older mutable name-only subject. Update every
|
|
7605
|
+
adopter-owned AWS OIDC role trust to the immutable identity before protected use; never retain
|
|
7606
|
+
both forms. Every
|
|
7165
7607
|
\`*-staging\` environment accepts only \`develop\`; every
|
|
7166
7608
|
\`*-production\` environment accepts only \`main\`; reviewer, self-review, and wait protection
|
|
7167
7609
|
matches the corresponding base stage. The credential-free \`npm-prerelease\` environment
|
|
@@ -7303,18 +7745,35 @@ materializes, close and reopen the pull request as a human and then approve it.
|
|
|
7303
7745
|
`,
|
|
7304
7746
|
'docs/repository-operating-policy.md': `# Repository Operating Policy
|
|
7305
7747
|
|
|
7306
|
-
\`ebk.repository-policy.json\` is the reviewed source of truth for
|
|
7307
|
-
|
|
7748
|
+
\`ebk.repository-policy.json\` is the reviewed source of truth for deployment profile, exact
|
|
7749
|
+
stage account IDs, deployment Region, production access, production approval, per-Slice
|
|
7750
|
+
readiness, and the one shared production Foundation profile.
|
|
7308
7751
|
New workspaces start with production disabled and explicit Team-compatible
|
|
7309
7752
|
\`productionApproval.mode: "promotion-pr"\`. A missing approval mode or Slice entry fails
|
|
7310
7753
|
closed; no GitHub capability is allowed to choose a mode implicitly.
|
|
7311
7754
|
|
|
7755
|
+
Deployment profiles are proportional to operator maturity:
|
|
7756
|
+
|
|
7757
|
+
- \`starter\` (default) requires one shared account, permits local development and staging
|
|
7758
|
+
through the governed root Nx target, and reserves production for protected GitHub deployment;
|
|
7759
|
+
- \`production-isolated\` requires one development/staging account and a distinct production
|
|
7760
|
+
account; only production is GitHub-only; and
|
|
7761
|
+
- \`fully-isolated\` requires three distinct accounts; staging and production are GitHub-only.
|
|
7762
|
+
|
|
7763
|
+
Equal account IDs never imply shared stage resources. Router, Aurora, Cognito, events, secrets,
|
|
7764
|
+
IAM, logs, queues, resource names, and SSM discovery remain stage-specific. Existing prerelease
|
|
7765
|
+
repositories migrate explicitly to \`fully-isolated\`; upgrades never infer or weaken topology.
|
|
7766
|
+
That profile-only migration retains the former authority model but stays undeployable until exact
|
|
7767
|
+
development, staging, and production account IDs plus \`deploymentRegion\` are committed.
|
|
7768
|
+
|
|
7312
7769
|
The named repository modes are source-writing presets, not runtime state. Report a change
|
|
7313
7770
|
first and inspect the complete current/next policy:
|
|
7314
7771
|
|
|
7315
7772
|
\`\`\`bash
|
|
7316
7773
|
pnpm nx g @empire-builder-kit/nx:repository-policy \\
|
|
7317
|
-
--
|
|
7774
|
+
--deploymentProfile=starter --deploymentRegion=us-east-1 \\
|
|
7775
|
+
--developmentAccountId=111122223333 --stagingAccountId=111122223333 \\
|
|
7776
|
+
--productionAccountId=111122223333
|
|
7318
7777
|
\`\`\`
|
|
7319
7778
|
|
|
7320
7779
|
Apply the reviewed source change separately with \`--apply\`. This command never contacts AWS
|
|
@@ -7324,6 +7783,66 @@ Use \`--productionApprovalMode=promotion-pr|environment-reviewer\` for an explic
|
|
|
7324
7783
|
transition. This changes committed policy only; a separate bootstrap report/apply reconciles
|
|
7325
7784
|
remote GitHub settings.
|
|
7326
7785
|
|
|
7786
|
+
After applying exact topology, authenticate with temporary federated credentials for development
|
|
7787
|
+
or staging and invoke
|
|
7788
|
+
\`pnpm nx run ${name}:deploy-local -- --stage=<stage> --profile=<profile>
|
|
7789
|
+
--expectedRoleArn=<role-arn>\`. Production runs only through the protected main-branch GitHub
|
|
7790
|
+
workflow. Each admitted local invocation plans the complete current stage, displays the stage
|
|
7791
|
+
summary and first material project, and requires a real TTY response of
|
|
7792
|
+
\`approve:<action>:<stage>:<project>:<current-plan-digest>\`. A human or authorized agent may type
|
|
7793
|
+
that acknowledgement; it is active operator control, not a claim of independent human review.
|
|
7794
|
+
The target creates one private ten-minute capability for that project, rechecks the exact source,
|
|
7795
|
+
artifacts, account, Region, role, stage, policy, state, and lock without a second provider preview,
|
|
7796
|
+
consumes the capability once, and removes it on every exit path. Unattended scripts cannot approve.
|
|
7797
|
+
Every material deploy or refresh mutates at most the first material project and ends with a
|
|
7798
|
+
controlled non-success \`replan-required\` handoff, including the last project in the ordered stage.
|
|
7799
|
+
Rerun the complete stage, review the new current summary, and repeat until one invocation proves
|
|
7800
|
+
every project is semantically converged. Only that convergence invocation succeeds. A preview is
|
|
7801
|
+
recent review and audit evidence; it is not represented as a cryptographically replayable Pulumi
|
|
7802
|
+
transaction. A complete, unlocked deploy plan containing only provider reads with no changed
|
|
7803
|
+
properties, replacement, failure, or unmatched risk finding is retained as observation evidence
|
|
7804
|
+
and counts as semantically converged. The standalone project planner may still return its
|
|
7805
|
+
conservative non-success for that drift observation; the root local transaction accepts only a
|
|
7806
|
+
freshly emitted exact provider-read-only record from that child. Repeated reads coalesce
|
|
7807
|
+
only when their logical ID and resource type match exactly. Any stale or ambiguous
|
|
7808
|
+
observation fails closed; a plan with desired changes remains material and requires review. One
|
|
7809
|
+
narrower case stays reviewable instead of becoming
|
|
7810
|
+
drift: when an upstream Foundation apply changes only both external framework-version tag
|
|
7811
|
+
projections (plus SST's already-bounded name hydration), the retained
|
|
7812
|
+
external identity must match one exact non-replacing provider read in the same ordinary plan.
|
|
7813
|
+
Missing read correlation, owned resources, other tag or property changes, incomplete tag pairs,
|
|
7814
|
+
and provider failures remain drift and fail closed. The local target requires the exact SST v5 account bootstrap normally
|
|
7815
|
+
created by the pinned \`ebk dev up\` path, then creates and attests only missing exact
|
|
7816
|
+
per-project/canonical-stage state passphrases before its formal read-only plans. This passphrase
|
|
7817
|
+
bootstrap is the only persistent pre-acknowledgement write; it creates no deployment authority
|
|
7818
|
+
and is reported in bounded local evidence. Clean immutable source, immediate per-project plans,
|
|
7819
|
+
migration and retention preflights, and secret-free result
|
|
7820
|
+
evidence remain mandatory. Direct SST is forbidden.
|
|
7821
|
+
|
|
7822
|
+
After an interrupted apply, use the same target with \`--action=refresh\` and the exact
|
|
7823
|
+
phase, stage, profile, and role. It retains the same plan-summary review boundary, admits only
|
|
7824
|
+
bounded drift-detected refresh evidence, and never turns refresh into an implicit deploy. When
|
|
7825
|
+
the interrupted write leaves a recorded provider output unusable by the ordinary desired preview,
|
|
7826
|
+
the plan stops without mutation. The non-writing refresh preview separates provider refresh
|
|
7827
|
+
records from its later desired preview and cannot replace failed ordinary desired authority;
|
|
7828
|
+
repair the recorded dependency or use the documented
|
|
7829
|
+
operator recovery path before planning again.
|
|
7830
|
+
|
|
7831
|
+
A stage-wide refresh still plans and authorizes every selected project. When one exact retained
|
|
7832
|
+
project plan proves an absent SST state that is unlocked, complete, empty, and drift-free, the
|
|
7833
|
+
root target records that project as a refresh no-op instead of invoking SST against a nonexistent
|
|
7834
|
+
stage. It also retains the pinned provider's exact SSM \`ebk:framework-version\` \`tagsAll\`
|
|
7835
|
+
normalization as a semantically inert observation after the same bounded shape
|
|
7836
|
+
repeats following refresh. Value changes, other tags, security findings, deletes, replacements,
|
|
7837
|
+
sensitive properties, and every other resource type remain material. Locked, incomplete, failed,
|
|
7838
|
+
or otherwise drifted plans are never skipped.
|
|
7839
|
+
|
|
7840
|
+
For a stage with no workload-role account baseline, publish the fixed Foundation-only candidate
|
|
7841
|
+
and invoke this root target with \`--phase=foundation-bootstrap\`. After Foundation outputs exist,
|
|
7842
|
+
publish the complete \`full-stage\` candidate and invoke the target without \`--phase\` (or with
|
|
7843
|
+
\`--phase=full-stage\`). The bootstrap phase selects exactly Foundation; it is not an arbitrary
|
|
7844
|
+
project filter.
|
|
7845
|
+
|
|
7327
7846
|
Validation requires both an explicit production approval mode and an explicit Foundation
|
|
7328
7847
|
profile. It does not infer omitted values. Use \`promotion-pr\` for a private GitHub Team
|
|
7329
7848
|
repository. Use \`environment-reviewer\` only when the repository plan can enforce a separate
|
|
@@ -7337,10 +7856,16 @@ environment reviewer.
|
|
|
7337
7856
|
|
|
7338
7857
|
Protected production deploy and refresh validate the complete Foundation/Slice set before AWS
|
|
7339
7858
|
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
|
-
|
|
7859
|
+
cannot be narrowed with an ad hoc project argument. The retained stage preview remains review
|
|
7860
|
+
evidence. Immediately before each project mutation the workflow computes one fresh plan against
|
|
7861
|
+
current state and applies it without a second redundant preview. Source, artifacts, configuration,
|
|
7862
|
+
policy, environment, and protected identities remain exact. Provider-only read/discard observations
|
|
7863
|
+
may appear or disappear, but any new delete, replacement, import, stateful or security change, IAM
|
|
7864
|
+
expansion, protection reduction, or unclassified resource stops for renewed approval. After an
|
|
7865
|
+
upstream mutation, every downstream project therefore receives a fresh independent risk decision.
|
|
7866
|
+
|
|
7867
|
+
\`standard\` keeps Aurora warm at 2–16 ACU. \`cost-optimized\` keeps a smaller
|
|
7868
|
+
0.5–4 ACU warm floor for starter production traffic. Both keep managed NAT,
|
|
7344
7869
|
30-day backups, deletion protection, retained data, and the protected GitHub/OIDC/AWS path.
|
|
7345
7870
|
Changing policy affects only later lifecycle requests; it never deploys or removes resources.
|
|
7346
7871
|
Changing profile requires a separate reviewed Foundation deployment and retained operational
|
|
@@ -7411,7 +7936,7 @@ deploy or refresh before AWS access. Protected remove and unlock remain usable f
|
|
|
7411
7936
|
There is no local or silent bypass in this prerelease contract; any future waiver mechanism
|
|
7412
7937
|
must be committed, reviewable, and auditable.
|
|
7413
7938
|
`,
|
|
7414
|
-
'ebk.repository-policy.json': json(
|
|
7939
|
+
'ebk.repository-policy.json': json(initialRepositoryPolicy),
|
|
7415
7940
|
'nx.json': json({
|
|
7416
7941
|
$schema: './node_modules/nx/schemas/nx-schema.json',
|
|
7417
7942
|
defaultBase: 'develop',
|
|
@@ -7451,6 +7976,7 @@ must be committed, reviewable, and auditable.
|
|
|
7451
7976
|
inputs: ['default', '^production'],
|
|
7452
7977
|
},
|
|
7453
7978
|
},
|
|
7979
|
+
analytics: false,
|
|
7454
7980
|
neverConnectToCloud: true,
|
|
7455
7981
|
useDaemonProcess: false,
|
|
7456
7982
|
}),
|
|
@@ -7460,8 +7986,8 @@ must be committed, reviewable, and auditable.
|
|
|
7460
7986
|
private: true,
|
|
7461
7987
|
packageManager: tool_versions_js_1.WORKSPACE_PNPM_DESCRIPTOR,
|
|
7462
7988
|
engines: {
|
|
7463
|
-
node: '>=22.
|
|
7464
|
-
pnpm: '>=
|
|
7989
|
+
node: '>=22.13.0',
|
|
7990
|
+
pnpm: '>=11.20.0',
|
|
7465
7991
|
},
|
|
7466
7992
|
scripts: {
|
|
7467
7993
|
build: `nx run-many -t build --exclude=${name}`,
|
|
@@ -7479,10 +8005,12 @@ must be committed, reviewable, and auditable.
|
|
|
7479
8005
|
},
|
|
7480
8006
|
devDependencies: {
|
|
7481
8007
|
'@aws-sdk/client-iam': TOOL_VERSIONS.awsSdk,
|
|
8008
|
+
'@aws-sdk/client-rds': TOOL_VERSIONS.awsSdk,
|
|
7482
8009
|
'@aws-sdk/client-s3': TOOL_VERSIONS.awsSdk,
|
|
7483
8010
|
'@aws-sdk/client-secrets-manager': TOOL_VERSIONS.awsSdk,
|
|
7484
8011
|
'@aws-sdk/client-ssm': TOOL_VERSIONS.awsSdk,
|
|
7485
8012
|
'@aws-sdk/client-sts': TOOL_VERSIONS.awsSdk,
|
|
8013
|
+
'@aws-sdk/credential-providers': TOOL_VERSIONS.awsSdk,
|
|
7486
8014
|
'@eslint/js': TOOL_VERSIONS.eslintJs,
|
|
7487
8015
|
'@empire-builder-kit/nx': frameworkVersion,
|
|
7488
8016
|
'@types/node': TOOL_VERSIONS.nodeTypes,
|
|
@@ -7515,6 +8043,15 @@ must be committed, reviewable, and auditable.
|
|
|
7515
8043
|
protectedRef: 'refs/remotes/origin/develop',
|
|
7516
8044
|
},
|
|
7517
8045
|
},
|
|
8046
|
+
'blueprint-trust-check-main': {
|
|
8047
|
+
executor: '@empire-builder-kit/nx:blueprint-trust',
|
|
8048
|
+
options: {
|
|
8049
|
+
operation: 'adopt',
|
|
8050
|
+
output: '.ebk/evidence/supply-chain/blueprint-trust.json',
|
|
8051
|
+
protectedBranch: 'main',
|
|
8052
|
+
protectedRef: 'refs/remotes/origin/main',
|
|
8053
|
+
},
|
|
8054
|
+
},
|
|
7518
8055
|
'blueprint-trust-check-protected': {
|
|
7519
8056
|
executor: '@empire-builder-kit/nx:blueprint-trust',
|
|
7520
8057
|
options: {
|
|
@@ -7557,6 +8094,13 @@ must be committed, reviewable, and auditable.
|
|
|
7557
8094
|
executor: 'nx:run-commands',
|
|
7558
8095
|
options: { command: 'node tools/install-ebk-hooks.mjs' },
|
|
7559
8096
|
},
|
|
8097
|
+
'deploy-local': {
|
|
8098
|
+
cache: false,
|
|
8099
|
+
executor: '@empire-builder-kit/nx:local-deploy',
|
|
8100
|
+
options: {
|
|
8101
|
+
stage: 'development',
|
|
8102
|
+
},
|
|
8103
|
+
},
|
|
7560
8104
|
'supply-chain-check': {
|
|
7561
8105
|
executor: 'nx:run-commands',
|
|
7562
8106
|
options: {
|
|
@@ -7598,6 +8142,11 @@ must be committed, reviewable, and auditable.
|
|
|
7598
8142
|
executor: 'nx:run-commands',
|
|
7599
8143
|
options: { command: 'node tools/workspace-health.mjs' },
|
|
7600
8144
|
},
|
|
8145
|
+
'verify-proposed': {
|
|
8146
|
+
dependsOn: ['supply-chain-check'],
|
|
8147
|
+
executor: 'nx:run-commands',
|
|
8148
|
+
options: { command: 'node tools/workspace-health.mjs' },
|
|
8149
|
+
},
|
|
7601
8150
|
'workspace-health': {
|
|
7602
8151
|
executor: 'nx:run-commands',
|
|
7603
8152
|
options: { command: 'node tools/workspace-health.mjs' },
|
|
@@ -7620,6 +8169,10 @@ Workspace creation does not deploy this project. Review \`docs/costs.md\`,
|
|
|
7620
8169
|
|
|
7621
8170
|
Set \`EBK_ROOT_DOMAIN\` before deploying when Route 53 in the target account owns a public
|
|
7622
8171
|
domain. Without it, the shared Router uses its functional provider URL.
|
|
8172
|
+
|
|
8173
|
+
Before deploying development or staging, add at least one exact operator/probe CIDR to
|
|
8174
|
+
\`security/router-waf.json\`. Production is public by default behind the same managed and
|
|
8175
|
+
rate-limiting baseline; select its allowlist mode explicitly when required.
|
|
7623
8176
|
`,
|
|
7624
8177
|
'packages/foundation/contracts/foundation.json': json({
|
|
7625
8178
|
contractVersion: '1.0.0',
|
|
@@ -7679,8 +8232,8 @@ seeded decision record aligned with organization-specific budgets and alerts.
|
|
|
7679
8232
|
- Development uses EC2 NAT, a development bastion, and Aurora scale-to-zero.
|
|
7680
8233
|
- Staging uses EC2 NAT and Aurora scale-to-zero by default.
|
|
7681
8234
|
- Production always uses managed NAT and retains protected data. Its explicit Repository
|
|
7682
|
-
Operating Policy profile selects either the standard
|
|
7683
|
-
|
|
8235
|
+
Operating Policy profile selects either the standard 2-ACU Aurora floor or the
|
|
8236
|
+
cost-optimized 0.5-ACU warm floor.
|
|
7684
8237
|
- ECS, EventBridge, Cognito, SNS, SSM, and CloudWatch are primarily usage-priced; logs,
|
|
7685
8238
|
metrics, alarms, and retained data still require active cost review.
|
|
7686
8239
|
|
|
@@ -7698,8 +8251,69 @@ Use Nx for every supported lifecycle action:
|
|
|
7698
8251
|
- \`pnpm nx run foundation:unlock --stage=development\`
|
|
7699
8252
|
- \`pnpm nx run foundation:remove --stage=development\`
|
|
7700
8253
|
|
|
7701
|
-
Do not invoke SST lifecycle commands directly.
|
|
7702
|
-
|
|
8254
|
+
Do not invoke SST lifecycle commands directly. The root \`deploy-local\` target is the governed
|
|
8255
|
+
local path for stages admitted by the committed deployment profile. Its fixed
|
|
8256
|
+
\`foundation-bootstrap\` phase is the only local partial-stage path and exists solely for the
|
|
8257
|
+
documented first workload-role publication; isolated protected stages remain on the trusted
|
|
8258
|
+
GitHub path.
|
|
8259
|
+
|
|
8260
|
+
Every local invocation plans the complete selected stage but mutates at most one material
|
|
8261
|
+
project. Type the exact current \`approve:<action>:<stage>:<project>:<plan-digest>\` challenge in a
|
|
8262
|
+
real TTY after reviewing the displayed summary. A human or authorized agent may operate that TTY;
|
|
8263
|
+
unattended input is refused. The acknowledgement creates one ten-minute, one-use, private
|
|
8264
|
+
capability bound to source, artifacts, policy, environment, credentials, and current state. A
|
|
8265
|
+
completed deploy or refresh returns the controlled non-success \`replan-required\`; rerun the
|
|
8266
|
+
complete stage, review the new current summary, and continue one project at a time. Only a
|
|
8267
|
+
semantically converged complete stage reports success. No upstream mutation can authorize a
|
|
8268
|
+
downstream project. A complete, unlocked deploy plan containing only provider reads with no
|
|
8269
|
+
changed properties, replacement, failure, or unmatched risk finding is retained as observation
|
|
8270
|
+
evidence and counts as semantically converged. The standalone project planner may still return
|
|
8271
|
+
its conservative non-success for that drift observation; the root local transaction accepts only
|
|
8272
|
+
a freshly emitted exact provider-read-only record from that child. Repeated reads coalesce
|
|
8273
|
+
only when their logical ID and resource type match exactly. Any stale or ambiguous
|
|
8274
|
+
observation fails closed; a plan with desired changes remains material and requires review. A
|
|
8275
|
+
material downstream plan may also retain an upstream Foundation framework-version tag advance as
|
|
8276
|
+
observation only when both external tag
|
|
8277
|
+
projections, the retained identity, optional bounded SST name hydration, and one non-replacing
|
|
8278
|
+
provider read correlate exactly. Every other external property or uncorroborated read remains
|
|
8279
|
+
drift and blocks before the TTY challenge.
|
|
8280
|
+
|
|
8281
|
+
For an interrupted local apply, run the same root target with \`--action=refresh\`, review its
|
|
8282
|
+
current provider-refresh evidence, and type the displayed TTY challenge. Before mutation the
|
|
8283
|
+
executor writes and verifies a private state recovery point plus a secret-free digest/version
|
|
8284
|
+
manifest. The refresh preview validates its provider and later desired-event partitions; any
|
|
8285
|
+
ordinary preview failure or malformed post-refresh desired event remains blocked. Provider-only
|
|
8286
|
+
observation noise is retained without claiming a replayable preview. Because refresh changes
|
|
8287
|
+
recorded state rather than cloud resources, provider-output security and protection findings
|
|
8288
|
+
remain explicit review requirements under the drift decision. Refresh never continues into deploy
|
|
8289
|
+
or claims desired-state convergence: run the governed deploy target afterward and resolve every
|
|
8290
|
+
remaining source-versus-state change. Exclude the \`.private.json\` recovery point from archives.
|
|
8291
|
+
|
|
8292
|
+
The root refresh target includes every selected project in its exact reviewed plan set. It skips
|
|
8293
|
+
the SST child only when that project's retained plan proves an absent, unlocked, complete, empty,
|
|
8294
|
+
and drift-free state, or when the only retained difference is the pinned provider's exact repeated
|
|
8295
|
+
SSM \`ebk:framework-version\` \`tagsAll\` normalization. It records the project as an explicit refresh no-op
|
|
8296
|
+
in either case while retaining the observation. Value changes, other tags, security findings,
|
|
8297
|
+
deletes, replacements, sensitive
|
|
8298
|
+
properties, and every other resource type remain material; every other plan must execute or fail
|
|
8299
|
+
closed.
|
|
8300
|
+
|
|
8301
|
+
## Router WAF
|
|
8302
|
+
|
|
8303
|
+
Each canonical stage owns one global-scope Web ACL attached to that stage's Foundation Router.
|
|
8304
|
+
The committed \`security/router-waf.json\` policy requires AWS Common Rule Set, Known Bad
|
|
8305
|
+
Inputs, Amazon IP Reputation, an IP rate limit, CloudWatch metrics, and sampled requests.
|
|
8306
|
+
Development and staging fail synthesis until their allowlists contain explicit CIDRs.
|
|
8307
|
+
Production is public by default and may be changed to an explicit allowlist.
|
|
8308
|
+
|
|
8309
|
+
For tuning, set a nonproduction stage to \`count\`, deploy and inspect sampled requests, then
|
|
8310
|
+
return staging to \`block\` and run the live allow/deny, managed-rule, rate-limit, metric, and
|
|
8311
|
+
sampled-request probes. Write their bounded, secret-free result to
|
|
8312
|
+
\`security/router-waf-live-evidence.json\`, then run
|
|
8313
|
+
\`pnpm nx run foundation:router-waf-rehearsal\`. The executor seals the exact policy digest
|
|
8314
|
+
into \`router-waf-blocking-rehearsal.json\`. Production synthesis rejects a missing, stale, or
|
|
8315
|
+
count-mode rehearsal. Full request logging is opt-in, blocked-only, retention-bounded, and
|
|
8316
|
+
redacts query strings plus credential-bearing headers.
|
|
7703
8317
|
|
|
7704
8318
|
Foundation stages are canonical. \`ebk dev\` derives a Product Slice personal
|
|
7705
8319
|
stage from the operating-system account and maps that consumer to the
|
|
@@ -7734,6 +8348,12 @@ connects as the Slice's no-password IAM-authenticated migrator user.
|
|
|
7734
8348
|
stage-scoped SSM/secret path and least-privilege IAM.
|
|
7735
8349
|
- Public domains are configured explicitly through \`EBK_ROOT_DOMAIN\`; generated source never
|
|
7736
8350
|
contains credentials or customer domain assumptions.
|
|
8351
|
+
- Every stage's CloudFront Router has its own global Web ACL. The baseline always includes AWS
|
|
8352
|
+
Common Rule Set, Known Bad Inputs, Amazon IP Reputation, rate blocking, CloudWatch metrics,
|
|
8353
|
+
and sampled requests. Nonproduction is allowlisted by default. Production is public by
|
|
8354
|
+
default, but cannot deploy until the exact policy has passed a staging blocking rehearsal.
|
|
8355
|
+
- Full WAF request logging is disabled by default. Enabling it keeps blocked requests only,
|
|
8356
|
+
uses bounded retention, and redacts query strings, cookies, authorization, and API keys.
|
|
7737
8357
|
|
|
7738
8358
|
The managed \`contracts/frontend-security-headers.json\` file owns the transport
|
|
7739
8359
|
minimum: HTTPS in deployed environments, one-year HSTS there, and the common
|
|
@@ -7766,7 +8386,12 @@ declare const $app: {
|
|
|
7766
8386
|
import {
|
|
7767
8387
|
FOUNDATION_SSM_SCHEMA_VERSION,
|
|
7768
8388
|
STANDARD_FOUNDATION_CAPABILITIES,
|
|
8389
|
+
createRouterWafRules,
|
|
8390
|
+
createRouterWafStagePlan,
|
|
8391
|
+
foundationDatabaseEndpointHost,
|
|
7769
8392
|
foundationSsmPath,
|
|
8393
|
+
normalizeRouterWafPolicy,
|
|
8394
|
+
reconcileRouterWafBlockingRehearsal,
|
|
7770
8395
|
type FoundationSsmKey,
|
|
7771
8396
|
} from '@empire-builder-kit/nx/foundation';
|
|
7772
8397
|
import {
|
|
@@ -7776,6 +8401,9 @@ import {
|
|
|
7776
8401
|
} from '@empire-builder-kit/nx/repository-policy';
|
|
7777
8402
|
import { createFoundationAccountCostControlPlan } from '@empire-builder-kit/nx/performance';
|
|
7778
8403
|
import accountCostPolicy from '../performance/account-cost-policy.json';
|
|
8404
|
+
import routerWafBlockingRehearsal from '../security/router-waf-blocking-rehearsal.json';
|
|
8405
|
+
import routerWafLiveEvidence from '../security/router-waf-live-evidence.json';
|
|
8406
|
+
import routerWafPolicy from '../security/router-waf.json';
|
|
7779
8407
|
import type { FoundationWorkloadRolePreflight } from './workload-roles/preflight.js';
|
|
7780
8408
|
|
|
7781
8409
|
function identityDomainPrefix(stage: string, accountId: string): string {
|
|
@@ -7827,6 +8455,18 @@ export function foundation(
|
|
|
7827
8455
|
)
|
|
7828
8456
|
: undefined;
|
|
7829
8457
|
const natMode = productionConfiguration?.contract.nat ?? 'ec2';
|
|
8458
|
+
const normalizedRouterWafPolicy = normalizeRouterWafPolicy(routerWafPolicy);
|
|
8459
|
+
const wafPlan = createRouterWafStagePlan({
|
|
8460
|
+
policy: normalizedRouterWafPolicy,
|
|
8461
|
+
rehearsal: isProduction
|
|
8462
|
+
? reconcileRouterWafBlockingRehearsal({
|
|
8463
|
+
evidence: routerWafLiveEvidence,
|
|
8464
|
+
policy: normalizedRouterWafPolicy,
|
|
8465
|
+
receipt: routerWafBlockingRehearsal,
|
|
8466
|
+
})
|
|
8467
|
+
: null,
|
|
8468
|
+
stage: $app.stage,
|
|
8469
|
+
});
|
|
7830
8470
|
const natBinding = workloadRoles?.bindings.nat;
|
|
7831
8471
|
if (natMode === 'ec2' && natBinding) {
|
|
7832
8472
|
$transform(
|
|
@@ -7836,6 +8476,9 @@ export function foundation(
|
|
|
7836
8476
|
args.name = natBinding.instanceProfileName;
|
|
7837
8477
|
args.role = natBinding.roleName;
|
|
7838
8478
|
opts.import = natBinding.instanceProfileName;
|
|
8479
|
+
opts.ignoreChanges = [
|
|
8480
|
+
...new Set([...(opts.ignoreChanges ?? []), 'tags', 'tagsAll']),
|
|
8481
|
+
];
|
|
7839
8482
|
},
|
|
7840
8483
|
);
|
|
7841
8484
|
}
|
|
@@ -7857,8 +8500,83 @@ export function foundation(
|
|
|
7857
8500
|
aliases: ['*.' + rootDomain],
|
|
7858
8501
|
dns: sst.aws.dns(),
|
|
7859
8502
|
},
|
|
7860
|
-
|
|
8503
|
+
}
|
|
7861
8504
|
: {}),
|
|
8505
|
+
waf: {
|
|
8506
|
+
logging: wafPlan.logging,
|
|
8507
|
+
managedRules: {
|
|
8508
|
+
coreRuleSet: true,
|
|
8509
|
+
knownBadInputs: true,
|
|
8510
|
+
sqlInjection: false,
|
|
8511
|
+
},
|
|
8512
|
+
rateLimitPerIp: wafPlan.rateLimitPerIp,
|
|
8513
|
+
},
|
|
8514
|
+
transform: {
|
|
8515
|
+
cdn(args: any) {
|
|
8516
|
+
if (!args.webAclArn) {
|
|
8517
|
+
throw new Error(
|
|
8518
|
+
'Foundation Router must attach its exact global Web ACL.',
|
|
8519
|
+
);
|
|
8520
|
+
}
|
|
8521
|
+
},
|
|
8522
|
+
waf(args: any, opts: any) {
|
|
8523
|
+
if (args.scope !== 'CLOUDFRONT') {
|
|
8524
|
+
throw new Error(
|
|
8525
|
+
'Foundation Router Web ACL must use global CloudFront scope.',
|
|
8526
|
+
);
|
|
8527
|
+
}
|
|
8528
|
+
const ipSets: { ipv4Arn?: any; ipv6Arn?: any } = {};
|
|
8529
|
+
if (wafPlan.ipv4Cidrs.length > 0) {
|
|
8530
|
+
ipSets.ipv4Arn = new aws.wafv2.IpSet(
|
|
8531
|
+
'FoundationRouterIpv4Allowlist',
|
|
8532
|
+
{
|
|
8533
|
+
addresses: wafPlan.ipv4Cidrs,
|
|
8534
|
+
ipAddressVersion: 'IPV4',
|
|
8535
|
+
name: 'foundation-' + wafPlan.stage + '-router-ipv4-allowlist',
|
|
8536
|
+
scope: 'CLOUDFRONT',
|
|
8537
|
+
tags: {
|
|
8538
|
+
'ebk:policy-digest': wafPlan.policyDigest,
|
|
8539
|
+
'ebk:stage': wafPlan.stage,
|
|
8540
|
+
},
|
|
8541
|
+
},
|
|
8542
|
+
{ provider: opts.provider },
|
|
8543
|
+
).arn;
|
|
8544
|
+
}
|
|
8545
|
+
if (wafPlan.ipv6Cidrs.length > 0) {
|
|
8546
|
+
ipSets.ipv6Arn = new aws.wafv2.IpSet(
|
|
8547
|
+
'FoundationRouterIpv6Allowlist',
|
|
8548
|
+
{
|
|
8549
|
+
addresses: wafPlan.ipv6Cidrs,
|
|
8550
|
+
ipAddressVersion: 'IPV6',
|
|
8551
|
+
name: 'foundation-' + wafPlan.stage + '-router-ipv6-allowlist',
|
|
8552
|
+
scope: 'CLOUDFRONT',
|
|
8553
|
+
tags: {
|
|
8554
|
+
'ebk:policy-digest': wafPlan.policyDigest,
|
|
8555
|
+
'ebk:stage': wafPlan.stage,
|
|
8556
|
+
},
|
|
8557
|
+
},
|
|
8558
|
+
{ provider: opts.provider },
|
|
8559
|
+
).arn;
|
|
8560
|
+
}
|
|
8561
|
+
args.defaultAction = { allow: {} };
|
|
8562
|
+
args.rules = createRouterWafRules(wafPlan, ipSets);
|
|
8563
|
+
args.scope = 'CLOUDFRONT';
|
|
8564
|
+
args.tags = {
|
|
8565
|
+
...(args.tags ?? {}),
|
|
8566
|
+
'ebk:policy-digest': wafPlan.policyDigest,
|
|
8567
|
+
'ebk:stage': wafPlan.stage,
|
|
8568
|
+
};
|
|
8569
|
+
args.visibilityConfig = {
|
|
8570
|
+
cloudwatchMetricsEnabled: true,
|
|
8571
|
+
metricName:
|
|
8572
|
+
'Ebk' +
|
|
8573
|
+
wafPlan.stage[0].toUpperCase() +
|
|
8574
|
+
wafPlan.stage.slice(1) +
|
|
8575
|
+
'FoundationRouterWaf',
|
|
8576
|
+
sampledRequestsEnabled: true,
|
|
8577
|
+
};
|
|
8578
|
+
},
|
|
8579
|
+
},
|
|
7862
8580
|
});
|
|
7863
8581
|
const database = new sst.aws.Aurora('FoundationDatabase', {
|
|
7864
8582
|
// SQL bootstrap and migrations run inside the VPC. Keep the public HTTPS
|
|
@@ -7882,6 +8600,46 @@ export function foundation(
|
|
|
7882
8600
|
},
|
|
7883
8601
|
vpc,
|
|
7884
8602
|
});
|
|
8603
|
+
const databaseClusterEndpoint = database.nodes.cluster.endpoint.apply(
|
|
8604
|
+
foundationDatabaseEndpointHost,
|
|
8605
|
+
);
|
|
8606
|
+
const databaseReaderEndpoint = database.nodes.cluster.readerEndpoint.apply(
|
|
8607
|
+
foundationDatabaseEndpointHost,
|
|
8608
|
+
);
|
|
8609
|
+
// SST v4.17.1 creates a top-level LinkRef on the next tick and its Aurora
|
|
8610
|
+
// implementation unconditionally splits cluster endpoints. During a
|
|
8611
|
+
// governed refresh after an interrupted first deploy, those provider
|
|
8612
|
+
// outputs are legitimately absent. Preserve the normal link contract while
|
|
8613
|
+
// keeping that read-only recovery program total; a real deploy resolves the
|
|
8614
|
+
// exact endpoints before any discovery resource is created.
|
|
8615
|
+
database.getSSTLink = () => ({
|
|
8616
|
+
include: [
|
|
8617
|
+
sst.aws.permission({
|
|
8618
|
+
actions: ['secretsmanager:GetSecretValue'],
|
|
8619
|
+
resources: [database.secretArn],
|
|
8620
|
+
}),
|
|
8621
|
+
sst.aws.permission({
|
|
8622
|
+
actions: [
|
|
8623
|
+
'rds-data:BatchExecuteStatement',
|
|
8624
|
+
'rds-data:BeginTransaction',
|
|
8625
|
+
'rds-data:CommitTransaction',
|
|
8626
|
+
'rds-data:ExecuteStatement',
|
|
8627
|
+
'rds-data:RollbackTransaction',
|
|
8628
|
+
],
|
|
8629
|
+
resources: [database.clusterArn],
|
|
8630
|
+
}),
|
|
8631
|
+
],
|
|
8632
|
+
properties: {
|
|
8633
|
+
clusterArn: database.clusterArn,
|
|
8634
|
+
database: database.database,
|
|
8635
|
+
host: databaseClusterEndpoint,
|
|
8636
|
+
password: database.password,
|
|
8637
|
+
port: database.port,
|
|
8638
|
+
reader: databaseReaderEndpoint,
|
|
8639
|
+
secretArn: database.secretArn,
|
|
8640
|
+
username: database.username,
|
|
8641
|
+
},
|
|
8642
|
+
});
|
|
7885
8643
|
const foundationAccountId = aws.getCallerIdentityOutput({}).accountId;
|
|
7886
8644
|
const foundationPartition = aws.getPartitionOutput({});
|
|
7887
8645
|
const foundationRegion = aws.getRegionOutput({});
|
|
@@ -7944,6 +8702,7 @@ export function foundation(
|
|
|
7944
8702
|
},
|
|
7945
8703
|
],
|
|
7946
8704
|
resources: [alertTopic.arn],
|
|
8705
|
+
sid: 'AllowBudgetAlerts',
|
|
7947
8706
|
},
|
|
7948
8707
|
{
|
|
7949
8708
|
actions: ['sns:Publish'],
|
|
@@ -7957,6 +8716,7 @@ export function foundation(
|
|
|
7957
8716
|
type: 'Service',
|
|
7958
8717
|
}],
|
|
7959
8718
|
resources: [alertTopic.arn],
|
|
8719
|
+
sid: 'AllowCostAnomalyAlerts',
|
|
7960
8720
|
},
|
|
7961
8721
|
{
|
|
7962
8722
|
actions: ['sns:Publish'],
|
|
@@ -7979,6 +8739,7 @@ export function foundation(
|
|
|
7979
8739
|
type: 'Service',
|
|
7980
8740
|
}],
|
|
7981
8741
|
resources: [alertTopic.arn],
|
|
8742
|
+
sid: 'AllowRdsEventAlerts',
|
|
7982
8743
|
},
|
|
7983
8744
|
{
|
|
7984
8745
|
actions: ['sns:Publish'],
|
|
@@ -8003,6 +8764,7 @@ export function foundation(
|
|
|
8003
8764
|
},
|
|
8004
8765
|
],
|
|
8005
8766
|
resources: [alertTopic.arn],
|
|
8767
|
+
sid: 'AllowCloudWatchAlarmAlerts',
|
|
8006
8768
|
},
|
|
8007
8769
|
],
|
|
8008
8770
|
}).json,
|
|
@@ -8031,6 +8793,41 @@ export function foundation(
|
|
|
8031
8793
|
{ dependsOn: [alertTopicPolicy] },
|
|
8032
8794
|
)
|
|
8033
8795
|
: undefined;
|
|
8796
|
+
const accountCostAnomalyMonitor = isProduction
|
|
8797
|
+
? new aws.costexplorer.AnomalyMonitor(
|
|
8798
|
+
'FoundationAccountCostAnomalyMonitor',
|
|
8799
|
+
{
|
|
8800
|
+
monitorDimension: 'SERVICE',
|
|
8801
|
+
monitorType: 'DIMENSIONAL',
|
|
8802
|
+
name: '${name}-account-service-cost-anomalies',
|
|
8803
|
+
tags: {
|
|
8804
|
+
'ebk:cost-policy-digest': accountCostControl.policyDigest,
|
|
8805
|
+
'ebk:cost-scope': '${name}/account/production',
|
|
8806
|
+
'ebk:owner': accountCostControl.owner,
|
|
8807
|
+
},
|
|
8808
|
+
},
|
|
8809
|
+
)
|
|
8810
|
+
: undefined;
|
|
8811
|
+
const accountCostAnomalySubscription =
|
|
8812
|
+
accountCostControl.anomaly && accountCostAnomalyMonitor
|
|
8813
|
+
? new aws.costexplorer.AnomalySubscription(
|
|
8814
|
+
'FoundationAccountCostAnomalySubscription',
|
|
8815
|
+
{
|
|
8816
|
+
frequency: accountCostControl.anomaly.subscription.frequency,
|
|
8817
|
+
monitorArnLists: [accountCostAnomalyMonitor.arn],
|
|
8818
|
+
name: accountCostControl.anomaly.subscription.name,
|
|
8819
|
+
subscribers: [{ address: alertTopic.arn, type: 'SNS' }],
|
|
8820
|
+
tags: {
|
|
8821
|
+
'ebk:cost-policy-digest': accountCostControl.policyDigest,
|
|
8822
|
+
'ebk:cost-scope': '${name}/account/production',
|
|
8823
|
+
'ebk:owner': accountCostControl.owner,
|
|
8824
|
+
},
|
|
8825
|
+
thresholdExpression:
|
|
8826
|
+
accountCostControl.anomaly.subscription.thresholdExpression,
|
|
8827
|
+
},
|
|
8828
|
+
{ dependsOn: [accountCostAnomalyMonitor, alertTopicPolicy] },
|
|
8829
|
+
)
|
|
8830
|
+
: undefined;
|
|
8034
8831
|
const databaseEvents = isProduction
|
|
8035
8832
|
? new aws.rds.EventSubscription(
|
|
8036
8833
|
'FoundationDatabaseEvents',
|
|
@@ -8133,12 +8930,10 @@ export function foundation(
|
|
|
8133
8930
|
clusterId: cluster.id,
|
|
8134
8931
|
contractVersion: '1.0.0',
|
|
8135
8932
|
databaseClusterArn: database.clusterArn,
|
|
8136
|
-
databaseClusterEndpoint
|
|
8137
|
-
(endpoint: string) => endpoint.split(':')[0],
|
|
8138
|
-
),
|
|
8933
|
+
databaseClusterEndpoint,
|
|
8139
8934
|
databaseClusterId: database.id,
|
|
8140
8935
|
databaseClusterResourceId: database.nodes.cluster.clusterResourceId,
|
|
8141
|
-
databaseHost:
|
|
8936
|
+
databaseHost: databaseClusterEndpoint,
|
|
8142
8937
|
databaseMasterSecretArn: database.secretArn,
|
|
8143
8938
|
databasePort: database.port.apply(String),
|
|
8144
8939
|
eventBusArn: eventBus.arn,
|
|
@@ -8160,6 +8955,7 @@ export function foundation(
|
|
|
8160
8955
|
capabilities: STANDARD_FOUNDATION_CAPABILITIES,
|
|
8161
8956
|
clusterId: cluster.id,
|
|
8162
8957
|
contractVersion: '1.0.0',
|
|
8958
|
+
costAnomalySubscriptionArn: accountCostAnomalySubscription?.arn,
|
|
8163
8959
|
databaseCapacityAlarmName: databaseCapacityAlarm.name,
|
|
8164
8960
|
dashboardName: dashboard.dashboardName,
|
|
8165
8961
|
databaseClusterId: database.id,
|
|
@@ -8191,13 +8987,49 @@ export function app(workloadRoles?: FoundationWorkloadRolePreflight) {
|
|
|
8191
8987
|
actualNotificationThresholdPercent: 80,
|
|
8192
8988
|
forecastNotificationThresholdPercent: 100,
|
|
8193
8989
|
kind: 'ebk-foundation-account-cost-policy',
|
|
8194
|
-
monthlyMaximumUsd: 1_000,
|
|
8990
|
+
monthlyMaximumUsd: deploymentProfile === 'starter' ? 250 : 1_000,
|
|
8195
8991
|
owner: 'replace-with-owner',
|
|
8196
8992
|
reviewAt: null,
|
|
8197
8993
|
schemaVersion: 1,
|
|
8198
8994
|
status: 'example',
|
|
8199
8995
|
workspace: name,
|
|
8200
8996
|
}),
|
|
8997
|
+
'packages/foundation/security/router-waf.json': json({
|
|
8998
|
+
kind: 'ebk-router-waf-policy',
|
|
8999
|
+
logging: {
|
|
9000
|
+
enabled: false,
|
|
9001
|
+
retention: '1 week',
|
|
9002
|
+
},
|
|
9003
|
+
rateLimitPerIp: 2_000,
|
|
9004
|
+
schemaVersion: 1,
|
|
9005
|
+
stages: {
|
|
9006
|
+
development: {
|
|
9007
|
+
access: 'allowlist',
|
|
9008
|
+
allowedCidrs: [],
|
|
9009
|
+
mode: 'block',
|
|
9010
|
+
},
|
|
9011
|
+
production: {
|
|
9012
|
+
access: 'public',
|
|
9013
|
+
allowedCidrs: [],
|
|
9014
|
+
mode: 'block',
|
|
9015
|
+
},
|
|
9016
|
+
staging: {
|
|
9017
|
+
access: 'allowlist',
|
|
9018
|
+
allowedCidrs: [],
|
|
9019
|
+
mode: 'block',
|
|
9020
|
+
},
|
|
9021
|
+
},
|
|
9022
|
+
}),
|
|
9023
|
+
'packages/foundation/security/router-waf-blocking-rehearsal.json': json({
|
|
9024
|
+
kind: 'ebk-router-waf-blocking-rehearsal',
|
|
9025
|
+
schemaVersion: 1,
|
|
9026
|
+
status: 'pending',
|
|
9027
|
+
}),
|
|
9028
|
+
'packages/foundation/security/router-waf-live-evidence.json': json({
|
|
9029
|
+
kind: 'ebk-router-waf-live-evidence',
|
|
9030
|
+
schemaVersion: 1,
|
|
9031
|
+
status: 'pending',
|
|
9032
|
+
}),
|
|
8201
9033
|
'packages/foundation/configuration/declarations.json': json({
|
|
8202
9034
|
project: 'foundation',
|
|
8203
9035
|
schemaVersion: 1,
|
|
@@ -8230,29 +9062,96 @@ export function app(workloadRoles?: FoundationWorkloadRolePreflight) {
|
|
|
8230
9062
|
version: '4.17.1',
|
|
8231
9063
|
},
|
|
8232
9064
|
policy: {
|
|
9065
|
+
adoptedImports: [
|
|
9066
|
+
{
|
|
9067
|
+
authority: 'workload-role-account-baseline',
|
|
9068
|
+
logicalId: 'FoundationVpcNatInstanceProfile',
|
|
9069
|
+
privateIdentifierTemplate: 'ebk-{stage}-foundation-nat-profile',
|
|
9070
|
+
recoveryRunbookRef: 'docs/workload-role-account-baseline.md',
|
|
9071
|
+
removalBehavior: 'retain',
|
|
9072
|
+
resourceType: 'aws:iam/instanceProfile:InstanceProfile',
|
|
9073
|
+
},
|
|
9074
|
+
],
|
|
8233
9075
|
additiveLowRiskTypes: [
|
|
8234
9076
|
'aws:cloudwatch/dashboard:Dashboard',
|
|
8235
9077
|
'aws:cloudwatch/logGroup:LogGroup',
|
|
8236
9078
|
'aws:cloudwatch/metricAlarm:MetricAlarm',
|
|
8237
9079
|
],
|
|
9080
|
+
additiveReviewedTypes: [
|
|
9081
|
+
'aws:cloudwatch/eventArchive:EventArchive',
|
|
9082
|
+
'aws:cloudwatch/eventBus:EventBus',
|
|
9083
|
+
'aws:cognito/userPoolDomain:UserPoolDomain',
|
|
9084
|
+
'aws:ec2/defaultSecurityGroup:DefaultSecurityGroup',
|
|
9085
|
+
'aws:ec2/internetGateway:InternetGateway',
|
|
9086
|
+
'aws:ec2/routeTable:RouteTable',
|
|
9087
|
+
'aws:ec2/routeTableAssociation:RouteTableAssociation',
|
|
9088
|
+
'aws:ec2/securityGroup:SecurityGroup',
|
|
9089
|
+
'aws:ec2/subnet:Subnet',
|
|
9090
|
+
'aws:ec2/vpc:Vpc',
|
|
9091
|
+
'aws:ecs/cluster:Cluster',
|
|
9092
|
+
'aws:ecs/clusterCapacityProviders:ClusterCapacityProviders',
|
|
9093
|
+
'aws:rds/clusterParameterGroup:ClusterParameterGroup',
|
|
9094
|
+
'aws:rds/eventSubscription:EventSubscription',
|
|
9095
|
+
'aws:rds/parameterGroup:ParameterGroup',
|
|
9096
|
+
'aws:rds/subnetGroup:SubnetGroup',
|
|
9097
|
+
'aws:servicediscovery/privateDnsNamespace:PrivateDnsNamespace',
|
|
9098
|
+
'aws:sns/topic:Topic',
|
|
9099
|
+
'aws:sns/topicPolicy:TopicPolicy',
|
|
9100
|
+
'aws:wafv2/webAclLoggingConfiguration:WebAclLoggingConfiguration',
|
|
9101
|
+
'pulumi-nodejs:dynamic:Resource',
|
|
9102
|
+
'pulumi:providers:aws',
|
|
9103
|
+
'pulumi:providers:pulumi-nodejs',
|
|
9104
|
+
'pulumi:providers:random',
|
|
9105
|
+
'pulumi:pulumi:Stack',
|
|
9106
|
+
'sst:aws:Aurora',
|
|
9107
|
+
'sst:aws:Bus',
|
|
9108
|
+
'sst:aws:CDN',
|
|
9109
|
+
'sst:aws:Cluster',
|
|
9110
|
+
'sst:aws:CognitoUserPool',
|
|
9111
|
+
'sst:aws:Router',
|
|
9112
|
+
'sst:aws:SnsTopic',
|
|
9113
|
+
'sst:aws:Vpc',
|
|
9114
|
+
'sst:sst:LinkRef',
|
|
9115
|
+
'sst:sst:Version',
|
|
9116
|
+
],
|
|
8238
9117
|
costOrQuotaTypes: [
|
|
8239
9118
|
'aws:apigatewayv2/api:Api',
|
|
8240
9119
|
'aws:budgets/budget:Budget',
|
|
9120
|
+
'aws:cloudfront/cachePolicy:CachePolicy',
|
|
8241
9121
|
'aws:cloudfront/distribution:Distribution',
|
|
9122
|
+
'aws:cloudfront/function:Function',
|
|
9123
|
+
'aws:cloudfront/keyValueStore:KeyValueStore',
|
|
9124
|
+
'aws:costexplorer/anomalyMonitor:AnomalyMonitor',
|
|
9125
|
+
'aws:costexplorer/anomalySubscription:AnomalySubscription',
|
|
8242
9126
|
'aws:cognito/userPool:UserPool',
|
|
9127
|
+
'aws:ec2/eip:Eip',
|
|
9128
|
+
'aws:ec2/eipAssociation:EipAssociation',
|
|
9129
|
+
'aws:ec2/instance:Instance',
|
|
8243
9130
|
'aws:ec2/natGateway:NatGateway',
|
|
8244
9131
|
'aws:rds/cluster:Cluster',
|
|
9132
|
+
'aws:rds/clusterInstance:ClusterInstance',
|
|
9133
|
+
'aws:wafv2/ipSet:IpSet',
|
|
9134
|
+
'aws:wafv2/webAcl:WebAcl',
|
|
8245
9135
|
],
|
|
8246
9136
|
failClosedOnUnknown: true,
|
|
8247
9137
|
protectedPlanAuthority: 'deploy-role-residual',
|
|
8248
9138
|
standingPolicyRiskClasses: ['no-change', 'additive-low-risk'],
|
|
8249
9139
|
statefulTypes: [
|
|
9140
|
+
'aws:cloudwatch/eventArchive:EventArchive',
|
|
9141
|
+
'aws:cloudwatch/eventBus:EventBus',
|
|
8250
9142
|
'aws:cognito/userPool:UserPool',
|
|
8251
9143
|
'aws:dynamodb/table:Table',
|
|
8252
9144
|
'aws:events/eventBus:EventBus',
|
|
8253
9145
|
'aws:kms/key:Key',
|
|
8254
9146
|
'aws:rds/cluster:Cluster',
|
|
8255
9147
|
'aws:s3/bucket:Bucket',
|
|
9148
|
+
'aws:secretsmanager/secret:Secret',
|
|
9149
|
+
'aws:secretsmanager/secretVersion:SecretVersion',
|
|
9150
|
+
'aws:sns/topic:Topic',
|
|
9151
|
+
'aws:ssm/parameter:Parameter',
|
|
9152
|
+
'aws:wafv2/ipSet:IpSet',
|
|
9153
|
+
'aws:wafv2/webAcl:WebAcl',
|
|
9154
|
+
'random:index/randomPassword:RandomPassword',
|
|
8256
9155
|
],
|
|
8257
9156
|
},
|
|
8258
9157
|
project: 'foundation',
|
|
@@ -8350,6 +9249,13 @@ export function app(workloadRoles?: FoundationWorkloadRolePreflight) {
|
|
|
8350
9249
|
executor: '@empire-builder-kit/nx:foundation-profile-preflight',
|
|
8351
9250
|
options: { stage: 'development' },
|
|
8352
9251
|
},
|
|
9252
|
+
'router-waf-rehearsal': {
|
|
9253
|
+
cache: false,
|
|
9254
|
+
executor: '@empire-builder-kit/nx:router-waf-rehearsal',
|
|
9255
|
+
outputs: [
|
|
9256
|
+
`{projectRoot}/security/router-waf-blocking-rehearsal.json`,
|
|
9257
|
+
],
|
|
9258
|
+
},
|
|
8353
9259
|
typecheck: {
|
|
8354
9260
|
executor: 'nx:run-commands',
|
|
8355
9261
|
options: {
|
|
@@ -8377,6 +9283,11 @@ export function app(workloadRoles?: FoundationWorkloadRolePreflight) {
|
|
|
8377
9283
|
}),
|
|
8378
9284
|
'packages/foundation/scripts/verify-foundation.mjs': `import { readFileSync } from 'node:fs';
|
|
8379
9285
|
|
|
9286
|
+
import {
|
|
9287
|
+
normalizeRouterWafPolicy,
|
|
9288
|
+
reconcileRouterWafBlockingRehearsal,
|
|
9289
|
+
} from '@empire-builder-kit/nx/foundation';
|
|
9290
|
+
|
|
8380
9291
|
const descriptor = JSON.parse(
|
|
8381
9292
|
readFileSync(new URL('../contracts/foundation.json', import.meta.url), 'utf8'),
|
|
8382
9293
|
);
|
|
@@ -8392,6 +9303,33 @@ const frontendSecurityHeaders = JSON.parse(
|
|
|
8392
9303
|
'utf8',
|
|
8393
9304
|
),
|
|
8394
9305
|
);
|
|
9306
|
+
const routerWafPolicy = normalizeRouterWafPolicy(
|
|
9307
|
+
JSON.parse(
|
|
9308
|
+
readFileSync(
|
|
9309
|
+
new URL('../security/router-waf.json', import.meta.url),
|
|
9310
|
+
'utf8',
|
|
9311
|
+
),
|
|
9312
|
+
),
|
|
9313
|
+
);
|
|
9314
|
+
reconcileRouterWafBlockingRehearsal({
|
|
9315
|
+
evidence: JSON.parse(
|
|
9316
|
+
readFileSync(
|
|
9317
|
+
new URL('../security/router-waf-live-evidence.json', import.meta.url),
|
|
9318
|
+
'utf8',
|
|
9319
|
+
),
|
|
9320
|
+
),
|
|
9321
|
+
policy: routerWafPolicy,
|
|
9322
|
+
receipt:
|
|
9323
|
+
JSON.parse(
|
|
9324
|
+
readFileSync(
|
|
9325
|
+
new URL(
|
|
9326
|
+
'../security/router-waf-blocking-rehearsal.json',
|
|
9327
|
+
import.meta.url,
|
|
9328
|
+
),
|
|
9329
|
+
'utf8',
|
|
9330
|
+
),
|
|
9331
|
+
),
|
|
9332
|
+
});
|
|
8395
9333
|
const expectedCapabilities = [
|
|
8396
9334
|
'network',
|
|
8397
9335
|
'routing',
|
|
@@ -8407,6 +9345,13 @@ const expectedCapabilities = [
|
|
|
8407
9345
|
if (descriptor.contractVersion !== '1.0.0') {
|
|
8408
9346
|
throw new Error('Foundation contractVersion must be 1.0.0.');
|
|
8409
9347
|
}
|
|
9348
|
+
if (
|
|
9349
|
+
routerWafPolicy.stages.development.access !== 'allowlist' ||
|
|
9350
|
+
routerWafPolicy.stages.staging.access !== 'allowlist' ||
|
|
9351
|
+
routerWafPolicy.stages.production.mode !== 'block'
|
|
9352
|
+
) {
|
|
9353
|
+
throw new Error('Foundation Router WAF stage defaults are invalid.');
|
|
9354
|
+
}
|
|
8410
9355
|
if (JSON.stringify(descriptor.capabilities) !== JSON.stringify(expectedCapabilities)) {
|
|
8411
9356
|
throw new Error('Foundation capability descriptor does not match the v1 standard.');
|
|
8412
9357
|
}
|
|
@@ -8415,7 +9360,7 @@ if (
|
|
|
8415
9360
|
costs.aurora?.engineVersion !== '${index_js_4.FOUNDATION_AURORA_ENGINE_VERSION}' ||
|
|
8416
9361
|
costs.productionProfileSource !== 'ebk.repository-policy.json' ||
|
|
8417
9362
|
costs.productionProfiles?.standard?.aurora?.min !== '2 ACU' ||
|
|
8418
|
-
costs.productionProfiles?.['cost-optimized']?.aurora?.min !== '0 ACU' ||
|
|
9363
|
+
costs.productionProfiles?.['cost-optimized']?.aurora?.min !== '0.5 ACU' ||
|
|
8419
9364
|
!costs.stages?.development ||
|
|
8420
9365
|
!costs.stages?.staging ||
|
|
8421
9366
|
costs.stages?.production?.dataRetention !== 'retained-and-protected'
|
|
@@ -8471,16 +9416,15 @@ export default $config({
|
|
|
8471
9416
|
providers: {
|
|
8472
9417
|
aws: {
|
|
8473
9418
|
defaultTags: {
|
|
9419
|
+
// SST contributes sst:app and sst:stage. Keep the provider baseline
|
|
9420
|
+
// at eight tags so generated S3 asset objects remain within AWS's
|
|
9421
|
+
// hard ten-tag object limit. Richer performance tags stay explicit.
|
|
8474
9422
|
tags: {
|
|
8475
9423
|
'ebk:cost-allocation': 'foundation',
|
|
8476
9424
|
'ebk:cost-scope': '${name}/foundation/' + stage,
|
|
8477
9425
|
'ebk:framework-version': '${frameworkVersion}',
|
|
8478
9426
|
'ebk:managed-by': 'empire-builder-kit',
|
|
8479
9427
|
'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
9428
|
'ebk:slice': 'foundation',
|
|
8485
9429
|
'ebk:stage': stage,
|
|
8486
9430
|
'ebk:workspace': '${name}',
|
|
@@ -8524,6 +9468,16 @@ export default $config({
|
|
|
8524
9468
|
- 'packages/*/services/*'
|
|
8525
9469
|
|
|
8526
9470
|
autoInstallPeers: true
|
|
9471
|
+
blockExoticSubdeps: true
|
|
9472
|
+
enableGlobalVirtualStore: false
|
|
9473
|
+
ignorePnpmfile: true
|
|
9474
|
+
pnpmfile: []
|
|
9475
|
+
minimumReleaseAge: 1440
|
|
9476
|
+
minimumReleaseAgeExclude:
|
|
9477
|
+
- '@empire-builder-kit/*'
|
|
9478
|
+
minimumReleaseAgeIgnoreMissingTime: false
|
|
9479
|
+
minimumReleaseAgeStrict: true
|
|
9480
|
+
verifyDepsBeforeRun: error
|
|
8527
9481
|
storeDir: .ebk/cache/pnpm-store
|
|
8528
9482
|
supportedArchitectures:
|
|
8529
9483
|
os:
|
|
@@ -9393,18 +10347,22 @@ if (eventName === 'pull_request' || eventName === 'push') {
|
|
|
9393
10347
|
}
|
|
9394
10348
|
if (eventName === 'pull_request') {
|
|
9395
10349
|
const baseRef = process.env.GITHUB_BASE_REF;
|
|
10350
|
+
const headRepository = process.env.EBK_HEAD_REPOSITORY;
|
|
9396
10351
|
const headRef = process.env.GITHUB_HEAD_REF;
|
|
9397
|
-
|
|
9398
|
-
|
|
10352
|
+
const repository = process.env.GITHUB_REPOSITORY;
|
|
10353
|
+
if (!baseRef || !headRepository || !headRef || !repository) {
|
|
10354
|
+
throw new Error('Pull-request branch and repository metadata is missing.');
|
|
9399
10355
|
}
|
|
9400
10356
|
Object.assign(request, {
|
|
9401
10357
|
baseIsAncestorOfHead: isAncestor('refs/remotes/origin/' + baseRef, 'HEAD'),
|
|
9402
10358
|
baseRef,
|
|
10359
|
+
headRepository,
|
|
9403
10360
|
headRef,
|
|
9404
10361
|
mainIsAncestorOfDevelop: isAncestor(
|
|
9405
10362
|
'refs/remotes/origin/main',
|
|
9406
10363
|
'refs/remotes/origin/develop',
|
|
9407
10364
|
),
|
|
10365
|
+
repository,
|
|
9408
10366
|
});
|
|
9409
10367
|
} else if (eventName === 'push') {
|
|
9410
10368
|
Object.assign(request, {
|