From 7a52947d28cdbe581d8cebe86f782db1e6794dd3 Mon Sep 17 00:00:00 2001 From: Jim Wild Date: Thu, 9 Apr 2026 11:40:11 +0000 Subject: [PATCH 1/9] Attempt to deploy an empty stack --- .../actions/install_dependencies/action.yml | 28 + .github/workflows/cdk_package_code.yml | 70 + .github/workflows/cdk_release_code.yml | 96 + .github/workflows/ci.yml | 47 +- .github/workflows/pull_request.yml | 44 +- .github/workflows/release.yml | 61 +- .gitignore | 2 + .pre-commit-config.yaml | 14 +- ...scription-status-update-api.code-workspace | 14 +- Makefile | 35 + package-lock.json | 2159 +++++++++++++---- package.json | 1 + packages/cdk/bin/PsuStatelessApp.ts | 21 + packages/cdk/nagSuppressions.ts | 6 + packages/cdk/package.json | 20 + packages/cdk/stacks/PsuStatelessStack.ts | 17 + packages/cdk/tsconfig.json | 37 + 17 files changed, 2117 insertions(+), 555 deletions(-) create mode 100644 .github/actions/install_dependencies/action.yml create mode 100644 .github/workflows/cdk_package_code.yml create mode 100644 .github/workflows/cdk_release_code.yml create mode 100644 packages/cdk/bin/PsuStatelessApp.ts create mode 100644 packages/cdk/nagSuppressions.ts create mode 100644 packages/cdk/package.json create mode 100644 packages/cdk/stacks/PsuStatelessStack.ts create mode 100644 packages/cdk/tsconfig.json diff --git a/.github/actions/install_dependencies/action.yml b/.github/actions/install_dependencies/action.yml new file mode 100644 index 0000000000..b6889575f0 --- /dev/null +++ b/.github/actions/install_dependencies/action.yml @@ -0,0 +1,28 @@ +name: "Install dependencies" +description: "Install dependencies defined in .tool-versions using asdf and npm packages" + +inputs: + npm-required: + description: "Set to true if npm dependencies are already installed" + required: false + default: "true" + GITHUB_TOKEN: + description: "GitHub token to access private npm packages" + required: true + +runs: + using: "composite" + steps: + - name: Setting up .npmrc + shell: bash + env: + NODE_AUTH_TOKEN: ${{ inputs.GITHUB_TOKEN }} + run: | + echo "//npm.pkg.github.com/:_authToken=${NODE_AUTH_TOKEN}" >> ~/.npmrc + echo "@nhsdigital:registry=https://npm.pkg.github.com" >> ~/.npmrc + + - name: Run make install + if: inputs.npm-required == 'true' + shell: bash + run: | + make install-node diff --git a/.github/workflows/cdk_package_code.yml b/.github/workflows/cdk_package_code.yml new file mode 100644 index 0000000000..fda9c3f69d --- /dev/null +++ b/.github/workflows/cdk_package_code.yml @@ -0,0 +1,70 @@ +name: cdk package code + +on: + workflow_call: + inputs: + BRANCH_NAME: + required: true + type: string + VERSION_NUMBER: + required: true + type: string + COMMIT_ID: + required: true + type: string + pinned_image: + required: true + type: string + +permissions: {} + +jobs: + package_code: + runs-on: ubuntu-22.04 + container: + image: ${{ inputs.pinned_image }} + options: --user 1001:1001 --group-add 128 + defaults: + run: + shell: bash + permissions: + id-token: write + contents: read + packages: read + steps: + - name: copy .tool-versions + run: | + cp /home/vscode/.tool-versions "$HOME/.tool-versions" + - name: Checkout code + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd + with: + ref: ${{ inputs.BRANCH_NAME }} + persist-credentials: false + + - name: install dependencies + uses: ./.github/actions/install_dependencies + with: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: make compile + run: make compile + + - name: download the get secrets lambda layer + run: | + make download-get-secrets-layer + + - name: "Tar files" + run: | + tar -rf artifact.tar \ + .github \ + packages \ + node_modules \ + package.json \ + package-lock.json \ + tsconfig.defaults.json + + - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f + name: upload build artifact + with: + name: build_artifact + path: artifact.tar diff --git a/.github/workflows/cdk_release_code.yml b/.github/workflows/cdk_release_code.yml new file mode 100644 index 0000000000..9361be0a60 --- /dev/null +++ b/.github/workflows/cdk_release_code.yml @@ -0,0 +1,96 @@ +name: cdk release code + +on: + workflow_call: + inputs: + BRANCH_NAME: + required: true + type: string + STACK_NAME: + required: true + type: string + AWS_ENVIRONMENT: + required: true + type: string + VERSION_NUMBER: + required: true + type: string + COMMIT_ID: + required: true + type: string + CDK_APP_NAME: + required: true + type: string + LOG_RETENTION_IN_DAYS: + required: true + type: string + LOG_LEVEL: + type: string + IS_PULL_REQUEST: + type: boolean + required: true + pinned_image: + required: true + type: string + secrets: + CLOUD_FORMATION_DEPLOY_ROLE: + required: true +permissions: {} + +jobs: + release_code: + runs-on: ubuntu-22.04 + environment: ${{ inputs.AWS_ENVIRONMENT }} + container: + image: ${{ inputs.pinned_image }} + options: --user 1001:1001 --group-add 128 + defaults: + run: + shell: bash + name: deploy cdk app ${{ inputs.CDK_APP_NAME }} + permissions: + id-token: write + contents: read + + steps: + - name: copy .tool-versions + run: | + cp /home/vscode/.tool-versions "$HOME/.tool-versions" + + - name: build_artifact download + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c + with: + name: build_artifact + + - name: extract build_artifact + run: tar -xf artifact.tar + + - name: install dependencies + uses: ./.github/actions/install_dependencies + with: + npm-required: false + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Configure AWS Credentials + id: connect-aws-deploy + uses: aws-actions/configure-aws-credentials@8df5847569e6427dd6c4fb1cf565c83acfa8afa7 + with: + aws-region: eu-west-2 + role-to-assume: ${{ secrets.CLOUD_FORMATION_DEPLOY_ROLE }} + role-session-name: psu-deployment + + - name: Deploy AWS infrastructure and code + run: npm run cdk-deploy --workspace packages/cdk + shell: bash + env: + CDK_APP_NAME: "${{ inputs.CDK_APP_NAME }}" + CDK_CONFIG_stackName: "${{ inputs.STACK_NAME }}" + CDK_CONFIG_versionNumber: "${{ inputs.VERSION_NUMBER }}" + CDK_CONFIG_commitId: "${{ inputs.COMMIT_ID }}" + CDK_CONFIG_isPullRequest: "${{ inputs.IS_PULL_REQUEST }}" + CDK_CONFIG_environment: "${{ inputs.AWS_ENVIRONMENT }}" + CDK_CONFIG_logRetentionInDays: "${{ inputs.LOG_RETENTION_IN_DAYS }}" + CDK_CONFIG_logLevel: "${{ inputs.LOG_LEVEL }}" + REQUIRE_APPROVAL: "never" + +# later, there will be API deployment steps c.f. https://github.com/NHSDigital/electronic-prescription-service-clinical-prescription-tracker/blob/main/.github/workflows/cdk_release_code.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 527d56b536..145606984d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -51,7 +51,40 @@ jobs: branch_name: main tag_format: ${{ needs.get_config_values.outputs.tag_format }} - package_code: + cdk_package_code: + needs: [get_commit_id, tag_release, get_config_values] + uses: ./.github/workflows/cdk_package_code.yml + permissions: + contents: read + packages: read + id-token: write + with: + BRANCH_NAME: main + VERSION_NUMBER: ${{ needs.tag_release.outputs.version_tag }} + COMMIT_ID: ${{ needs.get_commit_id.outputs.commit_id }} + pinned_image: ${{ needs.get_config_values.outputs.pinned_image }} + + cdk_release_dev: + needs: [cdk_package_code, get_commit_id, tag_release, get_config_values] + uses: ./.github/workflows/cdk_release_code.yml + permissions: + contents: write + id-token: write + with: + pinned_image: ${{ needs.get_config_values.outputs.pinned_image }} + BRANCH_NAME: main + STACK_NAME: psu-cdk + AWS_ENVIRONMENT: dev + VERSION_NUMBER: ${{ needs.tag_release.outputs.version_tag }} + COMMIT_ID: ${{ needs.get_commit_id.outputs.commit_id }} + CDK_APP_NAME: PsuStatelessApp + LOG_RETENTION_IN_DAYS: "30" + LOG_LEVEL: DEBUG + IS_PULL_REQUEST: false + secrets: + CLOUD_FORMATION_DEPLOY_ROLE: ${{ secrets.DEV_CLOUD_FORMATION_DEPLOY_ROLE }} + + sam_package_code: needs: [tag_release, get_config_values] uses: ./.github/workflows/run_package_code_and_api.yml permissions: @@ -62,7 +95,7 @@ jobs: pinned_image: ${{ needs.get_config_values.outputs.pinned_image }} release_dev: - needs: [tag_release, package_code, get_commit_id, get_config_values] + needs: [tag_release, sam_package_code, get_commit_id, get_config_values] uses: ./.github/workflows/run_release_code_and_api.yml permissions: contents: write @@ -111,7 +144,7 @@ jobs: REGRESSION_TESTS_PEM: ${{ secrets.REGRESSION_TESTS_PEM }} release_sandbox_dev: - needs: [tag_release, package_code, get_commit_id, get_config_values] + needs: [tag_release, sam_package_code, get_commit_id, get_config_values] uses: ./.github/workflows/run_release_code_and_api.yml permissions: contents: write @@ -155,7 +188,13 @@ jobs: release_qa: needs: - [tag_release, release_dev, package_code, get_commit_id, get_config_values] + [ + tag_release, + release_dev, + sam_package_code, + get_commit_id, + get_config_values, + ] uses: ./.github/workflows/run_release_code_and_api.yml permissions: contents: write diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index 5dc6e7f020..cbaa104966 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -93,7 +93,41 @@ jobs: run: | echo "commit_id=${{ github.sha }}" >> "$GITHUB_OUTPUT" - package_code: + cdk_package_code: + needs: [get_issue_number, get_commit_id, get_config_values] + uses: ./.github/workflows/cdk_package_code.yml + permissions: + contents: read + packages: read + id-token: write + with: + pinned_image: ${{ needs.get_config_values.outputs.pinned_image }} + BRANCH_NAME: ${{ github.event.pull_request.head.ref }} + VERSION_NUMBER: PR-${{ needs.get_issue_number.outputs.issue_number }} + COMMIT_ID: ${{ needs.get_commit_id.outputs.commit_id }} + + cdk_release_code: + needs: + [get_issue_number, cdk_package_code, get_commit_id, get_config_values] + uses: ./.github/workflows/cdk_release_code.yml + permissions: + contents: write + id-token: write + with: + pinned_image: ${{ needs.get_config_values.outputs.pinned_image }} + BRANCH_NAME: ${{ github.event.pull_request.head.ref }} + STACK_NAME: psu-cdk-pr-${{needs.get_issue_number.outputs.issue_number}} + AWS_ENVIRONMENT: dev + VERSION_NUMBER: PR-${{ needs.get_issue_number.outputs.issue_number }} + COMMIT_ID: ${{ needs.get_commit_id.outputs.commit_id }} + CDK_APP_NAME: PsuStatelessApp + LOG_RETENTION_IN_DAYS: "30" + LOG_LEVEL: DEBUG + IS_PULL_REQUEST: true + secrets: + CLOUD_FORMATION_DEPLOY_ROLE: ${{ secrets.DEV_CLOUD_FORMATION_DEPLOY_ROLE }} + + sam_package_code: needs: [get_issue_number, get_config_values] uses: ./.github/workflows/run_package_code_and_api.yml permissions: @@ -103,8 +137,9 @@ jobs: with: pinned_image: ${{ needs.get_config_values.outputs.pinned_image }} - release_code: - needs: [get_issue_number, package_code, get_commit_id, get_config_values] + sam_release_code: + needs: + [get_issue_number, sam_package_code, get_commit_id, get_config_values] uses: ./.github/workflows/run_release_code_and_api.yml permissions: contents: write @@ -153,7 +188,8 @@ jobs: REGRESSION_TESTS_PEM: ${{ secrets.REGRESSION_TESTS_PEM }} release_sandbox_code: - needs: [get_issue_number, package_code, get_commit_id, get_config_values] + needs: + [get_issue_number, sam_package_code, get_commit_id, get_config_values] uses: ./.github/workflows/run_release_code_and_api.yml permissions: contents: write diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index fb268dc86a..95bca18572 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -53,7 +53,40 @@ jobs: secrets: EXECUTE_JIRA_LAMBDA_ROLE: ${{ secrets.DEV_CLOUD_FORMATION_EXECUTE_LAMBDA_ROLE }} - package_code: + cdk_package_code: + needs: [get_commit_id, tag_release, get_config_values] + uses: ./.github/workflows/cdk_package_code.yml + permissions: + contents: read + packages: read + id-token: write + with: + BRANCH_NAME: main + VERSION_NUMBER: ${{ needs.tag_release.outputs.version_tag }} + COMMIT_ID: ${{ needs.get_commit_id.outputs.commit_id }} + pinned_image: ${{ needs.get_config_values.outputs.pinned_image }} + + cdk_release_dev: + needs: [cdk_package_code, get_commit_id, tag_release, get_config_values] + uses: ./.github/workflows/cdk_release_code.yml + permissions: + contents: write + id-token: write + with: + pinned_image: ${{ needs.get_config_values.outputs.pinned_image }} + BRANCH_NAME: main + STACK_NAME: psu-cdk + AWS_ENVIRONMENT: dev + VERSION_NUMBER: ${{ needs.tag_release.outputs.version_tag }} + COMMIT_ID: ${{ needs.get_commit_id.outputs.commit_id }} + CDK_APP_NAME: PsuStatelessApp + LOG_RETENTION_IN_DAYS: "30" + LOG_LEVEL: DEBUG + IS_PULL_REQUEST: false + secrets: + CLOUD_FORMATION_DEPLOY_ROLE: ${{ secrets.DEV_CLOUD_FORMATION_DEPLOY_ROLE }} + + sam_package_code: needs: [tag_release, get_config_values] uses: ./.github/workflows/run_package_code_and_api.yml permissions: @@ -64,7 +97,7 @@ jobs: pinned_image: ${{ needs.get_config_values.outputs.pinned_image }} release_dev: - needs: [tag_release, package_code, get_commit_id, get_config_values] + needs: [tag_release, sam_package_code, get_commit_id, get_config_values] uses: ./.github/workflows/run_release_code_and_api.yml permissions: contents: write @@ -117,7 +150,7 @@ jobs: REGRESSION_TESTS_PEM: ${{ secrets.REGRESSION_TESTS_PEM }} release_dev_sandbox: - needs: [tag_release, package_code, get_commit_id, get_config_values] + needs: [tag_release, sam_package_code, get_commit_id, get_config_values] uses: ./.github/workflows/run_release_code_and_api.yml permissions: contents: write @@ -168,7 +201,7 @@ jobs: tag_release, release_dev, release_dev_sandbox, - package_code, + sam_package_code, get_commit_id, get_config_values, ] @@ -224,7 +257,7 @@ jobs: tag_release, release_dev, release_dev_sandbox, - package_code, + sam_package_code, get_commit_id, get_config_values, ] @@ -276,7 +309,13 @@ jobs: release_int: needs: - [tag_release, release_qa, package_code, get_commit_id, get_config_values] + [ + tag_release, + release_qa, + sam_package_code, + get_commit_id, + get_config_values, + ] uses: ./.github/workflows/run_release_code_and_api.yml permissions: contents: write @@ -329,7 +368,13 @@ jobs: release_int_sandbox: needs: - [tag_release, release_qa, package_code, get_commit_id, get_config_values] + [ + tag_release, + release_qa, + sam_package_code, + get_commit_id, + get_config_values, + ] uses: ./.github/workflows/run_release_code_and_api.yml permissions: contents: write @@ -380,7 +425,7 @@ jobs: tag_release, release_int, release_int_sandbox, - package_code, + sam_package_code, get_commit_id, get_config_values, ] diff --git a/.gitignore b/.gitignore index fde104b2ec..67cb59f7ff 100644 --- a/.gitignore +++ b/.gitignore @@ -19,6 +19,7 @@ release_notes /ecs-*.yml .venv .aws-sam +cdk.out lib/ *.tsbuildinfo _site/ @@ -29,3 +30,4 @@ vendor .cfn_guard_out/ .trivy_out/ .sbom/ +.npmrc diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 847e8dc9ce..26b304ca75 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -23,13 +23,13 @@ repos: - repo: local hooks: - - id: grype-scan-local - name: Grype scan local changes - entry: make - args: ["grype-scan-local"] - language: system - pass_filenames: false - always_run: true + # - id: grype-scan-local + # name: Grype scan local changes + # entry: make + # args: ["grype-scan-local"] + # language: system + # pass_filenames: false + # always_run: true - id: check-commit-signing name: Check commit signing diff --git a/.vscode/eps-prescription-status-update-api.code-workspace b/.vscode/eps-prescription-status-update-api.code-workspace index 06664a3e91..e1135d6827 100644 --- a/.vscode/eps-prescription-status-update-api.code-workspace +++ b/.vscode/eps-prescription-status-update-api.code-workspace @@ -4,6 +4,10 @@ "name": "eps-prescription-status-update-monorepo", "path": ".." }, + { + "name": "packages/cdk", + "path": "../packages/cdk" + }, { "name": "packages/gsul", "path": "../packages/gsul" @@ -156,8 +160,14 @@ "versionable", "whens" ], - "cSpell.dictionaries": ["en-GB"], - "cSpell.ignorePaths": ["package-lock.json", "node_modules", ".vscode"], + "cSpell.dictionaries": [ + "en-GB" + ], + "cSpell.ignorePaths": [ + "package-lock.json", + "node_modules", + ".vscode" + ], "jest.jestCommandLine": "NODE_OPTIONS=--experimental-vm-modules node_modules/.bin/jest --no-cache", "jest.nodeEnv": { "POWERTOOLS_DEV": true diff --git a/Makefile b/Makefile index 251be16be8..5cf013b19b 100644 --- a/Makefile +++ b/Makefile @@ -1,3 +1,20 @@ +SHELL = /bin/bash +.SHELLFLAGS = -o pipefail -c +export CDK_APP_NAME=PsuStatelessApp +export CDK_CONFIG_stackName=${stack_name} +export CDK_CONFIG_versionNumber=undefined +export CDK_CONFIG_commitId=undefined +export CDK_CONFIG_isPullRequest=true +export CDK_CONFIG_environment=dev +export CDK_CONFIG_logRetentionInDays=30 +export CDK_CONFIG_logLevel=DEBUG + +guard-%: + @ if [ "${${*}}" = "" ]; then \ + echo "Environment variable $* not set"; \ + exit 1; \ + fi + .PHONY: install build test publish release clean lint compile install: install-node install-python install-hooks @@ -156,6 +173,7 @@ compile-specification: compile: compile-node compile-specification lint-node: compile-node + npm run lint --workspace packages/cdk npm run lint --workspace packages/updatePrescriptionStatus npm run lint --workspace packages/gsul npm run lint --workspace packages/nhsd-psu-sandbox @@ -217,12 +235,29 @@ clean: rm -rf packages/common/testing/lib rm -rf packages/common/middyErrorHandler/lib rm -rf packages/common/commonTypes/lib + rm -rf packages/cdk/lib rm -rf .aws-sam + rm -rf cdk.out deep-clean: clean rm -rf venv find . -name 'node_modules' -type d -prune -exec rm -rf '{}' + poetry env remove --all +cdk-deploy: + REQUIRE_APPROVAL="$${REQUIRE_APPROVAL:-any-change}" && \ + npm run cdk-deploy --workspace packages/cdk + +cdk-synth: + CDK_CONFIG_stackName=psu-api \ + npm run cdk-synth --workspace packages/cdk + +cdk-diff: + npm run cdk-diff --workspace packages/cdk + +cdk-watch: + REQUIRE_APPROVAL="$${REQUIRE_APPROVAL:-any-change}" && \ + npm run cdk-watch --workspace packages/cdk + %: @$(MAKE) -f /usr/local/share/eps/Mk/common.mk $@ diff --git a/package-lock.json b/package-lock.json index ed950f9e7e..ba69f5bb37 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "1.0.0", "license": "MIT", "workspaces": [ + "packages/cdk", "packages/gsul", "packages/nhsd-psu-sandbox", "packages/specification", @@ -54,6 +55,132 @@ "typescript-eslint": "^8.57.2" } }, + "node_modules/@aws-cdk/asset-awscli-v1": { + "version": "2.2.273", + "resolved": "https://registry.npmjs.org/@aws-cdk/asset-awscli-v1/-/asset-awscli-v1-2.2.273.tgz", + "integrity": "sha512-X57HYUtHt9BQrlrzUNcMyRsDUCoakYNnY6qh5lNwRCHPtQoTfXmuISkfLk0AjLkcbS5lw1LLTQFiQhTDXfiTvg==", + "license": "Apache-2.0" + }, + "node_modules/@aws-cdk/asset-node-proxy-agent-v6": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@aws-cdk/asset-node-proxy-agent-v6/-/asset-node-proxy-agent-v6-2.1.1.tgz", + "integrity": "sha512-We4bmHaowOPHr+IQR4/FyTGjRfjgBj4ICMjtqmJeBDWad3Q/6St12NT07leNtyuukv2qMhtSZJQorD8KpKTwRA==", + "license": "Apache-2.0" + }, + "node_modules/@aws-cdk/cloud-assembly-schema": { + "version": "53.14.0", + "resolved": "https://registry.npmjs.org/@aws-cdk/cloud-assembly-schema/-/cloud-assembly-schema-53.14.0.tgz", + "integrity": "sha512-prx2sbFfKrVf3NNXMOmWq6lsIBeWQDIqMTILLAddGiidn9j7OnLUubknWpJlLozMveTvQELdI++nUwq6jwCm9g==", + "bundleDependencies": [ + "jsonschema", + "semver" + ], + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "jsonschema": "~1.4.1", + "semver": "^7.7.4" + }, + "engines": { + "node": ">= 18.0.0" + } + }, + "node_modules/@aws-cdk/cloud-assembly-schema/node_modules/jsonschema": { + "version": "1.4.1", + "inBundle": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/@aws-cdk/cloud-assembly-schema/node_modules/semver": { + "version": "7.7.4", + "inBundle": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@aws-crypto/crc32": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/crc32/-/crc32-5.2.0.tgz", + "integrity": "sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-crypto/crc32c": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/crc32c/-/crc32c-5.2.0.tgz", + "integrity": "sha512-+iWb8qaHLYKrNvGRbiYRHSdKRWhto5XlZUEBwDjYNf+ly5SVYG6zEoYIdxvf5R3zyeP16w4PLBn3rH1xc74Rag==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/sha1-browser": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha1-browser/-/sha1-browser-5.2.0.tgz", + "integrity": "sha512-OH6lveCFfcDjX4dbAvCFSYUjJZjDr/3XJ3xHtjn3Oj5b9RjojQo8npoLeA/bNwkOkrSQ0wgrHzXk4tDRxGKJeg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/supports-web-crypto": "^5.2.0", + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "@aws-sdk/util-locate-window": "^3.0.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/sha1-browser/node_modules/@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha1-browser/node_modules/@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha1-browser/node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/@aws-crypto/sha256-browser": { "version": "5.2.0", "license": "Apache-2.0", @@ -277,6 +404,57 @@ "node": ">=20.0.0" } }, + "node_modules/@aws-sdk/client-cloudformation": { + "version": "3.1027.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-cloudformation/-/client-cloudformation-3.1027.0.tgz", + "integrity": "sha512-Ml96/FeA5bon0QY2l5KGS2/2XPHbEB4RVJQIn6O7VfYI21RruIUU5W1zgH1Aq9C+W+rOSQ5tsT1ZfExN/5EJEg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "^3.973.27", + "@aws-sdk/credential-provider-node": "^3.972.30", + "@aws-sdk/middleware-host-header": "^3.972.9", + "@aws-sdk/middleware-logger": "^3.972.9", + "@aws-sdk/middleware-recursion-detection": "^3.972.10", + "@aws-sdk/middleware-user-agent": "^3.972.29", + "@aws-sdk/region-config-resolver": "^3.972.11", + "@aws-sdk/types": "^3.973.7", + "@aws-sdk/util-endpoints": "^3.996.6", + "@aws-sdk/util-user-agent-browser": "^3.972.9", + "@aws-sdk/util-user-agent-node": "^3.973.15", + "@smithy/config-resolver": "^4.4.14", + "@smithy/core": "^3.23.14", + "@smithy/fetch-http-handler": "^5.3.16", + "@smithy/hash-node": "^4.2.13", + "@smithy/invalid-dependency": "^4.2.13", + "@smithy/middleware-content-length": "^4.2.13", + "@smithy/middleware-endpoint": "^4.4.29", + "@smithy/middleware-retry": "^4.5.0", + "@smithy/middleware-serde": "^4.2.17", + "@smithy/middleware-stack": "^4.2.13", + "@smithy/node-config-provider": "^4.3.13", + "@smithy/node-http-handler": "^4.5.2", + "@smithy/protocol-http": "^5.3.13", + "@smithy/smithy-client": "^4.12.9", + "@smithy/types": "^4.14.0", + "@smithy/url-parser": "^4.2.13", + "@smithy/util-base64": "^4.3.2", + "@smithy/util-body-length-browser": "^4.2.2", + "@smithy/util-body-length-node": "^4.2.3", + "@smithy/util-defaults-mode-browser": "^4.3.45", + "@smithy/util-defaults-mode-node": "^4.2.49", + "@smithy/util-endpoints": "^3.3.4", + "@smithy/util-middleware": "^4.2.13", + "@smithy/util-retry": "^4.3.0", + "@smithy/util-utf8": "^4.2.2", + "@smithy/util-waiter": "^4.2.15", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, "node_modules/@aws-sdk/client-dynamodb": { "version": "3.1024.0", "resolved": "https://registry.npmjs.org/@aws-sdk/client-dynamodb/-/client-dynamodb-3.1024.0.tgz", @@ -330,11 +508,130 @@ "node": ">=20.0.0" } }, + "node_modules/@aws-sdk/client-route-53": { + "version": "3.1027.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-route-53/-/client-route-53-3.1027.0.tgz", + "integrity": "sha512-vS+s4cteBkYMp55goEYgDDkY06pS6vAj5NAHtvhIHPXsVb42o+kFwaiaqGqaTwAeRLV6pt6H9veTSLtAM7EaWQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "^3.973.27", + "@aws-sdk/credential-provider-node": "^3.972.30", + "@aws-sdk/middleware-host-header": "^3.972.9", + "@aws-sdk/middleware-logger": "^3.972.9", + "@aws-sdk/middleware-recursion-detection": "^3.972.10", + "@aws-sdk/middleware-sdk-route53": "^3.972.11", + "@aws-sdk/middleware-user-agent": "^3.972.29", + "@aws-sdk/region-config-resolver": "^3.972.11", + "@aws-sdk/types": "^3.973.7", + "@aws-sdk/util-endpoints": "^3.996.6", + "@aws-sdk/util-user-agent-browser": "^3.972.9", + "@aws-sdk/util-user-agent-node": "^3.973.15", + "@smithy/config-resolver": "^4.4.14", + "@smithy/core": "^3.23.14", + "@smithy/fetch-http-handler": "^5.3.16", + "@smithy/hash-node": "^4.2.13", + "@smithy/invalid-dependency": "^4.2.13", + "@smithy/middleware-content-length": "^4.2.13", + "@smithy/middleware-endpoint": "^4.4.29", + "@smithy/middleware-retry": "^4.5.0", + "@smithy/middleware-serde": "^4.2.17", + "@smithy/middleware-stack": "^4.2.13", + "@smithy/node-config-provider": "^4.3.13", + "@smithy/node-http-handler": "^4.5.2", + "@smithy/protocol-http": "^5.3.13", + "@smithy/smithy-client": "^4.12.9", + "@smithy/types": "^4.14.0", + "@smithy/url-parser": "^4.2.13", + "@smithy/util-base64": "^4.3.2", + "@smithy/util-body-length-browser": "^4.2.2", + "@smithy/util-body-length-node": "^4.2.3", + "@smithy/util-defaults-mode-browser": "^4.3.45", + "@smithy/util-defaults-mode-node": "^4.2.49", + "@smithy/util-endpoints": "^3.3.4", + "@smithy/util-middleware": "^4.2.13", + "@smithy/util-retry": "^4.3.0", + "@smithy/util-utf8": "^4.2.2", + "@smithy/util-waiter": "^4.2.15", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/client-s3": { + "version": "3.1027.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.1027.0.tgz", + "integrity": "sha512-g6kaFE/pW0Tsoq/BYg8PfXa1hIZQBmyoKtmJTgcbdyzYWiOOu8vj4PZUE2kS8myita6avaY8Ama5IodHJ39lPA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha1-browser": "5.2.0", + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "^3.973.27", + "@aws-sdk/credential-provider-node": "^3.972.30", + "@aws-sdk/middleware-bucket-endpoint": "^3.972.9", + "@aws-sdk/middleware-expect-continue": "^3.972.9", + "@aws-sdk/middleware-flexible-checksums": "^3.974.7", + "@aws-sdk/middleware-host-header": "^3.972.9", + "@aws-sdk/middleware-location-constraint": "^3.972.9", + "@aws-sdk/middleware-logger": "^3.972.9", + "@aws-sdk/middleware-recursion-detection": "^3.972.10", + "@aws-sdk/middleware-sdk-s3": "^3.972.28", + "@aws-sdk/middleware-ssec": "^3.972.9", + "@aws-sdk/middleware-user-agent": "^3.972.29", + "@aws-sdk/region-config-resolver": "^3.972.11", + "@aws-sdk/signature-v4-multi-region": "^3.996.16", + "@aws-sdk/types": "^3.973.7", + "@aws-sdk/util-endpoints": "^3.996.6", + "@aws-sdk/util-user-agent-browser": "^3.972.9", + "@aws-sdk/util-user-agent-node": "^3.973.15", + "@smithy/config-resolver": "^4.4.14", + "@smithy/core": "^3.23.14", + "@smithy/eventstream-serde-browser": "^4.2.13", + "@smithy/eventstream-serde-config-resolver": "^4.3.13", + "@smithy/eventstream-serde-node": "^4.2.13", + "@smithy/fetch-http-handler": "^5.3.16", + "@smithy/hash-blob-browser": "^4.2.14", + "@smithy/hash-node": "^4.2.13", + "@smithy/hash-stream-node": "^4.2.13", + "@smithy/invalid-dependency": "^4.2.13", + "@smithy/md5-js": "^4.2.13", + "@smithy/middleware-content-length": "^4.2.13", + "@smithy/middleware-endpoint": "^4.4.29", + "@smithy/middleware-retry": "^4.5.0", + "@smithy/middleware-serde": "^4.2.17", + "@smithy/middleware-stack": "^4.2.13", + "@smithy/node-config-provider": "^4.3.13", + "@smithy/node-http-handler": "^4.5.2", + "@smithy/protocol-http": "^5.3.13", + "@smithy/smithy-client": "^4.12.9", + "@smithy/types": "^4.14.0", + "@smithy/url-parser": "^4.2.13", + "@smithy/util-base64": "^4.3.2", + "@smithy/util-body-length-browser": "^4.2.2", + "@smithy/util-body-length-node": "^4.2.3", + "@smithy/util-defaults-mode-browser": "^4.3.45", + "@smithy/util-defaults-mode-node": "^4.2.49", + "@smithy/util-endpoints": "^3.3.4", + "@smithy/util-middleware": "^4.2.13", + "@smithy/util-retry": "^4.3.0", + "@smithy/util-stream": "^4.5.22", + "@smithy/util-utf8": "^4.2.2", + "@smithy/util-waiter": "^4.2.15", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, "node_modules/@aws-sdk/client-secrets-manager": { "version": "3.1019.0", "resolved": "https://registry.npmjs.org/@aws-sdk/client-secrets-manager/-/client-secrets-manager-3.1019.0.tgz", "integrity": "sha512-pocE77Q7wmnt8grxi0qNKUIq05GW1USIqZ6jwr/pC9zd5lwp9BHIFWxg/pkJ4ffNPbX9LekimpoL8IYl3ScWbQ==", "license": "Apache-2.0", + "peer": true, "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", @@ -484,22 +781,22 @@ } }, "node_modules/@aws-sdk/core": { - "version": "3.973.26", - "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.973.26.tgz", - "integrity": "sha512-A/E6n2W42ruU+sfWk+mMUOyVXbsSgGrY3MJ9/0Az5qUdG67y8I6HYzzoAa+e/lzxxl1uCYmEL6BTMi9ZiZnplQ==", + "version": "3.973.27", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.973.27.tgz", + "integrity": "sha512-CUZ5m8hwMCH6OYI4Li/WgMfIEx10Q2PLI9Y3XOUTPGZJ53aZ0007jCv+X/ywsaERyKPdw5MRZWk877roQksQ4A==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "^3.973.6", - "@aws-sdk/xml-builder": "^3.972.16", - "@smithy/core": "^3.23.13", - "@smithy/node-config-provider": "^4.3.12", - "@smithy/property-provider": "^4.2.12", - "@smithy/protocol-http": "^5.3.12", - "@smithy/signature-v4": "^5.3.12", - "@smithy/smithy-client": "^4.12.8", - "@smithy/types": "^4.13.1", + "@aws-sdk/types": "^3.973.7", + "@aws-sdk/xml-builder": "^3.972.17", + "@smithy/core": "^3.23.14", + "@smithy/node-config-provider": "^4.3.13", + "@smithy/property-provider": "^4.2.13", + "@smithy/protocol-http": "^5.3.13", + "@smithy/signature-v4": "^5.3.13", + "@smithy/smithy-client": "^4.12.9", + "@smithy/types": "^4.14.0", "@smithy/util-base64": "^4.3.2", - "@smithy/util-middleware": "^4.2.12", + "@smithy/util-middleware": "^4.2.13", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" }, @@ -507,16 +804,29 @@ "node": ">=20.0.0" } }, + "node_modules/@aws-sdk/crc64-nvme": { + "version": "3.972.6", + "resolved": "https://registry.npmjs.org/@aws-sdk/crc64-nvme/-/crc64-nvme-3.972.6.tgz", + "integrity": "sha512-NMbiqKdruhwwgI6nzBVe2jWMkXjaoQz2YOs3rFX+2F3gGyrJDkDPwMpV/RsTFeq2vAQ055wZNtOXFK4NYSkM8g==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, "node_modules/@aws-sdk/credential-provider-env": { - "version": "3.972.24", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.972.24.tgz", - "integrity": "sha512-FWg8uFmT6vQM7VuzELzwVo5bzExGaKHdubn0StjgrcU5FvuLExUe+k06kn/40uKv59rYzhez8eFNM4yYE/Yb/w==", + "version": "3.972.25", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.972.25.tgz", + "integrity": "sha512-6QfI0wv4jpG5CrdO/AO0JfZ2ux+tKwJPrUwmvxXF50vI5KIypKVGNF6b4vlkYEnKumDTI1NX2zUBi8JoU5QU3A==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.973.26", - "@aws-sdk/types": "^3.973.6", - "@smithy/property-provider": "^4.2.12", - "@smithy/types": "^4.13.1", + "@aws-sdk/core": "^3.973.27", + "@aws-sdk/types": "^3.973.7", + "@smithy/property-provider": "^4.2.13", + "@smithy/types": "^4.14.0", "tslib": "^2.6.2" }, "engines": { @@ -524,20 +834,20 @@ } }, "node_modules/@aws-sdk/credential-provider-http": { - "version": "3.972.26", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.26.tgz", - "integrity": "sha512-CY4ppZ+qHYqcXqBVi//sdHST1QK3KzOEiLtpLsc9W2k2vfZPKExGaQIsOwcyvjpjUEolotitmd3mUNY56IwDEA==", + "version": "3.972.27", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.27.tgz", + "integrity": "sha512-3V3Usj9Gs93h865DqN4M2NWJhC5kXU9BvZskfN3+69omuYlE3TZxOEcVQtBGLOloJB7BVfJKXVLqeNhOzHqSlQ==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.973.26", - "@aws-sdk/types": "^3.973.6", - "@smithy/fetch-http-handler": "^5.3.15", - "@smithy/node-http-handler": "^4.5.1", - "@smithy/property-provider": "^4.2.12", - "@smithy/protocol-http": "^5.3.12", - "@smithy/smithy-client": "^4.12.8", - "@smithy/types": "^4.13.1", - "@smithy/util-stream": "^4.5.21", + "@aws-sdk/core": "^3.973.27", + "@aws-sdk/types": "^3.973.7", + "@smithy/fetch-http-handler": "^5.3.16", + "@smithy/node-http-handler": "^4.5.2", + "@smithy/property-provider": "^4.2.13", + "@smithy/protocol-http": "^5.3.13", + "@smithy/smithy-client": "^4.12.9", + "@smithy/types": "^4.14.0", + "@smithy/util-stream": "^4.5.22", "tslib": "^2.6.2" }, "engines": { @@ -545,24 +855,24 @@ } }, "node_modules/@aws-sdk/credential-provider-ini": { - "version": "3.972.28", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.972.28.tgz", - "integrity": "sha512-wXYvq3+uQcZV7k+bE4yDXCTBdzWTU9x/nMiKBfzInmv6yYK1veMK0AKvRfRBd72nGWYKcL6AxwiPg9z/pYlgpw==", + "version": "3.972.29", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.972.29.tgz", + "integrity": "sha512-SiBuAnXecCbT/OpAf3vqyI/AVE3mTaYr9ShXLybxZiPLBiPCCOIWSGAtYYGQWMRvobBTiqOewaB+wcgMMZI2Aw==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.973.26", - "@aws-sdk/credential-provider-env": "^3.972.24", - "@aws-sdk/credential-provider-http": "^3.972.26", - "@aws-sdk/credential-provider-login": "^3.972.28", - "@aws-sdk/credential-provider-process": "^3.972.24", - "@aws-sdk/credential-provider-sso": "^3.972.28", - "@aws-sdk/credential-provider-web-identity": "^3.972.28", - "@aws-sdk/nested-clients": "^3.996.18", - "@aws-sdk/types": "^3.973.6", - "@smithy/credential-provider-imds": "^4.2.12", - "@smithy/property-provider": "^4.2.12", - "@smithy/shared-ini-file-loader": "^4.4.7", - "@smithy/types": "^4.13.1", + "@aws-sdk/core": "^3.973.27", + "@aws-sdk/credential-provider-env": "^3.972.25", + "@aws-sdk/credential-provider-http": "^3.972.27", + "@aws-sdk/credential-provider-login": "^3.972.29", + "@aws-sdk/credential-provider-process": "^3.972.25", + "@aws-sdk/credential-provider-sso": "^3.972.29", + "@aws-sdk/credential-provider-web-identity": "^3.972.29", + "@aws-sdk/nested-clients": "^3.996.19", + "@aws-sdk/types": "^3.973.7", + "@smithy/credential-provider-imds": "^4.2.13", + "@smithy/property-provider": "^4.2.13", + "@smithy/shared-ini-file-loader": "^4.4.8", + "@smithy/types": "^4.14.0", "tslib": "^2.6.2" }, "engines": { @@ -570,18 +880,18 @@ } }, "node_modules/@aws-sdk/credential-provider-login": { - "version": "3.972.28", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.28.tgz", - "integrity": "sha512-ZSTfO6jqUTCysbdBPtEX5OUR//3rbD0lN7jO3sQeS2Gjr/Y+DT6SbIJ0oT2cemNw3UzKu97sNONd1CwNMthuZQ==", + "version": "3.972.29", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.29.tgz", + "integrity": "sha512-OGOslTbOlxXexKMqhxCEbBQbUIfuhGxU5UXw3Fm56ypXHvrXH4aTt/xb5Y884LOoteP1QST1lVZzHfcTnWhiPQ==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.973.26", - "@aws-sdk/nested-clients": "^3.996.18", - "@aws-sdk/types": "^3.973.6", - "@smithy/property-provider": "^4.2.12", - "@smithy/protocol-http": "^5.3.12", - "@smithy/shared-ini-file-loader": "^4.4.7", - "@smithy/types": "^4.13.1", + "@aws-sdk/core": "^3.973.27", + "@aws-sdk/nested-clients": "^3.996.19", + "@aws-sdk/types": "^3.973.7", + "@smithy/property-provider": "^4.2.13", + "@smithy/protocol-http": "^5.3.13", + "@smithy/shared-ini-file-loader": "^4.4.8", + "@smithy/types": "^4.14.0", "tslib": "^2.6.2" }, "engines": { @@ -589,22 +899,22 @@ } }, "node_modules/@aws-sdk/credential-provider-node": { - "version": "3.972.29", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.29.tgz", - "integrity": "sha512-clSzDcvndpFJAggLDnDb36sPdlZYyEs5Zm6zgZjjUhwsJgSWiWKwFIXUVBcbruidNyBdbpOv2tNDL9sX8y3/0g==", + "version": "3.972.30", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.30.tgz", + "integrity": "sha512-FMnAnWxc8PG+ZrZ2OBKzY4luCUJhe9CG0B9YwYr4pzrYGLXBS2rl+UoUvjGbAwiptxRL6hyA3lFn03Bv1TLqTw==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/credential-provider-env": "^3.972.24", - "@aws-sdk/credential-provider-http": "^3.972.26", - "@aws-sdk/credential-provider-ini": "^3.972.28", - "@aws-sdk/credential-provider-process": "^3.972.24", - "@aws-sdk/credential-provider-sso": "^3.972.28", - "@aws-sdk/credential-provider-web-identity": "^3.972.28", - "@aws-sdk/types": "^3.973.6", - "@smithy/credential-provider-imds": "^4.2.12", - "@smithy/property-provider": "^4.2.12", - "@smithy/shared-ini-file-loader": "^4.4.7", - "@smithy/types": "^4.13.1", + "@aws-sdk/credential-provider-env": "^3.972.25", + "@aws-sdk/credential-provider-http": "^3.972.27", + "@aws-sdk/credential-provider-ini": "^3.972.29", + "@aws-sdk/credential-provider-process": "^3.972.25", + "@aws-sdk/credential-provider-sso": "^3.972.29", + "@aws-sdk/credential-provider-web-identity": "^3.972.29", + "@aws-sdk/types": "^3.973.7", + "@smithy/credential-provider-imds": "^4.2.13", + "@smithy/property-provider": "^4.2.13", + "@smithy/shared-ini-file-loader": "^4.4.8", + "@smithy/types": "^4.14.0", "tslib": "^2.6.2" }, "engines": { @@ -612,16 +922,16 @@ } }, "node_modules/@aws-sdk/credential-provider-process": { - "version": "3.972.24", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.972.24.tgz", - "integrity": "sha512-Q2k/XLrFXhEztPHqj4SLCNID3hEPdlhh1CDLBpNnM+1L8fq7P+yON9/9M1IGN/dA5W45v44ylERfXtDAlmMNmw==", + "version": "3.972.25", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.972.25.tgz", + "integrity": "sha512-HR7ynNRdNhNsdVCOCegy1HsfsRzozCOPtD3RzzT1JouuaHobWyRfJzCBue/3jP7gECHt+kQyZUvwg/cYLWurNQ==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.973.26", - "@aws-sdk/types": "^3.973.6", - "@smithy/property-provider": "^4.2.12", - "@smithy/shared-ini-file-loader": "^4.4.7", - "@smithy/types": "^4.13.1", + "@aws-sdk/core": "^3.973.27", + "@aws-sdk/types": "^3.973.7", + "@smithy/property-provider": "^4.2.13", + "@smithy/shared-ini-file-loader": "^4.4.8", + "@smithy/types": "^4.14.0", "tslib": "^2.6.2" }, "engines": { @@ -629,18 +939,18 @@ } }, "node_modules/@aws-sdk/credential-provider-sso": { - "version": "3.972.28", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.972.28.tgz", - "integrity": "sha512-IoUlmKMLEITFn1SiCTjPfR6KrE799FBo5baWyk/5Ppar2yXZoUdaRqZzJzK6TcJxx450M8m8DbpddRVYlp5R/A==", + "version": "3.972.29", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.972.29.tgz", + "integrity": "sha512-HWv4SEq3jZDYPlwryZVef97+U8CxxRos5mK8sgGO1dQaFZpV5giZLzqGE5hkDmh2csYcBO2uf5XHjPTpZcJlig==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.973.26", - "@aws-sdk/nested-clients": "^3.996.18", - "@aws-sdk/token-providers": "3.1021.0", - "@aws-sdk/types": "^3.973.6", - "@smithy/property-provider": "^4.2.12", - "@smithy/shared-ini-file-loader": "^4.4.7", - "@smithy/types": "^4.13.1", + "@aws-sdk/core": "^3.973.27", + "@aws-sdk/nested-clients": "^3.996.19", + "@aws-sdk/token-providers": "3.1026.0", + "@aws-sdk/types": "^3.973.7", + "@smithy/property-provider": "^4.2.13", + "@smithy/shared-ini-file-loader": "^4.4.8", + "@smithy/types": "^4.14.0", "tslib": "^2.6.2" }, "engines": { @@ -648,17 +958,17 @@ } }, "node_modules/@aws-sdk/credential-provider-web-identity": { - "version": "3.972.28", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.972.28.tgz", - "integrity": "sha512-d+6h0SD8GGERzKe27v5rOzNGKOl0D+l0bWJdqrxH8WSQzHzjsQFIAPgIeOTUwBHVsKKwtSxc91K/SWax6XgswQ==", + "version": "3.972.29", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.972.29.tgz", + "integrity": "sha512-PdMBza1WEKEUPFEmMGCfnU2RYCz9MskU2e8JxjyUOsMKku7j9YaDKvbDi2dzC0ihFoM6ods2SbhfAAro+Gwlew==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.973.26", - "@aws-sdk/nested-clients": "^3.996.18", - "@aws-sdk/types": "^3.973.6", - "@smithy/property-provider": "^4.2.12", - "@smithy/shared-ini-file-loader": "^4.4.7", - "@smithy/types": "^4.13.1", + "@aws-sdk/core": "^3.973.27", + "@aws-sdk/nested-clients": "^3.996.19", + "@aws-sdk/types": "^3.973.7", + "@smithy/property-provider": "^4.2.13", + "@smithy/shared-ini-file-loader": "^4.4.8", + "@smithy/types": "^4.14.0", "tslib": "^2.6.2" }, "engines": { @@ -715,6 +1025,24 @@ "@aws-sdk/client-dynamodb": "^3.1019.0" } }, + "node_modules/@aws-sdk/middleware-bucket-endpoint": { + "version": "3.972.9", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-bucket-endpoint/-/middleware-bucket-endpoint-3.972.9.tgz", + "integrity": "sha512-COToYKgquDyligbcAep7ygs48RK+mwe/IYprq4+TSrVFzNOYmzWvHf6werpnKV5VYpRiwdn+Wa5ZXkPqLVwcTg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.7", + "@aws-sdk/util-arn-parser": "^3.972.3", + "@smithy/node-config-provider": "^4.3.13", + "@smithy/protocol-http": "^5.3.13", + "@smithy/types": "^4.14.0", + "@smithy/util-config-provider": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, "node_modules/@aws-sdk/middleware-endpoint-discovery": { "version": "3.972.9", "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-endpoint-discovery/-/middleware-endpoint-discovery-3.972.9.tgz", @@ -732,15 +1060,69 @@ "node": ">=20.0.0" } }, + "node_modules/@aws-sdk/middleware-expect-continue": { + "version": "3.972.9", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-expect-continue/-/middleware-expect-continue-3.972.9.tgz", + "integrity": "sha512-V/FNCjFxnh4VGu+HdSiW4Yg5GELihA1MIDSAdsEPvuayXBVmr0Jaa6jdLAZLH38KYXl/vVjri9DQJWnTAujHEA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.7", + "@smithy/protocol-http": "^5.3.13", + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/middleware-flexible-checksums": { + "version": "3.974.7", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-flexible-checksums/-/middleware-flexible-checksums-3.974.7.tgz", + "integrity": "sha512-uU4/ch2CLHB8Phu1oTKnnQ4e8Ujqi49zEnQYBhWYT53zfFvtJCdGsaOoypBr8Fm/pmCBssRmGoIQ4sixgdLP9w==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/crc32": "5.2.0", + "@aws-crypto/crc32c": "5.2.0", + "@aws-crypto/util": "5.2.0", + "@aws-sdk/core": "^3.973.27", + "@aws-sdk/crc64-nvme": "^3.972.6", + "@aws-sdk/types": "^3.973.7", + "@smithy/is-array-buffer": "^4.2.2", + "@smithy/node-config-provider": "^4.3.13", + "@smithy/protocol-http": "^5.3.13", + "@smithy/types": "^4.14.0", + "@smithy/util-middleware": "^4.2.13", + "@smithy/util-stream": "^4.5.22", + "@smithy/util-utf8": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, "node_modules/@aws-sdk/middleware-host-header": { - "version": "3.972.8", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.972.8.tgz", - "integrity": "sha512-wAr2REfKsqoKQ+OkNqvOShnBoh+nkPurDKW7uAeVSu6kUECnWlSJiPvnoqxGlfousEY/v9LfS9sNc46hjSYDIQ==", + "version": "3.972.9", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.972.9.tgz", + "integrity": "sha512-je5vRdNw4SkuTnmRbFZLdye4sQ0faLt8kwka5wnnSU30q1mHO4X+idGEJOOE+Tn1ME7Oryn05xxkDvIb3UaLaQ==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "^3.973.6", - "@smithy/protocol-http": "^5.3.12", - "@smithy/types": "^4.13.1", + "@aws-sdk/types": "^3.973.7", + "@smithy/protocol-http": "^5.3.13", + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/middleware-location-constraint": { + "version": "3.972.9", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-location-constraint/-/middleware-location-constraint-3.972.9.tgz", + "integrity": "sha512-TyfOi2XNdOZpNKeTJwRUsVAGa+14nkyMb2VVGG+eDgcWG/ed6+NUo72N3hT6QJioxym80NSinErD+LBRF0Ir1w==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.7", + "@smithy/types": "^4.14.0", "tslib": "^2.6.2" }, "engines": { @@ -748,13 +1130,13 @@ } }, "node_modules/@aws-sdk/middleware-logger": { - "version": "3.972.8", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.972.8.tgz", - "integrity": "sha512-CWl5UCM57WUFaFi5kB7IBY1UmOeLvNZAZ2/OZ5l20ldiJ3TiIz1pC65gYj8X0BCPWkeR1E32mpsCk1L1I4n+lA==", + "version": "3.972.9", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.972.9.tgz", + "integrity": "sha512-HsVgDrruhqI28RkaXALm8grJ7Agc1wF6Et0xh6pom8NdO2VdO/SD9U/tPwUjewwK/pVoka+EShBxyCvgsPCtog==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "^3.973.6", - "@smithy/types": "^4.13.1", + "@aws-sdk/types": "^3.973.7", + "@smithy/types": "^4.14.0", "tslib": "^2.6.2" }, "engines": { @@ -762,99 +1144,152 @@ } }, "node_modules/@aws-sdk/middleware-recursion-detection": { - "version": "3.972.9", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.972.9.tgz", - "integrity": "sha512-/Wt5+CT8dpTFQxEJ9iGy/UGrXr7p2wlIOEHvIr/YcHYByzoLjrqkYqXdJjd9UIgWjv7eqV2HnFJen93UTuwfTQ==", + "version": "3.972.10", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.972.10.tgz", + "integrity": "sha512-RVQQbq5orQ/GHUnXvqEOj2HHPBJm+mM+ySwZKS5UaLBwra5ugRtiH09PLUoOZRl7a1YzaOzXSuGbn9iD5j60WQ==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "^3.973.6", + "@aws-sdk/types": "^3.973.7", "@aws/lambda-invoke-store": "^0.2.2", - "@smithy/protocol-http": "^5.3.12", - "@smithy/types": "^4.13.1", + "@smithy/protocol-http": "^5.3.13", + "@smithy/types": "^4.14.0", "tslib": "^2.6.2" }, "engines": { "node": ">=20.0.0" } }, - "node_modules/@aws-sdk/middleware-sdk-sqs": { - "version": "3.972.17", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-sqs/-/middleware-sdk-sqs-3.972.17.tgz", - "integrity": "sha512-LnzPRRoDXGtlFV2G1p2rsY6fRKrbf6Pvvc21KliSLw3+NmQca2+Aa1QIMRbpQvZYedsSqkGYwxe+qvXwQ2uxDw==", + "node_modules/@aws-sdk/middleware-sdk-route53": { + "version": "3.972.11", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-route53/-/middleware-sdk-route53-3.972.11.tgz", + "integrity": "sha512-5nvSVRgcbxR677ON2+AAWPOJkyqyyjx9Pi9EBSNAkU/G7W4/tGPs9qxjBnuJx5YEGJX+XoTGz5qq0CWmXGJHkA==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "^3.973.6", - "@smithy/smithy-client": "^4.12.7", - "@smithy/types": "^4.13.1", - "@smithy/util-hex-encoding": "^4.2.2", - "@smithy/util-utf8": "^4.2.2", + "@aws-sdk/types": "^3.973.7", + "@smithy/types": "^4.14.0", "tslib": "^2.6.2" }, "engines": { "node": ">=20.0.0" } }, - "node_modules/@aws-sdk/middleware-user-agent": { + "node_modules/@aws-sdk/middleware-sdk-s3": { "version": "3.972.28", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.972.28.tgz", - "integrity": "sha512-cfWZFlVh7Va9lRay4PN2A9ARFzaBYcA097InT5M2CdRS05ECF5yaz86jET8Wsl2WcyKYEvVr/QNmKtYtafUHtQ==", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.972.28.tgz", + "integrity": "sha512-qJHcJQH9UNPUrnPlRtCozKjtqAaypQ5IgQxTNoPsVYIQeuwNIA8Rwt3NvGij1vCDYDfCmZaPLpnJEHlZXeFqmg==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.973.26", - "@aws-sdk/types": "^3.973.6", - "@aws-sdk/util-endpoints": "^3.996.5", - "@smithy/core": "^3.23.13", - "@smithy/protocol-http": "^5.3.12", - "@smithy/types": "^4.13.1", - "@smithy/util-retry": "^4.2.13", + "@aws-sdk/core": "^3.973.27", + "@aws-sdk/types": "^3.973.7", + "@aws-sdk/util-arn-parser": "^3.972.3", + "@smithy/core": "^3.23.14", + "@smithy/node-config-provider": "^4.3.13", + "@smithy/protocol-http": "^5.3.13", + "@smithy/signature-v4": "^5.3.13", + "@smithy/smithy-client": "^4.12.9", + "@smithy/types": "^4.14.0", + "@smithy/util-config-provider": "^4.2.2", + "@smithy/util-middleware": "^4.2.13", + "@smithy/util-stream": "^4.5.22", + "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" }, "engines": { "node": ">=20.0.0" } }, - "node_modules/@aws-sdk/nested-clients": { - "version": "3.996.18", - "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.996.18.tgz", - "integrity": "sha512-c7ZSIXrESxHKx2Mcopgd8AlzZgoXMr20fkx5ViPWPOLBvmyhw9VwJx/Govg8Ef/IhEon5R9l53Z8fdYSEmp6VA==", + "node_modules/@aws-sdk/middleware-sdk-sqs": { + "version": "3.972.17", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-sqs/-/middleware-sdk-sqs-3.972.17.tgz", + "integrity": "sha512-LnzPRRoDXGtlFV2G1p2rsY6fRKrbf6Pvvc21KliSLw3+NmQca2+Aa1QIMRbpQvZYedsSqkGYwxe+qvXwQ2uxDw==", "license": "Apache-2.0", "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "^3.973.26", - "@aws-sdk/middleware-host-header": "^3.972.8", - "@aws-sdk/middleware-logger": "^3.972.8", - "@aws-sdk/middleware-recursion-detection": "^3.972.9", - "@aws-sdk/middleware-user-agent": "^3.972.28", - "@aws-sdk/region-config-resolver": "^3.972.10", "@aws-sdk/types": "^3.973.6", - "@aws-sdk/util-endpoints": "^3.996.5", - "@aws-sdk/util-user-agent-browser": "^3.972.8", - "@aws-sdk/util-user-agent-node": "^3.973.14", - "@smithy/config-resolver": "^4.4.13", - "@smithy/core": "^3.23.13", - "@smithy/fetch-http-handler": "^5.3.15", - "@smithy/hash-node": "^4.2.12", - "@smithy/invalid-dependency": "^4.2.12", - "@smithy/middleware-content-length": "^4.2.12", - "@smithy/middleware-endpoint": "^4.4.28", - "@smithy/middleware-retry": "^4.4.46", - "@smithy/middleware-serde": "^4.2.16", - "@smithy/middleware-stack": "^4.2.12", - "@smithy/node-config-provider": "^4.3.12", - "@smithy/node-http-handler": "^4.5.1", - "@smithy/protocol-http": "^5.3.12", - "@smithy/smithy-client": "^4.12.8", + "@smithy/smithy-client": "^4.12.7", "@smithy/types": "^4.13.1", - "@smithy/url-parser": "^4.2.12", + "@smithy/util-hex-encoding": "^4.2.2", + "@smithy/util-utf8": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/middleware-ssec": { + "version": "3.972.9", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-ssec/-/middleware-ssec-3.972.9.tgz", + "integrity": "sha512-wSA2BR7L0CyBNDJeSrleIIzC+DzL93YNTdfU0KPGLiocK6YsRv1nPAzPF+BFSdcs0Qa5ku5Kcf4KvQcWwKGenQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.7", + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/middleware-user-agent": { + "version": "3.972.29", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.972.29.tgz", + "integrity": "sha512-f/sIRzuTfEjg6NsbMYvye2VsmnQoNgntntleQyx5uGacUYzszbfIlO3GcI6G6daWUmTm0IDZc11qMHWwF0o0mQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.973.27", + "@aws-sdk/types": "^3.973.7", + "@aws-sdk/util-endpoints": "^3.996.6", + "@smithy/core": "^3.23.14", + "@smithy/protocol-http": "^5.3.13", + "@smithy/types": "^4.14.0", + "@smithy/util-retry": "^4.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients": { + "version": "3.996.19", + "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.996.19.tgz", + "integrity": "sha512-uFkmCDXvmQYLanlYdOFS0+MQWkrj9wPMt/ZCc/0J0fjPim6F5jBVBmEomvGY/j77ILW6GTPwN22Jc174Mhkw6Q==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "^3.973.27", + "@aws-sdk/middleware-host-header": "^3.972.9", + "@aws-sdk/middleware-logger": "^3.972.9", + "@aws-sdk/middleware-recursion-detection": "^3.972.10", + "@aws-sdk/middleware-user-agent": "^3.972.29", + "@aws-sdk/region-config-resolver": "^3.972.11", + "@aws-sdk/types": "^3.973.7", + "@aws-sdk/util-endpoints": "^3.996.6", + "@aws-sdk/util-user-agent-browser": "^3.972.9", + "@aws-sdk/util-user-agent-node": "^3.973.15", + "@smithy/config-resolver": "^4.4.14", + "@smithy/core": "^3.23.14", + "@smithy/fetch-http-handler": "^5.3.16", + "@smithy/hash-node": "^4.2.13", + "@smithy/invalid-dependency": "^4.2.13", + "@smithy/middleware-content-length": "^4.2.13", + "@smithy/middleware-endpoint": "^4.4.29", + "@smithy/middleware-retry": "^4.5.0", + "@smithy/middleware-serde": "^4.2.17", + "@smithy/middleware-stack": "^4.2.13", + "@smithy/node-config-provider": "^4.3.13", + "@smithy/node-http-handler": "^4.5.2", + "@smithy/protocol-http": "^5.3.13", + "@smithy/smithy-client": "^4.12.9", + "@smithy/types": "^4.14.0", + "@smithy/url-parser": "^4.2.13", "@smithy/util-base64": "^4.3.2", "@smithy/util-body-length-browser": "^4.2.2", "@smithy/util-body-length-node": "^4.2.3", - "@smithy/util-defaults-mode-browser": "^4.3.44", - "@smithy/util-defaults-mode-node": "^4.2.48", - "@smithy/util-endpoints": "^3.3.3", - "@smithy/util-middleware": "^4.2.12", - "@smithy/util-retry": "^4.2.13", + "@smithy/util-defaults-mode-browser": "^4.3.45", + "@smithy/util-defaults-mode-node": "^4.2.49", + "@smithy/util-endpoints": "^3.3.4", + "@smithy/util-middleware": "^4.2.13", + "@smithy/util-retry": "^4.3.0", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" }, @@ -863,15 +1298,32 @@ } }, "node_modules/@aws-sdk/region-config-resolver": { - "version": "3.972.10", - "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.972.10.tgz", - "integrity": "sha512-1dq9ToC6e070QvnVhhbAs3bb5r6cQ10gTVc6cyRV5uvQe7P138TV2uG2i6+Yok4bAkVAcx5AqkTEBUvWEtBlsQ==", + "version": "3.972.11", + "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.972.11.tgz", + "integrity": "sha512-6Q8B1dcx6BBqUTY1Mc/eROKA0FImEEY5VPSd6AGPEUf0ErjExz4snVqa9kNJSoVDV1rKaNf3qrWojgcKW+SdDg==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "^3.973.6", - "@smithy/config-resolver": "^4.4.13", - "@smithy/node-config-provider": "^4.3.12", - "@smithy/types": "^4.13.1", + "@aws-sdk/types": "^3.973.7", + "@smithy/config-resolver": "^4.4.14", + "@smithy/node-config-provider": "^4.3.13", + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/signature-v4-multi-region": { + "version": "3.996.16", + "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.996.16.tgz", + "integrity": "sha512-EMdXYB4r/k5RWq86fugjRhid5JA+Z6MpS7n4sij4u5/C+STrkvuf9aFu41rJA9MjUzxCLzv8U2XL8cH2GSRYpQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/middleware-sdk-s3": "^3.972.28", + "@aws-sdk/types": "^3.973.7", + "@smithy/protocol-http": "^5.3.13", + "@smithy/signature-v4": "^5.3.13", + "@smithy/types": "^4.14.0", "tslib": "^2.6.2" }, "engines": { @@ -879,17 +1331,17 @@ } }, "node_modules/@aws-sdk/token-providers": { - "version": "3.1021.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.1021.0.tgz", - "integrity": "sha512-TKY6h9spUk3OLs5v1oAgW9mAeBE3LAGNBwJokLy96wwmd4W2v/tYlXseProyed9ValDj2u1jK/4Rg1T+1NXyJA==", + "version": "3.1026.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.1026.0.tgz", + "integrity": "sha512-Ieq/HiRrbEtrYP387Nes0XlR7H1pJiJOZKv+QyQzMYpvTiDs0VKy2ZB3E2Zf+aFovWmeE7lRE4lXyF7dYM6GgA==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.973.26", - "@aws-sdk/nested-clients": "^3.996.18", - "@aws-sdk/types": "^3.973.6", - "@smithy/property-provider": "^4.2.12", - "@smithy/shared-ini-file-loader": "^4.4.7", - "@smithy/types": "^4.13.1", + "@aws-sdk/core": "^3.973.27", + "@aws-sdk/nested-clients": "^3.996.19", + "@aws-sdk/types": "^3.973.7", + "@smithy/property-provider": "^4.2.13", + "@smithy/shared-ini-file-loader": "^4.4.8", + "@smithy/types": "^4.14.0", "tslib": "^2.6.2" }, "engines": { @@ -897,12 +1349,24 @@ } }, "node_modules/@aws-sdk/types": { - "version": "3.973.6", - "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.973.6.tgz", - "integrity": "sha512-Atfcy4E++beKtwJHiDln2Nby8W/mam64opFPTiHEqgsthqeydFS1pY+OUlN1ouNOmf8ArPU/6cDS65anOP3KQw==", + "version": "3.973.7", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.973.7.tgz", + "integrity": "sha512-reXRwoJ6CfChoqAsBszUYajAF8Z2LRE+CRcKocvFSMpIiLOtYU3aJ9trmn6VVPAzbbY5LXF+FfmUslbXk1SYFg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/util-arn-parser": { + "version": "3.972.3", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-arn-parser/-/util-arn-parser-3.972.3.tgz", + "integrity": "sha512-HzSD8PMFrvgi2Kserxuff5VitNq2sgf3w9qxmskKDiDTThWfVteJxuCS9JXiPIPtmCrp+7N9asfIaVhBFORllA==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.13.1", "tslib": "^2.6.2" }, "engines": { @@ -925,15 +1389,15 @@ } }, "node_modules/@aws-sdk/util-endpoints": { - "version": "3.996.5", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.996.5.tgz", - "integrity": "sha512-Uh93L5sXFNbyR5sEPMzUU8tJ++Ku97EY4udmC01nB8Zu+xfBPwpIwJ6F7snqQeq8h2pf+8SGN5/NoytfKgYPIw==", + "version": "3.996.6", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.996.6.tgz", + "integrity": "sha512-2nUQ+2ih7CShuKHpGSIYvvAIOHy52dOZguYG36zptBukhw6iFwcvGfG0tes0oZFWQqEWvgZe9HLWaNlvXGdOrg==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "^3.973.6", - "@smithy/types": "^4.13.1", - "@smithy/url-parser": "^4.2.12", - "@smithy/util-endpoints": "^3.3.3", + "@aws-sdk/types": "^3.973.7", + "@smithy/types": "^4.14.0", + "@smithy/url-parser": "^4.2.13", + "@smithy/util-endpoints": "^3.3.4", "tslib": "^2.6.2" }, "engines": { @@ -951,27 +1415,27 @@ } }, "node_modules/@aws-sdk/util-user-agent-browser": { - "version": "3.972.8", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.972.8.tgz", - "integrity": "sha512-B3KGXJviV2u6Cdw2SDY2aDhoJkVfY/Q/Trwk2CMSkikE1Oi6gRzxhvhIfiRpHfmIsAhV4EA54TVEX8K6CbHbkA==", + "version": "3.972.9", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.972.9.tgz", + "integrity": "sha512-sn/LMzTbGjYqCCF24390WxPd6hkpoSptiUn5DzVp4cD71yqw+yGEGm1YCxyEoPXyc8qciM8UzLJcZBFslxo5Uw==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "^3.973.6", - "@smithy/types": "^4.13.1", + "@aws-sdk/types": "^3.973.7", + "@smithy/types": "^4.14.0", "bowser": "^2.11.0", "tslib": "^2.6.2" } }, "node_modules/@aws-sdk/util-user-agent-node": { - "version": "3.973.14", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.973.14.tgz", - "integrity": "sha512-vNSB/DYaPOyujVZBg/zUznH9QC142MaTHVmaFlF7uzzfg3CgT9f/l4C0Yi+vU/tbBhxVcXVB90Oohk5+o+ZbWw==", + "version": "3.973.15", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.973.15.tgz", + "integrity": "sha512-fYn3s9PtKdgQkczGZCFMgkNEe8aq1JCVbnRqjqN9RSVW43xn2RV9xdcZ3z01a48Jpkuh/xCmBKJxdLOo4Ozg7w==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/middleware-user-agent": "^3.972.28", - "@aws-sdk/types": "^3.973.6", - "@smithy/node-config-provider": "^4.3.12", - "@smithy/types": "^4.13.1", + "@aws-sdk/middleware-user-agent": "^3.972.29", + "@aws-sdk/types": "^3.973.7", + "@smithy/node-config-provider": "^4.3.13", + "@smithy/types": "^4.14.0", "@smithy/util-config-provider": "^4.2.2", "tslib": "^2.6.2" }, @@ -988,12 +1452,12 @@ } }, "node_modules/@aws-sdk/xml-builder": { - "version": "3.972.16", - "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.16.tgz", - "integrity": "sha512-iu2pyvaqmeatIJLURLqx9D+4jKAdTH20ntzB6BFwjyN7V960r4jK32mx0Zf7YbtOYAbmbtQfDNuL60ONinyw7A==", + "version": "3.972.17", + "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.17.tgz", + "integrity": "sha512-Ra7hjqAZf1OXRRMueB13qex7mFJRDK/pgCvdSFemXBT8KCGnQDPoKzHY1SjN+TjJVmnpSF14W5tJ1vDamFu+Gg==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.13.1", + "@smithy/types": "^4.14.0", "fast-xml-parser": "5.5.8", "tslib": "^2.6.2" }, @@ -1035,6 +1499,7 @@ "version": "7.28.4", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.3", @@ -2765,6 +3230,7 @@ "node_modules/@middy/validator/node_modules/ajv": { "version": "8.17.1", "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -2838,6 +3304,21 @@ "uninstall": "^0.0.0" } }, + "node_modules/@nhsdigital/eps-cdk-constructs": { + "version": "1.6.1", + "resolved": "https://npm.pkg.github.com/download/@nhsdigital/eps-cdk-constructs/1.6.1/c2758a114d62470b00072c95d246f5c22de70e7a", + "integrity": "sha512-UbYkbO7GYGtDkFfSCzQmseVp8H+EiPtM59EN3eHDTVghX9BcaxXfJf8kKhNUHeImG6V+hQITj+ofNczF/zkxJw==", + "license": "MIT", + "dependencies": { + "@aws-sdk/client-cloudformation": "^3.1018.0", + "@aws-sdk/client-route-53": "^3.1018.0", + "@aws-sdk/client-s3": "^3.1018.0", + "aws-cdk": "^2.1114.1", + "aws-cdk-lib": "^2.244.0", + "cdk-nag": "^2.37.52", + "constructs": "^10.6.0" + } + }, "node_modules/@noble/hashes": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", @@ -2877,6 +3358,7 @@ "version": "1.9.0", "dev": true, "license": "Apache-2.0", + "peer": true, "engines": { "node": ">=8.0.0" } @@ -3683,17 +4165,42 @@ "dev": true, "license": "(Unlicense OR Apache-2.0)" }, + "node_modules/@smithy/chunked-blob-reader": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/@smithy/chunked-blob-reader/-/chunked-blob-reader-5.2.2.tgz", + "integrity": "sha512-St+kVicSyayWQca+I1rGitaOEH6uKgE8IUWoYnnEX26SWdWQcL6LvMSD19Lg+vYHKdT9B2Zuu7rd3i6Wnyb/iw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/chunked-blob-reader-native": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/@smithy/chunked-blob-reader-native/-/chunked-blob-reader-native-4.2.3.tgz", + "integrity": "sha512-jA5k5Udn7Y5717L86h4EIv06wIr3xn8GM1qHRi/Nf31annXcXHJjBKvgztnbn2TxH3xWrPBfgwHsOwZf0UmQWw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-base64": "^4.3.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/@smithy/config-resolver": { - "version": "4.4.13", - "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-4.4.13.tgz", - "integrity": "sha512-iIzMC5NmOUP6WL6o8iPBjFhUhBZ9pPjpUpQYWMUFQqKyXXzOftbfK8zcQCz/jFV1Psmf05BK5ypx4K2r4Tnwdg==", + "version": "4.4.14", + "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-4.4.14.tgz", + "integrity": "sha512-N55f8mPEccpzKetUagdvmAy8oohf0J5cuj9jLI1TaSceRlq0pJsIZepY3kmAXAhyxqXPV6hDerDQhqQPKWgAoQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/node-config-provider": "^4.3.12", - "@smithy/types": "^4.13.1", + "@smithy/node-config-provider": "^4.3.13", + "@smithy/types": "^4.14.0", "@smithy/util-config-provider": "^4.2.2", - "@smithy/util-endpoints": "^3.3.3", - "@smithy/util-middleware": "^4.2.12", + "@smithy/util-endpoints": "^3.3.4", + "@smithy/util-middleware": "^4.2.13", "tslib": "^2.6.2" }, "engines": { @@ -3701,18 +4208,18 @@ } }, "node_modules/@smithy/core": { - "version": "3.23.13", - "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.23.13.tgz", - "integrity": "sha512-J+2TT9D6oGsUVXVEMvz8h2EmdVnkBiy2auCie4aSJMvKlzUtO5hqjEzXhoCUkIMo7gAYjbQcN0g/MMSXEhDs1Q==", + "version": "3.23.14", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.23.14.tgz", + "integrity": "sha512-vJ0IhpZxZAkFYOegMKSrxw7ujhhT2pass/1UEcZ4kfl5srTAqtPU5I7MdYQoreVas3204ykCiNhY1o7Xlz6Yyg==", "license": "Apache-2.0", "dependencies": { - "@smithy/protocol-http": "^5.3.12", - "@smithy/types": "^4.13.1", - "@smithy/url-parser": "^4.2.12", + "@smithy/protocol-http": "^5.3.13", + "@smithy/types": "^4.14.0", + "@smithy/url-parser": "^4.2.13", "@smithy/util-base64": "^4.3.2", "@smithy/util-body-length-browser": "^4.2.2", - "@smithy/util-middleware": "^4.2.12", - "@smithy/util-stream": "^4.5.21", + "@smithy/util-middleware": "^4.2.13", + "@smithy/util-stream": "^4.5.22", "@smithy/util-utf8": "^4.2.2", "@smithy/uuid": "^1.1.2", "tslib": "^2.6.2" @@ -3722,15 +4229,85 @@ } }, "node_modules/@smithy/credential-provider-imds": { - "version": "4.2.12", - "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.2.12.tgz", - "integrity": "sha512-cr2lR792vNZcYMriSIj+Um3x9KWrjcu98kn234xA6reOAFMmbRpQMOv8KPgEmLLtx3eldU6c5wALKFqNOhugmg==", + "version": "4.2.13", + "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.2.13.tgz", + "integrity": "sha512-wboCPijzf6RJKLOvnjDAiBxGSmSnGXj35o5ZAWKDaHa/cvQ5U3ZJ13D4tMCE8JG4dxVAZFy/P0x/V9CwwdfULQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/node-config-provider": "^4.3.12", - "@smithy/property-provider": "^4.2.12", - "@smithy/types": "^4.13.1", - "@smithy/url-parser": "^4.2.12", + "@smithy/node-config-provider": "^4.3.13", + "@smithy/property-provider": "^4.2.13", + "@smithy/types": "^4.14.0", + "@smithy/url-parser": "^4.2.13", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-codec": { + "version": "4.2.13", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-codec/-/eventstream-codec-4.2.13.tgz", + "integrity": "sha512-vYahwBAtRaAcFbOmE9aLr12z7RiHYDSLcnogSdxfm7kKfsNa3wH+NU5r7vTeB5rKvLsWyPjVX8iH94brP7umiQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/crc32": "5.2.0", + "@smithy/types": "^4.14.0", + "@smithy/util-hex-encoding": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-browser": { + "version": "4.2.13", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-browser/-/eventstream-serde-browser-4.2.13.tgz", + "integrity": "sha512-wwybfcOX0tLqCcBP378TIU9IqrDuZq/tDV48LlZNydMpCnqnYr+hWBAYbRE+rFFf/p7IkDJySM3bgiMKP2ihPg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/eventstream-serde-universal": "^4.2.13", + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-config-resolver": { + "version": "4.3.13", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-config-resolver/-/eventstream-serde-config-resolver-4.3.13.tgz", + "integrity": "sha512-ied1lO559PtAsMJzg2TKRlctLnEi1PfkNeMMpdwXDImk1zV9uvS/Oxoy/vcy9uv1GKZAjDAB5xT6ziE9fzm5wA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-node": { + "version": "4.2.13", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-node/-/eventstream-serde-node-4.2.13.tgz", + "integrity": "sha512-hFyK+ORJrxAN3RYoaD6+gsGDQjeix8HOEkosoajvXYZ4VeqonM3G4jd9IIRm/sWGXUKmudkY9KdYjzosUqdM8A==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/eventstream-serde-universal": "^4.2.13", + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-universal": { + "version": "4.2.13", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-universal/-/eventstream-serde-universal-4.2.13.tgz", + "integrity": "sha512-kRrq4EKLGeOxhC2CBEhRNcu1KSzNJzYY7RK3S7CxMPgB5dRrv55WqQOtRwQxQLC04xqORFLUgnDlc6xrNUULaA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/eventstream-codec": "^4.2.13", + "@smithy/types": "^4.14.0", "tslib": "^2.6.2" }, "engines": { @@ -3738,14 +4315,14 @@ } }, "node_modules/@smithy/fetch-http-handler": { - "version": "5.3.15", - "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.3.15.tgz", - "integrity": "sha512-T4jFU5N/yiIfrtrsb9uOQn7RdELdM/7HbyLNr6uO/mpkj1ctiVs7CihVr51w4LyQlXWDpXFn4BElf1WmQvZu/A==", + "version": "5.3.16", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.3.16.tgz", + "integrity": "sha512-nYDRUIvNd4mFmuXraRWt6w5UsZTNqtj4hXJA/iiOD4tuseIdLP9Lq38teH/SZTcIFCa2f+27o7hYpIsWktJKEQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/protocol-http": "^5.3.12", - "@smithy/querystring-builder": "^4.2.12", - "@smithy/types": "^4.13.1", + "@smithy/protocol-http": "^5.3.13", + "@smithy/querystring-builder": "^4.2.13", + "@smithy/types": "^4.14.0", "@smithy/util-base64": "^4.3.2", "tslib": "^2.6.2" }, @@ -3753,13 +4330,28 @@ "node": ">=18.0.0" } }, + "node_modules/@smithy/hash-blob-browser": { + "version": "4.2.14", + "resolved": "https://registry.npmjs.org/@smithy/hash-blob-browser/-/hash-blob-browser-4.2.14.tgz", + "integrity": "sha512-rtQ5es8r/5v4rav7q5QTsfx9CtCyzrz/g7ZZZBH2xtMmd6G/KQrLOWfSHTvFOUPlVy59RQvxeBYJaLRoybMEyA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/chunked-blob-reader": "^5.2.2", + "@smithy/chunked-blob-reader-native": "^4.2.3", + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/@smithy/hash-node": { - "version": "4.2.12", - "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-4.2.12.tgz", - "integrity": "sha512-QhBYbGrbxTkZ43QoTPrK72DoYviDeg6YKDrHTMJbbC+A0sml3kSjzFtXP7BtbyJnXojLfTQldGdUR0RGD8dA3w==", + "version": "4.2.13", + "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-4.2.13.tgz", + "integrity": "sha512-4/oy9h0jjmY80a2gOIo75iLl8TOPhmtx4E2Hz+PfMjvx/vLtGY4TMU/35WRyH2JHPfT5CVB38u4JRow7gnmzJA==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.13.1", + "@smithy/types": "^4.14.0", "@smithy/util-buffer-from": "^4.2.2", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" @@ -3768,13 +4360,27 @@ "node": ">=18.0.0" } }, + "node_modules/@smithy/hash-stream-node": { + "version": "4.2.13", + "resolved": "https://registry.npmjs.org/@smithy/hash-stream-node/-/hash-stream-node-4.2.13.tgz", + "integrity": "sha512-WdQ7HwUjINXETeh6dqUeob1UHIYx8kAn9PSp1HhM2WWegiZBYVy2WXIs1lB07SZLan/udys9SBnQGt9MQbDpdg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.14.0", + "@smithy/util-utf8": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/@smithy/invalid-dependency": { - "version": "4.2.12", - "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-4.2.12.tgz", - "integrity": "sha512-/4F1zb7Z8LOu1PalTdESFHR0RbPwHd3FcaG1sI3UEIriQTWakysgJr65lc1jj6QY5ye7aFsisajotH6UhWfm/g==", + "version": "4.2.13", + "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-4.2.13.tgz", + "integrity": "sha512-jvC0RB/8BLj2SMIkY0Npl425IdnxZJxInpZJbu563zIRnVjpDMXevU3VMCRSabaLB0kf/eFIOusdGstrLJ8IDg==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.13.1", + "@smithy/types": "^4.14.0", "tslib": "^2.6.2" }, "engines": { @@ -3794,12 +4400,12 @@ } }, "node_modules/@smithy/md5-js": { - "version": "4.2.12", - "resolved": "https://registry.npmjs.org/@smithy/md5-js/-/md5-js-4.2.12.tgz", - "integrity": "sha512-W/oIpHCpWU2+iAkfZYyGWE+qkpuf3vEXHLxQQDx9FPNZTTdnul0dZ2d/gUFrtQ5je1G2kp4cjG0/24YueG2LbQ==", + "version": "4.2.13", + "resolved": "https://registry.npmjs.org/@smithy/md5-js/-/md5-js-4.2.13.tgz", + "integrity": "sha512-cNm7I9NXolFxtS20ojROddOEpSAeI1Obq6pd1Kj5HtHws3s9Fkk8DdHDfQSs5KuxCewZuVK6UqrJnfJmiMzDuQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.13.1", + "@smithy/types": "^4.14.0", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" }, @@ -3808,13 +4414,13 @@ } }, "node_modules/@smithy/middleware-content-length": { - "version": "4.2.12", - "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-4.2.12.tgz", - "integrity": "sha512-YE58Yz+cvFInWI/wOTrB+DbvUVz/pLn5mC5MvOV4fdRUc6qGwygyngcucRQjAhiCEbmfLOXX0gntSIcgMvAjmA==", + "version": "4.2.13", + "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-4.2.13.tgz", + "integrity": "sha512-IPMLm/LE4AZwu6qiE8Rr8vJsWhs9AtOdySRXrOM7xnvclp77Tyh7hMs/FRrMf26kgIe67vFJXXOSmVxS7oKeig==", "license": "Apache-2.0", "dependencies": { - "@smithy/protocol-http": "^5.3.12", - "@smithy/types": "^4.13.1", + "@smithy/protocol-http": "^5.3.13", + "@smithy/types": "^4.14.0", "tslib": "^2.6.2" }, "engines": { @@ -3822,18 +4428,18 @@ } }, "node_modules/@smithy/middleware-endpoint": { - "version": "4.4.28", - "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.4.28.tgz", - "integrity": "sha512-p1gfYpi91CHcs5cBq982UlGlDrxoYUX6XdHSo91cQ2KFuz6QloHosO7Jc60pJiVmkWrKOV8kFYlGFFbQ2WUKKQ==", + "version": "4.4.29", + "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.4.29.tgz", + "integrity": "sha512-R9Q/58U+qBiSARGWbAbFLczECg/RmysRksX6Q8BaQEpt75I7LI6WGDZnjuC9GXSGKljEbA7N118LhGaMbfrTXw==", "license": "Apache-2.0", "dependencies": { - "@smithy/core": "^3.23.13", - "@smithy/middleware-serde": "^4.2.16", - "@smithy/node-config-provider": "^4.3.12", - "@smithy/shared-ini-file-loader": "^4.4.7", - "@smithy/types": "^4.13.1", - "@smithy/url-parser": "^4.2.12", - "@smithy/util-middleware": "^4.2.12", + "@smithy/core": "^3.23.14", + "@smithy/middleware-serde": "^4.2.17", + "@smithy/node-config-provider": "^4.3.13", + "@smithy/shared-ini-file-loader": "^4.4.8", + "@smithy/types": "^4.14.0", + "@smithy/url-parser": "^4.2.13", + "@smithy/util-middleware": "^4.2.13", "tslib": "^2.6.2" }, "engines": { @@ -3841,18 +4447,19 @@ } }, "node_modules/@smithy/middleware-retry": { - "version": "4.4.46", - "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.4.46.tgz", - "integrity": "sha512-SpvWNNOPOrKQGUqZbEPO+es+FRXMWvIyzUKUOYdDgdlA6BdZj/R58p4umoQ76c2oJC44PiM7mKizyyex1IJzow==", + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.5.0.tgz", + "integrity": "sha512-/NzISn4grj/BRFVua/xnQwF+7fakYZgimpw2dfmlPgcqecBMKxpB9g5mLYRrmBD5OrPoODokw4Vi1hrSR4zRyw==", "license": "Apache-2.0", "dependencies": { - "@smithy/node-config-provider": "^4.3.12", - "@smithy/protocol-http": "^5.3.12", - "@smithy/service-error-classification": "^4.2.12", - "@smithy/smithy-client": "^4.12.8", - "@smithy/types": "^4.13.1", - "@smithy/util-middleware": "^4.2.12", - "@smithy/util-retry": "^4.2.13", + "@smithy/core": "^3.23.14", + "@smithy/node-config-provider": "^4.3.13", + "@smithy/protocol-http": "^5.3.13", + "@smithy/service-error-classification": "^4.2.13", + "@smithy/smithy-client": "^4.12.9", + "@smithy/types": "^4.14.0", + "@smithy/util-middleware": "^4.2.13", + "@smithy/util-retry": "^4.3.0", "@smithy/uuid": "^1.1.2", "tslib": "^2.6.2" }, @@ -3861,14 +4468,14 @@ } }, "node_modules/@smithy/middleware-serde": { - "version": "4.2.16", - "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.2.16.tgz", - "integrity": "sha512-beqfV+RZ9RSv+sQqor3xroUUYgRFCGRw6niGstPG8zO9LgTl0B0MCucxjmrH/2WwksQN7UUgI7KNANoZv+KALA==", + "version": "4.2.17", + "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.2.17.tgz", + "integrity": "sha512-0T2mcaM6v9W1xku86Dk0bEW7aEseG6KenFkPK98XNw0ZhOqOiD1MrMsdnQw9QsL3/Oa85T53iSMlm0SZdSuIEQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/core": "^3.23.13", - "@smithy/protocol-http": "^5.3.12", - "@smithy/types": "^4.13.1", + "@smithy/core": "^3.23.14", + "@smithy/protocol-http": "^5.3.13", + "@smithy/types": "^4.14.0", "tslib": "^2.6.2" }, "engines": { @@ -3876,12 +4483,12 @@ } }, "node_modules/@smithy/middleware-stack": { - "version": "4.2.12", - "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-4.2.12.tgz", - "integrity": "sha512-kruC5gRHwsCOuyCd4ouQxYjgRAym2uDlCvQ5acuMtRrcdfg7mFBg6blaxcJ09STpt3ziEkis6bhg1uwrWU7txw==", + "version": "4.2.13", + "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-4.2.13.tgz", + "integrity": "sha512-g72jN/sGDLyTanrCLH9fhg3oysO3f7tQa6eWWsMyn2BiYNCgjF24n4/I9wff/5XidFvjj9ilipAoQrurTUrLvw==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.13.1", + "@smithy/types": "^4.14.0", "tslib": "^2.6.2" }, "engines": { @@ -3889,14 +4496,14 @@ } }, "node_modules/@smithy/node-config-provider": { - "version": "4.3.12", - "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.3.12.tgz", - "integrity": "sha512-tr2oKX2xMcO+rBOjobSwVAkV05SIfUKz8iI53rzxEmgW3GOOPOv0UioSDk+J8OpRQnpnhsO3Af6IEBabQBVmiw==", + "version": "4.3.13", + "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.3.13.tgz", + "integrity": "sha512-iGxQ04DsKXLckbgnX4ipElrOTk+IHgTyu0q0WssZfYhDm9CQWHmu6cOeI5wmWRxpXbBDhIIfXMWz5tPEtcVqbw==", "license": "Apache-2.0", "dependencies": { - "@smithy/property-provider": "^4.2.12", - "@smithy/shared-ini-file-loader": "^4.4.7", - "@smithy/types": "^4.13.1", + "@smithy/property-provider": "^4.2.13", + "@smithy/shared-ini-file-loader": "^4.4.8", + "@smithy/types": "^4.14.0", "tslib": "^2.6.2" }, "engines": { @@ -3904,14 +4511,14 @@ } }, "node_modules/@smithy/node-http-handler": { - "version": "4.5.1", - "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.5.1.tgz", - "integrity": "sha512-ejjxdAXjkPIs9lyYyVutOGNOraqUE9v/NjGMKwwFrfOM354wfSD8lmlj8hVwUzQmlLLF4+udhfCX9Exnbmvfzw==", + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.5.2.tgz", + "integrity": "sha512-/oD7u8M0oj2ZTFw7GkuuHWpIxtWdLlnyNkbrWcyVYhd5RJNDuczdkb0wfnQICyNFrVPlr8YHOhamjNy3zidhmA==", "license": "Apache-2.0", "dependencies": { - "@smithy/protocol-http": "^5.3.12", - "@smithy/querystring-builder": "^4.2.12", - "@smithy/types": "^4.13.1", + "@smithy/protocol-http": "^5.3.13", + "@smithy/querystring-builder": "^4.2.13", + "@smithy/types": "^4.14.0", "tslib": "^2.6.2" }, "engines": { @@ -3919,12 +4526,12 @@ } }, "node_modules/@smithy/property-provider": { - "version": "4.2.12", - "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.2.12.tgz", - "integrity": "sha512-jqve46eYU1v7pZ5BM+fmkbq3DerkSluPr5EhvOcHxygxzD05ByDRppRwRPPpFrsFo5yDtCYLKu+kreHKVrvc7A==", + "version": "4.2.13", + "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.2.13.tgz", + "integrity": "sha512-bGzUCthxRmezuxkbu9wD33wWg9KX3hJpCXpQ93vVkPrHn9ZW6KNNdY5xAUWNuRCwQ+VyboFuWirG1lZhhkcyRQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.13.1", + "@smithy/types": "^4.14.0", "tslib": "^2.6.2" }, "engines": { @@ -3932,12 +4539,12 @@ } }, "node_modules/@smithy/protocol-http": { - "version": "5.3.12", - "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.3.12.tgz", - "integrity": "sha512-fit0GZK9I1xoRlR4jXmbLhoN0OdEpa96ul8M65XdmXnxXkuMxM0Y8HDT0Fh0Xb4I85MBvBClOzgSrV1X2s1Hxw==", + "version": "5.3.13", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.3.13.tgz", + "integrity": "sha512-+HsmuJUF4u8POo6s8/a2Yb/AQ5t/YgLovCuHF9oxbocqv+SZ6gd8lC2duBFiCA/vFHoHQhoq7QjqJqZC6xOxxg==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.13.1", + "@smithy/types": "^4.14.0", "tslib": "^2.6.2" }, "engines": { @@ -3945,12 +4552,12 @@ } }, "node_modules/@smithy/querystring-builder": { - "version": "4.2.12", - "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.2.12.tgz", - "integrity": "sha512-6wTZjGABQufekycfDGMEB84BgtdOE/rCVTov+EDXQ8NHKTUNIp/j27IliwP7tjIU9LR+sSzyGBOXjeEtVgzCHg==", + "version": "4.2.13", + "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.2.13.tgz", + "integrity": "sha512-tG4aOYFCZdPMjbgfhnIQ322H//ojujldp1SrHPHpBSb3NqgUp3dwiUGRJzie87hS1DYwWGqDuPaowoDF+rYCbQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.13.1", + "@smithy/types": "^4.14.0", "@smithy/util-uri-escape": "^4.2.2", "tslib": "^2.6.2" }, @@ -3959,12 +4566,12 @@ } }, "node_modules/@smithy/querystring-parser": { - "version": "4.2.12", - "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.2.12.tgz", - "integrity": "sha512-P2OdvrgiAKpkPNKlKUtWbNZKB1XjPxM086NeVhK+W+wI46pIKdWBe5QyXvhUm3MEcyS/rkLvY8rZzyUdmyDZBw==", + "version": "4.2.13", + "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.2.13.tgz", + "integrity": "sha512-hqW3Q4P+CDzUyQ87GrboGMeD7XYNMOF+CuTwu936UQRB/zeYn3jys8C3w+wMkDfY7CyyyVwZQ5cNFoG0x1pYmA==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.13.1", + "@smithy/types": "^4.14.0", "tslib": "^2.6.2" }, "engines": { @@ -3972,24 +4579,24 @@ } }, "node_modules/@smithy/service-error-classification": { - "version": "4.2.12", - "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.2.12.tgz", - "integrity": "sha512-LlP29oSQN0Tw0b6D0Xo6BIikBswuIiGYbRACy5ujw/JgWSzTdYj46U83ssf6Ux0GyNJVivs2uReU8pt7Eu9okQ==", + "version": "4.2.13", + "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.2.13.tgz", + "integrity": "sha512-a0s8XZMfOC/qpqq7RCPvJlk93rWFrElH6O++8WJKz0FqnA4Y7fkNi/0mnGgSH1C4x6MFsuBA8VKu4zxFrMe5Vw==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.13.1" + "@smithy/types": "^4.14.0" }, "engines": { "node": ">=18.0.0" } }, "node_modules/@smithy/shared-ini-file-loader": { - "version": "4.4.7", - "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.4.7.tgz", - "integrity": "sha512-HrOKWsUb+otTeo1HxVWeEb99t5ER1XrBi/xka2Wv6NVmTbuCUC1dvlrksdvxFtODLBjsC+PHK+fuy2x/7Ynyiw==", + "version": "4.4.8", + "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.4.8.tgz", + "integrity": "sha512-VZCZx2bZasxdqxVgEAhREvDSlkatTPnkdWy1+Kiy8w7kYPBosW0V5IeDwzDUMvWBt56zpK658rx1cOBFOYaPaw==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.13.1", + "@smithy/types": "^4.14.0", "tslib": "^2.6.2" }, "engines": { @@ -3997,16 +4604,16 @@ } }, "node_modules/@smithy/signature-v4": { - "version": "5.3.12", - "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.3.12.tgz", - "integrity": "sha512-B/FBwO3MVOL00DaRSXfXfa/TRXRheagt/q5A2NM13u7q+sHS59EOVGQNfG7DkmVtdQm5m3vOosoKAXSqn/OEgw==", + "version": "5.3.13", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.3.13.tgz", + "integrity": "sha512-YpYSyM0vMDwKbHD/JA7bVOF6kToVRpa+FM5ateEVRpsTNu564g1muBlkTubXhSKKYXInhpADF46FPyrZcTLpXg==", "license": "Apache-2.0", "dependencies": { "@smithy/is-array-buffer": "^4.2.2", - "@smithy/protocol-http": "^5.3.12", - "@smithy/types": "^4.13.1", + "@smithy/protocol-http": "^5.3.13", + "@smithy/types": "^4.14.0", "@smithy/util-hex-encoding": "^4.2.2", - "@smithy/util-middleware": "^4.2.12", + "@smithy/util-middleware": "^4.2.13", "@smithy/util-uri-escape": "^4.2.2", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" @@ -4016,17 +4623,17 @@ } }, "node_modules/@smithy/smithy-client": { - "version": "4.12.8", - "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.12.8.tgz", - "integrity": "sha512-aJaAX7vHe5i66smoSSID7t4rKY08PbD8EBU7DOloixvhOozfYWdcSYE4l6/tjkZ0vBZhGjheWzB2mh31sLgCMA==", + "version": "4.12.9", + "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.12.9.tgz", + "integrity": "sha512-ovaLEcTU5olSeHcRXcxV6viaKtpkHZumn6Ps0yn7dRf2rRSfy794vpjOtrWDO0d1auDSvAqxO+lyhERSXQ03EQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/core": "^3.23.13", - "@smithy/middleware-endpoint": "^4.4.28", - "@smithy/middleware-stack": "^4.2.12", - "@smithy/protocol-http": "^5.3.12", - "@smithy/types": "^4.13.1", - "@smithy/util-stream": "^4.5.21", + "@smithy/core": "^3.23.14", + "@smithy/middleware-endpoint": "^4.4.29", + "@smithy/middleware-stack": "^4.2.13", + "@smithy/protocol-http": "^5.3.13", + "@smithy/types": "^4.14.0", + "@smithy/util-stream": "^4.5.22", "tslib": "^2.6.2" }, "engines": { @@ -4034,9 +4641,9 @@ } }, "node_modules/@smithy/types": { - "version": "4.13.1", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.13.1.tgz", - "integrity": "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g==", + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.14.0.tgz", + "integrity": "sha512-OWgntFLW88kx2qvf/c/67Vno1yuXm/f9M7QFAtVkkO29IJXGBIg0ycEaBTH0kvCtwmvZxRujrgP5a86RvsXJAQ==", "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -4046,13 +4653,13 @@ } }, "node_modules/@smithy/url-parser": { - "version": "4.2.12", - "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.2.12.tgz", - "integrity": "sha512-wOPKPEpso+doCZGIlr+e1lVI6+9VAKfL4kZWFgzVgGWY2hZxshNKod4l2LXS3PRC9otH/JRSjtEHqQ/7eLciRA==", + "version": "4.2.13", + "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.2.13.tgz", + "integrity": "sha512-2G03yoboIRZlZze2+PT4GZEjgwQsJjUgn6iTsvxA02bVceHR6vp4Cuk7TUnPFWKF+ffNUk3kj4COwkENS2K3vw==", "license": "Apache-2.0", "dependencies": { - "@smithy/querystring-parser": "^4.2.12", - "@smithy/types": "^4.13.1", + "@smithy/querystring-parser": "^4.2.13", + "@smithy/types": "^4.14.0", "tslib": "^2.6.2" }, "engines": { @@ -4123,14 +4730,14 @@ } }, "node_modules/@smithy/util-defaults-mode-browser": { - "version": "4.3.44", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.3.44.tgz", - "integrity": "sha512-eZg6XzaCbVr2S5cAErU5eGBDaOVTuTo1I65i4tQcHENRcZ8rMWhQy1DaIYUSLyZjsfXvmCqZrstSMYyGFocvHA==", + "version": "4.3.45", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.3.45.tgz", + "integrity": "sha512-ag9sWc6/nWZAuK3Wm9KlFJUnRkXLrXn33RFjIAmCTFThqLHY+7wCst10BGq56FxslsDrjhSie46c8OULS+BiIw==", "license": "Apache-2.0", "dependencies": { - "@smithy/property-provider": "^4.2.12", - "@smithy/smithy-client": "^4.12.8", - "@smithy/types": "^4.13.1", + "@smithy/property-provider": "^4.2.13", + "@smithy/smithy-client": "^4.12.9", + "@smithy/types": "^4.14.0", "tslib": "^2.6.2" }, "engines": { @@ -4138,17 +4745,17 @@ } }, "node_modules/@smithy/util-defaults-mode-node": { - "version": "4.2.48", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.2.48.tgz", - "integrity": "sha512-FqOKTlqSaoV3nzO55pMs5NBnZX8EhoI0DGmn9kbYeXWppgHD6dchyuj2HLqp4INJDJbSrj6OFYJkAh/WhSzZPg==", + "version": "4.2.49", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.2.49.tgz", + "integrity": "sha512-jlN6vHwE8gY5AfiFBavtD3QtCX2f7lM3BKkz7nFKSNfFR5nXLXLg6sqXTJEEyDwtxbztIDBQCfjsGVXlIru2lQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/config-resolver": "^4.4.13", - "@smithy/credential-provider-imds": "^4.2.12", - "@smithy/node-config-provider": "^4.3.12", - "@smithy/property-provider": "^4.2.12", - "@smithy/smithy-client": "^4.12.8", - "@smithy/types": "^4.13.1", + "@smithy/config-resolver": "^4.4.14", + "@smithy/credential-provider-imds": "^4.2.13", + "@smithy/node-config-provider": "^4.3.13", + "@smithy/property-provider": "^4.2.13", + "@smithy/smithy-client": "^4.12.9", + "@smithy/types": "^4.14.0", "tslib": "^2.6.2" }, "engines": { @@ -4156,13 +4763,13 @@ } }, "node_modules/@smithy/util-endpoints": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.3.3.tgz", - "integrity": "sha512-VACQVe50j0HZPjpwWcjyT51KUQ4AnsvEaQ2lKHOSL4mNLD0G9BjEniQ+yCt1qqfKfiAHRAts26ud7hBjamrwig==", + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.3.4.tgz", + "integrity": "sha512-BKoR/ubPp9KNKFxPpg1J28N1+bgu8NGAtJblBP7yHy8yQPBWhIAv9+l92SlQLpolGm71CVO+btB60gTgzT0wog==", "license": "Apache-2.0", "dependencies": { - "@smithy/node-config-provider": "^4.3.12", - "@smithy/types": "^4.13.1", + "@smithy/node-config-provider": "^4.3.13", + "@smithy/types": "^4.14.0", "tslib": "^2.6.2" }, "engines": { @@ -4182,12 +4789,12 @@ } }, "node_modules/@smithy/util-middleware": { - "version": "4.2.12", - "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.2.12.tgz", - "integrity": "sha512-Er805uFUOvgc0l8nv0e0su0VFISoxhJ/AwOn3gL2NWNY2LUEldP5WtVcRYSQBcjg0y9NfG8JYrCJaYDpupBHJQ==", + "version": "4.2.13", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.2.13.tgz", + "integrity": "sha512-GTooyrlmRTqvUen4eK7/K1p6kryF7bnDfq6XsAbIsf2mo51B/utaH+XThY6dKgNCWzMAaH/+OLmqaBuLhLWRow==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.13.1", + "@smithy/types": "^4.14.0", "tslib": "^2.6.2" }, "engines": { @@ -4195,13 +4802,13 @@ } }, "node_modules/@smithy/util-retry": { - "version": "4.2.13", - "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.2.13.tgz", - "integrity": "sha512-qQQsIvL0MGIbUjeSrg0/VlQ3jGNKyM3/2iU3FPNgy01z+Sp4OvcaxbgIoFOTvB61ZoohtutuOvOcgmhbD0katQ==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.3.0.tgz", + "integrity": "sha512-tSOPQNT/4KfbvqeMovWC3g23KSYy8czHd3tlN+tOYVNIDLSfxIsrPJihYi5TpNcoV789KWtgChUVedh2y6dDPg==", "license": "Apache-2.0", "dependencies": { - "@smithy/service-error-classification": "^4.2.12", - "@smithy/types": "^4.13.1", + "@smithy/service-error-classification": "^4.2.13", + "@smithy/types": "^4.14.0", "tslib": "^2.6.2" }, "engines": { @@ -4209,14 +4816,14 @@ } }, "node_modules/@smithy/util-stream": { - "version": "4.5.21", - "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.5.21.tgz", - "integrity": "sha512-KzSg+7KKywLnkoKejRtIBXDmwBfjGvg1U1i/etkC7XSWUyFCoLno1IohV2c74IzQqdhX5y3uE44r/8/wuK+A7Q==", + "version": "4.5.22", + "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.5.22.tgz", + "integrity": "sha512-3H8iq/0BfQjUs2/4fbHZ9aG9yNzcuZs24LPkcX1Q7Z+qpqaGM8+qbGmE8zo9m2nCRgamyvS98cHdcWvR6YUsew==", "license": "Apache-2.0", "dependencies": { - "@smithy/fetch-http-handler": "^5.3.15", - "@smithy/node-http-handler": "^4.5.1", - "@smithy/types": "^4.13.1", + "@smithy/fetch-http-handler": "^5.3.16", + "@smithy/node-http-handler": "^4.5.2", + "@smithy/types": "^4.14.0", "@smithy/util-base64": "^4.3.2", "@smithy/util-buffer-from": "^4.2.2", "@smithy/util-hex-encoding": "^4.2.2", @@ -4253,12 +4860,12 @@ } }, "node_modules/@smithy/util-waiter": { - "version": "4.2.14", - "resolved": "https://registry.npmjs.org/@smithy/util-waiter/-/util-waiter-4.2.14.tgz", - "integrity": "sha512-2zqq5o/oizvMaFUlNiTyZ7dbgYv1a893aGut2uaxtbzTx/VYYnRxWzDHuD/ftgcw94ffenua+ZNLrbqwUYE+Bg==", + "version": "4.2.15", + "resolved": "https://registry.npmjs.org/@smithy/util-waiter/-/util-waiter-4.2.15.tgz", + "integrity": "sha512-oUt9o7n8hBv3BL56sLSneL0XeigZSuem0Hr78JaoK33D9oKieyCvVP8eTSe3j7g2mm/S1DvzxKieG7JEWNJUNg==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.13.1", + "@smithy/types": "^4.14.0", "tslib": "^2.6.2" }, "engines": { @@ -4420,6 +5027,7 @@ "integrity": "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "undici-types": "~7.18.0" } @@ -4503,6 +5111,7 @@ "integrity": "sha512-30ScMRHIAD33JJQkgfGW1t8CURZtjc2JpTrq5n2HFhOefbAhb7ucc7xJwdWcrEtqUIYJ73Nybpsggii6GtAHjA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.57.2", "@typescript-eslint/types": "8.57.2", @@ -5031,216 +5640,623 @@ "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", "dev": true, "license": "MIT", - "bin": { - "acorn": "bin/acorn" + "peer": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.4", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.11.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/agent-base": { + "version": "7.1.4", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/ajv": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", + "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", + "license": "MIT", + "peer": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats": { + "version": "3.0.1", + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ajv-formats-draft2019": { + "version": "1.6.1", + "license": "MIT", + "dependencies": { + "punycode": "^2.1.1", + "schemes": "^1.4.0", + "smtp-address-parser": "^1.0.3", + "uri-js": "^4.4.1" + }, + "peerDependencies": { + "ajv": "*" + } + }, + "node_modules/ajv-formats/node_modules/ajv": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats/node_modules/json-schema-traverse": { + "version": "1.0.0", + "license": "MIT" + }, + "node_modules/ajv-ftl-i18n": { + "version": "0.1.1", + "license": "MIT", + "dependencies": { + "commander": "10.0.0", + "fluent-transpiler": "0.2.1" + }, + "bin": { + "ajv-ftl": "cli.js" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/willfarrell" + } + }, + "node_modules/ajv-ftl-i18n/node_modules/commander": { + "version": "10.0.0", + "license": "MIT", + "engines": { + "node": ">=14" + } + }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/apim-spec": { + "resolved": "packages/specification", + "link": true + }, + "node_modules/arg": { + "version": "4.1.3", + "dev": true, + "license": "MIT" + }, + "node_modules/argparse": { + "version": "1.0.10", + "license": "MIT", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "license": "MIT", + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/aws-cdk": { + "version": "2.1118.0", + "resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.1118.0.tgz", + "integrity": "sha512-Tfd865GRewDTXIbTVtix/l+v8t3rZENvdHcQQZS2wXYVXfHzljULFXe9JKkgZUNDPB1zo9tSBUu8jjiHRm7nWg==", + "license": "Apache-2.0", + "bin": { + "cdk": "bin/cdk" + }, + "engines": { + "node": ">= 18.0.0" + } + }, + "node_modules/aws-cdk-lib": { + "version": "2.248.0", + "resolved": "https://registry.npmjs.org/aws-cdk-lib/-/aws-cdk-lib-2.248.0.tgz", + "integrity": "sha512-PGQycx/OdyX+t0o6QUFI1KJAOLoyIVj2WwrN0syrwCi8lYxW2KzldZsW0X+/UN/ALNQwcjSr927ImTpuDOh+bg==", + "bundleDependencies": [ + "@balena/dockerignore", + "@aws-cdk/cloud-assembly-api", + "case", + "fs-extra", + "ignore", + "jsonschema", + "minimatch", + "punycode", + "semver", + "table", + "yaml", + "mime-types" + ], + "license": "Apache-2.0", + "dependencies": { + "@aws-cdk/asset-awscli-v1": "2.2.273", + "@aws-cdk/asset-node-proxy-agent-v6": "^2.1.1", + "@aws-cdk/cloud-assembly-api": "^2.2.0", + "@aws-cdk/cloud-assembly-schema": "^53.0.0", + "@balena/dockerignore": "^1.0.2", + "case": "1.6.3", + "fs-extra": "^11.3.3", + "ignore": "^5.3.2", + "jsonschema": "^1.5.0", + "mime-types": "^2.1.35", + "minimatch": "^10.2.3", + "punycode": "^2.3.1", + "semver": "^7.7.4", + "table": "^6.9.0", + "yaml": "1.10.3" + }, + "engines": { + "node": ">= 20.0.0" + }, + "peerDependencies": { + "constructs": "^10.5.0" + } + }, + "node_modules/aws-cdk-lib/node_modules/@aws-cdk/cloud-assembly-api": { + "version": "2.2.0", + "bundleDependencies": [ + "jsonschema", + "semver" + ], + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "jsonschema": "~1.4.1", + "semver": "^7.7.4" + }, + "engines": { + "node": ">= 18.0.0" + }, + "peerDependencies": { + "@aws-cdk/cloud-assembly-schema": ">=53.0.0" + } + }, + "node_modules/aws-cdk-lib/node_modules/@aws-cdk/cloud-assembly-api/node_modules/jsonschema": { + "version": "1.4.1", + "inBundle": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/aws-cdk-lib/node_modules/@aws-cdk/cloud-assembly-api/node_modules/semver": { + "version": "7.7.4", + "inBundle": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/aws-cdk-lib/node_modules/@balena/dockerignore": { + "version": "1.0.2", + "inBundle": true, + "license": "Apache-2.0" + }, + "node_modules/aws-cdk-lib/node_modules/ajv": { + "version": "8.18.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/aws-cdk-lib/node_modules/ansi-regex": { + "version": "5.0.1", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/aws-cdk-lib/node_modules/ansi-styles": { + "version": "4.3.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/aws-cdk-lib/node_modules/astral-regex": { + "version": "2.0.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/aws-cdk-lib/node_modules/balanced-match": { + "version": "4.0.4", + "inBundle": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/aws-cdk-lib/node_modules/brace-expansion": { + "version": "5.0.5", + "inBundle": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/aws-cdk-lib/node_modules/case": { + "version": "1.6.3", + "inBundle": true, + "license": "(MIT OR GPL-3.0-or-later)", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/aws-cdk-lib/node_modules/color-convert": { + "version": "2.0.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/aws-cdk-lib/node_modules/color-name": { + "version": "1.1.4", + "inBundle": true, + "license": "MIT" + }, + "node_modules/aws-cdk-lib/node_modules/emoji-regex": { + "version": "8.0.0", + "inBundle": true, + "license": "MIT" + }, + "node_modules/aws-cdk-lib/node_modules/fast-deep-equal": { + "version": "3.1.3", + "inBundle": true, + "license": "MIT" + }, + "node_modules/aws-cdk-lib/node_modules/fast-uri": { + "version": "3.1.0", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "inBundle": true, + "license": "BSD-3-Clause" + }, + "node_modules/aws-cdk-lib/node_modules/fs-extra": { + "version": "11.3.3", + "inBundle": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" }, "engines": { - "node": ">=0.4.0" + "node": ">=14.14" } }, - "node_modules/acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" - } + "node_modules/aws-cdk-lib/node_modules/graceful-fs": { + "version": "4.2.11", + "inBundle": true, + "license": "ISC" }, - "node_modules/acorn-walk": { - "version": "8.3.4", - "dev": true, + "node_modules/aws-cdk-lib/node_modules/ignore": { + "version": "5.3.2", + "inBundle": true, "license": "MIT", - "dependencies": { - "acorn": "^8.11.0" - }, "engines": { - "node": ">=0.4.0" + "node": ">= 4" } }, - "node_modules/agent-base": { - "version": "7.1.4", - "dev": true, + "node_modules/aws-cdk-lib/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "inBundle": true, "license": "MIT", "engines": { - "node": ">= 14" + "node": ">=8" } }, - "node_modules/ajv": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", - "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", + "node_modules/aws-cdk-lib/node_modules/json-schema-traverse": { + "version": "1.0.0", + "inBundle": true, + "license": "MIT" + }, + "node_modules/aws-cdk-lib/node_modules/jsonfile": { + "version": "6.2.0", + "inBundle": true, "license": "MIT", "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" + "universalify": "^2.0.0" }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" + "optionalDependencies": { + "graceful-fs": "^4.1.6" } }, - "node_modules/ajv-formats": { - "version": "3.0.1", + "node_modules/aws-cdk-lib/node_modules/jsonschema": { + "version": "1.5.0", + "inBundle": true, "license": "MIT", - "dependencies": { - "ajv": "^8.0.0" - }, - "peerDependencies": { - "ajv": "^8.0.0" - }, - "peerDependenciesMeta": { - "ajv": { - "optional": true - } + "engines": { + "node": "*" } }, - "node_modules/ajv-formats-draft2019": { - "version": "1.6.1", + "node_modules/aws-cdk-lib/node_modules/lodash.truncate": { + "version": "4.4.2", + "inBundle": true, + "license": "MIT" + }, + "node_modules/aws-cdk-lib/node_modules/mime-db": { + "version": "1.52.0", + "inBundle": true, "license": "MIT", - "dependencies": { - "punycode": "^2.1.1", - "schemes": "^1.4.0", - "smtp-address-parser": "^1.0.3", - "uri-js": "^4.4.1" - }, - "peerDependencies": { - "ajv": "*" + "engines": { + "node": ">= 0.6" } }, - "node_modules/ajv-formats/node_modules/ajv": { - "version": "8.18.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", - "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "node_modules/aws-cdk-lib/node_modules/mime-types": { + "version": "2.1.35", + "inBundle": true, "license": "MIT", "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" + "mime-db": "1.52.0" }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" + "engines": { + "node": ">= 0.6" } }, - "node_modules/ajv-formats/node_modules/json-schema-traverse": { - "version": "1.0.0", - "license": "MIT" - }, - "node_modules/ajv-ftl-i18n": { - "version": "0.1.1", - "license": "MIT", + "node_modules/aws-cdk-lib/node_modules/minimatch": { + "version": "10.2.5", + "inBundle": true, + "license": "BlueOak-1.0.0", "dependencies": { - "commander": "10.0.0", - "fluent-transpiler": "0.2.1" + "brace-expansion": "^5.0.5" }, - "bin": { - "ajv-ftl": "cli.js" + "engines": { + "node": "18 || 20 || >=22" }, "funding": { - "type": "github", - "url": "https://github.com/sponsors/willfarrell" + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/ajv-ftl-i18n/node_modules/commander": { - "version": "10.0.0", + "node_modules/aws-cdk-lib/node_modules/punycode": { + "version": "2.3.1", + "inBundle": true, "license": "MIT", "engines": { - "node": ">=14" + "node": ">=6" } }, - "node_modules/ansi-escapes": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", - "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", - "dev": true, + "node_modules/aws-cdk-lib/node_modules/require-from-string": { + "version": "2.0.2", + "inBundle": true, "license": "MIT", - "dependencies": { - "type-fest": "^0.21.3" - }, "engines": { - "node": ">=8" + "node": ">=0.10.0" + } + }, + "node_modules/aws-cdk-lib/node_modules/semver": { + "version": "7.7.4", + "inBundle": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "engines": { + "node": ">=10" } }, - "node_modules/ansi-regex": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", - "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", - "dev": true, + "node_modules/aws-cdk-lib/node_modules/slice-ansi": { + "version": "4.0.0", + "inBundle": true, "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + }, "engines": { - "node": ">=12" + "node": ">=10" }, "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" + "url": "https://github.com/chalk/slice-ansi?sponsor=1" } }, - "node_modules/ansi-styles": { - "version": "4.3.0", - "dev": true, + "node_modules/aws-cdk-lib/node_modules/string-width": { + "version": "4.2.3", + "inBundle": true, "license": "MIT", "dependencies": { - "color-convert": "^2.0.1" + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" }, "engines": { "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/anymatch": { - "version": "3.1.3", - "dev": true, - "license": "ISC", + "node_modules/aws-cdk-lib/node_modules/strip-ansi": { + "version": "6.0.1", + "inBundle": true, + "license": "MIT", "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" + "ansi-regex": "^5.0.1" }, "engines": { - "node": ">= 8" + "node": ">=8" } }, - "node_modules/apim-spec": { - "resolved": "packages/specification", - "link": true - }, - "node_modules/arg": { - "version": "4.1.3", - "dev": true, - "license": "MIT" - }, - "node_modules/argparse": { - "version": "1.0.10", - "license": "MIT", + "node_modules/aws-cdk-lib/node_modules/table": { + "version": "6.9.0", + "inBundle": true, + "license": "BSD-3-Clause", "dependencies": { - "sprintf-js": "~1.0.2" + "ajv": "^8.0.1", + "lodash.truncate": "^4.4.2", + "slice-ansi": "^4.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=10.0.0" } }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "license": "MIT" - }, - "node_modules/available-typed-arrays": { - "version": "1.0.7", + "node_modules/aws-cdk-lib/node_modules/universalify": { + "version": "2.0.1", + "inBundle": true, "license": "MIT", - "dependencies": { - "possible-typed-array-names": "^1.0.0" - }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">= 10.0.0" + } + }, + "node_modules/aws-cdk-lib/node_modules/yaml": { + "version": "1.10.3", + "inBundle": true, + "license": "ISC", + "engines": { + "node": ">= 6" } }, "node_modules/aws-lambda": { @@ -5291,6 +6307,7 @@ "resolved": "https://registry.npmjs.org/axios/-/axios-1.14.0.tgz", "integrity": "sha512-3Y8yrqLSwjuzpXuZ0oIYZ/XGgLwUIBU3uLvbcpb0pidD9ctpShJd43KSlEEkVQg6DS0G9NKyzOvBfUtDKEyHvQ==", "license": "MIT", + "peer": true, "dependencies": { "follow-redirects": "^1.15.11", "form-data": "^4.0.5", @@ -5495,6 +6512,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.8.3", "caniuse-lite": "^1.0.30001741", @@ -5656,6 +6674,20 @@ "upper-case-first": "^2.0.2" } }, + "node_modules/cdk": { + "resolved": "packages/cdk", + "link": true + }, + "node_modules/cdk-nag": { + "version": "2.37.55", + "resolved": "https://registry.npmjs.org/cdk-nag/-/cdk-nag-2.37.55.tgz", + "integrity": "sha512-xcAkygwbph3pp7N0UEzJBmXUH/MIsluV7DYJSeZ/V3yCr0Y0QaRGO298WyD6mi4K+Rmnpl+EJoWUxcOblOqLKA==", + "license": "Apache-2.0", + "peerDependencies": { + "aws-cdk-lib": "^2.176.0", + "constructs": "^10.0.5" + } + }, "node_modules/chalk": { "version": "4.1.2", "dev": true, @@ -5872,6 +6904,13 @@ "upper-case": "^2.0.2" } }, + "node_modules/constructs": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/constructs/-/constructs-10.6.0.tgz", + "integrity": "sha512-TxHOnBO5zMo/G76ykzGF/wMpEHu257TbWiIxP9K0Yv/+t70UzgBQiTqjkAsWOPC6jW91DzJI0+ehQV6xDRNBuQ==", + "license": "Apache-2.0", + "peer": true + }, "node_modules/convert-source-map": { "version": "2.0.0", "dev": true, @@ -6219,6 +7258,7 @@ "integrity": "sha512-S9jlY/ELKEUwwQnqWDO+f+m6sercqOPSqXM5Go94l7DOmxHVDgmSFGWEzeE/gwgTAr0W103BWt0QLe/7mabIvA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.2", @@ -6273,6 +7313,7 @@ "version": "10.1.8", "dev": true, "license": "MIT", + "peer": true, "bin": { "eslint-config-prettier": "bin/cli.js" }, @@ -6809,7 +7850,6 @@ "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, "hasInstallScript": true, "license": "MIT", "optional": true, @@ -6897,6 +7937,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/get-tsconfig": { + "version": "4.13.7", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.7.tgz", + "integrity": "sha512-7tN6rFgBlMgpBML5j8typ92BKFi2sFQvIdpAqLA2beia5avZDrMs0FLZiM5etShWq5irVyGcGMEA1jcDaK7A/Q==", + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, "node_modules/glob": { "version": "10.5.0", "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", @@ -7395,6 +8447,7 @@ "integrity": "sha512-AkXIIFcaazymvey2i/+F94XRnM6TsVLZDhBMLsd1Sf/W0wzsvvpjeyUrCZD6HGG4SDYPgDJDBKeiJTBb10WzMg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@jest/core": "30.3.0", "@jest/types": "30.3.0", @@ -8402,6 +9455,7 @@ "version": "6.15.0", "dev": true, "license": "MIT", + "peer": true, "funding": { "type": "opencollective", "url": "https://opencollective.com/mobx" @@ -9323,6 +10377,7 @@ "version": "19.2.1", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -9331,6 +10386,7 @@ "version": "19.2.1", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "scheduler": "^0.27.0" }, @@ -9505,6 +10561,15 @@ "node": ">=8" } }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, "node_modules/ret": { "version": "0.1.15", "license": "MIT", @@ -10041,6 +11106,7 @@ "integrity": "sha512-J72R4ltw0UBVUlEjTzI0gg2STOqlI9JBhQOL4Dxt7aJOnnSesy0qJDn4PYfMCafk9cWOaVg129Pesl5o+DIh0Q==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@emotion/is-prop-valid": "1.4.0", "@emotion/unitless": "0.10.0", @@ -10222,6 +11288,7 @@ "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -10327,6 +11394,7 @@ "version": "10.9.2", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@cspotcode/source-map-support": "^0.8.0", "@tsconfig/node10": "^1.0.7", @@ -10369,6 +11437,25 @@ "version": "2.8.1", "license": "0BSD" }, + "node_modules/tsx": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz", + "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==", + "license": "MIT", + "dependencies": { + "esbuild": "~0.27.0", + "get-tsconfig": "^4.7.5" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, "node_modules/type-check": { "version": "0.4.0", "dev": true, @@ -10405,6 +11492,7 @@ "version": "5.9.3", "dev": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -10992,6 +12080,17 @@ "@psu-common/testing": "^1.0.0" } }, + "packages/cdk": { + "version": "0.1.0", + "dependencies": { + "@nhsdigital/eps-cdk-constructs": "^1.6.1", + "aws-cdk": "^2.1114.1", + "aws-cdk-lib": "^2.245.0", + "cdk-nag": "^2.37.55", + "constructs": "^10.6.0", + "tsx": "^4.21.0" + } + }, "packages/checkPrescriptionStatusUpdates": { "version": "1.0.0", "license": "MIT", diff --git a/package.json b/package.json index 1136800632..4b3dc48153 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "author": "NHS Digital", "license": "MIT", "workspaces": [ + "packages/cdk", "packages/gsul", "packages/nhsd-psu-sandbox", "packages/specification", diff --git a/packages/cdk/bin/PsuStatelessApp.ts b/packages/cdk/bin/PsuStatelessApp.ts new file mode 100644 index 0000000000..28208bad81 --- /dev/null +++ b/packages/cdk/bin/PsuStatelessApp.ts @@ -0,0 +1,21 @@ +import {calculateVersionedStackName, createApp, getConfigFromEnvVar} from "@nhsdigital/eps-cdk-constructs" +import {PsuStatelessStack} from "../stacks/PsuStatelessStack" + +async function main() { + const {app, props} = createApp({ + productName: "Prescription Status Update API", + appName: "PsuStatelessApp", + repoName: "eps-prescription-status-update-api", + driftDetectionGroup: "psu-api" + }) + + new PsuStatelessStack(app, "PsuStatelessStack", { + ...props, + stackName: calculateVersionedStackName(getConfigFromEnvVar("stackName"), props) + }) +} + +main().catch((error) => { + console.error(error) + process.exit(1) +}) diff --git a/packages/cdk/nagSuppressions.ts b/packages/cdk/nagSuppressions.ts new file mode 100644 index 0000000000..d53c150124 --- /dev/null +++ b/packages/cdk/nagSuppressions.ts @@ -0,0 +1,6 @@ +import {Stack} from "aws-cdk-lib" + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +export const nagSuppressions = (_stack: Stack) => { + // Nag suppressions will be added here as resources are migrated +} diff --git a/packages/cdk/package.json b/packages/cdk/package.json new file mode 100644 index 0000000000..c6988a51c4 --- /dev/null +++ b/packages/cdk/package.json @@ -0,0 +1,20 @@ +{ + "name": "cdk", + "version": "0.1.0", + "scripts": { + "cdk-synth": "cdk synth --output ../../cdk.out --quiet --app \"npm run tsx -- bin/${CDK_APP_NAME}.ts\"", + "cdk-diff": "cdk diff --app \"npm run tsx -- bin/${CDK_APP_NAME}.ts\"", + "cdk-deploy": "cdk deploy --app \"npm run tsx -- bin/${CDK_APP_NAME}.ts\" --all --ci true --require-approval ${REQUIRE_APPROVAL}", + "cdk-watch": "cdk deploy --app \"npm run tsx -- bin/${CDK_APP_NAME}.ts\" --watch --all --ci true --require-approval ${REQUIRE_APPROVAL}", + "lint": "eslint --max-warnings 0 --fix --config ../../eslint.config.mjs .", + "tsx": "tsx" + }, + "dependencies": { + "@nhsdigital/eps-cdk-constructs": "^1.6.1", + "aws-cdk": "^2.1114.1", + "aws-cdk-lib": "^2.245.0", + "cdk-nag": "^2.37.55", + "constructs": "^10.6.0", + "tsx": "^4.21.0" + } +} diff --git a/packages/cdk/stacks/PsuStatelessStack.ts b/packages/cdk/stacks/PsuStatelessStack.ts new file mode 100644 index 0000000000..c492c1469c --- /dev/null +++ b/packages/cdk/stacks/PsuStatelessStack.ts @@ -0,0 +1,17 @@ +import {App, Stack} from "aws-cdk-lib" +import {nagSuppressions} from "../nagSuppressions" +import {StandardStackProps} from "@nhsdigital/eps-cdk-constructs" + +export interface PsuStatelessStackProps extends StandardStackProps { + readonly stackName: string +} + +export class PsuStatelessStack extends Stack { + public constructor(scope: App, id: string, props: PsuStatelessStackProps) { + super(scope, id, props) + + // Resources will be added here as they are migrated from SAM templates + + nagSuppressions(this) + } +} diff --git a/packages/cdk/tsconfig.json b/packages/cdk/tsconfig.json new file mode 100644 index 0000000000..c7ce43bf53 --- /dev/null +++ b/packages/cdk/tsconfig.json @@ -0,0 +1,37 @@ +{ + "extends": "../../tsconfig.defaults.json", + "compilerOptions": { + "module": "commonjs", + "rootDir": ".", + "outDir": "lib", + "allowImportingTsExtensions": true, + "noEmit": true, + "strict": false, + "lib": [ + "es2020" + ], + "noImplicitAny": true, + "strictNullChecks": true, + "noImplicitThis": true, + "alwaysStrict": true, + "noUnusedLocals": false, + "noUnusedParameters": false, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": false, + "inlineSources": true, + "experimentalDecorators": true, + "strictPropertyInitialization": false, + "typeRoots": [ + "../../node_modules/@types" + ] + }, + "include": [ + "stacks/**/*", + "bin/**/*", + "nagSuppressions.ts" + ], + "exclude": [ + "node_modules", + "cdk.out" + ] +} From b0b7aa634d0bdcb45770655136f3df47c9091aaa Mon Sep 17 00:00:00 2001 From: Jim Wild Date: Thu, 9 Apr 2026 11:47:10 +0000 Subject: [PATCH 2/9] Zizimor ignore --- zizmor.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/zizmor.yml b/zizmor.yml index 6659946d6b..2776fe96ab 100644 --- a/zizmor.yml +++ b/zizmor.yml @@ -5,6 +5,8 @@ rules: - run_release_code_and_api.yml:146:7 - run_regression_tests.yml:26:7 - run_package_code_and_api.yml:16:7 + - cdk_package_code.yml:25:7 + - cdk_release_code.yml:45:7 secrets-outside-env: # these are ignored because they are using known secrets ignore: From 4e8db26434c5998e8729b8faaf4c22d795f9ce61 Mon Sep 17 00:00:00 2001 From: Jim Wild Date: Thu, 9 Apr 2026 12:25:47 +0000 Subject: [PATCH 3/9] Reenable the grype scan --- .pre-commit-config.yaml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 26b304ca75..847e8dc9ce 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -23,13 +23,13 @@ repos: - repo: local hooks: - # - id: grype-scan-local - # name: Grype scan local changes - # entry: make - # args: ["grype-scan-local"] - # language: system - # pass_filenames: false - # always_run: true + - id: grype-scan-local + name: Grype scan local changes + entry: make + args: ["grype-scan-local"] + language: system + pass_filenames: false + always_run: true - id: check-commit-signing name: Check commit signing From 1a42b7fea8472fdeddc82f37be30d8152ef03c63 Mon Sep 17 00:00:00 2001 From: Jim Wild Date: Thu, 9 Apr 2026 12:29:19 +0000 Subject: [PATCH 4/9] dont download get secrets layer yet --- .github/workflows/cdk_package_code.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.github/workflows/cdk_package_code.yml b/.github/workflows/cdk_package_code.yml index fda9c3f69d..0e41ae4f33 100644 --- a/.github/workflows/cdk_package_code.yml +++ b/.github/workflows/cdk_package_code.yml @@ -49,10 +49,6 @@ jobs: - name: make compile run: make compile - - name: download the get secrets lambda layer - run: | - make download-get-secrets-layer - - name: "Tar files" run: | tar -rf artifact.tar \ From 0728630964ca8b87390203f8b403547124ad2929 Mon Sep 17 00:00:00 2001 From: Jim Wild Date: Thu, 9 Apr 2026 14:49:09 +0000 Subject: [PATCH 5/9] move API gateway to stateless stack --- .github/workflows/cdk_release_code.yml | 16 +++ .github/workflows/ci.yml | 4 + .github/workflows/pull_request.yml | 4 + .github/workflows/release.yml | 4 + Makefile | 4 + packages/cdk/bin/PsuStatelessApp.ts | 16 ++- packages/cdk/resources/Apis.ts | 151 +++++++++++++++++++++++ packages/cdk/stacks/PsuStatelessStack.ts | 22 +++- packages/cdk/tsconfig.json | 1 + 9 files changed, 219 insertions(+), 3 deletions(-) create mode 100644 packages/cdk/resources/Apis.ts diff --git a/.github/workflows/cdk_release_code.yml b/.github/workflows/cdk_release_code.yml index 9361be0a60..ac579a968b 100644 --- a/.github/workflows/cdk_release_code.yml +++ b/.github/workflows/cdk_release_code.yml @@ -29,6 +29,18 @@ on: IS_PULL_REQUEST: type: boolean required: true + TRUSTSTORE_FILE: + type: string + required: true + FORWARD_CSOC_LOGS: + type: boolean + required: true + DEPLOY_CHECK_PRESCRIPTION_STATUS_UPDATE: + type: boolean + required: true + EXPOSE_GET_STATUS_UPDATES: + type: boolean + required: true pinned_image: required: true type: string @@ -91,6 +103,10 @@ jobs: CDK_CONFIG_environment: "${{ inputs.AWS_ENVIRONMENT }}" CDK_CONFIG_logRetentionInDays: "${{ inputs.LOG_RETENTION_IN_DAYS }}" CDK_CONFIG_logLevel: "${{ inputs.LOG_LEVEL }}" + CDK_CONFIG_trustStoreFile: "${{ inputs.TRUSTSTORE_FILE }}" + CDK_CONFIG_forwardCsocLogs: "${{ inputs.FORWARD_CSOC_LOGS }}" + CDK_CONFIG_deployCheckPrescriptionStatusUpdate: "${{ inputs.DEPLOY_CHECK_PRESCRIPTION_STATUS_UPDATE }}" + CDK_CONFIG_exposeGetStatusUpdates: "${{ inputs.EXPOSE_GET_STATUS_UPDATES }}" REQUIRE_APPROVAL: "never" # later, there will be API deployment steps c.f. https://github.com/NHSDigital/electronic-prescription-service-clinical-prescription-tracker/blob/main/.github/workflows/cdk_release_code.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 145606984d..2ba72eb7a4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -81,6 +81,10 @@ jobs: LOG_RETENTION_IN_DAYS: "30" LOG_LEVEL: DEBUG IS_PULL_REQUEST: false + TRUSTSTORE_FILE: psu-truststore.pem + FORWARD_CSOC_LOGS: false + DEPLOY_CHECK_PRESCRIPTION_STATUS_UPDATE: true + EXPOSE_GET_STATUS_UPDATES: false secrets: CLOUD_FORMATION_DEPLOY_ROLE: ${{ secrets.DEV_CLOUD_FORMATION_DEPLOY_ROLE }} diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index cbaa104966..87d6618a44 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -124,6 +124,10 @@ jobs: LOG_RETENTION_IN_DAYS: "30" LOG_LEVEL: DEBUG IS_PULL_REQUEST: true + TRUSTSTORE_FILE: psu-truststore.pem + FORWARD_CSOC_LOGS: false + DEPLOY_CHECK_PRESCRIPTION_STATUS_UPDATE: true + EXPOSE_GET_STATUS_UPDATES: false secrets: CLOUD_FORMATION_DEPLOY_ROLE: ${{ secrets.DEV_CLOUD_FORMATION_DEPLOY_ROLE }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 95bca18572..21f703f4f5 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -83,6 +83,10 @@ jobs: LOG_RETENTION_IN_DAYS: "30" LOG_LEVEL: DEBUG IS_PULL_REQUEST: false + TRUSTSTORE_FILE: psu-truststore.pem + FORWARD_CSOC_LOGS: false + DEPLOY_CHECK_PRESCRIPTION_STATUS_UPDATE: true + EXPOSE_GET_STATUS_UPDATES: false secrets: CLOUD_FORMATION_DEPLOY_ROLE: ${{ secrets.DEV_CLOUD_FORMATION_DEPLOY_ROLE }} diff --git a/Makefile b/Makefile index 5cf013b19b..e145acc941 100644 --- a/Makefile +++ b/Makefile @@ -8,6 +8,10 @@ export CDK_CONFIG_isPullRequest=true export CDK_CONFIG_environment=dev export CDK_CONFIG_logRetentionInDays=30 export CDK_CONFIG_logLevel=DEBUG +export CDK_CONFIG_trustStoreFile=psu-truststore.pem +export CDK_CONFIG_forwardCsocLogs=false +export CDK_CONFIG_deployCheckPrescriptionStatusUpdate=true +export CDK_CONFIG_exposeGetStatusUpdates=false guard-%: @ if [ "${${*}}" = "" ]; then \ diff --git a/packages/cdk/bin/PsuStatelessApp.ts b/packages/cdk/bin/PsuStatelessApp.ts index 28208bad81..cebc23ca91 100644 --- a/packages/cdk/bin/PsuStatelessApp.ts +++ b/packages/cdk/bin/PsuStatelessApp.ts @@ -1,4 +1,10 @@ -import {calculateVersionedStackName, createApp, getConfigFromEnvVar} from "@nhsdigital/eps-cdk-constructs" +import { + calculateVersionedStackName, + createApp, + getBooleanConfigFromEnvVar, + getConfigFromEnvVar, + getNumberConfigFromEnvVar +} from "@nhsdigital/eps-cdk-constructs" import {PsuStatelessStack} from "../stacks/PsuStatelessStack" async function main() { @@ -11,7 +17,13 @@ async function main() { new PsuStatelessStack(app, "PsuStatelessStack", { ...props, - stackName: calculateVersionedStackName(getConfigFromEnvVar("stackName"), props) + stackName: calculateVersionedStackName(getConfigFromEnvVar("stackName"), props), + logRetentionInDays: getNumberConfigFromEnvVar("logRetentionInDays"), + mutualTlsTrustStoreKey: props.isPullRequest ? undefined : getConfigFromEnvVar("trustStoreFile"), + csocApiGatewayDestination: "arn:aws:logs:eu-west-2:693466633220:destination:api_gateway_log_destination", + forwardCsocLogs: getBooleanConfigFromEnvVar("forwardCsocLogs"), + deployCheckPrescriptionStatusUpdate: getBooleanConfigFromEnvVar("deployCheckPrescriptionStatusUpdate"), + exposeGetStatusUpdates: getBooleanConfigFromEnvVar("exposeGetStatusUpdates") }) } diff --git a/packages/cdk/resources/Apis.ts b/packages/cdk/resources/Apis.ts new file mode 100644 index 0000000000..a7ddc3ad1f --- /dev/null +++ b/packages/cdk/resources/Apis.ts @@ -0,0 +1,151 @@ +import {LambdaIntegration, PassthroughBehavior, StepFunctionsIntegration} from "aws-cdk-lib/aws-apigateway" +import {IManagedPolicy} from "aws-cdk-lib/aws-iam" +import {HttpMethod} from "aws-cdk-lib/aws-lambda" +import {Construct} from "constructs" +import { + ExpressStateMachine, + LambdaEndpoint, + RestApiGateway, + StateMachineEndpoint, + TypescriptLambdaFunction +} from "@nhsdigital/eps-cdk-constructs" + +export interface ApisProps { + readonly stackName: string + readonly logRetentionInDays: number + readonly mutualTlsTrustStoreKey: string | undefined + readonly forwardCsocLogs: boolean + readonly csocApiGatewayDestination: string + readonly deployCheckPrescriptionStatusUpdate: boolean + readonly exposeGetStatusUpdates: boolean + functions: {[key: string]: TypescriptLambdaFunction} + stateMachines: {[key: string]: ExpressStateMachine} +} + +export class Apis extends Construct { + public constructor(scope: Construct, id: string, props: ApisProps) { + super(scope, id) + + // Collect execution policies for all resources that need API Gateway access + const executionPolicies: Array = [ + props.stateMachines.updatePrescriptionStatus.executionPolicy, + props.stateMachines.format1UpdatePrescriptionsStatus.executionPolicy, + props.functions.status.executionPolicy, + props.functions.capabilityStatement.executionPolicy, + props.functions.nhsNotifyUpdateCallback.executionPolicy + ] + + if (props.exposeGetStatusUpdates) { + executionPolicies.push(props.functions.getStatusUpdates.executionPolicy) + } + + if (props.deployCheckPrescriptionStatusUpdate) { + executionPolicies.push(props.functions.checkPrescriptionStatusUpdates.executionPolicy) + } + + const apiGateway = new RestApiGateway(this, "RestApiGateway", { + stackName: props.stackName, + logRetentionInDays: props.logRetentionInDays, + mutualTlsTrustStoreKey: props.mutualTlsTrustStoreKey, + forwardCsocLogs: props.forwardCsocLogs, + csocApiGatewayDestination: props.csocApiGatewayDestination, + executionPolicies + }) + + const rootResource = apiGateway.api.root + + // POST / — UpdatePrescriptionStatus state machine integration (root resource) + rootResource.addMethod( + HttpMethod.POST, + StepFunctionsIntegration.startExecution( + props.stateMachines.updatePrescriptionStatus.stateMachine, + { + credentialsRole: apiGateway.role, + passthroughBehavior: PassthroughBehavior.WHEN_NO_MATCH + } + ), + { + methodResponses: [ + {statusCode: "200"}, + {statusCode: "400"}, + {statusCode: "500"} + ] + } + ) + + // POST /format-1 — Format1 state machine integration + new StateMachineEndpoint(this, "Format1UpdatePrescriptionStatusEndpoint", { + parentResource: rootResource, + resourceName: "format-1", + method: HttpMethod.POST, + restApiGatewayRole: apiGateway.role, + stateMachine: props.stateMachines.format1UpdatePrescriptionsStatus + }) + + // POST /notification-delivery-status-callback — Lambda proxy integration + new LambdaEndpoint(this, "NotificationDeliveryStatusCallbackEndpoint", { + parentResource: rootResource, + resourceName: "notification-delivery-status-callback", + method: HttpMethod.POST, + restApiGatewayRole: apiGateway.role, + lambdaFunction: props.functions.nhsNotifyUpdateCallback + }) + + // GET /_status — Lambda proxy integration + new LambdaEndpoint(this, "StatusEndpoint", { + parentResource: rootResource, + resourceName: "_status", + method: HttpMethod.GET, + restApiGatewayRole: apiGateway.role, + lambdaFunction: props.functions.status + }) + + // GET /metadata — Lambda proxy integration + new LambdaEndpoint(this, "CapabilityStatementEndpoint", { + parentResource: rootResource, + resourceName: "metadata", + method: HttpMethod.GET, + restApiGatewayRole: apiGateway.role, + lambdaFunction: props.functions.capabilityStatement + }) + + // GET /checkprescriptionstatusupdates — conditional Lambda proxy integration + if (props.deployCheckPrescriptionStatusUpdate) { + new LambdaEndpoint(this, "CheckPrescriptionStatusUpdatesEndpoint", { + parentResource: rootResource, + resourceName: "checkprescriptionstatusupdates", + method: HttpMethod.GET, + restApiGatewayRole: apiGateway.role, + lambdaFunction: props.functions.checkPrescriptionStatusUpdates + }) + } + + // POST /get-status-updates — conditional Lambda integration (non-proxy) + if (props.exposeGetStatusUpdates) { + const getStatusUpdatesResource = rootResource.addResource("get-status-updates") + getStatusUpdatesResource.addMethod( + HttpMethod.POST, + new LambdaIntegration(props.functions.getStatusUpdates.function, { + credentialsRole: apiGateway.role, + proxy: false, + requestTemplates: { + "application/json": "$input.json('$')" + }, + integrationResponses: [ + { + statusCode: "200", + responseTemplates: { + "application/json": "$input.body" + } + } + ] + }), + { + methodResponses: [ + {statusCode: "200"} + ] + } + ) + } + } +} diff --git a/packages/cdk/stacks/PsuStatelessStack.ts b/packages/cdk/stacks/PsuStatelessStack.ts index c492c1469c..30d2368327 100644 --- a/packages/cdk/stacks/PsuStatelessStack.ts +++ b/packages/cdk/stacks/PsuStatelessStack.ts @@ -4,13 +4,33 @@ import {StandardStackProps} from "@nhsdigital/eps-cdk-constructs" export interface PsuStatelessStackProps extends StandardStackProps { readonly stackName: string + readonly logRetentionInDays: number + readonly mutualTlsTrustStoreKey: string | undefined + readonly forwardCsocLogs: boolean + readonly csocApiGatewayDestination: string + readonly deployCheckPrescriptionStatusUpdate: boolean + readonly exposeGetStatusUpdates: boolean } export class PsuStatelessStack extends Stack { public constructor(scope: App, id: string, props: PsuStatelessStackProps) { super(scope, id, props) - // Resources will be added here as they are migrated from SAM templates + // Apis construct will be instantiated here once Functions and StateMachines are migrated: + // + // const functions = new Functions(this, "Functions", { ... }) + // const stateMachines = new StateMachines(this, "StateMachines", { ... }) + // new Apis(this, "Apis", { + // stackName: props.stackName, + // logRetentionInDays: props.logRetentionInDays, + // mutualTlsTrustStoreKey: props.mutualTlsTrustStoreKey, + // forwardCsocLogs: props.forwardCsocLogs, + // csocApiGatewayDestination: props.csocApiGatewayDestination, + // deployCheckPrescriptionStatusUpdate: props.deployCheckPrescriptionStatusUpdate, + // exposeGetStatusUpdates: props.exposeGetStatusUpdates, + // functions: functions.functions, + // stateMachines: stateMachines.stateMachines, + // }) nagSuppressions(this) } diff --git a/packages/cdk/tsconfig.json b/packages/cdk/tsconfig.json index c7ce43bf53..280f62c4ca 100644 --- a/packages/cdk/tsconfig.json +++ b/packages/cdk/tsconfig.json @@ -28,6 +28,7 @@ "include": [ "stacks/**/*", "bin/**/*", + "resources/**/*", "nagSuppressions.ts" ], "exclude": [ From 8d158279b9c3c5c214b8e19097369ff12cdcb549 Mon Sep 17 00:00:00 2001 From: Jim Wild Date: Thu, 9 Apr 2026 15:17:09 +0000 Subject: [PATCH 6/9] Migrate lambda functions. There is a required reference to the SAM stack so that it can access the stateful resources, e.g. tables, SQS, params. --- .github/workflows/cdk_release_code.yml | 16 + .github/workflows/ci.yml | 4 + .github/workflows/pull_request.yml | 4 + .github/workflows/release.yml | 4 + Makefile | 4 + packages/cdk/bin/PsuStatelessApp.ts | 8 +- packages/cdk/resources/Functions.ts | 488 +++++++++++++++++++++++ packages/cdk/stacks/PsuStatelessStack.ts | 26 +- zizmor.yml | 2 + 9 files changed, 553 insertions(+), 3 deletions(-) create mode 100644 packages/cdk/resources/Functions.ts diff --git a/.github/workflows/cdk_release_code.yml b/.github/workflows/cdk_release_code.yml index ac579a968b..ccd24991db 100644 --- a/.github/workflows/cdk_release_code.yml +++ b/.github/workflows/cdk_release_code.yml @@ -41,6 +41,18 @@ on: EXPOSE_GET_STATUS_UPDATES: type: boolean required: true + SAM_STACK_NAME: + type: string + required: true + ENABLE_POST_DATED_NOTIFICATIONS: + type: boolean + required: true + REQUIRE_APPLICATION_NAME: + type: boolean + required: true + ENABLE_BACKUP: + type: boolean + required: true pinned_image: required: true type: string @@ -107,6 +119,10 @@ jobs: CDK_CONFIG_forwardCsocLogs: "${{ inputs.FORWARD_CSOC_LOGS }}" CDK_CONFIG_deployCheckPrescriptionStatusUpdate: "${{ inputs.DEPLOY_CHECK_PRESCRIPTION_STATUS_UPDATE }}" CDK_CONFIG_exposeGetStatusUpdates: "${{ inputs.EXPOSE_GET_STATUS_UPDATES }}" + CDK_CONFIG_samStackName: "${{ inputs.SAM_STACK_NAME }}" + CDK_CONFIG_enablePostDatedNotifications: "${{ inputs.ENABLE_POST_DATED_NOTIFICATIONS }}" + CDK_CONFIG_requireApplicationName: "${{ inputs.REQUIRE_APPLICATION_NAME }}" + CDK_CONFIG_enableBackup: "${{ inputs.ENABLE_BACKUP }}" REQUIRE_APPROVAL: "never" # later, there will be API deployment steps c.f. https://github.com/NHSDigital/electronic-prescription-service-clinical-prescription-tracker/blob/main/.github/workflows/cdk_release_code.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2ba72eb7a4..7528f64153 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -85,6 +85,10 @@ jobs: FORWARD_CSOC_LOGS: false DEPLOY_CHECK_PRESCRIPTION_STATUS_UPDATE: true EXPOSE_GET_STATUS_UPDATES: false + SAM_STACK_NAME: psu + ENABLE_POST_DATED_NOTIFICATIONS: true + REQUIRE_APPLICATION_NAME: false + ENABLE_BACKUP: true secrets: CLOUD_FORMATION_DEPLOY_ROLE: ${{ secrets.DEV_CLOUD_FORMATION_DEPLOY_ROLE }} diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index 87d6618a44..7fa9f9976c 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -128,6 +128,10 @@ jobs: FORWARD_CSOC_LOGS: false DEPLOY_CHECK_PRESCRIPTION_STATUS_UPDATE: true EXPOSE_GET_STATUS_UPDATES: false + SAM_STACK_NAME: psu-pr-${{needs.get_issue_number.outputs.issue_number}} + ENABLE_POST_DATED_NOTIFICATIONS: true + REQUIRE_APPLICATION_NAME: false + ENABLE_BACKUP: false secrets: CLOUD_FORMATION_DEPLOY_ROLE: ${{ secrets.DEV_CLOUD_FORMATION_DEPLOY_ROLE }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 21f703f4f5..11da192a67 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -87,6 +87,10 @@ jobs: FORWARD_CSOC_LOGS: false DEPLOY_CHECK_PRESCRIPTION_STATUS_UPDATE: true EXPOSE_GET_STATUS_UPDATES: false + SAM_STACK_NAME: psu + ENABLE_POST_DATED_NOTIFICATIONS: true + REQUIRE_APPLICATION_NAME: false + ENABLE_BACKUP: true secrets: CLOUD_FORMATION_DEPLOY_ROLE: ${{ secrets.DEV_CLOUD_FORMATION_DEPLOY_ROLE }} diff --git a/Makefile b/Makefile index e145acc941..e6ac7a7f6f 100644 --- a/Makefile +++ b/Makefile @@ -2,6 +2,7 @@ SHELL = /bin/bash .SHELLFLAGS = -o pipefail -c export CDK_APP_NAME=PsuStatelessApp export CDK_CONFIG_stackName=${stack_name} +export CDK_CONFIG_samStackName=${stack_name} export CDK_CONFIG_versionNumber=undefined export CDK_CONFIG_commitId=undefined export CDK_CONFIG_isPullRequest=true @@ -12,6 +13,9 @@ export CDK_CONFIG_trustStoreFile=psu-truststore.pem export CDK_CONFIG_forwardCsocLogs=false export CDK_CONFIG_deployCheckPrescriptionStatusUpdate=true export CDK_CONFIG_exposeGetStatusUpdates=false +export CDK_CONFIG_enablePostDatedNotifications=false +export CDK_CONFIG_requireApplicationName=false +export CDK_CONFIG_enableBackup=false guard-%: @ if [ "${${*}}" = "" ]; then \ diff --git a/packages/cdk/bin/PsuStatelessApp.ts b/packages/cdk/bin/PsuStatelessApp.ts index cebc23ca91..7116cc73c0 100644 --- a/packages/cdk/bin/PsuStatelessApp.ts +++ b/packages/cdk/bin/PsuStatelessApp.ts @@ -18,12 +18,18 @@ async function main() { new PsuStatelessStack(app, "PsuStatelessStack", { ...props, stackName: calculateVersionedStackName(getConfigFromEnvVar("stackName"), props), + samStackName: getConfigFromEnvVar("samStackName"), // TODO: REMOVE THE NEED FOR THIS logRetentionInDays: getNumberConfigFromEnvVar("logRetentionInDays"), + logLevel: getConfigFromEnvVar("logLevel"), + environment: getConfigFromEnvVar("environment"), mutualTlsTrustStoreKey: props.isPullRequest ? undefined : getConfigFromEnvVar("trustStoreFile"), csocApiGatewayDestination: "arn:aws:logs:eu-west-2:693466633220:destination:api_gateway_log_destination", forwardCsocLogs: getBooleanConfigFromEnvVar("forwardCsocLogs"), deployCheckPrescriptionStatusUpdate: getBooleanConfigFromEnvVar("deployCheckPrescriptionStatusUpdate"), - exposeGetStatusUpdates: getBooleanConfigFromEnvVar("exposeGetStatusUpdates") + exposeGetStatusUpdates: getBooleanConfigFromEnvVar("exposeGetStatusUpdates"), + enablePostDatedNotifications: getConfigFromEnvVar("enablePostDatedNotifications", undefined, "false"), + requireApplicationName: getConfigFromEnvVar("requireApplicationName", undefined, "false"), + enableBackup: getBooleanConfigFromEnvVar("enableBackup", undefined, "false") }) } diff --git a/packages/cdk/resources/Functions.ts b/packages/cdk/resources/Functions.ts new file mode 100644 index 0000000000..1f08cae758 --- /dev/null +++ b/packages/cdk/resources/Functions.ts @@ -0,0 +1,488 @@ +import {Fn} from "aws-cdk-lib" +import {CfnSchedule, CfnScheduleGroup} from "aws-cdk-lib/aws-scheduler" +import { + ManagedPolicy, + PolicyStatement, + Role, + ServicePrincipal +} from "aws-cdk-lib/aws-iam" +import {Rule} from "aws-cdk-lib/aws-events" +import {LambdaFunction as LambdaFunctionTarget} from "aws-cdk-lib/aws-events-targets" +import {IFunction} from "aws-cdk-lib/aws-lambda" +import {Construct} from "constructs" +import {TypescriptLambdaFunction} from "@nhsdigital/eps-cdk-constructs" +import {NagSuppressions} from "cdk-nag" +import {resolve} from "node:path" + +export interface FunctionsProps { + readonly stackName: string + readonly samStackName: string + readonly version: string + readonly commitId: string + readonly logRetentionInDays: number + readonly logLevel: string + readonly environment: string + readonly enablePostDatedNotifications: string + readonly requireApplicationName: string + readonly deployCheckPrescriptionStatusUpdate: boolean + readonly enableBackup: boolean +} + +const baseDir = resolve(__dirname, "../../..") + +export class Functions extends Construct { + functions: {[key: string]: TypescriptLambdaFunction} + + public constructor(scope: Construct, id: string, props: FunctionsProps) { + super(scope, id) + + const sam = props.samStackName + + // Table names derived from SAM stack naming convention (not exported) + const prescriptionStatusUpdatesTableName = `${sam}-PrescriptionStatusUpdates` + const prescriptionNotificationStatesTableName = `${sam}-PrescriptionNotificationStatesV1` + + // Cross-stack imports from SAM stateful stack — tables + const tableWritePolicy = ManagedPolicy.fromManagedPolicyArn( + this, "TableWritePolicy", + Fn.importValue(`${sam}:tables:${prescriptionStatusUpdatesTableName}:TableWritePolicyArn`) + ) + const tableReadPolicy = ManagedPolicy.fromManagedPolicyArn( + this, "TableReadPolicy", + Fn.importValue(`${sam}:tables:${prescriptionStatusUpdatesTableName}:TableReadPolicyArn`) + ) + const tableKmsPolicy = ManagedPolicy.fromManagedPolicyArn( + this, "TableKmsPolicy", + Fn.importValue(`${sam}:tables:UsePrescriptionStatusUpdatesKMSKeyPolicyArn`) + ) + const notificationStatesReadPolicy = ManagedPolicy.fromManagedPolicyArn( + this, "NotificationStatesTableReadPolicy", + Fn.importValue( + `${sam}:tables:${prescriptionNotificationStatesTableName}:TableReadPolicyArn` + ) + ) + const notificationStatesWritePolicy = ManagedPolicy.fromManagedPolicyArn( + this, "NotificationStatesTableWritePolicy", + Fn.importValue( + `${sam}:tables:${prescriptionNotificationStatesTableName}:TableWritePolicyArn` + ) + ) + const notificationStatesKmsPolicy = ManagedPolicy.fromManagedPolicyArn( + this, "NotificationStatesKmsPolicy", + Fn.importValue(`${sam}:tables:UsePrescriptionNotificationStatesKMSKeyPolicyArn`) + ) + + // Cross-stack imports from SAM stateful stack — messaging + const notificationSqsKmsPolicy = ManagedPolicy.fromManagedPolicyArn( + this, "NotificationSqsKmsPolicy", + Fn.importValue(`${sam}-UseNotificationSQSQueueKMSKeyPolicyArn`) + ) + const writeNotifySqsPolicy = ManagedPolicy.fromManagedPolicyArn( + this, "WriteNotifySqsPolicy", + Fn.importValue(`${sam}-WriteNHSNotifyPrescriptionsSQSQueuePolicyArn`) + ) + const readNotifySqsPolicy = ManagedPolicy.fromManagedPolicyArn( + this, "ReadNotifySqsPolicy", + Fn.importValue(`${sam}-ReadNHSNotifyPrescriptionsSQSQueuePolicyArn`) + ) + const writePostDatedSqsPolicy = ManagedPolicy.fromManagedPolicyArn( + this, "WritePostDatedSqsPolicy", + Fn.importValue(`${sam}-WritePostDatedNotificationsSQSQueuePolicyArn`) + ) + const readPostDatedSqsPolicy = ManagedPolicy.fromManagedPolicyArn( + this, "ReadPostDatedSqsPolicy", + Fn.importValue(`${sam}-ReadPostDatedNotificationsSQSQueuePolicyArn`) + ) + const nhsNotifyPrescriptionsSqsQueueUrl = Fn.importValue(`${sam}-NHSNotifyPrescriptionsSQSQueueUrl`) + const postDatedNotificationsSqsQueueUrl = Fn.importValue(`${sam}-PostDatedNotificationsSQSQueueUrl`) + + // Cross-stack imports from SAM stateful stack — secrets + const sqsSaltSecret = Fn.importValue(`${sam}-SQSSaltSecret`) + const getSqsSaltSecretPolicy = ManagedPolicy.fromManagedPolicyArn( + this, "GetSqsSaltSecretPolicy", + Fn.importValue(`${sam}-GetSQSSaltSecretPolicy`) + ) + const notifyApiKeySecret = Fn.importValue(`${sam}-PSU-Notify-API-Key`) + const notifyPrivateKeySecret = Fn.importValue(`${sam}-PSU-Notify-PrivateKey`) + const notifyKidSecret = Fn.importValue(`${sam}-PSU-Notify-KID`) + const notifyAppIdSecret = Fn.importValue(`${sam}-PSU-Notify-Application-ID`) + const getPsuSecretPolicy = ManagedPolicy.fromManagedPolicyArn( + this, "GetPsuSecretPolicy", + Fn.importValue(`${sam}-GetPSUSecretPolicy`) + ) + const usePsuSecretsKmsPolicy = ManagedPolicy.fromManagedPolicyArn( + this, "UsePsuSecretsKmsPolicy", + Fn.importValue(`${sam}-UsePSUSecretsKMSKeyPolicyArn`) + ) + + // Cross-stack imports from SAM stateful stack — parameters + const getNotificationsParamPolicy = ManagedPolicy.fromManagedPolicyArn( + this, "GetNotificationsParamPolicy", + Fn.importValue(`${sam}-GetNotificationsParameterPolicy`) + ) + const enabledSiteOdsCodesParam = Fn.importValue(`${sam}-PSUNotifyEnabledSiteODSCodesParam`) + const enabledSystemsParam = Fn.importValue(`${sam}-PSUNotifyEnabledSystemsParam`) + const enabledSupplierAppIdsParam = Fn.importValue(`${sam}-PSUNotifyEnabledSupplierApplicationIDsParam`) + const blockedSiteOdsCodesParam = Fn.importValue(`${sam}-PSUNotifyBlockedSiteODSCodesParam`) + const notifyRoutingPlanIdParam = Fn.importValue(`${sam}-PSUNotifyRoutingPlanIDParam`) + const notifyApiBaseUrlParam = Fn.importValue(`${sam}-PSUNotifyApiBaseUrlParam`) + const enableNotificationsExternalParam = Fn.importValue(`${sam}-EnableNotificationsExternalName`) + const enableNotificationsInternalParam = Fn.importValue(`${sam}-EnableNotificationsInternalName`) + const testPrescriptionsParamName1 = Fn.importValue(`${sam}-TestPrescriptionsParameterName1`) + const testPrescriptionsParamName2 = Fn.importValue(`${sam}-TestPrescriptionsParameterName2`) + const testPrescriptionsParamName3 = Fn.importValue(`${sam}-TestPrescriptionsParameterName3`) + const testPrescriptionsParamName4 = Fn.importValue(`${sam}-TestPrescriptionsParameterName4`) + + // Account-level imports + const lambdaAccessSecretsPolicy = ManagedPolicy.fromManagedPolicyArn( + this, "LambdaAccessSecretsPolicy", + Fn.importValue("account-resources:LambdaAccessSecretsPolicy") + ) + + // Lambda Functions + + const updatePrescriptionStatus = new TypescriptLambdaFunction( + this, "UpdatePrescriptionStatus", { + functionName: `${props.stackName}-UpdatePrescriptionStatus`, + projectBaseDir: baseDir, + packageBasePath: "packages/updatePrescriptionStatus", + entryPoint: "src/updatePrescriptionStatus.ts", + environmentVariables: { + TABLE_NAME: prescriptionStatusUpdatesTableName, + NHS_NOTIFY_PRESCRIPTIONS_SQS_QUEUE_URL: nhsNotifyPrescriptionsSqsQueueUrl, + POST_DATED_PRESCRIPTIONS_SQS_QUEUE_URL: postDatedNotificationsSqsQueueUrl, + ENABLE_POST_DATED_NOTIFICATIONS: props.enablePostDatedNotifications, + SQS_SALT: sqsSaltSecret, + USE_APP_ID_FOR_NOTIFICATIONS_FILTERING: "false", + ENABLED_SYSTEMS_PARAM: enabledSystemsParam, + ENABLED_APPLICATION_IDS_PARAM: enabledSupplierAppIdsParam, + ENABLED_SITE_ODS_CODES_PARAM: enabledSiteOdsCodesParam, + BLOCKED_SITE_ODS_CODES_PARAM: blockedSiteOdsCodesParam, + ENABLE_NOTIFICATIONS_PARAM: enableNotificationsInternalParam, + ENVIRONMENT: props.environment, + REQUIRE_APPLICATION_NAME: props.requireApplicationName, + TEST_PRESCRIPTIONS_PARAM_NAME_1: testPrescriptionsParamName1, + TEST_PRESCRIPTIONS_PARAM_NAME_2: testPrescriptionsParamName2, + TEST_PRESCRIPTIONS_PARAM_NAME_3: testPrescriptionsParamName3, + TEST_PRESCRIPTIONS_PARAM_NAME_4: testPrescriptionsParamName4 + }, + additionalPolicies: [ + tableWritePolicy, + tableReadPolicy, + tableKmsPolicy, + notificationSqsKmsPolicy, + writeNotifySqsPolicy, + writePostDatedSqsPolicy, + getSqsSaltSecretPolicy, + getNotificationsParamPolicy + ], + logRetentionInDays: props.logRetentionInDays, + logLevel: props.logLevel, + version: props.version, + commitId: props.commitId + }) + + const convertRequestToFhirFormat = new TypescriptLambdaFunction( + this, "ConvertRequestToFhirFormat", { + functionName: `${props.stackName}-ConvertRequestToFhirFormat`, + projectBaseDir: baseDir, + packageBasePath: "packages/cpsuLambda", + entryPoint: "src/cpsu.ts", + environmentVariables: {}, + logRetentionInDays: props.logRetentionInDays, + logLevel: props.logLevel, + version: props.version, + commitId: props.commitId + }) + + const getStatusUpdates = new TypescriptLambdaFunction(this, "GetStatusUpdates", { + functionName: `${props.stackName}-GetStatusUpdates`, + projectBaseDir: baseDir, + packageBasePath: "packages/gsul", + entryPoint: "src/getStatusUpdates.ts", + environmentVariables: { + TABLE_NAME: prescriptionStatusUpdatesTableName + }, + additionalPolicies: [tableReadPolicy, tableKmsPolicy], + logRetentionInDays: props.logRetentionInDays, + logLevel: props.logLevel, + version: props.version, + commitId: props.commitId + }) + + const status = new TypescriptLambdaFunction(this, "Status", { + functionName: `${props.stackName}-status`, + projectBaseDir: baseDir, + packageBasePath: "packages/statusLambda", + entryPoint: "src/statusLambda.ts", + environmentVariables: {}, + additionalPolicies: [lambdaAccessSecretsPolicy], + logRetentionInDays: props.logRetentionInDays, + logLevel: props.logLevel, + version: props.version, + commitId: props.commitId + }) + + const capabilityStatement = new TypescriptLambdaFunction(this, "CapabilityStatement", { + functionName: `${props.stackName}-CapabilityStatement`, + projectBaseDir: baseDir, + packageBasePath: "packages/capabilityStatement", + entryPoint: "src/capabilityStatement.ts", + environmentVariables: {}, + logRetentionInDays: props.logRetentionInDays, + logLevel: props.logLevel, + version: props.version, + commitId: props.commitId + }) + + const nhsNotifyUpdateCallback = new TypescriptLambdaFunction( + this, "NHSNotifyUpdateCallback", { + functionName: `${props.stackName}-NHSNotifyUpdateCallback`, + projectBaseDir: baseDir, + packageBasePath: "packages/nhsNotifyUpdateCallback", + entryPoint: "src/lambdaHandler.ts", + environmentVariables: { + TABLE_NAME: prescriptionNotificationStatesTableName, + APP_ID_SECRET: notifyAppIdSecret, + API_KEY_SECRET: notifyApiKeySecret + }, + additionalPolicies: [ + notificationStatesReadPolicy, + notificationStatesWritePolicy, + notificationStatesKmsPolicy, + getPsuSecretPolicy, + usePsuSecretsKmsPolicy + ], + logRetentionInDays: props.logRetentionInDays, + logLevel: props.logLevel, + version: props.version, + commitId: props.commitId + }) + + const notifyProcessor = new TypescriptLambdaFunction(this, "NotifyProcessor", { + functionName: `${props.stackName}-NotifyProcessor`, + projectBaseDir: baseDir, + packageBasePath: "packages/nhsNotifyLambda", + entryPoint: "src/nhsNotifyLambda.ts", + environmentVariables: { + NHS_NOTIFY_PRESCRIPTIONS_SQS_QUEUE_URL: nhsNotifyPrescriptionsSqsQueueUrl, + TABLE_NAME: prescriptionNotificationStatesTableName, + API_KEY_SECRET: notifyApiKeySecret, + PRIVATE_KEY_SECRET: notifyPrivateKeySecret, + KID_SECRET: notifyKidSecret, + NHS_NOTIFY_ROUTING_ID_PARAM: notifyRoutingPlanIdParam, + NOTIFY_API_BASE_URL_PARAM: notifyApiBaseUrlParam, + MAKE_REAL_NOTIFY_REQUESTS_PARAM: enableNotificationsExternalParam + }, + additionalPolicies: [ + writeNotifySqsPolicy, + readNotifySqsPolicy, + notificationSqsKmsPolicy, + notificationStatesReadPolicy, + notificationStatesWritePolicy, + notificationStatesKmsPolicy, + getNotificationsParamPolicy, + getPsuSecretPolicy, + usePsuSecretsKmsPolicy + ], + timeoutInSeconds: 900, + logRetentionInDays: props.logRetentionInDays, + logLevel: props.logLevel, + version: props.version, + commitId: props.commitId + }) + + this.createSchedule( + "NotifyProcessorSchedule", + `${props.stackName}-NotifySchedule`, + "rate(1 minute)", + notifyProcessor.function, + props.stackName + ) + + const postDatedNotifyLambda = new TypescriptLambdaFunction( + this, "PostDatedNotifyLambda", { + functionName: `${props.stackName}-postDatedNotifyLambda`, + projectBaseDir: baseDir, + packageBasePath: "packages/postDatedLambda", + entryPoint: "src/main.ts", + environmentVariables: { + NHS_NOTIFY_PRESCRIPTIONS_SQS_QUEUE_URL: nhsNotifyPrescriptionsSqsQueueUrl, + POST_DATED_PRESCRIPTIONS_SQS_QUEUE_URL: postDatedNotificationsSqsQueueUrl, + TABLE_NAME: prescriptionStatusUpdatesTableName + }, + additionalPolicies: [ + writeNotifySqsPolicy, + readNotifySqsPolicy, + notificationSqsKmsPolicy, + writePostDatedSqsPolicy, + readPostDatedSqsPolicy, + tableWritePolicy, + tableReadPolicy, + tableKmsPolicy + ], + timeoutInSeconds: 900, + logRetentionInDays: props.logRetentionInDays, + logLevel: props.logLevel, + version: props.version, + commitId: props.commitId + }) + + this.createSchedule( + "PostDatedNotifySchedule", + `${props.stackName}-PostDatedNotifySchedule`, + "rate(15 minutes)", + postDatedNotifyLambda.function, + props.stackName + ) + + // Conditional: CheckPrescriptionStatusUpdates + let checkPrescriptionStatusUpdates: TypescriptLambdaFunction | undefined + if (props.deployCheckPrescriptionStatusUpdate) { + checkPrescriptionStatusUpdates = new TypescriptLambdaFunction( + this, "CheckPrescriptionStatusUpdates", { + functionName: `${props.stackName}-CheckPrescriptionStatusUpdates`, + projectBaseDir: baseDir, + packageBasePath: "packages/checkPrescriptionStatusUpdates", + entryPoint: "src/checkPrescriptionStatusUpdates.ts", + environmentVariables: { + TABLE_NAME: prescriptionStatusUpdatesTableName, + MIN_RESULTS_RETURNED: "5", + MAX_RESULTS_RETURNED: "15" + }, + additionalPolicies: [tableReadPolicy, tableKmsPolicy], + logRetentionInDays: props.logRetentionInDays, + logLevel: props.logLevel, + version: props.version, + commitId: props.commitId + } + ) + } + + // Conditional: PsuRestoreValidation + if (props.enableBackup) { + const psuRestoreValidationPolicy = new ManagedPolicy(this, "PsuRestoreValidationPolicy", { + managedPolicyName: `${props.stackName}-PsuRestoreValidationPolicy`, + statements: [ + new PolicyStatement({ + actions: ["backup:PutRestoreValidationResult"], + resources: ["*"] + }), + new PolicyStatement({ + actions: [ + "dynamodb:GetItem", + "dynamodb:BatchGetItem", + "dynamodb:Scan", + "dynamodb:Query", + "dynamodb:ConditionCheckItem", + "dynamodb:DescribeTable" + ], + resources: [ + Fn.sub( + "arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/awsbackup-restore-test-*" + ) + ] + }) + ] + }) + + NagSuppressions.addResourceSuppressions(psuRestoreValidationPolicy, [ + { + id: "AwsSolutions-IAM5", + reason: "Wildcard needed for backup:PutRestoreValidationResult on any restore job" + } + ]) + + const psuRestoreValidation = new TypescriptLambdaFunction(this, "PsuRestoreValidation", { + functionName: `${props.stackName}-PsuRestoreValidation`, + projectBaseDir: baseDir, + packageBasePath: "packages/psuRestoreValidationLambda", + entryPoint: "src/handler.ts", + environmentVariables: {}, + additionalPolicies: [ + psuRestoreValidationPolicy, + tableReadPolicy, + tableKmsPolicy + ], + logRetentionInDays: props.logRetentionInDays, + logLevel: props.logLevel, + version: props.version, + commitId: props.commitId + }) + + const restoreRule = new Rule(this, "PsuBackupRestoreJobStateChangeRule", { + eventPattern: { + source: ["aws.backup"], + detailType: ["Restore Job State Change"], + detail: { + resourceType: ["DynamoDB.FullyManaged"], + status: ["COMPLETED"], + sourceResourceArn: [ + Fn.sub( + + `arn:aws:dynamodb:\${AWS::Region}:\${AWS::AccountId}:table/${prescriptionStatusUpdatesTableName}` + ) + ] + } + } + }) + restoreRule.addTarget(new LambdaFunctionTarget(psuRestoreValidation.function)) + } + + this.functions = { + updatePrescriptionStatus, + convertRequestToFhirFormat, + getStatusUpdates, + status, + capabilityStatement, + nhsNotifyUpdateCallback, + notifyProcessor, + postDatedNotifyLambda, + ...(checkPrescriptionStatusUpdates ? {checkPrescriptionStatusUpdates} : {}) + } + } + + private createSchedule( + id: string, + scheduleName: string, + scheduleExpression: string, + lambdaFunction: IFunction, + stackName: string + ): void { + const scheduleRole = new Role(this, `${id}Role`, { + assumedBy: new ServicePrincipal("scheduler.amazonaws.com") + }) + + const schedulePolicy = new ManagedPolicy(this, `${id}Policy`, { + statements: [ + new PolicyStatement({ + actions: ["lambda:InvokeFunction"], + resources: [lambdaFunction.functionArn] + }) + ] + }) + scheduleRole.addManagedPolicy(schedulePolicy) + + NagSuppressions.addResourceSuppressions(scheduleRole, [ + { + id: "AwsSolutions-IAM5", + reason: "Role only grants InvokeFunction on a specific Lambda" + } + ]) + + const scheduleGroup = new CfnScheduleGroup(this, `${id}Group`, { + name: `${stackName}-${id}Group` + }) + + new CfnSchedule(this, id, { + name: scheduleName, + groupName: scheduleGroup.name, + scheduleExpression: scheduleExpression, + flexibleTimeWindow: {mode: "OFF"}, + target: { + arn: lambdaFunction.functionArn, + roleArn: scheduleRole.roleArn + } + }) + } +} diff --git a/packages/cdk/stacks/PsuStatelessStack.ts b/packages/cdk/stacks/PsuStatelessStack.ts index 30d2368327..d58988c7a2 100644 --- a/packages/cdk/stacks/PsuStatelessStack.ts +++ b/packages/cdk/stacks/PsuStatelessStack.ts @@ -1,24 +1,46 @@ import {App, Stack} from "aws-cdk-lib" import {nagSuppressions} from "../nagSuppressions" import {StandardStackProps} from "@nhsdigital/eps-cdk-constructs" +import {Functions} from "../resources/Functions" +// Apis will be used once StateMachines are also migrated +// import {Apis} from "../resources/Apis" export interface PsuStatelessStackProps extends StandardStackProps { readonly stackName: string + readonly samStackName: string readonly logRetentionInDays: number + readonly logLevel: string + readonly environment: string readonly mutualTlsTrustStoreKey: string | undefined readonly forwardCsocLogs: boolean readonly csocApiGatewayDestination: string readonly deployCheckPrescriptionStatusUpdate: boolean readonly exposeGetStatusUpdates: boolean + readonly enablePostDatedNotifications: string + readonly requireApplicationName: string + readonly enableBackup: boolean } export class PsuStatelessStack extends Stack { public constructor(scope: App, id: string, props: PsuStatelessStackProps) { super(scope, id, props) - // Apis construct will be instantiated here once Functions and StateMachines are migrated: + new Functions(this, "Functions", { + stackName: props.stackName, + samStackName: props.samStackName, + version: props.version, + commitId: props.commitId, + logRetentionInDays: props.logRetentionInDays, + logLevel: props.logLevel, + environment: props.environment, + enablePostDatedNotifications: props.enablePostDatedNotifications, + requireApplicationName: props.requireApplicationName, + deployCheckPrescriptionStatusUpdate: props.deployCheckPrescriptionStatusUpdate, + enableBackup: props.enableBackup + }) + + // Apis construct will be fully wired once StateMachines are also migrated: // - // const functions = new Functions(this, "Functions", { ... }) // const stateMachines = new StateMachines(this, "StateMachines", { ... }) // new Apis(this, "Apis", { // stackName: props.stackName, diff --git a/zizmor.yml b/zizmor.yml index 2776fe96ab..728f741a11 100644 --- a/zizmor.yml +++ b/zizmor.yml @@ -7,6 +7,8 @@ rules: - run_package_code_and_api.yml:16:7 - cdk_package_code.yml:25:7 - cdk_release_code.yml:45:7 + - cdk_release_code.yml:57:7 + - cdk_release_code.yml:69:7 secrets-outside-env: # these are ignored because they are using known secrets ignore: From 9ef599475d9f082d05e17a4b0e4b4266b42d8312 Mon Sep 17 00:00:00 2001 From: Jim Wild Date: Thu, 9 Apr 2026 16:03:37 +0000 Subject: [PATCH 7/9] Migrate over state machine --- Makefile | 12 +- packages/cdk/nagSuppressions.ts | 80 ++++++++++- .../Format1UpdatePrescriptionsStatus.ts | 127 ++++++++++++++++++ .../UpdatePrescriptionStatus.ts | 84 ++++++++++++ packages/cdk/resources/StateMachines.ts | 92 +++++++++++++ packages/cdk/stacks/PsuStatelessStack.ts | 37 ++--- 6 files changed, 411 insertions(+), 21 deletions(-) create mode 100644 packages/cdk/resources/StateMachineDefinitions/Format1UpdatePrescriptionsStatus.ts create mode 100644 packages/cdk/resources/StateMachineDefinitions/UpdatePrescriptionStatus.ts create mode 100644 packages/cdk/resources/StateMachines.ts diff --git a/Makefile b/Makefile index e6ac7a7f6f..f06b8951e5 100644 --- a/Makefile +++ b/Makefile @@ -257,7 +257,17 @@ cdk-deploy: npm run cdk-deploy --workspace packages/cdk cdk-synth: - CDK_CONFIG_stackName=psu-api \ + CDK_CONFIG_stackName=psu-cdk \ + CDK_CONFIG_samStackName=psu \ + CDK_CONFIG_logRetentionInDays=30 \ + CDK_CONFIG_logLevel=DEBUG \ + CDK_CONFIG_environment=dev \ + CDK_CONFIG_forwardCsocLogs=false \ + CDK_CONFIG_deployCheckPrescriptionStatusUpdate=true \ + CDK_CONFIG_exposeGetStatusUpdates=false \ + CDK_CONFIG_enablePostDatedNotifications=false \ + CDK_CONFIG_requireApplicationName=false \ + CDK_CONFIG_enableBackup=false \ npm run cdk-synth --workspace packages/cdk cdk-diff: diff --git a/packages/cdk/nagSuppressions.ts b/packages/cdk/nagSuppressions.ts index d53c150124..d03ec56507 100644 --- a/packages/cdk/nagSuppressions.ts +++ b/packages/cdk/nagSuppressions.ts @@ -1,6 +1,80 @@ +/* eslint-disable max-len */ import {Stack} from "aws-cdk-lib" +import {safeAddNagSuppression, safeAddNagSuppressionGroup} from "@nhsdigital/eps-cdk-constructs" -// eslint-disable-next-line @typescript-eslint/no-unused-vars -export const nagSuppressions = (_stack: Stack) => { - // Nag suppressions will be added here as resources are migrated +export const nagSuppressions = (stack: Stack) => { + // State machine log policies require wildcard for log streams and log delivery actions + safeAddNagSuppressionGroup( + stack, + [ + "/PsuStatelessStack/StateMachines/UpdatePrescriptionStatusStateMachine/StateMachinePutLogsManagedPolicy/Resource", + "/PsuStatelessStack/StateMachines/Format1UpdatePrescriptionsStatusStateMachine/StateMachinePutLogsManagedPolicy/Resource" + ], + [ + { + id: "AwsSolutions-IAM5", + reason: "Wildcard on log-stream is required to write to any log stream under the log group. Wildcard on Resource::* is required for log delivery management actions (DescribeLogGroups, ListLogDeliveries, etc.) which do not support resource-level permissions." + } + ] + ) + + // API Gateway does not use request validation — validation is handled by service logic + safeAddNagSuppression( + stack, + "/PsuStatelessStack/Apis/RestApiGateway/ApiGateway/Resource", + [ + { + id: "AwsSolutions-APIG2", + reason: "Request validation is handled by the backend service logic and FHIR validation state machine, not at the API Gateway level." + } + ] + ) + + // API Gateway CloudWatch role uses AWS managed policy + safeAddNagSuppression( + stack, + "/PsuStatelessStack/Apis/RestApiGateway/ApiGateway/CloudWatchRole/Resource", + [ + { + id: "AwsSolutions-IAM4", + reason: "AWS managed policy AmazonAPIGatewayPushToCloudWatchLogs is the standard approach for API Gateway logging and is maintained by AWS.", + appliesTo: ["Policy::arn::iam::aws:policy/service-role/AmazonAPIGatewayPushToCloudWatchLogs"] + } + ] + ) + + // API Gateway stage is not associated with WAFv2 — WAF is managed externally via Apigee + safeAddNagSuppression( + stack, + "/PsuStatelessStack/Apis/RestApiGateway/ApiGateway/DeploymentStage.prod/Resource", + [ + { + id: "AwsSolutions-APIG3", + reason: "WAF is managed externally via the Apigee proxy layer, not at the API Gateway level." + } + ] + ) + + // API methods do not use authorization — mTLS and Apigee handle auth externally + safeAddNagSuppressionGroup( + stack, + [ + "/PsuStatelessStack/Apis/RestApiGateway/ApiGateway/Default/POST/Resource", + "/PsuStatelessStack/Apis/RestApiGateway/ApiGateway/Default/format-1/POST/Resource", + "/PsuStatelessStack/Apis/RestApiGateway/ApiGateway/Default/notification-delivery-status-callback/POST/Resource", + "/PsuStatelessStack/Apis/RestApiGateway/ApiGateway/Default/_status/GET/Resource", + "/PsuStatelessStack/Apis/RestApiGateway/ApiGateway/Default/metadata/GET/Resource", + "/PsuStatelessStack/Apis/RestApiGateway/ApiGateway/Default/checkprescriptionstatusupdates/GET/Resource" + ], + [ + { + id: "AwsSolutions-APIG4", + reason: "Authorization is handled externally via mutual TLS and the Apigee API gateway proxy. API Gateway methods do not require an additional authorizer." + }, + { + id: "AwsSolutions-COG4", + reason: "This API does not use Cognito for authentication. Auth is handled via mutual TLS and the Apigee API gateway proxy." + } + ] + ) } diff --git a/packages/cdk/resources/StateMachineDefinitions/Format1UpdatePrescriptionsStatus.ts b/packages/cdk/resources/StateMachineDefinitions/Format1UpdatePrescriptionsStatus.ts new file mode 100644 index 0000000000..7e3192549e --- /dev/null +++ b/packages/cdk/resources/StateMachineDefinitions/Format1UpdatePrescriptionsStatus.ts @@ -0,0 +1,127 @@ +import {IFunction} from "aws-cdk-lib/aws-lambda" +import {LambdaInvoke} from "aws-cdk-lib/aws-stepfunctions-tasks" +import {Construct} from "constructs" +import { + Chain, + Choice, + Condition, + IChainable, + Pass, + TaskInput +} from "aws-cdk-lib/aws-stepfunctions" + +export interface Format1UpdatePrescriptionsStatusDefinitionProps { + readonly convertRequestToFhirFormatFunction: IFunction + readonly updatePrescriptionStatusFunction: IFunction +} + +export class Format1UpdatePrescriptionsStatusDefinition extends Construct { + public readonly definition: IChainable + + public constructor( + scope: Construct, id: string, props: Format1UpdatePrescriptionsStatusDefinitionProps + ) { + super(scope, id) + + const catchAllError = new Pass(this, "CatchAllError", { + result: { + value: { + Payload: { + statusCode: 500, + headers: { + "Content-Type": "application/fhir+json", + "Cache-Control": "no-cache" + }, + body: JSON.stringify({ + resourceType: "OperationOutcome", + issue: [{severity: "error", code: "processing", diagnostics: "System error"}] + }) + } + } + } + }) + + const callConvertRequestToFhirFormat = new LambdaInvoke( + this, "Call Convert Request To Fhir Format", { + lambdaFunction: props.convertRequestToFhirFormatFunction, + assign: { + convertStatusCode: "{% $states.result.Payload.statusCode %}", + convertHeaders: "{% $states.result.Payload.headers %}", + convertBody: "{% $parse($states.result.Payload.body) %}" + } + } + ) + callConvertRequestToFhirFormat.addCatch(catchAllError) + + const failedConvertRequestToFhir = new Pass(this, "Failed Convert Request to FHIR", { + outputs: { + Payload: { + statusCode: "{% $convertStatusCode %}", + headers: "{% $convertHeaders %}", + body: "{% $string($convertBody) %}" + } + } + }) + + const callUpdatePrescriptionStatus = new LambdaInvoke( + this, "Call Update Prescription Status", { + lambdaFunction: props.updatePrescriptionStatusFunction, + payload: TaskInput.fromObject({ + body: "{% $string($convertBody) %}", + headers: "{% $convertHeaders %}" + }), + assign: { + updateStatusCode: "{% $states.result.Payload.statusCode %}", + updatePayload: "{% $states.result.Payload %}" + } + } + ) + callUpdatePrescriptionStatus.addCatch(catchAllError) + + const translate409To202 = new Pass(this, "Translate 409 to 202", { + result: { + value: { + Payload: { + statusCode: 202, + headers: { + "Content-Type": "application/fhir+json", + "Cache-Control": "no-cache" + }, + body: JSON.stringify({ + resourceType: "OperationOutcome", + issue: [{ + severity: "information", + code: "informational", + diagnostics: + "Duplicate update detected. The message was valid but did not result in an update." + }] + }) + } + } + } + }) + + const endState = new Pass(this, "End State") + + const checkConvertResult = new Choice(this, "Convert Request to FHIR result") + const convertNotOk = Condition.jsonata("{% $convertStatusCode != 200 %}") + + const checkUpdateResult = new Choice(this, "Check Update Prescription Status Result") + const updateIs409 = Condition.jsonata("{% $updateStatusCode = 409 %}") + + this.definition = Chain + .start(callConvertRequestToFhirFormat) + .next( + checkConvertResult + .when(convertNotOk, failedConvertRequestToFhir) + .otherwise( + callUpdatePrescriptionStatus + .next( + checkUpdateResult + .when(updateIs409, translate409To202) + .otherwise(endState) + ) + ) + ) + } +} diff --git a/packages/cdk/resources/StateMachineDefinitions/UpdatePrescriptionStatus.ts b/packages/cdk/resources/StateMachineDefinitions/UpdatePrescriptionStatus.ts new file mode 100644 index 0000000000..11847d5284 --- /dev/null +++ b/packages/cdk/resources/StateMachineDefinitions/UpdatePrescriptionStatus.ts @@ -0,0 +1,84 @@ +import {IFunction} from "aws-cdk-lib/aws-lambda" +import {LambdaInvoke} from "aws-cdk-lib/aws-stepfunctions-tasks" +import {Construct} from "constructs" +import { + Chain, + Choice, + Condition, + IChainable, + Pass +} from "aws-cdk-lib/aws-stepfunctions" + +export interface UpdatePrescriptionStatusDefinitionProps { + readonly fhirValidationFunction: IFunction + readonly updatePrescriptionStatusFunction: IFunction +} + +export class UpdatePrescriptionStatusDefinition extends Construct { + public readonly definition: IChainable + + public constructor( + scope: Construct, id: string, props: UpdatePrescriptionStatusDefinitionProps + ) { + super(scope, id) + + const catchAllError = new Pass(this, "CatchAllError", { + result: { + value: { + Payload: { + statusCode: 500, + headers: { + "Content-Type": "application/fhir+json", + "Cache-Control": "no-cache" + }, + body: JSON.stringify({ + resourceType: "OperationOutcome", + issue: [{severity: "error", code: "processing", diagnostics: "System error"}] + }) + } + } + } + }) + + const callFhirValidation = new LambdaInvoke(this, "Call FHIR Validation", { + lambdaFunction: props.fhirValidationFunction, + assign: { + fhirValidationResponse: "{% $states.result.Payload %}", + fhirValidationErrorCount: + "{% $count($states.result.Payload.issue[severity = 'error']) %}" + } + }) + callFhirValidation.addCatch(catchAllError) + + const returnFailedFhirValidationErrors = new Pass(this, "Return Failed FHIR Validation Errors", { + outputs: { + Payload: { + statusCode: 400, + headers: { + "Content-Type": "application/fhir+json", + "Cache-Control": "no-cache" + }, + body: "{% $string($fhirValidationResponse) %}" + } + } + }) + + const callUpdatePrescriptionStatus = new LambdaInvoke( + this, "Call Update Prescription Status", { + lambdaFunction: props.updatePrescriptionStatusFunction + } + ) + callUpdatePrescriptionStatus.addCatch(catchAllError) + + const doFhirValidationErrorsExist = new Choice(this, "Do FHIR Validation Errors Exist") + const hasErrors = Condition.jsonata("{% $fhirValidationErrorCount > 0 %}") + + this.definition = Chain + .start(callFhirValidation) + .next( + doFhirValidationErrorsExist + .when(hasErrors, returnFailedFhirValidationErrors) + .otherwise(callUpdatePrescriptionStatus) + ) + } +} diff --git a/packages/cdk/resources/StateMachines.ts b/packages/cdk/resources/StateMachines.ts new file mode 100644 index 0000000000..4fabb92215 --- /dev/null +++ b/packages/cdk/resources/StateMachines.ts @@ -0,0 +1,92 @@ +import {Fn} from "aws-cdk-lib" +import {Function} from "aws-cdk-lib/aws-lambda" +import {ManagedPolicy, PolicyStatement} from "aws-cdk-lib/aws-iam" +import {Construct} from "constructs" +import {ExpressStateMachine, TypescriptLambdaFunction} from "@nhsdigital/eps-cdk-constructs" +import {UpdatePrescriptionStatusDefinition} from "./StateMachineDefinitions/UpdatePrescriptionStatus" +import {Format1UpdatePrescriptionsStatusDefinition} from "./StateMachineDefinitions/Format1UpdatePrescriptionsStatus" + +export interface StateMachinesProps { + readonly stackName: string + readonly logRetentionInDays: number + readonly functions: {[key: string]: TypescriptLambdaFunction} +} + +export class StateMachines extends Construct { + stateMachines: {[key: string]: ExpressStateMachine} + + public constructor(scope: Construct, id: string, props: StateMachinesProps) { + super(scope, id) + + // Import the FHIR Validator function from the fhir-validator stack + const fhirValidatorFunctionArn = `${Fn.importValue("fhir-validator:FHIRValidatorUKCoreLambdaArn")}:$LATEST` + const fhirValidatorFunction = Function.fromFunctionArn( + this, "FhirValidatorFunction", fhirValidatorFunctionArn + ) + const fhirValidatorExecutePolicy = ManagedPolicy.fromManagedPolicyArn( + this, "FhirValidatorExecutePolicy", + Fn.importValue("fhir-validator:FHIRValidatorUKCoreExecuteLambdaPolicyArn") + ) + + // Policy to invoke the FHIR validator function (needed because the import + // uses a qualified ARN with :$LATEST suffix which may not be covered by + // the execute policy from the fhir-validator stack) + const callFhirValidatorPolicy = new ManagedPolicy(this, "CallFhirValidatorPolicy", { + description: "Invoke FHIR validator lambda from state machine", + statements: [ + new PolicyStatement({ + actions: ["lambda:InvokeFunction"], + resources: [fhirValidatorFunctionArn] + }) + ] + }) + + // UpdatePrescriptionStatus state machine definition + const updatePrescriptionStatusDefinition = new UpdatePrescriptionStatusDefinition( + this, "UpdatePrescriptionStatusDefinition", { + fhirValidationFunction: fhirValidatorFunction, + updatePrescriptionStatusFunction: props.functions.updatePrescriptionStatus.function + } + ) + + const updatePrescriptionStatusStateMachine = new ExpressStateMachine( + this, "UpdatePrescriptionStatusStateMachine", { + stackName: props.stackName, + stateMachineName: `${props.stackName}-UpdatePrescriptionStatus`, + definition: updatePrescriptionStatusDefinition.definition, + logRetentionInDays: props.logRetentionInDays, + additionalPolicies: [ + props.functions.updatePrescriptionStatus.executionPolicy, + fhirValidatorExecutePolicy, + callFhirValidatorPolicy + ] + } + ) + + // Format1UpdatePrescriptionsStatus state machine definition + const format1Definition = new Format1UpdatePrescriptionsStatusDefinition( + this, "Format1UpdatePrescriptionsStatusDefinition", { + convertRequestToFhirFormatFunction: props.functions.convertRequestToFhirFormat.function, + updatePrescriptionStatusFunction: props.functions.updatePrescriptionStatus.function + } + ) + + const format1UpdatePrescriptionsStatusStateMachine = new ExpressStateMachine( + this, "Format1UpdatePrescriptionsStatusStateMachine", { + stackName: props.stackName, + stateMachineName: `${props.stackName}-Format1UpdatePrescriptionsStatus`, + definition: format1Definition.definition, + logRetentionInDays: props.logRetentionInDays, + additionalPolicies: [ + props.functions.convertRequestToFhirFormat.executionPolicy, + props.functions.updatePrescriptionStatus.executionPolicy + ] + } + ) + + this.stateMachines = { + updatePrescriptionStatus: updatePrescriptionStatusStateMachine, + format1UpdatePrescriptionsStatus: format1UpdatePrescriptionsStatusStateMachine + } + } +} diff --git a/packages/cdk/stacks/PsuStatelessStack.ts b/packages/cdk/stacks/PsuStatelessStack.ts index d58988c7a2..c1a681484a 100644 --- a/packages/cdk/stacks/PsuStatelessStack.ts +++ b/packages/cdk/stacks/PsuStatelessStack.ts @@ -2,8 +2,8 @@ import {App, Stack} from "aws-cdk-lib" import {nagSuppressions} from "../nagSuppressions" import {StandardStackProps} from "@nhsdigital/eps-cdk-constructs" import {Functions} from "../resources/Functions" -// Apis will be used once StateMachines are also migrated -// import {Apis} from "../resources/Apis" +import {StateMachines} from "../resources/StateMachines" +import {Apis} from "../resources/Apis" export interface PsuStatelessStackProps extends StandardStackProps { readonly stackName: string @@ -25,7 +25,7 @@ export class PsuStatelessStack extends Stack { public constructor(scope: App, id: string, props: PsuStatelessStackProps) { super(scope, id, props) - new Functions(this, "Functions", { + const functions = new Functions(this, "Functions", { stackName: props.stackName, samStackName: props.samStackName, version: props.version, @@ -39,20 +39,23 @@ export class PsuStatelessStack extends Stack { enableBackup: props.enableBackup }) - // Apis construct will be fully wired once StateMachines are also migrated: - // - // const stateMachines = new StateMachines(this, "StateMachines", { ... }) - // new Apis(this, "Apis", { - // stackName: props.stackName, - // logRetentionInDays: props.logRetentionInDays, - // mutualTlsTrustStoreKey: props.mutualTlsTrustStoreKey, - // forwardCsocLogs: props.forwardCsocLogs, - // csocApiGatewayDestination: props.csocApiGatewayDestination, - // deployCheckPrescriptionStatusUpdate: props.deployCheckPrescriptionStatusUpdate, - // exposeGetStatusUpdates: props.exposeGetStatusUpdates, - // functions: functions.functions, - // stateMachines: stateMachines.stateMachines, - // }) + const stateMachines = new StateMachines(this, "StateMachines", { + stackName: props.stackName, + logRetentionInDays: props.logRetentionInDays, + functions: functions.functions + }) + + new Apis(this, "Apis", { + stackName: props.stackName, + logRetentionInDays: props.logRetentionInDays, + mutualTlsTrustStoreKey: props.mutualTlsTrustStoreKey, + forwardCsocLogs: props.forwardCsocLogs, + csocApiGatewayDestination: props.csocApiGatewayDestination, + deployCheckPrescriptionStatusUpdate: props.deployCheckPrescriptionStatusUpdate, + exposeGetStatusUpdates: props.exposeGetStatusUpdates, + functions: functions.functions, + stateMachines: stateMachines.stateMachines + }) nagSuppressions(this) } From f568a10968d99a9fe49b19c6260541a463bf3155 Mon Sep 17 00:00:00 2001 From: Jim Wild Date: Fri, 10 Apr 2026 09:06:11 +0000 Subject: [PATCH 8/9] Fix permission issue --- .grype.yaml | 8 ++++++++ packages/cdk/resources/Functions.ts | 16 ++++------------ 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/.grype.yaml b/.grype.yaml index d07cef7c42..fef22bd293 100644 --- a/.grype.yaml +++ b/.grype.yaml @@ -1,3 +1,11 @@ ignore: # path-to-regexp - dependency of aws-sdk-client-mock - vulnerability: GHSA-j3q9-mxjg-w52f + # This is a ReDoS vulnerability that we're not exposed to, since we don't take user regex input + - vulnerability: GHSA-2g4f-4pwh-qvx6 + reason: "We don't take user regex input, so we're not vulnerable to this issue." + # Axios vulnerability in checking NO_PROXY rules, which we don't use + - vulnerability: GHSA-3p68-rc4w-qgx5 + reason: "We don't use NO_PROXY rules, so we're not vulnerable to this issue." + - vulnerability: GHSA-j965-2qgj-vjmq + reason: "We use v3 packages already, so we're not vulnerable to this issue." diff --git a/packages/cdk/resources/Functions.ts b/packages/cdk/resources/Functions.ts index 1f08cae758..1c73242a6b 100644 --- a/packages/cdk/resources/Functions.ts +++ b/packages/cdk/resources/Functions.ts @@ -1,5 +1,5 @@ import {Fn} from "aws-cdk-lib" -import {CfnSchedule, CfnScheduleGroup} from "aws-cdk-lib/aws-scheduler" +import {CfnSchedule} from "aws-cdk-lib/aws-scheduler" import { ManagedPolicy, PolicyStatement, @@ -296,8 +296,7 @@ export class Functions extends Construct { "NotifyProcessorSchedule", `${props.stackName}-NotifySchedule`, "rate(1 minute)", - notifyProcessor.function, - props.stackName + notifyProcessor.function ) const postDatedNotifyLambda = new TypescriptLambdaFunction( @@ -332,8 +331,7 @@ export class Functions extends Construct { "PostDatedNotifySchedule", `${props.stackName}-PostDatedNotifySchedule`, "rate(15 minutes)", - postDatedNotifyLambda.function, - props.stackName + postDatedNotifyLambda.function ) // Conditional: CheckPrescriptionStatusUpdates @@ -446,8 +444,7 @@ export class Functions extends Construct { id: string, scheduleName: string, scheduleExpression: string, - lambdaFunction: IFunction, - stackName: string + lambdaFunction: IFunction ): void { const scheduleRole = new Role(this, `${id}Role`, { assumedBy: new ServicePrincipal("scheduler.amazonaws.com") @@ -470,13 +467,8 @@ export class Functions extends Construct { } ]) - const scheduleGroup = new CfnScheduleGroup(this, `${id}Group`, { - name: `${stackName}-${id}Group` - }) - new CfnSchedule(this, id, { name: scheduleName, - groupName: scheduleGroup.name, scheduleExpression: scheduleExpression, flexibleTimeWindow: {mode: "OFF"}, target: { From 983cd09bef0a91dfce140aacbdb279106cbe8cb4 Mon Sep 17 00:00:00 2001 From: Jim Wild Date: Fri, 10 Apr 2026 09:40:47 +0000 Subject: [PATCH 9/9] Commit todo file for posterity --- TODO.md | 95 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 95 insertions(+) create mode 100644 TODO.md diff --git a/TODO.md b/TODO.md new file mode 100644 index 0000000000..34f3bd7335 --- /dev/null +++ b/TODO.md @@ -0,0 +1,95 @@ +# FIXME: Delete this file + + +# CDK Migration TODO — Stateless Stack + +This file tracks the migration of stateless components from SAM templates to CDK. +Stateful resources (DynamoDB tables, SSM parameters, Secrets Manager secrets, SQS queues) remain in SAM. + +## Lambda Functions (`SAMtemplates/functions/main.yaml`) + +Each Lambda includes the function itself, a nested `lambda_resources.yaml` stack (IAM role, log group, Splunk subscription filter), and associated IAM policies. + +- [x] **UpdatePrescriptionStatus** — Core function that writes prescription status updates to DynamoDB and sends SQS messages for notifications. Has extensive IAM policies for DynamoDB, SQS, Secrets Manager, and SSM parameters. *(uses `TypescriptLambdaFunction` construct)* +- [x] **ConvertRequestToFhirFormat (CPSU)** — Converts custom format requests to FHIR format. No additional IAM policies beyond basic Lambda execution. *(uses `TypescriptLambdaFunction` construct)* +- [x] **GetStatusUpdates** — Reads prescription status updates from DynamoDB. Needs DynamoDB read and KMS policies. *(uses `TypescriptLambdaFunction` construct)* +- [x] **Status** — Returns `_status` health check info (version, commit ID). Needs Secrets Manager access. *(uses `TypescriptLambdaFunction` construct)* +- [x] **CapabilityStatement** — Returns FHIR CapabilityStatement. No additional IAM policies. *(uses `TypescriptLambdaFunction` construct)* +- [x] **CheckPrescriptionStatusUpdates** — Conditionally deployed (`DeployCheckPrescriptionStatusUpdate`). Reads from DynamoDB. Needs DynamoDB read and KMS policies. *(conditional `TypescriptLambdaFunction` construct)* +- [x] **NotifyProcessor** — Processes NHS Notify messages from SQS on a 1-minute schedule (EventBridge Scheduler). Needs SQS, DynamoDB, Secrets Manager, SSM, and KMS policies. Has a dedicated scheduler IAM role. *(uses `TypescriptLambdaFunction` + `CfnSchedule`)* +- [x] **PostDatedNotifyLambda** — Processes post-dated notifications on a 15-minute schedule (EventBridge Scheduler). Needs SQS and DynamoDB policies. Has a dedicated scheduler IAM role. *(uses `TypescriptLambdaFunction` + `CfnSchedule`)* +- [x] **NHSNotifyUpdateCallback** — Handles NHS Notify delivery status callbacks. Needs DynamoDB and Secrets Manager policies. *(uses `TypescriptLambdaFunction` construct)* +- [x] **PsuRestoreValidation** — Conditionally deployed (`EnableBackup`). Validates DynamoDB backup restores. Has a custom IAM policy for `backup:PutRestoreValidationResult` and an EventBridge rule for restore job state changes. *(conditional `TypescriptLambdaFunction` + `Rule`)* + +### Per-Lambda shared resources (`lambda_resources.yaml`) +Each Lambda above has a nested stack that creates: +- [x] IAM execution role with CloudWatch Logs permissions *(handled by `TypescriptLambdaFunction` construct)* +- [x] CloudWatch Log Group (with configurable retention, optional KMS encryption) *(handled by `TypescriptLambdaFunction` construct)* +- [x] Splunk subscription filter (conditional on `EnableSplunk`) *(handled by `TypescriptLambdaFunction` construct — always enabled)* +- [x] Execute Lambda managed policy (exported for cross-stack references) *(handled by `TypescriptLambdaFunction` construct)* + +## State Machines (`SAMtemplates/state_machines/main.yaml`) + +Each state machine includes the state machine itself and a nested `state_machine_resources.yaml` stack (IAM role, log group, Splunk subscription filter). + +- [x] **UpdatePrescriptionStatusStateMachine** — EXPRESS type. Invokes FHIR Validator (cross-stack import from `fhir-validator` stack) and UpdatePrescriptionStatus Lambda. Uses ASL definition file `UpdatePrescriptionStatusStateMachine.asl.json`. *(uses `ExpressStateMachine` construct + CDK step function definition)* +- [x] **Format1UpdatePrescriptionsStatusStateMachine** — EXPRESS type. Invokes ConvertRequestToFhirFormat and UpdatePrescriptionStatus Lambdas. Uses ASL definition file `Format1UpdatePrescriptionsStatusStateMachine.asl.json`. *(uses `ExpressStateMachine` construct + CDK step function definition)* + +### Per-state-machine shared resources (`state_machine_resources.yaml`) +Each state machine has a nested stack that creates: +- [x] IAM execution role with state machine and Lambda invoke permissions *(handled by `ExpressStateMachine` construct)* +- [x] CloudWatch Log Group (with configurable retention, optional KMS encryption) *(handled by `ExpressStateMachine` construct)* +- [x] Splunk subscription filter (conditional on `EnableSplunk`) *(handled by `ExpressStateMachine` construct — always enabled)* +- [x] Execute state machine managed policy (exported for cross-stack references) *(handled by `ExpressStateMachine` construct)* + +## API Gateway (`SAMtemplates/apis/main.yaml`) + +- [x] **ACM Certificate** (`GenerateCertificate`) — DNS-validated certificate for the custom domain. *(handled by `RestApiGateway` construct)* +- [x] **REST API** (`RestApiGateway`) — Regional REST API with optional execute-api endpoint disabling (when mTLS is enabled). *(handled by `RestApiGateway` construct)* +- [x] **Custom Domain** (`RestApiDomain`) — Regional domain with TLS 1.2, optional mTLS truststore from S3. *(handled by `RestApiGateway` construct)* +- [x] **Route 53 Record** (`RestApiRecordSet`) — Alias A record pointing to the API Gateway domain. *(handled by `RestApiGateway` construct)* +- [x] **API Deployment and Stage** (`RestApiGatewayDeploymentV2h`, `RestApiGatewayStage`) — Deployment with X-Ray tracing and structured access logging. *(handled by `RestApiGateway` construct via `deploy: true`)* +- [x] **Domain Mapping** (`RestApiDomainMapping`) — Base path mapping to the `prod` stage. *(handled by `RestApiGateway` construct)* + +### API Endpoints +- [x] **POST /** — `UpdatePrescriptionStatusMethod` — Step Functions integration (StartSyncExecution) to UpdatePrescriptionStatus state machine. *(root resource method in `Apis` construct)* +- [x] **POST /format-1** — `Format1UpdatePrescriptionStatusMethod` — Step Functions integration to Format1 state machine. *(uses `StateMachineEndpoint` construct)* +- [x] **POST /notification-delivery-status-callback** — `NotificationDeliveryStatusCallbackMethod` — Lambda proxy integration to NHSNotifyUpdateCallback. *(uses `LambdaEndpoint` construct)* +- [x] **GET /_status** — `StatusLambdaMethod` — Lambda proxy integration to Status function. *(uses `LambdaEndpoint` construct)* +- [x] **GET /metadata** — `CapabilityStatementMethod` — Lambda proxy integration to CapabilityStatement function. *(uses `LambdaEndpoint` construct)* +- [x] **GET /checkprescriptionstatusupdates** — `CheckPrescriptionStatusUpdatesMethod` — Conditional. Lambda proxy integration. *(conditional `LambdaEndpoint` in `Apis` construct)* +- [x] **POST /get-status-updates** — `GetStatusUpdatesMethod` — Conditional. Lambda integration (non-proxy) with request/response templates. *(conditional non-proxy `LambdaIntegration` in `Apis` construct)* + +### API shared resources (`api_resources.yaml`) +- [x] API Gateway IAM role with policies to invoke Lambdas and state machines *(handled by `RestApiGateway` construct)* +- [x] API Gateway access log group (with configurable retention, optional KMS encryption) *(handled by `RestApiGateway` construct)* +- [x] Splunk subscription filter (conditional on `EnableSplunk`) *(handled by `RestApiGateway` construct — always enabled)* +- [x] CSOC log forwarding (conditional on `ForwardCsocLogs`) *(handled by `RestApiGateway` construct)* + +## CloudWatch Alarms (`SAMtemplates/alarms/main.yaml`) + +### Log Metric Filters +- [ ] `GetStatusUpdatesErrorsLogsMetricFilter` — Filters ERROR level logs from GetStatusUpdates +- [ ] `PrescriptionStatusUpdateErrorsLogsMetricFilter` — Filters 500 status responses from UpdatePrescriptionStatus +- [ ] `PrescriptionStatusUpdateForcedErrorsLogsMetricFilter` — Filters forced INT test errors +- [ ] `CPSUFhirConverterErrorLogsMetricFilter` — Filters 500 status responses from CPSU converter +- [ ] `NotifyProcessorTimeoutsMetricFilter` — Filters timeout messages from NotifyProcessor + +### Alarms +- [ ] `GetStatusUpdatesErrorsAlarm` — Alerts on GetStatusUpdates errors +- [ ] `GetStatusUpdatesUnhandledErrorsAlarm` — Alerts on GetStatusUpdates unhandled Lambda errors +- [ ] `PrescriptionStatusUpdateErrorsAlarm` — Alerts on true 500 errors (excludes forced INT test errors via math expression) +- [ ] `PrescriptionStatusUpdateUnhandledErrorsAlarm` — Alerts on unhandled Lambda errors +- [ ] `DynamoDBSystemErrorsAlarm` — Alerts on DynamoDB system errors +- [ ] `DynamoDBWriteConsumptionAlarm` — Alerts when DynamoDB write usage nears provisioned capacity threshold +- [ ] `CPSUFhirConverterErrorsAlarm` — Alerts on CPSU FHIR converter errors +- [ ] `NotifyProcessorTimeoutsAlarm` — Alerts on NotifyProcessor timeouts +- [ ] `NHSNotifyPrescriptionsDeadLetterQueueMessagesAlarm` — Alerts on DLQ messages + +## Cross-cutting concerns + +- [ ] **Splunk log forwarding** — Conditionally enabled per log group via subscription filters. Requires `SplunkDeliveryStreamArn` and `SplunkSubscriptionFilterRole` from `lambda-resources` stack. +- [ ] **CSOC log forwarding** — Conditional forwarding of API Gateway access logs to CSOC destination. +- [ ] **KMS encryption** — CloudWatch log groups optionally encrypted with `account-resources:CloudwatchLogsKmsKeyArn`. +- [ ] **Cross-stack imports** — Many resources import values from `account-resources`, `lambda-resources`, `eps-route53-resources`, and `fhir-validator` stacks. These will become `Fn.importValue()` calls in CDK. +- [ ] **Conditional deployments** — `CheckPrescriptionStatusUpdates` and `GetStatusUpdates` endpoints are conditionally deployed. `PsuRestoreValidation` depends on `EnableBackup`. These will need CDK conditions or conditional construct instantiation.