diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml
index fea1d11c9dd..e32efe756b0 100644
--- a/.github/workflows/dependency-review.yml
+++ b/.github/workflows/dependency-review.yml
@@ -19,4 +19,4 @@ jobs:
- name: 'Checkout Repository'
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: 'Dependency Review'
- uses: actions/dependency-review-action@3b139cfc5fae8b618d3eae3675e383bb1769c019 # v4.5.0
+ uses: actions/dependency-review-action@ce3cf9537a52e8119d91fd484ab5b8a807627bf8 # v4.6.0
diff --git a/.github/workflows/publish_v2_layer.yml b/.github/workflows/publish_v2_layer.yml
index 62900b0e8c8..b5df55e3fc7 100644
--- a/.github/workflows/publish_v2_layer.yml
+++ b/.github/workflows/publish_v2_layer.yml
@@ -101,7 +101,7 @@ jobs:
- name: Install poetry
run: pipx install git+https://github.com/python-poetry/poetry@bd500dd3bdfaec3de6894144c9cedb3a9358be84 # v2.0.1
- name: Setup Node.js
- uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4.3.0
+ uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: "16.12"
- name: Setup python
diff --git a/.github/workflows/publish_v3_layer.yml b/.github/workflows/publish_v3_layer.yml
index 07bcb6dcb10..1e2fd005d3f 100644
--- a/.github/workflows/publish_v3_layer.yml
+++ b/.github/workflows/publish_v3_layer.yml
@@ -123,7 +123,7 @@ jobs:
pipx install git+https://github.com/python-poetry/poetry@bd500dd3bdfaec3de6894144c9cedb3a9358be84 # v2.0.1
pipx inject poetry git+https://github.com/python-poetry/poetry-plugin-export@8c83d26603ca94f2e203bfded7b6d7f530960e06 # v1.8.0
- name: Setup Node.js
- uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4.3.0
+ uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: "18.20.4"
- name: Setup python
diff --git a/.github/workflows/quality_check.yml b/.github/workflows/quality_check.yml
index fcb80fb0d5b..f0bc5dfb10a 100644
--- a/.github/workflows/quality_check.yml
+++ b/.github/workflows/quality_check.yml
@@ -63,6 +63,8 @@ jobs:
run: make dev-quality-code
- name: Checking third-party library licenses
run: make check-licenses
+ - name: Checking and enforcing format
+ run: make format-check
- name: Formatting and Linting
run: make lint
- name: Static type checking
@@ -76,7 +78,7 @@ jobs:
- name: Complexity baseline
run: make complexity-baseline
- name: Upload coverage to Codecov
- uses: codecov/codecov-action@0565863a31f2c772f9f0395002a31e3f06189574 # 5.4.0
+ uses: codecov/codecov-action@ad3126e916f78f00edff4ed0317cf185271ccc2d # 5.4.2
with:
token: ${{ secrets.CODECOV_TOKEN }}
file: ./coverage.xml
diff --git a/.github/workflows/reusable_deploy_v2_layer_stack.yml b/.github/workflows/reusable_deploy_v2_layer_stack.yml
index 1409c549cfd..d858a05c870 100644
--- a/.github/workflows/reusable_deploy_v2_layer_stack.yml
+++ b/.github/workflows/reusable_deploy_v2_layer_stack.yml
@@ -159,7 +159,7 @@ jobs:
role-to-assume: ${{ secrets.AWS_LAYERS_ROLE_ARN }}
mask-aws-account-id: true
- name: Setup Node.js
- uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4.3.0
+ uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: "16.12"
- name: Setup python
diff --git a/.github/workflows/reusable_deploy_v2_sar.yml b/.github/workflows/reusable_deploy_v2_sar.yml
index 44dd76460bf..6c4ed67d1b5 100644
--- a/.github/workflows/reusable_deploy_v2_sar.yml
+++ b/.github/workflows/reusable_deploy_v2_sar.yml
@@ -113,7 +113,7 @@ jobs:
mask-aws-account-id: true
- name: Setup Node.js
- uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4.3.0
+ uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: ${{ env.NODE_VERSION }}
- name: Download artifact
diff --git a/.github/workflows/reusable_deploy_v3_layer_stack.yml b/.github/workflows/reusable_deploy_v3_layer_stack.yml
index c305547df97..0702fa059f0 100644
--- a/.github/workflows/reusable_deploy_v3_layer_stack.yml
+++ b/.github/workflows/reusable_deploy_v3_layer_stack.yml
@@ -167,7 +167,7 @@ jobs:
role-to-assume: ${{ secrets.AWS_LAYERS_ROLE_ARN }}
mask-aws-account-id: true
- name: Setup Node.js
- uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4.3.0
+ uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: "18.20.4"
- name: Setup python
diff --git a/.github/workflows/reusable_deploy_v3_sar.yml b/.github/workflows/reusable_deploy_v3_sar.yml
index fa136646a4c..1c8de1a9007 100644
--- a/.github/workflows/reusable_deploy_v3_sar.yml
+++ b/.github/workflows/reusable_deploy_v3_sar.yml
@@ -109,7 +109,7 @@ jobs:
role-to-assume: ${{ secrets.AWS_SAR_V3_ROLE_ARN }}
mask-aws-account-id: true
- name: Setup Node.js
- uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4.3.0
+ uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: ${{ env.NODE_VERSION }}
- name: Download artifact
diff --git a/.github/workflows/run-e2e-tests.yml b/.github/workflows/run-e2e-tests.yml
index 0ca0333a704..4fd605af189 100644
--- a/.github/workflows/run-e2e-tests.yml
+++ b/.github/workflows/run-e2e-tests.yml
@@ -62,7 +62,7 @@ jobs:
architecture: "x64"
cache: "poetry"
- name: Setup Node.js
- uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4.3.0
+ uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: "20.10.0"
- name: Install CDK CLI
diff --git a/.github/workflows/secure_workflows.yml b/.github/workflows/secure_workflows.yml
index 378daf7d40e..cc26dca4723 100644
--- a/.github/workflows/secure_workflows.yml
+++ b/.github/workflows/secure_workflows.yml
@@ -32,7 +32,7 @@ jobs:
- name: Checkout code
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Ensure 3rd party workflows have SHA pinned
- uses: zgosalvez/github-actions-ensure-sha-pinned-actions@25ed13d0628a1601b4b44048e63cc4328ed03633 # v3.0.22
+ uses: zgosalvez/github-actions-ensure-sha-pinned-actions@4830be28ce81da52ec70d65c552a7403821d98d4 # v3.0.23
with:
allowlist: |
slsa-framework/slsa-github-generator
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 0a9cee41d5a..de0c36b21e0 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -12,13 +12,13 @@ repos:
- id: check-toml
- repo: local
hooks:
- - id: black
- name: formatting::black
- entry: poetry run black
+ - id: ruff
+ name: formatting::ruff
+ entry: poetry run ruff format
language: system
types: [python]
- id: ruff
- name: linting-format::ruff
+ name: linting::ruff
entry: poetry run ruff check
language: system
types: [python]
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 40e238de154..a7f86392f0e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,23 +4,163 @@
# Unreleased
+## Bug Fixes
+
+* **logger:** warn customers when the ALC log level is less verbose than log buffer ([#6509](https://github.com/aws-powertools/powertools-lambda-python/issues/6509))
+* **parser:** make key attribute optional in Kafka model ([#6523](https://github.com/aws-powertools/powertools-lambda-python/issues/6523))
+
+## Code Refactoring
+
+* **batch:** use standard collections for types ([#6475](https://github.com/aws-powertools/powertools-lambda-python/issues/6475))
+* **data_masking:** use standard collections for types ([#6493](https://github.com/aws-powertools/powertools-lambda-python/issues/6493))
+* **e2e-tests:** use standard collections for types + refactor code ([#6505](https://github.com/aws-powertools/powertools-lambda-python/issues/6505))
+* **event_handler:** use standard collections for types + refactor code ([#6495](https://github.com/aws-powertools/powertools-lambda-python/issues/6495))
+* **event_source:** use standard collections for types ([#6479](https://github.com/aws-powertools/powertools-lambda-python/issues/6479))
+* **feature_flags:** use standard collections for type ([#6489](https://github.com/aws-powertools/powertools-lambda-python/issues/6489))
+* **general:** add support for `ruff format` ([#6512](https://github.com/aws-powertools/powertools-lambda-python/issues/6512))
+* **idempotency:** use standard collections for types ([#6487](https://github.com/aws-powertools/powertools-lambda-python/issues/6487))
+* **logger:** use standard collections for types ([#6471](https://github.com/aws-powertools/powertools-lambda-python/issues/6471))
+* **metrics:** use standard collections for types ([#6472](https://github.com/aws-powertools/powertools-lambda-python/issues/6472))
+* **middleware_factory:** use standard collections for types ([#6485](https://github.com/aws-powertools/powertools-lambda-python/issues/6485))
+* **parameters:** use standard collections for types ([#6481](https://github.com/aws-powertools/powertools-lambda-python/issues/6481))
+* **streaming:** use standard collections for types ([#6483](https://github.com/aws-powertools/powertools-lambda-python/issues/6483))
+* **tests:** use standard collections for types + refactor code ([#6497](https://github.com/aws-powertools/powertools-lambda-python/issues/6497))
+* **tracer:** use standard collections for types ([#6473](https://github.com/aws-powertools/powertools-lambda-python/issues/6473))
+* **validation:** use standard collections for types ([#6491](https://github.com/aws-powertools/powertools-lambda-python/issues/6491))
+
+## Documentation
+
+* **bedrock:** fix BedrockServiceRole in template.yaml ([#6436](https://github.com/aws-powertools/powertools-lambda-python/issues/6436))
+* **bedrock_agents:** remove Pydantic v1 recommendation ([#6468](https://github.com/aws-powertools/powertools-lambda-python/issues/6468))
+* **event_handler:** fix typo in api keys swagger url ([#6536](https://github.com/aws-powertools/powertools-lambda-python/issues/6536))
+
+## Features
+
+* **bedrock:** add `openapi_extensions` in BedrockAgentResolver ([#6510](https://github.com/aws-powertools/powertools-lambda-python/issues/6510))
+* **data-masking:** add support for Pydantic models, dataclasses, and standard classes ([#6413](https://github.com/aws-powertools/powertools-lambda-python/issues/6413))
+* **event_handler:** add extras HTTP Error Code Exceptions ([#6454](https://github.com/aws-powertools/powertools-lambda-python/issues/6454))
+* **event_handler:** add route-level custom response validation in OpenAPI utility ([#6341](https://github.com/aws-powertools/powertools-lambda-python/issues/6341))
+* **logger:** add support for exception notes ([#6465](https://github.com/aws-powertools/powertools-lambda-python/issues/6465))
+
+## Maintenance
+
+* **ci:** new pre-release 3.10.1a10 ([#6538](https://github.com/aws-powertools/powertools-lambda-python/issues/6538))
+* **ci:** new pre-release 3.10.1a0 ([#6431](https://github.com/aws-powertools/powertools-lambda-python/issues/6431))
+* **ci:** new pre-release 3.10.1a9 ([#6533](https://github.com/aws-powertools/powertools-lambda-python/issues/6533))
+* **ci:** new pre-release 3.10.1a1 ([#6437](https://github.com/aws-powertools/powertools-lambda-python/issues/6437))
+* **ci:** new pre-release 3.10.1a2 ([#6446](https://github.com/aws-powertools/powertools-lambda-python/issues/6446))
+* **ci:** new pre-release 3.10.1a8 ([#6526](https://github.com/aws-powertools/powertools-lambda-python/issues/6526))
+* **ci:** new pre-release 3.10.1a3 ([#6455](https://github.com/aws-powertools/powertools-lambda-python/issues/6455))
+* **ci:** new pre-release 3.10.1a4 ([#6463](https://github.com/aws-powertools/powertools-lambda-python/issues/6463))
+* **ci:** new pre-release 3.10.1a7 ([#6518](https://github.com/aws-powertools/powertools-lambda-python/issues/6518))
+* **ci:** new pre-release 3.10.1a5 ([#6498](https://github.com/aws-powertools/powertools-lambda-python/issues/6498))
+* **ci:** new pre-release 3.10.1a6 ([#6506](https://github.com/aws-powertools/powertools-lambda-python/issues/6506))
+* **deps:** bump codecov/codecov-action from 5.4.0 to 5.4.2 ([#6458](https://github.com/aws-powertools/powertools-lambda-python/issues/6458))
+* **deps:** bump pydantic from 2.11.2 to 2.11.3 ([#6427](https://github.com/aws-powertools/powertools-lambda-python/issues/6427))
+* **deps:** bump pydantic-settings from 2.8.1 to 2.9.1 ([#6530](https://github.com/aws-powertools/powertools-lambda-python/issues/6530))
+* **deps:** bump typing-extensions from 4.13.1 to 4.13.2 ([#6451](https://github.com/aws-powertools/powertools-lambda-python/issues/6451))
+* **deps:** bump squidfunk/mkdocs-material from sha256:23b69789b1dd836c53ea25b32f62ef8e1a23366037acd07c90959a219fd1f285 to sha256:95f2ff42251979c043d6cb5b1c82e6ae8189e57e02105813dd1ce124021a418b in /docs ([#6513](https://github.com/aws-powertools/powertools-lambda-python/issues/6513))
+* **deps:** bump actions/setup-node from 4.3.0 to 4.4.0 ([#6457](https://github.com/aws-powertools/powertools-lambda-python/issues/6457))
+* **deps-dev:** bump boto3-stubs from 1.37.34 to 1.37.35 ([#6504](https://github.com/aws-powertools/powertools-lambda-python/issues/6504))
+* **deps-dev:** bump aws-cdk-aws-lambda-python-alpha from 2.189.0a0 to 2.189.1a0 ([#6462](https://github.com/aws-powertools/powertools-lambda-python/issues/6462))
+* **deps-dev:** bump boto3-stubs from 1.37.33 to 1.37.34 ([#6459](https://github.com/aws-powertools/powertools-lambda-python/issues/6459))
+* **deps-dev:** bump cdklabs-generative-ai-cdk-constructs from 0.1.301 to 0.1.302 ([#6460](https://github.com/aws-powertools/powertools-lambda-python/issues/6460))
+* **deps-dev:** bump aws-cdk-lib from 2.189.0 to 2.189.1 ([#6461](https://github.com/aws-powertools/powertools-lambda-python/issues/6461))
+* **deps-dev:** bump mkdocs-material from 9.6.11 to 9.6.12 ([#6514](https://github.com/aws-powertools/powertools-lambda-python/issues/6514))
+* **deps-dev:** bump multiprocess from 0.70.17 to 0.70.18 ([#6516](https://github.com/aws-powertools/powertools-lambda-python/issues/6516))
+* **deps-dev:** bump ruff from 0.11.5 to 0.11.6 ([#6515](https://github.com/aws-powertools/powertools-lambda-python/issues/6515))
+* **deps-dev:** bump boto3-stubs from 1.37.35 to 1.37.37 ([#6521](https://github.com/aws-powertools/powertools-lambda-python/issues/6521))
+* **deps-dev:** bump cfn-lint from 1.33.1 to 1.33.2 ([#6450](https://github.com/aws-powertools/powertools-lambda-python/issues/6450))
+* **deps-dev:** bump boto3-stubs from 1.37.31 to 1.37.33 ([#6449](https://github.com/aws-powertools/powertools-lambda-python/issues/6449))
+* **deps-dev:** bump sentry-sdk from 2.25.1 to 2.26.1 ([#6477](https://github.com/aws-powertools/powertools-lambda-python/issues/6477))
+* **deps-dev:** bump cfn-lint from 1.34.0 to 1.34.1 ([#6528](https://github.com/aws-powertools/powertools-lambda-python/issues/6528))
+* **deps-dev:** bump cdklabs-generative-ai-cdk-constructs from 0.1.302 to 0.1.304 ([#6531](https://github.com/aws-powertools/powertools-lambda-python/issues/6531))
+* **deps-dev:** bump ruff from 0.11.4 to 0.11.5 ([#6443](https://github.com/aws-powertools/powertools-lambda-python/issues/6443))
+* **deps-dev:** bump aws-cdk-aws-lambda-python-alpha from 2.188.0a0 to 2.189.0a0 ([#6444](https://github.com/aws-powertools/powertools-lambda-python/issues/6444))
+* **deps-dev:** bump aws-cdk-lib from 2.188.0 to 2.189.0 ([#6445](https://github.com/aws-powertools/powertools-lambda-python/issues/6445))
+* **deps-dev:** bump cfn-lint from 1.33.0 to 1.33.1 ([#6442](https://github.com/aws-powertools/powertools-lambda-python/issues/6442))
+* **deps-dev:** bump aws-cdk from 2.1007.0 to 2.1010.0 ([#6501](https://github.com/aws-powertools/powertools-lambda-python/issues/6501))
+* **deps-dev:** bump aws-cdk-aws-lambda-python-alpha from 2.189.1a0 to 2.190.0a0 ([#6529](https://github.com/aws-powertools/powertools-lambda-python/issues/6529))
+* **deps-dev:** bump boto3-stubs from 1.37.37 to 1.37.38 ([#6537](https://github.com/aws-powertools/powertools-lambda-python/issues/6537))
+* **deps-dev:** bump aws-cdk from 2.1010.0 to 2.1012.0 ([#6540](https://github.com/aws-powertools/powertools-lambda-python/issues/6540))
+* **deps-dev:** bump mypy-boto3-appconfigdata from 1.37.0 to 1.38.0 in the boto-typing group ([#6541](https://github.com/aws-powertools/powertools-lambda-python/issues/6541))
+* **deps-dev:** bump aws-cdk-aws-lambda-python-alpha from 2.187.0a0 to 2.188.0a0 ([#6434](https://github.com/aws-powertools/powertools-lambda-python/issues/6434))
+* **deps-dev:** bump ruff from 0.11.3 to 0.11.4 ([#6428](https://github.com/aws-powertools/powertools-lambda-python/issues/6428))
+* **deps-dev:** bump pytest-cov from 6.1.0 to 6.1.1 ([#6429](https://github.com/aws-powertools/powertools-lambda-python/issues/6429))
+* **deps-dev:** bump cfn-lint from 1.32.4 to 1.33.0 ([#6430](https://github.com/aws-powertools/powertools-lambda-python/issues/6430))
+* **deps-dev:** bump cfn-lint from 1.33.2 to 1.34.0 ([#6502](https://github.com/aws-powertools/powertools-lambda-python/issues/6502))
+* **deps-dev:** bump cdklabs-generative-ai-cdk-constructs from 0.1.304 to 0.1.305 ([#6545](https://github.com/aws-powertools/powertools-lambda-python/issues/6545))
+* **deps-dev:** bump aws-cdk-aws-lambda-python-alpha from 2.190.0a0 to 2.191.0a0 ([#6543](https://github.com/aws-powertools/powertools-lambda-python/issues/6543))
+* **deps-dev:** bump boto3-stubs from 1.37.29 to 1.37.31 ([#6433](https://github.com/aws-powertools/powertools-lambda-python/issues/6433))
+
+
+
+## [v3.10.0] - 2025-04-08
+## Bug Fixes
+
+* **event_source:** Added missing properties in APIGatewayWebSocketEvent class ([#6411](https://github.com/aws-powertools/powertools-lambda-python/issues/6411))
+* **event_source:** fix HomeDirectoryDetails type in TransferFamilyAuthorizerResponse method ([#6403](https://github.com/aws-powertools/powertools-lambda-python/issues/6403))
+* **logger:** improve behavior with `exc_info=True` to prevent errors ([#6417](https://github.com/aws-powertools/powertools-lambda-python/issues/6417))
+
## Documentation
* **homepage:** add SAR documentation ([#6347](https://github.com/aws-powertools/powertools-lambda-python/issues/6347))
+## Features
+
+* **parser:** add AppSyncResolver model ([#6400](https://github.com/aws-powertools/powertools-lambda-python/issues/6400))
+
## Maintenance
+* version bump
+* **ci:** new pre-release 3.9.1a4 ([#6377](https://github.com/aws-powertools/powertools-lambda-python/issues/6377))
+* **ci:** new pre-release 3.9.1a8 ([#6415](https://github.com/aws-powertools/powertools-lambda-python/issues/6415))
+* **ci:** new pre-release 3.9.1a9 ([#6422](https://github.com/aws-powertools/powertools-lambda-python/issues/6422))
* **ci:** new pre-release 3.9.1a0 ([#6354](https://github.com/aws-powertools/powertools-lambda-python/issues/6354))
-* **ci:** new pre-release 3.9.1a2 ([#6364](https://github.com/aws-powertools/powertools-lambda-python/issues/6364))
+* **ci:** new pre-release 3.9.1a5 ([#6385](https://github.com/aws-powertools/powertools-lambda-python/issues/6385))
+* **ci:** new pre-release 3.9.1a7 ([#6401](https://github.com/aws-powertools/powertools-lambda-python/issues/6401))
* **ci:** new pre-release 3.9.1a1 ([#6356](https://github.com/aws-powertools/powertools-lambda-python/issues/6356))
+* **ci:** new pre-release 3.9.1a2 ([#6364](https://github.com/aws-powertools/powertools-lambda-python/issues/6364))
+* **ci:** new pre-release 3.9.1a6 ([#6392](https://github.com/aws-powertools/powertools-lambda-python/issues/6392))
+* **ci:** new pre-release 3.9.1a3 ([#6369](https://github.com/aws-powertools/powertools-lambda-python/issues/6369))
* **deps:** bump aws-encryption-sdk from 4.0.0 to 4.0.1 ([#6360](https://github.com/aws-powertools/powertools-lambda-python/issues/6360))
+* **deps:** bump pydantic from 2.11.1 to 2.11.2 ([#6395](https://github.com/aws-powertools/powertools-lambda-python/issues/6395))
+* **deps:** bump zgosalvez/github-actions-ensure-sha-pinned-actions from 3.0.22 to 3.0.23 ([#6371](https://github.com/aws-powertools/powertools-lambda-python/issues/6371))
+* **deps:** bump squidfunk/mkdocs-material from `3555052` to `23b6978` in /docs ([#6404](https://github.com/aws-powertools/powertools-lambda-python/issues/6404))
+* **deps:** bump datadog-lambda from 6.106.0 to 6.107.0 ([#6405](https://github.com/aws-powertools/powertools-lambda-python/issues/6405))
+* **deps:** bump squidfunk/mkdocs-material from `f226a2d` to `3555052` in /docs ([#6372](https://github.com/aws-powertools/powertools-lambda-python/issues/6372))
+* **deps:** bump pydantic from 2.10.6 to 2.11.1 ([#6383](https://github.com/aws-powertools/powertools-lambda-python/issues/6383))
+* **deps:** bump typing-extensions from 4.12.2 to 4.13.1 ([#6418](https://github.com/aws-powertools/powertools-lambda-python/issues/6418))
* **deps:** bump actions/setup-python from 5.4.0 to 5.5.0 ([#6349](https://github.com/aws-powertools/powertools-lambda-python/issues/6349))
-* **deps-dev:** bump cfn-lint from 1.32.0 to 1.32.1 ([#6351](https://github.com/aws-powertools/powertools-lambda-python/issues/6351))
+* **deps:** bump actions/dependency-review-action from 4.5.0 to 4.6.0 ([#6380](https://github.com/aws-powertools/powertools-lambda-python/issues/6380))
+* **deps-dev:** bump aws-cdk-aws-lambda-python-alpha from 2.186.0a0 to 2.187.0a0 ([#6382](https://github.com/aws-powertools/powertools-lambda-python/issues/6382))
+* **deps-dev:** bump pytest-cov from 6.0.0 to 6.1.0 ([#6381](https://github.com/aws-powertools/powertools-lambda-python/issues/6381))
+* **deps-dev:** bump coverage from 7.7.1 to 7.8.0 ([#6376](https://github.com/aws-powertools/powertools-lambda-python/issues/6376))
+* **deps-dev:** bump mkdocs-material from 9.6.9 to 9.6.10 ([#6375](https://github.com/aws-powertools/powertools-lambda-python/issues/6375))
+* **deps-dev:** bump boto3-stubs from 1.37.23 to 1.37.24 ([#6374](https://github.com/aws-powertools/powertools-lambda-python/issues/6374))
+* **deps-dev:** bump boto3-stubs from 1.37.24 to 1.37.25 ([#6384](https://github.com/aws-powertools/powertools-lambda-python/issues/6384))
+* **deps-dev:** bump sentry-sdk from 2.24.1 to 2.25.0 ([#6373](https://github.com/aws-powertools/powertools-lambda-python/issues/6373))
+* **deps-dev:** bump aws-cdk from 2.1006.0 to 2.1007.0 ([#6387](https://github.com/aws-powertools/powertools-lambda-python/issues/6387))
+* **deps-dev:** bump boto3-stubs from 1.37.25 to 1.37.26 ([#6389](https://github.com/aws-powertools/powertools-lambda-python/issues/6389))
+* **deps-dev:** bump sentry-sdk from 2.25.0 to 2.25.1 ([#6391](https://github.com/aws-powertools/powertools-lambda-python/issues/6391))
+* **deps-dev:** bump boto3-stubs from 1.37.22 to 1.37.23 ([#6366](https://github.com/aws-powertools/powertools-lambda-python/issues/6366))
+* **deps-dev:** bump cdklabs-generative-ai-cdk-constructs from 0.1.298 to 0.1.299 ([#6390](https://github.com/aws-powertools/powertools-lambda-python/issues/6390))
+* **deps-dev:** bump cfn-lint from 1.32.1 to 1.32.3 ([#6388](https://github.com/aws-powertools/powertools-lambda-python/issues/6388))
+* **deps-dev:** bump aws-cdk-aws-lambda-python-alpha from 2.185.0a0 to 2.186.0a0 ([#6363](https://github.com/aws-powertools/powertools-lambda-python/issues/6363))
* **deps-dev:** bump boto3-stubs from 1.37.20 to 1.37.22 ([#6362](https://github.com/aws-powertools/powertools-lambda-python/issues/6362))
+* **deps-dev:** bump testcontainers from 4.9.2 to 4.10.0 ([#6397](https://github.com/aws-powertools/powertools-lambda-python/issues/6397))
+* **deps-dev:** bump mkdocstrings-python from 1.16.8 to 1.16.10 ([#6399](https://github.com/aws-powertools/powertools-lambda-python/issues/6399))
+* **deps-dev:** bump ruff from 0.11.2 to 0.11.3 ([#6398](https://github.com/aws-powertools/powertools-lambda-python/issues/6398))
+* **deps-dev:** bump boto3-stubs from 1.37.26 to 1.37.28 ([#6406](https://github.com/aws-powertools/powertools-lambda-python/issues/6406))
+* **deps-dev:** bump pytest-asyncio from 0.25.3 to 0.26.0 ([#6352](https://github.com/aws-powertools/powertools-lambda-python/issues/6352))
+* **deps-dev:** bump aws-cdk-lib from 2.187.0 to 2.188.0 ([#6407](https://github.com/aws-powertools/powertools-lambda-python/issues/6407))
+* **deps-dev:** bump cfn-lint from 1.32.0 to 1.32.1 ([#6351](https://github.com/aws-powertools/powertools-lambda-python/issues/6351))
+* **deps-dev:** bump cdklabs-generative-ai-cdk-constructs from 0.1.299 to 0.1.300 ([#6408](https://github.com/aws-powertools/powertools-lambda-python/issues/6408))
* **deps-dev:** bump aws-cdk from 2.1005.0 to 2.1006.0 ([#6350](https://github.com/aws-powertools/powertools-lambda-python/issues/6350))
-* **deps-dev:** bump aws-cdk-aws-lambda-python-alpha from 2.185.0a0 to 2.186.0a0 ([#6363](https://github.com/aws-powertools/powertools-lambda-python/issues/6363))
+* **deps-dev:** bump cfn-lint from 1.32.3 to 1.32.4 ([#6419](https://github.com/aws-powertools/powertools-lambda-python/issues/6419))
+* **deps-dev:** bump cdklabs-generative-ai-cdk-constructs from 0.1.300 to 0.1.301 ([#6420](https://github.com/aws-powertools/powertools-lambda-python/issues/6420))
+* **deps-dev:** bump boto3-stubs from 1.37.28 to 1.37.29 ([#6421](https://github.com/aws-powertools/powertools-lambda-python/issues/6421))
* **deps-dev:** bump boto3-stubs from 1.37.19 to 1.37.20 ([#6353](https://github.com/aws-powertools/powertools-lambda-python/issues/6353))
-* **deps-dev:** bump pytest-asyncio from 0.25.3 to 0.26.0 ([#6352](https://github.com/aws-powertools/powertools-lambda-python/issues/6352))
@@ -6295,7 +6435,8 @@
* Merge pull request [#5](https://github.com/aws-powertools/powertools-lambda-python/issues/5) from jfuss/feat/python38
-[Unreleased]: https://github.com/aws-powertools/powertools-lambda-python/compare/v3.9.0...HEAD
+[Unreleased]: https://github.com/aws-powertools/powertools-lambda-python/compare/v3.10.0...HEAD
+[v3.10.0]: https://github.com/aws-powertools/powertools-lambda-python/compare/v3.9.0...v3.10.0
[v3.9.0]: https://github.com/aws-powertools/powertools-lambda-python/compare/v3.8.0...v3.9.0
[v3.8.0]: https://github.com/aws-powertools/powertools-lambda-python/compare/v3.7.0...v3.8.0
[v3.7.0]: https://github.com/aws-powertools/powertools-lambda-python/compare/v3.6.0...v3.7.0
diff --git a/Makefile b/Makefile
index bde5516a832..843c8aab17e 100644
--- a/Makefile
+++ b/Makefile
@@ -25,8 +25,11 @@ dev-gitpod:
check-licenses:
poetry run licensecheck -u poetry:dev
+format-check:
+ poetry run ruff format aws_lambda_powertools tests examples --check
+
format:
- poetry run black aws_lambda_powertools tests examples
+ poetry run ruff format aws_lambda_powertools tests examples
lint: format
poetry run ruff check aws_lambda_powertools tests examples
diff --git a/aws_lambda_powertools/event_handler/__init__.py b/aws_lambda_powertools/event_handler/__init__.py
index ffbb2abe4ae..8bcf2d6636c 100644
--- a/aws_lambda_powertools/event_handler/__init__.py
+++ b/aws_lambda_powertools/event_handler/__init__.py
@@ -12,6 +12,7 @@
)
from aws_lambda_powertools.event_handler.appsync import AppSyncResolver
from aws_lambda_powertools.event_handler.bedrock_agent import BedrockAgentResolver
+from aws_lambda_powertools.event_handler.events_appsync.appsync_events import AppSyncEventsResolver
from aws_lambda_powertools.event_handler.lambda_function_url import (
LambdaFunctionUrlResolver,
)
@@ -19,6 +20,7 @@
__all__ = [
"AppSyncResolver",
+ "AppSyncEventsResolver",
"APIGatewayRestResolver",
"APIGatewayHttpResolver",
"ALBResolver",
diff --git a/aws_lambda_powertools/event_handler/api_gateway.py b/aws_lambda_powertools/event_handler/api_gateway.py
index c8e4248fda4..a5c5a7bb053 100644
--- a/aws_lambda_powertools/event_handler/api_gateway.py
+++ b/aws_lambda_powertools/event_handler/api_gateway.py
@@ -12,11 +12,12 @@
from functools import partial
from http import HTTPStatus
from pathlib import Path
-from typing import TYPE_CHECKING, Any, Callable, Generic, Literal, Mapping, Match, Pattern, Sequence, TypeVar, cast
+from typing import TYPE_CHECKING, Any, Generic, Literal, Match, Pattern, TypeVar, cast
from typing_extensions import override
from aws_lambda_powertools.event_handler import content_types
+from aws_lambda_powertools.event_handler.exception_handling import ExceptionHandlerManager
from aws_lambda_powertools.event_handler.exceptions import NotFoundError, ServiceError
from aws_lambda_powertools.event_handler.openapi.config import OpenAPIConfig
from aws_lambda_powertools.event_handler.openapi.constants import (
@@ -35,6 +36,7 @@
OpenAPIResponse,
OpenAPIResponseContentModel,
OpenAPIResponseContentSchema,
+ response_validation_error_response_definition,
validation_error_definition,
validation_error_response_definition,
)
@@ -58,6 +60,9 @@
)
from aws_lambda_powertools.utilities.data_classes.common import BaseProxyEvent
+if TYPE_CHECKING:
+ from collections.abc import Callable, Mapping, Sequence
+
logger = logging.getLogger(__name__)
_DYNAMIC_ROUTE_PATTERN = r"(<\w+>)"
@@ -67,6 +72,7 @@
_NAMED_GROUP_BOUNDARY_PATTERN = rf"(?P\1[{_SAFE_URI}{_UNSAFE_URI}\\w]+)"
_DEFAULT_OPENAPI_RESPONSE_DESCRIPTION = "Successful Response"
_ROUTE_REGEX = "^{}$"
+_JSON_DUMP_CALL = partial(json.dumps, separators=(",", ":"), cls=Encoder)
ResponseEventT = TypeVar("ResponseEventT", bound=BaseProxyEvent)
ResponseT = TypeVar("ResponseT")
@@ -319,6 +325,7 @@ def __init__(
security: list[dict[str, list[str]]] | None = None,
openapi_extensions: dict[str, Any] | None = None,
deprecated: bool = False,
+ custom_response_validation_http_code: HTTPStatus | None = None,
middlewares: list[Callable[..., Response]] | None = None,
):
"""
@@ -360,11 +367,13 @@ def __init__(
Additional OpenAPI extensions as a dictionary.
deprecated: bool
Whether or not to mark this route as deprecated in the OpenAPI schema
+ custom_response_validation_http_code: int | HTTPStatus | None, optional
+ Whether to have custom http status code for this route if response validation fails
middlewares: list[Callable[..., Response]] | None
The list of route middlewares to be called in order.
"""
self.method = method.upper()
- self.path = "/" if path.strip() == "" else path
+ self.path = path if path.strip() else "/"
# OpenAPI spec only understands paths with { }. So we'll have to convert Powertools' < >.
# https://swagger.io/specification/#path-templating
@@ -397,6 +406,8 @@ def __init__(
# _body_field is used to cache the dependant model for the body field
self._body_field: ModelField | None = None
+ self.custom_response_validation_http_code = custom_response_validation_http_code
+
def __call__(
self,
router_middlewares: list[Callable],
@@ -565,6 +576,16 @@ def _get_openapi_path(
},
}
+ # Add custom response validation response, if exists
+ if self.custom_response_validation_http_code:
+ http_code = self.custom_response_validation_http_code.value
+ operation_responses[http_code] = {
+ "description": "Response Validation Error",
+ "content": {"application/json": {"schema": {"$ref": f"{COMPONENT_REF_PREFIX}ResponseValidationError"}}},
+ }
+ # Add model definition
+ definitions["ResponseValidationError"] = response_validation_error_response_definition
+
# Add the response to the OpenAPI operation
if self.responses:
for status_code in list(self.responses):
@@ -814,7 +835,7 @@ class ResponseBuilder(Generic[ResponseEventT]):
def __init__(
self,
response: Response,
- serializer: Callable[[Any], str] = partial(json.dumps, separators=(",", ":"), cls=Encoder),
+ serializer: Callable[[Any], str] = _JSON_DUMP_CALL,
route: Route | None = None,
):
self.response = response
@@ -942,6 +963,7 @@ def route(
security: list[dict[str, list[str]]] | None = None,
openapi_extensions: dict[str, Any] | None = None,
deprecated: bool = False,
+ custom_response_validation_http_code: int | HTTPStatus | None = None,
middlewares: list[Callable[..., Any]] | None = None,
) -> Callable[[AnyCallableT], AnyCallableT]:
raise NotImplementedError()
@@ -1003,6 +1025,7 @@ def get(
security: list[dict[str, list[str]]] | None = None,
openapi_extensions: dict[str, Any] | None = None,
deprecated: bool = False,
+ custom_response_validation_http_code: int | HTTPStatus | None = None,
middlewares: list[Callable[..., Any]] | None = None,
) -> Callable[[AnyCallableT], AnyCallableT]:
"""Get route decorator with GET `method`
@@ -1043,6 +1066,7 @@ def lambda_handler(event, context):
security,
openapi_extensions,
deprecated,
+ custom_response_validation_http_code,
middlewares,
)
@@ -1062,6 +1086,7 @@ def post(
security: list[dict[str, list[str]]] | None = None,
openapi_extensions: dict[str, Any] | None = None,
deprecated: bool = False,
+ custom_response_validation_http_code: int | HTTPStatus | None = None,
middlewares: list[Callable[..., Any]] | None = None,
) -> Callable[[AnyCallableT], AnyCallableT]:
"""Post route decorator with POST `method`
@@ -1103,6 +1128,7 @@ def lambda_handler(event, context):
security,
openapi_extensions,
deprecated,
+ custom_response_validation_http_code,
middlewares,
)
@@ -1122,6 +1148,7 @@ def put(
security: list[dict[str, list[str]]] | None = None,
openapi_extensions: dict[str, Any] | None = None,
deprecated: bool = False,
+ custom_response_validation_http_code: int | HTTPStatus | None = None,
middlewares: list[Callable[..., Any]] | None = None,
) -> Callable[[AnyCallableT], AnyCallableT]:
"""Put route decorator with PUT `method`
@@ -1163,6 +1190,7 @@ def lambda_handler(event, context):
security,
openapi_extensions,
deprecated,
+ custom_response_validation_http_code,
middlewares,
)
@@ -1182,6 +1210,7 @@ def delete(
security: list[dict[str, list[str]]] | None = None,
openapi_extensions: dict[str, Any] | None = None,
deprecated: bool = False,
+ custom_response_validation_http_code: int | HTTPStatus | None = None,
middlewares: list[Callable[..., Any]] | None = None,
) -> Callable[[AnyCallableT], AnyCallableT]:
"""Delete route decorator with DELETE `method`
@@ -1222,6 +1251,7 @@ def lambda_handler(event, context):
security,
openapi_extensions,
deprecated,
+ custom_response_validation_http_code,
middlewares,
)
@@ -1241,6 +1271,7 @@ def patch(
security: list[dict[str, list[str]]] | None = None,
openapi_extensions: dict[str, Any] | None = None,
deprecated: bool = False,
+ custom_response_validation_http_code: int | HTTPStatus | None = None,
middlewares: list[Callable] | None = None,
) -> Callable[[AnyCallableT], AnyCallableT]:
"""Patch route decorator with PATCH `method`
@@ -1284,6 +1315,7 @@ def lambda_handler(event, context):
security,
openapi_extensions,
deprecated,
+ custom_response_validation_http_code,
middlewares,
)
@@ -1303,6 +1335,7 @@ def head(
security: list[dict[str, list[str]]] | None = None,
openapi_extensions: dict[str, Any] | None = None,
deprecated: bool = False,
+ custom_response_validation_http_code: int | HTTPStatus | None = None,
middlewares: list[Callable] | None = None,
) -> Callable[[AnyCallableT], AnyCallableT]:
"""Head route decorator with HEAD `method`
@@ -1345,6 +1378,7 @@ def lambda_handler(event, context):
security,
openapi_extensions,
deprecated,
+ custom_response_validation_http_code,
middlewares,
)
@@ -1543,6 +1577,7 @@ def __init__(
self.processed_stack_frames = []
self._response_builder_class = ResponseBuilder[BaseProxyEvent]
self.openapi_config = OpenAPIConfig() # starting an empty dataclass
+ self.exception_handler_manager = ExceptionHandlerManager()
self._has_response_validation_error = response_validation_error_http_code is not None
self._response_validation_error_http_code = self._validate_response_validation_error_http_code(
response_validation_error_http_code,
@@ -1579,7 +1614,6 @@ def _validate_response_validation_error_http_code(
not isinstance(response_validation_error_http_code, HTTPStatus)
and response_validation_error_http_code is not None
):
-
try:
response_validation_error_http_code = HTTPStatus(response_validation_error_http_code)
except ValueError:
@@ -1588,6 +1622,33 @@ def _validate_response_validation_error_http_code(
return response_validation_error_http_code or HTTPStatus.UNPROCESSABLE_ENTITY
+ def _add_resolver_response_validation_error_response_to_route(
+ self,
+ route_openapi_path: tuple[dict[str, Any], dict[str, Any]],
+ ) -> tuple[dict[str, Any], dict[str, Any]]:
+ """Adds resolver response validation error response to route's operations."""
+ path, path_definitions = route_openapi_path
+ if self._has_response_validation_error and "ResponseValidationError" not in path_definitions:
+ response_validation_error_response = {
+ "description": "Response Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {"$ref": f"{COMPONENT_REF_PREFIX}ResponseValidationError"},
+ },
+ },
+ }
+ http_code = self._response_validation_error_http_code.value
+ for operation in path.values():
+ operation["responses"][http_code] = response_validation_error_response
+ return path, path_definitions
+
+ def _generate_schemas(self, definitions: dict[str, dict[str, Any]]) -> dict[str, dict[str, Any]]:
+ schemas = {k: definitions[k] for k in sorted(definitions)}
+ # add response validation error definition
+ if self._response_validation_error_http_code:
+ schemas.setdefault("ResponseValidationError", response_validation_error_response_definition)
+ return schemas
+
def get_openapi_schema(
self,
*,
@@ -1666,8 +1727,9 @@ def get_openapi_schema(
security = security or self.openapi_config.security
openapi_extensions = openapi_extensions or self.openapi_config.openapi_extensions
+ from pydantic.json_schema import GenerateJsonSchema
+
from aws_lambda_powertools.event_handler.openapi.compat import (
- GenerateJsonSchema,
get_compat_model_name_map,
get_definitions,
)
@@ -1739,14 +1801,14 @@ def get_openapi_schema(
field_mapping=field_mapping,
)
if result:
- path, path_definitions = result
+ path, path_definitions = self._add_resolver_response_validation_error_response_to_route(result)
if path:
paths.setdefault(route.openapi_path, {}).update(path)
if path_definitions:
definitions.update(path_definitions)
if definitions:
- components["schemas"] = {k: definitions[k] for k in sorted(definitions)}
+ components["schemas"] = self._generate_schemas(definitions)
if security_schemes:
components["securitySchemes"] = security_schemes
if components:
@@ -2108,6 +2170,29 @@ def swagger_handler():
body=body,
)
+ def _validate_route_response_validation_error_http_code(
+ self,
+ custom_response_validation_http_code: int | HTTPStatus | None,
+ ) -> HTTPStatus | None:
+ if custom_response_validation_http_code and not self._enable_validation:
+ msg = (
+ "'custom_response_validation_http_code' cannot be set for route when enable_validation is False "
+ "on resolver."
+ )
+ raise ValueError(msg)
+
+ if (
+ not isinstance(custom_response_validation_http_code, HTTPStatus)
+ and custom_response_validation_http_code is not None
+ ):
+ try:
+ custom_response_validation_http_code = HTTPStatus(custom_response_validation_http_code)
+ except ValueError:
+ msg = f"'{custom_response_validation_http_code}' must be an integer representing an HTTP status code or an enum of type HTTPStatus." # noqa: E501
+ raise ValueError(msg) from None
+
+ return custom_response_validation_http_code
+
def route(
self,
rule: str,
@@ -2125,10 +2210,15 @@ def route(
security: list[dict[str, list[str]]] | None = None,
openapi_extensions: dict[str, Any] | None = None,
deprecated: bool = False,
+ custom_response_validation_http_code: int | HTTPStatus | None = None,
middlewares: list[Callable[..., Any]] | None = None,
) -> Callable[[AnyCallableT], AnyCallableT]:
"""Route decorator includes parameter `method`"""
+ custom_response_validation_http_code = self._validate_route_response_validation_error_http_code(
+ custom_response_validation_http_code,
+ )
+
def register_resolver(func: AnyCallableT) -> AnyCallableT:
methods = (method,) if isinstance(method, str) else method
logger.debug(f"Adding route using rule {rule} and methods: {','.join(m.upper() for m in methods)}")
@@ -2154,6 +2244,7 @@ def register_resolver(func: AnyCallableT) -> AnyCallableT:
security,
openapi_extensions,
deprecated,
+ custom_response_validation_http_code,
middlewares,
)
@@ -2409,7 +2500,7 @@ def not_found_handler():
return Response(status_code=204, content_type=None, headers=_headers, body="")
# Customer registered 404 route? Call it.
- custom_not_found_handler = self._lookup_exception_handler(NotFoundError)
+ custom_not_found_handler = self.exception_handler_manager.lookup_exception_handler(NotFoundError)
if custom_not_found_handler:
return custom_not_found_handler(NotFoundError())
@@ -2482,26 +2573,10 @@ def not_found(self, func: Callable | None = None):
return self.exception_handler(NotFoundError)(func)
def exception_handler(self, exc_class: type[Exception] | list[type[Exception]]):
- def register_exception_handler(func: Callable):
- if isinstance(exc_class, list): # pragma: no cover
- for exp in exc_class:
- self._exception_handlers[exp] = func
- else:
- self._exception_handlers[exc_class] = func
- return func
-
- return register_exception_handler
-
- def _lookup_exception_handler(self, exp_type: type) -> Callable | None:
- # Use "Method Resolution Order" to allow for matching against a base class
- # of an exception
- for cls in exp_type.__mro__:
- if cls in self._exception_handlers:
- return self._exception_handlers[cls]
- return None
+ return self.exception_handler_manager.exception_handler(exc_class=exc_class)
def _call_exception_handler(self, exp: Exception, route: Route) -> ResponseBuilder | None:
- handler = self._lookup_exception_handler(type(exp))
+ handler = self.exception_handler_manager.lookup_exception_handler(type(exp))
if handler:
try:
return self._response_builder_class(response=handler(exp), serializer=self._serializer, route=route)
@@ -2523,15 +2598,17 @@ def _call_exception_handler(self, exp: Exception, route: Route) -> ResponseBuild
)
# OpenAPIValidationMiddleware will only raise ResponseValidationError when
- # 'self._response_validation_error_http_code' is not None
+ # 'self._response_validation_error_http_code' is not None or
+ # when route has custom_response_validation_http_code
if isinstance(exp, ResponseValidationError):
- http_code = self._response_validation_error_http_code
+ # route validation must take precedence over app validation
+ http_code = route.custom_response_validation_http_code or self._response_validation_error_http_code
errors = [{"loc": e["loc"], "type": e["type"]} for e in exp.errors()]
return self._response_builder_class(
response=Response(
status_code=http_code.value,
content_type=content_types.APPLICATION_JSON,
- body={"statusCode": self._response_validation_error_http_code, "detail": errors},
+ body={"statusCode": http_code, "detail": errors},
),
serializer=self._serializer,
route=route,
@@ -2595,7 +2672,7 @@ def include_router(self, router: Router, prefix: str | None = None) -> None:
self._router_middlewares = self._router_middlewares + router._router_middlewares
logger.debug("Appending Router exception_handler into App exception_handler.")
- self._exception_handlers.update(router._exception_handlers)
+ self.exception_handler_manager.update_exception_handlers(router._exception_handlers)
# use pointer to allow context clearance after event is processed e.g., resolve(evt, ctx)
router.context = self.context
@@ -2682,6 +2759,7 @@ def route(
security: list[dict[str, list[str]]] | None = None,
openapi_extensions: dict[str, Any] | None = None,
deprecated: bool = False,
+ custom_response_validation_http_code: int | HTTPStatus | None = None,
middlewares: list[Callable[..., Any]] | None = None,
) -> Callable[[AnyCallableT], AnyCallableT]:
def register_route(func: AnyCallableT) -> AnyCallableT:
@@ -2708,6 +2786,7 @@ def register_route(func: AnyCallableT) -> AnyCallableT:
frozen_security,
frozen_openapi_extensions,
deprecated,
+ custom_response_validation_http_code,
)
# Collate Middleware for routes
@@ -2794,6 +2873,7 @@ def route(
security: list[dict[str, list[str]]] | None = None,
openapi_extensions: dict[str, Any] | None = None,
deprecated: bool = False,
+ custom_response_validation_http_code: int | HTTPStatus | None = None,
middlewares: list[Callable[..., Any]] | None = None,
) -> Callable[[AnyCallableT], AnyCallableT]:
# NOTE: see #1552 for more context.
@@ -2813,6 +2893,7 @@ def route(
security,
openapi_extensions,
deprecated,
+ custom_response_validation_http_code,
middlewares,
)
diff --git a/aws_lambda_powertools/event_handler/appsync.py b/aws_lambda_powertools/event_handler/appsync.py
index 3dbbf207859..3e284b7ad12 100644
--- a/aws_lambda_powertools/event_handler/appsync.py
+++ b/aws_lambda_powertools/event_handler/appsync.py
@@ -3,13 +3,16 @@
import asyncio
import logging
import warnings
-from typing import TYPE_CHECKING, Any, Callable
+from typing import TYPE_CHECKING, Any
+from aws_lambda_powertools.event_handler.exception_handling import ExceptionHandlerManager
from aws_lambda_powertools.event_handler.graphql_appsync.exceptions import InvalidBatchResponse, ResolverNotFoundError
from aws_lambda_powertools.event_handler.graphql_appsync.router import Router
from aws_lambda_powertools.utilities.data_classes import AppSyncResolverEvent
if TYPE_CHECKING:
+ from collections.abc import Callable
+
from aws_lambda_powertools.utilities.typing import LambdaContext
from aws_lambda_powertools.warnings import PowertoolsUserWarning
@@ -52,6 +55,7 @@ def __init__(self):
Initialize a new instance of the AppSyncResolver.
"""
super().__init__()
+ self.exception_handler_manager = ExceptionHandlerManager()
self.context = {} # early init as customers might add context before event resolution
self._exception_handlers: dict[type, Callable] = {}
@@ -151,7 +155,7 @@ def lambda_handler(event, context):
Router.current_event = data_model(event)
response = self._call_single_resolver(event=event, data_model=data_model)
except Exception as exp:
- response_builder = self._lookup_exception_handler(type(exp))
+ response_builder = self.exception_handler_manager.lookup_exception_handler(type(exp))
if response_builder:
return response_builder(exp)
raise
@@ -493,31 +497,4 @@ def exception_handler(self, exc_class: type[Exception] | list[type[Exception]]):
A decorator function that registers the exception handler.
"""
- def register_exception_handler(func: Callable):
- if isinstance(exc_class, list): # pragma: no cover
- for exp in exc_class:
- self._exception_handlers[exp] = func
- else:
- self._exception_handlers[exc_class] = func
- return func
-
- return register_exception_handler
-
- def _lookup_exception_handler(self, exp_type: type) -> Callable | None:
- """
- Looks up the registered exception handler for the given exception type or its base classes.
-
- Parameters
- ----------
- exp_type (type):
- The exception type to look up the handler for.
-
- Returns
- -------
- Callable | None:
- The registered exception handler function if found, otherwise None.
- """
- for cls in exp_type.__mro__:
- if cls in self._exception_handlers:
- return self._exception_handlers[cls]
- return None
+ return self.exception_handler_manager.exception_handler(exc_class=exc_class)
diff --git a/aws_lambda_powertools/event_handler/bedrock_agent.py b/aws_lambda_powertools/event_handler/bedrock_agent.py
index 215199e0022..221163b1ae4 100644
--- a/aws_lambda_powertools/event_handler/bedrock_agent.py
+++ b/aws_lambda_powertools/event_handler/bedrock_agent.py
@@ -1,7 +1,7 @@
from __future__ import annotations
import json
-from typing import TYPE_CHECKING, Any, Callable
+from typing import TYPE_CHECKING, Any
from typing_extensions import override
@@ -14,6 +14,8 @@
from aws_lambda_powertools.event_handler.openapi.constants import DEFAULT_API_VERSION, DEFAULT_OPENAPI_VERSION
if TYPE_CHECKING:
+ from collections.abc import Callable
+ from http import HTTPStatus
from re import Match
from aws_lambda_powertools.event_handler.openapi.models import Contact, License, SecurityScheme, Server, Tag
@@ -108,10 +110,11 @@ def get( # type: ignore[override]
tags: list[str] | None = None,
operation_id: str | None = None,
include_in_schema: bool = True,
+ openapi_extensions: dict[str, Any] | None = None,
deprecated: bool = False,
+ custom_response_validation_http_code: int | HTTPStatus | None = None,
middlewares: list[Callable[..., Any]] | None = None,
) -> Callable[[Callable[..., Any]], Callable[..., Any]]:
- openapi_extensions = None
security = None
return super().get(
@@ -129,6 +132,7 @@ def get( # type: ignore[override]
security,
openapi_extensions,
deprecated,
+ custom_response_validation_http_code,
middlewares,
)
@@ -147,10 +151,11 @@ def post( # type: ignore[override]
tags: list[str] | None = None,
operation_id: str | None = None,
include_in_schema: bool = True,
+ openapi_extensions: dict[str, Any] | None = None,
deprecated: bool = False,
+ custom_response_validation_http_code: int | HTTPStatus | None = None,
middlewares: list[Callable[..., Any]] | None = None,
):
- openapi_extensions = None
security = None
return super().post(
@@ -168,6 +173,7 @@ def post( # type: ignore[override]
security,
openapi_extensions,
deprecated,
+ custom_response_validation_http_code,
middlewares,
)
@@ -186,10 +192,11 @@ def put( # type: ignore[override]
tags: list[str] | None = None,
operation_id: str | None = None,
include_in_schema: bool = True,
+ openapi_extensions: dict[str, Any] | None = None,
deprecated: bool = False,
+ custom_response_validation_http_code: int | HTTPStatus | None = None,
middlewares: list[Callable[..., Any]] | None = None,
):
- openapi_extensions = None
security = None
return super().put(
@@ -207,6 +214,7 @@ def put( # type: ignore[override]
security,
openapi_extensions,
deprecated,
+ custom_response_validation_http_code,
middlewares,
)
@@ -225,10 +233,11 @@ def patch( # type: ignore[override]
tags: list[str] | None = None,
operation_id: str | None = None,
include_in_schema: bool = True,
+ openapi_extensions: dict[str, Any] | None = None,
deprecated: bool = False,
+ custom_response_validation_http_code: int | HTTPStatus | None = None,
middlewares: list[Callable] | None = None,
):
- openapi_extensions = None
security = None
return super().patch(
@@ -246,6 +255,7 @@ def patch( # type: ignore[override]
security,
openapi_extensions,
deprecated,
+ custom_response_validation_http_code,
middlewares,
)
@@ -264,10 +274,11 @@ def delete( # type: ignore[override]
tags: list[str] | None = None,
operation_id: str | None = None,
include_in_schema: bool = True,
+ openapi_extensions: dict[str, Any] | None = None,
deprecated: bool = False,
+ custom_response_validation_http_code: int | HTTPStatus | None = None,
middlewares: list[Callable[..., Any]] | None = None,
):
- openapi_extensions = None
security = None
return super().delete(
@@ -285,6 +296,7 @@ def delete( # type: ignore[override]
security,
openapi_extensions,
deprecated,
+ custom_response_validation_http_code,
middlewares,
)
@@ -313,6 +325,7 @@ def get_openapi_json_schema( # type: ignore[override]
license_info: License | None = None,
security_schemes: dict[str, SecurityScheme] | None = None,
security: list[dict[str, list[str]]] | None = None,
+ openapi_extensions: dict[str, Any] | None = None,
) -> str:
"""
Returns the OpenAPI schema as a JSON serializable dict.
@@ -353,8 +366,6 @@ def get_openapi_json_schema( # type: ignore[override]
"""
from aws_lambda_powertools.event_handler.openapi.compat import model_json
- openapi_extensions = None
-
schema = super().get_openapi_schema(
title=title,
version=version,
diff --git a/aws_lambda_powertools/event_handler/events_appsync/__init__.py b/aws_lambda_powertools/event_handler/events_appsync/__init__.py
new file mode 100644
index 00000000000..64387723526
--- /dev/null
+++ b/aws_lambda_powertools/event_handler/events_appsync/__init__.py
@@ -0,0 +1,5 @@
+from aws_lambda_powertools.event_handler.events_appsync.appsync_events import AppSyncEventsResolver
+
+__all__ = [
+ "AppSyncEventsResolver",
+]
diff --git a/aws_lambda_powertools/event_handler/events_appsync/_registry.py b/aws_lambda_powertools/event_handler/events_appsync/_registry.py
new file mode 100644
index 00000000000..a116bf645ea
--- /dev/null
+++ b/aws_lambda_powertools/event_handler/events_appsync/_registry.py
@@ -0,0 +1,94 @@
+from __future__ import annotations
+
+import logging
+import warnings
+from typing import TYPE_CHECKING
+
+from aws_lambda_powertools.event_handler.events_appsync.functions import find_best_route, is_valid_path
+from aws_lambda_powertools.warnings import PowertoolsUserWarning
+
+if TYPE_CHECKING:
+ from collections.abc import Callable
+
+ from aws_lambda_powertools.event_handler.events_appsync.types import ResolverTypeDef
+
+
+logger = logging.getLogger(__name__)
+
+
+class ResolverEventsRegistry:
+
+ def __init__(self, kind_resolver: str):
+ self.resolvers: dict[str, ResolverTypeDef] = {}
+ self.kind_resolver = kind_resolver
+
+ def register(
+ self,
+ path: str = "/default/*",
+ aggregate: bool = False,
+ ) -> Callable | None:
+ """Registers the resolver for path that includes namespace + channel
+
+ Parameters
+ ----------
+ path : str
+ Path including namespace + channel
+ aggregate: bool
+ A flag indicating whether the batch items should be processed at once or individually.
+ If True, the resolver will process all items as a single event.
+ If False (default), the resolver will process each item individually.
+
+ Return
+ ----------
+ Callable
+ A Callable
+ """
+
+ def _register(func) -> Callable | None:
+
+ if not is_valid_path(path):
+ warnings.warn(
+ f"The path `{path}` registered for `{self.kind_resolver}` is not valid and will be skipped."
+ f"A path should always have a namespace starting with '/'"
+ "A path can have multiple namespaces, all separated by '/'."
+ "Wildcards are allowed only at the end of the path.",
+ stacklevel=2,
+ category=PowertoolsUserWarning,
+ )
+ return None
+
+ logger.debug(
+ f"Adding resolver `{func.__name__}` for path `{path}` and kind_resolver `{self.kind_resolver}`",
+ )
+ self.resolvers[f"{path}"] = {
+ "func": func,
+ "aggregate": aggregate,
+ }
+ return func
+
+ return _register
+
+ def find_resolver(self, path: str) -> ResolverTypeDef | None:
+ """Find resolver based on type_name and field_name
+
+ Parameters
+ ----------
+ path : str
+ Type name
+ Return
+ ----------
+ dict | None
+ A dictionary with the resolver and if this is aggregated or not
+ """
+ logger.debug(f"Looking for resolver for path `{path}` and kind_resolver `{self.kind_resolver}`")
+ return self.resolvers.get(find_best_route(self.resolvers, path))
+
+ def merge(self, other_registry: ResolverEventsRegistry):
+ """Update current registry with incoming registry
+
+ Parameters
+ ----------
+ other_registry : ResolverRegistry
+ Registry to merge from
+ """
+ self.resolvers.update(**other_registry.resolvers)
diff --git a/aws_lambda_powertools/event_handler/events_appsync/appsync_events.py b/aws_lambda_powertools/event_handler/events_appsync/appsync_events.py
new file mode 100644
index 00000000000..ee03db5c625
--- /dev/null
+++ b/aws_lambda_powertools/event_handler/events_appsync/appsync_events.py
@@ -0,0 +1,422 @@
+from __future__ import annotations
+
+import asyncio
+import logging
+import warnings
+from typing import TYPE_CHECKING, Any
+
+from aws_lambda_powertools.event_handler.events_appsync.exceptions import UnauthorizedException
+from aws_lambda_powertools.event_handler.events_appsync.router import Router
+from aws_lambda_powertools.utilities.data_classes.appsync_resolver_events_event import AppSyncResolverEventsEvent
+from aws_lambda_powertools.warnings import PowertoolsUserWarning
+
+if TYPE_CHECKING:
+ from collections.abc import Callable
+
+ from aws_lambda_powertools.event_handler.events_appsync.types import ResolverTypeDef
+ from aws_lambda_powertools.utilities.typing.lambda_context import LambdaContext
+
+
+logger = logging.getLogger(__name__)
+
+
+class AppSyncEventsResolver(Router):
+ """
+ AppSync Events API Resolver for handling publish and subscribe operations.
+
+ This class extends the Router to process AppSync real-time API events, managing
+ both synchronous and asynchronous resolvers for event publishing and subscribing.
+
+ Attributes
+ ----------
+ context: dict
+ Dictionary to store context information accessible across resolvers
+ lambda_context: LambdaContext
+ Lambda context from the AWS Lambda function
+ current_event: AppSyncResolverEventsEvent
+ Current event being processed
+
+ Examples
+ --------
+ Define a simple AppSync events resolver for a chat application:
+
+ >>> from aws_lambda_powertools.event_handler import AppSyncEventsResolver
+ >>> app = AppSyncEventsResolver()
+ >>>
+ >>> # Using aggregate mode to process multiple messages at once
+ >>> @app.on_publish(channel_path="/default/*", aggregate=True)
+ >>> def handle_batch_messages(payload):
+ >>> processed_messages = []
+ >>> for message in payload:
+ >>> # Process each message
+ >>> processed_messages.append({
+ >>> "messageId": f"msg-{message.get('id')}",
+ >>> "processed": True
+ >>> })
+ >>> return processed_messages
+ >>>
+ >>> # Asynchronous resolver
+ >>> @app.async_on_publish(channel_path="/default/*")
+ >>> async def handle_async_messages(event):
+ >>> # Perform async operations (e.g., DB queries, HTTP calls)
+ >>> await asyncio.sleep(0.1) # Simulate async work
+ >>> return {
+ >>> "messageId": f"async-{event.get('id')}",
+ >>> "processed": True
+ >>> }
+ >>>
+ >>> # Lambda handler
+ >>> def lambda_handler(event, context):
+ >>> return events.resolve(event, context)
+ """
+
+ def __init__(self):
+ """Initialize the AppSyncEventsResolver."""
+ super().__init__()
+ self.context = {} # early init as customers might add context before event resolution
+ self._exception_handlers: dict[type, Callable] = {}
+
+ def __call__(
+ self,
+ event: dict | AppSyncResolverEventsEvent,
+ context: LambdaContext,
+ ) -> Any:
+ """
+ Implicit lambda handler which internally calls `resolve`.
+
+ Parameters
+ ----------
+ event: dict or AppSyncResolverEventsEvent
+ The AppSync event to process
+ context: LambdaContext
+ The Lambda context
+
+ Returns
+ -------
+ Any
+ The resolver's response
+ """
+ return self.resolve(event, context)
+
+ def resolve(
+ self,
+ event: dict | AppSyncResolverEventsEvent,
+ context: LambdaContext,
+ ) -> Any:
+ """
+ Resolves the response based on the provided event and decorator operation.
+
+ Parameters
+ ----------
+ event: dict or AppSyncResolverEventsEvent
+ The AppSync event to process
+ context: LambdaContext
+ The Lambda context
+
+ Returns
+ -------
+ Any
+ The resolver's response based on the operation type
+
+ Examples
+ --------
+ >>> events = AppSyncEventsResolver()
+ >>>
+ >>> # Explicit call to resolve in Lambda handler
+ >>> def lambda_handler(event, context):
+ >>> return events.resolve(event, context)
+ """
+
+ self._setup_context(event, context)
+
+ if self.current_event.info.operation == "PUBLISH":
+ response = self._publish_events(payload=self.current_event.events)
+ else:
+ response = self._subscribe_events()
+
+ self.clear_context()
+
+ return response
+
+ def _subscribe_events(self) -> Any:
+ """
+ Handle subscribe events.
+
+ Returns
+ -------
+ Any
+ Any response
+ """
+ channel_path = self.current_event.info.channel_path
+ logger.debug(f"Processing subscribe events for path {channel_path}")
+
+ resolver = self._subscribe_registry.find_resolver(channel_path)
+ if resolver:
+ try:
+ resolver["func"]()
+ return None # Must return None in subscribe events
+ except UnauthorizedException:
+ raise
+ except Exception as error:
+ return {"error": self._format_error_response(error)}
+
+ self._warn_no_resolver("subscribe", channel_path)
+ return None
+
+ def _publish_events(self, payload: list[dict[str, Any]]) -> list[dict[str, Any]] | dict[str, Any]:
+ """
+ Handle publish events.
+
+ Parameters
+ ----------
+ payload: list[dict[str, Any]]
+ The events payload to process
+
+ Returns
+ -------
+ list[dict[str, Any]] or dict[str, Any]
+ Processed events or error response
+ """
+
+ channel_path = self.current_event.info.channel_path
+
+ logger.debug(f"Processing publish events for path {channel_path}")
+
+ resolver = self._publish_registry.find_resolver(channel_path)
+ async_resolver = self._async_publish_registry.find_resolver(channel_path)
+
+ if resolver and async_resolver:
+ warnings.warn(
+ f"Both synchronous and asynchronous resolvers found for the same event and field."
+ f"The synchronous resolver takes precedence. Executing: {resolver['func'].__name__}",
+ stacklevel=2,
+ category=PowertoolsUserWarning,
+ )
+
+ if resolver:
+ logger.debug(f"Found sync resolver: {resolver}")
+ return self._process_publish_event_sync_resolver(resolver)
+
+ if async_resolver:
+ logger.debug(f"Found async resolver: {async_resolver}")
+ return asyncio.run(self._call_publish_event_async_resolver(async_resolver))
+
+ # No resolver found
+ # Warning and returning AS IS
+ self._warn_no_resolver("publish", channel_path, return_as_is=True)
+ return {"events": payload}
+
+ def _process_publish_event_sync_resolver(
+ self,
+ resolver: ResolverTypeDef,
+ ) -> list[dict[str, Any]] | dict[str, Any]:
+ """
+ Process events using a synchronous resolver.
+
+ Parameters
+ ----------
+ resolver : ResolverTypeDef
+ The resolver to use for processing events
+
+ Returns
+ -------
+ list[dict[str, Any]] or dict[str, Any]
+ Processed events or error response
+
+ Notes
+ -----
+ If the resolver is configured with aggregate=True, all events are processed
+ as a batch. Otherwise, each event is processed individually.
+ """
+
+ # Checks whether the entire batch should be processed at once
+ if resolver["aggregate"]:
+ try:
+ # Process the entire batch
+ response = resolver["func"](payload=self.current_event.events)
+
+ if not isinstance(response, list):
+ warnings.warn(
+ "Response must be a list when using aggregate, AppSync will drop those events.",
+ stacklevel=2,
+ category=PowertoolsUserWarning,
+ )
+
+ return {"events": response}
+ except UnauthorizedException:
+ raise
+ except Exception as error:
+ return {"error": self._format_error_response(error)}
+
+ # By default, we gracefully append `None` for any records that failed processing
+ results = []
+ for idx, event in enumerate(self.current_event.events):
+ try:
+ result_return = resolver["func"](payload=event.get("payload"))
+ results.append({"id": event.get("id"), "payload": result_return})
+ except Exception as error:
+ logger.debug(f"Failed to process event number {idx}")
+ error_return = {"id": event.get("id"), "error": self._format_error_response(error)}
+ results.append(error_return)
+
+ return {"events": results}
+
+ async def _call_publish_event_async_resolver(
+ self,
+ resolver: ResolverTypeDef,
+ ) -> list[dict[str, Any]] | dict[str, Any]:
+ """
+ Process events using an asynchronous resolver.
+
+ Parameters
+ ----------
+ resolver: ResolverTypeDef
+ The async resolver to use for processing events
+
+ Returns
+ -------
+ list[Any]
+ Processed events or error responses
+
+ Notes
+ -----
+ If the resolver is configured with aggregate=True, all events are processed
+ as a batch. Otherwise, each event is processed individually and in parallel.
+ """
+
+ # Checks whether the entire batch should be processed at once
+ if resolver["aggregate"]:
+ try:
+ # Process the entire batch
+ response = await resolver["func"](payload=self.current_event.events)
+ if not isinstance(response, list):
+ warnings.warn(
+ "Response must be a list when using aggregate, AppSync will drop those events.",
+ stacklevel=2,
+ category=PowertoolsUserWarning,
+ )
+
+ return {"events": response}
+ except UnauthorizedException:
+ raise
+ except Exception as error:
+ return {"error": self._format_error_response(error)}
+
+ response_async: list = []
+
+ # Prime coroutines
+ tasks = [resolver["func"](payload=e.get("payload")) for e in self.current_event.events]
+
+ # Aggregate results and exceptions, then filter them out
+ # Use `None` upon exception for graceful error handling at GraphQL engine level
+ #
+ # NOTE: asyncio.gather(return_exceptions=True) catches and includes exceptions in the results
+ # this will become useful when we support exception handling in AppSync resolver
+ # Aggregate results and exceptions, then filter them out
+ results = await asyncio.gather(*tasks, return_exceptions=True)
+ response_async.extend(
+ [
+ (
+ {"id": e.get("id"), "error": self._format_error_response(ret)}
+ if isinstance(ret, Exception)
+ else {"id": e.get("id"), "payload": ret}
+ )
+ for e, ret in zip(self.current_event.events, results)
+ ],
+ )
+
+ return {"events": response_async}
+
+ def include_router(self, router: Router) -> None:
+ """
+ Add all resolvers defined in a router to this resolver.
+
+ Parameters
+ ----------
+ router : Router
+ A router containing resolvers to include
+
+ Examples
+ --------
+ >>> # Create main resolver and a router
+ >>> app = AppSyncEventsResolver()
+ >>> router = Router()
+ >>>
+ >>> # Define resolvers in the router
+ >>> @router.publish(path="/chat/message")
+ >>> def handle_chat_message(payload):
+ >>> return {"processed": True, "messageId": payload.get("id")}
+ >>>
+ >>> # Include the router in the main resolver
+ >>> app.include_router(chat_router)
+ >>>
+ >>> # Now events can handle "/chat/message" channel_path
+ """
+
+ # Merge app and router context
+ logger.debug("Merging router and app context")
+ self.context.update(**router.context)
+
+ # use pointer to allow context clearance after event is processed e.g., resolve(evt, ctx)
+ router.context = self.context
+
+ logger.debug("Merging router resolver registries")
+ self._publish_registry.merge(router._publish_registry)
+ self._async_publish_registry.merge(router._async_publish_registry)
+ self._subscribe_registry.merge(router._subscribe_registry)
+
+ def _format_error_response(self, error=None) -> str:
+ """
+ Format error responses consistently.
+
+ Parameters
+ ----------
+ error: Exception or None
+ The error to format
+
+ Returns
+ -------
+ str
+ Formatted error message
+ """
+ if isinstance(error, Exception):
+ return f"{error.__class__.__name__} - {str(error)}"
+ return "An unknown error occurred"
+
+ def _warn_no_resolver(self, operation_type: str, path: str, return_as_is: bool = False) -> None:
+ """
+ Generate consistent warning messages for missing resolvers.
+
+ Parameters
+ ----------
+ operation_type : str
+ Type of operation (e.g., "publish", "subscribe")
+ path : str
+ The channel path that's missing a resolver
+ return_as_is : bool, optional
+ Whether payload will be returned as is, by default False
+ """
+ message = (
+ f"No resolvers were found for {operation_type} operations with path {path}"
+ f"{'. We will return the entire payload as is' if return_as_is else ''}"
+ )
+ warnings.warn(message, stacklevel=3, category=PowertoolsUserWarning)
+
+ def _setup_context(self, event: dict | AppSyncResolverEventsEvent, context: LambdaContext) -> None:
+ """
+ Set up the context and event for processing.
+
+ Parameters
+ ----------
+ event : dict or AppSyncResolverEventsEvent
+ The AppSync event to process
+ context : LambdaContext
+ The Lambda context
+ """
+ self.lambda_context = context
+ Router.lambda_context = context
+
+ Router.current_event = (
+ event if isinstance(event, AppSyncResolverEventsEvent) else AppSyncResolverEventsEvent(event)
+ )
+ self.current_event = Router.current_event
diff --git a/aws_lambda_powertools/event_handler/events_appsync/base.py b/aws_lambda_powertools/event_handler/events_appsync/base.py
new file mode 100644
index 00000000000..0973553cda4
--- /dev/null
+++ b/aws_lambda_powertools/event_handler/events_appsync/base.py
@@ -0,0 +1,42 @@
+from __future__ import annotations
+
+from abc import ABC, abstractmethod
+from typing import Callable
+
+
+class BaseRouter(ABC):
+ """Abstract base class for Router (resolvers)"""
+
+ @abstractmethod
+ def on_publish(
+ self,
+ path: str = "/default/*",
+ aggregate: bool = True,
+ ) -> Callable:
+ raise NotImplementedError
+
+ @abstractmethod
+ def async_on_publish(
+ self,
+ path: str = "/default/*",
+ aggregate: bool = True,
+ ) -> Callable:
+ raise NotImplementedError
+
+ @abstractmethod
+ def on_subscribe(
+ self,
+ path: str = "/default/*",
+ ) -> Callable:
+ raise NotImplementedError
+
+ def append_context(self, **additional_context) -> None:
+ """
+ Appends context information available under any route.
+
+ Parameters
+ -----------
+ **additional_context: dict
+ Additional context key-value pairs to append.
+ """
+ raise NotImplementedError
diff --git a/aws_lambda_powertools/event_handler/events_appsync/exceptions.py b/aws_lambda_powertools/event_handler/events_appsync/exceptions.py
new file mode 100644
index 00000000000..5093c68c603
--- /dev/null
+++ b/aws_lambda_powertools/event_handler/events_appsync/exceptions.py
@@ -0,0 +1,25 @@
+from __future__ import annotations
+
+
+class UnauthorizedException(Exception):
+ """
+ Error to be thrown to communicate the subscription is unauthorized.
+
+ When this error is raised, the client will receive a 40x error code
+ and the subscription will be closed.
+
+ Attributes:
+ message (str): The error message describing the unauthorized access.
+ """
+
+ def __init__(self, message: str | None = None, *args, **kwargs):
+ """
+ Initialize the UnauthorizedException.
+
+ Args:
+ message (str): A descriptive error message.
+ *args: Variable positional arguments.
+ **kwargs: Variable keyword arguments.
+ """
+ super().__init__(message, *args, **kwargs)
+ self.name = "UnauthorizedException"
diff --git a/aws_lambda_powertools/event_handler/events_appsync/functions.py b/aws_lambda_powertools/event_handler/events_appsync/functions.py
new file mode 100644
index 00000000000..7f4952a0dd7
--- /dev/null
+++ b/aws_lambda_powertools/event_handler/events_appsync/functions.py
@@ -0,0 +1,108 @@
+from __future__ import annotations
+
+import re
+from functools import lru_cache
+from typing import Any
+
+PATH_REGEX = re.compile(r"^\/([^\/\*]+)(\/[^\/\*]+)*(\/\*)?$")
+
+
+def is_valid_path(path: str) -> bool:
+ """
+ Checks if a given path is valid based on specific rules.
+
+ Parameters
+ ----------
+ path: str
+ The path to validate
+
+ Returns:
+ --------
+ bool:
+ True if the path is valid, False otherwise
+
+ Examples:
+ >>> is_valid_path('/*')
+ True
+ >>> is_valid_path('/users')
+ True
+ >>> is_valid_path('/users/profile')
+ True
+ >>> is_valid_path('/users/*/details')
+ False
+ >>> is_valid_path('/users/*')
+ True
+ >>> is_valid_path('users')
+ False
+ """
+ if path == "/*":
+ return True
+ return bool(PATH_REGEX.fullmatch(path))
+
+
+def find_best_route(routes: dict[str, Any], path: str):
+ """
+ Find the most specific matching route for a given path.
+
+ Examples of matches:
+ Route: /default/v1/* Path: /default/v1/users -> MATCH
+ Route: /default/v1/* Path: /default/v1/users/students -> MATCH
+ Route: /default/v1/users/* Path: /default/v1/users/123 -> MATCH (this wins over /default/v1/*)
+ Route: /* Path: /anything/here -> MATCH (lowest priority)
+
+ Parameters
+ ----------
+ routes: dict[str, Any]
+ Dictionary containing routes and their handlers
+ Format: {
+ 'resolvers': {
+ '/path/*': {'func': callable, 'aggregate': bool},
+ '/path/specific/*': {'func': callable, 'aggregate': bool}
+ }
+ }
+ path: str
+ Actual path to match (e.g., '/default/v1/users')
+
+ Returns
+ -------
+ str: Most specific matching route or None if no match
+ """
+
+ @lru_cache(maxsize=1024)
+ def pattern_to_regex(route):
+ """
+ Convert a route pattern to a regex pattern with caching.
+ Examples:
+ /default/v1/* -> ^/default/v1/[^/]+$
+ /default/v1/users/* -> ^/default/v1/users/.*$
+
+ Parameters
+ ----------
+ route: str
+ Route pattern with wildcards
+
+ Returns
+ -------
+ Pattern:
+ Compiled regex pattern
+ """
+ # Escape special regex chars but convert * to regex pattern
+ pattern = re.escape(route).replace("\\*", "[^/]+")
+
+ # If pattern ends with [^/]+, replace with .* for multi-segment match
+ if pattern.endswith("[^/]+"):
+ pattern = pattern[:-6] + ".*"
+
+ # Compile and return the regex pattern
+ return re.compile(f"^{pattern}$")
+
+ # Find all matching routes
+ matches = [route for route in routes.keys() if pattern_to_regex(route).match(path)]
+
+ # Return the most specific route (longest length minus wildcards)
+ # Examples of specificity:
+ # - '/default/v1/users' -> score: 14 (len=14, wildcards=0)
+ # - '/default/v1/users/*' -> score: 14 (len=15, wildcards=1)
+ # - '/default/v1/*' -> score: 8 (len=9, wildcards=1)
+ # - '/*' -> score: 0 (len=2, wildcards=1)
+ return max(matches, key=lambda x: len(x) - x.count("*"), default=None)
diff --git a/aws_lambda_powertools/event_handler/events_appsync/router.py b/aws_lambda_powertools/event_handler/events_appsync/router.py
new file mode 100644
index 00000000000..45d7c81ddbb
--- /dev/null
+++ b/aws_lambda_powertools/event_handler/events_appsync/router.py
@@ -0,0 +1,199 @@
+from __future__ import annotations
+
+from typing import TYPE_CHECKING
+
+from aws_lambda_powertools.event_handler.events_appsync._registry import ResolverEventsRegistry
+from aws_lambda_powertools.event_handler.events_appsync.base import BaseRouter
+
+if TYPE_CHECKING:
+ from collections.abc import Callable
+
+ from aws_lambda_powertools.utilities.data_classes.appsync_resolver_events_event import AppSyncResolverEventsEvent
+ from aws_lambda_powertools.utilities.typing.lambda_context import LambdaContext
+
+
+class Router(BaseRouter):
+ """
+ Router for AppSync real-time API event handling.
+
+ This class provides decorators to register resolver functions for publish and subscribe
+ operations in AppSync real-time APIs.
+
+ Parameters
+ ----------
+ context : dict
+ Dictionary to store context information accessible across resolvers
+ current_event : AppSyncResolverEventsEvent
+ Current event being processed
+ lambda_context : LambdaContext
+ Lambda context from the AWS Lambda function
+
+ Examples
+ --------
+ Create a router and define resolvers:
+
+ >>> chat_router = Router()
+ >>>
+ >>> # Register a resolver for publish operations
+ >>> @chat_router.on_publish(path="/chat/message")
+ >>> def handle_message(payload):
+ >>> # Process message
+ >>> return {"success": True, "messageId": payload.get("id")}
+ >>>
+ >>> # Register an async resolver for publish operations
+ >>> @chat_router.async_on_publish(path="/chat/typing")
+ >>> async def handle_typing(event):
+ >>> # Process typing indicator
+ >>> await some_async_operation()
+ >>> return {"processed": True}
+ >>>
+ >>> # Register a resolver for subscribe operations
+ >>> @chat_router.on_subscribe(path="/chat/room/*")
+ >>> def handle_subscribe(event):
+ >>> # Handle subscription setup
+ >>> return {"allowed": True}
+ """
+
+ context: dict
+ current_event: AppSyncResolverEventsEvent
+ lambda_context: LambdaContext
+
+ def __init__(self):
+ """
+ Initialize a new Router instance.
+
+ Sets up empty context and registry containers for different types of resolvers.
+ """
+ self.context = {} # early init as customers might add context before event resolution
+ self._publish_registry = ResolverEventsRegistry(kind_resolver="on_publish")
+ self._async_publish_registry = ResolverEventsRegistry(kind_resolver="async_on_publish")
+ self._subscribe_registry = ResolverEventsRegistry(kind_resolver="on_subscribe")
+
+ def on_publish(
+ self,
+ path: str = "/default/*",
+ aggregate: bool = False,
+ ) -> Callable:
+ """
+ Register a resolver function for publish operations.
+
+ Parameters
+ ----------
+ path : str, optional
+ The channel path pattern to match for this resolver, by default "/default/*"
+ aggregate : bool, optional
+ Whether to process events in aggregate (batch) mode, by default False
+
+ Returns
+ -------
+ Callable
+ Decorator function that registers the resolver
+
+ Examples
+ --------
+ >>> router = Router()
+ >>>
+ >>> # Basic usage
+ >>> @router.on_publish(path="/notifications/new")
+ >>> def handle_notification(payload):
+ >>> # Process a single notification
+ >>> return {"processed": True, "notificationId": payload.get("id")}
+ >>>
+ >>> # Aggregate mode for batch processing
+ >>> @router.on_publish(path="/notifications/batch", aggregate=True)
+ >>> def handle_batch_notifications(payload):
+ >>> # Process multiple notifications at once
+ >>> results = []
+ >>> for item in payload:
+ >>> # Process each item
+ >>> results.append({"processed": True, "id": item.get("id")})
+ >>> return results
+ """
+ return self._publish_registry.register(path=path, aggregate=aggregate)
+
+ def async_on_publish(
+ self,
+ path: str = "/default/*",
+ aggregate: bool = False,
+ ) -> Callable:
+ """
+ Register an asynchronous resolver function for publish operations.
+
+ Parameters
+ ----------
+ path : str, optional
+ The channel path pattern to match for this resolver, by default "/default/*"
+ aggregate : bool, optional
+ Whether to process events in aggregate (batch) mode, by default False
+
+ Returns
+ -------
+ Callable
+ Decorator function that registers the async resolver
+
+ Examples
+ --------
+ >>> router = Router()
+ >>>
+ >>> # Basic async usage
+ >>> @router.async_on_publish(path="/messages/send")
+ >>> async def handle_message(event):
+ >>> # Perform async operations
+ >>> result = await database.save_message(event)
+ >>> return {"saved": True, "messageId": result.id}
+ >>>
+ >>> # Aggregate mode for batch processing
+ >>> @router.async_on_publish(path="/messages/batch", aggregate=True)
+ >>> async def handle_batch_messages(events):
+ >>> # Process multiple messages asynchronously
+ >>> tasks = [database.save_message(e) for e in events]
+ >>> results = await asyncio.gather(*tasks)
+ >>> return [{"saved": True, "id": r.id} for r in results]
+ """
+ return self._async_publish_registry.register(path=path, aggregate=aggregate)
+
+ def on_subscribe(
+ self,
+ path: str = "/default/*",
+ ) -> Callable:
+ """
+ Register a resolver function for subscribe operations.
+
+ Parameters
+ ----------
+ path : str, optional
+ The channel path pattern to match for this resolver, by default "/default/*"
+
+ Returns
+ -------
+ Callable
+ Decorator function that registers the resolver
+
+ Examples
+ --------
+ >>> router = Router()
+ >>>
+ >>> # Handle subscription request
+ >>> @router.on_subscribe(path="/chat/room/*")
+ >>> def authorize_subscription(event):
+ >>> # Verify if the client can subscribe to this room
+ >>> room_id = event.info.channel_path.split('/')[-1]
+ >>> user_id = event.identity.username
+ >>>
+ >>> # Check if user is allowed in this room
+ >>> is_allowed = check_permission(user_id, room_id)
+ >>>
+ >>> return {
+ >>> "allowed": is_allowed,
+ >>> "roomId": room_id
+ >>> }
+ """
+ return self._subscribe_registry.register(path=path)
+
+ def append_context(self, **additional_context):
+ """Append key=value data as routing context"""
+ self.context.update(**additional_context)
+
+ def clear_context(self):
+ """Resets routing context"""
+ self.context.clear()
diff --git a/aws_lambda_powertools/event_handler/events_appsync/types.py b/aws_lambda_powertools/event_handler/events_appsync/types.py
new file mode 100644
index 00000000000..5a49977d888
--- /dev/null
+++ b/aws_lambda_powertools/event_handler/events_appsync/types.py
@@ -0,0 +1,30 @@
+from __future__ import annotations
+
+from typing import TYPE_CHECKING, Any, TypedDict
+
+if TYPE_CHECKING:
+ from collections.abc import Callable
+
+
+class ResolverTypeDef(TypedDict):
+ """
+ Type definition for resolver dictionary
+
+ Parameters
+ ----------
+ func: Callable[..., Any]
+ Resolver function
+ aggregate: bool
+ Aggregation flag or method
+ """
+
+ func: Callable[..., Any]
+ aggregate: bool
+
+
+class AppSyncEventsPayloadDict(TypedDict):
+ id: str
+ payload: dict[str, Any]
+
+
+AppSyncEventsPayloadList = list[AppSyncEventsPayloadDict]
diff --git a/aws_lambda_powertools/event_handler/exception_handling.py b/aws_lambda_powertools/event_handler/exception_handling.py
new file mode 100644
index 00000000000..6aa8731192a
--- /dev/null
+++ b/aws_lambda_powertools/event_handler/exception_handling.py
@@ -0,0 +1,131 @@
+from __future__ import annotations
+
+from typing import TYPE_CHECKING, Mapping
+
+if TYPE_CHECKING:
+ from collections.abc import Callable
+
+
+class ExceptionHandlerManager:
+ """
+ A class to manage exception handlers for different exception types.
+
+ This class allows registering handler functions for specific exception types
+ and looking up the appropriate handler when an exception occurs.
+
+ Example usage:
+ -------------
+ handler_manager = ExceptionHandlerManager()
+
+ @handler_manager.exception_handler(ValueError)
+ def handle_value_error(e):
+ print(f"Handling ValueError: {e}")
+ return "Error handled"
+
+ # To handle multiple exception types with the same handler:
+ @handler_manager.exception_handler([KeyError, TypeError])
+ def handle_multiple_errors(e):
+ print(f"Handling {type(e).__name__}: {e}")
+ return "Multiple error types handled"
+
+ # To find and execute a handler:
+ try:
+ # some code that might raise an exception
+ raise ValueError("Invalid value")
+ except Exception as e:
+ handler = handler_manager.lookup_exception_handler(type(e))
+ if handler:
+ result = handler(e)
+ """
+
+ def __init__(self):
+ """Initialize an empty dictionary to store exception handlers."""
+ self._exception_handlers: dict[type[Exception], Callable] = {}
+
+ def exception_handler(self, exc_class: type[Exception] | list[type[Exception]]):
+ """
+ A decorator function that registers a handler for one or more exception types.
+
+ Parameters
+ ----------
+ exc_class : type[Exception] | list[type[Exception]]
+ A single exception type or a list of exception types.
+
+ Returns
+ -------
+ Callable
+ A decorator function that registers the exception handler.
+ """
+
+ def register_exception_handler(func: Callable):
+ if isinstance(exc_class, list):
+ for exp in exc_class:
+ self._exception_handlers[exp] = func
+ else:
+ self._exception_handlers[exc_class] = func
+ return func
+
+ return register_exception_handler
+
+ def lookup_exception_handler(self, exp_type: type) -> Callable | None:
+ """
+ Looks up the registered exception handler for the given exception type or its base classes.
+
+ Parameters
+ ----------
+ exp_type : type
+ The exception type to look up the handler for.
+
+ Returns
+ -------
+ Callable | None
+ The registered exception handler function if found, otherwise None.
+ """
+ for cls in exp_type.__mro__:
+ if cls in self._exception_handlers:
+ return self._exception_handlers[cls]
+ return None
+
+ def update_exception_handlers(self, handlers: Mapping[type[Exception], Callable]) -> None:
+ """
+ Updates the exception handlers dictionary with new handler mappings.
+
+ This method allows bulk updates of exception handlers by providing a dictionary
+ mapping exception types to handler functions.
+
+ Parameters
+ ----------
+ handlers : Mapping[Type[Exception], Callable]
+ A dictionary mapping exception types to handler functions.
+
+ Example
+ -------
+ >>> def handle_value_error(e):
+ ... print(f"Value error: {e}")
+ ...
+ >>> def handle_key_error(e):
+ ... print(f"Key error: {e}")
+ ...
+ >>> handler_manager.update_exception_handlers({
+ ... ValueError: handle_value_error,
+ ... KeyError: handle_key_error
+ ... })
+ """
+ self._exception_handlers.update(handlers)
+
+ def get_registered_handlers(self) -> dict[type[Exception], Callable]:
+ """
+ Returns all registered exception handlers.
+
+ Returns
+ -------
+ Dict[Type[Exception], Callable]
+ A dictionary mapping exception types to their handler functions.
+ """
+ return self._exception_handlers.copy()
+
+ def clear_handlers(self) -> None:
+ """
+ Clears all registered exception handlers.
+ """
+ self._exception_handlers.clear()
diff --git a/aws_lambda_powertools/event_handler/exceptions.py b/aws_lambda_powertools/event_handler/exceptions.py
index ca5dbbc9830..e524d8a0eae 100644
--- a/aws_lambda_powertools/event_handler/exceptions.py
+++ b/aws_lambda_powertools/event_handler/exceptions.py
@@ -2,7 +2,7 @@
class ServiceError(Exception):
- """API Gateway and ALB HTTP Service Error"""
+ """Powertools class HTTP Service Error"""
def __init__(self, status_code: int, msg: str):
"""
@@ -18,28 +18,56 @@ def __init__(self, status_code: int, msg: str):
class BadRequestError(ServiceError):
- """API Gateway and ALB Bad Request Error (400)"""
+ """Powertools class Bad Request Error (400)"""
def __init__(self, msg: str):
super().__init__(HTTPStatus.BAD_REQUEST, msg)
class UnauthorizedError(ServiceError):
- """API Gateway and ALB Unauthorized Error (401)"""
+ """Powertools class Unauthorized Error (401)"""
def __init__(self, msg: str):
super().__init__(HTTPStatus.UNAUTHORIZED, msg)
+class ForbiddenError(ServiceError):
+ """Powertools class Forbidden Error (403)"""
+
+ def __init__(self, msg: str):
+ super().__init__(HTTPStatus.FORBIDDEN, msg)
+
+
class NotFoundError(ServiceError):
- """API Gateway and ALB Not Found Error (404)"""
+ """Powertools class Not Found Error (404)"""
def __init__(self, msg: str = "Not found"):
super().__init__(HTTPStatus.NOT_FOUND, msg)
+class RequestTimeoutError(ServiceError):
+ """Powertools class Request Timeout Error (408)"""
+
+ def __init__(self, msg: str):
+ super().__init__(HTTPStatus.REQUEST_TIMEOUT, msg)
+
+
+class RequestEntityTooLargeError(ServiceError):
+ """Powertools class Request Entity Too Large Error (413)"""
+
+ def __init__(self, msg: str):
+ super().__init__(HTTPStatus.REQUEST_ENTITY_TOO_LARGE, msg)
+
+
class InternalServerError(ServiceError):
- """API Gateway and ALB Internal Server Error (500)"""
+ """Powertools class Internal Server Error (500)"""
def __init__(self, message: str):
super().__init__(HTTPStatus.INTERNAL_SERVER_ERROR, message)
+
+
+class ServiceUnavailableError(ServiceError):
+ """Powertools class Service Unavailable Error (503)"""
+
+ def __init__(self, msg: str):
+ super().__init__(HTTPStatus.SERVICE_UNAVAILABLE, msg)
diff --git a/aws_lambda_powertools/event_handler/graphql_appsync/_registry.py b/aws_lambda_powertools/event_handler/graphql_appsync/_registry.py
index 9c8dd395a9f..dc88d904c25 100644
--- a/aws_lambda_powertools/event_handler/graphql_appsync/_registry.py
+++ b/aws_lambda_powertools/event_handler/graphql_appsync/_registry.py
@@ -1,7 +1,10 @@
from __future__ import annotations
import logging
-from typing import Any, Callable
+from typing import TYPE_CHECKING, Any
+
+if TYPE_CHECKING:
+ from collections.abc import Callable
logger = logging.getLogger(__name__)
diff --git a/aws_lambda_powertools/event_handler/graphql_appsync/base.py b/aws_lambda_powertools/event_handler/graphql_appsync/base.py
index f0fe4d78d19..ea03c44a3b0 100644
--- a/aws_lambda_powertools/event_handler/graphql_appsync/base.py
+++ b/aws_lambda_powertools/event_handler/graphql_appsync/base.py
@@ -1,7 +1,10 @@
from __future__ import annotations
from abc import ABC, abstractmethod
-from typing import Callable
+from typing import TYPE_CHECKING
+
+if TYPE_CHECKING:
+ from collections.abc import Callable
class BaseRouter(ABC):
diff --git a/aws_lambda_powertools/event_handler/graphql_appsync/router.py b/aws_lambda_powertools/event_handler/graphql_appsync/router.py
index cb0dce1adc7..b05e6f276f5 100644
--- a/aws_lambda_powertools/event_handler/graphql_appsync/router.py
+++ b/aws_lambda_powertools/event_handler/graphql_appsync/router.py
@@ -1,11 +1,13 @@
from __future__ import annotations
-from typing import TYPE_CHECKING, Callable
+from typing import TYPE_CHECKING
from aws_lambda_powertools.event_handler.graphql_appsync._registry import ResolverRegistry
from aws_lambda_powertools.event_handler.graphql_appsync.base import BaseRouter
if TYPE_CHECKING:
+ from collections.abc import Callable
+
from aws_lambda_powertools.utilities.data_classes.appsync_resolver_event import AppSyncResolverEvent
from aws_lambda_powertools.utilities.typing.lambda_context import LambdaContext
diff --git a/aws_lambda_powertools/event_handler/lambda_function_url.py b/aws_lambda_powertools/event_handler/lambda_function_url.py
index c761834e8b3..dbafe809176 100644
--- a/aws_lambda_powertools/event_handler/lambda_function_url.py
+++ b/aws_lambda_powertools/event_handler/lambda_function_url.py
@@ -1,6 +1,6 @@
from __future__ import annotations
-from typing import TYPE_CHECKING, Callable, Pattern
+from typing import TYPE_CHECKING, Pattern
from aws_lambda_powertools.event_handler.api_gateway import (
ApiGatewayResolver,
@@ -8,6 +8,7 @@
)
if TYPE_CHECKING:
+ from collections.abc import Callable
from http import HTTPStatus
from aws_lambda_powertools.event_handler import CORSConfig
diff --git a/aws_lambda_powertools/event_handler/middlewares/openapi_validation.py b/aws_lambda_powertools/event_handler/middlewares/openapi_validation.py
index a1490eb23ec..63baf9fe644 100644
--- a/aws_lambda_powertools/event_handler/middlewares/openapi_validation.py
+++ b/aws_lambda_powertools/event_handler/middlewares/openapi_validation.py
@@ -150,6 +150,7 @@ def _handle_response(self, *, route: Route, response: Response):
response.body = self._serialize_response(
field=route.dependant.return_param,
response_content=response.body,
+ has_route_custom_response_validation=route.custom_response_validation_http_code is not None,
)
return response
@@ -165,6 +166,7 @@ def _serialize_response(
exclude_unset: bool = False,
exclude_defaults: bool = False,
exclude_none: bool = False,
+ has_route_custom_response_validation: bool = False,
) -> Any:
"""
Serialize the response content according to the field type.
@@ -173,8 +175,16 @@ def _serialize_response(
errors: list[dict[str, Any]] = []
value = _validate_field(field=field, value=response_content, loc=("response",), existing_errors=errors)
if errors:
+ # route-level validation must take precedence over app-level
+ if has_route_custom_response_validation:
+ raise ResponseValidationError(
+ errors=_normalize_errors(errors),
+ body=response_content,
+ source="route",
+ )
if self._has_response_validation_error:
- raise ResponseValidationError(errors=_normalize_errors(errors), body=response_content)
+ raise ResponseValidationError(errors=_normalize_errors(errors), body=response_content, source="app")
+
raise RequestValidationError(errors=_normalize_errors(errors), body=response_content)
if hasattr(field, "serialize"):
@@ -355,7 +365,7 @@ def _validate_field(
"""
Validate a field, and append any errors to the existing_errors list.
"""
- validated_value, errors = field.validate(value, value, loc=loc)
+ validated_value, errors = field.validate(value=value, loc=loc)
if isinstance(errors, list):
processed_errors = _regenerate_error_with_loc(errors=errors, loc_prefix=())
diff --git a/aws_lambda_powertools/event_handler/openapi/compat.py b/aws_lambda_powertools/event_handler/openapi/compat.py
index b59e90b1655..d3340f34e4b 100644
--- a/aws_lambda_powertools/event_handler/openapi/compat.py
+++ b/aws_lambda_powertools/event_handler/openapi/compat.py
@@ -1,36 +1,30 @@
# mypy: ignore-errors
-# flake8: noqa
from __future__ import annotations
from collections import deque
-from copy import copy
+from collections.abc import Mapping, Sequence
# MAINTENANCE: remove when deprecating Pydantic v1. Mypy doesn't handle two different code paths that import different
# versions of a module, so we need to ignore errors here.
-
from dataclasses import dataclass, is_dataclass
-from enum import Enum
-from typing import TYPE_CHECKING, Any, Deque, FrozenSet, List, Mapping, Sequence, Set, Tuple, Union
-
-from typing_extensions import Annotated, Literal, get_origin, get_args
-
-from pydantic import BaseModel, create_model
-from pydantic.fields import FieldInfo
+from typing import TYPE_CHECKING, Any, Deque, FrozenSet, List, Set, Tuple, Union
-from aws_lambda_powertools.event_handler.openapi.types import COMPONENT_REF_PREFIX, UnionType
-
-from pydantic import TypeAdapter, ValidationError
+from pydantic import BaseModel, TypeAdapter, ValidationError, create_model
# Importing from internal libraries in Pydantic may introduce potential risks, as these internal libraries
# are not part of the public API and may change without notice in future releases.
# We use this for forward reference, as it allows us to handle forward references in type annotations.
from pydantic._internal._typing_extra import eval_type_lenient
-from pydantic.fields import FieldInfo
from pydantic._internal._utils import lenient_issubclass
-from pydantic.json_schema import GenerateJsonSchema, JsonSchemaValue
from pydantic_core import PydanticUndefined, PydanticUndefinedType
+from typing_extensions import Annotated, Literal, get_args, get_origin
+
+from aws_lambda_powertools.event_handler.openapi.types import UnionType
if TYPE_CHECKING:
+ from pydantic.fields import FieldInfo
+ from pydantic.json_schema import GenerateJsonSchema, JsonSchemaValue
+
from aws_lambda_powertools.event_handler.openapi.types import IncEx, ModelNameMap
Undefined = PydanticUndefined
@@ -119,7 +113,10 @@ def serialize(
)
def validate(
- self, value: Any, values: dict[str, Any] = {}, *, loc: tuple[int | str, ...] = ()
+ self,
+ value: Any,
+ *,
+ loc: tuple[int | str, ...] = (),
) -> tuple[Any, list[dict[str, Any]] | None]:
try:
return (self._type_adapter.validate_python(value, from_attributes=True), None)
@@ -184,7 +181,8 @@ def copy_field_info(*, field_info: FieldInfo, annotation: Any) -> FieldInfo:
def get_missing_field_error(loc: tuple[str, ...]) -> dict[str, Any]:
error = ValidationError.from_exception_data(
- "Field required", [{"type": "missing", "loc": loc, "input": {}}]
+ "Field required",
+ [{"type": "missing", "loc": loc, "input": {}}],
).errors()[0]
error["input"] = None
return error
@@ -308,7 +306,7 @@ def value_is_sequence(value: Any) -> bool:
def _annotation_is_complex(annotation: type[Any] | None) -> bool:
return (
- lenient_issubclass(annotation, (BaseModel, Mapping)) # TODO: UploadFile
+ lenient_issubclass(annotation, (BaseModel, Mapping)) # Keep it to UploadFile
or _annotation_is_sequence(annotation)
or is_dataclass(annotation)
)
diff --git a/aws_lambda_powertools/event_handler/openapi/dependant.py b/aws_lambda_powertools/event_handler/openapi/dependant.py
index f4d0551b739..976ce9f0454 100644
--- a/aws_lambda_powertools/event_handler/openapi/dependant.py
+++ b/aws_lambda_powertools/event_handler/openapi/dependant.py
@@ -2,7 +2,7 @@
import inspect
import re
-from typing import TYPE_CHECKING, Any, Callable, ForwardRef, cast
+from typing import TYPE_CHECKING, Any, ForwardRef, cast
from aws_lambda_powertools.event_handler.openapi.compat import (
ModelField,
@@ -27,6 +27,8 @@
from aws_lambda_powertools.event_handler.openapi.types import OpenAPIResponse, OpenAPIResponseContentModel
if TYPE_CHECKING:
+ from collections.abc import Callable
+
from pydantic import BaseModel
"""
diff --git a/aws_lambda_powertools/event_handler/openapi/encoders.py b/aws_lambda_powertools/event_handler/openapi/encoders.py
index b62ecb856ed..59ce47ebc1d 100644
--- a/aws_lambda_powertools/event_handler/openapi/encoders.py
+++ b/aws_lambda_powertools/event_handler/openapi/encoders.py
@@ -8,7 +8,7 @@
from pathlib import Path, PurePath
from re import Pattern
from types import GeneratorType
-from typing import TYPE_CHECKING, Any, Callable
+from typing import TYPE_CHECKING, Any
from uuid import UUID
from pydantic import BaseModel
@@ -17,6 +17,8 @@
from aws_lambda_powertools.event_handler.openapi.compat import _model_dump
if TYPE_CHECKING:
+ from collections.abc import Callable
+
from aws_lambda_powertools.event_handler.openapi.types import IncEx
from aws_lambda_powertools.event_handler.openapi.exceptions import SerializationError
diff --git a/aws_lambda_powertools/event_handler/openapi/exceptions.py b/aws_lambda_powertools/event_handler/openapi/exceptions.py
index 046a270cdf7..22807dfab29 100644
--- a/aws_lambda_powertools/event_handler/openapi/exceptions.py
+++ b/aws_lambda_powertools/event_handler/openapi/exceptions.py
@@ -1,4 +1,5 @@
-from typing import Any, Sequence
+from collections.abc import Sequence
+from typing import Any, Literal
class ValidationException(Exception):
@@ -28,9 +29,10 @@ class ResponseValidationError(ValidationException):
Raised when the response body does not match the OpenAPI schema
"""
- def __init__(self, errors: Sequence[Any], *, body: Any = None) -> None:
+ def __init__(self, errors: Sequence[Any], *, body: Any = None, source: Literal["route", "app"] = "app") -> None:
super().__init__(errors)
self.body = body
+ self.source = source
class SerializationError(Exception):
diff --git a/aws_lambda_powertools/event_handler/openapi/models.py b/aws_lambda_powertools/event_handler/openapi/models.py
index afeb0a77750..53becd3f870 100644
--- a/aws_lambda_powertools/event_handler/openapi/models.py
+++ b/aws_lambda_powertools/event_handler/openapi/models.py
@@ -36,7 +36,6 @@ class OpenAPIExtensions(BaseModel):
@model_validator(mode="before")
def serialize_openapi_extension_v2(self):
if isinstance(self, dict) and self.get("openapi_extensions"):
-
openapi_extension_value = self.get("openapi_extensions")
for extension_key in openapi_extension_value:
diff --git a/aws_lambda_powertools/event_handler/openapi/params.py b/aws_lambda_powertools/event_handler/openapi/params.py
index 7d4b25269a2..7b1b1c06f49 100644
--- a/aws_lambda_powertools/event_handler/openapi/params.py
+++ b/aws_lambda_powertools/event_handler/openapi/params.py
@@ -2,7 +2,7 @@
import inspect
from enum import Enum
-from typing import TYPE_CHECKING, Any, Callable, Literal
+from typing import TYPE_CHECKING, Any, Literal
from pydantic import BaseConfig
from pydantic.fields import FieldInfo
@@ -20,6 +20,8 @@
)
if TYPE_CHECKING:
+ from collections.abc import Callable
+
from aws_lambda_powertools.event_handler.openapi.models import Example
from aws_lambda_powertools.event_handler.openapi.types import CacheKey
diff --git a/aws_lambda_powertools/event_handler/openapi/types.py b/aws_lambda_powertools/event_handler/openapi/types.py
index 0f8d55e8158..61ac295f948 100644
--- a/aws_lambda_powertools/event_handler/openapi/types.py
+++ b/aws_lambda_powertools/event_handler/openapi/types.py
@@ -1,9 +1,10 @@
from __future__ import annotations
import types
-from typing import TYPE_CHECKING, Any, Callable, Dict, Set, Type, TypedDict, Union
+from typing import TYPE_CHECKING, Any, Dict, Set, Type, TypedDict, Union
if TYPE_CHECKING:
+ from collections.abc import Callable
from enum import Enum
from pydantic import BaseModel
@@ -49,6 +50,18 @@
},
}
+response_validation_error_response_definition = {
+ "title": "ResponseValidationError",
+ "type": "object",
+ "properties": {
+ "detail": {
+ "title": "Detail",
+ "type": "array",
+ "items": {"$ref": f"{COMPONENT_REF_PREFIX}ValidationError"},
+ },
+ },
+}
+
class OpenAPIResponseContentSchema(TypedDict, total=False):
schema: dict
diff --git a/aws_lambda_powertools/event_handler/util.py b/aws_lambda_powertools/event_handler/util.py
index a9695015df0..02fb805fa52 100644
--- a/aws_lambda_powertools/event_handler/util.py
+++ b/aws_lambda_powertools/event_handler/util.py
@@ -1,6 +1,9 @@
from __future__ import annotations
-from typing import Any, Dict, List, Mapping
+from typing import TYPE_CHECKING, Any
+
+if TYPE_CHECKING:
+ from collections.abc import Mapping
class _FrozenDict(dict):
@@ -18,7 +21,7 @@ def __hash__(self):
return hash(frozenset(self.keys()))
-class _FrozenListDict(List[Dict[str, List[str]]]):
+class _FrozenListDict(list[dict[str, list[str]]]):
"""
Freezes a list of dictionaries containing lists of strings.
diff --git a/aws_lambda_powertools/event_handler/vpc_lattice.py b/aws_lambda_powertools/event_handler/vpc_lattice.py
index 30ee8fd86fc..a59acaa9740 100644
--- a/aws_lambda_powertools/event_handler/vpc_lattice.py
+++ b/aws_lambda_powertools/event_handler/vpc_lattice.py
@@ -1,6 +1,6 @@
from __future__ import annotations
-from typing import TYPE_CHECKING, Callable, Pattern
+from typing import TYPE_CHECKING, Pattern
from aws_lambda_powertools.event_handler.api_gateway import (
ApiGatewayResolver,
@@ -8,6 +8,7 @@
)
if TYPE_CHECKING:
+ from collections.abc import Callable
from http import HTTPStatus
from aws_lambda_powertools.event_handler import CORSConfig
diff --git a/aws_lambda_powertools/logging/buffer/functions.py b/aws_lambda_powertools/logging/buffer/functions.py
index cc266354e91..cbd453bcb00 100644
--- a/aws_lambda_powertools/logging/buffer/functions.py
+++ b/aws_lambda_powertools/logging/buffer/functions.py
@@ -2,10 +2,11 @@
import sys
import time
-from typing import TYPE_CHECKING, Any, Mapping
+from typing import TYPE_CHECKING, Any
if TYPE_CHECKING:
import logging
+ from collections.abc import Mapping
def _create_buffer_record(
diff --git a/aws_lambda_powertools/logging/formatter.py b/aws_lambda_powertools/logging/formatter.py
index f04f7c87e73..705ca419823 100644
--- a/aws_lambda_powertools/logging/formatter.py
+++ b/aws_lambda_powertools/logging/formatter.py
@@ -11,12 +11,14 @@
from contextvars import ContextVar
from datetime import datetime, timezone
from functools import partial
-from typing import TYPE_CHECKING, Any, Callable, Generator, Iterable
+from typing import TYPE_CHECKING, Any
from aws_lambda_powertools.shared import constants
from aws_lambda_powertools.shared.functions import powertools_dev_is_set
if TYPE_CHECKING:
+ from collections.abc import Callable, Generator, Iterable
+
from aws_lambda_powertools.logging.types import LogRecord, LogStackTrace
RESERVED_LOG_ATTRS = (
@@ -68,7 +70,7 @@ def append_context_keys(self, **additional_keys: Any) -> Generator[None, None, N
yield
# These specific thread-safe methods are necessary to manage shared context in concurrent environments.
- # They prevent race conditions and ensure data consistency across multiple threads.
+ # They prevent race conditions and ensure data consistency across multiple threads and logger.
def thread_safe_append_keys(self, **additional_keys) -> None:
raise NotImplementedError()
@@ -194,9 +196,10 @@ def format(self, record: logging.LogRecord) -> str: # noqa: A003
# exception and exception_name fields can be added as extra key
# in any log level, we try to extract and use them first
- extracted_exception, extracted_exception_name = self._extract_log_exception(log_record=record)
+ extracted_exception, extracted_exception_name, exception_notes = self._extract_log_exception(log_record=record)
formatted_log["exception"] = formatted_log.get("exception", extracted_exception)
formatted_log["exception_name"] = formatted_log.get("exception_name", extracted_exception_name)
+ formatted_log["exception_notes"] = formatted_log.get("exception_notes", exception_notes)
if self.serialize_stacktrace:
# Generate the traceback from the traceback library
formatted_log["stack_trace"] = self._serialize_stacktrace(log_record=record)
@@ -357,24 +360,30 @@ def _extract_log_message(self, log_record: logging.LogRecord) -> dict[str, Any]
return message
def _serialize_stacktrace(self, log_record: logging.LogRecord) -> LogStackTrace | None:
- if log_record.exc_info:
+ # Check if the first element of exc_info has the __name__ attribute,
+ # which indicates it is likely an exception class or object.
+ # See: https://github.com/aws-powertools/powertools-lambda-python/issues/6358
+ if isinstance(log_record.exc_info, tuple) and hasattr(log_record.exc_info[0], "__name__"):
exception_info: LogStackTrace = {
"type": log_record.exc_info[0].__name__, # type: ignore
"value": log_record.exc_info[1], # type: ignore
"module": log_record.exc_info[1].__class__.__module__,
- "frames": [],
+ "frames": [
+ {
+ "file": fs.filename,
+ "line": fs.lineno,
+ "function": fs.name,
+ "statement": fs.line,
+ }
+ for fs in traceback.extract_tb(log_record.exc_info[2])
+ ],
}
- exception_info["frames"] = [
- {"file": fs.filename, "line": fs.lineno, "function": fs.name, "statement": fs.line}
- for fs in traceback.extract_tb(log_record.exc_info[2])
- ]
-
return exception_info
return None
- def _extract_log_exception(self, log_record: logging.LogRecord) -> tuple[str, str] | tuple[None, None]:
+ def _extract_log_exception(self, log_record: logging.LogRecord) -> tuple[str, str, list] | tuple[None, None, None]:
"""Format traceback information, if available
Parameters
@@ -387,10 +396,12 @@ def _extract_log_exception(self, log_record: logging.LogRecord) -> tuple[str, st
log_record: tuple[str, str] | tuple[None, None]
Log record with constant traceback info and exception name
"""
- if log_record.exc_info:
- return self.formatException(log_record.exc_info), log_record.exc_info[0].__name__ # type: ignore
- return None, None
+ if isinstance(log_record.exc_info, tuple) and hasattr(log_record.exc_info[0], "__name__"):
+ exception_notes = getattr(log_record.exc_info[1], "__notes__", None)
+ return self.formatException(log_record.exc_info), log_record.exc_info[0].__name__, exception_notes # type: ignore
+
+ return None, None, None
def _extract_log_keys(self, log_record: logging.LogRecord) -> dict[str, Any]:
"""Extract and parse custom and reserved log keys
diff --git a/aws_lambda_powertools/logging/formatters/datadog.py b/aws_lambda_powertools/logging/formatters/datadog.py
index 4f140d93683..03c8c11e4d5 100644
--- a/aws_lambda_powertools/logging/formatters/datadog.py
+++ b/aws_lambda_powertools/logging/formatters/datadog.py
@@ -1,10 +1,12 @@
from __future__ import annotations
-from typing import TYPE_CHECKING, Any, Callable
+from typing import TYPE_CHECKING, Any
from aws_lambda_powertools.logging.formatter import LambdaPowertoolsFormatter
if TYPE_CHECKING:
+ from collections.abc import Callable
+
from aws_lambda_powertools.logging.types import LogRecord
diff --git a/aws_lambda_powertools/logging/logger.py b/aws_lambda_powertools/logging/logger.py
index cb190030432..a85593c9db7 100644
--- a/aws_lambda_powertools/logging/logger.py
+++ b/aws_lambda_powertools/logging/logger.py
@@ -14,7 +14,7 @@
import sys
import warnings
from contextlib import contextmanager
-from typing import IO, TYPE_CHECKING, Any, Callable, Generator, Iterable, Mapping, TypeVar, cast, overload
+from typing import IO, TYPE_CHECKING, Any, TypeVar, cast, overload
from aws_lambda_powertools.logging.buffer.cache import LoggerBufferCache
from aws_lambda_powertools.logging.buffer.functions import _check_minimum_buffer_log_level, _create_buffer_record
@@ -45,6 +45,8 @@
from aws_lambda_powertools.warnings import PowertoolsUserWarning
if TYPE_CHECKING:
+ from collections.abc import Callable, Generator, Iterable, Mapping
+
from aws_lambda_powertools.logging.buffer.config import LoggerBufferConfig
from aws_lambda_powertools.shared.types import AnyCallableT
@@ -240,10 +242,6 @@ def __init__(
buffer_config: LoggerBufferConfig | None = None,
**kwargs,
) -> None:
-
- # Used in case of sampling
- self.initial_log_level = self._determine_log_level(level)
-
self.service = resolve_env_var_choice(
choice=service,
env=os.getenv(constants.SERVICE_NAME_ENV, "service_undefined"),
@@ -283,6 +281,9 @@ def __init__(
if self._buffer_config:
self._buffer_cache = LoggerBufferCache(max_size_bytes=self._buffer_config.max_bytes)
+ # Used in case of sampling
+ self.initial_log_level = self._determine_log_level(level)
+
self._init_logger(
formatter_options=formatter_options,
log_level=level,
@@ -1045,6 +1046,20 @@ def _determine_log_level(self, level: str | int | None) -> str | int:
stacklevel=2,
)
+ # Check if buffer level is less verbose than ALC
+ if (
+ hasattr(self, "_buffer_config")
+ and self._buffer_config
+ and logging.getLevelName(lambda_log_level)
+ > logging.getLevelName(self._buffer_config.buffer_at_verbosity)
+ ):
+ warnings.warn(
+ "Advanced Logging Controls (ALC) Log Level is less verbose than Log Buffering Log Level. "
+ "Buffered logs will be filtered by ALC",
+ PowertoolsUserWarning,
+ stacklevel=2,
+ )
+
# AWS Lambda Advanced Logging Controls takes precedence over Powertools log level and we use this
if lambda_log_level:
return lambda_log_level
@@ -1131,6 +1146,7 @@ def _add_log_record_to_buffer(
Handles special first invocation buffering and migration of log records
between different tracer contexts.
"""
+
# Determine tracer ID, defaulting to first invoke marker
tracer_id = get_tracer_id()
@@ -1178,6 +1194,7 @@ def flush_buffer(self) -> None:
Any exceptions from underlying logging or buffer mechanisms
will be propagated to caller
"""
+
tracer_id = get_tracer_id()
# Flushing log without a tracer id? Return
@@ -1189,6 +1206,21 @@ def flush_buffer(self) -> None:
if not buffer:
return
+ if not self._buffer_config:
+ return
+
+ # Check ALC level against buffer level
+ lambda_log_level = self._get_aws_lambda_log_level()
+ if lambda_log_level:
+ # Check if buffer level is less verbose than ALC
+ if logging.getLevelName(lambda_log_level) > logging.getLevelName(self._buffer_config.buffer_at_verbosity):
+ warnings.warn(
+ "Advanced Logging Controls (ALC) Log Level is less verbose than Log Buffering Log Level. "
+ "Some logs might be missing",
+ PowertoolsUserWarning,
+ stacklevel=2,
+ )
+
# Process log records
for log_line in buffer:
self._create_and_flush_log_record(log_line)
diff --git a/aws_lambda_powertools/logging/utils.py b/aws_lambda_powertools/logging/utils.py
index ccf704579e3..91a683ee0ce 100644
--- a/aws_lambda_powertools/logging/utils.py
+++ b/aws_lambda_powertools/logging/utils.py
@@ -1,9 +1,11 @@
from __future__ import annotations
import logging
-from typing import TYPE_CHECKING, Callable
+from typing import TYPE_CHECKING
if TYPE_CHECKING:
+ from collections.abc import Callable
+
from aws_lambda_powertools.logging.logger import Logger
PACKAGE_LOGGER = "aws_lambda_powertools"
diff --git a/aws_lambda_powertools/metrics/base.py b/aws_lambda_powertools/metrics/base.py
index 32325e5ce29..0465d54cc6e 100644
--- a/aws_lambda_powertools/metrics/base.py
+++ b/aws_lambda_powertools/metrics/base.py
@@ -15,7 +15,7 @@
import warnings
from collections import defaultdict
from contextlib import contextmanager
-from typing import TYPE_CHECKING, Any, Callable, Generator
+from typing import TYPE_CHECKING, Any
from aws_lambda_powertools.metrics.exceptions import (
MetricResolutionError,
@@ -34,6 +34,8 @@
from aws_lambda_powertools.shared.functions import resolve_env_var_choice
if TYPE_CHECKING:
+ from collections.abc import Callable, Generator
+
from aws_lambda_powertools.metrics.types import MetricNameUnitResolution
logger = logging.getLogger(__name__)
diff --git a/aws_lambda_powertools/metrics/metrics.py b/aws_lambda_powertools/metrics/metrics.py
index f81484f2ec6..873f09c6377 100644
--- a/aws_lambda_powertools/metrics/metrics.py
+++ b/aws_lambda_powertools/metrics/metrics.py
@@ -1,11 +1,13 @@
# NOTE: keeps for compatibility
from __future__ import annotations
-from typing import TYPE_CHECKING, Any, Callable
+from typing import TYPE_CHECKING, Any
from aws_lambda_powertools.metrics.provider.cloudwatch_emf.cloudwatch import AmazonCloudWatchEMFProvider
if TYPE_CHECKING:
+ from collections.abc import Callable
+
from aws_lambda_powertools.metrics.base import MetricResolution, MetricUnit
from aws_lambda_powertools.metrics.provider.cloudwatch_emf.types import CloudWatchEMFOutput
from aws_lambda_powertools.shared.types import AnyCallableT
diff --git a/aws_lambda_powertools/middleware_factory/__init__.py b/aws_lambda_powertools/middleware_factory/__init__.py
index ebdb338cc15..79f292ccaf0 100644
--- a/aws_lambda_powertools/middleware_factory/__init__.py
+++ b/aws_lambda_powertools/middleware_factory/__init__.py
@@ -3,6 +3,6 @@
[`Middleware Factory`](../utilities/middleware_factory.md)
"""
-from .factory import lambda_handler_decorator
+from aws_lambda_powertools.middleware_factory.factory import lambda_handler_decorator
__all__ = ["lambda_handler_decorator"]
diff --git a/aws_lambda_powertools/middleware_factory/factory.py b/aws_lambda_powertools/middleware_factory/factory.py
index 4f60c2be287..a4eabf1f259 100644
--- a/aws_lambda_powertools/middleware_factory/factory.py
+++ b/aws_lambda_powertools/middleware_factory/factory.py
@@ -4,7 +4,7 @@
import inspect
import logging
import os
-from typing import Any, Callable
+from typing import TYPE_CHECKING, Any
from aws_lambda_powertools.middleware_factory.exceptions import MiddlewareInvalidArgumentError
from aws_lambda_powertools.shared import constants
@@ -13,6 +13,9 @@
logger = logging.getLogger(__name__)
+if TYPE_CHECKING:
+ from collections.abc import Callable
+
# Maintenance: we can't yet provide an accurate return type without ParamSpec etc. see #1066
def lambda_handler_decorator(decorator: Callable | None = None, trace_execution: bool | None = None) -> Callable:
diff --git a/aws_lambda_powertools/shared/dynamodb_deserializer.py b/aws_lambda_powertools/shared/dynamodb_deserializer.py
index c3dc4e48264..d90e0a47554 100644
--- a/aws_lambda_powertools/shared/dynamodb_deserializer.py
+++ b/aws_lambda_powertools/shared/dynamodb_deserializer.py
@@ -1,7 +1,10 @@
from __future__ import annotations
from decimal import Clamped, Context, Decimal, Inexact, Overflow, Rounded, Underflow
-from typing import Any, Callable, Sequence
+from typing import TYPE_CHECKING, Any
+
+if TYPE_CHECKING:
+ from collections.abc import Callable, Sequence
# NOTE: DynamoDB supports up to 38 digits precision
# Therefore, this ensures our Decimal follows what's stored in the table
diff --git a/aws_lambda_powertools/shared/functions.py b/aws_lambda_powertools/shared/functions.py
index 4e961d4aee0..2d92af54360 100644
--- a/aws_lambda_powertools/shared/functions.py
+++ b/aws_lambda_powertools/shared/functions.py
@@ -8,10 +8,13 @@
import warnings
from binascii import Error as BinAsciiError
from pathlib import Path
-from typing import Any, Generator, overload
+from typing import TYPE_CHECKING, Any, overload
from aws_lambda_powertools.shared import constants
+if TYPE_CHECKING:
+ from collections.abc import Generator
+
logger = logging.getLogger(__name__)
diff --git a/aws_lambda_powertools/shared/types.py b/aws_lambda_powertools/shared/types.py
index c5c91535bd3..aeafb378dab 100644
--- a/aws_lambda_powertools/shared/types.py
+++ b/aws_lambda_powertools/shared/types.py
@@ -1,3 +1,4 @@
-from typing import Any, Callable, TypeVar
+from collections.abc import Callable
+from typing import Any, TypeVar
-AnyCallableT = TypeVar("AnyCallableT", bound=Callable[..., Any]) # noqa: VNE001
+AnyCallableT = TypeVar("AnyCallableT", bound=Callable[..., Any])
diff --git a/aws_lambda_powertools/shared/version.py b/aws_lambda_powertools/shared/version.py
index 8d32d9d5523..446f2c18ed4 100644
--- a/aws_lambda_powertools/shared/version.py
+++ b/aws_lambda_powertools/shared/version.py
@@ -1,3 +1,3 @@
"""Exposes version constant to avoid circular dependencies."""
-VERSION = "3.9.1a3"
+VERSION = "3.10.1a11"
diff --git a/aws_lambda_powertools/tracing/base.py b/aws_lambda_powertools/tracing/base.py
index e095287ce62..8469c075222 100644
--- a/aws_lambda_powertools/tracing/base.py
+++ b/aws_lambda_powertools/tracing/base.py
@@ -8,11 +8,12 @@
import abc
from contextlib import contextmanager
-from typing import TYPE_CHECKING, Any, Generator, Sequence
+from typing import TYPE_CHECKING, Any
if TYPE_CHECKING:
import numbers
import traceback
+ from collections.abc import Generator, Sequence
class BaseSegment(abc.ABC):
diff --git a/aws_lambda_powertools/tracing/tracer.py b/aws_lambda_powertools/tracing/tracer.py
index 503a8e71141..e2de4c66f48 100644
--- a/aws_lambda_powertools/tracing/tracer.py
+++ b/aws_lambda_powertools/tracing/tracer.py
@@ -6,7 +6,7 @@
import inspect
import logging
import os
-from typing import TYPE_CHECKING, Any, Callable, Sequence, TypeVar, cast, overload
+from typing import TYPE_CHECKING, Any, TypeVar, cast, overload
from aws_lambda_powertools.shared import constants
from aws_lambda_powertools.shared.functions import (
@@ -19,6 +19,7 @@
if TYPE_CHECKING:
import numbers
+ from collections.abc import Callable, Sequence
from aws_lambda_powertools.tracing.base import BaseProvider, BaseSegment
diff --git a/aws_lambda_powertools/utilities/batch/base.py b/aws_lambda_powertools/utilities/batch/base.py
index 2cb74296ca0..d21a329e7c9 100644
--- a/aws_lambda_powertools/utilities/batch/base.py
+++ b/aws_lambda_powertools/utilities/batch/base.py
@@ -14,7 +14,7 @@
import sys
from abc import ABC, abstractmethod
from enum import Enum
-from typing import TYPE_CHECKING, Any, Callable, Tuple, Union, overload
+from typing import TYPE_CHECKING, Any, Tuple, Union, overload
from aws_lambda_powertools.shared import constants
from aws_lambda_powertools.utilities.batch.exceptions import (
@@ -31,6 +31,8 @@
from aws_lambda_powertools.utilities.data_classes.sqs_event import SQSRecord
if TYPE_CHECKING:
+ from collections.abc import Callable
+
from aws_lambda_powertools.utilities.batch.types import (
PartialItemFailureResponse,
PartialItemFailures,
@@ -294,8 +296,7 @@ def _clean(self):
if self._entire_batch_failed() and self.raise_on_entire_batch_failure:
raise BatchProcessingError(
- msg=f"All records failed processing. {len(self.exceptions)} individual errors logged "
- f"separately below.",
+ msg=f"All records failed processing. {len(self.exceptions)} individual errors logged separately below.",
child_exceptions=self.exceptions,
)
diff --git a/aws_lambda_powertools/utilities/batch/decorators.py b/aws_lambda_powertools/utilities/batch/decorators.py
index 2b9f5433e70..320535141fc 100644
--- a/aws_lambda_powertools/utilities/batch/decorators.py
+++ b/aws_lambda_powertools/utilities/batch/decorators.py
@@ -1,7 +1,7 @@
from __future__ import annotations
import warnings
-from typing import TYPE_CHECKING, Any, Awaitable, Callable
+from typing import TYPE_CHECKING, Any
from typing_extensions import deprecated
@@ -16,6 +16,8 @@
from aws_lambda_powertools.warnings import PowertoolsDeprecationWarning
if TYPE_CHECKING:
+ from collections.abc import Awaitable, Callable
+
from aws_lambda_powertools.utilities.batch.types import PartialItemFailureResponse
from aws_lambda_powertools.utilities.typing import LambdaContext
diff --git a/aws_lambda_powertools/utilities/data_classes/__init__.py b/aws_lambda_powertools/utilities/data_classes/__init__.py
index 2757725dc62..7c1b67e6fa0 100644
--- a/aws_lambda_powertools/utilities/data_classes/__init__.py
+++ b/aws_lambda_powertools/utilities/data_classes/__init__.py
@@ -6,6 +6,7 @@
from .api_gateway_proxy_event import APIGatewayProxyEvent, APIGatewayProxyEventV2
from .api_gateway_websocket_event import APIGatewayWebSocketEvent
from .appsync_resolver_event import AppSyncResolverEvent
+from .appsync_resolver_events_event import AppSyncResolverEventsEvent
from .aws_config_rule_event import AWSConfigRuleEvent
from .bedrock_agent_event import BedrockAgentEvent
from .cloud_watch_alarm_event import (
@@ -55,6 +56,7 @@
"APIGatewayWebSocketEvent",
"SecretsManagerEvent",
"AppSyncResolverEvent",
+ "AppSyncResolverEventsEvent",
"ALBEvent",
"BedrockAgentEvent",
"CloudWatchAlarmData",
diff --git a/aws_lambda_powertools/utilities/data_classes/active_mq_event.py b/aws_lambda_powertools/utilities/data_classes/active_mq_event.py
index 8f5c952da3f..26ed82dc32c 100644
--- a/aws_lambda_powertools/utilities/data_classes/active_mq_event.py
+++ b/aws_lambda_powertools/utilities/data_classes/active_mq_event.py
@@ -1,11 +1,14 @@
from __future__ import annotations
from functools import cached_property
-from typing import Any, Iterator
+from typing import TYPE_CHECKING, Any
from aws_lambda_powertools.utilities.data_classes.common import DictWrapper
from aws_lambda_powertools.utilities.data_classes.shared_functions import base64_decode
+if TYPE_CHECKING:
+ from collections.abc import Iterator
+
class ActiveMQMessage(DictWrapper):
@property
diff --git a/aws_lambda_powertools/utilities/data_classes/api_gateway_websocket_event.py b/aws_lambda_powertools/utilities/data_classes/api_gateway_websocket_event.py
index f71e236f874..bb93cac7fe2 100644
--- a/aws_lambda_powertools/utilities/data_classes/api_gateway_websocket_event.py
+++ b/aws_lambda_powertools/utilities/data_classes/api_gateway_websocket_event.py
@@ -123,6 +123,14 @@ def headers(self) -> dict[str, str]:
def multi_value_headers(self) -> dict[str, list[str]]:
return CaseInsensitiveDict(self.get("multiValueHeaders"))
+ @property
+ def query_string_parameters(self) -> dict[str, str]:
+ return CaseInsensitiveDict(self.get("queryStringParameters"))
+
+ @property
+ def multi_value_query_string_parameters(self) -> dict[str, list[str]]:
+ return CaseInsensitiveDict(self.get("multiValueQueryStringParameters"))
+
@property
def request_context(self) -> APIGatewayWebSocketEventRequestContext:
return APIGatewayWebSocketEventRequestContext(self["requestContext"])
diff --git a/aws_lambda_powertools/utilities/data_classes/appsync_resolver_event.py b/aws_lambda_powertools/utilities/data_classes/appsync_resolver_event.py
index 83d266b119f..57f6b924351 100644
--- a/aws_lambda_powertools/utilities/data_classes/appsync_resolver_event.py
+++ b/aws_lambda_powertools/utilities/data_classes/appsync_resolver_event.py
@@ -26,6 +26,50 @@ def get_identity_object(identity: dict | None) -> Any:
return AppSyncIdentityIAM(identity)
+class AppSyncEventBase(DictWrapper):
+ """AppSync resolver event base to work with AppSync GraphQL + Events"""
+
+ @property
+ def request_headers(self) -> dict[str, str]:
+ """Request headers"""
+ return CaseInsensitiveDict(self["request"]["headers"])
+
+ @property
+ def domain_name(self) -> str | None:
+ """The domain name when using custom domain"""
+ return self["request"].get("domainName")
+
+ @property
+ def prev_result(self) -> dict[str, Any] | None:
+ """It represents the result of whatever previous operation was executed in a pipeline resolver."""
+ prev = self.get("prev")
+ if not prev:
+ return None
+ return prev.get("result")
+
+ @property
+ def stash(self) -> dict:
+ """The stash is a map that is made available inside each resolver and function mapping template.
+ The same stash instance lives through a single resolver execution. This means that you can use the
+ stash to pass arbitrary data across request and response mapping templates, and across functions in
+ a pipeline resolver."""
+ return self.get("stash") or {}
+
+ @property
+ def identity(self) -> AppSyncIdentityIAM | AppSyncIdentityCognito | None:
+ """An object that contains information about the caller.
+
+ Depending on the type of identify found:
+
+ - API_KEY authorization - returns None
+ - AWS_IAM authorization - returns AppSyncIdentityIAM
+ - AMAZON_COGNITO_USER_POOLS authorization - returns AppSyncIdentityCognito
+ - AWS_LAMBDA authorization - returns None - NEED TO TEST
+ - OPENID_CONNECT authorization - returns None - NEED TO TEST
+ """
+ return get_identity_object(self.get("identity"))
+
+
class AppSyncIdentityIAM(DictWrapper):
"""AWS_IAM authorization"""
@@ -141,7 +185,7 @@ def selection_set_graphql(self) -> str | None:
return self.get("selectionSetGraphQL")
-class AppSyncResolverEvent(DictWrapper):
+class AppSyncResolverEvent(AppSyncEventBase):
"""AppSync resolver event
**NOTE:** AppSync Resolver Events can come in various shapes this data class
@@ -178,49 +222,16 @@ def arguments(self) -> dict[str, Any]:
"""A map that contains all GraphQL arguments for this field."""
return self["arguments"]
- @property
- def identity(self) -> AppSyncIdentityIAM | AppSyncIdentityCognito | None:
- """An object that contains information about the caller.
-
- Depending on the type of identify found:
-
- - API_KEY authorization - returns None
- - AWS_IAM authorization - returns AppSyncIdentityIAM
- - AMAZON_COGNITO_USER_POOLS authorization - returns AppSyncIdentityCognito
- """
- return get_identity_object(self.get("identity"))
-
@property
def source(self) -> dict[str, Any]:
"""A map that contains the resolution of the parent field."""
return self.get("source") or {}
- @property
- def request_headers(self) -> dict[str, str]:
- """Request headers"""
- return CaseInsensitiveDict(self["request"]["headers"])
-
- @property
- def prev_result(self) -> dict[str, Any] | None:
- """It represents the result of whatever previous operation was executed in a pipeline resolver."""
- prev = self.get("prev")
- if not prev:
- return None
- return prev.get("result")
-
@property
def info(self) -> AppSyncResolverEventInfo:
"""The info section contains information about the GraphQL request."""
return self._info
- @property
- def stash(self) -> dict:
- """The stash is a map that is made available inside each resolver and function mapping template.
- The same stash instance lives through a single resolver execution. This means that you can use the
- stash to pass arbitrary data across request and response mapping templates, and across functions in
- a pipeline resolver."""
- return self.get("stash") or {}
-
@overload
def get_header_value(
self,
diff --git a/aws_lambda_powertools/utilities/data_classes/appsync_resolver_events_event.py b/aws_lambda_powertools/utilities/data_classes/appsync_resolver_events_event.py
new file mode 100644
index 00000000000..67a95783e03
--- /dev/null
+++ b/aws_lambda_powertools/utilities/data_classes/appsync_resolver_events_event.py
@@ -0,0 +1,58 @@
+from __future__ import annotations
+
+from typing import Any
+
+from aws_lambda_powertools.utilities.data_classes.appsync_resolver_event import AppSyncEventBase
+from aws_lambda_powertools.utilities.data_classes.common import DictWrapper
+
+
+class AppSyncResolverEventsInfo(DictWrapper):
+
+ @property
+ def channel(self) -> dict[str, Any]:
+ """Channel details including path and segments"""
+ return self["channel"]
+
+ @property
+ def channel_path(self) -> str:
+ """Provides direct access to the 'path' attribute within the 'channel' object."""
+ return self["channel"]["path"]
+
+ @property
+ def channel_segments(self) -> list[str]:
+ """Provides direct access to the 'segments' attribute within the 'channel' object."""
+ return self["channel"]["segments"]
+
+ @property
+ def channel_namespace(self) -> dict:
+ """Namespace configuration for the channel"""
+ return self["channelNamespace"]
+
+ @property
+ def operation(self) -> str:
+ """The operation being performed (e.g., PUBLISH, SUBSCRIBE)"""
+ return self["operation"]
+
+
+class AppSyncResolverEventsEvent(AppSyncEventBase):
+ """AppSync resolver event events
+
+ Documentation:
+ -------------
+ - TBD
+ """
+
+ @property
+ def events(self) -> list[dict[str, Any]]:
+ """The payload sent to Lambda"""
+ return self.get("events") or [{}]
+
+ @property
+ def out_errors(self) -> list:
+ """The outErrors property"""
+ return self.get("outErrors") or []
+
+ @property
+ def info(self) -> AppSyncResolverEventsInfo:
+ "The info containing information about channel, namespace, and event"
+ return AppSyncResolverEventsInfo(self["info"])
diff --git a/aws_lambda_powertools/utilities/data_classes/code_pipeline_job_event.py b/aws_lambda_powertools/utilities/data_classes/code_pipeline_job_event.py
index 3497227ed70..d314fde4cb3 100644
--- a/aws_lambda_powertools/utilities/data_classes/code_pipeline_job_event.py
+++ b/aws_lambda_powertools/utilities/data_classes/code_pipeline_job_event.py
@@ -314,7 +314,6 @@ def put_artifact(self, artifact_name: str, body: Any, content_type: str) -> None
# So we are using if/else instead.
if self.data.encryption_key:
-
encryption_key_id = self.data.encryption_key.get_id
encryption_key_type = self.data.encryption_key.get_type
if encryption_key_type == "KMS":
diff --git a/aws_lambda_powertools/utilities/data_classes/common.py b/aws_lambda_powertools/utilities/data_classes/common.py
index 5af24c27a13..ecc9a2033ab 100644
--- a/aws_lambda_powertools/utilities/data_classes/common.py
+++ b/aws_lambda_powertools/utilities/data_classes/common.py
@@ -9,14 +9,17 @@
import base64
import json
import warnings
+from collections.abc import Mapping
from functools import cached_property
-from typing import TYPE_CHECKING, Any, Callable, Iterator, Mapping, overload
+from typing import TYPE_CHECKING, Any, overload
from typing_extensions import deprecated
from aws_lambda_powertools.warnings import PowertoolsDeprecationWarning
if TYPE_CHECKING:
+ from collections.abc import Callable, Iterator
+
from aws_lambda_powertools.shared.headers_serializer import BaseHeadersSerializer
from aws_lambda_powertools.utilities.data_classes.shared_functions import (
diff --git a/aws_lambda_powertools/utilities/data_classes/dynamo_db_stream_event.py b/aws_lambda_powertools/utilities/data_classes/dynamo_db_stream_event.py
index 46e0d22fd87..8da2c983f88 100644
--- a/aws_lambda_powertools/utilities/data_classes/dynamo_db_stream_event.py
+++ b/aws_lambda_powertools/utilities/data_classes/dynamo_db_stream_event.py
@@ -2,11 +2,14 @@
from enum import Enum
from functools import cached_property
-from typing import Any, Iterator
+from typing import TYPE_CHECKING, Any
from aws_lambda_powertools.shared.dynamodb_deserializer import TypeDeserializer
from aws_lambda_powertools.utilities.data_classes.common import DictWrapper
+if TYPE_CHECKING:
+ from collections.abc import Iterator
+
class StreamViewType(Enum):
"""The type of data from the modified DynamoDB item that was captured in this stream record"""
diff --git a/aws_lambda_powertools/utilities/data_classes/event_source.py b/aws_lambda_powertools/utilities/data_classes/event_source.py
index 164e29dbd11..6096e3ae7bc 100644
--- a/aws_lambda_powertools/utilities/data_classes/event_source.py
+++ b/aws_lambda_powertools/utilities/data_classes/event_source.py
@@ -1,10 +1,12 @@
from __future__ import annotations
-from typing import TYPE_CHECKING, Any, Callable
+from typing import TYPE_CHECKING, Any
from aws_lambda_powertools.middleware_factory import lambda_handler_decorator
if TYPE_CHECKING:
+ from collections.abc import Callable
+
from aws_lambda_powertools.utilities.data_classes.common import DictWrapper
from aws_lambda_powertools.utilities.typing import LambdaContext
diff --git a/aws_lambda_powertools/utilities/data_classes/kafka_event.py b/aws_lambda_powertools/utilities/data_classes/kafka_event.py
index 9a22edcaccb..c3d549c0f49 100644
--- a/aws_lambda_powertools/utilities/data_classes/kafka_event.py
+++ b/aws_lambda_powertools/utilities/data_classes/kafka_event.py
@@ -2,10 +2,13 @@
import base64
from functools import cached_property
-from typing import Any, Iterator
+from typing import TYPE_CHECKING, Any
from aws_lambda_powertools.utilities.data_classes.common import CaseInsensitiveDict, DictWrapper
+if TYPE_CHECKING:
+ from collections.abc import Iterator
+
class KafkaEventRecord(DictWrapper):
@property
@@ -34,14 +37,25 @@ def timestamp_type(self) -> str:
return self["timestampType"]
@property
- def key(self) -> str:
- """The raw (base64 encoded) Kafka record key."""
- return self["key"]
+ def key(self) -> str | None:
+ """
+ The raw (base64 encoded) Kafka record key.
+
+ This key is optional; if not provided,
+ a round-robin algorithm will be used to determine
+ the partition for the message.
+ """
+
+ return self.get("key")
@property
- def decoded_key(self) -> bytes:
- """Decode the base64 encoded key as bytes."""
- return base64.b64decode(self.key)
+ def decoded_key(self) -> bytes | None:
+ """
+ Decode the base64 encoded key as bytes.
+
+ If the key is not provided, this will return None.
+ """
+ return None if self.key is None else base64.b64decode(self.key)
@property
def value(self) -> str:
diff --git a/aws_lambda_powertools/utilities/data_classes/kinesis_firehose_event.py b/aws_lambda_powertools/utilities/data_classes/kinesis_firehose_event.py
index bc61cd69cf0..7782bbded01 100644
--- a/aws_lambda_powertools/utilities/data_classes/kinesis_firehose_event.py
+++ b/aws_lambda_powertools/utilities/data_classes/kinesis_firehose_event.py
@@ -5,11 +5,13 @@
import warnings
from dataclasses import dataclass, field
from functools import cached_property
-from typing import TYPE_CHECKING, Any, Callable, ClassVar, Iterator
+from typing import TYPE_CHECKING, Any, ClassVar
from aws_lambda_powertools.utilities.data_classes.common import DictWrapper
if TYPE_CHECKING:
+ from collections.abc import Callable, Iterator
+
from typing_extensions import Literal
diff --git a/aws_lambda_powertools/utilities/data_classes/kinesis_stream_event.py b/aws_lambda_powertools/utilities/data_classes/kinesis_stream_event.py
index 8d6c96c557d..6b189f937fd 100644
--- a/aws_lambda_powertools/utilities/data_classes/kinesis_stream_event.py
+++ b/aws_lambda_powertools/utilities/data_classes/kinesis_stream_event.py
@@ -3,13 +3,16 @@
import base64
import json
import zlib
-from typing import Iterator
+from typing import TYPE_CHECKING
from aws_lambda_powertools.utilities.data_classes.cloud_watch_logs_event import (
CloudWatchLogsDecodedData,
)
from aws_lambda_powertools.utilities.data_classes.common import DictWrapper
+if TYPE_CHECKING:
+ from collections.abc import Iterator
+
class KinesisStreamRecordPayload(DictWrapper):
@property
diff --git a/aws_lambda_powertools/utilities/data_classes/s3_batch_operation_event.py b/aws_lambda_powertools/utilities/data_classes/s3_batch_operation_event.py
index 9ad201afc2d..ce4c16e436d 100644
--- a/aws_lambda_powertools/utilities/data_classes/s3_batch_operation_event.py
+++ b/aws_lambda_powertools/utilities/data_classes/s3_batch_operation_event.py
@@ -2,7 +2,7 @@
import warnings
from dataclasses import dataclass, field
-from typing import Any, Iterator, Literal
+from typing import TYPE_CHECKING, Any, Literal
from urllib.parse import unquote_plus
from aws_lambda_powertools.utilities.data_classes.common import DictWrapper
@@ -11,6 +11,9 @@
VALID_RESULT_CODES: tuple[str, str, str] = ("Succeeded", "TemporaryFailure", "PermanentFailure")
RESULT_CODE_TYPE = Literal["Succeeded", "TemporaryFailure", "PermanentFailure"]
+if TYPE_CHECKING:
+ from collections.abc import Iterator
+
@dataclass(repr=False, order=False)
class S3BatchOperationResponseRecord:
diff --git a/aws_lambda_powertools/utilities/data_classes/s3_event.py b/aws_lambda_powertools/utilities/data_classes/s3_event.py
index db5debc8db1..bf404f1ecbf 100644
--- a/aws_lambda_powertools/utilities/data_classes/s3_event.py
+++ b/aws_lambda_powertools/utilities/data_classes/s3_event.py
@@ -1,6 +1,6 @@
from __future__ import annotations
-from typing import Iterator
+from typing import TYPE_CHECKING
from urllib.parse import unquote_plus
from aws_lambda_powertools.utilities.data_classes.common import DictWrapper
@@ -8,6 +8,9 @@
EventBridgeEvent,
)
+if TYPE_CHECKING:
+ from collections.abc import Iterator
+
class S3Identity(DictWrapper):
@property
diff --git a/aws_lambda_powertools/utilities/data_classes/ses_event.py b/aws_lambda_powertools/utilities/data_classes/ses_event.py
index 1c405e540a7..2e49f63da14 100644
--- a/aws_lambda_powertools/utilities/data_classes/ses_event.py
+++ b/aws_lambda_powertools/utilities/data_classes/ses_event.py
@@ -1,9 +1,12 @@
from __future__ import annotations
-from typing import Iterator
+from typing import TYPE_CHECKING
from aws_lambda_powertools.utilities.data_classes.common import DictWrapper
+if TYPE_CHECKING:
+ from collections.abc import Iterator
+
class SESMailHeader(DictWrapper):
@property
diff --git a/aws_lambda_powertools/utilities/data_classes/sns_event.py b/aws_lambda_powertools/utilities/data_classes/sns_event.py
index 1720389ad05..828b6e18264 100644
--- a/aws_lambda_powertools/utilities/data_classes/sns_event.py
+++ b/aws_lambda_powertools/utilities/data_classes/sns_event.py
@@ -1,9 +1,12 @@
from __future__ import annotations
-from typing import Iterator
+from typing import TYPE_CHECKING
from aws_lambda_powertools.utilities.data_classes.common import DictWrapper
+if TYPE_CHECKING:
+ from collections.abc import Iterator
+
class SNSMessageAttribute(DictWrapper):
@property
diff --git a/aws_lambda_powertools/utilities/data_classes/sqs_event.py b/aws_lambda_powertools/utilities/data_classes/sqs_event.py
index dc149c04902..2de8c1ccc28 100644
--- a/aws_lambda_powertools/utilities/data_classes/sqs_event.py
+++ b/aws_lambda_powertools/utilities/data_classes/sqs_event.py
@@ -1,12 +1,15 @@
from __future__ import annotations
from functools import cached_property
-from typing import Any, Dict, ItemsView, Iterator, TypeVar
+from typing import TYPE_CHECKING, Any, ItemsView, Iterator, TypeVar
from aws_lambda_powertools.utilities.data_classes import S3Event
from aws_lambda_powertools.utilities.data_classes.common import DictWrapper
from aws_lambda_powertools.utilities.data_classes.sns_event import SNSMessage
+if TYPE_CHECKING:
+ from collections.abc import Iterator
+
class SQSRecordAttributes(DictWrapper):
@property
@@ -86,7 +89,7 @@ def data_type(self) -> str:
return self["dataType"]
-class SQSMessageAttributes(Dict[str, SQSMessageAttribute]):
+class SQSMessageAttributes(dict[str, SQSMessageAttribute]):
def __getitem__(self, key: str) -> SQSMessageAttribute | None: # type: ignore
item = super().get(key)
return None if item is None else SQSMessageAttribute(item) # type: ignore
diff --git a/aws_lambda_powertools/utilities/data_classes/transfer_family_event.py b/aws_lambda_powertools/utilities/data_classes/transfer_family_event.py
index eecc425e527..5949c58a318 100644
--- a/aws_lambda_powertools/utilities/data_classes/transfer_family_event.py
+++ b/aws_lambda_powertools/utilities/data_classes/transfer_family_event.py
@@ -1,5 +1,6 @@
from __future__ import annotations
+import json
from typing import Any, Literal
from aws_lambda_powertools.utilities.data_classes.common import (
@@ -38,19 +39,17 @@ def source_ip(self) -> str:
class TransferFamilyAuthorizerResponse:
-
def _build_authentication_response(
self,
role_arn: str,
policy: str | None = None,
home_directory: str | None = None,
- home_directory_details: dict | None = None,
+ home_directory_details: list[dict] | None = None,
home_directory_type: Literal["LOGICAL", "PATH"] = "PATH",
user_gid: int | None = None,
user_uid: int | None = None,
public_keys: str | None = None,
) -> dict[str, Any]:
-
response: dict[str, Any] = {}
if home_directory_type == "PATH":
@@ -62,7 +61,7 @@ def _build_authentication_response(
if not home_directory_details:
raise ValueError("home_directory_details must be set when home_directory_type is LOGICAL")
- response["HomeDirectoryDetails"] = [home_directory_details]
+ response["HomeDirectoryDetails"] = json.dumps(home_directory_details)
else:
raise ValueError(f"Invalid home_directory_type: {home_directory_type}")
@@ -88,7 +87,7 @@ def build_authentication_response_efs(
user_uid: int,
policy: str | None = None,
home_directory: str | None = None,
- home_directory_details: dict | None = None,
+ home_directory_details: list[dict] | None = None,
home_directory_type: Literal["LOGICAL", "PATH"] = "PATH",
public_keys: str | None = None,
) -> dict[str, Any]:
@@ -143,7 +142,7 @@ def build_authentication_response_s3(
role_arn: str,
policy: str | None = None,
home_directory: str | None = None,
- home_directory_details: dict | None = None,
+ home_directory_details: list[dict] | None = None,
home_directory_type: Literal["LOGICAL", "PATH"] = "PATH",
public_keys: str | None = None,
) -> dict[str, Any]:
diff --git a/aws_lambda_powertools/utilities/data_masking/base.py b/aws_lambda_powertools/utilities/data_masking/base.py
index 3eed26045c2..0c58ee2a861 100644
--- a/aws_lambda_powertools/utilities/data_masking/base.py
+++ b/aws_lambda_powertools/utilities/data_masking/base.py
@@ -6,11 +6,12 @@
from __future__ import annotations
+import dataclasses
import functools
import logging
import warnings
from copy import deepcopy
-from typing import TYPE_CHECKING, Any, Callable, Mapping, Sequence
+from typing import TYPE_CHECKING, Any
from jsonpath_ng.ext import parse
@@ -22,11 +23,57 @@
from aws_lambda_powertools.warnings import PowertoolsUserWarning
if TYPE_CHECKING:
+ from collections.abc import Callable, Mapping, Sequence
from numbers import Number
logger = logging.getLogger(__name__)
+def prepare_data(data: Any, _visited: set[int] | None = None) -> Any:
+ """
+ Recursively convert complex objects into dictionaries or simple types.
+ Handles dataclasses, Pydantic models, and prevents circular references.
+ """
+ _visited = _visited or set()
+
+ # Handle circular references and primitive types
+ data_id = id(data)
+ if data_id in _visited or isinstance(data, (str, int, float, bool, type(None))):
+ return data
+
+ _visited.add(data_id)
+
+ # Define handlers as (condition, transformer) pairs
+ handlers: list[tuple[Callable[[Any], bool], Callable[[Any], Any]]] = [
+ # Dataclasses
+ (lambda x: hasattr(x, "__dataclass_fields__"), lambda x: prepare_data(dataclasses.asdict(x), _visited)),
+ # Pydantic models
+ (lambda x: callable(getattr(x, "model_dump", None)), lambda x: prepare_data(x.model_dump(), _visited)),
+ # Objects with dict() method
+ (
+ lambda x: callable(getattr(x, "dict", None)) and not isinstance(x, dict),
+ lambda x: prepare_data(x.dict(), _visited),
+ ),
+ # Dictionaries
+ (
+ lambda x: isinstance(x, dict),
+ lambda x: {prepare_data(k, _visited): prepare_data(v, _visited) for k, v in x.items()},
+ ),
+ # Lists, tuples, sets
+ (lambda x: isinstance(x, (list, tuple, set)), lambda x: type(x)(prepare_data(item, _visited) for item in x)),
+ # Objects with __dict__
+ (lambda x: hasattr(x, "__dict__"), lambda x: prepare_data(vars(x), _visited)),
+ ]
+
+ # Find and apply the first matching handler
+ for condition, transformer in handlers:
+ if condition(data):
+ return transformer(data)
+
+ # Default fallback
+ return data
+
+
class DataMasking:
"""
The DataMasking class orchestrates erasing, encrypting, and decrypting
@@ -93,6 +140,7 @@ def encrypt(
data_masker = DataMasking(provider=encryption_provider)
encrypted = data_masker.encrypt({"secret": "value"})
"""
+ data = prepare_data(data)
return self._apply_action(
data=data,
fields=None,
@@ -135,7 +183,7 @@ def decrypt(
data_masker = DataMasking(provider=encryption_provider)
encrypted = data_masker.decrypt(encrypted_data)
"""
-
+ data = prepare_data(data)
return self._apply_action(
data=data,
fields=None,
@@ -184,6 +232,7 @@ def erase(
Any
The data with sensitive information erased or masked.
"""
+ data = prepare_data(data)
if masking_rules:
return self._apply_masking_rules(data=data, masking_rules=masking_rules)
else:
diff --git a/aws_lambda_powertools/utilities/data_masking/provider/base.py b/aws_lambda_powertools/utilities/data_masking/provider/base.py
index 16fa22d16b8..7905fa57db8 100644
--- a/aws_lambda_powertools/utilities/data_masking/provider/base.py
+++ b/aws_lambda_powertools/utilities/data_masking/provider/base.py
@@ -3,13 +3,18 @@
import functools
import json
import re
-from typing import Any, Callable
+from typing import TYPE_CHECKING, Any
from aws_lambda_powertools.utilities.data_masking.constants import DATA_MASKING_STRING
+if TYPE_CHECKING:
+ from collections.abc import Callable
+
PRESERVE_CHARS = set("-_. ")
_regex_cache = {}
+JSON_DUMPS_CALL = functools.partial(json.dumps, ensure_ascii=False)
+
class BaseProvider:
"""
@@ -49,7 +54,7 @@ def lambda_handler(event, context):
def __init__(
self,
- json_serializer: Callable[..., str] = functools.partial(json.dumps, ensure_ascii=False),
+ json_serializer: Callable[..., str] = JSON_DUMPS_CALL,
json_deserializer: Callable[[str], Any] = json.loads,
) -> None:
self.json_serializer = json_serializer
@@ -77,7 +82,6 @@ def erase(
masking_rules: dict | None = None,
**kwargs,
) -> Any:
-
result: Any = DATA_MASKING_STRING
if not any([dynamic_mask, custom_mask, regex_pattern, mask_format, masking_rules]):
diff --git a/aws_lambda_powertools/utilities/data_masking/provider/kms/aws_encryption_sdk.py b/aws_lambda_powertools/utilities/data_masking/provider/kms/aws_encryption_sdk.py
index 07d48efe569..c9c902d51cc 100644
--- a/aws_lambda_powertools/utilities/data_masking/provider/kms/aws_encryption_sdk.py
+++ b/aws_lambda_powertools/utilities/data_masking/provider/kms/aws_encryption_sdk.py
@@ -4,7 +4,7 @@
import json
import logging
from binascii import Error
-from typing import Any, Callable
+from typing import TYPE_CHECKING, Any
import botocore
from aws_encryption_sdk import (
@@ -41,8 +41,13 @@
)
from aws_lambda_powertools.utilities.data_masking.provider import BaseProvider
+if TYPE_CHECKING:
+ from collections.abc import Callable
+
logger = logging.getLogger(__name__)
+JSON_DUMPS_CALL = functools.partial(json.dumps, ensure_ascii=False)
+
class AWSEncryptionSDKProvider(BaseProvider):
"""
@@ -81,7 +86,7 @@ def __init__(
max_cache_age_seconds: float = MAX_CACHE_AGE_SECONDS,
max_messages_encrypted: int = MAX_MESSAGES_ENCRYPTED,
max_bytes_encrypted: int = MAX_BYTES_ENCRYPTED,
- json_serializer: Callable[..., str] = functools.partial(json.dumps, ensure_ascii=False),
+ json_serializer: Callable[..., str] = JSON_DUMPS_CALL,
json_deserializer: Callable[[str], Any] = json.loads,
):
super().__init__(json_serializer=json_serializer, json_deserializer=json_deserializer)
diff --git a/aws_lambda_powertools/utilities/feature_flags/feature_flags.py b/aws_lambda_powertools/utilities/feature_flags/feature_flags.py
index 6fb8d51753a..d62dbfc625f 100644
--- a/aws_lambda_powertools/utilities/feature_flags/feature_flags.py
+++ b/aws_lambda_powertools/utilities/feature_flags/feature_flags.py
@@ -1,7 +1,7 @@
from __future__ import annotations
import logging
-from typing import TYPE_CHECKING, Any, Callable, List, cast
+from typing import TYPE_CHECKING, Any, cast
from aws_lambda_powertools.utilities.feature_flags import schema
from aws_lambda_powertools.utilities.feature_flags.comparators import (
@@ -16,6 +16,8 @@
from aws_lambda_powertools.utilities.feature_flags.exceptions import ConfigurationStoreError
if TYPE_CHECKING:
+ from collections.abc import Callable
+
from aws_lambda_powertools.logging import Logger
from aws_lambda_powertools.utilities.feature_flags.base import StoreProvider
from aws_lambda_powertools.utilities.feature_flags.types import JSONType, P, T
@@ -103,7 +105,7 @@ def _evaluate_conditions(
) -> bool:
"""Evaluates whether context matches conditions, return False otherwise"""
rule_match_value = rule.get(schema.RULE_MATCH_VALUE)
- conditions = cast(List[dict], rule.get(schema.CONDITIONS_KEY))
+ conditions = cast(list[dict], rule.get(schema.CONDITIONS_KEY))
if not conditions:
self.logger.debug(
diff --git a/aws_lambda_powertools/utilities/idempotency/base.py b/aws_lambda_powertools/utilities/idempotency/base.py
index 1ab1b9fa570..e1a7d78da40 100644
--- a/aws_lambda_powertools/utilities/idempotency/base.py
+++ b/aws_lambda_powertools/utilities/idempotency/base.py
@@ -9,7 +9,7 @@
import datetime
import logging
from copy import deepcopy
-from typing import TYPE_CHECKING, Any, Callable
+from typing import TYPE_CHECKING, Any
from aws_lambda_powertools.utilities.idempotency.exceptions import (
IdempotencyAlreadyInProgressError,
@@ -29,6 +29,8 @@
)
if TYPE_CHECKING:
+ from collections.abc import Callable
+
from aws_lambda_powertools.utilities.idempotency.config import (
IdempotencyConfig,
)
diff --git a/aws_lambda_powertools/utilities/idempotency/idempotency.py b/aws_lambda_powertools/utilities/idempotency/idempotency.py
index 6aec2572fb1..f59d7df7179 100644
--- a/aws_lambda_powertools/utilities/idempotency/idempotency.py
+++ b/aws_lambda_powertools/utilities/idempotency/idempotency.py
@@ -9,7 +9,7 @@
import os
import warnings
from inspect import isclass
-from typing import TYPE_CHECKING, Any, Callable, cast
+from typing import TYPE_CHECKING, Any, cast
from aws_lambda_powertools.middleware_factory import lambda_handler_decorator
from aws_lambda_powertools.shared import constants
@@ -23,6 +23,8 @@
)
if TYPE_CHECKING:
+ from collections.abc import Callable
+
from aws_lambda_powertools.utilities.idempotency.persistence.base import (
BasePersistenceLayer,
)
diff --git a/aws_lambda_powertools/utilities/idempotency/serialization/custom_dict.py b/aws_lambda_powertools/utilities/idempotency/serialization/custom_dict.py
index 3483f86b3a3..116178f6955 100644
--- a/aws_lambda_powertools/utilities/idempotency/serialization/custom_dict.py
+++ b/aws_lambda_powertools/utilities/idempotency/serialization/custom_dict.py
@@ -1,9 +1,12 @@
from __future__ import annotations
-from typing import Any, Callable
+from typing import TYPE_CHECKING, Any
from aws_lambda_powertools.utilities.idempotency.serialization.base import BaseIdempotencySerializer
+if TYPE_CHECKING:
+ from collections.abc import Callable
+
class CustomDictSerializer(BaseIdempotencySerializer):
def __init__(self, to_dict: Callable[[Any], dict], from_dict: Callable[[dict], Any]):
diff --git a/aws_lambda_powertools/utilities/idempotency/serialization/dataclass.py b/aws_lambda_powertools/utilities/idempotency/serialization/dataclass.py
index fc8b72252c0..6477eb17984 100644
--- a/aws_lambda_powertools/utilities/idempotency/serialization/dataclass.py
+++ b/aws_lambda_powertools/utilities/idempotency/serialization/dataclass.py
@@ -38,7 +38,6 @@ def from_dict(self, data: dict) -> DataClass:
@classmethod
def instantiate(cls, model_type: Any) -> BaseIdempotencySerializer:
-
model_type = get_actual_type(model_type=model_type)
if model_type is None:
diff --git a/aws_lambda_powertools/utilities/idempotency/serialization/pydantic.py b/aws_lambda_powertools/utilities/idempotency/serialization/pydantic.py
index 8ba45a40583..924d005ddbd 100644
--- a/aws_lambda_powertools/utilities/idempotency/serialization/pydantic.py
+++ b/aws_lambda_powertools/utilities/idempotency/serialization/pydantic.py
@@ -35,7 +35,6 @@ def from_dict(self, data: dict) -> BaseModel:
@classmethod
def instantiate(cls, model_type: Any) -> BaseIdempotencySerializer:
-
model_type = get_actual_type(model_type=model_type)
if model_type is None:
diff --git a/aws_lambda_powertools/utilities/parameters/base.py b/aws_lambda_powertools/utilities/parameters/base.py
index 610530f3263..42c15e11304 100644
--- a/aws_lambda_powertools/utilities/parameters/base.py
+++ b/aws_lambda_powertools/utilities/parameters/base.py
@@ -8,8 +8,9 @@
import os
from abc import ABC, abstractmethod
+from collections.abc import Callable
from datetime import datetime, timedelta
-from typing import TYPE_CHECKING, Any, Callable, NamedTuple, cast, overload
+from typing import TYPE_CHECKING, Any, NamedTuple, cast, overload
from aws_lambda_powertools.shared import constants, user_agent
from aws_lambda_powertools.shared.functions import resolve_max_age
diff --git a/aws_lambda_powertools/utilities/parser/models/__init__.py b/aws_lambda_powertools/utilities/parser/models/__init__.py
index 8d6be2f40e0..7ea8da2dc22 100644
--- a/aws_lambda_powertools/utilities/parser/models/__init__.py
+++ b/aws_lambda_powertools/utilities/parser/models/__init__.py
@@ -27,6 +27,9 @@
RequestContextV2AuthorizerJwt,
RequestContextV2Http,
)
+from .appsync import (
+ AppSyncResolverEventModel,
+)
from .bedrock_agent import (
BedrockAgentEventModel,
BedrockAgentModel,
@@ -137,6 +140,7 @@
"AlbModel",
"AlbRequestContext",
"AlbRequestContextData",
+ "AppSyncResolverEventModel",
"DynamoDBStreamModel",
"EventBridgeModel",
"DynamoDBStreamChangedRecordModel",
diff --git a/aws_lambda_powertools/utilities/parser/models/appsync.py b/aws_lambda_powertools/utilities/parser/models/appsync.py
new file mode 100644
index 00000000000..a483f597857
--- /dev/null
+++ b/aws_lambda_powertools/utilities/parser/models/appsync.py
@@ -0,0 +1,72 @@
+from typing import Any, Dict, List, Optional, Union
+
+from pydantic import BaseModel
+
+
+class AppSyncIamIdentity(BaseModel):
+ accountId: str
+ cognitoIdentityPoolId: Optional[str]
+ cognitoIdentityId: Optional[str]
+ sourceIp: List[str]
+ username: str
+ userArn: str
+ cognitoIdentityAuthType: Optional[str]
+ cognitoIdentityAuthProvider: Optional[str]
+
+
+class AppSyncCognitoIdentity(BaseModel):
+ sub: str
+ issuer: str
+ username: str
+ claims: Dict[str, Any]
+ sourceIp: List[str]
+ defaultAuthStrategy: str
+ groups: Optional[List[str]]
+
+
+class AppSyncOidcIdentity(BaseModel):
+ claims: Dict[str, Any]
+ issuer: str
+ sub: str
+
+
+class AppSyncLambdaIdentity(BaseModel):
+ resolverContext: Dict[str, Any]
+
+
+AppSyncIdentity = Union[
+ AppSyncIamIdentity,
+ AppSyncCognitoIdentity,
+ AppSyncOidcIdentity,
+ AppSyncLambdaIdentity,
+]
+
+
+class AppSyncRequestModel(BaseModel):
+ domainName: Optional[str]
+ headers: Dict[str, str]
+
+
+class AppSyncInfoModel(BaseModel):
+ selectionSetList: List[str]
+ selectionSetGraphQL: str
+ parentTypeName: str
+ fieldName: str
+ variables: Dict[str, Any]
+
+
+class AppSyncPrevModel(BaseModel):
+ result: Dict[str, Any]
+
+
+class AppSyncResolverEventModel(BaseModel):
+ arguments: Dict[str, Any]
+ identity: Optional[AppSyncIdentity]
+ source: Optional[Dict[str, Any]]
+ request: AppSyncRequestModel
+ info: AppSyncInfoModel
+ prev: Optional[AppSyncPrevModel]
+ stash: Dict[str, Any]
+
+
+AppSyncBatchResolverEventModel = List[AppSyncResolverEventModel]
diff --git a/aws_lambda_powertools/utilities/parser/models/kafka.py b/aws_lambda_powertools/utilities/parser/models/kafka.py
index 447b96c406b..c365c51c63c 100644
--- a/aws_lambda_powertools/utilities/parser/models/kafka.py
+++ b/aws_lambda_powertools/utilities/parser/models/kafka.py
@@ -1,5 +1,5 @@
from datetime import datetime
-from typing import Dict, List, Literal, Type, Union
+from typing import Dict, List, Literal, Optional, Type, Union
from pydantic import BaseModel, field_validator
@@ -14,12 +14,16 @@ class KafkaRecordModel(BaseModel):
offset: int
timestamp: datetime
timestampType: str
- key: bytes
+ key: Optional[bytes] = None
value: Union[str, Type[BaseModel]]
headers: List[Dict[str, bytes]]
- # Added type ignore to keep compatibility between Pydantic v1 and v2
- _decode_key = field_validator("key")(base64_decode) # type: ignore[type-var, unused-ignore]
+ # key is optional; only decode if not None
+ @field_validator("key", mode="before")
+ def decode_key(cls, value):
+ if value is not None:
+ return base64_decode(value)
+ return value
@field_validator("value", mode="before")
def data_base64_decode(cls, value):
diff --git a/aws_lambda_powertools/utilities/serialization.py b/aws_lambda_powertools/utilities/serialization.py
index cb5289ae4af..5c29b556c15 100644
--- a/aws_lambda_powertools/utilities/serialization.py
+++ b/aws_lambda_powertools/utilities/serialization.py
@@ -2,7 +2,8 @@
import base64
import json
-from typing import Any, Callable
+from collections.abc import Callable
+from typing import Any
def base64_encode(data: str) -> str:
diff --git a/aws_lambda_powertools/utilities/streaming/_s3_seekable_io.py b/aws_lambda_powertools/utilities/streaming/_s3_seekable_io.py
index 0f7186da561..a4794df4eaf 100644
--- a/aws_lambda_powertools/utilities/streaming/_s3_seekable_io.py
+++ b/aws_lambda_powertools/utilities/streaming/_s3_seekable_io.py
@@ -2,7 +2,7 @@
import io
import logging
-from typing import IO, TYPE_CHECKING, Any, Iterable, Sequence, TypeVar, cast
+from typing import IO, TYPE_CHECKING, Any, TypeVar, cast
import boto3
@@ -11,6 +11,7 @@
from aws_lambda_powertools.utilities.streaming.constants import MESSAGE_STREAM_NOT_WRITABLE
if TYPE_CHECKING:
+ from collections.abc import Iterable, Sequence
from mmap import mmap
from mypy_boto3_s3.client import S3Client
diff --git a/aws_lambda_powertools/utilities/streaming/s3_object.py b/aws_lambda_powertools/utilities/streaming/s3_object.py
index 84767b14435..0be161d72c1 100644
--- a/aws_lambda_powertools/utilities/streaming/s3_object.py
+++ b/aws_lambda_powertools/utilities/streaming/s3_object.py
@@ -1,7 +1,8 @@
from __future__ import annotations
import io
-from typing import IO, TYPE_CHECKING, Any, Iterable, Literal, Sequence, TypeVar, cast, overload
+from collections.abc import Sequence
+from typing import IO, TYPE_CHECKING, Any, Literal, TypeVar, cast, overload
from aws_lambda_powertools.utilities.streaming._s3_seekable_io import _S3SeekableIO
from aws_lambda_powertools.utilities.streaming.constants import MESSAGE_STREAM_NOT_WRITABLE
@@ -12,6 +13,7 @@
from aws_lambda_powertools.utilities.streaming.types import T
if TYPE_CHECKING:
+ from collections.abc import Iterable
from mmap import mmap
from mypy_boto3_s3.client import S3Client
diff --git a/aws_lambda_powertools/utilities/validation/base.py b/aws_lambda_powertools/utilities/validation/base.py
index 4da5906ea3b..77b57e63bec 100644
--- a/aws_lambda_powertools/utilities/validation/base.py
+++ b/aws_lambda_powertools/utilities/validation/base.py
@@ -2,7 +2,7 @@
import logging
-import fastjsonschema # type: ignore
+import fastjsonschema
from aws_lambda_powertools.utilities.validation.exceptions import InvalidSchemaFormatError, SchemaValidationError
diff --git a/aws_lambda_powertools/utilities/validation/validator.py b/aws_lambda_powertools/utilities/validation/validator.py
index b38a0e8293b..bd9bb0db738 100644
--- a/aws_lambda_powertools/utilities/validation/validator.py
+++ b/aws_lambda_powertools/utilities/validation/validator.py
@@ -1,12 +1,15 @@
from __future__ import annotations
import logging
-from typing import Any, Callable
+from typing import TYPE_CHECKING, Any
from aws_lambda_powertools.middleware_factory import lambda_handler_decorator
from aws_lambda_powertools.utilities import jmespath_utils
from aws_lambda_powertools.utilities.validation.base import validate_data_against_schema
+if TYPE_CHECKING:
+ from collections.abc import Callable
+
logger = logging.getLogger(__name__)
diff --git a/docs/Dockerfile b/docs/Dockerfile
index 09cb94b0049..f912654fe54 100644
--- a/docs/Dockerfile
+++ b/docs/Dockerfile
@@ -1,5 +1,5 @@
# v9.1.18
-FROM squidfunk/mkdocs-material@sha256:f226a2d2d5983643cab401491fc40e8a5711a50e90c21433f80e91c014cff1f5
+FROM squidfunk/mkdocs-material@sha256:95f2ff42251979c043d6cb5b1c82e6ae8189e57e02105813dd1ce124021a418b
# pip-compile --generate-hashes --output-file=requirements.txt requirements.in
COPY requirements.txt /tmp/
RUN pip install --require-hashes -r /tmp/requirements.txt
diff --git a/docs/core/event_handler/api_gateway.md b/docs/core/event_handler/api_gateway.md
index 4919598b3ec..da500cc56be 100644
--- a/docs/core/event_handler/api_gateway.md
+++ b/docs/core/event_handler/api_gateway.md
@@ -22,9 +22,7 @@ Event handler for Amazon API Gateway REST and HTTP APIs, Application Load Balanc
!!! info "This is not necessary if you're installing Powertools for AWS Lambda (Python) via [Lambda Layer/SAR](../../index.md#lambda-layer){target="_blank"}."
-**When using the data validation feature**, you need to add `pydantic` as a dependency in your preferred tool _e.g., requirements.txt, pyproject.toml_.
-
-As of now, both Pydantic V1 and V2 are supported. For a future major version, we will only support Pydantic V2.
+**When using the data validation feature**, you need to add `pydantic` as a dependency in your preferred tool _e.g., requirements.txt, pyproject.toml_. At this time, we only support Pydantic V2.
### Required resources
@@ -400,8 +398,21 @@ We use the `Annotated` and OpenAPI `Body` type to instruct Event Handler that ou
You can use `response_validation_error_http_code` to set a custom HTTP code for failed response validation. When this field is set, we will raise a `ResponseValidationError` instead of a `RequestValidationError`.
+For a more granular control over the failed response validation http code, the `custom_response_validation_http_code` argument can be set per route.
+This value will override the value of the failed response validation http code set at constructor level (if any).
+
=== "customizing_response_validation.py"
+ ```python hl_lines="1 16 29 33 38"
+ --8<-- "examples/event_handler_rest/src/customizing_response_validation.py"
+ ```
+
+ 1. A response with status code set here will be returned if response data is not valid.
+ 2. Operation returns a string as oppose to a `Todo` object. This will lead to a `500` response as set in line 16.
+ 3. Operation will return a `422 Unprocessable Entity` response if response is not a `Todo` object. This overrides the custom http code set in line 16.
+
+=== "customizing_route_response_validation.py"
+
```python hl_lines="1 16 29 33"
--8<-- "examples/event_handler_rest/src/customizing_response_validation.py"
```
@@ -582,9 +593,9 @@ You can easily raise any HTTP Error back to the client using `ServiceError` exce
???+ info
If you need to send custom headers, use [Response](#fine-grained-responses) class instead.
-We provide pre-defined errors for the most popular ones such as HTTP 400, 401, 404, 500.
+We provide pre-defined errors for the most popular ones based on [AWS Lambda API Reference Common Erros](https://docs.aws.amazon.com/lambda/latest/api/CommonErrors.html).
-```python hl_lines="6-11 23 28 33 38 43" title="Raising common HTTP Status errors (4xx, 5xx)"
+```python hl_lines="7-15 27 32 37 42 47 52 57 62 67" title="Raising common HTTP Status errors (4xx, 5xx)"
--8<-- "examples/event_handler_rest/src/raising_http_errors.py"
```
@@ -1135,7 +1146,7 @@ OpenAPI 3 lets you describe APIs protected using the following security schemes:
| Security Scheme | Type | Description |
| --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [HTTP auth](https://www.iana.org/assignments/http-authschemes/http-authschemes.xhtml){target="_blank"} | `HTTPBase` | HTTP authentication schemes using the Authorization header (e.g: [Basic auth](https://swagger.io/docs/specification/authentication/basic-authentication/){target="_blank"}, [Bearer](https://swagger.io/docs/specification/authentication/bearer-authentication/){target="_blank"}) |
-| [API keys](https://swagger.io/docs/specification/authentication/api-keys/https://swagger.io/docs/specification/authentication/api-keys/){target="_blank"} (e.g: query strings, cookies) | `APIKey` | API keys in headers, query strings or [cookies](https://swagger.io/docs/specification/authentication/cookie-authentication/){target="_blank"}. |
+| [API keys](https://swagger.io/docs/specification/authentication/api-keys/){target="_blank"} (e.g: query strings, cookies) | `APIKey` | API keys in headers, query strings or [cookies](https://swagger.io/docs/specification/authentication/cookie-authentication/){target="_blank"}. |
| [OAuth 2](https://swagger.io/docs/specification/authentication/oauth2/){target="_blank"} | `OAuth2` | Authorization protocol that gives an API client limited access to user data on a web server. |
| [OpenID Connect Discovery](https://swagger.io/docs/specification/authentication/openid-connect-discovery/){target="_blank"} | `OpenIdConnect` | Identity layer built [on top of the OAuth 2.0 protocol](https://openid.net/developers/how-connect-works/){target="_blank"} and supported by some OAuth 2.0. |
| [Mutual TLS](https://swagger.io/specification/#security-scheme-object){target="_blank"}. | `MutualTLS` | Client/server certificate mutual authentication scheme. |
diff --git a/docs/core/event_handler/appsync_events.md b/docs/core/event_handler/appsync_events.md
new file mode 100644
index 00000000000..0cc70fa9f31
--- /dev/null
+++ b/docs/core/event_handler/appsync_events.md
@@ -0,0 +1,395 @@
+---
+title: AppSync Events
+description: Core utility
+status: new
+---
+
+Event Handler for AWS AppSync real-time events.
+
+```mermaid
+stateDiagram-v2
+ direction LR
+ EventSource: AppSync Events
+ EventHandlerResolvers: Publish & Subscribe events
+ LambdaInit: Lambda invocation
+ EventHandler: Event Handler
+ EventHandlerResolver: Route event based on namespace/channel
+ YourLogic: Run your registered handler function
+ EventHandlerResolverBuilder: Adapts response to AppSync contract
+ LambdaResponse: Lambda response
+
+ state EventSource {
+ EventHandlerResolvers
+ }
+
+ EventHandlerResolvers --> LambdaInit
+
+ LambdaInit --> EventHandler
+ EventHandler --> EventHandlerResolver
+
+ state EventHandler {
+ [*] --> EventHandlerResolver: app.resolve(event, context)
+ EventHandlerResolver --> YourLogic
+ YourLogic --> EventHandlerResolverBuilder
+ }
+
+ EventHandler --> LambdaResponse
+```
+
+## Key Features
+
+* Easily handle publish and subscribe events with dedicated handler methods
+* Automatic routing based on namespace and channel patterns
+* Support for wildcard patterns to create catch-all handlers
+* Process events in parallel corontrol aggregation for batch processing
+* Graceful error handling for individual events
+
+## Terminology
+
+**[AWS AppSync Events](https://docs.aws.amazon.com/appsync/latest/eventapi/event-api-welcome.html){target="_blank"}**. A service that enables you to quickly build secure, scalable real-time WebSocket APIs without managing infrastructure or writing API code.
+
+It handles connection management, message broadcasting, authentication, and monitoring, reducing time to market and operational costs.
+
+## Getting started
+
+???+ tip "Tip: New to AppSync Real-time API?"
+ Visit [AWS AppSync Real-time documentation](https://docs.aws.amazon.com/appsync/latest/eventapi/event-api-getting-started.html){target="_blank"} to understand how to set up subscriptions and pub/sub messaging.
+
+### Required resources
+
+You must have an existing AppSync Events API with real-time capabilities enabled and IAM permissions to invoke your Lambda function. That said, there are no additional permissions required to use Event Handler as routing requires no dependency (_standard library_).
+
+=== "getting_started_with_appsync_events.yaml"
+
+ ```python hl_lines="5 10 12"
+ --8<-- "examples/event_handler_appsync_events/src/getting_started_with_appsync_events.yaml"
+ ```
+
+### AppSync request and response format
+
+AppSync Events uses a specific event format for Lambda requests and responses. In most scenarios, Powertools for AWS simplifies this interaction by automatically formatting resolver returns to match the expected AppSync response structure.
+
+=== "payload_request.json"
+
+ ```python hl_lines="5 10 12"
+ --8<-- "examples/event_handler_appsync_events/src/payload_request.json"
+ ```
+
+=== "payload_response.json"
+
+ ```python hl_lines="5 10 12"
+ --8<-- "examples/event_handler_appsync_events/src/payload_response.json"
+ ```
+
+=== "payload_response_with_error.json"
+
+ ```python hl_lines="5 10 12"
+ --8<-- "examples/event_handler_appsync_events/src/payload_response_with_error.json"
+ ```
+
+=== "payload_response_fail_request.json"
+
+ ```python hl_lines="5 10 12"
+ --8<-- "examples/event_handler_appsync_events/src/payload_response_fail_request.json"
+ ```
+
+#### Events response with error
+
+When processing events with Lambda, you can return errors to AppSync in three ways:
+
+* **Item specific error:** Return an `error` key within each individual item's response. AppSync Events expects this format for item-specific errors.
+* **Fail entire request:** Return a JSON object with a top-level `error` key. This signals a general failure, and AppSync treats the entire request as unsuccessful.
+* **Unauthorized exception**: Raise the **UnauthorizedException** exception to reject a subscribe or publish request with HTTP 403.
+
+### Resolver decorator
+
+???+ important
+ The event handler automatically parses the incoming event data and invokes the appropriate handler based on the namespace/channel pattern you register.
+
+You can define your handlers for different event types using the `app.on_publish()`, `app.async_on_publish()`, and `app.on_subscribe()` methods.
+
+=== "getting_started_with_publish_events.py"
+
+ ```python hl_lines="5 10 12"
+ --8<-- "examples/event_handler_appsync_events/src/getting_started_with_publish_events.py"
+ ```
+
+=== "getting_started_with_subscribe_events.py"
+
+ ```python hl_lines="5 6 13"
+ --8<-- "examples/event_handler_appsync_events/src/getting_started_with_subscribe_events.py"
+ ```
+
+## Advanced
+
+### Wildcard patterns and handler precedence
+
+You can use wildcard patterns to create catch-all handlers for multiple channels or namespaces. This is particularly useful for centralizing logic that applies to multiple channels.
+
+When an event matches with multiple handlers, the most specific pattern takes precedence.
+
+???+ note "Supported wildcard patterns"
+ Only the following patterns are supported:
+
+ * `/namespace/*` - Matches all channels in the specified namespace
+ * `/*` - Matches all channels in all namespaces
+
+ Patterns like `/namespace/channel*` or `/namespace/*/subpath` are not supported.
+
+ More specific routes will always take precedence over less specific ones. For example, `/default/channel1` will take precedence over `/default/*`, which will take precedence over `/*`.
+
+=== "working_with_wildcard_resolvers.py"
+
+ ```python hl_lines="5 6 13"
+ --8<-- "examples/event_handler_appsync_events/src/working_with_wildcard_resolvers.py"
+ ```
+
+If the event doesn't match any registered handler, the Event Handler will log a warning and skip processing the event.
+
+### Aggregated processing
+
+???+ note "Aggregate Processing"
+ When `aggregate=True`, your handler receives a list of all events, requiring you to manage the response format. Ensure your response includes results for each event in the expected [AppSync Request and Response Format](#appsync-request-and-response-format).
+
+In some scenarios, you might want to process all events for a channel as a batch rather than individually. This is useful when you need to:
+
+* Optimize database operations by making a single batch query
+* Ensure all events are processed together or not at all
+* Apply custom error handling logic for the entire batch
+
+You can enable this with the `aggregate` parameter:
+
+=== "working_with_aggregated_events.py"
+
+ ```python hl_lines="5 6 13"
+ --8<-- "examples/event_handler_appsync_events/src/working_with_aggregated_events.py"
+ ```
+
+### Handling errors
+
+You can filter or reject events by raising exceptions in your resolvers or by formatting the payload according to the expected response structure. This instructs AppSync not to propagate that specific message, so subscribers will not receive it.
+
+#### Handling errors with individual items
+
+When processing items individually with `aggregate=False`, you can raise an exception to fail a specific message. When this happens, the Event Handler will catch it and include the exception name and message in the response.
+
+=== "working_with_error_handling.py"
+
+ ```python hl_lines="5 6 13"
+ --8<-- "examples/event_handler_appsync_events/src/working_with_error_handling.py"
+ ```
+
+=== "working_with_error_handling_response.json"
+
+ ```python hl_lines="5 6 13"
+ --8<-- "examples/event_handler_appsync_events/src/working_with_error_handling_response.json"
+ ```
+
+#### Handling errors with batch of items
+
+When processing batch of items with `aggregate=True`, you must format the payload according the expected response.
+
+=== "working_with_error_handling_multiple.py"
+
+ ```python hl_lines="5 6 13"
+ --8<-- "examples/event_handler_appsync_events/src/working_with_error_handling_multiple.py"
+ ```
+
+=== "working_with_error_handling_response.json"
+
+ ```python hl_lines="5 6 13"
+ --8<-- "examples/event_handler_appsync_events/src/working_with_error_handling_response.json"
+ ```
+
+If instead you want to fail the entire batch, you can throw an exception. This will cause the Event Handler to return an error response to AppSync and fail the entire batch.
+
+=== "working_with_error_handling_multiple.py"
+
+ ```python hl_lines="5 6 13"
+ --8<-- "examples/event_handler_appsync_events/src/working_with_error_handling_multiple.py"
+ ```
+
+=== "working_with_error_handling_response.json"
+
+ ```python hl_lines="5 6 13"
+ --8<-- "examples/event_handler_appsync_events/src/working_with_error_handling_response.json"
+ ```
+
+#### Authorization control
+
+!!! warning "Raising `UnauthorizedException` will cause the Lambda invocation to fail."
+
+You can also do content based authorization for channel by raising the `UnauthorizedException` exception. This can cause two situations:
+
+- **When working with publish events** Powertools for AWS stop processing messages and subscribers will not receive any message.
+- **When working with subscribe events** the subscription won't be established.
+
+=== "working_with_error_handling.py"
+
+ ```python hl_lines="5 6 13"
+ --8<-- "examples/event_handler_appsync_events/src/working_with_error_handling.py"
+ ```
+
+=== "working_with_error_handling_response.json"
+
+ ```python hl_lines="5 6 13"
+ --8<-- "examples/event_handler_appsync_events/src/working_with_error_handling_response.json"
+ ```
+
+### Processing events with async resolvers
+
+Use the `@app.async_on_publish()` decorator to process events asynchronously.
+
+We use `asyncio` module to support async functions, and we ensure reliable execution by managing the event loop.
+
+???+ note "Events order and AppSync Events"
+ AppSync does not rely on event order. As long as each event includes the original `id`, AppSync processes them correctly regardless of the order in which they are received.
+
+=== "working_with_async_resolvers.py"
+
+ ```python hl_lines="5 6 13"
+ --8<-- "examples/event_handler_appsync_events/src/working_with_async_resolvers.py"
+ ```
+
+### Accessing Lambda context and event
+
+You can access to the original Lambda event or context for additional information. These are accessible via the app instance:
+
+=== "accessing_event_and_context.py"
+
+ ```python hl_lines="5 6 13"
+ --8<-- "examples/event_handler_appsync_events/src/accessing_event_and_context.py"
+ ```
+
+## Event Handler workflow
+
+#### Working with single items
+
+```mermaid
+sequenceDiagram
+ participant Client
+ participant AppSync
+ participant Lambda
+ participant EventHandler
+ note over Client,EventHandler: Individual Event Processing (aggregate=False)
+ Client->>+AppSync: Send multiple events to channel
+ AppSync->>+Lambda: Invoke Lambda with batch of events
+ Lambda->>+EventHandler: Process events with aggregate=False
+ loop For each event in batch
+ EventHandler->>EventHandler: Process individual event
+ end
+ EventHandler-->>-Lambda: Return array of processed events
+ Lambda-->>-AppSync: Return event-by-event responses
+ AppSync-->>-Client: Report individual event statuses
+```
+
+
+
+#### Working with aggregated items
+
+```mermaid
+sequenceDiagram
+ participant Client
+ participant AppSync
+ participant Lambda
+ participant EventHandler
+ note over Client,EventHandler: Aggregate Processing Workflow
+ Client->>+AppSync: Send multiple events to channel
+ AppSync->>+Lambda: Invoke Lambda with batch of events
+ Lambda->>+EventHandler: Process events with aggregate=True
+ EventHandler->>EventHandler: Batch of events
+ EventHandler->>EventHandler: Process entire batch at once
+ EventHandler->>EventHandler: Format response for each event
+ EventHandler-->>-Lambda: Return aggregated results
+ Lambda-->>-AppSync: Return success responses
+ AppSync-->>-Client: Confirm all events processed
+```
+
+
+#### Authorization fails for publish
+
+
+```mermaid
+sequenceDiagram
+ participant Client
+ participant AppSync
+ participant Lambda
+ participant EventHandler
+ note over Client,EventHandler: Publish Event Authorization Flow
+ Client->>AppSync: Publish message to channel
+ AppSync->>Lambda: Invoke Lambda with publish event
+ Lambda->>EventHandler: Process publish event
+ alt Authorization Failed
+ EventHandler->>EventHandler: Authorization check fails
+ EventHandler->>Lambda: Raise UnauthorizedException
+ Lambda->>AppSync: Return error response
+ AppSync--xClient: Message not delivered
+ AppSync--xAppSync: No distribution to subscribers
+ else Authorization Passed
+ EventHandler->>Lambda: Return successful response
+ Lambda->>AppSync: Return processed event
+ AppSync->>Client: Acknowledge message
+ AppSync->>AppSync: Distribute to subscribers
+ end
+```
+
+
+#### Authorization fails for subscribe
+
+```mermaid
+sequenceDiagram
+ participant Client
+ participant AppSync
+ participant Lambda
+ participant EventHandler
+ note over Client,EventHandler: Subscribe Event Authorization Flow
+ Client->>AppSync: Request subscription to channel
+ AppSync->>Lambda: Invoke Lambda with subscribe event
+ Lambda->>EventHandler: Process subscribe event
+ alt Authorization Failed
+ EventHandler->>EventHandler: Authorization check fails
+ EventHandler->>Lambda: Raise UnauthorizedException
+ Lambda->>AppSync: Return error response
+ AppSync--xClient: Subscription denied (HTTP 403)
+ else Authorization Passed
+ EventHandler->>Lambda: Return successful response
+ Lambda->>AppSync: Return authorization success
+ AppSync->>Client: Subscription established
+ end
+```
+
+
+
+
+
+## Testing your code
+
+You can test your event handlers by passing a mocked or actual AppSync Events Lambda event.
+
+### Testing publish events
+
+=== "getting_started_with_testing_publish.py"
+
+ ```python hl_lines="5 6 13"
+ --8<-- "examples/event_handler_appsync_events/src/getting_started_with_testing_publish.py"
+ ```
+
+=== "getting_started_with_testing_publish_event.json"
+
+ ```python hl_lines="5 6 13"
+ --8<-- "examples/event_handler_appsync_events/src/getting_started_with_testing_publish_event.json"
+ ```
+
+### Testing subscribe events
+
+=== "getting_started_with_testing_subscribe.py"
+
+ ```python hl_lines="5 6 13"
+ --8<-- "examples/event_handler_appsync_events/src/getting_started_with_testing_subscribe.py"
+ ```
+
+=== "getting_started_with_testing_subscribe_event.json"
+
+ ```python hl_lines="5 6 13"
+ --8<-- "examples/event_handler_appsync_events/src/getting_started_with_testing_subscribe_event.json"
+ ```
diff --git a/docs/core/event_handler/bedrock_agents.md b/docs/core/event_handler/bedrock_agents.md
index d5c1d1c9534..9665628ff30 100644
--- a/docs/core/event_handler/bedrock_agents.md
+++ b/docs/core/event_handler/bedrock_agents.md
@@ -40,7 +40,7 @@ Create [Agents for Amazon Bedrock](https://docs.aws.amazon.com/bedrock/latest/us
!!! info "This is unnecessary if you're installing Powertools for AWS Lambda (Python) via [Lambda Layer/SAR](../../index.md#lambda-layer){target="_blank"}."
-You need to add `pydantic` as a dependency in your preferred tool _e.g., requirements.txt, pyproject.toml_. At this time, we only support Pydantic V1, due to an incompatibility with Pydantic V2 generated schemas and the Agents' API.
+You need to add `pydantic` as a dependency in your preferred tool _e.g., requirements.txt, pyproject.toml_. At this time, we only support Pydantic V2.
### Required resources
@@ -313,6 +313,16 @@ To implement these customizations, include extra parameters when defining your r
--8<-- "examples/event_handler_bedrock_agents/src/customizing_bedrock_api_operations.py"
```
+#### Enabling user confirmation
+
+You can enable user confirmation with Bedrock Agents to have your application ask for explicit user approval before invoking an action.
+
+```python hl_lines="14" title="enabling_user_confirmation.py" title="Enabling user confirmation"
+--8<-- "examples/event_handler_bedrock_agents/src/enabling_user_confirmation.py"
+```
+
+1. Add an openapi extension
+
## Testing your code
Test your routes by passing an [Agent for Amazon Bedrock proxy event](https://docs.aws.amazon.com/bedrock/latest/userguide/agents-lambda.html#agents-lambda-input) request:
diff --git a/docs/core/logger.md b/docs/core/logger.md
index caf3381853b..41c52b69db4 100644
--- a/docs/core/logger.md
+++ b/docs/core/logger.md
@@ -476,6 +476,22 @@ By default, the Logger will automatically include the full stack trace in JSON f
--8<-- "examples/logger/src/logging_stacktrace_output.json"
```
+#### Adding exception notes
+
+You can add notes to exceptions, which `logger.exception` propagates via a new `exception_notes` key in the log line. This works only in [Python 3.11 and later](https://peps.python.org/pep-0678/){target="_blank" rel="nofollow"}.
+
+=== "logging_exception_notes.py"
+
+ ```python hl_lines="15"
+ --8<-- "examples/logger/src/logging_exception_notes.py"
+ ```
+
+=== "logging_exception_notes_output.json"
+
+ ```json hl_lines="9-11"
+ --8<-- "examples/logger/src/logging_exception_notes_output.json"
+ ```
+
### Date formatting
Logger uses Python's standard logging date format with the addition of timezone: `2021-05-03 11:47:12,494+0000`.
diff --git a/docs/includes/_layer_homepage_arm64.md b/docs/includes/_layer_homepage_arm64.md
index 8d45e87e988..137a6cf2e1b 100644
--- a/docs/includes/_layer_homepage_arm64.md
+++ b/docs/includes/_layer_homepage_arm64.md
@@ -5,178 +5,178 @@
| Region | Layer ARN |
| -------------------- | -------------------------------------------------------------------------------------------------------------------------- |
- | **`af-south-1`** | **arn:aws:lambda:af-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python39-arm64:11**{: .copyMe}:clipboard: |
- | **`ap-east-1`** | **arn:aws:lambda:ap-east-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python39-arm64:11**{: .copyMe}:clipboard: |
- | **`ap-northeast-1`** | **arn:aws:lambda:ap-northeast-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python39-arm64:11**{: .copyMe}:clipboard: |
- | **`ap-northeast-2`** | **arn:aws:lambda:ap-northeast-2:017000801446:layer:AWSLambdaPowertoolsPythonV3-python39-arm64:11**{: .copyMe}:clipboard: |
- | **`ap-northeast-3`** | **arn:aws:lambda:ap-northeast-3:017000801446:layer:AWSLambdaPowertoolsPythonV3-python39-arm64:11**{: .copyMe}:clipboard: |
- | **`ap-south-1`** | **arn:aws:lambda:ap-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python39-arm64:11**{: .copyMe}:clipboard: |
- | **`ap-south-2`** | **arn:aws:lambda:ap-south-2:017000801446:layer:AWSLambdaPowertoolsPythonV3-python39-arm64:11**{: .copyMe}:clipboard: |
- | **`ap-southeast-1`** | **arn:aws:lambda:ap-southeast-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python39-arm64:11**{: .copyMe}:clipboard: |
- | **`ap-southeast-2`** | **arn:aws:lambda:ap-southeast-2:017000801446:layer:AWSLambdaPowertoolsPythonV3-python39-arm64:11**{: .copyMe}:clipboard: |
- | **`ap-southeast-3`** | **arn:aws:lambda:ap-southeast-3:017000801446:layer:AWSLambdaPowertoolsPythonV3-python39-arm64:11**{: .copyMe}:clipboard: |
- | **`ap-southeast-4`** | **arn:aws:lambda:ap-southeast-4:017000801446:layer:AWSLambdaPowertoolsPythonV3-python39-arm64:11**{: .copyMe}:clipboard: |
- | **`ap-southeast-5`** | **arn:aws:lambda:ap-southeast-5:017000801446:layer:AWSLambdaPowertoolsPythonV3-python39-arm64:11**{: .copyMe}:clipboard: |
- | **`ap-southeast-7`** | **arn:aws:lambda:ap-southeast-7:017000801446:layer:AWSLambdaPowertoolsPythonV3-python39-arm64:11**{: .copyMe}:clipboard: |
- | **`ca-central-1`** | **arn:aws:lambda:ca-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python39-arm64:11**{: .copyMe}:clipboard: |
- | **`eu-central-1`** | **arn:aws:lambda:eu-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python39-arm64:11**{: .copyMe}:clipboard: |
- | **`eu-central-2`** | **arn:aws:lambda:eu-central-2:017000801446:layer:AWSLambdaPowertoolsPythonV3-python39-arm64:11**{: .copyMe}:clipboard: |
- | **`eu-north-1`** | **arn:aws:lambda:eu-north-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python39-arm64:11**{: .copyMe}:clipboard: |
- | **`eu-south-1`** | **arn:aws:lambda:eu-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python39-arm64:11**{: .copyMe}:clipboard: |
- | **`eu-south-2`** | **arn:aws:lambda:eu-south-2:017000801446:layer:AWSLambdaPowertoolsPythonV3-python39-arm64:11**{: .copyMe}:clipboard: |
- | **`eu-west-1`** | **arn:aws:lambda:eu-west-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python39-arm64:11**{: .copyMe}:clipboard: |
- | **`eu-west-2`** | **arn:aws:lambda:eu-west-2:017000801446:layer:AWSLambdaPowertoolsPythonV3-python39-arm64:11**{: .copyMe}:clipboard: |
- | **`eu-west-3`** | **arn:aws:lambda:eu-west-3:017000801446:layer:AWSLambdaPowertoolsPythonV3-python39-arm64:11**{: .copyMe}:clipboard: |
- | **`il-central-1`** | **arn:aws:lambda:il-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python39-arm64:11**{: .copyMe}:clipboard: |
- | **`me-central-1`** | **arn:aws:lambda:me-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python39-arm64:11**{: .copyMe}:clipboard: |
- | **`me-south-1`** | **arn:aws:lambda:me-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python39-arm64:11**{: .copyMe}:clipboard: |
- | **`mx-central-1`** | **arn:aws:lambda:mx-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python39-arm64:11**{: .copyMe}:clipboard: |
- | **`sa-east-1`** | **arn:aws:lambda:sa-east-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python39-arm64:11**{: .copyMe}:clipboard: |
- | **`us-east-1`** | **arn:aws:lambda:us-east-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python39-arm64:11**{: .copyMe}:clipboard: |
- | **`us-east-2`** | **arn:aws:lambda:us-east-2:017000801446:layer:AWSLambdaPowertoolsPythonV3-python39-arm64:11**{: .copyMe}:clipboard: |
- | **`us-west-1`** | **arn:aws:lambda:us-west-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python39-arm64:11**{: .copyMe}:clipboard: |
- | **`us-west-2`** | **arn:aws:lambda:us-west-2:017000801446:layer:AWSLambdaPowertoolsPythonV3-python39-arm64:11**{: .copyMe}:clipboard: |
+ | **`af-south-1`** | **arn:aws:lambda:af-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python39-arm64:12**{: .copyMe}:clipboard: |
+ | **`ap-east-1`** | **arn:aws:lambda:ap-east-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python39-arm64:12**{: .copyMe}:clipboard: |
+ | **`ap-northeast-1`** | **arn:aws:lambda:ap-northeast-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python39-arm64:12**{: .copyMe}:clipboard: |
+ | **`ap-northeast-2`** | **arn:aws:lambda:ap-northeast-2:017000801446:layer:AWSLambdaPowertoolsPythonV3-python39-arm64:12**{: .copyMe}:clipboard: |
+ | **`ap-northeast-3`** | **arn:aws:lambda:ap-northeast-3:017000801446:layer:AWSLambdaPowertoolsPythonV3-python39-arm64:12**{: .copyMe}:clipboard: |
+ | **`ap-south-1`** | **arn:aws:lambda:ap-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python39-arm64:12**{: .copyMe}:clipboard: |
+ | **`ap-south-2`** | **arn:aws:lambda:ap-south-2:017000801446:layer:AWSLambdaPowertoolsPythonV3-python39-arm64:12**{: .copyMe}:clipboard: |
+ | **`ap-southeast-1`** | **arn:aws:lambda:ap-southeast-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python39-arm64:12**{: .copyMe}:clipboard: |
+ | **`ap-southeast-2`** | **arn:aws:lambda:ap-southeast-2:017000801446:layer:AWSLambdaPowertoolsPythonV3-python39-arm64:12**{: .copyMe}:clipboard: |
+ | **`ap-southeast-3`** | **arn:aws:lambda:ap-southeast-3:017000801446:layer:AWSLambdaPowertoolsPythonV3-python39-arm64:12**{: .copyMe}:clipboard: |
+ | **`ap-southeast-4`** | **arn:aws:lambda:ap-southeast-4:017000801446:layer:AWSLambdaPowertoolsPythonV3-python39-arm64:12**{: .copyMe}:clipboard: |
+ | **`ap-southeast-5`** | **arn:aws:lambda:ap-southeast-5:017000801446:layer:AWSLambdaPowertoolsPythonV3-python39-arm64:12**{: .copyMe}:clipboard: |
+ | **`ap-southeast-7`** | **arn:aws:lambda:ap-southeast-7:017000801446:layer:AWSLambdaPowertoolsPythonV3-python39-arm64:12**{: .copyMe}:clipboard: |
+ | **`ca-central-1`** | **arn:aws:lambda:ca-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python39-arm64:12**{: .copyMe}:clipboard: |
+ | **`eu-central-1`** | **arn:aws:lambda:eu-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python39-arm64:12**{: .copyMe}:clipboard: |
+ | **`eu-central-2`** | **arn:aws:lambda:eu-central-2:017000801446:layer:AWSLambdaPowertoolsPythonV3-python39-arm64:12**{: .copyMe}:clipboard: |
+ | **`eu-north-1`** | **arn:aws:lambda:eu-north-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python39-arm64:12**{: .copyMe}:clipboard: |
+ | **`eu-south-1`** | **arn:aws:lambda:eu-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python39-arm64:12**{: .copyMe}:clipboard: |
+ | **`eu-south-2`** | **arn:aws:lambda:eu-south-2:017000801446:layer:AWSLambdaPowertoolsPythonV3-python39-arm64:12**{: .copyMe}:clipboard: |
+ | **`eu-west-1`** | **arn:aws:lambda:eu-west-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python39-arm64:12**{: .copyMe}:clipboard: |
+ | **`eu-west-2`** | **arn:aws:lambda:eu-west-2:017000801446:layer:AWSLambdaPowertoolsPythonV3-python39-arm64:12**{: .copyMe}:clipboard: |
+ | **`eu-west-3`** | **arn:aws:lambda:eu-west-3:017000801446:layer:AWSLambdaPowertoolsPythonV3-python39-arm64:12**{: .copyMe}:clipboard: |
+ | **`il-central-1`** | **arn:aws:lambda:il-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python39-arm64:12**{: .copyMe}:clipboard: |
+ | **`me-central-1`** | **arn:aws:lambda:me-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python39-arm64:12**{: .copyMe}:clipboard: |
+ | **`me-south-1`** | **arn:aws:lambda:me-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python39-arm64:12**{: .copyMe}:clipboard: |
+ | **`mx-central-1`** | **arn:aws:lambda:mx-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python39-arm64:12**{: .copyMe}:clipboard: |
+ | **`sa-east-1`** | **arn:aws:lambda:sa-east-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python39-arm64:12**{: .copyMe}:clipboard: |
+ | **`us-east-1`** | **arn:aws:lambda:us-east-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python39-arm64:12**{: .copyMe}:clipboard: |
+ | **`us-east-2`** | **arn:aws:lambda:us-east-2:017000801446:layer:AWSLambdaPowertoolsPythonV3-python39-arm64:12**{: .copyMe}:clipboard: |
+ | **`us-west-1`** | **arn:aws:lambda:us-west-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python39-arm64:12**{: .copyMe}:clipboard: |
+ | **`us-west-2`** | **arn:aws:lambda:us-west-2:017000801446:layer:AWSLambdaPowertoolsPythonV3-python39-arm64:12**{: .copyMe}:clipboard: |
=== "Python 3.10"
| Region | Layer ARN |
| -------------------- | --------------------------------------------------------------------------------------------------------------- |
- | **`af-south-1`** | **arn:aws:lambda:af-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python310-arm64:11**{: .copyMe}:clipboard: |
- | **`ap-east-1`** | **arn:aws:lambda:ap-east-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python310-arm64:11**{: .copyMe}:clipboard: |
- | **`ap-northeast-1`** | **arn:aws:lambda:ap-northeast-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python310-arm64:11**{: .copyMe}:clipboard: |
- | **`ap-northeast-2`** | **arn:aws:lambda:ap-northeast-2:017000801446:layer:AWSLambdaPowertoolsPythonV3-python310-arm64:11**{: .copyMe}:clipboard: |
- | **`ap-northeast-3`** | **arn:aws:lambda:ap-northeast-3:017000801446:layer:AWSLambdaPowertoolsPythonV3-python310-arm64:11**{: .copyMe}:clipboard: |
- | **`ap-south-1`** | **arn:aws:lambda:ap-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python310-arm64:11**{: .copyMe}:clipboard: |
- | **`ap-south-2`** | **arn:aws:lambda:ap-south-2:017000801446:layer:AWSLambdaPowertoolsPythonV3-python310-arm64:11**{: .copyMe}:clipboard: |
- | **`ap-southeast-1`** | **arn:aws:lambda:ap-southeast-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python310-arm64:11**{: .copyMe}:clipboard: |
- | **`ap-southeast-2`** | **arn:aws:lambda:ap-southeast-2:017000801446:layer:AWSLambdaPowertoolsPythonV3-python310-arm64:11**{: .copyMe}:clipboard: |
- | **`ap-southeast-3`** | **arn:aws:lambda:ap-southeast-3:017000801446:layer:AWSLambdaPowertoolsPythonV3-python310-arm64:11**{: .copyMe}:clipboard: |
- | **`ap-southeast-4`** | **arn:aws:lambda:ap-southeast-4:017000801446:layer:AWSLambdaPowertoolsPythonV3-python310-arm64:11**{: .copyMe}:clipboard: |
- | **`ap-southeast-5`** | **arn:aws:lambda:ap-southeast-5:017000801446:layer:AWSLambdaPowertoolsPythonV3-python310-arm64:11**{: .copyMe}:clipboard: |
- | **`ap-southeast-7`** | **arn:aws:lambda:ap-southeast-7:017000801446:layer:AWSLambdaPowertoolsPythonV3-python310-arm64:11**{: .copyMe}:clipboard: |
- | **`ca-central-1`** | **arn:aws:lambda:ca-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python310-arm64:11**{: .copyMe}:clipboard: |
- | **`eu-central-1`** | **arn:aws:lambda:eu-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python310-arm64:11**{: .copyMe}:clipboard: |
- | **`eu-central-2`** | **arn:aws:lambda:eu-central-2:017000801446:layer:AWSLambdaPowertoolsPythonV3-python310-arm64:11**{: .copyMe}:clipboard: |
- | **`eu-north-1`** | **arn:aws:lambda:eu-north-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python310-arm64:11**{: .copyMe}:clipboard: |
- | **`eu-south-1`** | **arn:aws:lambda:eu-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python310-arm64:11**{: .copyMe}:clipboard: |
- | **`eu-south-2`** | **arn:aws:lambda:eu-south-2:017000801446:layer:AWSLambdaPowertoolsPythonV3-python310-arm64:11**{: .copyMe}:clipboard: |
- | **`eu-west-1`** | **arn:aws:lambda:eu-west-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python310-arm64:11**{: .copyMe}:clipboard: |
- | **`eu-west-2`** | **arn:aws:lambda:eu-west-2:017000801446:layer:AWSLambdaPowertoolsPythonV3-python310-arm64:11**{: .copyMe}:clipboard: |
- | **`eu-west-3`** | **arn:aws:lambda:eu-west-3:017000801446:layer:AWSLambdaPowertoolsPythonV3-python310-arm64:11**{: .copyMe}:clipboard: |
- | **`il-central-1`** | **arn:aws:lambda:il-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python310-arm64:11**{: .copyMe}:clipboard: |
- | **`me-central-1`** | **arn:aws:lambda:me-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python310-arm64:11**{: .copyMe}:clipboard: |
- | **`me-south-1`** | **arn:aws:lambda:me-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python310-arm64:11**{: .copyMe}:clipboard: |
- | **`mx-central-1`** | **arn:aws:lambda:mx-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python310-arm64:11**{: .copyMe}:clipboard: |
- | **`sa-east-1`** | **arn:aws:lambda:sa-east-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python310-arm64:11**{: .copyMe}:clipboard: |
- | **`us-east-1`** | **arn:aws:lambda:us-east-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python310-arm64:11**{: .copyMe}:clipboard: |
- | **`us-east-2`** | **arn:aws:lambda:us-east-2:017000801446:layer:AWSLambdaPowertoolsPythonV3-python310-arm64:11**{: .copyMe}:clipboard: |
- | **`us-west-1`** | **arn:aws:lambda:us-west-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python310-arm64:11**{: .copyMe}:clipboard: |
- | **`us-west-2`** | **arn:aws:lambda:us-west-2:017000801446:layer:AWSLambdaPowertoolsPythonV3-python310-arm64:11**{: .copyMe}:clipboard: |
+ | **`af-south-1`** | **arn:aws:lambda:af-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python310-arm64:12**{: .copyMe}:clipboard: |
+ | **`ap-east-1`** | **arn:aws:lambda:ap-east-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python310-arm64:12**{: .copyMe}:clipboard: |
+ | **`ap-northeast-1`** | **arn:aws:lambda:ap-northeast-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python310-arm64:12**{: .copyMe}:clipboard: |
+ | **`ap-northeast-2`** | **arn:aws:lambda:ap-northeast-2:017000801446:layer:AWSLambdaPowertoolsPythonV3-python310-arm64:12**{: .copyMe}:clipboard: |
+ | **`ap-northeast-3`** | **arn:aws:lambda:ap-northeast-3:017000801446:layer:AWSLambdaPowertoolsPythonV3-python310-arm64:12**{: .copyMe}:clipboard: |
+ | **`ap-south-1`** | **arn:aws:lambda:ap-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python310-arm64:12**{: .copyMe}:clipboard: |
+ | **`ap-south-2`** | **arn:aws:lambda:ap-south-2:017000801446:layer:AWSLambdaPowertoolsPythonV3-python310-arm64:12**{: .copyMe}:clipboard: |
+ | **`ap-southeast-1`** | **arn:aws:lambda:ap-southeast-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python310-arm64:12**{: .copyMe}:clipboard: |
+ | **`ap-southeast-2`** | **arn:aws:lambda:ap-southeast-2:017000801446:layer:AWSLambdaPowertoolsPythonV3-python310-arm64:12**{: .copyMe}:clipboard: |
+ | **`ap-southeast-3`** | **arn:aws:lambda:ap-southeast-3:017000801446:layer:AWSLambdaPowertoolsPythonV3-python310-arm64:12**{: .copyMe}:clipboard: |
+ | **`ap-southeast-4`** | **arn:aws:lambda:ap-southeast-4:017000801446:layer:AWSLambdaPowertoolsPythonV3-python310-arm64:12**{: .copyMe}:clipboard: |
+ | **`ap-southeast-5`** | **arn:aws:lambda:ap-southeast-5:017000801446:layer:AWSLambdaPowertoolsPythonV3-python310-arm64:12**{: .copyMe}:clipboard: |
+ | **`ap-southeast-7`** | **arn:aws:lambda:ap-southeast-7:017000801446:layer:AWSLambdaPowertoolsPythonV3-python310-arm64:12**{: .copyMe}:clipboard: |
+ | **`ca-central-1`** | **arn:aws:lambda:ca-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python310-arm64:12**{: .copyMe}:clipboard: |
+ | **`eu-central-1`** | **arn:aws:lambda:eu-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python310-arm64:12**{: .copyMe}:clipboard: |
+ | **`eu-central-2`** | **arn:aws:lambda:eu-central-2:017000801446:layer:AWSLambdaPowertoolsPythonV3-python310-arm64:12**{: .copyMe}:clipboard: |
+ | **`eu-north-1`** | **arn:aws:lambda:eu-north-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python310-arm64:12**{: .copyMe}:clipboard: |
+ | **`eu-south-1`** | **arn:aws:lambda:eu-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python310-arm64:12**{: .copyMe}:clipboard: |
+ | **`eu-south-2`** | **arn:aws:lambda:eu-south-2:017000801446:layer:AWSLambdaPowertoolsPythonV3-python310-arm64:12**{: .copyMe}:clipboard: |
+ | **`eu-west-1`** | **arn:aws:lambda:eu-west-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python310-arm64:12**{: .copyMe}:clipboard: |
+ | **`eu-west-2`** | **arn:aws:lambda:eu-west-2:017000801446:layer:AWSLambdaPowertoolsPythonV3-python310-arm64:12**{: .copyMe}:clipboard: |
+ | **`eu-west-3`** | **arn:aws:lambda:eu-west-3:017000801446:layer:AWSLambdaPowertoolsPythonV3-python310-arm64:12**{: .copyMe}:clipboard: |
+ | **`il-central-1`** | **arn:aws:lambda:il-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python310-arm64:12**{: .copyMe}:clipboard: |
+ | **`me-central-1`** | **arn:aws:lambda:me-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python310-arm64:12**{: .copyMe}:clipboard: |
+ | **`me-south-1`** | **arn:aws:lambda:me-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python310-arm64:12**{: .copyMe}:clipboard: |
+ | **`mx-central-1`** | **arn:aws:lambda:mx-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python310-arm64:12**{: .copyMe}:clipboard: |
+ | **`sa-east-1`** | **arn:aws:lambda:sa-east-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python310-arm64:12**{: .copyMe}:clipboard: |
+ | **`us-east-1`** | **arn:aws:lambda:us-east-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python310-arm64:12**{: .copyMe}:clipboard: |
+ | **`us-east-2`** | **arn:aws:lambda:us-east-2:017000801446:layer:AWSLambdaPowertoolsPythonV3-python310-arm64:12**{: .copyMe}:clipboard: |
+ | **`us-west-1`** | **arn:aws:lambda:us-west-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python310-arm64:12**{: .copyMe}:clipboard: |
+ | **`us-west-2`** | **arn:aws:lambda:us-west-2:017000801446:layer:AWSLambdaPowertoolsPythonV3-python310-arm64:12**{: .copyMe}:clipboard: |
=== "Python 3.11"
| Region | Layer ARN |
| -------------------- | --------------------------------------------------------------------------------------------------------------- |
- | **`af-south-1`** | **arn:aws:lambda:af-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python311-arm64:11**{: .copyMe}:clipboard: |
- | **`ap-east-1`** | **arn:aws:lambda:ap-east-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python311-arm64:11**{: .copyMe}:clipboard: |
- | **`ap-northeast-1`** | **arn:aws:lambda:ap-northeast-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python311-arm64:11**{: .copyMe}:clipboard: |
- | **`ap-northeast-2`** | **arn:aws:lambda:ap-northeast-2:017000801446:layer:AWSLambdaPowertoolsPythonV3-python311-arm64:11**{: .copyMe}:clipboard: |
- | **`ap-northeast-3`** | **arn:aws:lambda:ap-northeast-3:017000801446:layer:AWSLambdaPowertoolsPythonV3-python311-arm64:11**{: .copyMe}:clipboard: |
- | **`ap-south-1`** | **arn:aws:lambda:ap-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python311-arm64:11**{: .copyMe}:clipboard: |
- | **`ap-south-2`** | **arn:aws:lambda:ap-south-2:017000801446:layer:AWSLambdaPowertoolsPythonV3-python311-arm64:11**{: .copyMe}:clipboard: |
- | **`ap-southeast-1`** | **arn:aws:lambda:ap-southeast-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python311-arm64:11**{: .copyMe}:clipboard: |
- | **`ap-southeast-2`** | **arn:aws:lambda:ap-southeast-2:017000801446:layer:AWSLambdaPowertoolsPythonV3-python311-arm64:11**{: .copyMe}:clipboard: |
- | **`ap-southeast-3`** | **arn:aws:lambda:ap-southeast-3:017000801446:layer:AWSLambdaPowertoolsPythonV3-python311-arm64:11**{: .copyMe}:clipboard: |
- | **`ap-southeast-4`** | **arn:aws:lambda:ap-southeast-4:017000801446:layer:AWSLambdaPowertoolsPythonV3-python311-arm64:11**{: .copyMe}:clipboard: |
- | **`ap-southeast-5`** | **arn:aws:lambda:ap-southeast-5:017000801446:layer:AWSLambdaPowertoolsPythonV3-python311-arm64:11**{: .copyMe}:clipboard: |
- | **`ap-southeast-7`** | **arn:aws:lambda:ap-southeast-7:017000801446:layer:AWSLambdaPowertoolsPythonV3-python311-arm64:11**{: .copyMe}:clipboard: |
- | **`ca-central-1`** | **arn:aws:lambda:ca-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python311-arm64:11**{: .copyMe}:clipboard: |
- | **`eu-central-1`** | **arn:aws:lambda:eu-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python311-arm64:11**{: .copyMe}:clipboard: |
- | **`eu-central-2`** | **arn:aws:lambda:eu-central-2:017000801446:layer:AWSLambdaPowertoolsPythonV3-python311-arm64:11**{: .copyMe}:clipboard: |
- | **`eu-north-1`** | **arn:aws:lambda:eu-north-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python311-arm64:11**{: .copyMe}:clipboard: |
- | **`eu-south-1`** | **arn:aws:lambda:eu-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python311-arm64:11**{: .copyMe}:clipboard: |
- | **`eu-south-2`** | **arn:aws:lambda:eu-south-2:017000801446:layer:AWSLambdaPowertoolsPythonV3-python311-arm64:11**{: .copyMe}:clipboard: |
- | **`eu-west-1`** | **arn:aws:lambda:eu-west-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python311-arm64:11**{: .copyMe}:clipboard: |
- | **`eu-west-2`** | **arn:aws:lambda:eu-west-2:017000801446:layer:AWSLambdaPowertoolsPythonV3-python311-arm64:11**{: .copyMe}:clipboard: |
- | **`eu-west-3`** | **arn:aws:lambda:eu-west-3:017000801446:layer:AWSLambdaPowertoolsPythonV3-python311-arm64:11**{: .copyMe}:clipboard: |
- | **`il-central-1`** | **arn:aws:lambda:il-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python311-arm64:11**{: .copyMe}:clipboard: |
- | **`me-central-1`** | **arn:aws:lambda:me-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python311-arm64:11**{: .copyMe}:clipboard: |
- | **`me-south-1`** | **arn:aws:lambda:me-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python311-arm64:11**{: .copyMe}:clipboard: |
- | **`mx-central-1`** | **arn:aws:lambda:mx-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python311-arm64:11**{: .copyMe}:clipboard: |
- | **`sa-east-1`** | **arn:aws:lambda:sa-east-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python311-arm64:11**{: .copyMe}:clipboard: |
- | **`us-east-1`** | **arn:aws:lambda:us-east-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python311-arm64:11**{: .copyMe}:clipboard: |
- | **`us-east-2`** | **arn:aws:lambda:us-east-2:017000801446:layer:AWSLambdaPowertoolsPythonV3-python311-arm64:11**{: .copyMe}:clipboard: |
- | **`us-west-1`** | **arn:aws:lambda:us-west-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python311-arm64:11**{: .copyMe}:clipboard: |
- | **`us-west-2`** | **arn:aws:lambda:us-west-2:017000801446:layer:AWSLambdaPowertoolsPythonV3-python311-arm64:11**{: .copyMe}:clipboard: |
+ | **`af-south-1`** | **arn:aws:lambda:af-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python311-arm64:12**{: .copyMe}:clipboard: |
+ | **`ap-east-1`** | **arn:aws:lambda:ap-east-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python311-arm64:12**{: .copyMe}:clipboard: |
+ | **`ap-northeast-1`** | **arn:aws:lambda:ap-northeast-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python311-arm64:12**{: .copyMe}:clipboard: |
+ | **`ap-northeast-2`** | **arn:aws:lambda:ap-northeast-2:017000801446:layer:AWSLambdaPowertoolsPythonV3-python311-arm64:12**{: .copyMe}:clipboard: |
+ | **`ap-northeast-3`** | **arn:aws:lambda:ap-northeast-3:017000801446:layer:AWSLambdaPowertoolsPythonV3-python311-arm64:12**{: .copyMe}:clipboard: |
+ | **`ap-south-1`** | **arn:aws:lambda:ap-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python311-arm64:12**{: .copyMe}:clipboard: |
+ | **`ap-south-2`** | **arn:aws:lambda:ap-south-2:017000801446:layer:AWSLambdaPowertoolsPythonV3-python311-arm64:12**{: .copyMe}:clipboard: |
+ | **`ap-southeast-1`** | **arn:aws:lambda:ap-southeast-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python311-arm64:12**{: .copyMe}:clipboard: |
+ | **`ap-southeast-2`** | **arn:aws:lambda:ap-southeast-2:017000801446:layer:AWSLambdaPowertoolsPythonV3-python311-arm64:12**{: .copyMe}:clipboard: |
+ | **`ap-southeast-3`** | **arn:aws:lambda:ap-southeast-3:017000801446:layer:AWSLambdaPowertoolsPythonV3-python311-arm64:12**{: .copyMe}:clipboard: |
+ | **`ap-southeast-4`** | **arn:aws:lambda:ap-southeast-4:017000801446:layer:AWSLambdaPowertoolsPythonV3-python311-arm64:12**{: .copyMe}:clipboard: |
+ | **`ap-southeast-5`** | **arn:aws:lambda:ap-southeast-5:017000801446:layer:AWSLambdaPowertoolsPythonV3-python311-arm64:12**{: .copyMe}:clipboard: |
+ | **`ap-southeast-7`** | **arn:aws:lambda:ap-southeast-7:017000801446:layer:AWSLambdaPowertoolsPythonV3-python311-arm64:12**{: .copyMe}:clipboard: |
+ | **`ca-central-1`** | **arn:aws:lambda:ca-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python311-arm64:12**{: .copyMe}:clipboard: |
+ | **`eu-central-1`** | **arn:aws:lambda:eu-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python311-arm64:12**{: .copyMe}:clipboard: |
+ | **`eu-central-2`** | **arn:aws:lambda:eu-central-2:017000801446:layer:AWSLambdaPowertoolsPythonV3-python311-arm64:12**{: .copyMe}:clipboard: |
+ | **`eu-north-1`** | **arn:aws:lambda:eu-north-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python311-arm64:12**{: .copyMe}:clipboard: |
+ | **`eu-south-1`** | **arn:aws:lambda:eu-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python311-arm64:12**{: .copyMe}:clipboard: |
+ | **`eu-south-2`** | **arn:aws:lambda:eu-south-2:017000801446:layer:AWSLambdaPowertoolsPythonV3-python311-arm64:12**{: .copyMe}:clipboard: |
+ | **`eu-west-1`** | **arn:aws:lambda:eu-west-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python311-arm64:12**{: .copyMe}:clipboard: |
+ | **`eu-west-2`** | **arn:aws:lambda:eu-west-2:017000801446:layer:AWSLambdaPowertoolsPythonV3-python311-arm64:12**{: .copyMe}:clipboard: |
+ | **`eu-west-3`** | **arn:aws:lambda:eu-west-3:017000801446:layer:AWSLambdaPowertoolsPythonV3-python311-arm64:12**{: .copyMe}:clipboard: |
+ | **`il-central-1`** | **arn:aws:lambda:il-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python311-arm64:12**{: .copyMe}:clipboard: |
+ | **`me-central-1`** | **arn:aws:lambda:me-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python311-arm64:12**{: .copyMe}:clipboard: |
+ | **`me-south-1`** | **arn:aws:lambda:me-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python311-arm64:12**{: .copyMe}:clipboard: |
+ | **`mx-central-1`** | **arn:aws:lambda:mx-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python311-arm64:12**{: .copyMe}:clipboard: |
+ | **`sa-east-1`** | **arn:aws:lambda:sa-east-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python311-arm64:12**{: .copyMe}:clipboard: |
+ | **`us-east-1`** | **arn:aws:lambda:us-east-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python311-arm64:12**{: .copyMe}:clipboard: |
+ | **`us-east-2`** | **arn:aws:lambda:us-east-2:017000801446:layer:AWSLambdaPowertoolsPythonV3-python311-arm64:12**{: .copyMe}:clipboard: |
+ | **`us-west-1`** | **arn:aws:lambda:us-west-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python311-arm64:12**{: .copyMe}:clipboard: |
+ | **`us-west-2`** | **arn:aws:lambda:us-west-2:017000801446:layer:AWSLambdaPowertoolsPythonV3-python311-arm64:12**{: .copyMe}:clipboard: |
=== "Python 3.12"
| Region | Layer ARN |
| -------------------- | --------------------------------------------------------------------------------------------------------------- |
- | **`af-south-1`** | **arn:aws:lambda:af-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python312-arm64:11**{: .copyMe}:clipboard: |
- | **`ap-east-1`** | **arn:aws:lambda:ap-east-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python312-arm64:11**{: .copyMe}:clipboard: |
- | **`ap-northeast-1`** | **arn:aws:lambda:ap-northeast-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python312-arm64:11**{: .copyMe}:clipboard: |
- | **`ap-northeast-2`** | **arn:aws:lambda:ap-northeast-2:017000801446:layer:AWSLambdaPowertoolsPythonV3-python312-arm64:11**{: .copyMe}:clipboard: |
- | **`ap-northeast-3`** | **arn:aws:lambda:ap-northeast-3:017000801446:layer:AWSLambdaPowertoolsPythonV3-python312-arm64:11**{: .copyMe}:clipboard: |
- | **`ap-south-1`** | **arn:aws:lambda:ap-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python312-arm64:11**{: .copyMe}:clipboard: |
- | **`ap-south-2`** | **arn:aws:lambda:ap-south-2:017000801446:layer:AWSLambdaPowertoolsPythonV3-python312-arm64:11**{: .copyMe}:clipboard: |
- | **`ap-southeast-1`** | **arn:aws:lambda:ap-southeast-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python312-arm64:11**{: .copyMe}:clipboard: |
- | **`ap-southeast-2`** | **arn:aws:lambda:ap-southeast-2:017000801446:layer:AWSLambdaPowertoolsPythonV3-python312-arm64:11**{: .copyMe}:clipboard: |
- | **`ap-southeast-3`** | **arn:aws:lambda:ap-southeast-3:017000801446:layer:AWSLambdaPowertoolsPythonV3-python312-arm64:11**{: .copyMe}:clipboard: |
- | **`ap-southeast-4`** | **arn:aws:lambda:ap-southeast-4:017000801446:layer:AWSLambdaPowertoolsPythonV3-python312-arm64:11**{: .copyMe}:clipboard: |
- | **`ap-southeast-5`** | **arn:aws:lambda:ap-southeast-5:017000801446:layer:AWSLambdaPowertoolsPythonV3-python312-arm64:11**{: .copyMe}:clipboard: |
- | **`ap-southeast-7`** | **arn:aws:lambda:ap-southeast-7:017000801446:layer:AWSLambdaPowertoolsPythonV3-python312-arm64:11**{: .copyMe}:clipboard: |
- | **`ca-central-1`** | **arn:aws:lambda:ca-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python312-arm64:11**{: .copyMe}:clipboard: |
- | **`eu-central-1`** | **arn:aws:lambda:eu-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python312-arm64:11**{: .copyMe}:clipboard: |
- | **`eu-central-2`** | **arn:aws:lambda:eu-central-2:017000801446:layer:AWSLambdaPowertoolsPythonV3-python312-arm64:11**{: .copyMe}:clipboard: |
- | **`eu-north-1`** | **arn:aws:lambda:eu-north-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python312-arm64:11**{: .copyMe}:clipboard: |
- | **`eu-south-1`** | **arn:aws:lambda:eu-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python312-arm64:11**{: .copyMe}:clipboard: |
- | **`eu-south-2`** | **arn:aws:lambda:eu-south-2:017000801446:layer:AWSLambdaPowertoolsPythonV3-python312-arm64:11**{: .copyMe}:clipboard: |
- | **`eu-west-1`** | **arn:aws:lambda:eu-west-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python312-arm64:11**{: .copyMe}:clipboard: |
- | **`eu-west-2`** | **arn:aws:lambda:eu-west-2:017000801446:layer:AWSLambdaPowertoolsPythonV3-python312-arm64:11**{: .copyMe}:clipboard: |
- | **`eu-west-3`** | **arn:aws:lambda:eu-west-3:017000801446:layer:AWSLambdaPowertoolsPythonV3-python312-arm64:11**{: .copyMe}:clipboard: |
- | **`il-central-1`** | **arn:aws:lambda:il-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python312-arm64:11**{: .copyMe}:clipboard: |
- | **`me-central-1`** | **arn:aws:lambda:me-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python312-arm64:11**{: .copyMe}:clipboard: |
- | **`me-south-1`** | **arn:aws:lambda:me-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python312-arm64:11**{: .copyMe}:clipboard: |
- | **`mx-central-1`** | **arn:aws:lambda:mx-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python312-arm64:11**{: .copyMe}:clipboard: |
- | **`sa-east-1`** | **arn:aws:lambda:sa-east-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python312-arm64:11**{: .copyMe}:clipboard: |
- | **`us-east-1`** | **arn:aws:lambda:us-east-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python312-arm64:11**{: .copyMe}:clipboard: |
- | **`us-east-2`** | **arn:aws:lambda:us-east-2:017000801446:layer:AWSLambdaPowertoolsPythonV3-python312-arm64:11**{: .copyMe}:clipboard: |
- | **`us-west-1`** | **arn:aws:lambda:us-west-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python312-arm64:11**{: .copyMe}:clipboard: |
- | **`us-west-2`** | **arn:aws:lambda:us-west-2:017000801446:layer:AWSLambdaPowertoolsPythonV3-python312-arm64:11**{: .copyMe}:clipboard: |
+ | **`af-south-1`** | **arn:aws:lambda:af-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python312-arm64:12**{: .copyMe}:clipboard: |
+ | **`ap-east-1`** | **arn:aws:lambda:ap-east-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python312-arm64:12**{: .copyMe}:clipboard: |
+ | **`ap-northeast-1`** | **arn:aws:lambda:ap-northeast-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python312-arm64:12**{: .copyMe}:clipboard: |
+ | **`ap-northeast-2`** | **arn:aws:lambda:ap-northeast-2:017000801446:layer:AWSLambdaPowertoolsPythonV3-python312-arm64:12**{: .copyMe}:clipboard: |
+ | **`ap-northeast-3`** | **arn:aws:lambda:ap-northeast-3:017000801446:layer:AWSLambdaPowertoolsPythonV3-python312-arm64:12**{: .copyMe}:clipboard: |
+ | **`ap-south-1`** | **arn:aws:lambda:ap-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python312-arm64:12**{: .copyMe}:clipboard: |
+ | **`ap-south-2`** | **arn:aws:lambda:ap-south-2:017000801446:layer:AWSLambdaPowertoolsPythonV3-python312-arm64:12**{: .copyMe}:clipboard: |
+ | **`ap-southeast-1`** | **arn:aws:lambda:ap-southeast-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python312-arm64:12**{: .copyMe}:clipboard: |
+ | **`ap-southeast-2`** | **arn:aws:lambda:ap-southeast-2:017000801446:layer:AWSLambdaPowertoolsPythonV3-python312-arm64:12**{: .copyMe}:clipboard: |
+ | **`ap-southeast-3`** | **arn:aws:lambda:ap-southeast-3:017000801446:layer:AWSLambdaPowertoolsPythonV3-python312-arm64:12**{: .copyMe}:clipboard: |
+ | **`ap-southeast-4`** | **arn:aws:lambda:ap-southeast-4:017000801446:layer:AWSLambdaPowertoolsPythonV3-python312-arm64:12**{: .copyMe}:clipboard: |
+ | **`ap-southeast-5`** | **arn:aws:lambda:ap-southeast-5:017000801446:layer:AWSLambdaPowertoolsPythonV3-python312-arm64:12**{: .copyMe}:clipboard: |
+ | **`ap-southeast-7`** | **arn:aws:lambda:ap-southeast-7:017000801446:layer:AWSLambdaPowertoolsPythonV3-python312-arm64:12**{: .copyMe}:clipboard: |
+ | **`ca-central-1`** | **arn:aws:lambda:ca-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python312-arm64:12**{: .copyMe}:clipboard: |
+ | **`eu-central-1`** | **arn:aws:lambda:eu-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python312-arm64:12**{: .copyMe}:clipboard: |
+ | **`eu-central-2`** | **arn:aws:lambda:eu-central-2:017000801446:layer:AWSLambdaPowertoolsPythonV3-python312-arm64:12**{: .copyMe}:clipboard: |
+ | **`eu-north-1`** | **arn:aws:lambda:eu-north-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python312-arm64:12**{: .copyMe}:clipboard: |
+ | **`eu-south-1`** | **arn:aws:lambda:eu-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python312-arm64:12**{: .copyMe}:clipboard: |
+ | **`eu-south-2`** | **arn:aws:lambda:eu-south-2:017000801446:layer:AWSLambdaPowertoolsPythonV3-python312-arm64:12**{: .copyMe}:clipboard: |
+ | **`eu-west-1`** | **arn:aws:lambda:eu-west-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python312-arm64:12**{: .copyMe}:clipboard: |
+ | **`eu-west-2`** | **arn:aws:lambda:eu-west-2:017000801446:layer:AWSLambdaPowertoolsPythonV3-python312-arm64:12**{: .copyMe}:clipboard: |
+ | **`eu-west-3`** | **arn:aws:lambda:eu-west-3:017000801446:layer:AWSLambdaPowertoolsPythonV3-python312-arm64:12**{: .copyMe}:clipboard: |
+ | **`il-central-1`** | **arn:aws:lambda:il-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python312-arm64:12**{: .copyMe}:clipboard: |
+ | **`me-central-1`** | **arn:aws:lambda:me-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python312-arm64:12**{: .copyMe}:clipboard: |
+ | **`me-south-1`** | **arn:aws:lambda:me-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python312-arm64:12**{: .copyMe}:clipboard: |
+ | **`mx-central-1`** | **arn:aws:lambda:mx-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python312-arm64:12**{: .copyMe}:clipboard: |
+ | **`sa-east-1`** | **arn:aws:lambda:sa-east-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python312-arm64:12**{: .copyMe}:clipboard: |
+ | **`us-east-1`** | **arn:aws:lambda:us-east-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python312-arm64:12**{: .copyMe}:clipboard: |
+ | **`us-east-2`** | **arn:aws:lambda:us-east-2:017000801446:layer:AWSLambdaPowertoolsPythonV3-python312-arm64:12**{: .copyMe}:clipboard: |
+ | **`us-west-1`** | **arn:aws:lambda:us-west-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python312-arm64:12**{: .copyMe}:clipboard: |
+ | **`us-west-2`** | **arn:aws:lambda:us-west-2:017000801446:layer:AWSLambdaPowertoolsPythonV3-python312-arm64:12**{: .copyMe}:clipboard: |
=== "Python 3.13"
| Region | Layer ARN |
| -------------------- | --------------------------------------------------------------------------------------------------------------- |
- | **`af-south-1`** | **arn:aws:lambda:af-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python313-arm64:11**{: .copyMe}:clipboard: |
- | **`ap-east-1`** | **arn:aws:lambda:ap-east-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python313-arm64:11**{: .copyMe}:clipboard: |
- | **`ap-northeast-1`** | **arn:aws:lambda:ap-northeast-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python313-arm64:11**{: .copyMe}:clipboard: |
- | **`ap-northeast-2`** | **arn:aws:lambda:ap-northeast-2:017000801446:layer:AWSLambdaPowertoolsPythonV3-python313-arm64:11**{: .copyMe}:clipboard: |
- | **`ap-northeast-3`** | **arn:aws:lambda:ap-northeast-3:017000801446:layer:AWSLambdaPowertoolsPythonV3-python313-arm64:11**{: .copyMe}:clipboard: |
- | **`ap-south-1`** | **arn:aws:lambda:ap-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python313-arm64:11**{: .copyMe}:clipboard: |
- | **`ap-south-2`** | **arn:aws:lambda:ap-south-2:017000801446:layer:AWSLambdaPowertoolsPythonV3-python313-arm64:11**{: .copyMe}:clipboard: |
- | **`ap-southeast-1`** | **arn:aws:lambda:ap-southeast-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python313-arm64:11**{: .copyMe}:clipboard: |
- | **`ap-southeast-2`** | **arn:aws:lambda:ap-southeast-2:017000801446:layer:AWSLambdaPowertoolsPythonV3-python313-arm64:11**{: .copyMe}:clipboard: |
- | **`ap-southeast-3`** | **arn:aws:lambda:ap-southeast-3:017000801446:layer:AWSLambdaPowertoolsPythonV3-python313-arm64:11**{: .copyMe}:clipboard: |
- | **`ap-southeast-4`** | **arn:aws:lambda:ap-southeast-4:017000801446:layer:AWSLambdaPowertoolsPythonV3-python313-arm64:11**{: .copyMe}:clipboard: |
- | **`ap-southeast-5`** | **arn:aws:lambda:ap-southeast-5:017000801446:layer:AWSLambdaPowertoolsPythonV3-python313-arm64:11**{: .copyMe}:clipboard: |
- | **`ap-southeast-7`** | **arn:aws:lambda:ap-southeast-7:017000801446:layer:AWSLambdaPowertoolsPythonV3-python313-arm64:11**{: .copyMe}:clipboard: |
- | **`ca-central-1`** | **arn:aws:lambda:ca-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python313-arm64:11**{: .copyMe}:clipboard: |
- | **`eu-central-1`** | **arn:aws:lambda:eu-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python313-arm64:11**{: .copyMe}:clipboard: |
- | **`eu-central-2`** | **arn:aws:lambda:eu-central-2:017000801446:layer:AWSLambdaPowertoolsPythonV3-python313-arm64:11**{: .copyMe}:clipboard: |
- | **`eu-north-1`** | **arn:aws:lambda:eu-north-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python313-arm64:11**{: .copyMe}:clipboard: |
- | **`eu-south-1`** | **arn:aws:lambda:eu-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python313-arm64:11**{: .copyMe}:clipboard: |
- | **`eu-south-2`** | **arn:aws:lambda:eu-south-2:017000801446:layer:AWSLambdaPowertoolsPythonV3-python313-arm64:11**{: .copyMe}:clipboard: |
- | **`eu-west-1`** | **arn:aws:lambda:eu-west-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python313-arm64:11**{: .copyMe}:clipboard: |
- | **`eu-west-2`** | **arn:aws:lambda:eu-west-2:017000801446:layer:AWSLambdaPowertoolsPythonV3-python313-arm64:11**{: .copyMe}:clipboard: |
- | **`eu-west-3`** | **arn:aws:lambda:eu-west-3:017000801446:layer:AWSLambdaPowertoolsPythonV3-python313-arm64:11**{: .copyMe}:clipboard: |
- | **`il-central-1`** | **arn:aws:lambda:il-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python313-arm64:11**{: .copyMe}:clipboard: |
- | **`me-central-1`** | **arn:aws:lambda:me-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python313-arm64:11**{: .copyMe}:clipboard: |
- | **`me-south-1`** | **arn:aws:lambda:me-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python313-arm64:11**{: .copyMe}:clipboard: |
- | **`mx-central-1`** | **arn:aws:lambda:mx-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python313-arm64:11**{: .copyMe}:clipboard: |
- | **`sa-east-1`** | **arn:aws:lambda:sa-east-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python313-arm64:11**{: .copyMe}:clipboard: |
- | **`us-east-1`** | **arn:aws:lambda:us-east-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python313-arm64:11**{: .copyMe}:clipboard: |
- | **`us-east-2`** | **arn:aws:lambda:us-east-2:017000801446:layer:AWSLambdaPowertoolsPythonV3-python313-arm64:11**{: .copyMe}:clipboard: |
- | **`us-west-1`** | **arn:aws:lambda:us-west-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python313-arm64:11**{: .copyMe}:clipboard: |
- | **`us-west-2`** | **arn:aws:lambda:us-west-2:017000801446:layer:AWSLambdaPowertoolsPythonV3-python313-arm64:11**{: .copyMe}:clipboard: |
+ | **`af-south-1`** | **arn:aws:lambda:af-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python313-arm64:12**{: .copyMe}:clipboard: |
+ | **`ap-east-1`** | **arn:aws:lambda:ap-east-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python313-arm64:12**{: .copyMe}:clipboard: |
+ | **`ap-northeast-1`** | **arn:aws:lambda:ap-northeast-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python313-arm64:12**{: .copyMe}:clipboard: |
+ | **`ap-northeast-2`** | **arn:aws:lambda:ap-northeast-2:017000801446:layer:AWSLambdaPowertoolsPythonV3-python313-arm64:12**{: .copyMe}:clipboard: |
+ | **`ap-northeast-3`** | **arn:aws:lambda:ap-northeast-3:017000801446:layer:AWSLambdaPowertoolsPythonV3-python313-arm64:12**{: .copyMe}:clipboard: |
+ | **`ap-south-1`** | **arn:aws:lambda:ap-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python313-arm64:12**{: .copyMe}:clipboard: |
+ | **`ap-south-2`** | **arn:aws:lambda:ap-south-2:017000801446:layer:AWSLambdaPowertoolsPythonV3-python313-arm64:12**{: .copyMe}:clipboard: |
+ | **`ap-southeast-1`** | **arn:aws:lambda:ap-southeast-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python313-arm64:12**{: .copyMe}:clipboard: |
+ | **`ap-southeast-2`** | **arn:aws:lambda:ap-southeast-2:017000801446:layer:AWSLambdaPowertoolsPythonV3-python313-arm64:12**{: .copyMe}:clipboard: |
+ | **`ap-southeast-3`** | **arn:aws:lambda:ap-southeast-3:017000801446:layer:AWSLambdaPowertoolsPythonV3-python313-arm64:12**{: .copyMe}:clipboard: |
+ | **`ap-southeast-4`** | **arn:aws:lambda:ap-southeast-4:017000801446:layer:AWSLambdaPowertoolsPythonV3-python313-arm64:12**{: .copyMe}:clipboard: |
+ | **`ap-southeast-5`** | **arn:aws:lambda:ap-southeast-5:017000801446:layer:AWSLambdaPowertoolsPythonV3-python313-arm64:12**{: .copyMe}:clipboard: |
+ | **`ap-southeast-7`** | **arn:aws:lambda:ap-southeast-7:017000801446:layer:AWSLambdaPowertoolsPythonV3-python313-arm64:12**{: .copyMe}:clipboard: |
+ | **`ca-central-1`** | **arn:aws:lambda:ca-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python313-arm64:12**{: .copyMe}:clipboard: |
+ | **`eu-central-1`** | **arn:aws:lambda:eu-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python313-arm64:12**{: .copyMe}:clipboard: |
+ | **`eu-central-2`** | **arn:aws:lambda:eu-central-2:017000801446:layer:AWSLambdaPowertoolsPythonV3-python313-arm64:12**{: .copyMe}:clipboard: |
+ | **`eu-north-1`** | **arn:aws:lambda:eu-north-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python313-arm64:12**{: .copyMe}:clipboard: |
+ | **`eu-south-1`** | **arn:aws:lambda:eu-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python313-arm64:12**{: .copyMe}:clipboard: |
+ | **`eu-south-2`** | **arn:aws:lambda:eu-south-2:017000801446:layer:AWSLambdaPowertoolsPythonV3-python313-arm64:12**{: .copyMe}:clipboard: |
+ | **`eu-west-1`** | **arn:aws:lambda:eu-west-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python313-arm64:12**{: .copyMe}:clipboard: |
+ | **`eu-west-2`** | **arn:aws:lambda:eu-west-2:017000801446:layer:AWSLambdaPowertoolsPythonV3-python313-arm64:12**{: .copyMe}:clipboard: |
+ | **`eu-west-3`** | **arn:aws:lambda:eu-west-3:017000801446:layer:AWSLambdaPowertoolsPythonV3-python313-arm64:12**{: .copyMe}:clipboard: |
+ | **`il-central-1`** | **arn:aws:lambda:il-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python313-arm64:12**{: .copyMe}:clipboard: |
+ | **`me-central-1`** | **arn:aws:lambda:me-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python313-arm64:12**{: .copyMe}:clipboard: |
+ | **`me-south-1`** | **arn:aws:lambda:me-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python313-arm64:12**{: .copyMe}:clipboard: |
+ | **`mx-central-1`** | **arn:aws:lambda:mx-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python313-arm64:12**{: .copyMe}:clipboard: |
+ | **`sa-east-1`** | **arn:aws:lambda:sa-east-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python313-arm64:12**{: .copyMe}:clipboard: |
+ | **`us-east-1`** | **arn:aws:lambda:us-east-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python313-arm64:12**{: .copyMe}:clipboard: |
+ | **`us-east-2`** | **arn:aws:lambda:us-east-2:017000801446:layer:AWSLambdaPowertoolsPythonV3-python313-arm64:12**{: .copyMe}:clipboard: |
+ | **`us-west-1`** | **arn:aws:lambda:us-west-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python313-arm64:12**{: .copyMe}:clipboard: |
+ | **`us-west-2`** | **arn:aws:lambda:us-west-2:017000801446:layer:AWSLambdaPowertoolsPythonV3-python313-arm64:12**{: .copyMe}:clipboard: |
diff --git a/docs/includes/_layer_homepage_x86.md b/docs/includes/_layer_homepage_x86.md
index f6a3d4784ed..01bafda7bfd 100644
--- a/docs/includes/_layer_homepage_x86.md
+++ b/docs/includes/_layer_homepage_x86.md
@@ -5,183 +5,183 @@
| Region | Layer ARN |
| -------------------- | --------------------------------------------------------------------------------------------------------- |
- | **`af-south-1`** | **arn:aws:lambda:af-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python39-x86_64:11**{: .copyMe}:clipboard: |
- | **`ap-east-1`** | **arn:aws:lambda:ap-east-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python39-x86_64:11**{: .copyMe}:clipboard: |
- | **`ap-northeast-1`** | **arn:aws:lambda:ap-northeast-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python39-x86_64:11**{: .copyMe}:clipboard: |
- | **`ap-northeast-2`** | **arn:aws:lambda:ap-northeast-2:017000801446:layer:AWSLambdaPowertoolsPythonV3-python39-x86_64:11**{: .copyMe}:clipboard: |
- | **`ap-northeast-3`** | **arn:aws:lambda:ap-northeast-3:017000801446:layer:AWSLambdaPowertoolsPythonV3-python39-x86_64:11**{: .copyMe}:clipboard: |
- | **`ap-south-1`** | **arn:aws:lambda:ap-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python39-x86_64:11**{: .copyMe}:clipboard: |
- | **`ap-south-2`** | **arn:aws:lambda:ap-south-2:017000801446:layer:AWSLambdaPowertoolsPythonV3-python39-x86_64:11**{: .copyMe}:clipboard: |
- | **`ap-southeast-1`** | **arn:aws:lambda:ap-southeast-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python39-x86_64:11**{: .copyMe}:clipboard: |
- | **`ap-southeast-2`** | **arn:aws:lambda:ap-southeast-2:017000801446:layer:AWSLambdaPowertoolsPythonV3-python39-x86_64:11**{: .copyMe}:clipboard: |
- | **`ap-southeast-3`** | **arn:aws:lambda:ap-southeast-3:017000801446:layer:AWSLambdaPowertoolsPythonV3-python39-x86_64:11**{: .copyMe}:clipboard: |
- | **`ap-southeast-4`** | **arn:aws:lambda:ap-southeast-4:017000801446:layer:AWSLambdaPowertoolsPythonV3-python39-x86_64:11**{: .copyMe}:clipboard: |
- | **`ap-southeast-5`** | **arn:aws:lambda:ap-southeast-5:017000801446:layer:AWSLambdaPowertoolsPythonV3-python39-x86_64:11**{: .copyMe}:clipboard: |
- | **`ap-southeast-7`** | **arn:aws:lambda:ap-southeast-7:017000801446:layer:AWSLambdaPowertoolsPythonV3-python39-x86_64:11**{: .copyMe}:clipboard: |
- | **`ca-central-1`** | **arn:aws:lambda:ca-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python39-x86_64:11**{: .copyMe}:clipboard: |
- | **`ca-west-1`** | **arn:aws:lambda:ca-west-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python39-x86_64:11**{: .copyMe}:clipboard: |
- | **`eu-central-1`** | **arn:aws:lambda:eu-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python39-x86_64:11**{: .copyMe}:clipboard: |
- | **`eu-central-2`** | **arn:aws:lambda:eu-central-2:017000801446:layer:AWSLambdaPowertoolsPythonV3-python39-x86_64:11**{: .copyMe}:clipboard: |
- | **`eu-north-1`** | **arn:aws:lambda:eu-north-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python39-x86_64:11**{: .copyMe}:clipboard: |
- | **`eu-south-1`** | **arn:aws:lambda:eu-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python39-x86_64:11**{: .copyMe}:clipboard: |
- | **`eu-south-2`** | **arn:aws:lambda:eu-south-2:017000801446:layer:AWSLambdaPowertoolsPythonV3-python39-x86_64:11**{: .copyMe}:clipboard: |
- | **`eu-west-1`** | **arn:aws:lambda:eu-west-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python39-x86_64:11**{: .copyMe}:clipboard: |
- | **`eu-west-2`** | **arn:aws:lambda:eu-west-2:017000801446:layer:AWSLambdaPowertoolsPythonV3-python39-x86_64:11**{: .copyMe}:clipboard: |
- | **`eu-west-3`** | **arn:aws:lambda:eu-west-3:017000801446:layer:AWSLambdaPowertoolsPythonV3-python39-x86_64:11**{: .copyMe}:clipboard: |
- | **`il-central-1`** | **arn:aws:lambda:il-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python39-x86_64:11**{: .copyMe}:clipboard: |
- | **`me-central-1`** | **arn:aws:lambda:me-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python39-x86_64:11**{: .copyMe}:clipboard: |
- | **`me-south-1`** | **arn:aws:lambda:me-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python39-x86_64:11**{: .copyMe}:clipboard: |
- | **`mx-central-1`** | **arn:aws:lambda:mx-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python39-x86_64:11**{: .copyMe}:clipboard: |
- | **`sa-east-1`** | **arn:aws:lambda:sa-east-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python39-x86_64:11**{: .copyMe}:clipboard: |
- | **`us-east-1`** | **arn:aws:lambda:us-east-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python39-x86_64:11**{: .copyMe}:clipboard: |
- | **`us-east-2`** | **arn:aws:lambda:us-east-2:017000801446:layer:AWSLambdaPowertoolsPythonV3-python39-x86_64:11**{: .copyMe}:clipboard: |
- | **`us-west-1`** | **arn:aws:lambda:us-west-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python39-x86_64:11**{: .copyMe}:clipboard: |
- | **`us-west-2`** | **arn:aws:lambda:us-west-2:017000801446:layer:AWSLambdaPowertoolsPythonV3-python39-x86_64:11**{: .copyMe}:clipboard: |
+ | **`af-south-1`** | **arn:aws:lambda:af-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python39-x86_64:12**{: .copyMe}:clipboard: |
+ | **`ap-east-1`** | **arn:aws:lambda:ap-east-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python39-x86_64:12**{: .copyMe}:clipboard: |
+ | **`ap-northeast-1`** | **arn:aws:lambda:ap-northeast-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python39-x86_64:12**{: .copyMe}:clipboard: |
+ | **`ap-northeast-2`** | **arn:aws:lambda:ap-northeast-2:017000801446:layer:AWSLambdaPowertoolsPythonV3-python39-x86_64:12**{: .copyMe}:clipboard: |
+ | **`ap-northeast-3`** | **arn:aws:lambda:ap-northeast-3:017000801446:layer:AWSLambdaPowertoolsPythonV3-python39-x86_64:12**{: .copyMe}:clipboard: |
+ | **`ap-south-1`** | **arn:aws:lambda:ap-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python39-x86_64:12**{: .copyMe}:clipboard: |
+ | **`ap-south-2`** | **arn:aws:lambda:ap-south-2:017000801446:layer:AWSLambdaPowertoolsPythonV3-python39-x86_64:12**{: .copyMe}:clipboard: |
+ | **`ap-southeast-1`** | **arn:aws:lambda:ap-southeast-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python39-x86_64:12**{: .copyMe}:clipboard: |
+ | **`ap-southeast-2`** | **arn:aws:lambda:ap-southeast-2:017000801446:layer:AWSLambdaPowertoolsPythonV3-python39-x86_64:12**{: .copyMe}:clipboard: |
+ | **`ap-southeast-3`** | **arn:aws:lambda:ap-southeast-3:017000801446:layer:AWSLambdaPowertoolsPythonV3-python39-x86_64:12**{: .copyMe}:clipboard: |
+ | **`ap-southeast-4`** | **arn:aws:lambda:ap-southeast-4:017000801446:layer:AWSLambdaPowertoolsPythonV3-python39-x86_64:12**{: .copyMe}:clipboard: |
+ | **`ap-southeast-5`** | **arn:aws:lambda:ap-southeast-5:017000801446:layer:AWSLambdaPowertoolsPythonV3-python39-x86_64:12**{: .copyMe}:clipboard: |
+ | **`ap-southeast-7`** | **arn:aws:lambda:ap-southeast-7:017000801446:layer:AWSLambdaPowertoolsPythonV3-python39-x86_64:12**{: .copyMe}:clipboard: |
+ | **`ca-central-1`** | **arn:aws:lambda:ca-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python39-x86_64:12**{: .copyMe}:clipboard: |
+ | **`ca-west-1`** | **arn:aws:lambda:ca-west-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python39-x86_64:12**{: .copyMe}:clipboard: |
+ | **`eu-central-1`** | **arn:aws:lambda:eu-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python39-x86_64:12**{: .copyMe}:clipboard: |
+ | **`eu-central-2`** | **arn:aws:lambda:eu-central-2:017000801446:layer:AWSLambdaPowertoolsPythonV3-python39-x86_64:12**{: .copyMe}:clipboard: |
+ | **`eu-north-1`** | **arn:aws:lambda:eu-north-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python39-x86_64:12**{: .copyMe}:clipboard: |
+ | **`eu-south-1`** | **arn:aws:lambda:eu-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python39-x86_64:12**{: .copyMe}:clipboard: |
+ | **`eu-south-2`** | **arn:aws:lambda:eu-south-2:017000801446:layer:AWSLambdaPowertoolsPythonV3-python39-x86_64:12**{: .copyMe}:clipboard: |
+ | **`eu-west-1`** | **arn:aws:lambda:eu-west-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python39-x86_64:12**{: .copyMe}:clipboard: |
+ | **`eu-west-2`** | **arn:aws:lambda:eu-west-2:017000801446:layer:AWSLambdaPowertoolsPythonV3-python39-x86_64:12**{: .copyMe}:clipboard: |
+ | **`eu-west-3`** | **arn:aws:lambda:eu-west-3:017000801446:layer:AWSLambdaPowertoolsPythonV3-python39-x86_64:12**{: .copyMe}:clipboard: |
+ | **`il-central-1`** | **arn:aws:lambda:il-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python39-x86_64:12**{: .copyMe}:clipboard: |
+ | **`me-central-1`** | **arn:aws:lambda:me-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python39-x86_64:12**{: .copyMe}:clipboard: |
+ | **`me-south-1`** | **arn:aws:lambda:me-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python39-x86_64:12**{: .copyMe}:clipboard: |
+ | **`mx-central-1`** | **arn:aws:lambda:mx-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python39-x86_64:12**{: .copyMe}:clipboard: |
+ | **`sa-east-1`** | **arn:aws:lambda:sa-east-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python39-x86_64:12**{: .copyMe}:clipboard: |
+ | **`us-east-1`** | **arn:aws:lambda:us-east-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python39-x86_64:12**{: .copyMe}:clipboard: |
+ | **`us-east-2`** | **arn:aws:lambda:us-east-2:017000801446:layer:AWSLambdaPowertoolsPythonV3-python39-x86_64:12**{: .copyMe}:clipboard: |
+ | **`us-west-1`** | **arn:aws:lambda:us-west-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python39-x86_64:12**{: .copyMe}:clipboard: |
+ | **`us-west-2`** | **arn:aws:lambda:us-west-2:017000801446:layer:AWSLambdaPowertoolsPythonV3-python39-x86_64:12**{: .copyMe}:clipboard: |
=== "Python 3.10"
| Region | Layer ARN |
| -------------------- | --------------------------------------------------------------------------------------------------------- |
- | **`af-south-1`** | **arn:aws:lambda:af-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python310-x86_64:11**{: .copyMe}:clipboard: |
- | **`ap-east-1`** | **arn:aws:lambda:ap-east-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python310-x86_64:11**{: .copyMe}:clipboard: |
- | **`ap-northeast-1`** | **arn:aws:lambda:ap-northeast-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python310-x86_64:11**{: .copyMe}:clipboard: |
- | **`ap-northeast-2`** | **arn:aws:lambda:ap-northeast-2:017000801446:layer:AWSLambdaPowertoolsPythonV3-python310-x86_64:11**{: .copyMe}:clipboard: |
- | **`ap-northeast-3`** | **arn:aws:lambda:ap-northeast-3:017000801446:layer:AWSLambdaPowertoolsPythonV3-python310-x86_64:11**{: .copyMe}:clipboard: |
- | **`ap-south-1`** | **arn:aws:lambda:ap-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python310-x86_64:11**{: .copyMe}:clipboard: |
- | **`ap-south-2`** | **arn:aws:lambda:ap-south-2:017000801446:layer:AWSLambdaPowertoolsPythonV3-python310-x86_64:11**{: .copyMe}:clipboard: |
- | **`ap-southeast-1`** | **arn:aws:lambda:ap-southeast-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python310-x86_64:11**{: .copyMe}:clipboard: |
- | **`ap-southeast-2`** | **arn:aws:lambda:ap-southeast-2:017000801446:layer:AWSLambdaPowertoolsPythonV3-python310-x86_64:11**{: .copyMe}:clipboard: |
- | **`ap-southeast-3`** | **arn:aws:lambda:ap-southeast-3:017000801446:layer:AWSLambdaPowertoolsPythonV3-python310-x86_64:11**{: .copyMe}:clipboard: |
- | **`ap-southeast-4`** | **arn:aws:lambda:ap-southeast-4:017000801446:layer:AWSLambdaPowertoolsPythonV3-python310-x86_64:11**{: .copyMe}:clipboard: |
- | **`ap-southeast-5`** | **arn:aws:lambda:ap-southeast-5:017000801446:layer:AWSLambdaPowertoolsPythonV3-python310-x86_64:11**{: .copyMe}:clipboard: |
- | **`ap-southeast-7`** | **arn:aws:lambda:ap-southeast-7:017000801446:layer:AWSLambdaPowertoolsPythonV3-python310-x86_64:11**{: .copyMe}:clipboard: |
- | **`ca-central-1`** | **arn:aws:lambda:ca-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python310-x86_64:11**{: .copyMe}:clipboard: |
- | **`ca-west-1`** | **arn:aws:lambda:ca-west-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python310-x86_64:11**{: .copyMe}:clipboard: |
- | **`eu-central-1`** | **arn:aws:lambda:eu-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python310-x86_64:11**{: .copyMe}:clipboard: |
- | **`eu-central-2`** | **arn:aws:lambda:eu-central-2:017000801446:layer:AWSLambdaPowertoolsPythonV3-python310-x86_64:11**{: .copyMe}:clipboard: |
- | **`eu-north-1`** | **arn:aws:lambda:eu-north-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python310-x86_64:11**{: .copyMe}:clipboard: |
- | **`eu-south-1`** | **arn:aws:lambda:eu-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python310-x86_64:11**{: .copyMe}:clipboard: |
- | **`eu-south-2`** | **arn:aws:lambda:eu-south-2:017000801446:layer:AWSLambdaPowertoolsPythonV3-python310-x86_64:11**{: .copyMe}:clipboard: |
- | **`eu-west-1`** | **arn:aws:lambda:eu-west-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python310-x86_64:11**{: .copyMe}:clipboard: |
- | **`eu-west-2`** | **arn:aws:lambda:eu-west-2:017000801446:layer:AWSLambdaPowertoolsPythonV3-python310-x86_64:11**{: .copyMe}:clipboard: |
- | **`eu-west-3`** | **arn:aws:lambda:eu-west-3:017000801446:layer:AWSLambdaPowertoolsPythonV3-python310-x86_64:11**{: .copyMe}:clipboard: |
- | **`il-central-1`** | **arn:aws:lambda:il-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python310-x86_64:11**{: .copyMe}:clipboard: |
- | **`me-central-1`** | **arn:aws:lambda:me-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python310-x86_64:11**{: .copyMe}:clipboard: |
- | **`me-south-1`** | **arn:aws:lambda:me-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python310-x86_64:11**{: .copyMe}:clipboard: |
- | **`mx-central-1`** | **arn:aws:lambda:mx-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python310-x86_64:11**{: .copyMe}:clipboard: |
- | **`sa-east-1`** | **arn:aws:lambda:sa-east-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python310-x86_64:11**{: .copyMe}:clipboard: |
- | **`us-east-1`** | **arn:aws:lambda:us-east-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python310-x86_64:11**{: .copyMe}:clipboard: |
- | **`us-east-2`** | **arn:aws:lambda:us-east-2:017000801446:layer:AWSLambdaPowertoolsPythonV3-python310-x86_64:11**{: .copyMe}:clipboard: |
- | **`us-west-1`** | **arn:aws:lambda:us-west-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python310-x86_64:11**{: .copyMe}:clipboard: |
- | **`us-west-2`** | **arn:aws:lambda:us-west-2:017000801446:layer:AWSLambdaPowertoolsPythonV3-python310-x86_64:11**{: .copyMe}:clipboard: |
+ | **`af-south-1`** | **arn:aws:lambda:af-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python310-x86_64:12**{: .copyMe}:clipboard: |
+ | **`ap-east-1`** | **arn:aws:lambda:ap-east-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python310-x86_64:12**{: .copyMe}:clipboard: |
+ | **`ap-northeast-1`** | **arn:aws:lambda:ap-northeast-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python310-x86_64:12**{: .copyMe}:clipboard: |
+ | **`ap-northeast-2`** | **arn:aws:lambda:ap-northeast-2:017000801446:layer:AWSLambdaPowertoolsPythonV3-python310-x86_64:12**{: .copyMe}:clipboard: |
+ | **`ap-northeast-3`** | **arn:aws:lambda:ap-northeast-3:017000801446:layer:AWSLambdaPowertoolsPythonV3-python310-x86_64:12**{: .copyMe}:clipboard: |
+ | **`ap-south-1`** | **arn:aws:lambda:ap-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python310-x86_64:12**{: .copyMe}:clipboard: |
+ | **`ap-south-2`** | **arn:aws:lambda:ap-south-2:017000801446:layer:AWSLambdaPowertoolsPythonV3-python310-x86_64:12**{: .copyMe}:clipboard: |
+ | **`ap-southeast-1`** | **arn:aws:lambda:ap-southeast-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python310-x86_64:12**{: .copyMe}:clipboard: |
+ | **`ap-southeast-2`** | **arn:aws:lambda:ap-southeast-2:017000801446:layer:AWSLambdaPowertoolsPythonV3-python310-x86_64:12**{: .copyMe}:clipboard: |
+ | **`ap-southeast-3`** | **arn:aws:lambda:ap-southeast-3:017000801446:layer:AWSLambdaPowertoolsPythonV3-python310-x86_64:12**{: .copyMe}:clipboard: |
+ | **`ap-southeast-4`** | **arn:aws:lambda:ap-southeast-4:017000801446:layer:AWSLambdaPowertoolsPythonV3-python310-x86_64:12**{: .copyMe}:clipboard: |
+ | **`ap-southeast-5`** | **arn:aws:lambda:ap-southeast-5:017000801446:layer:AWSLambdaPowertoolsPythonV3-python310-x86_64:12**{: .copyMe}:clipboard: |
+ | **`ap-southeast-7`** | **arn:aws:lambda:ap-southeast-7:017000801446:layer:AWSLambdaPowertoolsPythonV3-python310-x86_64:12**{: .copyMe}:clipboard: |
+ | **`ca-central-1`** | **arn:aws:lambda:ca-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python310-x86_64:12**{: .copyMe}:clipboard: |
+ | **`ca-west-1`** | **arn:aws:lambda:ca-west-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python310-x86_64:12**{: .copyMe}:clipboard: |
+ | **`eu-central-1`** | **arn:aws:lambda:eu-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python310-x86_64:12**{: .copyMe}:clipboard: |
+ | **`eu-central-2`** | **arn:aws:lambda:eu-central-2:017000801446:layer:AWSLambdaPowertoolsPythonV3-python310-x86_64:12**{: .copyMe}:clipboard: |
+ | **`eu-north-1`** | **arn:aws:lambda:eu-north-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python310-x86_64:12**{: .copyMe}:clipboard: |
+ | **`eu-south-1`** | **arn:aws:lambda:eu-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python310-x86_64:12**{: .copyMe}:clipboard: |
+ | **`eu-south-2`** | **arn:aws:lambda:eu-south-2:017000801446:layer:AWSLambdaPowertoolsPythonV3-python310-x86_64:12**{: .copyMe}:clipboard: |
+ | **`eu-west-1`** | **arn:aws:lambda:eu-west-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python310-x86_64:12**{: .copyMe}:clipboard: |
+ | **`eu-west-2`** | **arn:aws:lambda:eu-west-2:017000801446:layer:AWSLambdaPowertoolsPythonV3-python310-x86_64:12**{: .copyMe}:clipboard: |
+ | **`eu-west-3`** | **arn:aws:lambda:eu-west-3:017000801446:layer:AWSLambdaPowertoolsPythonV3-python310-x86_64:12**{: .copyMe}:clipboard: |
+ | **`il-central-1`** | **arn:aws:lambda:il-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python310-x86_64:12**{: .copyMe}:clipboard: |
+ | **`me-central-1`** | **arn:aws:lambda:me-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python310-x86_64:12**{: .copyMe}:clipboard: |
+ | **`me-south-1`** | **arn:aws:lambda:me-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python310-x86_64:12**{: .copyMe}:clipboard: |
+ | **`mx-central-1`** | **arn:aws:lambda:mx-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python310-x86_64:12**{: .copyMe}:clipboard: |
+ | **`sa-east-1`** | **arn:aws:lambda:sa-east-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python310-x86_64:12**{: .copyMe}:clipboard: |
+ | **`us-east-1`** | **arn:aws:lambda:us-east-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python310-x86_64:12**{: .copyMe}:clipboard: |
+ | **`us-east-2`** | **arn:aws:lambda:us-east-2:017000801446:layer:AWSLambdaPowertoolsPythonV3-python310-x86_64:12**{: .copyMe}:clipboard: |
+ | **`us-west-1`** | **arn:aws:lambda:us-west-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python310-x86_64:12**{: .copyMe}:clipboard: |
+ | **`us-west-2`** | **arn:aws:lambda:us-west-2:017000801446:layer:AWSLambdaPowertoolsPythonV3-python310-x86_64:12**{: .copyMe}:clipboard: |
=== "Python 3.11"
| Region | Layer ARN |
| -------------------- | --------------------------------------------------------------------------------------------------------- |
- | **`af-south-1`** | **arn:aws:lambda:af-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python311-x86_64:11**{: .copyMe}:clipboard: |
- | **`ap-east-1`** | **arn:aws:lambda:ap-east-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python311-x86_64:11**{: .copyMe}:clipboard: |
- | **`ap-northeast-1`** | **arn:aws:lambda:ap-northeast-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python311-x86_64:11**{: .copyMe}:clipboard: |
- | **`ap-northeast-2`** | **arn:aws:lambda:ap-northeast-2:017000801446:layer:AWSLambdaPowertoolsPythonV3-python311-x86_64:11**{: .copyMe}:clipboard: |
- | **`ap-northeast-3`** | **arn:aws:lambda:ap-northeast-3:017000801446:layer:AWSLambdaPowertoolsPythonV3-python311-x86_64:11**{: .copyMe}:clipboard: |
- | **`ap-south-1`** | **arn:aws:lambda:ap-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python311-x86_64:11**{: .copyMe}:clipboard: |
- | **`ap-south-2`** | **arn:aws:lambda:ap-south-2:017000801446:layer:AWSLambdaPowertoolsPythonV3-python311-x86_64:11**{: .copyMe}:clipboard: |
- | **`ap-southeast-1`** | **arn:aws:lambda:ap-southeast-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python311-x86_64:11**{: .copyMe}:clipboard: |
- | **`ap-southeast-2`** | **arn:aws:lambda:ap-southeast-2:017000801446:layer:AWSLambdaPowertoolsPythonV3-python311-x86_64:11**{: .copyMe}:clipboard: |
- | **`ap-southeast-3`** | **arn:aws:lambda:ap-southeast-3:017000801446:layer:AWSLambdaPowertoolsPythonV3-python311-x86_64:11**{: .copyMe}:clipboard: |
- | **`ap-southeast-4`** | **arn:aws:lambda:ap-southeast-4:017000801446:layer:AWSLambdaPowertoolsPythonV3-python311-x86_64:11**{: .copyMe}:clipboard: |
- | **`ap-southeast-5`** | **arn:aws:lambda:ap-southeast-5:017000801446:layer:AWSLambdaPowertoolsPythonV3-python311-x86_64:11**{: .copyMe}:clipboard: |
- | **`ap-southeast-7`** | **arn:aws:lambda:ap-southeast-7:017000801446:layer:AWSLambdaPowertoolsPythonV3-python311-x86_64:11**{: .copyMe}:clipboard: |
- | **`ca-central-1`** | **arn:aws:lambda:ca-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python311-x86_64:11**{: .copyMe}:clipboard: |
- | **`ca-west-1`** | **arn:aws:lambda:ca-west-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python311-x86_64:11**{: .copyMe}:clipboard: |
- | **`eu-central-1`** | **arn:aws:lambda:eu-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python311-x86_64:11**{: .copyMe}:clipboard: |
- | **`eu-central-2`** | **arn:aws:lambda:eu-central-2:017000801446:layer:AWSLambdaPowertoolsPythonV3-python311-x86_64:11**{: .copyMe}:clipboard: |
- | **`eu-north-1`** | **arn:aws:lambda:eu-north-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python311-x86_64:11**{: .copyMe}:clipboard: |
- | **`eu-south-1`** | **arn:aws:lambda:eu-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python311-x86_64:11**{: .copyMe}:clipboard: |
- | **`eu-south-2`** | **arn:aws:lambda:eu-south-2:017000801446:layer:AWSLambdaPowertoolsPythonV3-python311-x86_64:11**{: .copyMe}:clipboard: |
- | **`eu-west-1`** | **arn:aws:lambda:eu-west-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python311-x86_64:11**{: .copyMe}:clipboard: |
- | **`eu-west-2`** | **arn:aws:lambda:eu-west-2:017000801446:layer:AWSLambdaPowertoolsPythonV3-python311-x86_64:11**{: .copyMe}:clipboard: |
- | **`eu-west-3`** | **arn:aws:lambda:eu-west-3:017000801446:layer:AWSLambdaPowertoolsPythonV3-python311-x86_64:11**{: .copyMe}:clipboard: |
- | **`il-central-1`** | **arn:aws:lambda:il-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python311-x86_64:11**{: .copyMe}:clipboard: |
- | **`me-central-1`** | **arn:aws:lambda:me-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python311-x86_64:11**{: .copyMe}:clipboard: |
- | **`me-south-1`** | **arn:aws:lambda:me-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python311-x86_64:11**{: .copyMe}:clipboard: |
- | **`mx-central-1`** | **arn:aws:lambda:mx-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python311-x86_64:11**{: .copyMe}:clipboard: |
- | **`sa-east-1`** | **arn:aws:lambda:sa-east-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python311-x86_64:11**{: .copyMe}:clipboard: |
- | **`us-east-1`** | **arn:aws:lambda:us-east-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python311-x86_64:11**{: .copyMe}:clipboard: |
- | **`us-east-2`** | **arn:aws:lambda:us-east-2:017000801446:layer:AWSLambdaPowertoolsPythonV3-python311-x86_64:11**{: .copyMe}:clipboard: |
- | **`us-west-1`** | **arn:aws:lambda:us-west-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python311-x86_64:11**{: .copyMe}:clipboard: |
- | **`us-west-2`** | **arn:aws:lambda:us-west-2:017000801446:layer:AWSLambdaPowertoolsPythonV3-python311-x86_64:11**{: .copyMe}:clipboard: |
+ | **`af-south-1`** | **arn:aws:lambda:af-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python311-x86_64:12**{: .copyMe}:clipboard: |
+ | **`ap-east-1`** | **arn:aws:lambda:ap-east-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python311-x86_64:12**{: .copyMe}:clipboard: |
+ | **`ap-northeast-1`** | **arn:aws:lambda:ap-northeast-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python311-x86_64:12**{: .copyMe}:clipboard: |
+ | **`ap-northeast-2`** | **arn:aws:lambda:ap-northeast-2:017000801446:layer:AWSLambdaPowertoolsPythonV3-python311-x86_64:12**{: .copyMe}:clipboard: |
+ | **`ap-northeast-3`** | **arn:aws:lambda:ap-northeast-3:017000801446:layer:AWSLambdaPowertoolsPythonV3-python311-x86_64:12**{: .copyMe}:clipboard: |
+ | **`ap-south-1`** | **arn:aws:lambda:ap-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python311-x86_64:12**{: .copyMe}:clipboard: |
+ | **`ap-south-2`** | **arn:aws:lambda:ap-south-2:017000801446:layer:AWSLambdaPowertoolsPythonV3-python311-x86_64:12**{: .copyMe}:clipboard: |
+ | **`ap-southeast-1`** | **arn:aws:lambda:ap-southeast-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python311-x86_64:12**{: .copyMe}:clipboard: |
+ | **`ap-southeast-2`** | **arn:aws:lambda:ap-southeast-2:017000801446:layer:AWSLambdaPowertoolsPythonV3-python311-x86_64:12**{: .copyMe}:clipboard: |
+ | **`ap-southeast-3`** | **arn:aws:lambda:ap-southeast-3:017000801446:layer:AWSLambdaPowertoolsPythonV3-python311-x86_64:12**{: .copyMe}:clipboard: |
+ | **`ap-southeast-4`** | **arn:aws:lambda:ap-southeast-4:017000801446:layer:AWSLambdaPowertoolsPythonV3-python311-x86_64:12**{: .copyMe}:clipboard: |
+ | **`ap-southeast-5`** | **arn:aws:lambda:ap-southeast-5:017000801446:layer:AWSLambdaPowertoolsPythonV3-python311-x86_64:12**{: .copyMe}:clipboard: |
+ | **`ap-southeast-7`** | **arn:aws:lambda:ap-southeast-7:017000801446:layer:AWSLambdaPowertoolsPythonV3-python311-x86_64:12**{: .copyMe}:clipboard: |
+ | **`ca-central-1`** | **arn:aws:lambda:ca-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python311-x86_64:12**{: .copyMe}:clipboard: |
+ | **`ca-west-1`** | **arn:aws:lambda:ca-west-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python311-x86_64:12**{: .copyMe}:clipboard: |
+ | **`eu-central-1`** | **arn:aws:lambda:eu-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python311-x86_64:12**{: .copyMe}:clipboard: |
+ | **`eu-central-2`** | **arn:aws:lambda:eu-central-2:017000801446:layer:AWSLambdaPowertoolsPythonV3-python311-x86_64:12**{: .copyMe}:clipboard: |
+ | **`eu-north-1`** | **arn:aws:lambda:eu-north-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python311-x86_64:12**{: .copyMe}:clipboard: |
+ | **`eu-south-1`** | **arn:aws:lambda:eu-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python311-x86_64:12**{: .copyMe}:clipboard: |
+ | **`eu-south-2`** | **arn:aws:lambda:eu-south-2:017000801446:layer:AWSLambdaPowertoolsPythonV3-python311-x86_64:12**{: .copyMe}:clipboard: |
+ | **`eu-west-1`** | **arn:aws:lambda:eu-west-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python311-x86_64:12**{: .copyMe}:clipboard: |
+ | **`eu-west-2`** | **arn:aws:lambda:eu-west-2:017000801446:layer:AWSLambdaPowertoolsPythonV3-python311-x86_64:12**{: .copyMe}:clipboard: |
+ | **`eu-west-3`** | **arn:aws:lambda:eu-west-3:017000801446:layer:AWSLambdaPowertoolsPythonV3-python311-x86_64:12**{: .copyMe}:clipboard: |
+ | **`il-central-1`** | **arn:aws:lambda:il-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python311-x86_64:12**{: .copyMe}:clipboard: |
+ | **`me-central-1`** | **arn:aws:lambda:me-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python311-x86_64:12**{: .copyMe}:clipboard: |
+ | **`me-south-1`** | **arn:aws:lambda:me-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python311-x86_64:12**{: .copyMe}:clipboard: |
+ | **`mx-central-1`** | **arn:aws:lambda:mx-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python311-x86_64:12**{: .copyMe}:clipboard: |
+ | **`sa-east-1`** | **arn:aws:lambda:sa-east-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python311-x86_64:12**{: .copyMe}:clipboard: |
+ | **`us-east-1`** | **arn:aws:lambda:us-east-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python311-x86_64:12**{: .copyMe}:clipboard: |
+ | **`us-east-2`** | **arn:aws:lambda:us-east-2:017000801446:layer:AWSLambdaPowertoolsPythonV3-python311-x86_64:12**{: .copyMe}:clipboard: |
+ | **`us-west-1`** | **arn:aws:lambda:us-west-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python311-x86_64:12**{: .copyMe}:clipboard: |
+ | **`us-west-2`** | **arn:aws:lambda:us-west-2:017000801446:layer:AWSLambdaPowertoolsPythonV3-python311-x86_64:12**{: .copyMe}:clipboard: |
=== "Python 3.12"
| Region | Layer ARN |
| -------------------- | --------------------------------------------------------------------------------------------------------- |
- | **`af-south-1`** | **arn:aws:lambda:af-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python312-x86_64:11**{: .copyMe}:clipboard: |
- | **`ap-east-1`** | **arn:aws:lambda:ap-east-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python312-x86_64:11**{: .copyMe}:clipboard: |
- | **`ap-northeast-1`** | **arn:aws:lambda:ap-northeast-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python312-x86_64:11**{: .copyMe}:clipboard: |
- | **`ap-northeast-2`** | **arn:aws:lambda:ap-northeast-2:017000801446:layer:AWSLambdaPowertoolsPythonV3-python312-x86_64:11**{: .copyMe}:clipboard: |
- | **`ap-northeast-3`** | **arn:aws:lambda:ap-northeast-3:017000801446:layer:AWSLambdaPowertoolsPythonV3-python312-x86_64:11**{: .copyMe}:clipboard: |
- | **`ap-south-1`** | **arn:aws:lambda:ap-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python312-x86_64:11**{: .copyMe}:clipboard: |
- | **`ap-south-2`** | **arn:aws:lambda:ap-south-2:017000801446:layer:AWSLambdaPowertoolsPythonV3-python312-x86_64:11**{: .copyMe}:clipboard: |
- | **`ap-southeast-1`** | **arn:aws:lambda:ap-southeast-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python312-x86_64:11**{: .copyMe}:clipboard: |
- | **`ap-southeast-2`** | **arn:aws:lambda:ap-southeast-2:017000801446:layer:AWSLambdaPowertoolsPythonV3-python312-x86_64:11**{: .copyMe}:clipboard: |
- | **`ap-southeast-3`** | **arn:aws:lambda:ap-southeast-3:017000801446:layer:AWSLambdaPowertoolsPythonV3-python312-x86_64:11**{: .copyMe}:clipboard: |
- | **`ap-southeast-4`** | **arn:aws:lambda:ap-southeast-4:017000801446:layer:AWSLambdaPowertoolsPythonV3-python312-x86_64:11**{: .copyMe}:clipboard: |
- | **`ap-southeast-5`** | **arn:aws:lambda:ap-southeast-5:017000801446:layer:AWSLambdaPowertoolsPythonV3-python312-x86_64:11**{: .copyMe}:clipboard: |
- | **`ap-southeast-7`** | **arn:aws:lambda:ap-southeast-7:017000801446:layer:AWSLambdaPowertoolsPythonV3-python312-x86_64:11**{: .copyMe}:clipboard: |
- | **`ca-central-1`** | **arn:aws:lambda:ca-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python312-x86_64:11**{: .copyMe}:clipboard: |
- | **`ca-west-1`** | **arn:aws:lambda:ca-west-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python312-x86_64:11**{: .copyMe}:clipboard: |
- | **`eu-central-1`** | **arn:aws:lambda:eu-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python312-x86_64:11**{: .copyMe}:clipboard: |
- | **`eu-central-2`** | **arn:aws:lambda:eu-central-2:017000801446:layer:AWSLambdaPowertoolsPythonV3-python312-x86_64:11**{: .copyMe}:clipboard: |
- | **`eu-north-1`** | **arn:aws:lambda:eu-north-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python312-x86_64:11**{: .copyMe}:clipboard: |
- | **`eu-south-1`** | **arn:aws:lambda:eu-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python312-x86_64:11**{: .copyMe}:clipboard: |
- | **`eu-south-2`** | **arn:aws:lambda:eu-south-2:017000801446:layer:AWSLambdaPowertoolsPythonV3-python312-x86_64:11**{: .copyMe}:clipboard: |
- | **`eu-west-1`** | **arn:aws:lambda:eu-west-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python312-x86_64:11**{: .copyMe}:clipboard: |
- | **`eu-west-2`** | **arn:aws:lambda:eu-west-2:017000801446:layer:AWSLambdaPowertoolsPythonV3-python312-x86_64:11**{: .copyMe}:clipboard: |
- | **`eu-west-3`** | **arn:aws:lambda:eu-west-3:017000801446:layer:AWSLambdaPowertoolsPythonV3-python312-x86_64:11**{: .copyMe}:clipboard: |
- | **`il-central-1`** | **arn:aws:lambda:il-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python312-x86_64:11**{: .copyMe}:clipboard: |
- | **`me-central-1`** | **arn:aws:lambda:me-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python312-x86_64:11**{: .copyMe}:clipboard: |
- | **`me-south-1`** | **arn:aws:lambda:me-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python312-x86_64:11**{: .copyMe}:clipboard: |
- | **`mx-central-1`** | **arn:aws:lambda:mx-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python312-x86_64:11**{: .copyMe}:clipboard: |
- | **`sa-east-1`** | **arn:aws:lambda:sa-east-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python312-x86_64:11**{: .copyMe}:clipboard: |
- | **`us-east-1`** | **arn:aws:lambda:us-east-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python312-x86_64:11**{: .copyMe}:clipboard: |
- | **`us-east-2`** | **arn:aws:lambda:us-east-2:017000801446:layer:AWSLambdaPowertoolsPythonV3-python312-x86_64:11**{: .copyMe}:clipboard: |
- | **`us-west-1`** | **arn:aws:lambda:us-west-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python312-x86_64:11**{: .copyMe}:clipboard: |
- | **`us-west-2`** | **arn:aws:lambda:us-west-2:017000801446:layer:AWSLambdaPowertoolsPythonV3-python312-x86_64:11**{: .copyMe}:clipboard: |
+ | **`af-south-1`** | **arn:aws:lambda:af-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python312-x86_64:12**{: .copyMe}:clipboard: |
+ | **`ap-east-1`** | **arn:aws:lambda:ap-east-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python312-x86_64:12**{: .copyMe}:clipboard: |
+ | **`ap-northeast-1`** | **arn:aws:lambda:ap-northeast-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python312-x86_64:12**{: .copyMe}:clipboard: |
+ | **`ap-northeast-2`** | **arn:aws:lambda:ap-northeast-2:017000801446:layer:AWSLambdaPowertoolsPythonV3-python312-x86_64:12**{: .copyMe}:clipboard: |
+ | **`ap-northeast-3`** | **arn:aws:lambda:ap-northeast-3:017000801446:layer:AWSLambdaPowertoolsPythonV3-python312-x86_64:12**{: .copyMe}:clipboard: |
+ | **`ap-south-1`** | **arn:aws:lambda:ap-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python312-x86_64:12**{: .copyMe}:clipboard: |
+ | **`ap-south-2`** | **arn:aws:lambda:ap-south-2:017000801446:layer:AWSLambdaPowertoolsPythonV3-python312-x86_64:12**{: .copyMe}:clipboard: |
+ | **`ap-southeast-1`** | **arn:aws:lambda:ap-southeast-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python312-x86_64:12**{: .copyMe}:clipboard: |
+ | **`ap-southeast-2`** | **arn:aws:lambda:ap-southeast-2:017000801446:layer:AWSLambdaPowertoolsPythonV3-python312-x86_64:12**{: .copyMe}:clipboard: |
+ | **`ap-southeast-3`** | **arn:aws:lambda:ap-southeast-3:017000801446:layer:AWSLambdaPowertoolsPythonV3-python312-x86_64:12**{: .copyMe}:clipboard: |
+ | **`ap-southeast-4`** | **arn:aws:lambda:ap-southeast-4:017000801446:layer:AWSLambdaPowertoolsPythonV3-python312-x86_64:12**{: .copyMe}:clipboard: |
+ | **`ap-southeast-5`** | **arn:aws:lambda:ap-southeast-5:017000801446:layer:AWSLambdaPowertoolsPythonV3-python312-x86_64:12**{: .copyMe}:clipboard: |
+ | **`ap-southeast-7`** | **arn:aws:lambda:ap-southeast-7:017000801446:layer:AWSLambdaPowertoolsPythonV3-python312-x86_64:12**{: .copyMe}:clipboard: |
+ | **`ca-central-1`** | **arn:aws:lambda:ca-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python312-x86_64:12**{: .copyMe}:clipboard: |
+ | **`ca-west-1`** | **arn:aws:lambda:ca-west-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python312-x86_64:12**{: .copyMe}:clipboard: |
+ | **`eu-central-1`** | **arn:aws:lambda:eu-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python312-x86_64:12**{: .copyMe}:clipboard: |
+ | **`eu-central-2`** | **arn:aws:lambda:eu-central-2:017000801446:layer:AWSLambdaPowertoolsPythonV3-python312-x86_64:12**{: .copyMe}:clipboard: |
+ | **`eu-north-1`** | **arn:aws:lambda:eu-north-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python312-x86_64:12**{: .copyMe}:clipboard: |
+ | **`eu-south-1`** | **arn:aws:lambda:eu-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python312-x86_64:12**{: .copyMe}:clipboard: |
+ | **`eu-south-2`** | **arn:aws:lambda:eu-south-2:017000801446:layer:AWSLambdaPowertoolsPythonV3-python312-x86_64:12**{: .copyMe}:clipboard: |
+ | **`eu-west-1`** | **arn:aws:lambda:eu-west-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python312-x86_64:12**{: .copyMe}:clipboard: |
+ | **`eu-west-2`** | **arn:aws:lambda:eu-west-2:017000801446:layer:AWSLambdaPowertoolsPythonV3-python312-x86_64:12**{: .copyMe}:clipboard: |
+ | **`eu-west-3`** | **arn:aws:lambda:eu-west-3:017000801446:layer:AWSLambdaPowertoolsPythonV3-python312-x86_64:12**{: .copyMe}:clipboard: |
+ | **`il-central-1`** | **arn:aws:lambda:il-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python312-x86_64:12**{: .copyMe}:clipboard: |
+ | **`me-central-1`** | **arn:aws:lambda:me-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python312-x86_64:12**{: .copyMe}:clipboard: |
+ | **`me-south-1`** | **arn:aws:lambda:me-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python312-x86_64:12**{: .copyMe}:clipboard: |
+ | **`mx-central-1`** | **arn:aws:lambda:mx-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python312-x86_64:12**{: .copyMe}:clipboard: |
+ | **`sa-east-1`** | **arn:aws:lambda:sa-east-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python312-x86_64:12**{: .copyMe}:clipboard: |
+ | **`us-east-1`** | **arn:aws:lambda:us-east-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python312-x86_64:12**{: .copyMe}:clipboard: |
+ | **`us-east-2`** | **arn:aws:lambda:us-east-2:017000801446:layer:AWSLambdaPowertoolsPythonV3-python312-x86_64:12**{: .copyMe}:clipboard: |
+ | **`us-west-1`** | **arn:aws:lambda:us-west-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python312-x86_64:12**{: .copyMe}:clipboard: |
+ | **`us-west-2`** | **arn:aws:lambda:us-west-2:017000801446:layer:AWSLambdaPowertoolsPythonV3-python312-x86_64:12**{: .copyMe}:clipboard: |
=== "Python 3.13"
| Region | Layer ARN |
| -------------------- | --------------------------------------------------------------------------------------------------------- |
- | **`af-south-1`** | **arn:aws:lambda:af-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python313-x86_64:11**{: .copyMe}:clipboard: |
- | **`ap-east-1`** | **arn:aws:lambda:ap-east-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python313-x86_64:11**{: .copyMe}:clipboard: |
- | **`ap-northeast-1`** | **arn:aws:lambda:ap-northeast-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python313-x86_64:11**{: .copyMe}:clipboard: |
- | **`ap-northeast-2`** | **arn:aws:lambda:ap-northeast-2:017000801446:layer:AWSLambdaPowertoolsPythonV3-python313-x86_64:11**{: .copyMe}:clipboard: |
- | **`ap-northeast-3`** | **arn:aws:lambda:ap-northeast-3:017000801446:layer:AWSLambdaPowertoolsPythonV3-python313-x86_64:11**{: .copyMe}:clipboard: |
- | **`ap-south-1`** | **arn:aws:lambda:ap-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python313-x86_64:11**{: .copyMe}:clipboard: |
- | **`ap-south-2`** | **arn:aws:lambda:ap-south-2:017000801446:layer:AWSLambdaPowertoolsPythonV3-python313-x86_64:11**{: .copyMe}:clipboard: |
- | **`ap-southeast-1`** | **arn:aws:lambda:ap-southeast-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python313-x86_64:11**{: .copyMe}:clipboard: |
- | **`ap-southeast-2`** | **arn:aws:lambda:ap-southeast-2:017000801446:layer:AWSLambdaPowertoolsPythonV3-python313-x86_64:11**{: .copyMe}:clipboard: |
- | **`ap-southeast-3`** | **arn:aws:lambda:ap-southeast-3:017000801446:layer:AWSLambdaPowertoolsPythonV3-python313-x86_64:11**{: .copyMe}:clipboard: |
- | **`ap-southeast-4`** | **arn:aws:lambda:ap-southeast-4:017000801446:layer:AWSLambdaPowertoolsPythonV3-python313-x86_64:11**{: .copyMe}:clipboard: |
- | **`ap-southeast-5`** | **arn:aws:lambda:ap-southeast-5:017000801446:layer:AWSLambdaPowertoolsPythonV3-python313-x86_64:11**{: .copyMe}:clipboard: |
- | **`ap-southeast-7`** | **arn:aws:lambda:ap-southeast-7:017000801446:layer:AWSLambdaPowertoolsPythonV3-python313-x86_64:11**{: .copyMe}:clipboard: |
- | **`ca-central-1`** | **arn:aws:lambda:ca-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python313-x86_64:11**{: .copyMe}:clipboard: |
- | **`ca-west-1`** | **arn:aws:lambda:ca-west-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python313-x86_64:11**{: .copyMe}:clipboard: |
- | **`eu-central-1`** | **arn:aws:lambda:eu-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python313-x86_64:11**{: .copyMe}:clipboard: |
- | **`eu-central-2`** | **arn:aws:lambda:eu-central-2:017000801446:layer:AWSLambdaPowertoolsPythonV3-python313-x86_64:11**{: .copyMe}:clipboard: |
- | **`eu-north-1`** | **arn:aws:lambda:eu-north-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python313-x86_64:11**{: .copyMe}:clipboard: |
- | **`eu-south-1`** | **arn:aws:lambda:eu-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python313-x86_64:11**{: .copyMe}:clipboard: |
- | **`eu-south-2`** | **arn:aws:lambda:eu-south-2:017000801446:layer:AWSLambdaPowertoolsPythonV3-python313-x86_64:11**{: .copyMe}:clipboard: |
- | **`eu-west-1`** | **arn:aws:lambda:eu-west-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python313-x86_64:11**{: .copyMe}:clipboard: |
- | **`eu-west-2`** | **arn:aws:lambda:eu-west-2:017000801446:layer:AWSLambdaPowertoolsPythonV3-python313-x86_64:11**{: .copyMe}:clipboard: |
- | **`eu-west-3`** | **arn:aws:lambda:eu-west-3:017000801446:layer:AWSLambdaPowertoolsPythonV3-python313-x86_64:11**{: .copyMe}:clipboard: |
- | **`il-central-1`** | **arn:aws:lambda:il-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python313-x86_64:11**{: .copyMe}:clipboard: |
- | **`me-central-1`** | **arn:aws:lambda:me-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python313-x86_64:11**{: .copyMe}:clipboard: |
- | **`me-south-1`** | **arn:aws:lambda:me-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python313-x86_64:11**{: .copyMe}:clipboard: |
- | **`mx-central-1`** | **arn:aws:lambda:mx-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python313-x86_64:11**{: .copyMe}:clipboard: |
- | **`sa-east-1`** | **arn:aws:lambda:sa-east-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python313-x86_64:11**{: .copyMe}:clipboard: |
- | **`us-east-1`** | **arn:aws:lambda:us-east-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python313-x86_64:11**{: .copyMe}:clipboard: |
- | **`us-east-2`** | **arn:aws:lambda:us-east-2:017000801446:layer:AWSLambdaPowertoolsPythonV3-python313-x86_64:11**{: .copyMe}:clipboard: |
- | **`us-west-1`** | **arn:aws:lambda:us-west-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python313-x86_64:11**{: .copyMe}:clipboard: |
- | **`us-west-2`** | **arn:aws:lambda:us-west-2:017000801446:layer:AWSLambdaPowertoolsPythonV3-python313-x86_64:11**{: .copyMe}:clipboard: |
+ | **`af-south-1`** | **arn:aws:lambda:af-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python313-x86_64:12**{: .copyMe}:clipboard: |
+ | **`ap-east-1`** | **arn:aws:lambda:ap-east-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python313-x86_64:12**{: .copyMe}:clipboard: |
+ | **`ap-northeast-1`** | **arn:aws:lambda:ap-northeast-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python313-x86_64:12**{: .copyMe}:clipboard: |
+ | **`ap-northeast-2`** | **arn:aws:lambda:ap-northeast-2:017000801446:layer:AWSLambdaPowertoolsPythonV3-python313-x86_64:12**{: .copyMe}:clipboard: |
+ | **`ap-northeast-3`** | **arn:aws:lambda:ap-northeast-3:017000801446:layer:AWSLambdaPowertoolsPythonV3-python313-x86_64:12**{: .copyMe}:clipboard: |
+ | **`ap-south-1`** | **arn:aws:lambda:ap-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python313-x86_64:12**{: .copyMe}:clipboard: |
+ | **`ap-south-2`** | **arn:aws:lambda:ap-south-2:017000801446:layer:AWSLambdaPowertoolsPythonV3-python313-x86_64:12**{: .copyMe}:clipboard: |
+ | **`ap-southeast-1`** | **arn:aws:lambda:ap-southeast-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python313-x86_64:12**{: .copyMe}:clipboard: |
+ | **`ap-southeast-2`** | **arn:aws:lambda:ap-southeast-2:017000801446:layer:AWSLambdaPowertoolsPythonV3-python313-x86_64:12**{: .copyMe}:clipboard: |
+ | **`ap-southeast-3`** | **arn:aws:lambda:ap-southeast-3:017000801446:layer:AWSLambdaPowertoolsPythonV3-python313-x86_64:12**{: .copyMe}:clipboard: |
+ | **`ap-southeast-4`** | **arn:aws:lambda:ap-southeast-4:017000801446:layer:AWSLambdaPowertoolsPythonV3-python313-x86_64:12**{: .copyMe}:clipboard: |
+ | **`ap-southeast-5`** | **arn:aws:lambda:ap-southeast-5:017000801446:layer:AWSLambdaPowertoolsPythonV3-python313-x86_64:12**{: .copyMe}:clipboard: |
+ | **`ap-southeast-7`** | **arn:aws:lambda:ap-southeast-7:017000801446:layer:AWSLambdaPowertoolsPythonV3-python313-x86_64:12**{: .copyMe}:clipboard: |
+ | **`ca-central-1`** | **arn:aws:lambda:ca-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python313-x86_64:12**{: .copyMe}:clipboard: |
+ | **`ca-west-1`** | **arn:aws:lambda:ca-west-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python313-x86_64:12**{: .copyMe}:clipboard: |
+ | **`eu-central-1`** | **arn:aws:lambda:eu-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python313-x86_64:12**{: .copyMe}:clipboard: |
+ | **`eu-central-2`** | **arn:aws:lambda:eu-central-2:017000801446:layer:AWSLambdaPowertoolsPythonV3-python313-x86_64:12**{: .copyMe}:clipboard: |
+ | **`eu-north-1`** | **arn:aws:lambda:eu-north-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python313-x86_64:12**{: .copyMe}:clipboard: |
+ | **`eu-south-1`** | **arn:aws:lambda:eu-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python313-x86_64:12**{: .copyMe}:clipboard: |
+ | **`eu-south-2`** | **arn:aws:lambda:eu-south-2:017000801446:layer:AWSLambdaPowertoolsPythonV3-python313-x86_64:12**{: .copyMe}:clipboard: |
+ | **`eu-west-1`** | **arn:aws:lambda:eu-west-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python313-x86_64:12**{: .copyMe}:clipboard: |
+ | **`eu-west-2`** | **arn:aws:lambda:eu-west-2:017000801446:layer:AWSLambdaPowertoolsPythonV3-python313-x86_64:12**{: .copyMe}:clipboard: |
+ | **`eu-west-3`** | **arn:aws:lambda:eu-west-3:017000801446:layer:AWSLambdaPowertoolsPythonV3-python313-x86_64:12**{: .copyMe}:clipboard: |
+ | **`il-central-1`** | **arn:aws:lambda:il-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python313-x86_64:12**{: .copyMe}:clipboard: |
+ | **`me-central-1`** | **arn:aws:lambda:me-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python313-x86_64:12**{: .copyMe}:clipboard: |
+ | **`me-south-1`** | **arn:aws:lambda:me-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python313-x86_64:12**{: .copyMe}:clipboard: |
+ | **`mx-central-1`** | **arn:aws:lambda:mx-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python313-x86_64:12**{: .copyMe}:clipboard: |
+ | **`sa-east-1`** | **arn:aws:lambda:sa-east-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python313-x86_64:12**{: .copyMe}:clipboard: |
+ | **`us-east-1`** | **arn:aws:lambda:us-east-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python313-x86_64:12**{: .copyMe}:clipboard: |
+ | **`us-east-2`** | **arn:aws:lambda:us-east-2:017000801446:layer:AWSLambdaPowertoolsPythonV3-python313-x86_64:12**{: .copyMe}:clipboard: |
+ | **`us-west-1`** | **arn:aws:lambda:us-west-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python313-x86_64:12**{: .copyMe}:clipboard: |
+ | **`us-west-2`** | **arn:aws:lambda:us-west-2:017000801446:layer:AWSLambdaPowertoolsPythonV3-python313-x86_64:12**{: .copyMe}:clipboard: |
diff --git a/docs/index.md b/docs/index.md
index 228f8510c09..3b0992cba07 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -205,7 +205,7 @@ You can install Powertools for AWS Lambda (Python) using your favorite dependenc
You can use AWS CLI to generate a pre-signed URL to download the contents of our Lambda Layer.
```bash title="AWS CLI command to download Lambda Layer content"
- aws lambda get-layer-version-by-arn --arn arn:aws:lambda:eu-west-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python312-x86_64:11 --region eu-west-1
+ aws lambda get-layer-version-by-arn --arn arn:aws:lambda:eu-west-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python312-x86_64:12 --region eu-west-1
```
You'll find the pre-signed URL under `Location` key as part of the CLI command output.
@@ -319,7 +319,7 @@ In this context, `[aws-sdk]` is an alias to the `boto3` package. Due to dependen
The pre-signed URL to download this Lambda Layer will be within `Location` key in the CLI output. The CLI output will also contain the Powertools for AWS Lambda version it contains.
```bash title="AWS CLI command to download Lambda Layer content"
-aws lambda get-layer-version-by-arn --arn arn:aws:lambda:eu-west-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python312-x86_64:11 --region eu-west-1
+aws lambda get-layer-version-by-arn --arn arn:aws:lambda:eu-west-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python312-x86_64:12 --region eu-west-1
```
#### SAR
diff --git a/docs/utilities/data_masking.md b/docs/utilities/data_masking.md
index 1de6419c390..5abcc185938 100644
--- a/docs/utilities/data_masking.md
+++ b/docs/utilities/data_masking.md
@@ -440,21 +440,41 @@ Note that the return will be a deserialized JSON and your desired fields updated
### Data serialization
-???+ note "Current limitations"
- 1. Python classes, `Dataclasses`, and `Pydantic models` are not supported yet.
+???+ tip "Extended input support"
+ We support `Pydantic models`, `Dataclasses`, and custom classes with `dict()` or `__dict__` for input.
+
+ These types are automatically converted into dictionaries before `masking` and `encrypting` operations. Please not that we **don't convert back** to the original type, and the returned object will be a dictionary.
Before we traverse the data structure, we perform two important operations on input data:
1. If `JSON string`, **deserialize** using default or provided deserializer.
-2. If `dictionary`, **normalize** into `JSON` to prevent traversing unsupported data types.
-
-When decrypting, we revert the operation to restore the original data structure.
+2. If `dictionary or complex types`, **normalize** into `JSON` to prevent traversing unsupported data types.
For compatibility or performance, you can optionally pass your own JSON serializer and deserializer to replace `json.dumps` and `json.loads` respectively:
-```python hl_lines="17-18" title="advanced_custom_serializer.py"
---8<-- "examples/data_masking/src/advanced_custom_serializer.py"
-```
+=== "Working with custom types"
+
+ ```python
+ --8<-- "examples/data_masking/src/working_with_custom_types.py"
+ ```
+
+=== "Working with Pydantic"
+
+ ```python
+ --8<-- "examples/data_masking/src/working_with_pydantic_types.py"
+ ```
+
+=== "Working with dataclasses"
+
+ ```python
+ --8<-- "examples/data_masking/src/working_with_dataclass_types.py"
+ ```
+
+=== "Working with serializer"
+
+ ```python
+ --8<-- "examples/data_masking/src/advanced_custom_serializer.py"
+ ```
### Using multiple keys
diff --git a/docs/utilities/parser.md b/docs/utilities/parser.md
index b6abbe965e1..4cdf0d452f2 100644
--- a/docs/utilities/parser.md
+++ b/docs/utilities/parser.md
@@ -111,6 +111,7 @@ The example above uses `SqsModel`. Other built-in models can be found below.
| **APIGatewayWebSocketMessageEventModel** | Lambda Event Source payload for Amazon API Gateway WebSocket API message body |
| **APIGatewayWebSocketConnectEventModel** | Lambda Event Source payload for Amazon API Gateway WebSocket API $connect message |
| **APIGatewayWebSocketDisconnectEventModel** | Lambda Event Source payload for Amazon API Gateway WebSocket API $disconnect message |
+| **AppSyncResolverEventModel** | Lambda Event Source payload for AWS AppSync Resolver |
| **BedrockAgentEventModel** | Lambda Event Source payload for Bedrock Agents |
| **CloudFormationCustomResourceCreateModel** | Lambda Event Source payload for AWS CloudFormation `CREATE` operation |
| **CloudFormationCustomResourceUpdateModel** | Lambda Event Source payload for AWS CloudFormation `UPDATE` operation |
diff --git a/examples/batch_processing/src/getting_started_error_handling.py b/examples/batch_processing/src/getting_started_error_handling.py
index 7307f0d0d09..0b4b0637db7 100644
--- a/examples/batch_processing/src/getting_started_error_handling.py
+++ b/examples/batch_processing/src/getting_started_error_handling.py
@@ -12,8 +12,7 @@
logger = Logger()
-class InvalidPayload(Exception):
- ...
+class InvalidPayload(Exception): ...
@tracer.capture_method
diff --git a/examples/data_masking/src/aws_encryption_provider_example.py b/examples/data_masking/src/aws_encryption_provider_example.py
index 2ef34a82934..51ca5fba310 100644
--- a/examples/data_masking/src/aws_encryption_provider_example.py
+++ b/examples/data_masking/src/aws_encryption_provider_example.py
@@ -16,7 +16,8 @@
local_cache_capacity=200,
max_cache_age_seconds=400,
max_messages_encrypted=200,
- max_bytes_encrypted=2000)
+ max_bytes_encrypted=2000,
+)
data_masker = DataMasking(provider=encryption_provider)
diff --git a/examples/data_masking/src/working_with_custom_types.py b/examples/data_masking/src/working_with_custom_types.py
new file mode 100644
index 00000000000..833fe3465ec
--- /dev/null
+++ b/examples/data_masking/src/working_with_custom_types.py
@@ -0,0 +1,17 @@
+from aws_lambda_powertools.utilities.data_masking import DataMasking
+
+data_masker = DataMasking()
+
+
+class User:
+ def __init__(self, name, age):
+ self.name = name
+ self.age = age
+
+ def dict(self):
+ return {"name": self.name, "age": self.age}
+
+
+def lambda_handler(event, context):
+ user = User("powertools", 42)
+ return data_masker.erase(user, fields=["age"])
diff --git a/examples/data_masking/src/working_with_dataclass_types.py b/examples/data_masking/src/working_with_dataclass_types.py
new file mode 100644
index 00000000000..bcd9b13de6d
--- /dev/null
+++ b/examples/data_masking/src/working_with_dataclass_types.py
@@ -0,0 +1,16 @@
+from dataclasses import dataclass
+
+from aws_lambda_powertools.utilities.data_masking import DataMasking
+
+data_masker = DataMasking()
+
+
+@dataclass
+class User:
+ name: str
+ age: int
+
+
+def lambda_handler(event, context):
+ user = User(name="powertools", age=42)
+ return data_masker.erase(user, fields=["age"])
diff --git a/examples/data_masking/src/working_with_pydantic_types.py b/examples/data_masking/src/working_with_pydantic_types.py
new file mode 100644
index 00000000000..b9f3db293b5
--- /dev/null
+++ b/examples/data_masking/src/working_with_pydantic_types.py
@@ -0,0 +1,15 @@
+from pydantic import BaseModel
+
+from aws_lambda_powertools.utilities.data_masking import DataMasking
+
+data_masker = DataMasking()
+
+
+class User(BaseModel):
+ name: str
+ age: int
+
+
+def lambda_handler(event, context):
+ user = User(name="powertools", age=42)
+ return data_masker.erase(user, fields=["age"])
diff --git a/examples/event_handler_appsync_events/src/accessing_event_and_context.py b/examples/event_handler_appsync_events/src/accessing_event_and_context.py
new file mode 100644
index 00000000000..210350a6ed9
--- /dev/null
+++ b/examples/event_handler_appsync_events/src/accessing_event_and_context.py
@@ -0,0 +1,38 @@
+from __future__ import annotations
+
+from typing import TYPE_CHECKING, Any
+
+from aws_lambda_powertools.event_handler import AppSyncEventsResolver
+from aws_lambda_powertools.utilities.data_classes import AppSyncResolverEventsEvent
+
+if TYPE_CHECKING:
+ from aws_lambda_powertools.utilities.typing import LambdaContext
+
+app = AppSyncEventsResolver()
+
+
+class ValidationError(Exception):
+ pass
+
+
+@app.on_publish("/default/channel1")
+def handle_channel1_publish(payload: dict[str, Any]):
+ # Access the full event and context
+ lambda_event: AppSyncResolverEventsEvent = app.current_event
+ lambda_context: LambdaContext = app.context
+
+ # Access request headers
+ headers = lambda_event.get("request", {}).get("headers", {})
+
+ # Check remaining time
+ remaining_time = lambda_context.get_remaining_time_in_millis()
+
+ return {
+ "originalMessage": payload,
+ "userAgent": headers.get("User-Agent"),
+ "timeRemaining": remaining_time,
+ }
+
+
+def lambda_handler(event: dict, context: LambdaContext):
+ return app.resolve(event, context)
diff --git a/examples/event_handler_appsync_events/src/getting_started_with_appsync_events.yaml b/examples/event_handler_appsync_events/src/getting_started_with_appsync_events.yaml
new file mode 100644
index 00000000000..096346b3e46
--- /dev/null
+++ b/examples/event_handler_appsync_events/src/getting_started_with_appsync_events.yaml
@@ -0,0 +1,27 @@
+Resources:
+ WebsocketAPI:
+ Type: AWS::AppSync::Api
+ Properties:
+ EventConfig:
+ AuthProviders:
+ - AuthType: API_KEY
+ ConnectionAuthModes:
+ - AuthType: API_KEY
+ DefaultPublishAuthModes:
+ - AuthType: API_KEY
+ DefaultSubscribeAuthModes:
+ - AuthType: API_KEY
+ Name: RealTimeEventAPI
+
+ WebasocketApiKey:
+ Type: AWS::AppSync::ApiKey
+ Properties:
+ ApiId: !GetAtt WebsocketAPI.ApiId
+ Description: "API KEY"
+ Expires: 365
+
+ WebsocketAPINamespace:
+ Type: AWS::AppSync::ChannelNamespace
+ Properties:
+ ApiId: !GetAtt WebsocketAPI.ApiId
+ Name: powertools
diff --git a/examples/event_handler_appsync_events/src/getting_started_with_publish_events.py b/examples/event_handler_appsync_events/src/getting_started_with_publish_events.py
new file mode 100644
index 00000000000..bd4fa00142f
--- /dev/null
+++ b/examples/event_handler_appsync_events/src/getting_started_with_publish_events.py
@@ -0,0 +1,23 @@
+from __future__ import annotations
+
+from typing import TYPE_CHECKING, Any
+
+from aws_lambda_powertools.event_handler import AppSyncEventsResolver
+
+if TYPE_CHECKING:
+ from aws_lambda_powertools.utilities.typing import LambdaContext
+
+app = AppSyncEventsResolver()
+
+
+@app.on_publish("/default/channel")
+def handle_channel1_publish(payload: dict[str, Any]):
+ # Process the payload for this specific channel
+ return {
+ "processed": True,
+ "original_payload": payload,
+ }
+
+
+def lambda_handler(event: dict, context: LambdaContext):
+ return app.resolve(event, context)
diff --git a/examples/event_handler_appsync_events/src/getting_started_with_subscribe_events.py b/examples/event_handler_appsync_events/src/getting_started_with_subscribe_events.py
new file mode 100644
index 00000000000..f9f10c1d6a7
--- /dev/null
+++ b/examples/event_handler_appsync_events/src/getting_started_with_subscribe_events.py
@@ -0,0 +1,34 @@
+from __future__ import annotations
+
+from typing import TYPE_CHECKING
+
+from aws_lambda_powertools.event_handler import AppSyncEventsResolver
+from aws_lambda_powertools.event_handler.events_appsync.exceptions import UnauthorizedException
+
+if TYPE_CHECKING:
+ from aws_lambda_powertools.utilities.typing import LambdaContext
+
+app = AppSyncEventsResolver()
+
+
+@app.on_subscribe("/*")
+def handle_all_subscriptions():
+ path = app.current_event.info.channel_path
+
+ # Perform access control checks
+ if not is_authorized(path):
+ raise UnauthorizedException("You are not authorized to subscribe to this channel")
+
+ return True
+
+
+def is_authorized(path: str):
+ # Your authorization logic here
+ if path == "not_allowed_path_here":
+ return False
+
+ return True
+
+
+def lambda_handler(event: dict, context: LambdaContext):
+ return app.resolve(event, context)
diff --git a/examples/event_handler_appsync_events/src/getting_started_with_testing_publish.py b/examples/event_handler_appsync_events/src/getting_started_with_testing_publish.py
new file mode 100644
index 00000000000..9d9eaefbb78
--- /dev/null
+++ b/examples/event_handler_appsync_events/src/getting_started_with_testing_publish.py
@@ -0,0 +1,42 @@
+import json
+from pathlib import Path
+
+from aws_lambda_powertools.event_handler import AppSyncEventsResolver
+
+
+class LambdaContext:
+ def __init__(self):
+ self.function_name = "test-func"
+ self.memory_limit_in_mb = 128
+ self.invoked_function_arn = "arn:aws:lambda:eu-west-1:809313241234:function:test-func"
+ self.aws_request_id = "52fdfc07-2182-154f-163f-5f0f9a621d72"
+
+ def get_remaining_time_in_millis(self) -> int:
+ return 1000
+
+
+def test_publish_event_with_synchronous_resolver():
+ """Test handling a publish event with a synchronous resolver."""
+ # GIVEN a sample publish event
+ with Path.open("getting_started_with_testing_publish_event.json", "r") as f:
+ event = json.load(f)
+
+ lambda_context = LambdaContext()
+
+ # GIVEN an AppSyncEventsResolver with a synchronous resolver
+ app = AppSyncEventsResolver()
+
+ @app.on_publish(path="/default/*")
+ def test_handler(payload):
+ return {"processed": True, "data": payload["data"]}
+
+ # WHEN we resolve the event
+ result = app.resolve(event, lambda_context)
+
+ # THEN we should get the correct response
+ expected_result = {
+ "events": [
+ {"id": "123", "payload": {"processed": True, "data": "test data"}},
+ ],
+ }
+ assert result == expected_result
diff --git a/examples/event_handler_appsync_events/src/getting_started_with_testing_publish_event.json b/examples/event_handler_appsync_events/src/getting_started_with_testing_publish_event.json
new file mode 100644
index 00000000000..29167ae6fba
--- /dev/null
+++ b/examples/event_handler_appsync_events/src/getting_started_with_testing_publish_event.json
@@ -0,0 +1,65 @@
+{
+ "identity":"None",
+ "result":"None",
+ "request":{
+ "headers": {
+ "x-forwarded-for": "1.1.1.1, 2.2.2.2",
+ "cloudfront-viewer-country": "US",
+ "cloudfront-is-tablet-viewer": "false",
+ "via": "2.0 xxxxxxxxxxxxxxxx.cloudfront.net (CloudFront)",
+ "cloudfront-forwarded-proto": "https",
+ "origin": "https://us-west-1.console.aws.amazon.com",
+ "content-length": "217",
+ "accept-language": "en-US,en;q=0.9",
+ "host": "xxxxxxxxxxxxxxxx.appsync-api.us-west-1.amazonaws.com",
+ "x-forwarded-proto": "https",
+ "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.83 Safari/537.36",
+ "accept": "*/*",
+ "cloudfront-is-mobile-viewer": "false",
+ "cloudfront-is-smarttv-viewer": "false",
+ "accept-encoding": "gzip, deflate, br",
+ "referer": "https://us-west-1.console.aws.amazon.com/appsync/home?region=us-west-1",
+ "content-type": "application/json",
+ "sec-fetch-mode": "cors",
+ "x-amz-cf-id": "3aykhqlUwQeANU-HGY7E_guV5EkNeMMtwyOgiA==",
+ "x-amzn-trace-id": "Root=1-5f512f51-fac632066c5e848ae714",
+ "authorization": "eyJraWQiOiJScWFCSlJqYVJlM0hrSnBTUFpIcVRXazNOW...",
+ "sec-fetch-dest": "empty",
+ "x-amz-user-agent": "AWS-Console-AppSync/",
+ "cloudfront-is-desktop-viewer": "true",
+ "sec-fetch-site": "cross-site",
+ "x-forwarded-port": "443"
+ },
+ "domainName":"None"
+ },
+ "info":{
+ "channel":{
+ "path":"/default/channel",
+ "segments":[
+ "default",
+ "channel"
+ ]
+ },
+ "channelNamespace":{
+ "name":"default"
+ },
+ "operation":"PUBLISH"
+ },
+ "error":"None",
+ "prev":"None",
+ "stash":{
+
+ },
+ "outErrors":[
+
+ ],
+ "events":[
+ {
+ "payload":{
+ "data": "test data"
+ },
+ "id":"123"
+ }
+ ]
+ }
+
\ No newline at end of file
diff --git a/examples/event_handler_appsync_events/src/getting_started_with_testing_subscribe.py b/examples/event_handler_appsync_events/src/getting_started_with_testing_subscribe.py
new file mode 100644
index 00000000000..54ef103183b
--- /dev/null
+++ b/examples/event_handler_appsync_events/src/getting_started_with_testing_subscribe.py
@@ -0,0 +1,37 @@
+import json
+from pathlib import Path
+
+from aws_lambda_powertools.event_handler import AppSyncEventsResolver
+
+
+class LambdaContext:
+ def __init__(self):
+ self.function_name = "test-func"
+ self.memory_limit_in_mb = 128
+ self.invoked_function_arn = "arn:aws:lambda:eu-west-1:809313241234:function:test-func"
+ self.aws_request_id = "52fdfc07-2182-154f-163f-5f0f9a621d72"
+
+ def get_remaining_time_in_millis(self) -> int:
+ return 1000
+
+
+def test_subscribe_event_with_valid_return():
+ """Test error handling during publish event processing."""
+ # GIVEN a sample publish event
+ with Path.open("getting_started_with_testing_publish_event.json", "r") as f:
+ event = json.load(f)
+
+ lambda_context = LambdaContext()
+
+ # GIVEN an AppSyncEventsResolver with a resolver that returns ok
+ app = AppSyncEventsResolver()
+
+ @app.on_subscribe(path="/default/*")
+ def test_handler():
+ pass
+
+ # WHEN we resolve the event
+ result = app.resolve(event, lambda_context)
+
+ # THEN we should return None because subscribe always must return None
+ assert result is None
diff --git a/examples/event_handler_appsync_events/src/getting_started_with_testing_subscribe_event.json b/examples/event_handler_appsync_events/src/getting_started_with_testing_subscribe_event.json
new file mode 100644
index 00000000000..a9d0b3458bf
--- /dev/null
+++ b/examples/event_handler_appsync_events/src/getting_started_with_testing_subscribe_event.json
@@ -0,0 +1,58 @@
+{
+ "identity":"None",
+ "result":"None",
+ "request":{
+ "headers": {
+ "x-forwarded-for": "1.1.1.1, 2.2.2.2",
+ "cloudfront-viewer-country": "US",
+ "cloudfront-is-tablet-viewer": "false",
+ "via": "2.0 xxxxxxxxxxxxxxxx.cloudfront.net (CloudFront)",
+ "cloudfront-forwarded-proto": "https",
+ "origin": "https://us-west-1.console.aws.amazon.com",
+ "content-length": "217",
+ "accept-language": "en-US,en;q=0.9",
+ "host": "xxxxxxxxxxxxxxxx.appsync-api.us-west-1.amazonaws.com",
+ "x-forwarded-proto": "https",
+ "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.83 Safari/537.36",
+ "accept": "*/*",
+ "cloudfront-is-mobile-viewer": "false",
+ "cloudfront-is-smarttv-viewer": "false",
+ "accept-encoding": "gzip, deflate, br",
+ "referer": "https://us-west-1.console.aws.amazon.com/appsync/home?region=us-west-1",
+ "content-type": "application/json",
+ "sec-fetch-mode": "cors",
+ "x-amz-cf-id": "3aykhqlUwQeANU-HGY7E_guV5EkNeMMtwyOgiA==",
+ "x-amzn-trace-id": "Root=1-5f512f51-fac632066c5e848ae714",
+ "authorization": "eyJraWQiOiJScWFCSlJqYVJlM0hrSnBTUFpIcVRXazNOW...",
+ "sec-fetch-dest": "empty",
+ "x-amz-user-agent": "AWS-Console-AppSync/",
+ "cloudfront-is-desktop-viewer": "true",
+ "sec-fetch-site": "cross-site",
+ "x-forwarded-port": "443"
+ },
+ "domainName":"None"
+ },
+ "info":{
+ "channel":{
+ "path":"/default/channel",
+ "segments":[
+ "default",
+ "channel"
+ ]
+ },
+ "channelNamespace":{
+ "name":"default"
+ },
+ "operation":"SUBSCRIBE"
+ },
+ "error":"None",
+ "prev":"None",
+ "stash":{
+
+ },
+ "outErrors":[
+
+ ],
+ "events":[]
+ }
+
\ No newline at end of file
diff --git a/examples/event_handler_appsync_events/src/payload_request.json b/examples/event_handler_appsync_events/src/payload_request.json
new file mode 100644
index 00000000000..54cf3b85cfc
--- /dev/null
+++ b/examples/event_handler_appsync_events/src/payload_request.json
@@ -0,0 +1,47 @@
+{
+ "identity":"None",
+ "result":"None",
+ "request":{
+ "headers": {
+ "x-forwarded-for": "1.1.1.1, 2.2.2.2",
+ "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.83 Safari/537.36",
+ },
+ "domainName":"None"
+ },
+ "info":{
+ "channel":{
+ "path":"/default/channel",
+ "segments":[
+ "default",
+ "channel"
+ ]
+ },
+ "channelNamespace":{
+ "name":"default"
+ },
+ "operation":"PUBLISH"
+ },
+ "error":"None",
+ "prev":"None",
+ "stash":{
+
+ },
+ "outErrors":[
+
+ ],
+ "events":[
+ {
+ "payload":{
+ "data":"data_1"
+ },
+ "id":"1"
+ },
+ {
+ "payload":{
+ "data":"data_2"
+ },
+ "id":"2"
+ }
+ ]
+}
+
\ No newline at end of file
diff --git a/examples/event_handler_appsync_events/src/payload_response.json b/examples/event_handler_appsync_events/src/payload_response.json
new file mode 100644
index 00000000000..06285d18ab8
--- /dev/null
+++ b/examples/event_handler_appsync_events/src/payload_response.json
@@ -0,0 +1,17 @@
+{
+ "events":[
+ {
+ "payload":{
+ "data":"data_1"
+ },
+ "id":"1"
+ },
+ {
+ "payload":{
+ "data":"data_2"
+ },
+ "id":"2"
+ }
+ ]
+}
+
\ No newline at end of file
diff --git a/examples/event_handler_appsync_events/src/payload_response_fail_request.json b/examples/event_handler_appsync_events/src/payload_response_fail_request.json
new file mode 100644
index 00000000000..efd8c50004e
--- /dev/null
+++ b/examples/event_handler_appsync_events/src/payload_response_fail_request.json
@@ -0,0 +1,4 @@
+{
+ "error": "Exception - An exception occurred"
+}
+
\ No newline at end of file
diff --git a/examples/event_handler_appsync_events/src/payload_response_with_error.json b/examples/event_handler_appsync_events/src/payload_response_with_error.json
new file mode 100644
index 00000000000..00d363465bc
--- /dev/null
+++ b/examples/event_handler_appsync_events/src/payload_response_with_error.json
@@ -0,0 +1,15 @@
+{
+ "events":[
+ {
+ "error": "Error message",
+ "id":"1"
+ },
+ {
+ "payload":{
+ "data":"data_2"
+ },
+ "id":"2"
+ }
+ ]
+}
+
\ No newline at end of file
diff --git a/examples/event_handler_appsync_events/src/working_with_aggregated_events.py b/examples/event_handler_appsync_events/src/working_with_aggregated_events.py
new file mode 100644
index 00000000000..70eeee8b12f
--- /dev/null
+++ b/examples/event_handler_appsync_events/src/working_with_aggregated_events.py
@@ -0,0 +1,26 @@
+from __future__ import annotations
+
+from typing import TYPE_CHECKING, Any
+
+from aws_lambda_powertools.event_handler import AppSyncEventsResolver
+
+if TYPE_CHECKING:
+ from aws_lambda_powertools.utilities.typing import LambdaContext
+
+app = AppSyncEventsResolver()
+
+
+@app.on_publish("/default/*", aggregate=True)
+def handle_default_namespace_batch(payload_list: list[dict[str, Any]]):
+ results: list = []
+
+ # Process all events in the batch together
+ for event in payload_list:
+ # Process each event
+ results.append({"id": event.get("id"), "payload": {"processed": True, "originalEvent": event}})
+
+ return results
+
+
+def lambda_handler(event: dict, context: LambdaContext):
+ return app.resolve(event, context)
diff --git a/examples/event_handler_appsync_events/src/working_with_async_resolvers.py b/examples/event_handler_appsync_events/src/working_with_async_resolvers.py
new file mode 100644
index 00000000000..7d0fd2ef401
--- /dev/null
+++ b/examples/event_handler_appsync_events/src/working_with_async_resolvers.py
@@ -0,0 +1,26 @@
+from __future__ import annotations
+
+import asyncio
+from typing import TYPE_CHECKING, Any
+
+from aws_lambda_powertools.event_handler import AppSyncEventsResolver
+
+if TYPE_CHECKING:
+ from aws_lambda_powertools.utilities.typing import LambdaContext
+
+app = AppSyncEventsResolver()
+
+
+@app.async_on_publish("/default/channel1")
+async def handle_channel1_publish(payload: dict[str, Any]):
+ result = await async_process_data(payload)
+ return result
+
+
+async def async_process_data(payload: dict[str, Any]):
+ await asyncio.sleep(0.1)
+ return {"processed": payload, "async": True}
+
+
+def lambda_handler(event: dict, context: LambdaContext):
+ return app.resolve(event, context)
diff --git a/examples/event_handler_appsync_events/src/working_with_error_handling.py b/examples/event_handler_appsync_events/src/working_with_error_handling.py
new file mode 100644
index 00000000000..7dcd5bb183a
--- /dev/null
+++ b/examples/event_handler_appsync_events/src/working_with_error_handling.py
@@ -0,0 +1,34 @@
+from __future__ import annotations
+
+from typing import TYPE_CHECKING, Any
+
+from aws_lambda_powertools.event_handler import AppSyncEventsResolver
+
+if TYPE_CHECKING:
+ from aws_lambda_powertools.utilities.typing import LambdaContext
+
+app = AppSyncEventsResolver()
+
+
+class ValidationError(Exception):
+ pass
+
+
+@app.on_publish("/default/channel")
+def handle_channel1_publish(payload: dict[str, Any]):
+ if not is_valid_payload(payload):
+ raise ValidationError("Invalid payload format")
+
+ return process_payload(payload)
+
+
+def is_valid_payload(payload: dict[str, Any]):
+ return "data" in payload
+
+
+def process_payload(payload: dict[str, Any]):
+ return {"processed": payload["data"]}
+
+
+def lambda_handler(event: dict, context: LambdaContext):
+ return app.resolve(event, context)
diff --git a/examples/event_handler_appsync_events/src/working_with_error_handling_multiple.py b/examples/event_handler_appsync_events/src/working_with_error_handling_multiple.py
new file mode 100644
index 00000000000..4b9bcad9d1c
--- /dev/null
+++ b/examples/event_handler_appsync_events/src/working_with_error_handling_multiple.py
@@ -0,0 +1,35 @@
+from __future__ import annotations
+
+from typing import TYPE_CHECKING, Any
+
+from aws_lambda_powertools.event_handler import AppSyncEventsResolver
+
+if TYPE_CHECKING:
+ from aws_lambda_powertools.utilities.typing import LambdaContext
+
+app = AppSyncEventsResolver()
+
+
+@app.on_publish("/default/*", aggregate=True)
+def handle_default_namespace_batch(payload_list: list[dict[str, Any]]):
+ results: list = []
+
+ # Process all events in the batch together
+ for event in payload_list:
+ try:
+ # Process each event
+ results.append({"id": event.get("id"), "payload": {"processed": True, "originalEvent": event}})
+ except Exception as e:
+ # Handle errors for individual events
+ results.append(
+ {
+ "error": str(e),
+ "id": event.get("id"),
+ }
+ )
+
+ return results
+
+
+def lambda_handler(event: dict, context: LambdaContext):
+ return app.resolve(event, context)
diff --git a/examples/event_handler_appsync_events/src/working_with_error_handling_response.json b/examples/event_handler_appsync_events/src/working_with_error_handling_response.json
new file mode 100644
index 00000000000..8d5bfe2a27a
--- /dev/null
+++ b/examples/event_handler_appsync_events/src/working_with_error_handling_response.json
@@ -0,0 +1,15 @@
+{
+ "events":[
+ {
+ "error": "Error message",
+ "id":"1"
+ },
+ {
+ "payload":{
+ "data":"data_2"
+ },
+ "id":"2"
+ }
+ ]
+ }
+
\ No newline at end of file
diff --git a/examples/event_handler_appsync_events/src/working_with_wildcard_resolvers.py b/examples/event_handler_appsync_events/src/working_with_wildcard_resolvers.py
new file mode 100644
index 00000000000..c6f2447c744
--- /dev/null
+++ b/examples/event_handler_appsync_events/src/working_with_wildcard_resolvers.py
@@ -0,0 +1,34 @@
+from __future__ import annotations
+
+from typing import TYPE_CHECKING, Any
+
+from aws_lambda_powertools.event_handler import AppSyncEventsResolver
+
+if TYPE_CHECKING:
+ from aws_lambda_powertools.utilities.typing import LambdaContext
+
+app = AppSyncEventsResolver()
+
+
+@app.on_publish("/default/channel1")
+def handle_specific_channel(payload: dict[str, Any]):
+ # This handler will be called for events on /default/channel1
+ return {"source": "specific_handler", "data": payload}
+
+
+@app.on_publish("/default/*")
+def handle_default_namespace(payload: dict[str, Any]):
+ # This handler will be called for all channels in the default namespace
+ # EXCEPT for /default/channel1 which has a more specific handler
+ return {"source": "namespace_handler", "data": payload}
+
+
+@app.on_publish("/*")
+def handle_all_channels(payload: dict[str, Any]):
+ # This handler will be called for all channels in all namespaces
+ # EXCEPT for those that have more specific handlers
+ return {"source": "catch_all_handler", "data": payload}
+
+
+def lambda_handler(event: dict, context: LambdaContext):
+ return app.resolve(event, context)
diff --git a/examples/event_handler_bedrock_agents/cdk/bedrock_agent_stack.py b/examples/event_handler_bedrock_agents/cdk/bedrock_agent_stack.py
index e6d427bc8c3..17f07c47296 100644
--- a/examples/event_handler_bedrock_agents/cdk/bedrock_agent_stack.py
+++ b/examples/event_handler_bedrock_agents/cdk/bedrock_agent_stack.py
@@ -8,7 +8,6 @@
class AgentsCdkStack(Stack):
-
def __init__(self, scope: Construct, construct_id: str, **kwargs) -> None:
super().__init__(scope, construct_id, **kwargs)
diff --git a/examples/event_handler_bedrock_agents/sam/template.yaml b/examples/event_handler_bedrock_agents/sam/template.yaml
index 34d4cb25ec7..67b0b80c34d 100644
--- a/examples/event_handler_bedrock_agents/sam/template.yaml
+++ b/examples/event_handler_bedrock_agents/sam/template.yaml
@@ -38,10 +38,10 @@ Resources:
Statement:
- Effect: Allow
Principal:
- Action:
- - sts:assumeRole
Service:
- bedrock.amazonaws.com
+ Action:
+ - sts:AssumeRole
Policies:
- PolicyName: bedrock
PolicyDocument:
diff --git a/examples/event_handler_bedrock_agents/src/enabling_user_confirmation.py b/examples/event_handler_bedrock_agents/src/enabling_user_confirmation.py
new file mode 100644
index 00000000000..21a10666014
--- /dev/null
+++ b/examples/event_handler_bedrock_agents/src/enabling_user_confirmation.py
@@ -0,0 +1,26 @@
+from time import time
+
+from aws_lambda_powertools import Logger
+from aws_lambda_powertools.event_handler import BedrockAgentResolver
+from aws_lambda_powertools.utilities.typing import LambdaContext
+
+logger = Logger()
+app = BedrockAgentResolver()
+
+
+@app.get(
+ "/current_time",
+ description="Gets the current time in seconds",
+ openapi_extensions={"x-requireConfirmation": "ENABLED"}, # (1)!
+)
+def current_time() -> int:
+ return int(time())
+
+
+@logger.inject_lambda_context
+def lambda_handler(event: dict, context: LambdaContext):
+ return app.resolve(event, context)
+
+
+if __name__ == "__main__":
+ print(app.get_openapi_json_schema())
diff --git a/examples/event_handler_graphql/src/enable_exceptions_batch_resolver.py b/examples/event_handler_graphql/src/enable_exceptions_batch_resolver.py
index f77374527ea..1d94e4693c8 100644
--- a/examples/event_handler_graphql/src/enable_exceptions_batch_resolver.py
+++ b/examples/event_handler_graphql/src/enable_exceptions_batch_resolver.py
@@ -14,8 +14,7 @@
}
-class PostRelatedNotFound(Exception):
- ...
+class PostRelatedNotFound(Exception): ...
@app.batch_resolver(type_name="Query", field_name="relatedPosts", raise_on_error=True) # (1)!
diff --git a/examples/event_handler_rest/src/customizing_response_validation.py b/examples/event_handler_rest/src/customizing_response_validation.py
index 2b7b2c16c9f..25aa07bf52a 100644
--- a/examples/event_handler_rest/src/customizing_response_validation.py
+++ b/examples/event_handler_rest/src/customizing_response_validation.py
@@ -33,6 +33,18 @@ def get_todo_by_id(todo_id: int) -> Todo:
return todo.json()["title"] # (2)!
+@app.get(
+ "/todos_bad_response_with_custom_http_code/",
+ custom_response_validation_http_code=HTTPStatus.UNPROCESSABLE_ENTITY, # (3)!
+)
+@tracer.capture_method
+def get_todo_by_id_custom(todo_id: int) -> Todo:
+ todo = requests.get(f"https://jsonplaceholder.typicode.com/todos/{todo_id}")
+ todo.raise_for_status()
+
+ return todo.json()["title"]
+
+
@logger.inject_lambda_context(correlation_id_path=correlation_paths.API_GATEWAY_HTTP)
@tracer.capture_lambda_handler
def lambda_handler(event: dict, context: LambdaContext) -> dict:
diff --git a/examples/event_handler_rest/src/raising_http_errors.py b/examples/event_handler_rest/src/raising_http_errors.py
index 97e7cc5048f..c792c7908ec 100644
--- a/examples/event_handler_rest/src/raising_http_errors.py
+++ b/examples/event_handler_rest/src/raising_http_errors.py
@@ -5,9 +5,13 @@
from aws_lambda_powertools.event_handler import APIGatewayRestResolver
from aws_lambda_powertools.event_handler.exceptions import (
BadRequestError,
+ ForbiddenError,
InternalServerError,
NotFoundError,
+ RequestEntityTooLargeError,
+ RequestTimeoutError,
ServiceError,
+ ServiceUnavailableError,
UnauthorizedError,
)
from aws_lambda_powertools.logging import correlation_paths
@@ -28,21 +32,41 @@ def unauthorized_error():
raise UnauthorizedError("Unauthorized") # HTTP 401
+@app.get(rule="/forbidden-error")
+def forbidden_error():
+ raise ForbiddenError("Access denied") # HTTP 403
+
+
@app.get(rule="/not-found-error")
def not_found_error():
raise NotFoundError # HTTP 404
+@app.get(rule="/request-timeout-error")
+def request_timeout_error():
+ raise RequestTimeoutError("Request timed out") # HTTP 408
+
+
@app.get(rule="/internal-server-error")
def internal_server_error():
raise InternalServerError("Internal server error") # HTTP 500
+@app.get(rule="/request-entity-too-large-error")
+def request_entity_too_large_error():
+ raise RequestEntityTooLargeError("Request payload too large") # HTTP 413
+
+
@app.get(rule="/service-error", cors=True)
def service_error():
raise ServiceError(502, "Something went wrong!")
+@app.get(rule="/service-unavailable-error")
+def service_unavailable_error():
+ raise ServiceUnavailableError("Service is temporarily unavailable") # HTTP 503
+
+
@app.get("/todos")
@tracer.capture_method
def get_todos():
diff --git a/examples/event_sources/src/cloudWatchDashboard.py b/examples/event_sources/src/cloudWatchDashboard.py
index ff8b896a806..583f97df68a 100644
--- a/examples/event_sources/src/cloudWatchDashboard.py
+++ b/examples/event_sources/src/cloudWatchDashboard.py
@@ -26,6 +26,6 @@ def lambda_handler(event: CloudWatchDashboardCustomWidgetEvent, context):
"markdown": f"""
Dashboard: {event.widget_context.dashboard_name}
Time Range: {time_range.start} to {time_range.end}
- Theme: {event.widget_context.theme or 'default'}
+ Theme: {event.widget_context.theme or "default"}
""",
}
diff --git a/examples/event_sources/src/s3_batch_operation.py b/examples/event_sources/src/s3_batch_operation.py
index e292d8cae47..81eb5181c41 100644
--- a/examples/event_sources/src/s3_batch_operation.py
+++ b/examples/event_sources/src/s3_batch_operation.py
@@ -33,5 +33,4 @@ def lambda_handler(event: S3BatchOperationEvent, context: LambdaContext):
return response.asdict()
-def do_some_work(s3_client, src_bucket: str, src_key: str):
- ...
+def do_some_work(s3_client, src_bucket: str, src_key: str): ...
diff --git a/examples/homepage/install/arm64/amplify.txt b/examples/homepage/install/arm64/amplify.txt
index a2b1f9458aa..bf37763297b 100644
--- a/examples/homepage/install/arm64/amplify.txt
+++ b/examples/homepage/install/arm64/amplify.txt
@@ -6,7 +6,7 @@
? Do you want to configure advanced settings? Yes
...
? Do you want to enable Lambda layers for this function? Yes
-? Enter up to 5 existing Lambda layer ARNs (comma-separated): arn:aws:lambda:eu-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python312-arm64:11
+? Enter up to 5 existing Lambda layer ARNs (comma-separated): arn:aws:lambda:eu-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python312-arm64:12
❯ amplify push -y
@@ -17,5 +17,5 @@ General information
- Name:
? Which setting do you want to update? Lambda layers configuration
? Do you want to enable Lambda layers for this function? Yes
-? Enter up to 5 existing Lambda layer ARNs (comma-separated): arn:aws:lambda:eu-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python312-arm64:11
+? Enter up to 5 existing Lambda layer ARNs (comma-separated): arn:aws:lambda:eu-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python312-arm64:12
? Do you want to edit the local lambda function now? No
diff --git a/examples/homepage/install/arm64/cdk_arm64.py b/examples/homepage/install/arm64/cdk_arm64.py
index 4aa9ad929ea..a49bdfcec6f 100644
--- a/examples/homepage/install/arm64/cdk_arm64.py
+++ b/examples/homepage/install/arm64/cdk_arm64.py
@@ -3,14 +3,13 @@
class SampleApp(Stack):
-
def __init__(self, scope: Construct, construct_id: str, **kwargs) -> None:
super().__init__(scope, construct_id, **kwargs)
powertools_layer = aws_lambda.LayerVersion.from_layer_version_arn(
self,
id="lambda-powertools",
- layer_version_arn=f"arn:aws:lambda:{Aws.REGION}:017000801446:layer:AWSLambdaPowertoolsPythonV3-python312-arm64:11",
+ layer_version_arn=f"arn:aws:lambda:{Aws.REGION}:017000801446:layer:AWSLambdaPowertoolsPythonV3-python312-arm64:12",
)
aws_lambda.Function(
self,
diff --git a/examples/homepage/install/arm64/pulumi_arm64.py b/examples/homepage/install/arm64/pulumi_arm64.py
index 4eb54d61f22..0c7af65599e 100644
--- a/examples/homepage/install/arm64/pulumi_arm64.py
+++ b/examples/homepage/install/arm64/pulumi_arm64.py
@@ -22,7 +22,7 @@
pulumi.Output.concat(
"arn:aws:lambda:",
aws.get_region_output().name,
- ":017000801446:layer:AWSLambdaPowertoolsPythonV3-python312-arm64:11",
+ ":017000801446:layer:AWSLambdaPowertoolsPythonV3-python312-arm64:12",
),
],
tracing_config={"mode": "Active"},
diff --git a/examples/homepage/install/arm64/sam.yaml b/examples/homepage/install/arm64/sam.yaml
index d2f243e68d5..7eb25480761 100644
--- a/examples/homepage/install/arm64/sam.yaml
+++ b/examples/homepage/install/arm64/sam.yaml
@@ -9,4 +9,4 @@ Resources:
Runtime: python3.12
Handler: app.lambda_handler
Layers:
- - !Sub arn:aws:lambda:${AWS::Region}:017000801446:layer:AWSLambdaPowertoolsPythonV3-python312-arm64:11
+ - !Sub arn:aws:lambda:${AWS::Region}:017000801446:layer:AWSLambdaPowertoolsPythonV3-python312-arm64:12
diff --git a/examples/homepage/install/arm64/serverless.yml b/examples/homepage/install/arm64/serverless.yml
index ea40dc533de..f0a1e4144fe 100644
--- a/examples/homepage/install/arm64/serverless.yml
+++ b/examples/homepage/install/arm64/serverless.yml
@@ -10,4 +10,4 @@ functions:
handler: lambda_function.lambda_handler
architecture: arm64
layers:
- - arn:aws:lambda:${aws:region}:017000801446:layer:AWSLambdaPowertoolsPythonV3-python312-arm64:11
+ - arn:aws:lambda:${aws:region}:017000801446:layer:AWSLambdaPowertoolsPythonV3-python312-arm64:12
diff --git a/examples/homepage/install/arm64/terraform.tf b/examples/homepage/install/arm64/terraform.tf
index 6c4c0611580..0f60ad84516 100644
--- a/examples/homepage/install/arm64/terraform.tf
+++ b/examples/homepage/install/arm64/terraform.tf
@@ -34,7 +34,7 @@ resource "aws_lambda_function" "test_lambda" {
role = aws_iam_role.iam_for_lambda.arn
handler = "index.test"
runtime = "python3.12"
- layers = ["arn:aws:lambda:{region}:017000801446:layer:AWSLambdaPowertoolsPythonV3-python312-arm64:11"]
+ layers = ["arn:aws:lambda:{region}:017000801446:layer:AWSLambdaPowertoolsPythonV3-python312-arm64:12"]
architectures = ["arm64"]
source_code_hash = filebase64sha256("lambda_function_payload.zip")
diff --git a/examples/homepage/install/sar/cdk_sar.py b/examples/homepage/install/sar/cdk_sar.py
index 1d07f4a7098..19502444590 100644
--- a/examples/homepage/install/sar/cdk_sar.py
+++ b/examples/homepage/install/sar/cdk_sar.py
@@ -10,7 +10,6 @@
class SampleApp(Stack):
-
def __init__(self, scope: Construct, id_: str) -> None:
super().__init__(scope, id_)
diff --git a/examples/homepage/install/x86_64/amplify.txt b/examples/homepage/install/x86_64/amplify.txt
index 1d5c2c94f0b..9e295423cea 100644
--- a/examples/homepage/install/x86_64/amplify.txt
+++ b/examples/homepage/install/x86_64/amplify.txt
@@ -6,7 +6,7 @@
? Do you want to configure advanced settings? Yes
...
? Do you want to enable Lambda layers for this function? Yes
-? Enter up to 5 existing Lambda layer ARNs (comma-separated): arn:aws:lambda:eu-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python312-x86_64:11
+? Enter up to 5 existing Lambda layer ARNs (comma-separated): arn:aws:lambda:eu-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python312-x86_64:12
❯ amplify push -y
@@ -17,5 +17,5 @@ General information
- Name:
? Which setting do you want to update? Lambda layers configuration
? Do you want to enable Lambda layers for this function? Yes
-? Enter up to 5 existing Lambda layer ARNs (comma-separated): arn:aws:lambda:eu-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python312-x86_64:11
+? Enter up to 5 existing Lambda layer ARNs (comma-separated): arn:aws:lambda:eu-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV3-python312-x86_64:12
? Do you want to edit the local lambda function now? No
diff --git a/examples/homepage/install/x86_64/cdk_x86.py b/examples/homepage/install/x86_64/cdk_x86.py
index a5be03c893e..159859bf409 100644
--- a/examples/homepage/install/x86_64/cdk_x86.py
+++ b/examples/homepage/install/x86_64/cdk_x86.py
@@ -3,14 +3,13 @@
class SampleApp(Stack):
-
def __init__(self, scope: Construct, construct_id: str, **kwargs) -> None:
super().__init__(scope, construct_id, **kwargs)
powertools_layer = aws_lambda.LayerVersion.from_layer_version_arn(
self,
id="lambda-powertools",
- layer_version_arn=f"arn:aws:lambda:{Aws.REGION}:017000801446:layer:AWSLambdaPowertoolsPythonV3-python312-x86_64:11",
+ layer_version_arn=f"arn:aws:lambda:{Aws.REGION}:017000801446:layer:AWSLambdaPowertoolsPythonV3-python312-x86_64:12",
)
aws_lambda.Function(
self,
diff --git a/examples/homepage/install/x86_64/pulumi_x86.py b/examples/homepage/install/x86_64/pulumi_x86.py
index cd23ad4af2b..8820151b48d 100644
--- a/examples/homepage/install/x86_64/pulumi_x86.py
+++ b/examples/homepage/install/x86_64/pulumi_x86.py
@@ -22,7 +22,7 @@
pulumi.Output.concat(
"arn:aws:lambda:",
aws.get_region_output().name,
- ":017000801446:layer:AWSLambdaPowertoolsPythonV3-python312-x86_64:11",
+ ":017000801446:layer:AWSLambdaPowertoolsPythonV3-python312-x86_64:12",
),
],
tracing_config={"mode": "Active"},
diff --git a/examples/homepage/install/x86_64/sam.yaml b/examples/homepage/install/x86_64/sam.yaml
index 20a71dde5ac..c1ec706d88f 100644
--- a/examples/homepage/install/x86_64/sam.yaml
+++ b/examples/homepage/install/x86_64/sam.yaml
@@ -8,4 +8,4 @@ Resources:
Runtime: python3.12
Handler: app.lambda_handler
Layers:
- - !Sub arn:aws:lambda:${AWS::Region}:017000801446:layer:AWSLambdaPowertoolsPythonV3-python312-x86_64:11
+ - !Sub arn:aws:lambda:${AWS::Region}:017000801446:layer:AWSLambdaPowertoolsPythonV3-python312-x86_64:12
diff --git a/examples/homepage/install/x86_64/serverless.yml b/examples/homepage/install/x86_64/serverless.yml
index 1f322494c4e..17e77fbfad5 100644
--- a/examples/homepage/install/x86_64/serverless.yml
+++ b/examples/homepage/install/x86_64/serverless.yml
@@ -10,4 +10,4 @@ functions:
handler: lambda_function.lambda_handler
architecture: arm64
layers:
- - arn:aws:lambda:${aws:region}:017000801446:layer:AWSLambdaPowertoolsPythonV3-python312-x86_64:11
+ - arn:aws:lambda:${aws:region}:017000801446:layer:AWSLambdaPowertoolsPythonV3-python312-x86_64:12
diff --git a/examples/homepage/install/x86_64/terraform.tf b/examples/homepage/install/x86_64/terraform.tf
index 945d49b069c..b3c6bf940fc 100644
--- a/examples/homepage/install/x86_64/terraform.tf
+++ b/examples/homepage/install/x86_64/terraform.tf
@@ -34,7 +34,7 @@ resource "aws_lambda_function" "test_lambda" {
role = aws_iam_role.iam_for_lambda.arn
handler = "index.test"
runtime = "python3.12"
- layers = ["arn:aws:lambda:{region}:017000801446:layer:AWSLambdaPowertoolsPythonV3-python312-x86_64:11"]
+ layers = ["arn:aws:lambda:{region}:017000801446:layer:AWSLambdaPowertoolsPythonV3-python312-x86_64:12"]
source_code_hash = filebase64sha256("lambda_function_payload.zip")
}
diff --git a/examples/idempotency/src/working_with_custom_idempotency_key_prefix.py b/examples/idempotency/src/working_with_custom_idempotency_key_prefix.py
index eacc2d3254b..b41c3c1c212 100644
--- a/examples/idempotency/src/working_with_custom_idempotency_key_prefix.py
+++ b/examples/idempotency/src/working_with_custom_idempotency_key_prefix.py
@@ -22,7 +22,7 @@ class Payment:
class PaymentError(Exception): ...
-@idempotent(persistence_store=persistence_layer, key_prefix="my_custom_prefix") # (1)!
+@idempotent(persistence_store=persistence_layer, key_prefix="my_custom_prefix") # (1)!
def lambda_handler(event: dict, context: LambdaContext):
try:
payment: Payment = create_subscription_payment(event)
diff --git a/examples/idempotency/src/working_with_custom_idempotency_key_prefix_standalone.py b/examples/idempotency/src/working_with_custom_idempotency_key_prefix_standalone.py
index 2fb8bd92275..4092e23b5ae 100644
--- a/examples/idempotency/src/working_with_custom_idempotency_key_prefix_standalone.py
+++ b/examples/idempotency/src/working_with_custom_idempotency_key_prefix_standalone.py
@@ -29,15 +29,15 @@ class Order:
data_keyword_argument="order",
config=config,
persistence_store=dynamodb,
- key_prefix="my_custom_prefix", # (1)!
+ key_prefix="my_custom_prefix", # (1)!
)
-def process_order(order: Order):
+def process_order(order: Order):
return f"processed order {order.order_id}"
def lambda_handler(event: dict, context: LambdaContext):
# see Lambda timeouts section
- config.register_lambda_context(context)
+ config.register_lambda_context(context)
order_item = OrderItem(sku="fake", description="sample")
order = Order(item=order_item, order_id=1)
diff --git a/examples/jmespath_functions/src/powertools_json_idempotency_jmespath.py b/examples/jmespath_functions/src/powertools_json_idempotency_jmespath.py
index 776d5485741..6aa83a00018 100644
--- a/examples/jmespath_functions/src/powertools_json_idempotency_jmespath.py
+++ b/examples/jmespath_functions/src/powertools_json_idempotency_jmespath.py
@@ -16,8 +16,7 @@
config = IdempotencyConfig(event_key_jmespath="powertools_json(body)")
-class PaymentError(Exception):
- ...
+class PaymentError(Exception): ...
@idempotent(config=config, persistence_store=persistence_layer)
diff --git a/examples/logger/sam/template.yaml b/examples/logger/sam/template.yaml
index b0043a03351..91351149c83 100644
--- a/examples/logger/sam/template.yaml
+++ b/examples/logger/sam/template.yaml
@@ -14,7 +14,7 @@ Globals:
Layers:
# Find the latest Layer version in the official documentation
# https://docs.powertools.aws.dev/lambda/python/latest/#lambda-layer
- - !Sub arn:aws:lambda:${AWS::Region}:017000801446:layer:AWSLambdaPowertoolsPythonV3-python312-x86_64:11
+ - !Sub arn:aws:lambda:${AWS::Region}:017000801446:layer:AWSLambdaPowertoolsPythonV3-python312-x86_64:12
Resources:
LoggerLambdaHandlerExample:
diff --git a/examples/logger/src/append_keys_vs_extra.py b/examples/logger/src/append_keys_vs_extra.py
index 432dd1c23aa..5953df2de14 100644
--- a/examples/logger/src/append_keys_vs_extra.py
+++ b/examples/logger/src/append_keys_vs_extra.py
@@ -8,8 +8,7 @@
logger = Logger(service="payment")
-class PaymentError(Exception):
- ...
+class PaymentError(Exception): ...
def lambda_handler(event, context):
diff --git a/examples/logger/src/logging_exception_notes.py b/examples/logger/src/logging_exception_notes.py
new file mode 100644
index 00000000000..7c05427b6e6
--- /dev/null
+++ b/examples/logger/src/logging_exception_notes.py
@@ -0,0 +1,19 @@
+import requests
+
+from aws_lambda_powertools import Logger
+from aws_lambda_powertools.utilities.typing import LambdaContext
+
+ENDPOINT = "https://httpbin.org/status/500"
+logger = Logger(serialize_stacktrace=False)
+
+
+def lambda_handler(event: dict, context: LambdaContext) -> str:
+ try:
+ ret = requests.get(ENDPOINT)
+ ret.raise_for_status()
+ except requests.HTTPError as e:
+ e.add_note("Can't connect to the endpoint") # type: ignore[attr-defined]
+ logger.exception(e)
+ raise RuntimeError("Unable to fullfil request") from e
+
+ return "hello world"
diff --git a/examples/logger/src/logging_exception_notes_output.json b/examples/logger/src/logging_exception_notes_output.json
new file mode 100644
index 00000000000..f50f12d689a
--- /dev/null
+++ b/examples/logger/src/logging_exception_notes_output.json
@@ -0,0 +1,12 @@
+{
+ "level": "ERROR",
+ "location": "collect.handler:15",
+ "message": "Received a HTTP 5xx error",
+ "timestamp": "2021-05-03 11:47:12,494+0000",
+ "service": "payment",
+ "exception_name": "RuntimeError",
+ "exception": "Traceback (most recent call last):\n File \"\", line 2, in RuntimeError: Unable to fullfil request",
+ "exception_notes":[
+ "Can't connect to the endpoint"
+ ]
+}
diff --git a/examples/logger/src/logging_exceptions.py b/examples/logger/src/logging_exceptions.py
index 05e5c1a1e15..20a45102992 100644
--- a/examples/logger/src/logging_exceptions.py
+++ b/examples/logger/src/logging_exceptions.py
@@ -3,8 +3,8 @@
from aws_lambda_powertools import Logger
from aws_lambda_powertools.utilities.typing import LambdaContext
-ENDPOINT = "http://httpbin.org/status/500"
-logger = Logger()
+ENDPOINT = "https://httpbin.org/status/500"
+logger = Logger(serialize_stacktrace=False)
def lambda_handler(event: dict, context: LambdaContext) -> str:
diff --git a/examples/logger/src/logging_stacktrace.py b/examples/logger/src/logging_stacktrace.py
index 128836f5138..40e7e052be8 100644
--- a/examples/logger/src/logging_stacktrace.py
+++ b/examples/logger/src/logging_stacktrace.py
@@ -3,7 +3,7 @@
from aws_lambda_powertools import Logger
from aws_lambda_powertools.utilities.typing import LambdaContext
-ENDPOINT = "http://httpbin.org/status/500"
+ENDPOINT = "https://httpbin.org/status/500"
logger = Logger(serialize_stacktrace=True)
diff --git a/examples/metrics/sam/template.yaml b/examples/metrics/sam/template.yaml
index 206d719b6a8..1794bc258cf 100644
--- a/examples/metrics/sam/template.yaml
+++ b/examples/metrics/sam/template.yaml
@@ -16,7 +16,7 @@ Globals:
Layers:
# Find the latest Layer version in the official documentation
# https://docs.powertools.aws.dev/lambda/python/latest/#lambda-layer
- - !Sub arn:aws:lambda:${AWS::Region}:017000801446:layer:AWSLambdaPowertoolsPythonV3-python312-x86_64:11
+ - !Sub arn:aws:lambda:${AWS::Region}:017000801446:layer:AWSLambdaPowertoolsPythonV3-python312-x86_64:12
Resources:
CaptureLambdaHandlerExample:
diff --git a/examples/metrics/src/capture_cold_start_metric.py b/examples/metrics/src/capture_cold_start_metric.py
index 93468eba345..0d2da53b0bf 100644
--- a/examples/metrics/src/capture_cold_start_metric.py
+++ b/examples/metrics/src/capture_cold_start_metric.py
@@ -5,5 +5,4 @@
@metrics.log_metrics(capture_cold_start_metric=True)
-def lambda_handler(event: dict, context: LambdaContext):
- ...
+def lambda_handler(event: dict, context: LambdaContext): ...
diff --git a/examples/metrics/src/flush_metrics.py b/examples/metrics/src/flush_metrics.py
index a66ce07cbf7..72ad71f7c6e 100644
--- a/examples/metrics/src/flush_metrics.py
+++ b/examples/metrics/src/flush_metrics.py
@@ -5,7 +5,7 @@
metrics = Metrics()
-def book_flight(flight_id: str, **kwargs):
+def book_flight(flight_id: str, **kwargs):
# logic to book flight
...
metrics.add_metric(name="SuccessfulBooking", unit=MetricUnit.Count, value=1)
diff --git a/examples/metrics/src/single_metric_with_different_timestamp.py b/examples/metrics/src/single_metric_with_different_timestamp.py
index bd99041c007..10a274bbc41 100644
--- a/examples/metrics/src/single_metric_with_different_timestamp.py
+++ b/examples/metrics/src/single_metric_with_different_timestamp.py
@@ -6,9 +6,7 @@
def lambda_handler(event: dict, context: LambdaContext):
-
for record in event:
-
record_id: str = record.get("record_id")
amount: int = record.get("amount")
timestamp: int = record.get("timestamp")
diff --git a/examples/metrics_datadog/sam/template.yaml b/examples/metrics_datadog/sam/template.yaml
index d2b740763c6..eefcec17d37 100644
--- a/examples/metrics_datadog/sam/template.yaml
+++ b/examples/metrics_datadog/sam/template.yaml
@@ -20,7 +20,7 @@ Globals:
Layers:
# Find the latest Layer version in the official documentation
# https://docs.powertools.aws.dev/lambda/python/latest/#lambda-layer
- - !Sub arn:aws:lambda:${AWS::Region}:017000801446:layer:AWSLambdaPowertoolsPythonV3-python312-x86_64:11
+ - !Sub arn:aws:lambda:${AWS::Region}:017000801446:layer:AWSLambdaPowertoolsPythonV3-python312-x86_64:12
# Find the latest Layer version in the Datadog official documentation
# Datadog SDK
diff --git a/examples/parameters/src/builtin_provider_dynamodb_custom_endpoint.py b/examples/parameters/src/builtin_provider_dynamodb_custom_endpoint.py
index e77506f27d7..3bc054f00fe 100644
--- a/examples/parameters/src/builtin_provider_dynamodb_custom_endpoint.py
+++ b/examples/parameters/src/builtin_provider_dynamodb_custom_endpoint.py
@@ -9,7 +9,6 @@
def lambda_handler(event: dict, context: LambdaContext):
-
try:
# Usually an endpoint is not sensitive data, so we store it in DynamoDB Table
endpoint_comments: Any = dynamodb_provider.get("comments_endpoint")
diff --git a/examples/parameters/src/builtin_provider_dynamodb_recursive_parameter.py b/examples/parameters/src/builtin_provider_dynamodb_recursive_parameter.py
index 7db0d4d913a..48f1cd9bcc1 100644
--- a/examples/parameters/src/builtin_provider_dynamodb_recursive_parameter.py
+++ b/examples/parameters/src/builtin_provider_dynamodb_recursive_parameter.py
@@ -9,7 +9,6 @@
def lambda_handler(event: dict, context: LambdaContext):
-
try:
# Retrieve multiple parameters using HASH KEY
all_parameters: Any = dynamodb_provider.get_multiple("config")
@@ -17,7 +16,6 @@ def lambda_handler(event: dict, context: LambdaContext):
limit = 2
for parameter, value in all_parameters.items():
-
if parameter == "endpoint_comments":
endpoint_comments = value
diff --git a/examples/parameters/src/builtin_provider_dynamodb_single_parameter.py b/examples/parameters/src/builtin_provider_dynamodb_single_parameter.py
index 036058f2b33..490b32715c6 100644
--- a/examples/parameters/src/builtin_provider_dynamodb_single_parameter.py
+++ b/examples/parameters/src/builtin_provider_dynamodb_single_parameter.py
@@ -9,7 +9,6 @@
def lambda_handler(event: dict, context: LambdaContext):
-
try:
# Usually an endpoint is not sensitive data, so we store it in DynamoDB Table
endpoint_comments: Any = dynamodb_provider.get("comments_endpoint")
diff --git a/examples/parameters/src/builtin_provider_secret.py b/examples/parameters/src/builtin_provider_secret.py
index 449664c1863..5be600dd71c 100644
--- a/examples/parameters/src/builtin_provider_secret.py
+++ b/examples/parameters/src/builtin_provider_secret.py
@@ -11,7 +11,6 @@
def lambda_handler(event: dict, context: LambdaContext):
-
try:
# Usually an endpoint is not sensitive data, so we store it in SSM Parameters
endpoint_comments: Any = parameters.get_parameter("/lambda-powertools/endpoint_comments")
diff --git a/examples/parameters/src/builtin_provider_ssm_with_no_recursive.py b/examples/parameters/src/builtin_provider_ssm_with_no_recursive.py
index 0f92d27bfbc..a3f9ff4b2a1 100644
--- a/examples/parameters/src/builtin_provider_ssm_with_no_recursive.py
+++ b/examples/parameters/src/builtin_provider_ssm_with_no_recursive.py
@@ -8,8 +8,7 @@
ssm_provider = parameters.SSMProvider()
-class ConfigNotFound(Exception):
- ...
+class ConfigNotFound(Exception): ...
def lambda_handler(event: dict, context: LambdaContext):
@@ -22,7 +21,6 @@ def lambda_handler(event: dict, context: LambdaContext):
endpoint_comments = "https://jsonplaceholder.typicode.com/comments/"
for parameter, value in all_parameters.items():
-
# query parameter is used to query endpoint
if "query" in parameter:
endpoint_comments = f"{endpoint_comments}{value}"
diff --git a/examples/parameters/src/getting_started_secret.py b/examples/parameters/src/getting_started_secret.py
index 1f10394e834..4f03fc14293 100644
--- a/examples/parameters/src/getting_started_secret.py
+++ b/examples/parameters/src/getting_started_secret.py
@@ -7,7 +7,6 @@
def lambda_handler(event: dict, context: LambdaContext):
-
try:
# Usually an endpoint is not sensitive data, so we store it in SSM Parameters
endpoint_comments: Any = parameters.get_parameter("/lambda-powertools/endpoint_comments")
diff --git a/examples/parameters/src/recursive_ssm_parameter_force_fetch.py b/examples/parameters/src/recursive_ssm_parameter_force_fetch.py
index 6082a0173d4..29bdfed7328 100644
--- a/examples/parameters/src/recursive_ssm_parameter_force_fetch.py
+++ b/examples/parameters/src/recursive_ssm_parameter_force_fetch.py
@@ -13,7 +13,6 @@ def lambda_handler(event: dict, context: LambdaContext):
endpoint_comments = "https://jsonplaceholder.typicode.com/noexists/"
for parameter, value in all_parameters.items():
-
if parameter == "endpoint_comments":
endpoint_comments = value
diff --git a/examples/parameters/src/recursive_ssm_parameter_with_cache.py b/examples/parameters/src/recursive_ssm_parameter_with_cache.py
index 9cf48b39dde..7d1afe572bc 100644
--- a/examples/parameters/src/recursive_ssm_parameter_with_cache.py
+++ b/examples/parameters/src/recursive_ssm_parameter_with_cache.py
@@ -13,7 +13,6 @@ def lambda_handler(event: dict, context: LambdaContext):
endpoint_comments = "https://jsonplaceholder.typicode.com/noexists/"
for parameter, value in all_parameters.items():
-
if parameter == "endpoint_comments":
endpoint_comments = value
diff --git a/examples/parameters/src/secret_force_fetch.py b/examples/parameters/src/secret_force_fetch.py
index 121d9f57bfb..3578cbc3a58 100644
--- a/examples/parameters/src/secret_force_fetch.py
+++ b/examples/parameters/src/secret_force_fetch.py
@@ -7,7 +7,6 @@
def lambda_handler(event: dict, context: LambdaContext):
-
try:
# Usually an endpoint is not sensitive data, so we store it in SSM Parameters
endpoint_comments: Any = parameters.get_parameter("/lambda-powertools/endpoint_comments")
diff --git a/examples/parameters/src/secret_with_cache.py b/examples/parameters/src/secret_with_cache.py
index 8d3ed927107..ed9b16084ca 100644
--- a/examples/parameters/src/secret_with_cache.py
+++ b/examples/parameters/src/secret_with_cache.py
@@ -7,7 +7,6 @@
def lambda_handler(event: dict, context: LambdaContext):
-
try:
# Usually an endpoint is not sensitive data, so we store it in SSM Parameters
endpoint_comments: Any = parameters.get_parameter("/lambda-powertools/endpoint_comments")
diff --git a/examples/parameters/src/working_with_own_provider_s3.py b/examples/parameters/src/working_with_own_provider_s3.py
index d4f011a9e23..eb3326724ba 100644
--- a/examples/parameters/src/working_with_own_provider_s3.py
+++ b/examples/parameters/src/working_with_own_provider_s3.py
@@ -12,7 +12,6 @@
def lambda_handler(event: dict, context: LambdaContext):
-
try:
# Retrieve a single parameter using key
endpoint_comments: Any = s3_provider.get("comments_endpoint")
diff --git a/examples/tracer/sam/template.yaml b/examples/tracer/sam/template.yaml
index 254a033a81e..32591c121c9 100644
--- a/examples/tracer/sam/template.yaml
+++ b/examples/tracer/sam/template.yaml
@@ -13,7 +13,7 @@ Globals:
Layers:
# Find the latest Layer version in the official documentation
# https://docs.powertools.aws.dev/lambda/python/latest/#lambda-layer
- - !Sub arn:aws:lambda:${AWS::Region}:017000801446:layer:AWSLambdaPowertoolsPythonV3-python312-x86_64:11
+ - !Sub arn:aws:lambda:${AWS::Region}:017000801446:layer:AWSLambdaPowertoolsPythonV3-python312-x86_64:12
Resources:
CaptureLambdaHandlerExample:
diff --git a/examples/tracer/src/disable_capture_error.py b/examples/tracer/src/disable_capture_error.py
index 59fc2d2376a..85497ee906b 100644
--- a/examples/tracer/src/disable_capture_error.py
+++ b/examples/tracer/src/disable_capture_error.py
@@ -9,8 +9,7 @@
ENDPOINT = os.getenv("PAYMENT_API", "")
-class PaymentError(Exception):
- ...
+class PaymentError(Exception): ...
@tracer.capture_method(capture_error=False)
diff --git a/examples/tracer/src/ignore_endpoints.py b/examples/tracer/src/ignore_endpoints.py
index 0fe256aeee9..3b73af17481 100644
--- a/examples/tracer/src/ignore_endpoints.py
+++ b/examples/tracer/src/ignore_endpoints.py
@@ -13,8 +13,7 @@
tracer.ignore_endpoint(hostname=f"*.{ENDPOINT}", urls=IGNORE_URLS) # `.ENDPOINT`
-class PaymentError(Exception):
- ...
+class PaymentError(Exception): ...
@tracer.capture_method(capture_error=False)
diff --git a/examples/validation/src/getting_started_validator_decorator_function.py b/examples/validation/src/getting_started_validator_decorator_function.py
index 1e9b1bd2a09..3ad416c9211 100644
--- a/examples/validation/src/getting_started_validator_decorator_function.py
+++ b/examples/validation/src/getting_started_validator_decorator_function.py
@@ -12,8 +12,7 @@
ALLOWED_IPS = parameters.get_parameter("/lambda-powertools/allowed_ips")
-class UserPermissionsError(Exception):
- ...
+class UserPermissionsError(Exception): ...
@dataclass
diff --git a/mkdocs.yml b/mkdocs.yml
index 1bd460bf218..de566fb1f08 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -24,6 +24,7 @@ nav:
- Event Handler:
- core/event_handler/api_gateway.md
- core/event_handler/appsync.md
+ - core/event_handler/appsync_events.md
- core/event_handler/bedrock_agents.md
- utilities/parameters.md
- utilities/batch.md
diff --git a/mypy.ini b/mypy.ini
index 3e834abcc39..0021372f416 100644
--- a/mypy.ini
+++ b/mypy.ini
@@ -59,3 +59,6 @@ ignore_missing_imports = True
[mypy-ujson]
ignore_missing_imports = True
+
+[mypy-fastjsonschema]
+ignore_missing_imports = True
diff --git a/package-lock.json b/package-lock.json
index e61ece6f644..8ab9192b389 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -11,13 +11,13 @@
"package-lock.json": "^1.0.0"
},
"devDependencies": {
- "aws-cdk": "^2.1006.0"
+ "aws-cdk": "^2.1012.0"
}
},
"node_modules/aws-cdk": {
- "version": "2.1006.0",
- "resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.1006.0.tgz",
- "integrity": "sha512-6qYnCt4mBN+3i/5F+FC2yMETkDHY/IL7gt3EuqKVPcaAO4jU7oXfVSlR60CYRkZWL4fnAurUV14RkJuJyVG/IA==",
+ "version": "2.1012.0",
+ "resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.1012.0.tgz",
+ "integrity": "sha512-C6jSWkqP0hkY2Cs300VJHjspmTXDTMfB813kwZvRbd/OsKBfTBJBbYU16VoLAp1LVEOnQMf8otSlaSgzVF0X9A==",
"dev": true,
"license": "Apache-2.0",
"bin": {
diff --git a/package.json b/package.json
index b02e87809b9..95835b395ee 100644
--- a/package.json
+++ b/package.json
@@ -2,7 +2,7 @@
"name": "aws-lambda-powertools-python-e2e",
"version": "1.0.0",
"devDependencies": {
- "aws-cdk": "^2.1006.0"
+ "aws-cdk": "^2.1012.0"
},
"dependencies": {
"package-lock.json": "^1.0.0"
diff --git a/poetry.lock b/poetry.lock
index 5e22ef7dcf7..e4eb5744f04 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -99,18 +99,18 @@ tests-no-zope = ["attrs[tests-mypy]", "cloudpickle ; platform_python_implementat
[[package]]
name = "aws-cdk-asset-awscli-v1"
-version = "2.2.228"
+version = "2.2.230"
description = "A library that contains the AWS CLI for use in Lambda Layers"
optional = false
python-versions = "~=3.9"
groups = ["dev"]
files = [
- {file = "aws_cdk.asset_awscli_v1-2.2.228-py3-none-any.whl", hash = "sha256:a79a76948a32e672c5f0743384c5abea14ee8b802b4a080f63a1f310fd7d5861"},
- {file = "aws_cdk_asset_awscli_v1-2.2.228.tar.gz", hash = "sha256:65def76adc852eb9fdab16ebe1b9193ce9892546fdb13ab0d0c3211c220887cc"},
+ {file = "aws_cdk_asset_awscli_v1-2.2.230-py3-none-any.whl", hash = "sha256:e41bf095ca74af9924e9b2e3244091ba3298f40b938b2397634f551d6ec8a099"},
+ {file = "aws_cdk_asset_awscli_v1-2.2.230.tar.gz", hash = "sha256:9e2281ce1ffe2cdb8d433bd26d3b2c5767eac282871064ab66de9a2ecc987fec"},
]
[package.dependencies]
-jsii = ">=1.109.0,<2.0.0"
+jsii = ">=1.110.0,<2.0.0"
publication = ">=0.0.3"
typeguard = ">=2.13.3,<4.3.0"
@@ -211,33 +211,33 @@ typeguard = ">=2.13.3,<2.14.0"
[[package]]
name = "aws-cdk-aws-lambda-python-alpha"
-version = "2.186.0a0"
+version = "2.191.0a0"
description = "The CDK Construct Library for AWS Lambda in Python"
optional = false
python-versions = "~=3.9"
groups = ["dev"]
files = [
- {file = "aws_cdk.aws_lambda_python_alpha-2.186.0a0-py3-none-any.whl", hash = "sha256:716c64c9fe7e87f50428726e48cb1a67ba7f2dbfdc20327ecb503dd7324522ef"},
- {file = "aws_cdk_aws_lambda_python_alpha-2.186.0a0.tar.gz", hash = "sha256:e067e20aa1b5e9c698a7f66568471a6a0b66bf72e46d1ab1961d497473c503a0"},
+ {file = "aws_cdk_aws_lambda_python_alpha-2.191.0a0-py3-none-any.whl", hash = "sha256:6d04fbcbae87b60720a35cdfafaf78fdd1be75cfba775086d27d92a5725a8f18"},
+ {file = "aws_cdk_aws_lambda_python_alpha-2.191.0a0.tar.gz", hash = "sha256:93f920d4ede008bd8868cc59f66078e42d1df8e21b17d9a5179b07d1a9daeae0"},
]
[package.dependencies]
-aws-cdk-lib = ">=2.186.0,<3.0.0"
+aws-cdk-lib = ">=2.191.0,<3.0.0"
constructs = ">=10.0.0,<11.0.0"
-jsii = ">=1.109.0,<2.0.0"
+jsii = ">=1.110.0,<2.0.0"
publication = ">=0.0.3"
typeguard = ">=2.13.3,<4.3.0"
[[package]]
name = "aws-cdk-cloud-assembly-schema"
-version = "40.7.0"
+version = "41.2.0"
description = "Schema for the protocol between CDK framework and CDK CLI"
optional = false
python-versions = "~=3.8"
groups = ["dev"]
files = [
- {file = "aws_cdk.cloud_assembly_schema-40.7.0-py3-none-any.whl", hash = "sha256:a86de4f46a72f9445f0a0ae646c348702041047c72d10b76e3b4c8dc5e460ee1"},
- {file = "aws_cdk_cloud_assembly_schema-40.7.0.tar.gz", hash = "sha256:8269a74cce261e56750290a2492f04d0e6825913d321a8ab17ba3b5f872fc193"},
+ {file = "aws_cdk.cloud_assembly_schema-41.2.0-py3-none-any.whl", hash = "sha256:779ca7e3edb02695e0a94a1f38e322b04fbe192cd7944553f80b681a21edd670"},
+ {file = "aws_cdk_cloud_assembly_schema-41.2.0.tar.gz", hash = "sha256:7064ac13f6944fd53f8d8eace611d3c5d8db7014049d629f5c47ede8dc5f2e3b"},
]
[package.dependencies]
@@ -247,22 +247,22 @@ typeguard = ">=2.13.3,<4.3.0"
[[package]]
name = "aws-cdk-lib"
-version = "2.186.0"
+version = "2.191.0"
description = "Version 2 of the AWS Cloud Development Kit library"
optional = false
python-versions = "~=3.9"
groups = ["dev"]
files = [
- {file = "aws_cdk_lib-2.186.0-py3-none-any.whl", hash = "sha256:230bfb2bcef5f9a7d763ba20d72eb3d225f34ad43d894083e0a02a5c248e8e5c"},
- {file = "aws_cdk_lib-2.186.0.tar.gz", hash = "sha256:d1371c158ae7ebd0ed5ac047cff66a349836f0bcf7c85654d1c78e207f305350"},
+ {file = "aws_cdk_lib-2.191.0-py3-none-any.whl", hash = "sha256:4e9b04be4fbcaef592a2a9446ce1a4700294f03b3d1a2290343ca9080e1a0353"},
+ {file = "aws_cdk_lib-2.191.0.tar.gz", hash = "sha256:db8bd5c8f03cb6ffb3ee31e05a4c1e662d5da54078a0503d7d9f6c0568fb6248"},
]
[package.dependencies]
-"aws-cdk.asset-awscli-v1" = ">=2.2.227,<3.0.0"
+"aws-cdk.asset-awscli-v1" = ">=2.2.229,<3.0.0"
"aws-cdk.asset-node-proxy-agent-v6" = ">=2.1.0,<3.0.0"
-"aws-cdk.cloud-assembly-schema" = ">=40.7.0,<41.0.0"
+"aws-cdk.cloud-assembly-schema" = ">=41.0.0,<42.0.0"
constructs = ">=10.0.0,<11.0.0"
-jsii = ">=1.109.0,<2.0.0"
+jsii = ">=1.110.0,<2.0.0"
publication = ">=0.0.3"
typeguard = ">=2.13.3,<4.3.0"
@@ -305,21 +305,21 @@ requests = ">=0.14.0"
[[package]]
name = "aws-sam-translator"
-version = "1.95.0"
+version = "1.97.0"
description = "AWS SAM Translator is a library that transform SAM templates into AWS CloudFormation templates"
optional = false
python-versions = "!=4.0,<=4.0,>=3.8"
groups = ["dev"]
files = [
- {file = "aws_sam_translator-1.95.0-py3-none-any.whl", hash = "sha256:c9e0f22cbe83c768f7d20a3afb7e654bd6bfc087b387528bd48e98366b82ae40"},
- {file = "aws_sam_translator-1.95.0.tar.gz", hash = "sha256:fd2b891fc4cbdde1e06130eaf2710de5cc74442a656b7859b3840691144494cf"},
+ {file = "aws_sam_translator-1.97.0-py3-none-any.whl", hash = "sha256:305701ab49eb546fd720b3682e99cadcd43539f4ddb8395ea03c90c9e14d3325"},
+ {file = "aws_sam_translator-1.97.0.tar.gz", hash = "sha256:6f7ec94de0a9b220dd1f1a0bf7e2df95dd44a85592301ee830744da2f209b7e6"},
]
[package.dependencies]
boto3 = ">=1.19.5,<2.dev0"
jsonschema = ">=3.2,<5"
pydantic = ">=1.8,<1.10.15 || >1.10.15,<1.10.17 || >1.10.17,<3"
-typing-extensions = ">=4.4"
+typing_extensions = ">=4.4"
[package.extras]
dev = ["black (==24.3.0)", "boto3 (>=1.23,<2)", "boto3-stubs[appconfig,serverlessrepo] (>=1.19.5,<2.dev0)", "coverage (>=5.3,<8)", "dateparser (>=1.1,<2.0)", "mypy (>=1.3.0,<1.4.0)", "parameterized (>=0.7,<1.0)", "pytest (>=6.2,<8)", "pytest-cov (>=2.10,<5)", "pytest-env (>=0.6,<1)", "pytest-rerunfailures (>=9.1,<12)", "pytest-xdist (>=2.5,<4)", "pyyaml (>=6.0,<7.0)", "requests (>=2.28,<3.0)", "ruamel.yaml (==0.17.21)", "ruff (>=0.4.5,<0.5.0)", "tenacity (>=8.0,<9.0)", "types-PyYAML (>=6.0,<7.0)", "types-jsonschema (>=3.2,<4.0)"]
@@ -400,53 +400,6 @@ test = ["beautifulsoup4 (>=4.8.0)", "coverage (>=4.5.4)", "fixtures (>=3.0.0)",
toml = ["tomli (>=1.1.0) ; python_version < \"3.11\""]
yaml = ["PyYAML"]
-[[package]]
-name = "black"
-version = "25.1.0"
-description = "The uncompromising code formatter."
-optional = false
-python-versions = ">=3.9"
-groups = ["dev"]
-files = [
- {file = "black-25.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:759e7ec1e050a15f89b770cefbf91ebee8917aac5c20483bc2d80a6c3a04df32"},
- {file = "black-25.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0e519ecf93120f34243e6b0054db49c00a35f84f195d5bce7e9f5cfc578fc2da"},
- {file = "black-25.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:055e59b198df7ac0b7efca5ad7ff2516bca343276c466be72eb04a3bcc1f82d7"},
- {file = "black-25.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:db8ea9917d6f8fc62abd90d944920d95e73c83a5ee3383493e35d271aca872e9"},
- {file = "black-25.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a39337598244de4bae26475f77dda852ea00a93bd4c728e09eacd827ec929df0"},
- {file = "black-25.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:96c1c7cd856bba8e20094e36e0f948718dc688dba4a9d78c3adde52b9e6c2299"},
- {file = "black-25.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bce2e264d59c91e52d8000d507eb20a9aca4a778731a08cfff7e5ac4a4bb7096"},
- {file = "black-25.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:172b1dbff09f86ce6f4eb8edf9dede08b1fce58ba194c87d7a4f1a5aa2f5b3c2"},
- {file = "black-25.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4b60580e829091e6f9238c848ea6750efed72140b91b048770b64e74fe04908b"},
- {file = "black-25.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1e2978f6df243b155ef5fa7e558a43037c3079093ed5d10fd84c43900f2d8ecc"},
- {file = "black-25.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3b48735872ec535027d979e8dcb20bf4f70b5ac75a8ea99f127c106a7d7aba9f"},
- {file = "black-25.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:ea0213189960bda9cf99be5b8c8ce66bb054af5e9e861249cd23471bd7b0b3ba"},
- {file = "black-25.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8f0b18a02996a836cc9c9c78e5babec10930862827b1b724ddfe98ccf2f2fe4f"},
- {file = "black-25.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:afebb7098bfbc70037a053b91ae8437c3857482d3a690fefc03e9ff7aa9a5fd3"},
- {file = "black-25.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:030b9759066a4ee5e5aca28c3c77f9c64789cdd4de8ac1df642c40b708be6171"},
- {file = "black-25.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:a22f402b410566e2d1c950708c77ebf5ebd5d0d88a6a2e87c86d9fb48afa0d18"},
- {file = "black-25.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a1ee0a0c330f7b5130ce0caed9936a904793576ef4d2b98c40835d6a65afa6a0"},
- {file = "black-25.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f3df5f1bf91d36002b0a75389ca8663510cf0531cca8aa5c1ef695b46d98655f"},
- {file = "black-25.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d9e6827d563a2c820772b32ce8a42828dc6790f095f441beef18f96aa6f8294e"},
- {file = "black-25.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:bacabb307dca5ebaf9c118d2d2f6903da0d62c9faa82bd21a33eecc319559355"},
- {file = "black-25.1.0-py3-none-any.whl", hash = "sha256:95e8176dae143ba9097f351d174fdaf0ccd29efb414b362ae3fd72bf0f710717"},
- {file = "black-25.1.0.tar.gz", hash = "sha256:33496d5cd1222ad73391352b4ae8da15253c5de89b93a80b3e2c8d9a19ec2666"},
-]
-
-[package.dependencies]
-click = ">=8.0.0"
-mypy-extensions = ">=0.4.3"
-packaging = ">=22.0"
-pathspec = ">=0.9.0"
-platformdirs = ">=2"
-tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""}
-typing-extensions = {version = ">=4.0.1", markers = "python_version < \"3.11\""}
-
-[package.extras]
-colorama = ["colorama (>=0.4.3)"]
-d = ["aiohttp (>=3.10)"]
-jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"]
-uvloop = ["uvloop (>=0.15.2)"]
-
[[package]]
name = "boto3"
version = "1.37.14"
@@ -469,441 +422,441 @@ crt = ["botocore[crt] (>=1.21.0,<2.0a0)"]
[[package]]
name = "boto3-stubs"
-version = "1.37.23"
-description = "Type annotations for boto3 1.37.23 generated with mypy-boto3-builder 8.10.1"
+version = "1.38.1"
+description = "Type annotations for boto3 1.38.1 generated with mypy-boto3-builder 8.10.1"
optional = false
python-versions = ">=3.8"
groups = ["dev"]
files = [
- {file = "boto3_stubs-1.37.23-py3-none-any.whl", hash = "sha256:a00884a3df819bdc6b040c857e57a87b4f33df963ee88f8f406b13bf2cd983ca"},
- {file = "boto3_stubs-1.37.23.tar.gz", hash = "sha256:011f06dadcd5ef3c627ec9808b9afa4e1837b0f009d82b8209f12a84ffbb3867"},
+ {file = "boto3_stubs-1.38.1-py3-none-any.whl", hash = "sha256:3501f98c39b8c2d613b1138a4e8881ceef2ac9497ac030be47cf4336f1aa0573"},
+ {file = "boto3_stubs-1.38.1.tar.gz", hash = "sha256:25b03fdbda288c1576fbe002ecf40088e9f5d6cdf0518de8a84a7467aa898092"},
]
[package.dependencies]
botocore-stubs = "*"
-mypy-boto3-appconfig = {version = ">=1.37.0,<1.38.0", optional = true, markers = "extra == \"appconfig\""}
-mypy-boto3-appconfigdata = {version = ">=1.37.0,<1.38.0", optional = true, markers = "extra == \"appconfigdata\""}
-mypy-boto3-cloudformation = {version = ">=1.37.0,<1.38.0", optional = true, markers = "extra == \"cloudformation\""}
-mypy-boto3-cloudwatch = {version = ">=1.37.0,<1.38.0", optional = true, markers = "extra == \"cloudwatch\""}
-mypy-boto3-dynamodb = {version = ">=1.37.0,<1.38.0", optional = true, markers = "extra == \"dynamodb\""}
-mypy-boto3-lambda = {version = ">=1.37.0,<1.38.0", optional = true, markers = "extra == \"lambda\""}
-mypy-boto3-logs = {version = ">=1.37.0,<1.38.0", optional = true, markers = "extra == \"logs\""}
-mypy-boto3-s3 = {version = ">=1.37.0,<1.38.0", optional = true, markers = "extra == \"s3\""}
-mypy-boto3-secretsmanager = {version = ">=1.37.0,<1.38.0", optional = true, markers = "extra == \"secretsmanager\""}
-mypy-boto3-ssm = {version = ">=1.37.0,<1.38.0", optional = true, markers = "extra == \"ssm\""}
-mypy-boto3-xray = {version = ">=1.37.0,<1.38.0", optional = true, markers = "extra == \"xray\""}
+mypy-boto3-appconfig = {version = ">=1.38.0,<1.39.0", optional = true, markers = "extra == \"appconfig\""}
+mypy-boto3-appconfigdata = {version = ">=1.38.0,<1.39.0", optional = true, markers = "extra == \"appconfigdata\""}
+mypy-boto3-cloudformation = {version = ">=1.38.0,<1.39.0", optional = true, markers = "extra == \"cloudformation\""}
+mypy-boto3-cloudwatch = {version = ">=1.38.0,<1.39.0", optional = true, markers = "extra == \"cloudwatch\""}
+mypy-boto3-dynamodb = {version = ">=1.38.0,<1.39.0", optional = true, markers = "extra == \"dynamodb\""}
+mypy-boto3-lambda = {version = ">=1.38.0,<1.39.0", optional = true, markers = "extra == \"lambda\""}
+mypy-boto3-logs = {version = ">=1.38.0,<1.39.0", optional = true, markers = "extra == \"logs\""}
+mypy-boto3-s3 = {version = ">=1.38.0,<1.39.0", optional = true, markers = "extra == \"s3\""}
+mypy-boto3-secretsmanager = {version = ">=1.38.0,<1.39.0", optional = true, markers = "extra == \"secretsmanager\""}
+mypy-boto3-ssm = {version = ">=1.38.0,<1.39.0", optional = true, markers = "extra == \"ssm\""}
+mypy-boto3-xray = {version = ">=1.38.0,<1.39.0", optional = true, markers = "extra == \"xray\""}
types-s3transfer = "*"
typing-extensions = {version = ">=4.1.0", markers = "python_version < \"3.12\""}
[package.extras]
-accessanalyzer = ["mypy-boto3-accessanalyzer (>=1.37.0,<1.38.0)"]
-account = ["mypy-boto3-account (>=1.37.0,<1.38.0)"]
-acm = ["mypy-boto3-acm (>=1.37.0,<1.38.0)"]
-acm-pca = ["mypy-boto3-acm-pca (>=1.37.0,<1.38.0)"]
-all = ["mypy-boto3-accessanalyzer (>=1.37.0,<1.38.0)", "mypy-boto3-account (>=1.37.0,<1.38.0)", "mypy-boto3-acm (>=1.37.0,<1.38.0)", "mypy-boto3-acm-pca (>=1.37.0,<1.38.0)", "mypy-boto3-amp (>=1.37.0,<1.38.0)", "mypy-boto3-amplify (>=1.37.0,<1.38.0)", "mypy-boto3-amplifybackend (>=1.37.0,<1.38.0)", "mypy-boto3-amplifyuibuilder (>=1.37.0,<1.38.0)", "mypy-boto3-apigateway (>=1.37.0,<1.38.0)", "mypy-boto3-apigatewaymanagementapi (>=1.37.0,<1.38.0)", "mypy-boto3-apigatewayv2 (>=1.37.0,<1.38.0)", "mypy-boto3-appconfig (>=1.37.0,<1.38.0)", "mypy-boto3-appconfigdata (>=1.37.0,<1.38.0)", "mypy-boto3-appfabric (>=1.37.0,<1.38.0)", "mypy-boto3-appflow (>=1.37.0,<1.38.0)", "mypy-boto3-appintegrations (>=1.37.0,<1.38.0)", "mypy-boto3-application-autoscaling (>=1.37.0,<1.38.0)", "mypy-boto3-application-insights (>=1.37.0,<1.38.0)", "mypy-boto3-application-signals (>=1.37.0,<1.38.0)", "mypy-boto3-applicationcostprofiler (>=1.37.0,<1.38.0)", "mypy-boto3-appmesh (>=1.37.0,<1.38.0)", "mypy-boto3-apprunner (>=1.37.0,<1.38.0)", "mypy-boto3-appstream (>=1.37.0,<1.38.0)", "mypy-boto3-appsync (>=1.37.0,<1.38.0)", "mypy-boto3-apptest (>=1.37.0,<1.38.0)", "mypy-boto3-arc-zonal-shift (>=1.37.0,<1.38.0)", "mypy-boto3-artifact (>=1.37.0,<1.38.0)", "mypy-boto3-athena (>=1.37.0,<1.38.0)", "mypy-boto3-auditmanager (>=1.37.0,<1.38.0)", "mypy-boto3-autoscaling (>=1.37.0,<1.38.0)", "mypy-boto3-autoscaling-plans (>=1.37.0,<1.38.0)", "mypy-boto3-b2bi (>=1.37.0,<1.38.0)", "mypy-boto3-backup (>=1.37.0,<1.38.0)", "mypy-boto3-backup-gateway (>=1.37.0,<1.38.0)", "mypy-boto3-backupsearch (>=1.37.0,<1.38.0)", "mypy-boto3-batch (>=1.37.0,<1.38.0)", "mypy-boto3-bcm-data-exports (>=1.37.0,<1.38.0)", "mypy-boto3-bcm-pricing-calculator (>=1.37.0,<1.38.0)", "mypy-boto3-bedrock (>=1.37.0,<1.38.0)", "mypy-boto3-bedrock-agent (>=1.37.0,<1.38.0)", "mypy-boto3-bedrock-agent-runtime (>=1.37.0,<1.38.0)", "mypy-boto3-bedrock-data-automation (>=1.37.0,<1.38.0)", "mypy-boto3-bedrock-data-automation-runtime (>=1.37.0,<1.38.0)", "mypy-boto3-bedrock-runtime (>=1.37.0,<1.38.0)", "mypy-boto3-billing (>=1.37.0,<1.38.0)", "mypy-boto3-billingconductor (>=1.37.0,<1.38.0)", "mypy-boto3-braket (>=1.37.0,<1.38.0)", "mypy-boto3-budgets (>=1.37.0,<1.38.0)", "mypy-boto3-ce (>=1.37.0,<1.38.0)", "mypy-boto3-chatbot (>=1.37.0,<1.38.0)", "mypy-boto3-chime (>=1.37.0,<1.38.0)", "mypy-boto3-chime-sdk-identity (>=1.37.0,<1.38.0)", "mypy-boto3-chime-sdk-media-pipelines (>=1.37.0,<1.38.0)", "mypy-boto3-chime-sdk-meetings (>=1.37.0,<1.38.0)", "mypy-boto3-chime-sdk-messaging (>=1.37.0,<1.38.0)", "mypy-boto3-chime-sdk-voice (>=1.37.0,<1.38.0)", "mypy-boto3-cleanrooms (>=1.37.0,<1.38.0)", "mypy-boto3-cleanroomsml (>=1.37.0,<1.38.0)", "mypy-boto3-cloud9 (>=1.37.0,<1.38.0)", "mypy-boto3-cloudcontrol (>=1.37.0,<1.38.0)", "mypy-boto3-clouddirectory (>=1.37.0,<1.38.0)", "mypy-boto3-cloudformation (>=1.37.0,<1.38.0)", "mypy-boto3-cloudfront (>=1.37.0,<1.38.0)", "mypy-boto3-cloudfront-keyvaluestore (>=1.37.0,<1.38.0)", "mypy-boto3-cloudhsm (>=1.37.0,<1.38.0)", "mypy-boto3-cloudhsmv2 (>=1.37.0,<1.38.0)", "mypy-boto3-cloudsearch (>=1.37.0,<1.38.0)", "mypy-boto3-cloudsearchdomain (>=1.37.0,<1.38.0)", "mypy-boto3-cloudtrail (>=1.37.0,<1.38.0)", "mypy-boto3-cloudtrail-data (>=1.37.0,<1.38.0)", "mypy-boto3-cloudwatch (>=1.37.0,<1.38.0)", "mypy-boto3-codeartifact (>=1.37.0,<1.38.0)", "mypy-boto3-codebuild (>=1.37.0,<1.38.0)", "mypy-boto3-codecatalyst (>=1.37.0,<1.38.0)", "mypy-boto3-codecommit (>=1.37.0,<1.38.0)", "mypy-boto3-codeconnections (>=1.37.0,<1.38.0)", "mypy-boto3-codedeploy (>=1.37.0,<1.38.0)", "mypy-boto3-codeguru-reviewer (>=1.37.0,<1.38.0)", "mypy-boto3-codeguru-security (>=1.37.0,<1.38.0)", "mypy-boto3-codeguruprofiler (>=1.37.0,<1.38.0)", "mypy-boto3-codepipeline (>=1.37.0,<1.38.0)", "mypy-boto3-codestar-connections (>=1.37.0,<1.38.0)", "mypy-boto3-codestar-notifications (>=1.37.0,<1.38.0)", "mypy-boto3-cognito-identity (>=1.37.0,<1.38.0)", "mypy-boto3-cognito-idp (>=1.37.0,<1.38.0)", "mypy-boto3-cognito-sync (>=1.37.0,<1.38.0)", "mypy-boto3-comprehend (>=1.37.0,<1.38.0)", "mypy-boto3-comprehendmedical (>=1.37.0,<1.38.0)", "mypy-boto3-compute-optimizer (>=1.37.0,<1.38.0)", "mypy-boto3-config (>=1.37.0,<1.38.0)", "mypy-boto3-connect (>=1.37.0,<1.38.0)", "mypy-boto3-connect-contact-lens (>=1.37.0,<1.38.0)", "mypy-boto3-connectcampaigns (>=1.37.0,<1.38.0)", "mypy-boto3-connectcampaignsv2 (>=1.37.0,<1.38.0)", "mypy-boto3-connectcases (>=1.37.0,<1.38.0)", "mypy-boto3-connectparticipant (>=1.37.0,<1.38.0)", "mypy-boto3-controlcatalog (>=1.37.0,<1.38.0)", "mypy-boto3-controltower (>=1.37.0,<1.38.0)", "mypy-boto3-cost-optimization-hub (>=1.37.0,<1.38.0)", "mypy-boto3-cur (>=1.37.0,<1.38.0)", "mypy-boto3-customer-profiles (>=1.37.0,<1.38.0)", "mypy-boto3-databrew (>=1.37.0,<1.38.0)", "mypy-boto3-dataexchange (>=1.37.0,<1.38.0)", "mypy-boto3-datapipeline (>=1.37.0,<1.38.0)", "mypy-boto3-datasync (>=1.37.0,<1.38.0)", "mypy-boto3-datazone (>=1.37.0,<1.38.0)", "mypy-boto3-dax (>=1.37.0,<1.38.0)", "mypy-boto3-deadline (>=1.37.0,<1.38.0)", "mypy-boto3-detective (>=1.37.0,<1.38.0)", "mypy-boto3-devicefarm (>=1.37.0,<1.38.0)", "mypy-boto3-devops-guru (>=1.37.0,<1.38.0)", "mypy-boto3-directconnect (>=1.37.0,<1.38.0)", "mypy-boto3-discovery (>=1.37.0,<1.38.0)", "mypy-boto3-dlm (>=1.37.0,<1.38.0)", "mypy-boto3-dms (>=1.37.0,<1.38.0)", "mypy-boto3-docdb (>=1.37.0,<1.38.0)", "mypy-boto3-docdb-elastic (>=1.37.0,<1.38.0)", "mypy-boto3-drs (>=1.37.0,<1.38.0)", "mypy-boto3-ds (>=1.37.0,<1.38.0)", "mypy-boto3-ds-data (>=1.37.0,<1.38.0)", "mypy-boto3-dsql (>=1.37.0,<1.38.0)", "mypy-boto3-dynamodb (>=1.37.0,<1.38.0)", "mypy-boto3-dynamodbstreams (>=1.37.0,<1.38.0)", "mypy-boto3-ebs (>=1.37.0,<1.38.0)", "mypy-boto3-ec2 (>=1.37.0,<1.38.0)", "mypy-boto3-ec2-instance-connect (>=1.37.0,<1.38.0)", "mypy-boto3-ecr (>=1.37.0,<1.38.0)", "mypy-boto3-ecr-public (>=1.37.0,<1.38.0)", "mypy-boto3-ecs (>=1.37.0,<1.38.0)", "mypy-boto3-efs (>=1.37.0,<1.38.0)", "mypy-boto3-eks (>=1.37.0,<1.38.0)", "mypy-boto3-eks-auth (>=1.37.0,<1.38.0)", "mypy-boto3-elasticache (>=1.37.0,<1.38.0)", "mypy-boto3-elasticbeanstalk (>=1.37.0,<1.38.0)", "mypy-boto3-elastictranscoder (>=1.37.0,<1.38.0)", "mypy-boto3-elb (>=1.37.0,<1.38.0)", "mypy-boto3-elbv2 (>=1.37.0,<1.38.0)", "mypy-boto3-emr (>=1.37.0,<1.38.0)", "mypy-boto3-emr-containers (>=1.37.0,<1.38.0)", "mypy-boto3-emr-serverless (>=1.37.0,<1.38.0)", "mypy-boto3-entityresolution (>=1.37.0,<1.38.0)", "mypy-boto3-es (>=1.37.0,<1.38.0)", "mypy-boto3-events (>=1.37.0,<1.38.0)", "mypy-boto3-evidently (>=1.37.0,<1.38.0)", "mypy-boto3-finspace (>=1.37.0,<1.38.0)", "mypy-boto3-finspace-data (>=1.37.0,<1.38.0)", "mypy-boto3-firehose (>=1.37.0,<1.38.0)", "mypy-boto3-fis (>=1.37.0,<1.38.0)", "mypy-boto3-fms (>=1.37.0,<1.38.0)", "mypy-boto3-forecast (>=1.37.0,<1.38.0)", "mypy-boto3-forecastquery (>=1.37.0,<1.38.0)", "mypy-boto3-frauddetector (>=1.37.0,<1.38.0)", "mypy-boto3-freetier (>=1.37.0,<1.38.0)", "mypy-boto3-fsx (>=1.37.0,<1.38.0)", "mypy-boto3-gamelift (>=1.37.0,<1.38.0)", "mypy-boto3-gameliftstreams (>=1.37.0,<1.38.0)", "mypy-boto3-geo-maps (>=1.37.0,<1.38.0)", "mypy-boto3-geo-places (>=1.37.0,<1.38.0)", "mypy-boto3-geo-routes (>=1.37.0,<1.38.0)", "mypy-boto3-glacier (>=1.37.0,<1.38.0)", "mypy-boto3-globalaccelerator (>=1.37.0,<1.38.0)", "mypy-boto3-glue (>=1.37.0,<1.38.0)", "mypy-boto3-grafana (>=1.37.0,<1.38.0)", "mypy-boto3-greengrass (>=1.37.0,<1.38.0)", "mypy-boto3-greengrassv2 (>=1.37.0,<1.38.0)", "mypy-boto3-groundstation (>=1.37.0,<1.38.0)", "mypy-boto3-guardduty (>=1.37.0,<1.38.0)", "mypy-boto3-health (>=1.37.0,<1.38.0)", "mypy-boto3-healthlake (>=1.37.0,<1.38.0)", "mypy-boto3-iam (>=1.37.0,<1.38.0)", "mypy-boto3-identitystore (>=1.37.0,<1.38.0)", "mypy-boto3-imagebuilder (>=1.37.0,<1.38.0)", "mypy-boto3-importexport (>=1.37.0,<1.38.0)", "mypy-boto3-inspector (>=1.37.0,<1.38.0)", "mypy-boto3-inspector-scan (>=1.37.0,<1.38.0)", "mypy-boto3-inspector2 (>=1.37.0,<1.38.0)", "mypy-boto3-internetmonitor (>=1.37.0,<1.38.0)", "mypy-boto3-invoicing (>=1.37.0,<1.38.0)", "mypy-boto3-iot (>=1.37.0,<1.38.0)", "mypy-boto3-iot-data (>=1.37.0,<1.38.0)", "mypy-boto3-iot-jobs-data (>=1.37.0,<1.38.0)", "mypy-boto3-iot-managed-integrations (>=1.37.0,<1.38.0)", "mypy-boto3-iotanalytics (>=1.37.0,<1.38.0)", "mypy-boto3-iotdeviceadvisor (>=1.37.0,<1.38.0)", "mypy-boto3-iotevents (>=1.37.0,<1.38.0)", "mypy-boto3-iotevents-data (>=1.37.0,<1.38.0)", "mypy-boto3-iotfleethub (>=1.37.0,<1.38.0)", "mypy-boto3-iotfleetwise (>=1.37.0,<1.38.0)", "mypy-boto3-iotsecuretunneling (>=1.37.0,<1.38.0)", "mypy-boto3-iotsitewise (>=1.37.0,<1.38.0)", "mypy-boto3-iotthingsgraph (>=1.37.0,<1.38.0)", "mypy-boto3-iottwinmaker (>=1.37.0,<1.38.0)", "mypy-boto3-iotwireless (>=1.37.0,<1.38.0)", "mypy-boto3-ivs (>=1.37.0,<1.38.0)", "mypy-boto3-ivs-realtime (>=1.37.0,<1.38.0)", "mypy-boto3-ivschat (>=1.37.0,<1.38.0)", "mypy-boto3-kafka (>=1.37.0,<1.38.0)", "mypy-boto3-kafkaconnect (>=1.37.0,<1.38.0)", "mypy-boto3-kendra (>=1.37.0,<1.38.0)", "mypy-boto3-kendra-ranking (>=1.37.0,<1.38.0)", "mypy-boto3-keyspaces (>=1.37.0,<1.38.0)", "mypy-boto3-kinesis (>=1.37.0,<1.38.0)", "mypy-boto3-kinesis-video-archived-media (>=1.37.0,<1.38.0)", "mypy-boto3-kinesis-video-media (>=1.37.0,<1.38.0)", "mypy-boto3-kinesis-video-signaling (>=1.37.0,<1.38.0)", "mypy-boto3-kinesis-video-webrtc-storage (>=1.37.0,<1.38.0)", "mypy-boto3-kinesisanalytics (>=1.37.0,<1.38.0)", "mypy-boto3-kinesisanalyticsv2 (>=1.37.0,<1.38.0)", "mypy-boto3-kinesisvideo (>=1.37.0,<1.38.0)", "mypy-boto3-kms (>=1.37.0,<1.38.0)", "mypy-boto3-lakeformation (>=1.37.0,<1.38.0)", "mypy-boto3-lambda (>=1.37.0,<1.38.0)", "mypy-boto3-launch-wizard (>=1.37.0,<1.38.0)", "mypy-boto3-lex-models (>=1.37.0,<1.38.0)", "mypy-boto3-lex-runtime (>=1.37.0,<1.38.0)", "mypy-boto3-lexv2-models (>=1.37.0,<1.38.0)", "mypy-boto3-lexv2-runtime (>=1.37.0,<1.38.0)", "mypy-boto3-license-manager (>=1.37.0,<1.38.0)", "mypy-boto3-license-manager-linux-subscriptions (>=1.37.0,<1.38.0)", "mypy-boto3-license-manager-user-subscriptions (>=1.37.0,<1.38.0)", "mypy-boto3-lightsail (>=1.37.0,<1.38.0)", "mypy-boto3-location (>=1.37.0,<1.38.0)", "mypy-boto3-logs (>=1.37.0,<1.38.0)", "mypy-boto3-lookoutequipment (>=1.37.0,<1.38.0)", "mypy-boto3-lookoutmetrics (>=1.37.0,<1.38.0)", "mypy-boto3-lookoutvision (>=1.37.0,<1.38.0)", "mypy-boto3-m2 (>=1.37.0,<1.38.0)", "mypy-boto3-machinelearning (>=1.37.0,<1.38.0)", "mypy-boto3-macie2 (>=1.37.0,<1.38.0)", "mypy-boto3-mailmanager (>=1.37.0,<1.38.0)", "mypy-boto3-managedblockchain (>=1.37.0,<1.38.0)", "mypy-boto3-managedblockchain-query (>=1.37.0,<1.38.0)", "mypy-boto3-marketplace-agreement (>=1.37.0,<1.38.0)", "mypy-boto3-marketplace-catalog (>=1.37.0,<1.38.0)", "mypy-boto3-marketplace-deployment (>=1.37.0,<1.38.0)", "mypy-boto3-marketplace-entitlement (>=1.37.0,<1.38.0)", "mypy-boto3-marketplace-reporting (>=1.37.0,<1.38.0)", "mypy-boto3-marketplacecommerceanalytics (>=1.37.0,<1.38.0)", "mypy-boto3-mediaconnect (>=1.37.0,<1.38.0)", "mypy-boto3-mediaconvert (>=1.37.0,<1.38.0)", "mypy-boto3-medialive (>=1.37.0,<1.38.0)", "mypy-boto3-mediapackage (>=1.37.0,<1.38.0)", "mypy-boto3-mediapackage-vod (>=1.37.0,<1.38.0)", "mypy-boto3-mediapackagev2 (>=1.37.0,<1.38.0)", "mypy-boto3-mediastore (>=1.37.0,<1.38.0)", "mypy-boto3-mediastore-data (>=1.37.0,<1.38.0)", "mypy-boto3-mediatailor (>=1.37.0,<1.38.0)", "mypy-boto3-medical-imaging (>=1.37.0,<1.38.0)", "mypy-boto3-memorydb (>=1.37.0,<1.38.0)", "mypy-boto3-meteringmarketplace (>=1.37.0,<1.38.0)", "mypy-boto3-mgh (>=1.37.0,<1.38.0)", "mypy-boto3-mgn (>=1.37.0,<1.38.0)", "mypy-boto3-migration-hub-refactor-spaces (>=1.37.0,<1.38.0)", "mypy-boto3-migrationhub-config (>=1.37.0,<1.38.0)", "mypy-boto3-migrationhuborchestrator (>=1.37.0,<1.38.0)", "mypy-boto3-migrationhubstrategy (>=1.37.0,<1.38.0)", "mypy-boto3-mq (>=1.37.0,<1.38.0)", "mypy-boto3-mturk (>=1.37.0,<1.38.0)", "mypy-boto3-mwaa (>=1.37.0,<1.38.0)", "mypy-boto3-neptune (>=1.37.0,<1.38.0)", "mypy-boto3-neptune-graph (>=1.37.0,<1.38.0)", "mypy-boto3-neptunedata (>=1.37.0,<1.38.0)", "mypy-boto3-network-firewall (>=1.37.0,<1.38.0)", "mypy-boto3-networkflowmonitor (>=1.37.0,<1.38.0)", "mypy-boto3-networkmanager (>=1.37.0,<1.38.0)", "mypy-boto3-networkmonitor (>=1.37.0,<1.38.0)", "mypy-boto3-notifications (>=1.37.0,<1.38.0)", "mypy-boto3-notificationscontacts (>=1.37.0,<1.38.0)", "mypy-boto3-oam (>=1.37.0,<1.38.0)", "mypy-boto3-observabilityadmin (>=1.37.0,<1.38.0)", "mypy-boto3-omics (>=1.37.0,<1.38.0)", "mypy-boto3-opensearch (>=1.37.0,<1.38.0)", "mypy-boto3-opensearchserverless (>=1.37.0,<1.38.0)", "mypy-boto3-opsworks (>=1.37.0,<1.38.0)", "mypy-boto3-opsworkscm (>=1.37.0,<1.38.0)", "mypy-boto3-organizations (>=1.37.0,<1.38.0)", "mypy-boto3-osis (>=1.37.0,<1.38.0)", "mypy-boto3-outposts (>=1.37.0,<1.38.0)", "mypy-boto3-panorama (>=1.37.0,<1.38.0)", "mypy-boto3-partnercentral-selling (>=1.37.0,<1.38.0)", "mypy-boto3-payment-cryptography (>=1.37.0,<1.38.0)", "mypy-boto3-payment-cryptography-data (>=1.37.0,<1.38.0)", "mypy-boto3-pca-connector-ad (>=1.37.0,<1.38.0)", "mypy-boto3-pca-connector-scep (>=1.37.0,<1.38.0)", "mypy-boto3-pcs (>=1.37.0,<1.38.0)", "mypy-boto3-personalize (>=1.37.0,<1.38.0)", "mypy-boto3-personalize-events (>=1.37.0,<1.38.0)", "mypy-boto3-personalize-runtime (>=1.37.0,<1.38.0)", "mypy-boto3-pi (>=1.37.0,<1.38.0)", "mypy-boto3-pinpoint (>=1.37.0,<1.38.0)", "mypy-boto3-pinpoint-email (>=1.37.0,<1.38.0)", "mypy-boto3-pinpoint-sms-voice (>=1.37.0,<1.38.0)", "mypy-boto3-pinpoint-sms-voice-v2 (>=1.37.0,<1.38.0)", "mypy-boto3-pipes (>=1.37.0,<1.38.0)", "mypy-boto3-polly (>=1.37.0,<1.38.0)", "mypy-boto3-pricing (>=1.37.0,<1.38.0)", "mypy-boto3-privatenetworks (>=1.37.0,<1.38.0)", "mypy-boto3-proton (>=1.37.0,<1.38.0)", "mypy-boto3-qapps (>=1.37.0,<1.38.0)", "mypy-boto3-qbusiness (>=1.37.0,<1.38.0)", "mypy-boto3-qconnect (>=1.37.0,<1.38.0)", "mypy-boto3-qldb (>=1.37.0,<1.38.0)", "mypy-boto3-qldb-session (>=1.37.0,<1.38.0)", "mypy-boto3-quicksight (>=1.37.0,<1.38.0)", "mypy-boto3-ram (>=1.37.0,<1.38.0)", "mypy-boto3-rbin (>=1.37.0,<1.38.0)", "mypy-boto3-rds (>=1.37.0,<1.38.0)", "mypy-boto3-rds-data (>=1.37.0,<1.38.0)", "mypy-boto3-redshift (>=1.37.0,<1.38.0)", "mypy-boto3-redshift-data (>=1.37.0,<1.38.0)", "mypy-boto3-redshift-serverless (>=1.37.0,<1.38.0)", "mypy-boto3-rekognition (>=1.37.0,<1.38.0)", "mypy-boto3-repostspace (>=1.37.0,<1.38.0)", "mypy-boto3-resiliencehub (>=1.37.0,<1.38.0)", "mypy-boto3-resource-explorer-2 (>=1.37.0,<1.38.0)", "mypy-boto3-resource-groups (>=1.37.0,<1.38.0)", "mypy-boto3-resourcegroupstaggingapi (>=1.37.0,<1.38.0)", "mypy-boto3-robomaker (>=1.37.0,<1.38.0)", "mypy-boto3-rolesanywhere (>=1.37.0,<1.38.0)", "mypy-boto3-route53 (>=1.37.0,<1.38.0)", "mypy-boto3-route53-recovery-cluster (>=1.37.0,<1.38.0)", "mypy-boto3-route53-recovery-control-config (>=1.37.0,<1.38.0)", "mypy-boto3-route53-recovery-readiness (>=1.37.0,<1.38.0)", "mypy-boto3-route53domains (>=1.37.0,<1.38.0)", "mypy-boto3-route53profiles (>=1.37.0,<1.38.0)", "mypy-boto3-route53resolver (>=1.37.0,<1.38.0)", "mypy-boto3-rum (>=1.37.0,<1.38.0)", "mypy-boto3-s3 (>=1.37.0,<1.38.0)", "mypy-boto3-s3control (>=1.37.0,<1.38.0)", "mypy-boto3-s3outposts (>=1.37.0,<1.38.0)", "mypy-boto3-s3tables (>=1.37.0,<1.38.0)", "mypy-boto3-sagemaker (>=1.37.0,<1.38.0)", "mypy-boto3-sagemaker-a2i-runtime (>=1.37.0,<1.38.0)", "mypy-boto3-sagemaker-edge (>=1.37.0,<1.38.0)", "mypy-boto3-sagemaker-featurestore-runtime (>=1.37.0,<1.38.0)", "mypy-boto3-sagemaker-geospatial (>=1.37.0,<1.38.0)", "mypy-boto3-sagemaker-metrics (>=1.37.0,<1.38.0)", "mypy-boto3-sagemaker-runtime (>=1.37.0,<1.38.0)", "mypy-boto3-savingsplans (>=1.37.0,<1.38.0)", "mypy-boto3-scheduler (>=1.37.0,<1.38.0)", "mypy-boto3-schemas (>=1.37.0,<1.38.0)", "mypy-boto3-sdb (>=1.37.0,<1.38.0)", "mypy-boto3-secretsmanager (>=1.37.0,<1.38.0)", "mypy-boto3-security-ir (>=1.37.0,<1.38.0)", "mypy-boto3-securityhub (>=1.37.0,<1.38.0)", "mypy-boto3-securitylake (>=1.37.0,<1.38.0)", "mypy-boto3-serverlessrepo (>=1.37.0,<1.38.0)", "mypy-boto3-service-quotas (>=1.37.0,<1.38.0)", "mypy-boto3-servicecatalog (>=1.37.0,<1.38.0)", "mypy-boto3-servicecatalog-appregistry (>=1.37.0,<1.38.0)", "mypy-boto3-servicediscovery (>=1.37.0,<1.38.0)", "mypy-boto3-ses (>=1.37.0,<1.38.0)", "mypy-boto3-sesv2 (>=1.37.0,<1.38.0)", "mypy-boto3-shield (>=1.37.0,<1.38.0)", "mypy-boto3-signer (>=1.37.0,<1.38.0)", "mypy-boto3-simspaceweaver (>=1.37.0,<1.38.0)", "mypy-boto3-sms (>=1.37.0,<1.38.0)", "mypy-boto3-sms-voice (>=1.37.0,<1.38.0)", "mypy-boto3-snow-device-management (>=1.37.0,<1.38.0)", "mypy-boto3-snowball (>=1.37.0,<1.38.0)", "mypy-boto3-sns (>=1.37.0,<1.38.0)", "mypy-boto3-socialmessaging (>=1.37.0,<1.38.0)", "mypy-boto3-sqs (>=1.37.0,<1.38.0)", "mypy-boto3-ssm (>=1.37.0,<1.38.0)", "mypy-boto3-ssm-contacts (>=1.37.0,<1.38.0)", "mypy-boto3-ssm-incidents (>=1.37.0,<1.38.0)", "mypy-boto3-ssm-quicksetup (>=1.37.0,<1.38.0)", "mypy-boto3-ssm-sap (>=1.37.0,<1.38.0)", "mypy-boto3-sso (>=1.37.0,<1.38.0)", "mypy-boto3-sso-admin (>=1.37.0,<1.38.0)", "mypy-boto3-sso-oidc (>=1.37.0,<1.38.0)", "mypy-boto3-stepfunctions (>=1.37.0,<1.38.0)", "mypy-boto3-storagegateway (>=1.37.0,<1.38.0)", "mypy-boto3-sts (>=1.37.0,<1.38.0)", "mypy-boto3-supplychain (>=1.37.0,<1.38.0)", "mypy-boto3-support (>=1.37.0,<1.38.0)", "mypy-boto3-support-app (>=1.37.0,<1.38.0)", "mypy-boto3-swf (>=1.37.0,<1.38.0)", "mypy-boto3-synthetics (>=1.37.0,<1.38.0)", "mypy-boto3-taxsettings (>=1.37.0,<1.38.0)", "mypy-boto3-textract (>=1.37.0,<1.38.0)", "mypy-boto3-timestream-influxdb (>=1.37.0,<1.38.0)", "mypy-boto3-timestream-query (>=1.37.0,<1.38.0)", "mypy-boto3-timestream-write (>=1.37.0,<1.38.0)", "mypy-boto3-tnb (>=1.37.0,<1.38.0)", "mypy-boto3-transcribe (>=1.37.0,<1.38.0)", "mypy-boto3-transfer (>=1.37.0,<1.38.0)", "mypy-boto3-translate (>=1.37.0,<1.38.0)", "mypy-boto3-trustedadvisor (>=1.37.0,<1.38.0)", "mypy-boto3-verifiedpermissions (>=1.37.0,<1.38.0)", "mypy-boto3-voice-id (>=1.37.0,<1.38.0)", "mypy-boto3-vpc-lattice (>=1.37.0,<1.38.0)", "mypy-boto3-waf (>=1.37.0,<1.38.0)", "mypy-boto3-waf-regional (>=1.37.0,<1.38.0)", "mypy-boto3-wafv2 (>=1.37.0,<1.38.0)", "mypy-boto3-wellarchitected (>=1.37.0,<1.38.0)", "mypy-boto3-wisdom (>=1.37.0,<1.38.0)", "mypy-boto3-workdocs (>=1.37.0,<1.38.0)", "mypy-boto3-workmail (>=1.37.0,<1.38.0)", "mypy-boto3-workmailmessageflow (>=1.37.0,<1.38.0)", "mypy-boto3-workspaces (>=1.37.0,<1.38.0)", "mypy-boto3-workspaces-thin-client (>=1.37.0,<1.38.0)", "mypy-boto3-workspaces-web (>=1.37.0,<1.38.0)", "mypy-boto3-xray (>=1.37.0,<1.38.0)"]
-amp = ["mypy-boto3-amp (>=1.37.0,<1.38.0)"]
-amplify = ["mypy-boto3-amplify (>=1.37.0,<1.38.0)"]
-amplifybackend = ["mypy-boto3-amplifybackend (>=1.37.0,<1.38.0)"]
-amplifyuibuilder = ["mypy-boto3-amplifyuibuilder (>=1.37.0,<1.38.0)"]
-apigateway = ["mypy-boto3-apigateway (>=1.37.0,<1.38.0)"]
-apigatewaymanagementapi = ["mypy-boto3-apigatewaymanagementapi (>=1.37.0,<1.38.0)"]
-apigatewayv2 = ["mypy-boto3-apigatewayv2 (>=1.37.0,<1.38.0)"]
-appconfig = ["mypy-boto3-appconfig (>=1.37.0,<1.38.0)"]
-appconfigdata = ["mypy-boto3-appconfigdata (>=1.37.0,<1.38.0)"]
-appfabric = ["mypy-boto3-appfabric (>=1.37.0,<1.38.0)"]
-appflow = ["mypy-boto3-appflow (>=1.37.0,<1.38.0)"]
-appintegrations = ["mypy-boto3-appintegrations (>=1.37.0,<1.38.0)"]
-application-autoscaling = ["mypy-boto3-application-autoscaling (>=1.37.0,<1.38.0)"]
-application-insights = ["mypy-boto3-application-insights (>=1.37.0,<1.38.0)"]
-application-signals = ["mypy-boto3-application-signals (>=1.37.0,<1.38.0)"]
-applicationcostprofiler = ["mypy-boto3-applicationcostprofiler (>=1.37.0,<1.38.0)"]
-appmesh = ["mypy-boto3-appmesh (>=1.37.0,<1.38.0)"]
-apprunner = ["mypy-boto3-apprunner (>=1.37.0,<1.38.0)"]
-appstream = ["mypy-boto3-appstream (>=1.37.0,<1.38.0)"]
-appsync = ["mypy-boto3-appsync (>=1.37.0,<1.38.0)"]
-apptest = ["mypy-boto3-apptest (>=1.37.0,<1.38.0)"]
-arc-zonal-shift = ["mypy-boto3-arc-zonal-shift (>=1.37.0,<1.38.0)"]
-artifact = ["mypy-boto3-artifact (>=1.37.0,<1.38.0)"]
-athena = ["mypy-boto3-athena (>=1.37.0,<1.38.0)"]
-auditmanager = ["mypy-boto3-auditmanager (>=1.37.0,<1.38.0)"]
-autoscaling = ["mypy-boto3-autoscaling (>=1.37.0,<1.38.0)"]
-autoscaling-plans = ["mypy-boto3-autoscaling-plans (>=1.37.0,<1.38.0)"]
-b2bi = ["mypy-boto3-b2bi (>=1.37.0,<1.38.0)"]
-backup = ["mypy-boto3-backup (>=1.37.0,<1.38.0)"]
-backup-gateway = ["mypy-boto3-backup-gateway (>=1.37.0,<1.38.0)"]
-backupsearch = ["mypy-boto3-backupsearch (>=1.37.0,<1.38.0)"]
-batch = ["mypy-boto3-batch (>=1.37.0,<1.38.0)"]
-bcm-data-exports = ["mypy-boto3-bcm-data-exports (>=1.37.0,<1.38.0)"]
-bcm-pricing-calculator = ["mypy-boto3-bcm-pricing-calculator (>=1.37.0,<1.38.0)"]
-bedrock = ["mypy-boto3-bedrock (>=1.37.0,<1.38.0)"]
-bedrock-agent = ["mypy-boto3-bedrock-agent (>=1.37.0,<1.38.0)"]
-bedrock-agent-runtime = ["mypy-boto3-bedrock-agent-runtime (>=1.37.0,<1.38.0)"]
-bedrock-data-automation = ["mypy-boto3-bedrock-data-automation (>=1.37.0,<1.38.0)"]
-bedrock-data-automation-runtime = ["mypy-boto3-bedrock-data-automation-runtime (>=1.37.0,<1.38.0)"]
-bedrock-runtime = ["mypy-boto3-bedrock-runtime (>=1.37.0,<1.38.0)"]
-billing = ["mypy-boto3-billing (>=1.37.0,<1.38.0)"]
-billingconductor = ["mypy-boto3-billingconductor (>=1.37.0,<1.38.0)"]
-boto3 = ["boto3 (==1.37.23)"]
-braket = ["mypy-boto3-braket (>=1.37.0,<1.38.0)"]
-budgets = ["mypy-boto3-budgets (>=1.37.0,<1.38.0)"]
-ce = ["mypy-boto3-ce (>=1.37.0,<1.38.0)"]
-chatbot = ["mypy-boto3-chatbot (>=1.37.0,<1.38.0)"]
-chime = ["mypy-boto3-chime (>=1.37.0,<1.38.0)"]
-chime-sdk-identity = ["mypy-boto3-chime-sdk-identity (>=1.37.0,<1.38.0)"]
-chime-sdk-media-pipelines = ["mypy-boto3-chime-sdk-media-pipelines (>=1.37.0,<1.38.0)"]
-chime-sdk-meetings = ["mypy-boto3-chime-sdk-meetings (>=1.37.0,<1.38.0)"]
-chime-sdk-messaging = ["mypy-boto3-chime-sdk-messaging (>=1.37.0,<1.38.0)"]
-chime-sdk-voice = ["mypy-boto3-chime-sdk-voice (>=1.37.0,<1.38.0)"]
-cleanrooms = ["mypy-boto3-cleanrooms (>=1.37.0,<1.38.0)"]
-cleanroomsml = ["mypy-boto3-cleanroomsml (>=1.37.0,<1.38.0)"]
-cloud9 = ["mypy-boto3-cloud9 (>=1.37.0,<1.38.0)"]
-cloudcontrol = ["mypy-boto3-cloudcontrol (>=1.37.0,<1.38.0)"]
-clouddirectory = ["mypy-boto3-clouddirectory (>=1.37.0,<1.38.0)"]
-cloudformation = ["mypy-boto3-cloudformation (>=1.37.0,<1.38.0)"]
-cloudfront = ["mypy-boto3-cloudfront (>=1.37.0,<1.38.0)"]
-cloudfront-keyvaluestore = ["mypy-boto3-cloudfront-keyvaluestore (>=1.37.0,<1.38.0)"]
-cloudhsm = ["mypy-boto3-cloudhsm (>=1.37.0,<1.38.0)"]
-cloudhsmv2 = ["mypy-boto3-cloudhsmv2 (>=1.37.0,<1.38.0)"]
-cloudsearch = ["mypy-boto3-cloudsearch (>=1.37.0,<1.38.0)"]
-cloudsearchdomain = ["mypy-boto3-cloudsearchdomain (>=1.37.0,<1.38.0)"]
-cloudtrail = ["mypy-boto3-cloudtrail (>=1.37.0,<1.38.0)"]
-cloudtrail-data = ["mypy-boto3-cloudtrail-data (>=1.37.0,<1.38.0)"]
-cloudwatch = ["mypy-boto3-cloudwatch (>=1.37.0,<1.38.0)"]
-codeartifact = ["mypy-boto3-codeartifact (>=1.37.0,<1.38.0)"]
-codebuild = ["mypy-boto3-codebuild (>=1.37.0,<1.38.0)"]
-codecatalyst = ["mypy-boto3-codecatalyst (>=1.37.0,<1.38.0)"]
-codecommit = ["mypy-boto3-codecommit (>=1.37.0,<1.38.0)"]
-codeconnections = ["mypy-boto3-codeconnections (>=1.37.0,<1.38.0)"]
-codedeploy = ["mypy-boto3-codedeploy (>=1.37.0,<1.38.0)"]
-codeguru-reviewer = ["mypy-boto3-codeguru-reviewer (>=1.37.0,<1.38.0)"]
-codeguru-security = ["mypy-boto3-codeguru-security (>=1.37.0,<1.38.0)"]
-codeguruprofiler = ["mypy-boto3-codeguruprofiler (>=1.37.0,<1.38.0)"]
-codepipeline = ["mypy-boto3-codepipeline (>=1.37.0,<1.38.0)"]
-codestar-connections = ["mypy-boto3-codestar-connections (>=1.37.0,<1.38.0)"]
-codestar-notifications = ["mypy-boto3-codestar-notifications (>=1.37.0,<1.38.0)"]
-cognito-identity = ["mypy-boto3-cognito-identity (>=1.37.0,<1.38.0)"]
-cognito-idp = ["mypy-boto3-cognito-idp (>=1.37.0,<1.38.0)"]
-cognito-sync = ["mypy-boto3-cognito-sync (>=1.37.0,<1.38.0)"]
-comprehend = ["mypy-boto3-comprehend (>=1.37.0,<1.38.0)"]
-comprehendmedical = ["mypy-boto3-comprehendmedical (>=1.37.0,<1.38.0)"]
-compute-optimizer = ["mypy-boto3-compute-optimizer (>=1.37.0,<1.38.0)"]
-config = ["mypy-boto3-config (>=1.37.0,<1.38.0)"]
-connect = ["mypy-boto3-connect (>=1.37.0,<1.38.0)"]
-connect-contact-lens = ["mypy-boto3-connect-contact-lens (>=1.37.0,<1.38.0)"]
-connectcampaigns = ["mypy-boto3-connectcampaigns (>=1.37.0,<1.38.0)"]
-connectcampaignsv2 = ["mypy-boto3-connectcampaignsv2 (>=1.37.0,<1.38.0)"]
-connectcases = ["mypy-boto3-connectcases (>=1.37.0,<1.38.0)"]
-connectparticipant = ["mypy-boto3-connectparticipant (>=1.37.0,<1.38.0)"]
-controlcatalog = ["mypy-boto3-controlcatalog (>=1.37.0,<1.38.0)"]
-controltower = ["mypy-boto3-controltower (>=1.37.0,<1.38.0)"]
-cost-optimization-hub = ["mypy-boto3-cost-optimization-hub (>=1.37.0,<1.38.0)"]
-cur = ["mypy-boto3-cur (>=1.37.0,<1.38.0)"]
-customer-profiles = ["mypy-boto3-customer-profiles (>=1.37.0,<1.38.0)"]
-databrew = ["mypy-boto3-databrew (>=1.37.0,<1.38.0)"]
-dataexchange = ["mypy-boto3-dataexchange (>=1.37.0,<1.38.0)"]
-datapipeline = ["mypy-boto3-datapipeline (>=1.37.0,<1.38.0)"]
-datasync = ["mypy-boto3-datasync (>=1.37.0,<1.38.0)"]
-datazone = ["mypy-boto3-datazone (>=1.37.0,<1.38.0)"]
-dax = ["mypy-boto3-dax (>=1.37.0,<1.38.0)"]
-deadline = ["mypy-boto3-deadline (>=1.37.0,<1.38.0)"]
-detective = ["mypy-boto3-detective (>=1.37.0,<1.38.0)"]
-devicefarm = ["mypy-boto3-devicefarm (>=1.37.0,<1.38.0)"]
-devops-guru = ["mypy-boto3-devops-guru (>=1.37.0,<1.38.0)"]
-directconnect = ["mypy-boto3-directconnect (>=1.37.0,<1.38.0)"]
-discovery = ["mypy-boto3-discovery (>=1.37.0,<1.38.0)"]
-dlm = ["mypy-boto3-dlm (>=1.37.0,<1.38.0)"]
-dms = ["mypy-boto3-dms (>=1.37.0,<1.38.0)"]
-docdb = ["mypy-boto3-docdb (>=1.37.0,<1.38.0)"]
-docdb-elastic = ["mypy-boto3-docdb-elastic (>=1.37.0,<1.38.0)"]
-drs = ["mypy-boto3-drs (>=1.37.0,<1.38.0)"]
-ds = ["mypy-boto3-ds (>=1.37.0,<1.38.0)"]
-ds-data = ["mypy-boto3-ds-data (>=1.37.0,<1.38.0)"]
-dsql = ["mypy-boto3-dsql (>=1.37.0,<1.38.0)"]
-dynamodb = ["mypy-boto3-dynamodb (>=1.37.0,<1.38.0)"]
-dynamodbstreams = ["mypy-boto3-dynamodbstreams (>=1.37.0,<1.38.0)"]
-ebs = ["mypy-boto3-ebs (>=1.37.0,<1.38.0)"]
-ec2 = ["mypy-boto3-ec2 (>=1.37.0,<1.38.0)"]
-ec2-instance-connect = ["mypy-boto3-ec2-instance-connect (>=1.37.0,<1.38.0)"]
-ecr = ["mypy-boto3-ecr (>=1.37.0,<1.38.0)"]
-ecr-public = ["mypy-boto3-ecr-public (>=1.37.0,<1.38.0)"]
-ecs = ["mypy-boto3-ecs (>=1.37.0,<1.38.0)"]
-efs = ["mypy-boto3-efs (>=1.37.0,<1.38.0)"]
-eks = ["mypy-boto3-eks (>=1.37.0,<1.38.0)"]
-eks-auth = ["mypy-boto3-eks-auth (>=1.37.0,<1.38.0)"]
-elasticache = ["mypy-boto3-elasticache (>=1.37.0,<1.38.0)"]
-elasticbeanstalk = ["mypy-boto3-elasticbeanstalk (>=1.37.0,<1.38.0)"]
-elastictranscoder = ["mypy-boto3-elastictranscoder (>=1.37.0,<1.38.0)"]
-elb = ["mypy-boto3-elb (>=1.37.0,<1.38.0)"]
-elbv2 = ["mypy-boto3-elbv2 (>=1.37.0,<1.38.0)"]
-emr = ["mypy-boto3-emr (>=1.37.0,<1.38.0)"]
-emr-containers = ["mypy-boto3-emr-containers (>=1.37.0,<1.38.0)"]
-emr-serverless = ["mypy-boto3-emr-serverless (>=1.37.0,<1.38.0)"]
-entityresolution = ["mypy-boto3-entityresolution (>=1.37.0,<1.38.0)"]
-es = ["mypy-boto3-es (>=1.37.0,<1.38.0)"]
-essential = ["mypy-boto3-cloudformation (>=1.37.0,<1.38.0)", "mypy-boto3-dynamodb (>=1.37.0,<1.38.0)", "mypy-boto3-ec2 (>=1.37.0,<1.38.0)", "mypy-boto3-lambda (>=1.37.0,<1.38.0)", "mypy-boto3-rds (>=1.37.0,<1.38.0)", "mypy-boto3-s3 (>=1.37.0,<1.38.0)", "mypy-boto3-sqs (>=1.37.0,<1.38.0)"]
-events = ["mypy-boto3-events (>=1.37.0,<1.38.0)"]
-evidently = ["mypy-boto3-evidently (>=1.37.0,<1.38.0)"]
-finspace = ["mypy-boto3-finspace (>=1.37.0,<1.38.0)"]
-finspace-data = ["mypy-boto3-finspace-data (>=1.37.0,<1.38.0)"]
-firehose = ["mypy-boto3-firehose (>=1.37.0,<1.38.0)"]
-fis = ["mypy-boto3-fis (>=1.37.0,<1.38.0)"]
-fms = ["mypy-boto3-fms (>=1.37.0,<1.38.0)"]
-forecast = ["mypy-boto3-forecast (>=1.37.0,<1.38.0)"]
-forecastquery = ["mypy-boto3-forecastquery (>=1.37.0,<1.38.0)"]
-frauddetector = ["mypy-boto3-frauddetector (>=1.37.0,<1.38.0)"]
-freetier = ["mypy-boto3-freetier (>=1.37.0,<1.38.0)"]
-fsx = ["mypy-boto3-fsx (>=1.37.0,<1.38.0)"]
-full = ["boto3-stubs-full (>=1.37.0,<1.38.0)"]
-gamelift = ["mypy-boto3-gamelift (>=1.37.0,<1.38.0)"]
-gameliftstreams = ["mypy-boto3-gameliftstreams (>=1.37.0,<1.38.0)"]
-geo-maps = ["mypy-boto3-geo-maps (>=1.37.0,<1.38.0)"]
-geo-places = ["mypy-boto3-geo-places (>=1.37.0,<1.38.0)"]
-geo-routes = ["mypy-boto3-geo-routes (>=1.37.0,<1.38.0)"]
-glacier = ["mypy-boto3-glacier (>=1.37.0,<1.38.0)"]
-globalaccelerator = ["mypy-boto3-globalaccelerator (>=1.37.0,<1.38.0)"]
-glue = ["mypy-boto3-glue (>=1.37.0,<1.38.0)"]
-grafana = ["mypy-boto3-grafana (>=1.37.0,<1.38.0)"]
-greengrass = ["mypy-boto3-greengrass (>=1.37.0,<1.38.0)"]
-greengrassv2 = ["mypy-boto3-greengrassv2 (>=1.37.0,<1.38.0)"]
-groundstation = ["mypy-boto3-groundstation (>=1.37.0,<1.38.0)"]
-guardduty = ["mypy-boto3-guardduty (>=1.37.0,<1.38.0)"]
-health = ["mypy-boto3-health (>=1.37.0,<1.38.0)"]
-healthlake = ["mypy-boto3-healthlake (>=1.37.0,<1.38.0)"]
-iam = ["mypy-boto3-iam (>=1.37.0,<1.38.0)"]
-identitystore = ["mypy-boto3-identitystore (>=1.37.0,<1.38.0)"]
-imagebuilder = ["mypy-boto3-imagebuilder (>=1.37.0,<1.38.0)"]
-importexport = ["mypy-boto3-importexport (>=1.37.0,<1.38.0)"]
-inspector = ["mypy-boto3-inspector (>=1.37.0,<1.38.0)"]
-inspector-scan = ["mypy-boto3-inspector-scan (>=1.37.0,<1.38.0)"]
-inspector2 = ["mypy-boto3-inspector2 (>=1.37.0,<1.38.0)"]
-internetmonitor = ["mypy-boto3-internetmonitor (>=1.37.0,<1.38.0)"]
-invoicing = ["mypy-boto3-invoicing (>=1.37.0,<1.38.0)"]
-iot = ["mypy-boto3-iot (>=1.37.0,<1.38.0)"]
-iot-data = ["mypy-boto3-iot-data (>=1.37.0,<1.38.0)"]
-iot-jobs-data = ["mypy-boto3-iot-jobs-data (>=1.37.0,<1.38.0)"]
-iot-managed-integrations = ["mypy-boto3-iot-managed-integrations (>=1.37.0,<1.38.0)"]
-iotanalytics = ["mypy-boto3-iotanalytics (>=1.37.0,<1.38.0)"]
-iotdeviceadvisor = ["mypy-boto3-iotdeviceadvisor (>=1.37.0,<1.38.0)"]
-iotevents = ["mypy-boto3-iotevents (>=1.37.0,<1.38.0)"]
-iotevents-data = ["mypy-boto3-iotevents-data (>=1.37.0,<1.38.0)"]
-iotfleethub = ["mypy-boto3-iotfleethub (>=1.37.0,<1.38.0)"]
-iotfleetwise = ["mypy-boto3-iotfleetwise (>=1.37.0,<1.38.0)"]
-iotsecuretunneling = ["mypy-boto3-iotsecuretunneling (>=1.37.0,<1.38.0)"]
-iotsitewise = ["mypy-boto3-iotsitewise (>=1.37.0,<1.38.0)"]
-iotthingsgraph = ["mypy-boto3-iotthingsgraph (>=1.37.0,<1.38.0)"]
-iottwinmaker = ["mypy-boto3-iottwinmaker (>=1.37.0,<1.38.0)"]
-iotwireless = ["mypy-boto3-iotwireless (>=1.37.0,<1.38.0)"]
-ivs = ["mypy-boto3-ivs (>=1.37.0,<1.38.0)"]
-ivs-realtime = ["mypy-boto3-ivs-realtime (>=1.37.0,<1.38.0)"]
-ivschat = ["mypy-boto3-ivschat (>=1.37.0,<1.38.0)"]
-kafka = ["mypy-boto3-kafka (>=1.37.0,<1.38.0)"]
-kafkaconnect = ["mypy-boto3-kafkaconnect (>=1.37.0,<1.38.0)"]
-kendra = ["mypy-boto3-kendra (>=1.37.0,<1.38.0)"]
-kendra-ranking = ["mypy-boto3-kendra-ranking (>=1.37.0,<1.38.0)"]
-keyspaces = ["mypy-boto3-keyspaces (>=1.37.0,<1.38.0)"]
-kinesis = ["mypy-boto3-kinesis (>=1.37.0,<1.38.0)"]
-kinesis-video-archived-media = ["mypy-boto3-kinesis-video-archived-media (>=1.37.0,<1.38.0)"]
-kinesis-video-media = ["mypy-boto3-kinesis-video-media (>=1.37.0,<1.38.0)"]
-kinesis-video-signaling = ["mypy-boto3-kinesis-video-signaling (>=1.37.0,<1.38.0)"]
-kinesis-video-webrtc-storage = ["mypy-boto3-kinesis-video-webrtc-storage (>=1.37.0,<1.38.0)"]
-kinesisanalytics = ["mypy-boto3-kinesisanalytics (>=1.37.0,<1.38.0)"]
-kinesisanalyticsv2 = ["mypy-boto3-kinesisanalyticsv2 (>=1.37.0,<1.38.0)"]
-kinesisvideo = ["mypy-boto3-kinesisvideo (>=1.37.0,<1.38.0)"]
-kms = ["mypy-boto3-kms (>=1.37.0,<1.38.0)"]
-lakeformation = ["mypy-boto3-lakeformation (>=1.37.0,<1.38.0)"]
-lambda = ["mypy-boto3-lambda (>=1.37.0,<1.38.0)"]
-launch-wizard = ["mypy-boto3-launch-wizard (>=1.37.0,<1.38.0)"]
-lex-models = ["mypy-boto3-lex-models (>=1.37.0,<1.38.0)"]
-lex-runtime = ["mypy-boto3-lex-runtime (>=1.37.0,<1.38.0)"]
-lexv2-models = ["mypy-boto3-lexv2-models (>=1.37.0,<1.38.0)"]
-lexv2-runtime = ["mypy-boto3-lexv2-runtime (>=1.37.0,<1.38.0)"]
-license-manager = ["mypy-boto3-license-manager (>=1.37.0,<1.38.0)"]
-license-manager-linux-subscriptions = ["mypy-boto3-license-manager-linux-subscriptions (>=1.37.0,<1.38.0)"]
-license-manager-user-subscriptions = ["mypy-boto3-license-manager-user-subscriptions (>=1.37.0,<1.38.0)"]
-lightsail = ["mypy-boto3-lightsail (>=1.37.0,<1.38.0)"]
-location = ["mypy-boto3-location (>=1.37.0,<1.38.0)"]
-logs = ["mypy-boto3-logs (>=1.37.0,<1.38.0)"]
-lookoutequipment = ["mypy-boto3-lookoutequipment (>=1.37.0,<1.38.0)"]
-lookoutmetrics = ["mypy-boto3-lookoutmetrics (>=1.37.0,<1.38.0)"]
-lookoutvision = ["mypy-boto3-lookoutvision (>=1.37.0,<1.38.0)"]
-m2 = ["mypy-boto3-m2 (>=1.37.0,<1.38.0)"]
-machinelearning = ["mypy-boto3-machinelearning (>=1.37.0,<1.38.0)"]
-macie2 = ["mypy-boto3-macie2 (>=1.37.0,<1.38.0)"]
-mailmanager = ["mypy-boto3-mailmanager (>=1.37.0,<1.38.0)"]
-managedblockchain = ["mypy-boto3-managedblockchain (>=1.37.0,<1.38.0)"]
-managedblockchain-query = ["mypy-boto3-managedblockchain-query (>=1.37.0,<1.38.0)"]
-marketplace-agreement = ["mypy-boto3-marketplace-agreement (>=1.37.0,<1.38.0)"]
-marketplace-catalog = ["mypy-boto3-marketplace-catalog (>=1.37.0,<1.38.0)"]
-marketplace-deployment = ["mypy-boto3-marketplace-deployment (>=1.37.0,<1.38.0)"]
-marketplace-entitlement = ["mypy-boto3-marketplace-entitlement (>=1.37.0,<1.38.0)"]
-marketplace-reporting = ["mypy-boto3-marketplace-reporting (>=1.37.0,<1.38.0)"]
-marketplacecommerceanalytics = ["mypy-boto3-marketplacecommerceanalytics (>=1.37.0,<1.38.0)"]
-mediaconnect = ["mypy-boto3-mediaconnect (>=1.37.0,<1.38.0)"]
-mediaconvert = ["mypy-boto3-mediaconvert (>=1.37.0,<1.38.0)"]
-medialive = ["mypy-boto3-medialive (>=1.37.0,<1.38.0)"]
-mediapackage = ["mypy-boto3-mediapackage (>=1.37.0,<1.38.0)"]
-mediapackage-vod = ["mypy-boto3-mediapackage-vod (>=1.37.0,<1.38.0)"]
-mediapackagev2 = ["mypy-boto3-mediapackagev2 (>=1.37.0,<1.38.0)"]
-mediastore = ["mypy-boto3-mediastore (>=1.37.0,<1.38.0)"]
-mediastore-data = ["mypy-boto3-mediastore-data (>=1.37.0,<1.38.0)"]
-mediatailor = ["mypy-boto3-mediatailor (>=1.37.0,<1.38.0)"]
-medical-imaging = ["mypy-boto3-medical-imaging (>=1.37.0,<1.38.0)"]
-memorydb = ["mypy-boto3-memorydb (>=1.37.0,<1.38.0)"]
-meteringmarketplace = ["mypy-boto3-meteringmarketplace (>=1.37.0,<1.38.0)"]
-mgh = ["mypy-boto3-mgh (>=1.37.0,<1.38.0)"]
-mgn = ["mypy-boto3-mgn (>=1.37.0,<1.38.0)"]
-migration-hub-refactor-spaces = ["mypy-boto3-migration-hub-refactor-spaces (>=1.37.0,<1.38.0)"]
-migrationhub-config = ["mypy-boto3-migrationhub-config (>=1.37.0,<1.38.0)"]
-migrationhuborchestrator = ["mypy-boto3-migrationhuborchestrator (>=1.37.0,<1.38.0)"]
-migrationhubstrategy = ["mypy-boto3-migrationhubstrategy (>=1.37.0,<1.38.0)"]
-mq = ["mypy-boto3-mq (>=1.37.0,<1.38.0)"]
-mturk = ["mypy-boto3-mturk (>=1.37.0,<1.38.0)"]
-mwaa = ["mypy-boto3-mwaa (>=1.37.0,<1.38.0)"]
-neptune = ["mypy-boto3-neptune (>=1.37.0,<1.38.0)"]
-neptune-graph = ["mypy-boto3-neptune-graph (>=1.37.0,<1.38.0)"]
-neptunedata = ["mypy-boto3-neptunedata (>=1.37.0,<1.38.0)"]
-network-firewall = ["mypy-boto3-network-firewall (>=1.37.0,<1.38.0)"]
-networkflowmonitor = ["mypy-boto3-networkflowmonitor (>=1.37.0,<1.38.0)"]
-networkmanager = ["mypy-boto3-networkmanager (>=1.37.0,<1.38.0)"]
-networkmonitor = ["mypy-boto3-networkmonitor (>=1.37.0,<1.38.0)"]
-notifications = ["mypy-boto3-notifications (>=1.37.0,<1.38.0)"]
-notificationscontacts = ["mypy-boto3-notificationscontacts (>=1.37.0,<1.38.0)"]
-oam = ["mypy-boto3-oam (>=1.37.0,<1.38.0)"]
-observabilityadmin = ["mypy-boto3-observabilityadmin (>=1.37.0,<1.38.0)"]
-omics = ["mypy-boto3-omics (>=1.37.0,<1.38.0)"]
-opensearch = ["mypy-boto3-opensearch (>=1.37.0,<1.38.0)"]
-opensearchserverless = ["mypy-boto3-opensearchserverless (>=1.37.0,<1.38.0)"]
-opsworks = ["mypy-boto3-opsworks (>=1.37.0,<1.38.0)"]
-opsworkscm = ["mypy-boto3-opsworkscm (>=1.37.0,<1.38.0)"]
-organizations = ["mypy-boto3-organizations (>=1.37.0,<1.38.0)"]
-osis = ["mypy-boto3-osis (>=1.37.0,<1.38.0)"]
-outposts = ["mypy-boto3-outposts (>=1.37.0,<1.38.0)"]
-panorama = ["mypy-boto3-panorama (>=1.37.0,<1.38.0)"]
-partnercentral-selling = ["mypy-boto3-partnercentral-selling (>=1.37.0,<1.38.0)"]
-payment-cryptography = ["mypy-boto3-payment-cryptography (>=1.37.0,<1.38.0)"]
-payment-cryptography-data = ["mypy-boto3-payment-cryptography-data (>=1.37.0,<1.38.0)"]
-pca-connector-ad = ["mypy-boto3-pca-connector-ad (>=1.37.0,<1.38.0)"]
-pca-connector-scep = ["mypy-boto3-pca-connector-scep (>=1.37.0,<1.38.0)"]
-pcs = ["mypy-boto3-pcs (>=1.37.0,<1.38.0)"]
-personalize = ["mypy-boto3-personalize (>=1.37.0,<1.38.0)"]
-personalize-events = ["mypy-boto3-personalize-events (>=1.37.0,<1.38.0)"]
-personalize-runtime = ["mypy-boto3-personalize-runtime (>=1.37.0,<1.38.0)"]
-pi = ["mypy-boto3-pi (>=1.37.0,<1.38.0)"]
-pinpoint = ["mypy-boto3-pinpoint (>=1.37.0,<1.38.0)"]
-pinpoint-email = ["mypy-boto3-pinpoint-email (>=1.37.0,<1.38.0)"]
-pinpoint-sms-voice = ["mypy-boto3-pinpoint-sms-voice (>=1.37.0,<1.38.0)"]
-pinpoint-sms-voice-v2 = ["mypy-boto3-pinpoint-sms-voice-v2 (>=1.37.0,<1.38.0)"]
-pipes = ["mypy-boto3-pipes (>=1.37.0,<1.38.0)"]
-polly = ["mypy-boto3-polly (>=1.37.0,<1.38.0)"]
-pricing = ["mypy-boto3-pricing (>=1.37.0,<1.38.0)"]
-privatenetworks = ["mypy-boto3-privatenetworks (>=1.37.0,<1.38.0)"]
-proton = ["mypy-boto3-proton (>=1.37.0,<1.38.0)"]
-qapps = ["mypy-boto3-qapps (>=1.37.0,<1.38.0)"]
-qbusiness = ["mypy-boto3-qbusiness (>=1.37.0,<1.38.0)"]
-qconnect = ["mypy-boto3-qconnect (>=1.37.0,<1.38.0)"]
-qldb = ["mypy-boto3-qldb (>=1.37.0,<1.38.0)"]
-qldb-session = ["mypy-boto3-qldb-session (>=1.37.0,<1.38.0)"]
-quicksight = ["mypy-boto3-quicksight (>=1.37.0,<1.38.0)"]
-ram = ["mypy-boto3-ram (>=1.37.0,<1.38.0)"]
-rbin = ["mypy-boto3-rbin (>=1.37.0,<1.38.0)"]
-rds = ["mypy-boto3-rds (>=1.37.0,<1.38.0)"]
-rds-data = ["mypy-boto3-rds-data (>=1.37.0,<1.38.0)"]
-redshift = ["mypy-boto3-redshift (>=1.37.0,<1.38.0)"]
-redshift-data = ["mypy-boto3-redshift-data (>=1.37.0,<1.38.0)"]
-redshift-serverless = ["mypy-boto3-redshift-serverless (>=1.37.0,<1.38.0)"]
-rekognition = ["mypy-boto3-rekognition (>=1.37.0,<1.38.0)"]
-repostspace = ["mypy-boto3-repostspace (>=1.37.0,<1.38.0)"]
-resiliencehub = ["mypy-boto3-resiliencehub (>=1.37.0,<1.38.0)"]
-resource-explorer-2 = ["mypy-boto3-resource-explorer-2 (>=1.37.0,<1.38.0)"]
-resource-groups = ["mypy-boto3-resource-groups (>=1.37.0,<1.38.0)"]
-resourcegroupstaggingapi = ["mypy-boto3-resourcegroupstaggingapi (>=1.37.0,<1.38.0)"]
-robomaker = ["mypy-boto3-robomaker (>=1.37.0,<1.38.0)"]
-rolesanywhere = ["mypy-boto3-rolesanywhere (>=1.37.0,<1.38.0)"]
-route53 = ["mypy-boto3-route53 (>=1.37.0,<1.38.0)"]
-route53-recovery-cluster = ["mypy-boto3-route53-recovery-cluster (>=1.37.0,<1.38.0)"]
-route53-recovery-control-config = ["mypy-boto3-route53-recovery-control-config (>=1.37.0,<1.38.0)"]
-route53-recovery-readiness = ["mypy-boto3-route53-recovery-readiness (>=1.37.0,<1.38.0)"]
-route53domains = ["mypy-boto3-route53domains (>=1.37.0,<1.38.0)"]
-route53profiles = ["mypy-boto3-route53profiles (>=1.37.0,<1.38.0)"]
-route53resolver = ["mypy-boto3-route53resolver (>=1.37.0,<1.38.0)"]
-rum = ["mypy-boto3-rum (>=1.37.0,<1.38.0)"]
-s3 = ["mypy-boto3-s3 (>=1.37.0,<1.38.0)"]
-s3control = ["mypy-boto3-s3control (>=1.37.0,<1.38.0)"]
-s3outposts = ["mypy-boto3-s3outposts (>=1.37.0,<1.38.0)"]
-s3tables = ["mypy-boto3-s3tables (>=1.37.0,<1.38.0)"]
-sagemaker = ["mypy-boto3-sagemaker (>=1.37.0,<1.38.0)"]
-sagemaker-a2i-runtime = ["mypy-boto3-sagemaker-a2i-runtime (>=1.37.0,<1.38.0)"]
-sagemaker-edge = ["mypy-boto3-sagemaker-edge (>=1.37.0,<1.38.0)"]
-sagemaker-featurestore-runtime = ["mypy-boto3-sagemaker-featurestore-runtime (>=1.37.0,<1.38.0)"]
-sagemaker-geospatial = ["mypy-boto3-sagemaker-geospatial (>=1.37.0,<1.38.0)"]
-sagemaker-metrics = ["mypy-boto3-sagemaker-metrics (>=1.37.0,<1.38.0)"]
-sagemaker-runtime = ["mypy-boto3-sagemaker-runtime (>=1.37.0,<1.38.0)"]
-savingsplans = ["mypy-boto3-savingsplans (>=1.37.0,<1.38.0)"]
-scheduler = ["mypy-boto3-scheduler (>=1.37.0,<1.38.0)"]
-schemas = ["mypy-boto3-schemas (>=1.37.0,<1.38.0)"]
-sdb = ["mypy-boto3-sdb (>=1.37.0,<1.38.0)"]
-secretsmanager = ["mypy-boto3-secretsmanager (>=1.37.0,<1.38.0)"]
-security-ir = ["mypy-boto3-security-ir (>=1.37.0,<1.38.0)"]
-securityhub = ["mypy-boto3-securityhub (>=1.37.0,<1.38.0)"]
-securitylake = ["mypy-boto3-securitylake (>=1.37.0,<1.38.0)"]
-serverlessrepo = ["mypy-boto3-serverlessrepo (>=1.37.0,<1.38.0)"]
-service-quotas = ["mypy-boto3-service-quotas (>=1.37.0,<1.38.0)"]
-servicecatalog = ["mypy-boto3-servicecatalog (>=1.37.0,<1.38.0)"]
-servicecatalog-appregistry = ["mypy-boto3-servicecatalog-appregistry (>=1.37.0,<1.38.0)"]
-servicediscovery = ["mypy-boto3-servicediscovery (>=1.37.0,<1.38.0)"]
-ses = ["mypy-boto3-ses (>=1.37.0,<1.38.0)"]
-sesv2 = ["mypy-boto3-sesv2 (>=1.37.0,<1.38.0)"]
-shield = ["mypy-boto3-shield (>=1.37.0,<1.38.0)"]
-signer = ["mypy-boto3-signer (>=1.37.0,<1.38.0)"]
-simspaceweaver = ["mypy-boto3-simspaceweaver (>=1.37.0,<1.38.0)"]
-sms = ["mypy-boto3-sms (>=1.37.0,<1.38.0)"]
-sms-voice = ["mypy-boto3-sms-voice (>=1.37.0,<1.38.0)"]
-snow-device-management = ["mypy-boto3-snow-device-management (>=1.37.0,<1.38.0)"]
-snowball = ["mypy-boto3-snowball (>=1.37.0,<1.38.0)"]
-sns = ["mypy-boto3-sns (>=1.37.0,<1.38.0)"]
-socialmessaging = ["mypy-boto3-socialmessaging (>=1.37.0,<1.38.0)"]
-sqs = ["mypy-boto3-sqs (>=1.37.0,<1.38.0)"]
-ssm = ["mypy-boto3-ssm (>=1.37.0,<1.38.0)"]
-ssm-contacts = ["mypy-boto3-ssm-contacts (>=1.37.0,<1.38.0)"]
-ssm-incidents = ["mypy-boto3-ssm-incidents (>=1.37.0,<1.38.0)"]
-ssm-quicksetup = ["mypy-boto3-ssm-quicksetup (>=1.37.0,<1.38.0)"]
-ssm-sap = ["mypy-boto3-ssm-sap (>=1.37.0,<1.38.0)"]
-sso = ["mypy-boto3-sso (>=1.37.0,<1.38.0)"]
-sso-admin = ["mypy-boto3-sso-admin (>=1.37.0,<1.38.0)"]
-sso-oidc = ["mypy-boto3-sso-oidc (>=1.37.0,<1.38.0)"]
-stepfunctions = ["mypy-boto3-stepfunctions (>=1.37.0,<1.38.0)"]
-storagegateway = ["mypy-boto3-storagegateway (>=1.37.0,<1.38.0)"]
-sts = ["mypy-boto3-sts (>=1.37.0,<1.38.0)"]
-supplychain = ["mypy-boto3-supplychain (>=1.37.0,<1.38.0)"]
-support = ["mypy-boto3-support (>=1.37.0,<1.38.0)"]
-support-app = ["mypy-boto3-support-app (>=1.37.0,<1.38.0)"]
-swf = ["mypy-boto3-swf (>=1.37.0,<1.38.0)"]
-synthetics = ["mypy-boto3-synthetics (>=1.37.0,<1.38.0)"]
-taxsettings = ["mypy-boto3-taxsettings (>=1.37.0,<1.38.0)"]
-textract = ["mypy-boto3-textract (>=1.37.0,<1.38.0)"]
-timestream-influxdb = ["mypy-boto3-timestream-influxdb (>=1.37.0,<1.38.0)"]
-timestream-query = ["mypy-boto3-timestream-query (>=1.37.0,<1.38.0)"]
-timestream-write = ["mypy-boto3-timestream-write (>=1.37.0,<1.38.0)"]
-tnb = ["mypy-boto3-tnb (>=1.37.0,<1.38.0)"]
-transcribe = ["mypy-boto3-transcribe (>=1.37.0,<1.38.0)"]
-transfer = ["mypy-boto3-transfer (>=1.37.0,<1.38.0)"]
-translate = ["mypy-boto3-translate (>=1.37.0,<1.38.0)"]
-trustedadvisor = ["mypy-boto3-trustedadvisor (>=1.37.0,<1.38.0)"]
-verifiedpermissions = ["mypy-boto3-verifiedpermissions (>=1.37.0,<1.38.0)"]
-voice-id = ["mypy-boto3-voice-id (>=1.37.0,<1.38.0)"]
-vpc-lattice = ["mypy-boto3-vpc-lattice (>=1.37.0,<1.38.0)"]
-waf = ["mypy-boto3-waf (>=1.37.0,<1.38.0)"]
-waf-regional = ["mypy-boto3-waf-regional (>=1.37.0,<1.38.0)"]
-wafv2 = ["mypy-boto3-wafv2 (>=1.37.0,<1.38.0)"]
-wellarchitected = ["mypy-boto3-wellarchitected (>=1.37.0,<1.38.0)"]
-wisdom = ["mypy-boto3-wisdom (>=1.37.0,<1.38.0)"]
-workdocs = ["mypy-boto3-workdocs (>=1.37.0,<1.38.0)"]
-workmail = ["mypy-boto3-workmail (>=1.37.0,<1.38.0)"]
-workmailmessageflow = ["mypy-boto3-workmailmessageflow (>=1.37.0,<1.38.0)"]
-workspaces = ["mypy-boto3-workspaces (>=1.37.0,<1.38.0)"]
-workspaces-thin-client = ["mypy-boto3-workspaces-thin-client (>=1.37.0,<1.38.0)"]
-workspaces-web = ["mypy-boto3-workspaces-web (>=1.37.0,<1.38.0)"]
-xray = ["mypy-boto3-xray (>=1.37.0,<1.38.0)"]
+accessanalyzer = ["mypy-boto3-accessanalyzer (>=1.38.0,<1.39.0)"]
+account = ["mypy-boto3-account (>=1.38.0,<1.39.0)"]
+acm = ["mypy-boto3-acm (>=1.38.0,<1.39.0)"]
+acm-pca = ["mypy-boto3-acm-pca (>=1.38.0,<1.39.0)"]
+all = ["mypy-boto3-accessanalyzer (>=1.38.0,<1.39.0)", "mypy-boto3-account (>=1.38.0,<1.39.0)", "mypy-boto3-acm (>=1.38.0,<1.39.0)", "mypy-boto3-acm-pca (>=1.38.0,<1.39.0)", "mypy-boto3-amp (>=1.38.0,<1.39.0)", "mypy-boto3-amplify (>=1.38.0,<1.39.0)", "mypy-boto3-amplifybackend (>=1.38.0,<1.39.0)", "mypy-boto3-amplifyuibuilder (>=1.38.0,<1.39.0)", "mypy-boto3-apigateway (>=1.38.0,<1.39.0)", "mypy-boto3-apigatewaymanagementapi (>=1.38.0,<1.39.0)", "mypy-boto3-apigatewayv2 (>=1.38.0,<1.39.0)", "mypy-boto3-appconfig (>=1.38.0,<1.39.0)", "mypy-boto3-appconfigdata (>=1.38.0,<1.39.0)", "mypy-boto3-appfabric (>=1.38.0,<1.39.0)", "mypy-boto3-appflow (>=1.38.0,<1.39.0)", "mypy-boto3-appintegrations (>=1.38.0,<1.39.0)", "mypy-boto3-application-autoscaling (>=1.38.0,<1.39.0)", "mypy-boto3-application-insights (>=1.38.0,<1.39.0)", "mypy-boto3-application-signals (>=1.38.0,<1.39.0)", "mypy-boto3-applicationcostprofiler (>=1.38.0,<1.39.0)", "mypy-boto3-appmesh (>=1.38.0,<1.39.0)", "mypy-boto3-apprunner (>=1.38.0,<1.39.0)", "mypy-boto3-appstream (>=1.38.0,<1.39.0)", "mypy-boto3-appsync (>=1.38.0,<1.39.0)", "mypy-boto3-apptest (>=1.38.0,<1.39.0)", "mypy-boto3-arc-zonal-shift (>=1.38.0,<1.39.0)", "mypy-boto3-artifact (>=1.38.0,<1.39.0)", "mypy-boto3-athena (>=1.38.0,<1.39.0)", "mypy-boto3-auditmanager (>=1.38.0,<1.39.0)", "mypy-boto3-autoscaling (>=1.38.0,<1.39.0)", "mypy-boto3-autoscaling-plans (>=1.38.0,<1.39.0)", "mypy-boto3-b2bi (>=1.38.0,<1.39.0)", "mypy-boto3-backup (>=1.38.0,<1.39.0)", "mypy-boto3-backup-gateway (>=1.38.0,<1.39.0)", "mypy-boto3-backupsearch (>=1.38.0,<1.39.0)", "mypy-boto3-batch (>=1.38.0,<1.39.0)", "mypy-boto3-bcm-data-exports (>=1.38.0,<1.39.0)", "mypy-boto3-bcm-pricing-calculator (>=1.38.0,<1.39.0)", "mypy-boto3-bedrock (>=1.38.0,<1.39.0)", "mypy-boto3-bedrock-agent (>=1.38.0,<1.39.0)", "mypy-boto3-bedrock-agent-runtime (>=1.38.0,<1.39.0)", "mypy-boto3-bedrock-data-automation (>=1.38.0,<1.39.0)", "mypy-boto3-bedrock-data-automation-runtime (>=1.38.0,<1.39.0)", "mypy-boto3-bedrock-runtime (>=1.38.0,<1.39.0)", "mypy-boto3-billing (>=1.38.0,<1.39.0)", "mypy-boto3-billingconductor (>=1.38.0,<1.39.0)", "mypy-boto3-braket (>=1.38.0,<1.39.0)", "mypy-boto3-budgets (>=1.38.0,<1.39.0)", "mypy-boto3-ce (>=1.38.0,<1.39.0)", "mypy-boto3-chatbot (>=1.38.0,<1.39.0)", "mypy-boto3-chime (>=1.38.0,<1.39.0)", "mypy-boto3-chime-sdk-identity (>=1.38.0,<1.39.0)", "mypy-boto3-chime-sdk-media-pipelines (>=1.38.0,<1.39.0)", "mypy-boto3-chime-sdk-meetings (>=1.38.0,<1.39.0)", "mypy-boto3-chime-sdk-messaging (>=1.38.0,<1.39.0)", "mypy-boto3-chime-sdk-voice (>=1.38.0,<1.39.0)", "mypy-boto3-cleanrooms (>=1.38.0,<1.39.0)", "mypy-boto3-cleanroomsml (>=1.38.0,<1.39.0)", "mypy-boto3-cloud9 (>=1.38.0,<1.39.0)", "mypy-boto3-cloudcontrol (>=1.38.0,<1.39.0)", "mypy-boto3-clouddirectory (>=1.38.0,<1.39.0)", "mypy-boto3-cloudformation (>=1.38.0,<1.39.0)", "mypy-boto3-cloudfront (>=1.38.0,<1.39.0)", "mypy-boto3-cloudfront-keyvaluestore (>=1.38.0,<1.39.0)", "mypy-boto3-cloudhsm (>=1.38.0,<1.39.0)", "mypy-boto3-cloudhsmv2 (>=1.38.0,<1.39.0)", "mypy-boto3-cloudsearch (>=1.38.0,<1.39.0)", "mypy-boto3-cloudsearchdomain (>=1.38.0,<1.39.0)", "mypy-boto3-cloudtrail (>=1.38.0,<1.39.0)", "mypy-boto3-cloudtrail-data (>=1.38.0,<1.39.0)", "mypy-boto3-cloudwatch (>=1.38.0,<1.39.0)", "mypy-boto3-codeartifact (>=1.38.0,<1.39.0)", "mypy-boto3-codebuild (>=1.38.0,<1.39.0)", "mypy-boto3-codecatalyst (>=1.38.0,<1.39.0)", "mypy-boto3-codecommit (>=1.38.0,<1.39.0)", "mypy-boto3-codeconnections (>=1.38.0,<1.39.0)", "mypy-boto3-codedeploy (>=1.38.0,<1.39.0)", "mypy-boto3-codeguru-reviewer (>=1.38.0,<1.39.0)", "mypy-boto3-codeguru-security (>=1.38.0,<1.39.0)", "mypy-boto3-codeguruprofiler (>=1.38.0,<1.39.0)", "mypy-boto3-codepipeline (>=1.38.0,<1.39.0)", "mypy-boto3-codestar-connections (>=1.38.0,<1.39.0)", "mypy-boto3-codestar-notifications (>=1.38.0,<1.39.0)", "mypy-boto3-cognito-identity (>=1.38.0,<1.39.0)", "mypy-boto3-cognito-idp (>=1.38.0,<1.39.0)", "mypy-boto3-cognito-sync (>=1.38.0,<1.39.0)", "mypy-boto3-comprehend (>=1.38.0,<1.39.0)", "mypy-boto3-comprehendmedical (>=1.38.0,<1.39.0)", "mypy-boto3-compute-optimizer (>=1.38.0,<1.39.0)", "mypy-boto3-config (>=1.38.0,<1.39.0)", "mypy-boto3-connect (>=1.38.0,<1.39.0)", "mypy-boto3-connect-contact-lens (>=1.38.0,<1.39.0)", "mypy-boto3-connectcampaigns (>=1.38.0,<1.39.0)", "mypy-boto3-connectcampaignsv2 (>=1.38.0,<1.39.0)", "mypy-boto3-connectcases (>=1.38.0,<1.39.0)", "mypy-boto3-connectparticipant (>=1.38.0,<1.39.0)", "mypy-boto3-controlcatalog (>=1.38.0,<1.39.0)", "mypy-boto3-controltower (>=1.38.0,<1.39.0)", "mypy-boto3-cost-optimization-hub (>=1.38.0,<1.39.0)", "mypy-boto3-cur (>=1.38.0,<1.39.0)", "mypy-boto3-customer-profiles (>=1.38.0,<1.39.0)", "mypy-boto3-databrew (>=1.38.0,<1.39.0)", "mypy-boto3-dataexchange (>=1.38.0,<1.39.0)", "mypy-boto3-datapipeline (>=1.38.0,<1.39.0)", "mypy-boto3-datasync (>=1.38.0,<1.39.0)", "mypy-boto3-datazone (>=1.38.0,<1.39.0)", "mypy-boto3-dax (>=1.38.0,<1.39.0)", "mypy-boto3-deadline (>=1.38.0,<1.39.0)", "mypy-boto3-detective (>=1.38.0,<1.39.0)", "mypy-boto3-devicefarm (>=1.38.0,<1.39.0)", "mypy-boto3-devops-guru (>=1.38.0,<1.39.0)", "mypy-boto3-directconnect (>=1.38.0,<1.39.0)", "mypy-boto3-discovery (>=1.38.0,<1.39.0)", "mypy-boto3-dlm (>=1.38.0,<1.39.0)", "mypy-boto3-dms (>=1.38.0,<1.39.0)", "mypy-boto3-docdb (>=1.38.0,<1.39.0)", "mypy-boto3-docdb-elastic (>=1.38.0,<1.39.0)", "mypy-boto3-drs (>=1.38.0,<1.39.0)", "mypy-boto3-ds (>=1.38.0,<1.39.0)", "mypy-boto3-ds-data (>=1.38.0,<1.39.0)", "mypy-boto3-dsql (>=1.38.0,<1.39.0)", "mypy-boto3-dynamodb (>=1.38.0,<1.39.0)", "mypy-boto3-dynamodbstreams (>=1.38.0,<1.39.0)", "mypy-boto3-ebs (>=1.38.0,<1.39.0)", "mypy-boto3-ec2 (>=1.38.0,<1.39.0)", "mypy-boto3-ec2-instance-connect (>=1.38.0,<1.39.0)", "mypy-boto3-ecr (>=1.38.0,<1.39.0)", "mypy-boto3-ecr-public (>=1.38.0,<1.39.0)", "mypy-boto3-ecs (>=1.38.0,<1.39.0)", "mypy-boto3-efs (>=1.38.0,<1.39.0)", "mypy-boto3-eks (>=1.38.0,<1.39.0)", "mypy-boto3-eks-auth (>=1.38.0,<1.39.0)", "mypy-boto3-elasticache (>=1.38.0,<1.39.0)", "mypy-boto3-elasticbeanstalk (>=1.38.0,<1.39.0)", "mypy-boto3-elastictranscoder (>=1.38.0,<1.39.0)", "mypy-boto3-elb (>=1.38.0,<1.39.0)", "mypy-boto3-elbv2 (>=1.38.0,<1.39.0)", "mypy-boto3-emr (>=1.38.0,<1.39.0)", "mypy-boto3-emr-containers (>=1.38.0,<1.39.0)", "mypy-boto3-emr-serverless (>=1.38.0,<1.39.0)", "mypy-boto3-entityresolution (>=1.38.0,<1.39.0)", "mypy-boto3-es (>=1.38.0,<1.39.0)", "mypy-boto3-events (>=1.38.0,<1.39.0)", "mypy-boto3-evidently (>=1.38.0,<1.39.0)", "mypy-boto3-finspace (>=1.38.0,<1.39.0)", "mypy-boto3-finspace-data (>=1.38.0,<1.39.0)", "mypy-boto3-firehose (>=1.38.0,<1.39.0)", "mypy-boto3-fis (>=1.38.0,<1.39.0)", "mypy-boto3-fms (>=1.38.0,<1.39.0)", "mypy-boto3-forecast (>=1.38.0,<1.39.0)", "mypy-boto3-forecastquery (>=1.38.0,<1.39.0)", "mypy-boto3-frauddetector (>=1.38.0,<1.39.0)", "mypy-boto3-freetier (>=1.38.0,<1.39.0)", "mypy-boto3-fsx (>=1.38.0,<1.39.0)", "mypy-boto3-gamelift (>=1.38.0,<1.39.0)", "mypy-boto3-gameliftstreams (>=1.38.0,<1.39.0)", "mypy-boto3-geo-maps (>=1.38.0,<1.39.0)", "mypy-boto3-geo-places (>=1.38.0,<1.39.0)", "mypy-boto3-geo-routes (>=1.38.0,<1.39.0)", "mypy-boto3-glacier (>=1.38.0,<1.39.0)", "mypy-boto3-globalaccelerator (>=1.38.0,<1.39.0)", "mypy-boto3-glue (>=1.38.0,<1.39.0)", "mypy-boto3-grafana (>=1.38.0,<1.39.0)", "mypy-boto3-greengrass (>=1.38.0,<1.39.0)", "mypy-boto3-greengrassv2 (>=1.38.0,<1.39.0)", "mypy-boto3-groundstation (>=1.38.0,<1.39.0)", "mypy-boto3-guardduty (>=1.38.0,<1.39.0)", "mypy-boto3-health (>=1.38.0,<1.39.0)", "mypy-boto3-healthlake (>=1.38.0,<1.39.0)", "mypy-boto3-iam (>=1.38.0,<1.39.0)", "mypy-boto3-identitystore (>=1.38.0,<1.39.0)", "mypy-boto3-imagebuilder (>=1.38.0,<1.39.0)", "mypy-boto3-importexport (>=1.38.0,<1.39.0)", "mypy-boto3-inspector (>=1.38.0,<1.39.0)", "mypy-boto3-inspector-scan (>=1.38.0,<1.39.0)", "mypy-boto3-inspector2 (>=1.38.0,<1.39.0)", "mypy-boto3-internetmonitor (>=1.38.0,<1.39.0)", "mypy-boto3-invoicing (>=1.38.0,<1.39.0)", "mypy-boto3-iot (>=1.38.0,<1.39.0)", "mypy-boto3-iot-data (>=1.38.0,<1.39.0)", "mypy-boto3-iot-jobs-data (>=1.38.0,<1.39.0)", "mypy-boto3-iot-managed-integrations (>=1.38.0,<1.39.0)", "mypy-boto3-iotanalytics (>=1.38.0,<1.39.0)", "mypy-boto3-iotdeviceadvisor (>=1.38.0,<1.39.0)", "mypy-boto3-iotevents (>=1.38.0,<1.39.0)", "mypy-boto3-iotevents-data (>=1.38.0,<1.39.0)", "mypy-boto3-iotfleethub (>=1.38.0,<1.39.0)", "mypy-boto3-iotfleetwise (>=1.38.0,<1.39.0)", "mypy-boto3-iotsecuretunneling (>=1.38.0,<1.39.0)", "mypy-boto3-iotsitewise (>=1.38.0,<1.39.0)", "mypy-boto3-iotthingsgraph (>=1.38.0,<1.39.0)", "mypy-boto3-iottwinmaker (>=1.38.0,<1.39.0)", "mypy-boto3-iotwireless (>=1.38.0,<1.39.0)", "mypy-boto3-ivs (>=1.38.0,<1.39.0)", "mypy-boto3-ivs-realtime (>=1.38.0,<1.39.0)", "mypy-boto3-ivschat (>=1.38.0,<1.39.0)", "mypy-boto3-kafka (>=1.38.0,<1.39.0)", "mypy-boto3-kafkaconnect (>=1.38.0,<1.39.0)", "mypy-boto3-kendra (>=1.38.0,<1.39.0)", "mypy-boto3-kendra-ranking (>=1.38.0,<1.39.0)", "mypy-boto3-keyspaces (>=1.38.0,<1.39.0)", "mypy-boto3-kinesis (>=1.38.0,<1.39.0)", "mypy-boto3-kinesis-video-archived-media (>=1.38.0,<1.39.0)", "mypy-boto3-kinesis-video-media (>=1.38.0,<1.39.0)", "mypy-boto3-kinesis-video-signaling (>=1.38.0,<1.39.0)", "mypy-boto3-kinesis-video-webrtc-storage (>=1.38.0,<1.39.0)", "mypy-boto3-kinesisanalytics (>=1.38.0,<1.39.0)", "mypy-boto3-kinesisanalyticsv2 (>=1.38.0,<1.39.0)", "mypy-boto3-kinesisvideo (>=1.38.0,<1.39.0)", "mypy-boto3-kms (>=1.38.0,<1.39.0)", "mypy-boto3-lakeformation (>=1.38.0,<1.39.0)", "mypy-boto3-lambda (>=1.38.0,<1.39.0)", "mypy-boto3-launch-wizard (>=1.38.0,<1.39.0)", "mypy-boto3-lex-models (>=1.38.0,<1.39.0)", "mypy-boto3-lex-runtime (>=1.38.0,<1.39.0)", "mypy-boto3-lexv2-models (>=1.38.0,<1.39.0)", "mypy-boto3-lexv2-runtime (>=1.38.0,<1.39.0)", "mypy-boto3-license-manager (>=1.38.0,<1.39.0)", "mypy-boto3-license-manager-linux-subscriptions (>=1.38.0,<1.39.0)", "mypy-boto3-license-manager-user-subscriptions (>=1.38.0,<1.39.0)", "mypy-boto3-lightsail (>=1.38.0,<1.39.0)", "mypy-boto3-location (>=1.38.0,<1.39.0)", "mypy-boto3-logs (>=1.38.0,<1.39.0)", "mypy-boto3-lookoutequipment (>=1.38.0,<1.39.0)", "mypy-boto3-lookoutmetrics (>=1.38.0,<1.39.0)", "mypy-boto3-lookoutvision (>=1.38.0,<1.39.0)", "mypy-boto3-m2 (>=1.38.0,<1.39.0)", "mypy-boto3-machinelearning (>=1.38.0,<1.39.0)", "mypy-boto3-macie2 (>=1.38.0,<1.39.0)", "mypy-boto3-mailmanager (>=1.38.0,<1.39.0)", "mypy-boto3-managedblockchain (>=1.38.0,<1.39.0)", "mypy-boto3-managedblockchain-query (>=1.38.0,<1.39.0)", "mypy-boto3-marketplace-agreement (>=1.38.0,<1.39.0)", "mypy-boto3-marketplace-catalog (>=1.38.0,<1.39.0)", "mypy-boto3-marketplace-deployment (>=1.38.0,<1.39.0)", "mypy-boto3-marketplace-entitlement (>=1.38.0,<1.39.0)", "mypy-boto3-marketplace-reporting (>=1.38.0,<1.39.0)", "mypy-boto3-marketplacecommerceanalytics (>=1.38.0,<1.39.0)", "mypy-boto3-mediaconnect (>=1.38.0,<1.39.0)", "mypy-boto3-mediaconvert (>=1.38.0,<1.39.0)", "mypy-boto3-medialive (>=1.38.0,<1.39.0)", "mypy-boto3-mediapackage (>=1.38.0,<1.39.0)", "mypy-boto3-mediapackage-vod (>=1.38.0,<1.39.0)", "mypy-boto3-mediapackagev2 (>=1.38.0,<1.39.0)", "mypy-boto3-mediastore (>=1.38.0,<1.39.0)", "mypy-boto3-mediastore-data (>=1.38.0,<1.39.0)", "mypy-boto3-mediatailor (>=1.38.0,<1.39.0)", "mypy-boto3-medical-imaging (>=1.38.0,<1.39.0)", "mypy-boto3-memorydb (>=1.38.0,<1.39.0)", "mypy-boto3-meteringmarketplace (>=1.38.0,<1.39.0)", "mypy-boto3-mgh (>=1.38.0,<1.39.0)", "mypy-boto3-mgn (>=1.38.0,<1.39.0)", "mypy-boto3-migration-hub-refactor-spaces (>=1.38.0,<1.39.0)", "mypy-boto3-migrationhub-config (>=1.38.0,<1.39.0)", "mypy-boto3-migrationhuborchestrator (>=1.38.0,<1.39.0)", "mypy-boto3-migrationhubstrategy (>=1.38.0,<1.39.0)", "mypy-boto3-mq (>=1.38.0,<1.39.0)", "mypy-boto3-mturk (>=1.38.0,<1.39.0)", "mypy-boto3-mwaa (>=1.38.0,<1.39.0)", "mypy-boto3-neptune (>=1.38.0,<1.39.0)", "mypy-boto3-neptune-graph (>=1.38.0,<1.39.0)", "mypy-boto3-neptunedata (>=1.38.0,<1.39.0)", "mypy-boto3-network-firewall (>=1.38.0,<1.39.0)", "mypy-boto3-networkflowmonitor (>=1.38.0,<1.39.0)", "mypy-boto3-networkmanager (>=1.38.0,<1.39.0)", "mypy-boto3-networkmonitor (>=1.38.0,<1.39.0)", "mypy-boto3-notifications (>=1.38.0,<1.39.0)", "mypy-boto3-notificationscontacts (>=1.38.0,<1.39.0)", "mypy-boto3-oam (>=1.38.0,<1.39.0)", "mypy-boto3-observabilityadmin (>=1.38.0,<1.39.0)", "mypy-boto3-omics (>=1.38.0,<1.39.0)", "mypy-boto3-opensearch (>=1.38.0,<1.39.0)", "mypy-boto3-opensearchserverless (>=1.38.0,<1.39.0)", "mypy-boto3-opsworks (>=1.38.0,<1.39.0)", "mypy-boto3-opsworkscm (>=1.38.0,<1.39.0)", "mypy-boto3-organizations (>=1.38.0,<1.39.0)", "mypy-boto3-osis (>=1.38.0,<1.39.0)", "mypy-boto3-outposts (>=1.38.0,<1.39.0)", "mypy-boto3-panorama (>=1.38.0,<1.39.0)", "mypy-boto3-partnercentral-selling (>=1.38.0,<1.39.0)", "mypy-boto3-payment-cryptography (>=1.38.0,<1.39.0)", "mypy-boto3-payment-cryptography-data (>=1.38.0,<1.39.0)", "mypy-boto3-pca-connector-ad (>=1.38.0,<1.39.0)", "mypy-boto3-pca-connector-scep (>=1.38.0,<1.39.0)", "mypy-boto3-pcs (>=1.38.0,<1.39.0)", "mypy-boto3-personalize (>=1.38.0,<1.39.0)", "mypy-boto3-personalize-events (>=1.38.0,<1.39.0)", "mypy-boto3-personalize-runtime (>=1.38.0,<1.39.0)", "mypy-boto3-pi (>=1.38.0,<1.39.0)", "mypy-boto3-pinpoint (>=1.38.0,<1.39.0)", "mypy-boto3-pinpoint-email (>=1.38.0,<1.39.0)", "mypy-boto3-pinpoint-sms-voice (>=1.38.0,<1.39.0)", "mypy-boto3-pinpoint-sms-voice-v2 (>=1.38.0,<1.39.0)", "mypy-boto3-pipes (>=1.38.0,<1.39.0)", "mypy-boto3-polly (>=1.38.0,<1.39.0)", "mypy-boto3-pricing (>=1.38.0,<1.39.0)", "mypy-boto3-privatenetworks (>=1.38.0,<1.39.0)", "mypy-boto3-proton (>=1.38.0,<1.39.0)", "mypy-boto3-qapps (>=1.38.0,<1.39.0)", "mypy-boto3-qbusiness (>=1.38.0,<1.39.0)", "mypy-boto3-qconnect (>=1.38.0,<1.39.0)", "mypy-boto3-qldb (>=1.38.0,<1.39.0)", "mypy-boto3-qldb-session (>=1.38.0,<1.39.0)", "mypy-boto3-quicksight (>=1.38.0,<1.39.0)", "mypy-boto3-ram (>=1.38.0,<1.39.0)", "mypy-boto3-rbin (>=1.38.0,<1.39.0)", "mypy-boto3-rds (>=1.38.0,<1.39.0)", "mypy-boto3-rds-data (>=1.38.0,<1.39.0)", "mypy-boto3-redshift (>=1.38.0,<1.39.0)", "mypy-boto3-redshift-data (>=1.38.0,<1.39.0)", "mypy-boto3-redshift-serverless (>=1.38.0,<1.39.0)", "mypy-boto3-rekognition (>=1.38.0,<1.39.0)", "mypy-boto3-repostspace (>=1.38.0,<1.39.0)", "mypy-boto3-resiliencehub (>=1.38.0,<1.39.0)", "mypy-boto3-resource-explorer-2 (>=1.38.0,<1.39.0)", "mypy-boto3-resource-groups (>=1.38.0,<1.39.0)", "mypy-boto3-resourcegroupstaggingapi (>=1.38.0,<1.39.0)", "mypy-boto3-robomaker (>=1.38.0,<1.39.0)", "mypy-boto3-rolesanywhere (>=1.38.0,<1.39.0)", "mypy-boto3-route53 (>=1.38.0,<1.39.0)", "mypy-boto3-route53-recovery-cluster (>=1.38.0,<1.39.0)", "mypy-boto3-route53-recovery-control-config (>=1.38.0,<1.39.0)", "mypy-boto3-route53-recovery-readiness (>=1.38.0,<1.39.0)", "mypy-boto3-route53domains (>=1.38.0,<1.39.0)", "mypy-boto3-route53profiles (>=1.38.0,<1.39.0)", "mypy-boto3-route53resolver (>=1.38.0,<1.39.0)", "mypy-boto3-rum (>=1.38.0,<1.39.0)", "mypy-boto3-s3 (>=1.38.0,<1.39.0)", "mypy-boto3-s3control (>=1.38.0,<1.39.0)", "mypy-boto3-s3outposts (>=1.38.0,<1.39.0)", "mypy-boto3-s3tables (>=1.38.0,<1.39.0)", "mypy-boto3-sagemaker (>=1.38.0,<1.39.0)", "mypy-boto3-sagemaker-a2i-runtime (>=1.38.0,<1.39.0)", "mypy-boto3-sagemaker-edge (>=1.38.0,<1.39.0)", "mypy-boto3-sagemaker-featurestore-runtime (>=1.38.0,<1.39.0)", "mypy-boto3-sagemaker-geospatial (>=1.38.0,<1.39.0)", "mypy-boto3-sagemaker-metrics (>=1.38.0,<1.39.0)", "mypy-boto3-sagemaker-runtime (>=1.38.0,<1.39.0)", "mypy-boto3-savingsplans (>=1.38.0,<1.39.0)", "mypy-boto3-scheduler (>=1.38.0,<1.39.0)", "mypy-boto3-schemas (>=1.38.0,<1.39.0)", "mypy-boto3-sdb (>=1.38.0,<1.39.0)", "mypy-boto3-secretsmanager (>=1.38.0,<1.39.0)", "mypy-boto3-security-ir (>=1.38.0,<1.39.0)", "mypy-boto3-securityhub (>=1.38.0,<1.39.0)", "mypy-boto3-securitylake (>=1.38.0,<1.39.0)", "mypy-boto3-serverlessrepo (>=1.38.0,<1.39.0)", "mypy-boto3-service-quotas (>=1.38.0,<1.39.0)", "mypy-boto3-servicecatalog (>=1.38.0,<1.39.0)", "mypy-boto3-servicecatalog-appregistry (>=1.38.0,<1.39.0)", "mypy-boto3-servicediscovery (>=1.38.0,<1.39.0)", "mypy-boto3-ses (>=1.38.0,<1.39.0)", "mypy-boto3-sesv2 (>=1.38.0,<1.39.0)", "mypy-boto3-shield (>=1.38.0,<1.39.0)", "mypy-boto3-signer (>=1.38.0,<1.39.0)", "mypy-boto3-simspaceweaver (>=1.38.0,<1.39.0)", "mypy-boto3-sms (>=1.38.0,<1.39.0)", "mypy-boto3-sms-voice (>=1.38.0,<1.39.0)", "mypy-boto3-snow-device-management (>=1.38.0,<1.39.0)", "mypy-boto3-snowball (>=1.38.0,<1.39.0)", "mypy-boto3-sns (>=1.38.0,<1.39.0)", "mypy-boto3-socialmessaging (>=1.38.0,<1.39.0)", "mypy-boto3-sqs (>=1.38.0,<1.39.0)", "mypy-boto3-ssm (>=1.38.0,<1.39.0)", "mypy-boto3-ssm-contacts (>=1.38.0,<1.39.0)", "mypy-boto3-ssm-incidents (>=1.38.0,<1.39.0)", "mypy-boto3-ssm-quicksetup (>=1.38.0,<1.39.0)", "mypy-boto3-ssm-sap (>=1.38.0,<1.39.0)", "mypy-boto3-sso (>=1.38.0,<1.39.0)", "mypy-boto3-sso-admin (>=1.38.0,<1.39.0)", "mypy-boto3-sso-oidc (>=1.38.0,<1.39.0)", "mypy-boto3-stepfunctions (>=1.38.0,<1.39.0)", "mypy-boto3-storagegateway (>=1.38.0,<1.39.0)", "mypy-boto3-sts (>=1.38.0,<1.39.0)", "mypy-boto3-supplychain (>=1.38.0,<1.39.0)", "mypy-boto3-support (>=1.38.0,<1.39.0)", "mypy-boto3-support-app (>=1.38.0,<1.39.0)", "mypy-boto3-swf (>=1.38.0,<1.39.0)", "mypy-boto3-synthetics (>=1.38.0,<1.39.0)", "mypy-boto3-taxsettings (>=1.38.0,<1.39.0)", "mypy-boto3-textract (>=1.38.0,<1.39.0)", "mypy-boto3-timestream-influxdb (>=1.38.0,<1.39.0)", "mypy-boto3-timestream-query (>=1.38.0,<1.39.0)", "mypy-boto3-timestream-write (>=1.38.0,<1.39.0)", "mypy-boto3-tnb (>=1.38.0,<1.39.0)", "mypy-boto3-transcribe (>=1.38.0,<1.39.0)", "mypy-boto3-transfer (>=1.38.0,<1.39.0)", "mypy-boto3-translate (>=1.38.0,<1.39.0)", "mypy-boto3-trustedadvisor (>=1.38.0,<1.39.0)", "mypy-boto3-verifiedpermissions (>=1.38.0,<1.39.0)", "mypy-boto3-voice-id (>=1.38.0,<1.39.0)", "mypy-boto3-vpc-lattice (>=1.38.0,<1.39.0)", "mypy-boto3-waf (>=1.38.0,<1.39.0)", "mypy-boto3-waf-regional (>=1.38.0,<1.39.0)", "mypy-boto3-wafv2 (>=1.38.0,<1.39.0)", "mypy-boto3-wellarchitected (>=1.38.0,<1.39.0)", "mypy-boto3-wisdom (>=1.38.0,<1.39.0)", "mypy-boto3-workdocs (>=1.38.0,<1.39.0)", "mypy-boto3-workmail (>=1.38.0,<1.39.0)", "mypy-boto3-workmailmessageflow (>=1.38.0,<1.39.0)", "mypy-boto3-workspaces (>=1.38.0,<1.39.0)", "mypy-boto3-workspaces-thin-client (>=1.38.0,<1.39.0)", "mypy-boto3-workspaces-web (>=1.38.0,<1.39.0)", "mypy-boto3-xray (>=1.38.0,<1.39.0)"]
+amp = ["mypy-boto3-amp (>=1.38.0,<1.39.0)"]
+amplify = ["mypy-boto3-amplify (>=1.38.0,<1.39.0)"]
+amplifybackend = ["mypy-boto3-amplifybackend (>=1.38.0,<1.39.0)"]
+amplifyuibuilder = ["mypy-boto3-amplifyuibuilder (>=1.38.0,<1.39.0)"]
+apigateway = ["mypy-boto3-apigateway (>=1.38.0,<1.39.0)"]
+apigatewaymanagementapi = ["mypy-boto3-apigatewaymanagementapi (>=1.38.0,<1.39.0)"]
+apigatewayv2 = ["mypy-boto3-apigatewayv2 (>=1.38.0,<1.39.0)"]
+appconfig = ["mypy-boto3-appconfig (>=1.38.0,<1.39.0)"]
+appconfigdata = ["mypy-boto3-appconfigdata (>=1.38.0,<1.39.0)"]
+appfabric = ["mypy-boto3-appfabric (>=1.38.0,<1.39.0)"]
+appflow = ["mypy-boto3-appflow (>=1.38.0,<1.39.0)"]
+appintegrations = ["mypy-boto3-appintegrations (>=1.38.0,<1.39.0)"]
+application-autoscaling = ["mypy-boto3-application-autoscaling (>=1.38.0,<1.39.0)"]
+application-insights = ["mypy-boto3-application-insights (>=1.38.0,<1.39.0)"]
+application-signals = ["mypy-boto3-application-signals (>=1.38.0,<1.39.0)"]
+applicationcostprofiler = ["mypy-boto3-applicationcostprofiler (>=1.38.0,<1.39.0)"]
+appmesh = ["mypy-boto3-appmesh (>=1.38.0,<1.39.0)"]
+apprunner = ["mypy-boto3-apprunner (>=1.38.0,<1.39.0)"]
+appstream = ["mypy-boto3-appstream (>=1.38.0,<1.39.0)"]
+appsync = ["mypy-boto3-appsync (>=1.38.0,<1.39.0)"]
+apptest = ["mypy-boto3-apptest (>=1.38.0,<1.39.0)"]
+arc-zonal-shift = ["mypy-boto3-arc-zonal-shift (>=1.38.0,<1.39.0)"]
+artifact = ["mypy-boto3-artifact (>=1.38.0,<1.39.0)"]
+athena = ["mypy-boto3-athena (>=1.38.0,<1.39.0)"]
+auditmanager = ["mypy-boto3-auditmanager (>=1.38.0,<1.39.0)"]
+autoscaling = ["mypy-boto3-autoscaling (>=1.38.0,<1.39.0)"]
+autoscaling-plans = ["mypy-boto3-autoscaling-plans (>=1.38.0,<1.39.0)"]
+b2bi = ["mypy-boto3-b2bi (>=1.38.0,<1.39.0)"]
+backup = ["mypy-boto3-backup (>=1.38.0,<1.39.0)"]
+backup-gateway = ["mypy-boto3-backup-gateway (>=1.38.0,<1.39.0)"]
+backupsearch = ["mypy-boto3-backupsearch (>=1.38.0,<1.39.0)"]
+batch = ["mypy-boto3-batch (>=1.38.0,<1.39.0)"]
+bcm-data-exports = ["mypy-boto3-bcm-data-exports (>=1.38.0,<1.39.0)"]
+bcm-pricing-calculator = ["mypy-boto3-bcm-pricing-calculator (>=1.38.0,<1.39.0)"]
+bedrock = ["mypy-boto3-bedrock (>=1.38.0,<1.39.0)"]
+bedrock-agent = ["mypy-boto3-bedrock-agent (>=1.38.0,<1.39.0)"]
+bedrock-agent-runtime = ["mypy-boto3-bedrock-agent-runtime (>=1.38.0,<1.39.0)"]
+bedrock-data-automation = ["mypy-boto3-bedrock-data-automation (>=1.38.0,<1.39.0)"]
+bedrock-data-automation-runtime = ["mypy-boto3-bedrock-data-automation-runtime (>=1.38.0,<1.39.0)"]
+bedrock-runtime = ["mypy-boto3-bedrock-runtime (>=1.38.0,<1.39.0)"]
+billing = ["mypy-boto3-billing (>=1.38.0,<1.39.0)"]
+billingconductor = ["mypy-boto3-billingconductor (>=1.38.0,<1.39.0)"]
+boto3 = ["boto3 (==1.38.1)"]
+braket = ["mypy-boto3-braket (>=1.38.0,<1.39.0)"]
+budgets = ["mypy-boto3-budgets (>=1.38.0,<1.39.0)"]
+ce = ["mypy-boto3-ce (>=1.38.0,<1.39.0)"]
+chatbot = ["mypy-boto3-chatbot (>=1.38.0,<1.39.0)"]
+chime = ["mypy-boto3-chime (>=1.38.0,<1.39.0)"]
+chime-sdk-identity = ["mypy-boto3-chime-sdk-identity (>=1.38.0,<1.39.0)"]
+chime-sdk-media-pipelines = ["mypy-boto3-chime-sdk-media-pipelines (>=1.38.0,<1.39.0)"]
+chime-sdk-meetings = ["mypy-boto3-chime-sdk-meetings (>=1.38.0,<1.39.0)"]
+chime-sdk-messaging = ["mypy-boto3-chime-sdk-messaging (>=1.38.0,<1.39.0)"]
+chime-sdk-voice = ["mypy-boto3-chime-sdk-voice (>=1.38.0,<1.39.0)"]
+cleanrooms = ["mypy-boto3-cleanrooms (>=1.38.0,<1.39.0)"]
+cleanroomsml = ["mypy-boto3-cleanroomsml (>=1.38.0,<1.39.0)"]
+cloud9 = ["mypy-boto3-cloud9 (>=1.38.0,<1.39.0)"]
+cloudcontrol = ["mypy-boto3-cloudcontrol (>=1.38.0,<1.39.0)"]
+clouddirectory = ["mypy-boto3-clouddirectory (>=1.38.0,<1.39.0)"]
+cloudformation = ["mypy-boto3-cloudformation (>=1.38.0,<1.39.0)"]
+cloudfront = ["mypy-boto3-cloudfront (>=1.38.0,<1.39.0)"]
+cloudfront-keyvaluestore = ["mypy-boto3-cloudfront-keyvaluestore (>=1.38.0,<1.39.0)"]
+cloudhsm = ["mypy-boto3-cloudhsm (>=1.38.0,<1.39.0)"]
+cloudhsmv2 = ["mypy-boto3-cloudhsmv2 (>=1.38.0,<1.39.0)"]
+cloudsearch = ["mypy-boto3-cloudsearch (>=1.38.0,<1.39.0)"]
+cloudsearchdomain = ["mypy-boto3-cloudsearchdomain (>=1.38.0,<1.39.0)"]
+cloudtrail = ["mypy-boto3-cloudtrail (>=1.38.0,<1.39.0)"]
+cloudtrail-data = ["mypy-boto3-cloudtrail-data (>=1.38.0,<1.39.0)"]
+cloudwatch = ["mypy-boto3-cloudwatch (>=1.38.0,<1.39.0)"]
+codeartifact = ["mypy-boto3-codeartifact (>=1.38.0,<1.39.0)"]
+codebuild = ["mypy-boto3-codebuild (>=1.38.0,<1.39.0)"]
+codecatalyst = ["mypy-boto3-codecatalyst (>=1.38.0,<1.39.0)"]
+codecommit = ["mypy-boto3-codecommit (>=1.38.0,<1.39.0)"]
+codeconnections = ["mypy-boto3-codeconnections (>=1.38.0,<1.39.0)"]
+codedeploy = ["mypy-boto3-codedeploy (>=1.38.0,<1.39.0)"]
+codeguru-reviewer = ["mypy-boto3-codeguru-reviewer (>=1.38.0,<1.39.0)"]
+codeguru-security = ["mypy-boto3-codeguru-security (>=1.38.0,<1.39.0)"]
+codeguruprofiler = ["mypy-boto3-codeguruprofiler (>=1.38.0,<1.39.0)"]
+codepipeline = ["mypy-boto3-codepipeline (>=1.38.0,<1.39.0)"]
+codestar-connections = ["mypy-boto3-codestar-connections (>=1.38.0,<1.39.0)"]
+codestar-notifications = ["mypy-boto3-codestar-notifications (>=1.38.0,<1.39.0)"]
+cognito-identity = ["mypy-boto3-cognito-identity (>=1.38.0,<1.39.0)"]
+cognito-idp = ["mypy-boto3-cognito-idp (>=1.38.0,<1.39.0)"]
+cognito-sync = ["mypy-boto3-cognito-sync (>=1.38.0,<1.39.0)"]
+comprehend = ["mypy-boto3-comprehend (>=1.38.0,<1.39.0)"]
+comprehendmedical = ["mypy-boto3-comprehendmedical (>=1.38.0,<1.39.0)"]
+compute-optimizer = ["mypy-boto3-compute-optimizer (>=1.38.0,<1.39.0)"]
+config = ["mypy-boto3-config (>=1.38.0,<1.39.0)"]
+connect = ["mypy-boto3-connect (>=1.38.0,<1.39.0)"]
+connect-contact-lens = ["mypy-boto3-connect-contact-lens (>=1.38.0,<1.39.0)"]
+connectcampaigns = ["mypy-boto3-connectcampaigns (>=1.38.0,<1.39.0)"]
+connectcampaignsv2 = ["mypy-boto3-connectcampaignsv2 (>=1.38.0,<1.39.0)"]
+connectcases = ["mypy-boto3-connectcases (>=1.38.0,<1.39.0)"]
+connectparticipant = ["mypy-boto3-connectparticipant (>=1.38.0,<1.39.0)"]
+controlcatalog = ["mypy-boto3-controlcatalog (>=1.38.0,<1.39.0)"]
+controltower = ["mypy-boto3-controltower (>=1.38.0,<1.39.0)"]
+cost-optimization-hub = ["mypy-boto3-cost-optimization-hub (>=1.38.0,<1.39.0)"]
+cur = ["mypy-boto3-cur (>=1.38.0,<1.39.0)"]
+customer-profiles = ["mypy-boto3-customer-profiles (>=1.38.0,<1.39.0)"]
+databrew = ["mypy-boto3-databrew (>=1.38.0,<1.39.0)"]
+dataexchange = ["mypy-boto3-dataexchange (>=1.38.0,<1.39.0)"]
+datapipeline = ["mypy-boto3-datapipeline (>=1.38.0,<1.39.0)"]
+datasync = ["mypy-boto3-datasync (>=1.38.0,<1.39.0)"]
+datazone = ["mypy-boto3-datazone (>=1.38.0,<1.39.0)"]
+dax = ["mypy-boto3-dax (>=1.38.0,<1.39.0)"]
+deadline = ["mypy-boto3-deadline (>=1.38.0,<1.39.0)"]
+detective = ["mypy-boto3-detective (>=1.38.0,<1.39.0)"]
+devicefarm = ["mypy-boto3-devicefarm (>=1.38.0,<1.39.0)"]
+devops-guru = ["mypy-boto3-devops-guru (>=1.38.0,<1.39.0)"]
+directconnect = ["mypy-boto3-directconnect (>=1.38.0,<1.39.0)"]
+discovery = ["mypy-boto3-discovery (>=1.38.0,<1.39.0)"]
+dlm = ["mypy-boto3-dlm (>=1.38.0,<1.39.0)"]
+dms = ["mypy-boto3-dms (>=1.38.0,<1.39.0)"]
+docdb = ["mypy-boto3-docdb (>=1.38.0,<1.39.0)"]
+docdb-elastic = ["mypy-boto3-docdb-elastic (>=1.38.0,<1.39.0)"]
+drs = ["mypy-boto3-drs (>=1.38.0,<1.39.0)"]
+ds = ["mypy-boto3-ds (>=1.38.0,<1.39.0)"]
+ds-data = ["mypy-boto3-ds-data (>=1.38.0,<1.39.0)"]
+dsql = ["mypy-boto3-dsql (>=1.38.0,<1.39.0)"]
+dynamodb = ["mypy-boto3-dynamodb (>=1.38.0,<1.39.0)"]
+dynamodbstreams = ["mypy-boto3-dynamodbstreams (>=1.38.0,<1.39.0)"]
+ebs = ["mypy-boto3-ebs (>=1.38.0,<1.39.0)"]
+ec2 = ["mypy-boto3-ec2 (>=1.38.0,<1.39.0)"]
+ec2-instance-connect = ["mypy-boto3-ec2-instance-connect (>=1.38.0,<1.39.0)"]
+ecr = ["mypy-boto3-ecr (>=1.38.0,<1.39.0)"]
+ecr-public = ["mypy-boto3-ecr-public (>=1.38.0,<1.39.0)"]
+ecs = ["mypy-boto3-ecs (>=1.38.0,<1.39.0)"]
+efs = ["mypy-boto3-efs (>=1.38.0,<1.39.0)"]
+eks = ["mypy-boto3-eks (>=1.38.0,<1.39.0)"]
+eks-auth = ["mypy-boto3-eks-auth (>=1.38.0,<1.39.0)"]
+elasticache = ["mypy-boto3-elasticache (>=1.38.0,<1.39.0)"]
+elasticbeanstalk = ["mypy-boto3-elasticbeanstalk (>=1.38.0,<1.39.0)"]
+elastictranscoder = ["mypy-boto3-elastictranscoder (>=1.38.0,<1.39.0)"]
+elb = ["mypy-boto3-elb (>=1.38.0,<1.39.0)"]
+elbv2 = ["mypy-boto3-elbv2 (>=1.38.0,<1.39.0)"]
+emr = ["mypy-boto3-emr (>=1.38.0,<1.39.0)"]
+emr-containers = ["mypy-boto3-emr-containers (>=1.38.0,<1.39.0)"]
+emr-serverless = ["mypy-boto3-emr-serverless (>=1.38.0,<1.39.0)"]
+entityresolution = ["mypy-boto3-entityresolution (>=1.38.0,<1.39.0)"]
+es = ["mypy-boto3-es (>=1.38.0,<1.39.0)"]
+essential = ["mypy-boto3-cloudformation (>=1.38.0,<1.39.0)", "mypy-boto3-dynamodb (>=1.38.0,<1.39.0)", "mypy-boto3-ec2 (>=1.38.0,<1.39.0)", "mypy-boto3-lambda (>=1.38.0,<1.39.0)", "mypy-boto3-rds (>=1.38.0,<1.39.0)", "mypy-boto3-s3 (>=1.38.0,<1.39.0)", "mypy-boto3-sqs (>=1.38.0,<1.39.0)"]
+events = ["mypy-boto3-events (>=1.38.0,<1.39.0)"]
+evidently = ["mypy-boto3-evidently (>=1.38.0,<1.39.0)"]
+finspace = ["mypy-boto3-finspace (>=1.38.0,<1.39.0)"]
+finspace-data = ["mypy-boto3-finspace-data (>=1.38.0,<1.39.0)"]
+firehose = ["mypy-boto3-firehose (>=1.38.0,<1.39.0)"]
+fis = ["mypy-boto3-fis (>=1.38.0,<1.39.0)"]
+fms = ["mypy-boto3-fms (>=1.38.0,<1.39.0)"]
+forecast = ["mypy-boto3-forecast (>=1.38.0,<1.39.0)"]
+forecastquery = ["mypy-boto3-forecastquery (>=1.38.0,<1.39.0)"]
+frauddetector = ["mypy-boto3-frauddetector (>=1.38.0,<1.39.0)"]
+freetier = ["mypy-boto3-freetier (>=1.38.0,<1.39.0)"]
+fsx = ["mypy-boto3-fsx (>=1.38.0,<1.39.0)"]
+full = ["boto3-stubs-full (>=1.38.0,<1.39.0)"]
+gamelift = ["mypy-boto3-gamelift (>=1.38.0,<1.39.0)"]
+gameliftstreams = ["mypy-boto3-gameliftstreams (>=1.38.0,<1.39.0)"]
+geo-maps = ["mypy-boto3-geo-maps (>=1.38.0,<1.39.0)"]
+geo-places = ["mypy-boto3-geo-places (>=1.38.0,<1.39.0)"]
+geo-routes = ["mypy-boto3-geo-routes (>=1.38.0,<1.39.0)"]
+glacier = ["mypy-boto3-glacier (>=1.38.0,<1.39.0)"]
+globalaccelerator = ["mypy-boto3-globalaccelerator (>=1.38.0,<1.39.0)"]
+glue = ["mypy-boto3-glue (>=1.38.0,<1.39.0)"]
+grafana = ["mypy-boto3-grafana (>=1.38.0,<1.39.0)"]
+greengrass = ["mypy-boto3-greengrass (>=1.38.0,<1.39.0)"]
+greengrassv2 = ["mypy-boto3-greengrassv2 (>=1.38.0,<1.39.0)"]
+groundstation = ["mypy-boto3-groundstation (>=1.38.0,<1.39.0)"]
+guardduty = ["mypy-boto3-guardduty (>=1.38.0,<1.39.0)"]
+health = ["mypy-boto3-health (>=1.38.0,<1.39.0)"]
+healthlake = ["mypy-boto3-healthlake (>=1.38.0,<1.39.0)"]
+iam = ["mypy-boto3-iam (>=1.38.0,<1.39.0)"]
+identitystore = ["mypy-boto3-identitystore (>=1.38.0,<1.39.0)"]
+imagebuilder = ["mypy-boto3-imagebuilder (>=1.38.0,<1.39.0)"]
+importexport = ["mypy-boto3-importexport (>=1.38.0,<1.39.0)"]
+inspector = ["mypy-boto3-inspector (>=1.38.0,<1.39.0)"]
+inspector-scan = ["mypy-boto3-inspector-scan (>=1.38.0,<1.39.0)"]
+inspector2 = ["mypy-boto3-inspector2 (>=1.38.0,<1.39.0)"]
+internetmonitor = ["mypy-boto3-internetmonitor (>=1.38.0,<1.39.0)"]
+invoicing = ["mypy-boto3-invoicing (>=1.38.0,<1.39.0)"]
+iot = ["mypy-boto3-iot (>=1.38.0,<1.39.0)"]
+iot-data = ["mypy-boto3-iot-data (>=1.38.0,<1.39.0)"]
+iot-jobs-data = ["mypy-boto3-iot-jobs-data (>=1.38.0,<1.39.0)"]
+iot-managed-integrations = ["mypy-boto3-iot-managed-integrations (>=1.38.0,<1.39.0)"]
+iotanalytics = ["mypy-boto3-iotanalytics (>=1.38.0,<1.39.0)"]
+iotdeviceadvisor = ["mypy-boto3-iotdeviceadvisor (>=1.38.0,<1.39.0)"]
+iotevents = ["mypy-boto3-iotevents (>=1.38.0,<1.39.0)"]
+iotevents-data = ["mypy-boto3-iotevents-data (>=1.38.0,<1.39.0)"]
+iotfleethub = ["mypy-boto3-iotfleethub (>=1.38.0,<1.39.0)"]
+iotfleetwise = ["mypy-boto3-iotfleetwise (>=1.38.0,<1.39.0)"]
+iotsecuretunneling = ["mypy-boto3-iotsecuretunneling (>=1.38.0,<1.39.0)"]
+iotsitewise = ["mypy-boto3-iotsitewise (>=1.38.0,<1.39.0)"]
+iotthingsgraph = ["mypy-boto3-iotthingsgraph (>=1.38.0,<1.39.0)"]
+iottwinmaker = ["mypy-boto3-iottwinmaker (>=1.38.0,<1.39.0)"]
+iotwireless = ["mypy-boto3-iotwireless (>=1.38.0,<1.39.0)"]
+ivs = ["mypy-boto3-ivs (>=1.38.0,<1.39.0)"]
+ivs-realtime = ["mypy-boto3-ivs-realtime (>=1.38.0,<1.39.0)"]
+ivschat = ["mypy-boto3-ivschat (>=1.38.0,<1.39.0)"]
+kafka = ["mypy-boto3-kafka (>=1.38.0,<1.39.0)"]
+kafkaconnect = ["mypy-boto3-kafkaconnect (>=1.38.0,<1.39.0)"]
+kendra = ["mypy-boto3-kendra (>=1.38.0,<1.39.0)"]
+kendra-ranking = ["mypy-boto3-kendra-ranking (>=1.38.0,<1.39.0)"]
+keyspaces = ["mypy-boto3-keyspaces (>=1.38.0,<1.39.0)"]
+kinesis = ["mypy-boto3-kinesis (>=1.38.0,<1.39.0)"]
+kinesis-video-archived-media = ["mypy-boto3-kinesis-video-archived-media (>=1.38.0,<1.39.0)"]
+kinesis-video-media = ["mypy-boto3-kinesis-video-media (>=1.38.0,<1.39.0)"]
+kinesis-video-signaling = ["mypy-boto3-kinesis-video-signaling (>=1.38.0,<1.39.0)"]
+kinesis-video-webrtc-storage = ["mypy-boto3-kinesis-video-webrtc-storage (>=1.38.0,<1.39.0)"]
+kinesisanalytics = ["mypy-boto3-kinesisanalytics (>=1.38.0,<1.39.0)"]
+kinesisanalyticsv2 = ["mypy-boto3-kinesisanalyticsv2 (>=1.38.0,<1.39.0)"]
+kinesisvideo = ["mypy-boto3-kinesisvideo (>=1.38.0,<1.39.0)"]
+kms = ["mypy-boto3-kms (>=1.38.0,<1.39.0)"]
+lakeformation = ["mypy-boto3-lakeformation (>=1.38.0,<1.39.0)"]
+lambda = ["mypy-boto3-lambda (>=1.38.0,<1.39.0)"]
+launch-wizard = ["mypy-boto3-launch-wizard (>=1.38.0,<1.39.0)"]
+lex-models = ["mypy-boto3-lex-models (>=1.38.0,<1.39.0)"]
+lex-runtime = ["mypy-boto3-lex-runtime (>=1.38.0,<1.39.0)"]
+lexv2-models = ["mypy-boto3-lexv2-models (>=1.38.0,<1.39.0)"]
+lexv2-runtime = ["mypy-boto3-lexv2-runtime (>=1.38.0,<1.39.0)"]
+license-manager = ["mypy-boto3-license-manager (>=1.38.0,<1.39.0)"]
+license-manager-linux-subscriptions = ["mypy-boto3-license-manager-linux-subscriptions (>=1.38.0,<1.39.0)"]
+license-manager-user-subscriptions = ["mypy-boto3-license-manager-user-subscriptions (>=1.38.0,<1.39.0)"]
+lightsail = ["mypy-boto3-lightsail (>=1.38.0,<1.39.0)"]
+location = ["mypy-boto3-location (>=1.38.0,<1.39.0)"]
+logs = ["mypy-boto3-logs (>=1.38.0,<1.39.0)"]
+lookoutequipment = ["mypy-boto3-lookoutequipment (>=1.38.0,<1.39.0)"]
+lookoutmetrics = ["mypy-boto3-lookoutmetrics (>=1.38.0,<1.39.0)"]
+lookoutvision = ["mypy-boto3-lookoutvision (>=1.38.0,<1.39.0)"]
+m2 = ["mypy-boto3-m2 (>=1.38.0,<1.39.0)"]
+machinelearning = ["mypy-boto3-machinelearning (>=1.38.0,<1.39.0)"]
+macie2 = ["mypy-boto3-macie2 (>=1.38.0,<1.39.0)"]
+mailmanager = ["mypy-boto3-mailmanager (>=1.38.0,<1.39.0)"]
+managedblockchain = ["mypy-boto3-managedblockchain (>=1.38.0,<1.39.0)"]
+managedblockchain-query = ["mypy-boto3-managedblockchain-query (>=1.38.0,<1.39.0)"]
+marketplace-agreement = ["mypy-boto3-marketplace-agreement (>=1.38.0,<1.39.0)"]
+marketplace-catalog = ["mypy-boto3-marketplace-catalog (>=1.38.0,<1.39.0)"]
+marketplace-deployment = ["mypy-boto3-marketplace-deployment (>=1.38.0,<1.39.0)"]
+marketplace-entitlement = ["mypy-boto3-marketplace-entitlement (>=1.38.0,<1.39.0)"]
+marketplace-reporting = ["mypy-boto3-marketplace-reporting (>=1.38.0,<1.39.0)"]
+marketplacecommerceanalytics = ["mypy-boto3-marketplacecommerceanalytics (>=1.38.0,<1.39.0)"]
+mediaconnect = ["mypy-boto3-mediaconnect (>=1.38.0,<1.39.0)"]
+mediaconvert = ["mypy-boto3-mediaconvert (>=1.38.0,<1.39.0)"]
+medialive = ["mypy-boto3-medialive (>=1.38.0,<1.39.0)"]
+mediapackage = ["mypy-boto3-mediapackage (>=1.38.0,<1.39.0)"]
+mediapackage-vod = ["mypy-boto3-mediapackage-vod (>=1.38.0,<1.39.0)"]
+mediapackagev2 = ["mypy-boto3-mediapackagev2 (>=1.38.0,<1.39.0)"]
+mediastore = ["mypy-boto3-mediastore (>=1.38.0,<1.39.0)"]
+mediastore-data = ["mypy-boto3-mediastore-data (>=1.38.0,<1.39.0)"]
+mediatailor = ["mypy-boto3-mediatailor (>=1.38.0,<1.39.0)"]
+medical-imaging = ["mypy-boto3-medical-imaging (>=1.38.0,<1.39.0)"]
+memorydb = ["mypy-boto3-memorydb (>=1.38.0,<1.39.0)"]
+meteringmarketplace = ["mypy-boto3-meteringmarketplace (>=1.38.0,<1.39.0)"]
+mgh = ["mypy-boto3-mgh (>=1.38.0,<1.39.0)"]
+mgn = ["mypy-boto3-mgn (>=1.38.0,<1.39.0)"]
+migration-hub-refactor-spaces = ["mypy-boto3-migration-hub-refactor-spaces (>=1.38.0,<1.39.0)"]
+migrationhub-config = ["mypy-boto3-migrationhub-config (>=1.38.0,<1.39.0)"]
+migrationhuborchestrator = ["mypy-boto3-migrationhuborchestrator (>=1.38.0,<1.39.0)"]
+migrationhubstrategy = ["mypy-boto3-migrationhubstrategy (>=1.38.0,<1.39.0)"]
+mq = ["mypy-boto3-mq (>=1.38.0,<1.39.0)"]
+mturk = ["mypy-boto3-mturk (>=1.38.0,<1.39.0)"]
+mwaa = ["mypy-boto3-mwaa (>=1.38.0,<1.39.0)"]
+neptune = ["mypy-boto3-neptune (>=1.38.0,<1.39.0)"]
+neptune-graph = ["mypy-boto3-neptune-graph (>=1.38.0,<1.39.0)"]
+neptunedata = ["mypy-boto3-neptunedata (>=1.38.0,<1.39.0)"]
+network-firewall = ["mypy-boto3-network-firewall (>=1.38.0,<1.39.0)"]
+networkflowmonitor = ["mypy-boto3-networkflowmonitor (>=1.38.0,<1.39.0)"]
+networkmanager = ["mypy-boto3-networkmanager (>=1.38.0,<1.39.0)"]
+networkmonitor = ["mypy-boto3-networkmonitor (>=1.38.0,<1.39.0)"]
+notifications = ["mypy-boto3-notifications (>=1.38.0,<1.39.0)"]
+notificationscontacts = ["mypy-boto3-notificationscontacts (>=1.38.0,<1.39.0)"]
+oam = ["mypy-boto3-oam (>=1.38.0,<1.39.0)"]
+observabilityadmin = ["mypy-boto3-observabilityadmin (>=1.38.0,<1.39.0)"]
+omics = ["mypy-boto3-omics (>=1.38.0,<1.39.0)"]
+opensearch = ["mypy-boto3-opensearch (>=1.38.0,<1.39.0)"]
+opensearchserverless = ["mypy-boto3-opensearchserverless (>=1.38.0,<1.39.0)"]
+opsworks = ["mypy-boto3-opsworks (>=1.38.0,<1.39.0)"]
+opsworkscm = ["mypy-boto3-opsworkscm (>=1.38.0,<1.39.0)"]
+organizations = ["mypy-boto3-organizations (>=1.38.0,<1.39.0)"]
+osis = ["mypy-boto3-osis (>=1.38.0,<1.39.0)"]
+outposts = ["mypy-boto3-outposts (>=1.38.0,<1.39.0)"]
+panorama = ["mypy-boto3-panorama (>=1.38.0,<1.39.0)"]
+partnercentral-selling = ["mypy-boto3-partnercentral-selling (>=1.38.0,<1.39.0)"]
+payment-cryptography = ["mypy-boto3-payment-cryptography (>=1.38.0,<1.39.0)"]
+payment-cryptography-data = ["mypy-boto3-payment-cryptography-data (>=1.38.0,<1.39.0)"]
+pca-connector-ad = ["mypy-boto3-pca-connector-ad (>=1.38.0,<1.39.0)"]
+pca-connector-scep = ["mypy-boto3-pca-connector-scep (>=1.38.0,<1.39.0)"]
+pcs = ["mypy-boto3-pcs (>=1.38.0,<1.39.0)"]
+personalize = ["mypy-boto3-personalize (>=1.38.0,<1.39.0)"]
+personalize-events = ["mypy-boto3-personalize-events (>=1.38.0,<1.39.0)"]
+personalize-runtime = ["mypy-boto3-personalize-runtime (>=1.38.0,<1.39.0)"]
+pi = ["mypy-boto3-pi (>=1.38.0,<1.39.0)"]
+pinpoint = ["mypy-boto3-pinpoint (>=1.38.0,<1.39.0)"]
+pinpoint-email = ["mypy-boto3-pinpoint-email (>=1.38.0,<1.39.0)"]
+pinpoint-sms-voice = ["mypy-boto3-pinpoint-sms-voice (>=1.38.0,<1.39.0)"]
+pinpoint-sms-voice-v2 = ["mypy-boto3-pinpoint-sms-voice-v2 (>=1.38.0,<1.39.0)"]
+pipes = ["mypy-boto3-pipes (>=1.38.0,<1.39.0)"]
+polly = ["mypy-boto3-polly (>=1.38.0,<1.39.0)"]
+pricing = ["mypy-boto3-pricing (>=1.38.0,<1.39.0)"]
+privatenetworks = ["mypy-boto3-privatenetworks (>=1.38.0,<1.39.0)"]
+proton = ["mypy-boto3-proton (>=1.38.0,<1.39.0)"]
+qapps = ["mypy-boto3-qapps (>=1.38.0,<1.39.0)"]
+qbusiness = ["mypy-boto3-qbusiness (>=1.38.0,<1.39.0)"]
+qconnect = ["mypy-boto3-qconnect (>=1.38.0,<1.39.0)"]
+qldb = ["mypy-boto3-qldb (>=1.38.0,<1.39.0)"]
+qldb-session = ["mypy-boto3-qldb-session (>=1.38.0,<1.39.0)"]
+quicksight = ["mypy-boto3-quicksight (>=1.38.0,<1.39.0)"]
+ram = ["mypy-boto3-ram (>=1.38.0,<1.39.0)"]
+rbin = ["mypy-boto3-rbin (>=1.38.0,<1.39.0)"]
+rds = ["mypy-boto3-rds (>=1.38.0,<1.39.0)"]
+rds-data = ["mypy-boto3-rds-data (>=1.38.0,<1.39.0)"]
+redshift = ["mypy-boto3-redshift (>=1.38.0,<1.39.0)"]
+redshift-data = ["mypy-boto3-redshift-data (>=1.38.0,<1.39.0)"]
+redshift-serverless = ["mypy-boto3-redshift-serverless (>=1.38.0,<1.39.0)"]
+rekognition = ["mypy-boto3-rekognition (>=1.38.0,<1.39.0)"]
+repostspace = ["mypy-boto3-repostspace (>=1.38.0,<1.39.0)"]
+resiliencehub = ["mypy-boto3-resiliencehub (>=1.38.0,<1.39.0)"]
+resource-explorer-2 = ["mypy-boto3-resource-explorer-2 (>=1.38.0,<1.39.0)"]
+resource-groups = ["mypy-boto3-resource-groups (>=1.38.0,<1.39.0)"]
+resourcegroupstaggingapi = ["mypy-boto3-resourcegroupstaggingapi (>=1.38.0,<1.39.0)"]
+robomaker = ["mypy-boto3-robomaker (>=1.38.0,<1.39.0)"]
+rolesanywhere = ["mypy-boto3-rolesanywhere (>=1.38.0,<1.39.0)"]
+route53 = ["mypy-boto3-route53 (>=1.38.0,<1.39.0)"]
+route53-recovery-cluster = ["mypy-boto3-route53-recovery-cluster (>=1.38.0,<1.39.0)"]
+route53-recovery-control-config = ["mypy-boto3-route53-recovery-control-config (>=1.38.0,<1.39.0)"]
+route53-recovery-readiness = ["mypy-boto3-route53-recovery-readiness (>=1.38.0,<1.39.0)"]
+route53domains = ["mypy-boto3-route53domains (>=1.38.0,<1.39.0)"]
+route53profiles = ["mypy-boto3-route53profiles (>=1.38.0,<1.39.0)"]
+route53resolver = ["mypy-boto3-route53resolver (>=1.38.0,<1.39.0)"]
+rum = ["mypy-boto3-rum (>=1.38.0,<1.39.0)"]
+s3 = ["mypy-boto3-s3 (>=1.38.0,<1.39.0)"]
+s3control = ["mypy-boto3-s3control (>=1.38.0,<1.39.0)"]
+s3outposts = ["mypy-boto3-s3outposts (>=1.38.0,<1.39.0)"]
+s3tables = ["mypy-boto3-s3tables (>=1.38.0,<1.39.0)"]
+sagemaker = ["mypy-boto3-sagemaker (>=1.38.0,<1.39.0)"]
+sagemaker-a2i-runtime = ["mypy-boto3-sagemaker-a2i-runtime (>=1.38.0,<1.39.0)"]
+sagemaker-edge = ["mypy-boto3-sagemaker-edge (>=1.38.0,<1.39.0)"]
+sagemaker-featurestore-runtime = ["mypy-boto3-sagemaker-featurestore-runtime (>=1.38.0,<1.39.0)"]
+sagemaker-geospatial = ["mypy-boto3-sagemaker-geospatial (>=1.38.0,<1.39.0)"]
+sagemaker-metrics = ["mypy-boto3-sagemaker-metrics (>=1.38.0,<1.39.0)"]
+sagemaker-runtime = ["mypy-boto3-sagemaker-runtime (>=1.38.0,<1.39.0)"]
+savingsplans = ["mypy-boto3-savingsplans (>=1.38.0,<1.39.0)"]
+scheduler = ["mypy-boto3-scheduler (>=1.38.0,<1.39.0)"]
+schemas = ["mypy-boto3-schemas (>=1.38.0,<1.39.0)"]
+sdb = ["mypy-boto3-sdb (>=1.38.0,<1.39.0)"]
+secretsmanager = ["mypy-boto3-secretsmanager (>=1.38.0,<1.39.0)"]
+security-ir = ["mypy-boto3-security-ir (>=1.38.0,<1.39.0)"]
+securityhub = ["mypy-boto3-securityhub (>=1.38.0,<1.39.0)"]
+securitylake = ["mypy-boto3-securitylake (>=1.38.0,<1.39.0)"]
+serverlessrepo = ["mypy-boto3-serverlessrepo (>=1.38.0,<1.39.0)"]
+service-quotas = ["mypy-boto3-service-quotas (>=1.38.0,<1.39.0)"]
+servicecatalog = ["mypy-boto3-servicecatalog (>=1.38.0,<1.39.0)"]
+servicecatalog-appregistry = ["mypy-boto3-servicecatalog-appregistry (>=1.38.0,<1.39.0)"]
+servicediscovery = ["mypy-boto3-servicediscovery (>=1.38.0,<1.39.0)"]
+ses = ["mypy-boto3-ses (>=1.38.0,<1.39.0)"]
+sesv2 = ["mypy-boto3-sesv2 (>=1.38.0,<1.39.0)"]
+shield = ["mypy-boto3-shield (>=1.38.0,<1.39.0)"]
+signer = ["mypy-boto3-signer (>=1.38.0,<1.39.0)"]
+simspaceweaver = ["mypy-boto3-simspaceweaver (>=1.38.0,<1.39.0)"]
+sms = ["mypy-boto3-sms (>=1.38.0,<1.39.0)"]
+sms-voice = ["mypy-boto3-sms-voice (>=1.38.0,<1.39.0)"]
+snow-device-management = ["mypy-boto3-snow-device-management (>=1.38.0,<1.39.0)"]
+snowball = ["mypy-boto3-snowball (>=1.38.0,<1.39.0)"]
+sns = ["mypy-boto3-sns (>=1.38.0,<1.39.0)"]
+socialmessaging = ["mypy-boto3-socialmessaging (>=1.38.0,<1.39.0)"]
+sqs = ["mypy-boto3-sqs (>=1.38.0,<1.39.0)"]
+ssm = ["mypy-boto3-ssm (>=1.38.0,<1.39.0)"]
+ssm-contacts = ["mypy-boto3-ssm-contacts (>=1.38.0,<1.39.0)"]
+ssm-incidents = ["mypy-boto3-ssm-incidents (>=1.38.0,<1.39.0)"]
+ssm-quicksetup = ["mypy-boto3-ssm-quicksetup (>=1.38.0,<1.39.0)"]
+ssm-sap = ["mypy-boto3-ssm-sap (>=1.38.0,<1.39.0)"]
+sso = ["mypy-boto3-sso (>=1.38.0,<1.39.0)"]
+sso-admin = ["mypy-boto3-sso-admin (>=1.38.0,<1.39.0)"]
+sso-oidc = ["mypy-boto3-sso-oidc (>=1.38.0,<1.39.0)"]
+stepfunctions = ["mypy-boto3-stepfunctions (>=1.38.0,<1.39.0)"]
+storagegateway = ["mypy-boto3-storagegateway (>=1.38.0,<1.39.0)"]
+sts = ["mypy-boto3-sts (>=1.38.0,<1.39.0)"]
+supplychain = ["mypy-boto3-supplychain (>=1.38.0,<1.39.0)"]
+support = ["mypy-boto3-support (>=1.38.0,<1.39.0)"]
+support-app = ["mypy-boto3-support-app (>=1.38.0,<1.39.0)"]
+swf = ["mypy-boto3-swf (>=1.38.0,<1.39.0)"]
+synthetics = ["mypy-boto3-synthetics (>=1.38.0,<1.39.0)"]
+taxsettings = ["mypy-boto3-taxsettings (>=1.38.0,<1.39.0)"]
+textract = ["mypy-boto3-textract (>=1.38.0,<1.39.0)"]
+timestream-influxdb = ["mypy-boto3-timestream-influxdb (>=1.38.0,<1.39.0)"]
+timestream-query = ["mypy-boto3-timestream-query (>=1.38.0,<1.39.0)"]
+timestream-write = ["mypy-boto3-timestream-write (>=1.38.0,<1.39.0)"]
+tnb = ["mypy-boto3-tnb (>=1.38.0,<1.39.0)"]
+transcribe = ["mypy-boto3-transcribe (>=1.38.0,<1.39.0)"]
+transfer = ["mypy-boto3-transfer (>=1.38.0,<1.39.0)"]
+translate = ["mypy-boto3-translate (>=1.38.0,<1.39.0)"]
+trustedadvisor = ["mypy-boto3-trustedadvisor (>=1.38.0,<1.39.0)"]
+verifiedpermissions = ["mypy-boto3-verifiedpermissions (>=1.38.0,<1.39.0)"]
+voice-id = ["mypy-boto3-voice-id (>=1.38.0,<1.39.0)"]
+vpc-lattice = ["mypy-boto3-vpc-lattice (>=1.38.0,<1.39.0)"]
+waf = ["mypy-boto3-waf (>=1.38.0,<1.39.0)"]
+waf-regional = ["mypy-boto3-waf-regional (>=1.38.0,<1.39.0)"]
+wafv2 = ["mypy-boto3-wafv2 (>=1.38.0,<1.39.0)"]
+wellarchitected = ["mypy-boto3-wellarchitected (>=1.38.0,<1.39.0)"]
+wisdom = ["mypy-boto3-wisdom (>=1.38.0,<1.39.0)"]
+workdocs = ["mypy-boto3-workdocs (>=1.38.0,<1.39.0)"]
+workmail = ["mypy-boto3-workmail (>=1.38.0,<1.39.0)"]
+workmailmessageflow = ["mypy-boto3-workmailmessageflow (>=1.38.0,<1.39.0)"]
+workspaces = ["mypy-boto3-workspaces (>=1.38.0,<1.39.0)"]
+workspaces-thin-client = ["mypy-boto3-workspaces-thin-client (>=1.38.0,<1.39.0)"]
+workspaces-web = ["mypy-boto3-workspaces-web (>=1.38.0,<1.39.0)"]
+xray = ["mypy-boto3-xray (>=1.38.0,<1.39.0)"]
[[package]]
name = "botocore"
@@ -990,40 +943,40 @@ ujson = ["ujson (>=5.7.0)"]
[[package]]
name = "cdk-nag"
-version = "2.35.48"
+version = "2.35.78"
description = "Check CDK v2 applications for best practices using a combination on available rule packs."
optional = false
python-versions = "~=3.9"
groups = ["dev"]
files = [
- {file = "cdk_nag-2.35.48-py3-none-any.whl", hash = "sha256:b90c94f2007f2ca9ce30ee85629a5fa2130f7e5cc89327a195049c255e7919f2"},
- {file = "cdk_nag-2.35.48.tar.gz", hash = "sha256:6d1804b4770c2aed60dcbbaae87aff795e558b304acfa1d3d2c9161b2cfc7580"},
+ {file = "cdk_nag-2.35.78-py3-none-any.whl", hash = "sha256:762a3616bba6a7fc22dab2612267ff4e531a611910d42a6784f16af325ef2ca5"},
+ {file = "cdk_nag-2.35.78.tar.gz", hash = "sha256:d1c34919d476d8e1a4cca35f3c699e66adc5c6b305f0995a22885c66f659a746"},
]
[package.dependencies]
aws-cdk-lib = ">=2.156.0,<3.0.0"
constructs = ">=10.0.5,<11.0.0"
-jsii = ">=1.109.0,<2.0.0"
+jsii = ">=1.111.0,<2.0.0"
publication = ">=0.0.3"
typeguard = ">=2.13.3,<4.3.0"
[[package]]
name = "cdklabs-generative-ai-cdk-constructs"
-version = "0.1.298"
+version = "0.1.305"
description = "AWS Generative AI CDK Constructs is a library for well-architected generative AI patterns."
optional = false
python-versions = "~=3.9"
groups = ["dev"]
files = [
- {file = "cdklabs.generative_ai_cdk_constructs-0.1.298-py3-none-any.whl", hash = "sha256:a4ae2cd3bc6c5ebf1bf314ef9bedde448170f05f4945097176f72b3b24f48040"},
- {file = "cdklabs_generative_ai_cdk_constructs-0.1.298.tar.gz", hash = "sha256:43d10f42ea5c79b5ce5b79412cf580929a8a08be78c28acbe527c4d45ec2176a"},
+ {file = "cdklabs_generative_ai_cdk_constructs-0.1.305-py3-none-any.whl", hash = "sha256:7edfa7415a33edcff8184e29318c3d154c800a9079cf561bd4451dfd15b36b45"},
+ {file = "cdklabs_generative_ai_cdk_constructs-0.1.305.tar.gz", hash = "sha256:6608f8ee576dc2c10d8d30f11fcc7bfa65c31b6f996bada3c978b1155099fa06"},
]
[package.dependencies]
-aws-cdk-lib = ">=2.181.1,<3.0.0"
-cdk-nag = ">=2.35.46,<3.0.0"
+aws-cdk-lib = ">=2.189.0,<3.0.0"
+cdk-nag = ">=2.35.77,<3.0.0"
constructs = ">=10.3.0,<11.0.0"
-jsii = ">=1.109.0,<2.0.0"
+jsii = ">=1.111.0,<2.0.0"
publication = ">=0.0.3"
typeguard = ">=2.13.3,<4.3.0"
@@ -1122,18 +1075,18 @@ pycparser = "*"
[[package]]
name = "cfn-lint"
-version = "1.32.1"
+version = "1.34.1"
description = "Checks CloudFormation templates for practices and behaviour that could potentially be improved"
optional = false
python-versions = ">=3.9"
groups = ["dev"]
files = [
- {file = "cfn_lint-1.32.1-py3-none-any.whl", hash = "sha256:a8ea63ac8daa69a66a54a796998362fd063d9ba1e9c1fc3c932213b0c027669c"},
- {file = "cfn_lint-1.32.1.tar.gz", hash = "sha256:10282c0ec7fc6391da4877d9381a6b954f3c54ddcc0d3c97ee86f4783b5ae680"},
+ {file = "cfn_lint-1.34.1-py3-none-any.whl", hash = "sha256:2c21a908fa5d9b0c1caac2081c10261eaae7fce88364e4edc607717892f36d68"},
+ {file = "cfn_lint-1.34.1.tar.gz", hash = "sha256:58f4352c370a710b72b389eda0acd6762e62f926e534d2eb1609d5326ded4bed"},
]
[package.dependencies]
-aws-sam-translator = ">=1.95.0"
+aws-sam-translator = ">=1.97.0"
jsonpatch = "*"
networkx = ">=2.4,<4"
pyyaml = ">5.4"
@@ -1313,75 +1266,75 @@ typeguard = ">=2.13.3,<2.14.0"
[[package]]
name = "coverage"
-version = "7.7.1"
+version = "7.8.0"
description = "Code coverage measurement for Python"
optional = false
python-versions = ">=3.9"
groups = ["dev"]
files = [
- {file = "coverage-7.7.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:553ba93f8e3c70e1b0031e4dfea36aba4e2b51fe5770db35e99af8dc5c5a9dfe"},
- {file = "coverage-7.7.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:44683f2556a56c9a6e673b583763096b8efbd2df022b02995609cf8e64fc8ae0"},
- {file = "coverage-7.7.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:02fad4f8faa4153db76f9246bc95c1d99f054f4e0a884175bff9155cf4f856cb"},
- {file = "coverage-7.7.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4c181ceba2e6808ede1e964f7bdc77bd8c7eb62f202c63a48cc541e5ffffccb6"},
- {file = "coverage-7.7.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80b5b207a8b08c6a934b214e364cab2fa82663d4af18981a6c0a9e95f8df7602"},
- {file = "coverage-7.7.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:25fe40967717bad0ce628a0223f08a10d54c9d739e88c9cbb0f77b5959367542"},
- {file = "coverage-7.7.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:881cae0f9cbd928c9c001487bb3dcbfd0b0af3ef53ae92180878591053be0cb3"},
- {file = "coverage-7.7.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:c90e9141e9221dd6fbc16a2727a5703c19443a8d9bf7d634c792fa0287cee1ab"},
- {file = "coverage-7.7.1-cp310-cp310-win32.whl", hash = "sha256:ae13ed5bf5542d7d4a0a42ff5160e07e84adc44eda65ddaa635c484ff8e55917"},
- {file = "coverage-7.7.1-cp310-cp310-win_amd64.whl", hash = "sha256:171e9977c6a5d2b2be9efc7df1126fd525ce7cad0eb9904fe692da007ba90d81"},
- {file = "coverage-7.7.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1165490be0069e34e4f99d08e9c5209c463de11b471709dfae31e2a98cbd49fd"},
- {file = "coverage-7.7.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:44af11c00fd3b19b8809487630f8a0039130d32363239dfd15238e6d37e41a48"},
- {file = "coverage-7.7.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fbba59022e7c20124d2f520842b75904c7b9f16c854233fa46575c69949fb5b9"},
- {file = "coverage-7.7.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:af94fb80e4f159f4d93fb411800448ad87b6039b0500849a403b73a0d36bb5ae"},
- {file = "coverage-7.7.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eae79f8e3501133aa0e220bbc29573910d096795882a70e6f6e6637b09522133"},
- {file = "coverage-7.7.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:e33426a5e1dc7743dd54dfd11d3a6c02c5d127abfaa2edd80a6e352b58347d1a"},
- {file = "coverage-7.7.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:b559adc22486937786731dac69e57296cb9aede7e2687dfc0d2696dbd3b1eb6b"},
- {file = "coverage-7.7.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b838a91e84e1773c3436f6cc6996e000ed3ca5721799e7789be18830fad009a2"},
- {file = "coverage-7.7.1-cp311-cp311-win32.whl", hash = "sha256:2c492401bdb3a85824669d6a03f57b3dfadef0941b8541f035f83bbfc39d4282"},
- {file = "coverage-7.7.1-cp311-cp311-win_amd64.whl", hash = "sha256:1e6f867379fd033a0eeabb1be0cffa2bd660582b8b0c9478895c509d875a9d9e"},
- {file = "coverage-7.7.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:eff187177d8016ff6addf789dcc421c3db0d014e4946c1cc3fbf697f7852459d"},
- {file = "coverage-7.7.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2444fbe1ba1889e0b29eb4d11931afa88f92dc507b7248f45be372775b3cef4f"},
- {file = "coverage-7.7.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:177d837339883c541f8524683e227adcaea581eca6bb33823a2a1fdae4c988e1"},
- {file = "coverage-7.7.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:15d54ecef1582b1d3ec6049b20d3c1a07d5e7f85335d8a3b617c9960b4f807e0"},
- {file = "coverage-7.7.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75c82b27c56478d5e1391f2e7b2e7f588d093157fa40d53fd9453a471b1191f2"},
- {file = "coverage-7.7.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:315ff74b585110ac3b7ab631e89e769d294f303c6d21302a816b3554ed4c81af"},
- {file = "coverage-7.7.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:4dd532dac197d68c478480edde74fd4476c6823355987fd31d01ad9aa1e5fb59"},
- {file = "coverage-7.7.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:385618003e3d608001676bb35dc67ae3ad44c75c0395d8de5780af7bb35be6b2"},
- {file = "coverage-7.7.1-cp312-cp312-win32.whl", hash = "sha256:63306486fcb5a827449464f6211d2991f01dfa2965976018c9bab9d5e45a35c8"},
- {file = "coverage-7.7.1-cp312-cp312-win_amd64.whl", hash = "sha256:37351dc8123c154fa05b7579fdb126b9f8b1cf42fd6f79ddf19121b7bdd4aa04"},
- {file = "coverage-7.7.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:eebd927b86761a7068a06d3699fd6c20129becf15bb44282db085921ea0f1585"},
- {file = "coverage-7.7.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2a79c4a09765d18311c35975ad2eb1ac613c0401afdd9cb1ca4110aeb5dd3c4c"},
- {file = "coverage-7.7.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b1c65a739447c5ddce5b96c0a388fd82e4bbdff7251396a70182b1d83631019"},
- {file = "coverage-7.7.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:392cc8fd2b1b010ca36840735e2a526fcbd76795a5d44006065e79868cc76ccf"},
- {file = "coverage-7.7.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9bb47cc9f07a59a451361a850cb06d20633e77a9118d05fd0f77b1864439461b"},
- {file = "coverage-7.7.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b4c144c129343416a49378e05c9451c34aae5ccf00221e4fa4f487db0816ee2f"},
- {file = "coverage-7.7.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:bc96441c9d9ca12a790b5ae17d2fa6654da4b3962ea15e0eabb1b1caed094777"},
- {file = "coverage-7.7.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:3d03287eb03186256999539d98818c425c33546ab4901028c8fa933b62c35c3a"},
- {file = "coverage-7.7.1-cp313-cp313-win32.whl", hash = "sha256:8fed429c26b99641dc1f3a79179860122b22745dd9af36f29b141e178925070a"},
- {file = "coverage-7.7.1-cp313-cp313-win_amd64.whl", hash = "sha256:092b134129a8bb940c08b2d9ceb4459af5fb3faea77888af63182e17d89e1cf1"},
- {file = "coverage-7.7.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:d3154b369141c3169b8133973ac00f63fcf8d6dbcc297d788d36afbb7811e511"},
- {file = "coverage-7.7.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:264ff2bcce27a7f455b64ac0dfe097680b65d9a1a293ef902675fa8158d20b24"},
- {file = "coverage-7.7.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ba8480ebe401c2f094d10a8c4209b800a9b77215b6c796d16b6ecdf665048950"},
- {file = "coverage-7.7.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:520af84febb6bb54453e7fbb730afa58c7178fd018c398a8fcd8e269a79bf96d"},
- {file = "coverage-7.7.1-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88d96127ae01ff571d465d4b0be25c123789cef88ba0879194d673fdea52f54e"},
- {file = "coverage-7.7.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:0ce92c5a9d7007d838456f4b77ea159cb628187a137e1895331e530973dcf862"},
- {file = "coverage-7.7.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:0dab4ef76d7b14f432057fdb7a0477e8bffca0ad39ace308be6e74864e632271"},
- {file = "coverage-7.7.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:7e688010581dbac9cab72800e9076e16f7cccd0d89af5785b70daa11174e94de"},
- {file = "coverage-7.7.1-cp313-cp313t-win32.whl", hash = "sha256:e52eb31ae3afacdacfe50705a15b75ded67935770c460d88c215a9c0c40d0e9c"},
- {file = "coverage-7.7.1-cp313-cp313t-win_amd64.whl", hash = "sha256:a6b6b3bd121ee2ec4bd35039319f3423d0be282b9752a5ae9f18724bc93ebe7c"},
- {file = "coverage-7.7.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:34a3bf6b92e6621fc4dcdaab353e173ccb0ca9e4bfbcf7e49a0134c86c9cd303"},
- {file = "coverage-7.7.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d6874929d624d3a670f676efafbbc747f519a6121b581dd41d012109e70a5ebd"},
- {file = "coverage-7.7.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ba5ff236c87a7b7aa1441a216caf44baee14cbfbd2256d306f926d16b026578"},
- {file = "coverage-7.7.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:452735fafe8ff5918236d5fe1feac322b359e57692269c75151f9b4ee4b7e1bc"},
- {file = "coverage-7.7.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5f99a93cecf799738e211f9746dc83749b5693538fbfac279a61682ba309387"},
- {file = "coverage-7.7.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:11dd6f52c2a7ce8bf0a5f3b6e4a8eb60e157ffedc3c4b4314a41c1dfbd26ce58"},
- {file = "coverage-7.7.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:b52edb940d087e2a96e73c1523284a2e94a4e66fa2ea1e2e64dddc67173bad94"},
- {file = "coverage-7.7.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:d2e73e2ac468536197e6b3ab79bc4a5c9da0f078cd78cfcc7fe27cf5d1195ef0"},
- {file = "coverage-7.7.1-cp39-cp39-win32.whl", hash = "sha256:18f544356bceef17cc55fcf859e5664f06946c1b68efcea6acdc50f8f6a6e776"},
- {file = "coverage-7.7.1-cp39-cp39-win_amd64.whl", hash = "sha256:d66ff48ab3bb6f762a153e29c0fc1eb5a62a260217bc64470d7ba602f5886d20"},
- {file = "coverage-7.7.1-pp39.pp310.pp311-none-any.whl", hash = "sha256:5b7b02e50d54be6114cc4f6a3222fec83164f7c42772ba03b520138859b5fde1"},
- {file = "coverage-7.7.1-py3-none-any.whl", hash = "sha256:822fa99dd1ac686061e1219b67868e25d9757989cf2259f735a4802497d6da31"},
- {file = "coverage-7.7.1.tar.gz", hash = "sha256:199a1272e642266b90c9f40dec7fd3d307b51bf639fa0d15980dc0b3246c1393"},
+ {file = "coverage-7.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2931f66991175369859b5fd58529cd4b73582461877ecfd859b6549869287ffe"},
+ {file = "coverage-7.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:52a523153c568d2c0ef8826f6cc23031dc86cffb8c6aeab92c4ff776e7951b28"},
+ {file = "coverage-7.8.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c8a5c139aae4c35cbd7cadca1df02ea8cf28a911534fc1b0456acb0b14234f3"},
+ {file = "coverage-7.8.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5a26c0c795c3e0b63ec7da6efded5f0bc856d7c0b24b2ac84b4d1d7bc578d676"},
+ {file = "coverage-7.8.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:821f7bcbaa84318287115d54becb1915eece6918136c6f91045bb84e2f88739d"},
+ {file = "coverage-7.8.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a321c61477ff8ee705b8a5fed370b5710c56b3a52d17b983d9215861e37b642a"},
+ {file = "coverage-7.8.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:ed2144b8a78f9d94d9515963ed273d620e07846acd5d4b0a642d4849e8d91a0c"},
+ {file = "coverage-7.8.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:042e7841a26498fff7a37d6fda770d17519982f5b7d8bf5278d140b67b61095f"},
+ {file = "coverage-7.8.0-cp310-cp310-win32.whl", hash = "sha256:f9983d01d7705b2d1f7a95e10bbe4091fabc03a46881a256c2787637b087003f"},
+ {file = "coverage-7.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:5a570cd9bd20b85d1a0d7b009aaf6c110b52b5755c17be6962f8ccd65d1dbd23"},
+ {file = "coverage-7.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e7ac22a0bb2c7c49f441f7a6d46c9c80d96e56f5a8bc6972529ed43c8b694e27"},
+ {file = "coverage-7.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bf13d564d310c156d1c8e53877baf2993fb3073b2fc9f69790ca6a732eb4bfea"},
+ {file = "coverage-7.8.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5761c70c017c1b0d21b0815a920ffb94a670c8d5d409d9b38857874c21f70d7"},
+ {file = "coverage-7.8.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e5ff52d790c7e1628241ffbcaeb33e07d14b007b6eb00a19320c7b8a7024c040"},
+ {file = "coverage-7.8.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d39fc4817fd67b3915256af5dda75fd4ee10621a3d484524487e33416c6f3543"},
+ {file = "coverage-7.8.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b44674870709017e4b4036e3d0d6c17f06a0e6d4436422e0ad29b882c40697d2"},
+ {file = "coverage-7.8.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8f99eb72bf27cbb167b636eb1726f590c00e1ad375002230607a844d9e9a2318"},
+ {file = "coverage-7.8.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b571bf5341ba8c6bc02e0baeaf3b061ab993bf372d982ae509807e7f112554e9"},
+ {file = "coverage-7.8.0-cp311-cp311-win32.whl", hash = "sha256:e75a2ad7b647fd8046d58c3132d7eaf31b12d8a53c0e4b21fa9c4d23d6ee6d3c"},
+ {file = "coverage-7.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:3043ba1c88b2139126fc72cb48574b90e2e0546d4c78b5299317f61b7f718b78"},
+ {file = "coverage-7.8.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:bbb5cc845a0292e0c520656d19d7ce40e18d0e19b22cb3e0409135a575bf79fc"},
+ {file = "coverage-7.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4dfd9a93db9e78666d178d4f08a5408aa3f2474ad4d0e0378ed5f2ef71640cb6"},
+ {file = "coverage-7.8.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f017a61399f13aa6d1039f75cd467be388d157cd81f1a119b9d9a68ba6f2830d"},
+ {file = "coverage-7.8.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0915742f4c82208ebf47a2b154a5334155ed9ef9fe6190674b8a46c2fb89cb05"},
+ {file = "coverage-7.8.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a40fcf208e021eb14b0fac6bdb045c0e0cab53105f93ba0d03fd934c956143a"},
+ {file = "coverage-7.8.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a1f406a8e0995d654b2ad87c62caf6befa767885301f3b8f6f73e6f3c31ec3a6"},
+ {file = "coverage-7.8.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:77af0f6447a582fdc7de5e06fa3757a3ef87769fbb0fdbdeba78c23049140a47"},
+ {file = "coverage-7.8.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f2d32f95922927186c6dbc8bc60df0d186b6edb828d299ab10898ef3f40052fe"},
+ {file = "coverage-7.8.0-cp312-cp312-win32.whl", hash = "sha256:769773614e676f9d8e8a0980dd7740f09a6ea386d0f383db6821df07d0f08545"},
+ {file = "coverage-7.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:e5d2b9be5b0693cf21eb4ce0ec8d211efb43966f6657807f6859aab3814f946b"},
+ {file = "coverage-7.8.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5ac46d0c2dd5820ce93943a501ac5f6548ea81594777ca585bf002aa8854cacd"},
+ {file = "coverage-7.8.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:771eb7587a0563ca5bb6f622b9ed7f9d07bd08900f7589b4febff05f469bea00"},
+ {file = "coverage-7.8.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42421e04069fb2cbcbca5a696c4050b84a43b05392679d4068acbe65449b5c64"},
+ {file = "coverage-7.8.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:554fec1199d93ab30adaa751db68acec2b41c5602ac944bb19187cb9a41a8067"},
+ {file = "coverage-7.8.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5aaeb00761f985007b38cf463b1d160a14a22c34eb3f6a39d9ad6fc27cb73008"},
+ {file = "coverage-7.8.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:581a40c7b94921fffd6457ffe532259813fc68eb2bdda60fa8cc343414ce3733"},
+ {file = "coverage-7.8.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:f319bae0321bc838e205bf9e5bc28f0a3165f30c203b610f17ab5552cff90323"},
+ {file = "coverage-7.8.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:04bfec25a8ef1c5f41f5e7e5c842f6b615599ca8ba8391ec33a9290d9d2db3a3"},
+ {file = "coverage-7.8.0-cp313-cp313-win32.whl", hash = "sha256:dd19608788b50eed889e13a5d71d832edc34fc9dfce606f66e8f9f917eef910d"},
+ {file = "coverage-7.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:a9abbccd778d98e9c7e85038e35e91e67f5b520776781d9a1e2ee9d400869487"},
+ {file = "coverage-7.8.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:18c5ae6d061ad5b3e7eef4363fb27a0576012a7447af48be6c75b88494c6cf25"},
+ {file = "coverage-7.8.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:95aa6ae391a22bbbce1b77ddac846c98c5473de0372ba5c463480043a07bff42"},
+ {file = "coverage-7.8.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e013b07ba1c748dacc2a80e69a46286ff145935f260eb8c72df7185bf048f502"},
+ {file = "coverage-7.8.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d766a4f0e5aa1ba056ec3496243150698dc0481902e2b8559314368717be82b1"},
+ {file = "coverage-7.8.0-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad80e6b4a0c3cb6f10f29ae4c60e991f424e6b14219d46f1e7d442b938ee68a4"},
+ {file = "coverage-7.8.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b87eb6fc9e1bb8f98892a2458781348fa37e6925f35bb6ceb9d4afd54ba36c73"},
+ {file = "coverage-7.8.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:d1ba00ae33be84066cfbe7361d4e04dec78445b2b88bdb734d0d1cbab916025a"},
+ {file = "coverage-7.8.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f3c38e4e5ccbdc9198aecc766cedbb134b2d89bf64533973678dfcf07effd883"},
+ {file = "coverage-7.8.0-cp313-cp313t-win32.whl", hash = "sha256:379fe315e206b14e21db5240f89dc0774bdd3e25c3c58c2c733c99eca96f1ada"},
+ {file = "coverage-7.8.0-cp313-cp313t-win_amd64.whl", hash = "sha256:2e4b6b87bb0c846a9315e3ab4be2d52fac905100565f4b92f02c445c8799e257"},
+ {file = "coverage-7.8.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:fa260de59dfb143af06dcf30c2be0b200bed2a73737a8a59248fcb9fa601ef0f"},
+ {file = "coverage-7.8.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:96121edfa4c2dfdda409877ea8608dd01de816a4dc4a0523356067b305e4e17a"},
+ {file = "coverage-7.8.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6b8af63b9afa1031c0ef05b217faa598f3069148eeee6bb24b79da9012423b82"},
+ {file = "coverage-7.8.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:89b1f4af0d4afe495cd4787a68e00f30f1d15939f550e869de90a86efa7e0814"},
+ {file = "coverage-7.8.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94ec0be97723ae72d63d3aa41961a0b9a6f5a53ff599813c324548d18e3b9e8c"},
+ {file = "coverage-7.8.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:8a1d96e780bdb2d0cbb297325711701f7c0b6f89199a57f2049e90064c29f6bd"},
+ {file = "coverage-7.8.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:f1d8a2a57b47142b10374902777e798784abf400a004b14f1b0b9eaf1e528ba4"},
+ {file = "coverage-7.8.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:cf60dd2696b457b710dd40bf17ad269d5f5457b96442f7f85722bdb16fa6c899"},
+ {file = "coverage-7.8.0-cp39-cp39-win32.whl", hash = "sha256:be945402e03de47ba1872cd5236395e0f4ad635526185a930735f66710e1bd3f"},
+ {file = "coverage-7.8.0-cp39-cp39-win_amd64.whl", hash = "sha256:90e7fbc6216ecaffa5a880cdc9c77b7418c1dcb166166b78dbc630d07f278cc3"},
+ {file = "coverage-7.8.0-pp39.pp310.pp311-none-any.whl", hash = "sha256:b8194fb8e50d556d5849753de991d390c5a1edeeba50f68e3a9253fbd8bf8ccd"},
+ {file = "coverage-7.8.0-py3-none-any.whl", hash = "sha256:dbf364b4c5e7bae9250528167dfe40219b62e2d573c854d74be213e1e52069f7"},
+ {file = "coverage-7.8.0.tar.gz", hash = "sha256:7a3d62b3b03b4b6fd41a085f3574874cf946cb4604d2b4d3e8dca8cd570ca501"},
]
[package.dependencies]
@@ -1517,24 +1470,24 @@ requests = ">=2.6.0"
[[package]]
name = "datadog-lambda"
-version = "6.106.0"
+version = "6.107.0"
description = "The Datadog AWS Lambda Library"
optional = false
python-versions = "<4,>=3.8.0"
groups = ["main", "dev"]
files = [
- {file = "datadog_lambda-6.106.0-py3-none-any.whl", hash = "sha256:0b648c4d16e11e3bc95536f41b26d6ca3a973217221b705bd0a40aece364eee2"},
- {file = "datadog_lambda-6.106.0.tar.gz", hash = "sha256:17abb2a4f1fe803e54bc4b35a6977e3e873c290c38b4c963096e054052fd0eea"},
+ {file = "datadog_lambda-6.107.0-py3-none-any.whl", hash = "sha256:3de4d1a9019649e82d9736d6170b6a9ba6ee5a531cb477675b45cfec2784bd86"},
+ {file = "datadog_lambda-6.107.0.tar.gz", hash = "sha256:6389e9342c0b9900509f62c4b3581a25bbb0916f6aa76e1e0065126c58b8f6e7"},
]
[package.dependencies]
datadog = ">=0.51.0,<1.0.0"
-ddtrace = ">=2.20.0"
+ddtrace = ">=2.20.0,<4"
ujson = ">=5.9.0"
wrapt = ">=1.11.2,<2.0.0"
[package.extras]
-dev = ["boto3 (>=1.34.0,<2.0.0)", "flake8 (>=5.0.4,<6.0.0)", "pytest (>=8.0.0,<9.0.0)", "pytest-benchmark (>=4.0,<5.0)", "requests (>=2.22.0,<3.0.0)"]
+dev = ["botocore (>=1.34.0,<2.0.0)", "flake8 (>=5.0.4,<6.0.0)", "pytest (>=8.0.0,<9.0.0)", "pytest-benchmark (>=4.0,<5.0)", "requests (>=2.22.0,<3.0.0)"]
[[package]]
name = "ddtrace"
@@ -1656,14 +1609,14 @@ dev = ["PyTest", "PyTest-Cov", "bump2version (<1)", "setuptools ; python_version
[[package]]
name = "dill"
-version = "0.3.9"
+version = "0.4.0"
description = "serialize all of Python"
optional = false
python-versions = ">=3.8"
groups = ["dev"]
files = [
- {file = "dill-0.3.9-py3-none-any.whl", hash = "sha256:468dff3b89520b474c0397703366b7b95eebe6303f108adf9b19da1f702be87a"},
- {file = "dill-0.3.9.tar.gz", hash = "sha256:81aa267dddf68cbfe8029c42ca9ec6a4ab3b22371d1c450abc54422577b4512c"},
+ {file = "dill-0.4.0-py3-none-any.whl", hash = "sha256:44f54bf6412c2c8464c14e8243eb163690a9800dbe2c367330883b19c7561049"},
+ {file = "dill-0.4.0.tar.gz", hash = "sha256:0633f1d2df477324f53a895b02c901fb961bdbf65a17122586ea7019292cbcf0"},
]
[package.extras]
@@ -1884,62 +1837,59 @@ colorama = ">=0.4"
[[package]]
name = "h11"
-version = "0.14.0"
+version = "0.16.0"
description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1"
optional = false
-python-versions = ">=3.7"
+python-versions = ">=3.8"
groups = ["dev"]
files = [
- {file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"},
- {file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"},
+ {file = "h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86"},
+ {file = "h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1"},
]
[[package]]
name = "httpcore"
-version = "1.0.7"
+version = "0.13.2"
description = "A minimal low-level HTTP client."
optional = false
-python-versions = ">=3.8"
+python-versions = ">=3.6"
groups = ["dev"]
files = [
- {file = "httpcore-1.0.7-py3-none-any.whl", hash = "sha256:a3fff8f43dc260d5bd363d9f9cf1830fa3a458b332856f34282de498ed420edd"},
- {file = "httpcore-1.0.7.tar.gz", hash = "sha256:8551cb62a169ec7162ac7be8d4817d561f60e08eaa485234898414bb5a8a0b4c"},
+ {file = "httpcore-0.13.2-py3-none-any.whl", hash = "sha256:52b7d9413f6f5592a667de9209d70d4d41aba3fb0540dd7c93475c78b85941e9"},
+ {file = "httpcore-0.13.2.tar.gz", hash = "sha256:c16efbdf643e1b57bde0adc12c53b08645d7d92d6d345a3f71adfc2a083e7fd2"},
]
[package.dependencies]
-certifi = "*"
-h11 = ">=0.13,<0.15"
+h11 = "==0.*"
+sniffio = "==1.*"
[package.extras]
-asyncio = ["anyio (>=4.0,<5.0)"]
http2 = ["h2 (>=3,<5)"]
-socks = ["socksio (==1.*)"]
-trio = ["trio (>=0.22.0,<1.0)"]
[[package]]
name = "httpx"
-version = "0.28.1"
+version = "0.25.1"
description = "The next generation HTTP client."
optional = false
python-versions = ">=3.8"
groups = ["dev"]
files = [
- {file = "httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad"},
- {file = "httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc"},
+ {file = "httpx-0.25.1-py3-none-any.whl", hash = "sha256:fec7d6cc5c27c578a391f7e87b9aa7d3d8fbcd034f6399f9f79b45bcc12a866a"},
+ {file = "httpx-0.25.1.tar.gz", hash = "sha256:ffd96d5cf901e63863d9f1b4b6807861dbea4d301613415d9e6e57ead15fc5d0"},
]
[package.dependencies]
anyio = "*"
certifi = "*"
-httpcore = "==1.*"
+httpcore = "*"
idna = "*"
+sniffio = "*"
[package.extras]
brotli = ["brotli ; platform_python_implementation == \"CPython\"", "brotlicffi ; platform_python_implementation != \"CPython\""]
cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"]
http2 = ["h2 (>=3,<5)"]
socks = ["socksio (==1.*)"]
-zstd = ["zstandard (>=0.18.0)"]
[[package]]
name = "hvac"
@@ -2185,14 +2135,14 @@ files = [
[[package]]
name = "jsii"
-version = "1.109.0"
+version = "1.111.0"
description = "Python client for jsii runtime"
optional = false
python-versions = "~=3.9"
groups = ["dev"]
files = [
- {file = "jsii-1.109.0-py3-none-any.whl", hash = "sha256:100bb48c7f74b8e22b3182c5466db1c32565ddb681ed5e2bf556076a734d3f07"},
- {file = "jsii-1.109.0.tar.gz", hash = "sha256:85e0deca8089e2918776541e986d5abab90a66d4330eedfc14e8a060dd507bad"},
+ {file = "jsii-1.111.0-py3-none-any.whl", hash = "sha256:3084e31173e73d2eefee678c8ee31aa49428830509043057a421a4c0dde94434"},
+ {file = "jsii-1.111.0.tar.gz", hash = "sha256:db523ab9b6575c84d6ed8779cdbdc739abd48a7cb0723b66beb84c1a9dc31c7c"},
]
[package.dependencies]
@@ -2610,21 +2560,21 @@ mkdocs = ">=0.17"
[[package]]
name = "mkdocs-material"
-version = "9.6.9"
+version = "9.6.12"
description = "Documentation that simply works"
optional = false
python-versions = ">=3.8"
groups = ["dev"]
files = [
- {file = "mkdocs_material-9.6.9-py3-none-any.whl", hash = "sha256:6e61b7fb623ce2aa4622056592b155a9eea56ff3487d0835075360be45a4c8d1"},
- {file = "mkdocs_material-9.6.9.tar.gz", hash = "sha256:a4872139715a1f27b2aa3f3dc31a9794b7bbf36333c0ba4607cf04786c94f89c"},
+ {file = "mkdocs_material-9.6.12-py3-none-any.whl", hash = "sha256:92b4fbdc329e4febc267ca6e2c51e8501fa97b2225c5f4deb4d4e43550f8e61e"},
+ {file = "mkdocs_material-9.6.12.tar.gz", hash = "sha256:add6a6337b29f9ea7912cb1efc661de2c369060b040eb5119855d794ea85b473"},
]
[package.dependencies]
babel = ">=2.10,<3.0"
backrefs = ">=5.7.post1,<6.0"
colorama = ">=0.4,<1.0"
-jinja2 = ">=3.0,<4.0"
+jinja2 = ">=3.1,<4.0"
markdown = ">=3.2,<4.0"
mkdocs = ">=1.6,<2.0"
mkdocs-material-extensions = ">=1.3,<2.0"
@@ -2679,14 +2629,14 @@ python-legacy = ["mkdocstrings-python-legacy (>=0.2.1)"]
[[package]]
name = "mkdocstrings-python"
-version = "1.16.8"
+version = "1.16.10"
description = "A Python handler for mkdocstrings."
optional = false
python-versions = ">=3.9"
groups = ["dev"]
files = [
- {file = "mkdocstrings_python-1.16.8-py3-none-any.whl", hash = "sha256:211b7aaf776cd45578ecb531e5ad0d3a35a8be9101a6bfa10de38a69af9d8fd8"},
- {file = "mkdocstrings_python-1.16.8.tar.gz", hash = "sha256:9453ccae69be103810c1cf6435ce71c8f714ae37fef4d87d16aa92a7c800fe1d"},
+ {file = "mkdocstrings_python-1.16.10-py3-none-any.whl", hash = "sha256:63bb9f01f8848a644bdb6289e86dc38ceddeaa63ecc2e291e3b2ca52702a6643"},
+ {file = "mkdocstrings_python-1.16.10.tar.gz", hash = "sha256:f9eedfd98effb612ab4d0ed6dd2b73aff6eba5215e0a65cea6d877717f75502e"},
]
[package.dependencies]
@@ -2715,32 +2665,35 @@ tests = ["pytest (>=4.6)"]
[[package]]
name = "multiprocess"
-version = "0.70.17"
+version = "0.70.18"
description = "better multiprocessing and multithreading in Python"
optional = false
python-versions = ">=3.8"
groups = ["dev"]
files = [
- {file = "multiprocess-0.70.17-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7ddb24e5bcdb64e90ec5543a1f05a39463068b6d3b804aa3f2a4e16ec28562d6"},
- {file = "multiprocess-0.70.17-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:d729f55198a3579f6879766a6d9b72b42d4b320c0dcb7844afb774d75b573c62"},
- {file = "multiprocess-0.70.17-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:c2c82d0375baed8d8dd0d8c38eb87c5ae9c471f8e384ad203a36f095ee860f67"},
- {file = "multiprocess-0.70.17-pp38-pypy38_pp73-macosx_10_9_arm64.whl", hash = "sha256:a22a6b1a482b80eab53078418bb0f7025e4f7d93cc8e1f36481477a023884861"},
- {file = "multiprocess-0.70.17-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:349525099a0c9ac5936f0488b5ee73199098dac3ac899d81d326d238f9fd3ccd"},
- {file = "multiprocess-0.70.17-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:27b8409c02b5dd89d336107c101dfbd1530a2cd4fd425fc27dcb7adb6e0b47bf"},
- {file = "multiprocess-0.70.17-pp39-pypy39_pp73-macosx_10_13_arm64.whl", hash = "sha256:2ea0939b0f4760a16a548942c65c76ff5afd81fbf1083c56ae75e21faf92e426"},
- {file = "multiprocess-0.70.17-pp39-pypy39_pp73-macosx_10_13_x86_64.whl", hash = "sha256:2b12e081df87ab755190e227341b2c3b17ee6587e9c82fecddcbe6aa812cd7f7"},
- {file = "multiprocess-0.70.17-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:a0f01cd9d079af7a8296f521dc03859d1a414d14c1e2b6e676ef789333421c95"},
- {file = "multiprocess-0.70.17-py310-none-any.whl", hash = "sha256:38357ca266b51a2e22841b755d9a91e4bb7b937979a54d411677111716c32744"},
- {file = "multiprocess-0.70.17-py311-none-any.whl", hash = "sha256:2884701445d0177aec5bd5f6ee0df296773e4fb65b11903b94c613fb46cfb7d1"},
- {file = "multiprocess-0.70.17-py312-none-any.whl", hash = "sha256:2818af14c52446b9617d1b0755fa70ca2f77c28b25ed97bdaa2c69a22c47b46c"},
- {file = "multiprocess-0.70.17-py313-none-any.whl", hash = "sha256:20c28ca19079a6c879258103a6d60b94d4ffe2d9da07dda93fb1c8bc6243f522"},
- {file = "multiprocess-0.70.17-py38-none-any.whl", hash = "sha256:1d52f068357acd1e5bbc670b273ef8f81d57863235d9fbf9314751886e141968"},
- {file = "multiprocess-0.70.17-py39-none-any.whl", hash = "sha256:c3feb874ba574fbccfb335980020c1ac631fbf2a3f7bee4e2042ede62558a021"},
- {file = "multiprocess-0.70.17.tar.gz", hash = "sha256:4ae2f11a3416809ebc9a48abfc8b14ecce0652a0944731a1493a3c1ba44ff57a"},
+ {file = "multiprocess-0.70.18-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:25d4012dcaaf66b9e8e955f58482b42910c2ee526d532844d8bcf661bbc604df"},
+ {file = "multiprocess-0.70.18-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:06b19433de0d02afe5869aec8931dd5c01d99074664f806c73896b0d9e527213"},
+ {file = "multiprocess-0.70.18-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:6fa1366f994373aaf2d4738b0f56e707caeaa05486e97a7f71ee0853823180c2"},
+ {file = "multiprocess-0.70.18-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:8b8940ae30139e04b076da6c5b83e9398585ebdf0f2ad3250673fef5b2ff06d6"},
+ {file = "multiprocess-0.70.18-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:0929ba95831adb938edbd5fb801ac45e705ecad9d100b3e653946b7716cb6bd3"},
+ {file = "multiprocess-0.70.18-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:4d77f8e4bfe6c6e2e661925bbf9aed4d5ade9a1c6502d5dfc10129b9d1141797"},
+ {file = "multiprocess-0.70.18-pp38-pypy38_pp73-macosx_10_9_arm64.whl", hash = "sha256:2dbaae9bffa1fb2d58077c0044ffe87a8c8974e90fcf778cdf90e139c970d42a"},
+ {file = "multiprocess-0.70.18-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:bcac5a4e81f1554d98d1bba963eeb1bd24966432f04fcbd29b6e1a16251ad712"},
+ {file = "multiprocess-0.70.18-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:c0c7cd75d0987ab6166d64e654787c781dbacbcbcaaede4c1ffe664720b3e14b"},
+ {file = "multiprocess-0.70.18-pp39-pypy39_pp73-macosx_10_13_arm64.whl", hash = "sha256:9fd8d662f7524a95a1be7cbea271f0b33089fe792baabec17d93103d368907da"},
+ {file = "multiprocess-0.70.18-pp39-pypy39_pp73-macosx_10_13_x86_64.whl", hash = "sha256:3fbba48bfcd932747c33f0b152b26207c4e0840c35cab359afaff7a8672b1031"},
+ {file = "multiprocess-0.70.18-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:5f9be0342e597dde86152c10442c5fb6c07994b1c29de441b7a3a08b0e6be2a0"},
+ {file = "multiprocess-0.70.18-py310-none-any.whl", hash = "sha256:60c194974c31784019c1f459d984e8f33ee48f10fcf42c309ba97b30d9bd53ea"},
+ {file = "multiprocess-0.70.18-py311-none-any.whl", hash = "sha256:5aa6eef98e691281b3ad923be2832bf1c55dd2c859acd73e5ec53a66aae06a1d"},
+ {file = "multiprocess-0.70.18-py312-none-any.whl", hash = "sha256:9b78f8e5024b573730bfb654783a13800c2c0f2dfc0c25e70b40d184d64adaa2"},
+ {file = "multiprocess-0.70.18-py313-none-any.whl", hash = "sha256:871743755f43ef57d7910a38433cfe41319e72be1bbd90b79c7a5ac523eb9334"},
+ {file = "multiprocess-0.70.18-py38-none-any.whl", hash = "sha256:dbf705e52a154fe5e90fb17b38f02556169557c2dd8bb084f2e06c2784d8279b"},
+ {file = "multiprocess-0.70.18-py39-none-any.whl", hash = "sha256:e78ca805a72b1b810c690b6b4cc32579eba34f403094bbbae962b7b5bf9dfcb8"},
+ {file = "multiprocess-0.70.18.tar.gz", hash = "sha256:f9597128e6b3e67b23956da07cf3d2e5cba79e2f4e0fba8d7903636663ec6d0d"},
]
[package.dependencies]
-dill = ">=0.3.9"
+dill = ">=0.4.0"
[[package]]
name = "mypy"
@@ -2798,14 +2751,14 @@ reports = ["lxml"]
[[package]]
name = "mypy-boto3-appconfig"
-version = "1.37.0"
-description = "Type annotations for boto3 AppConfig 1.37.0 service generated with mypy-boto3-builder 8.9.2"
+version = "1.38.0"
+description = "Type annotations for boto3 AppConfig 1.38.0 service generated with mypy-boto3-builder 8.10.1"
optional = false
python-versions = ">=3.8"
groups = ["dev"]
files = [
- {file = "mypy_boto3_appconfig-1.37.0-py3-none-any.whl", hash = "sha256:370bbd5cca3aac0dd1d163e9de13575bb4e2d3cd9dc812c51aa16740abf074ab"},
- {file = "mypy_boto3_appconfig-1.37.0.tar.gz", hash = "sha256:757ca4cbc94a9ac82c88296e91d74374a1c0c2ed55edd943ef5592861cd80491"},
+ {file = "mypy_boto3_appconfig-1.38.0-py3-none-any.whl", hash = "sha256:86cd3f27e4f8cf0f41a324c2ebc6490887afebb16dab627cbb2dc9c3ccef88fa"},
+ {file = "mypy_boto3_appconfig-1.38.0.tar.gz", hash = "sha256:a32ac95e45c746f491286c52e4cb52be12aa5d6189fb680624c0e423239b2fe2"},
]
[package.dependencies]
@@ -2813,14 +2766,14 @@ typing-extensions = {version = "*", markers = "python_version < \"3.12\""}
[[package]]
name = "mypy-boto3-appconfigdata"
-version = "1.37.0"
-description = "Type annotations for boto3 AppConfigData 1.37.0 service generated with mypy-boto3-builder 8.9.2"
+version = "1.38.0"
+description = "Type annotations for boto3 AppConfigData 1.38.0 service generated with mypy-boto3-builder 8.10.1"
optional = false
python-versions = ">=3.8"
groups = ["dev"]
files = [
- {file = "mypy_boto3_appconfigdata-1.37.0-py3-none-any.whl", hash = "sha256:d90e474c751f4bed26dadddd68c03f4c21ed76809cc6c0141b08e3dd86d861f6"},
- {file = "mypy_boto3_appconfigdata-1.37.0.tar.gz", hash = "sha256:44f2e277d637c3ce1471326865ac4e117e456a08677bce7e240f9186c26cda9c"},
+ {file = "mypy_boto3_appconfigdata-1.38.0-py3-none-any.whl", hash = "sha256:d73010c5b8afeadbed17268a0fb48843140a911ed68aad6d3152877a3a850607"},
+ {file = "mypy_boto3_appconfigdata-1.38.0.tar.gz", hash = "sha256:a96042dc7ce969532f3dbeb56dec1e819391fc9f9f41300ec6a235a1d2a3b599"},
]
[package.dependencies]
@@ -2828,14 +2781,14 @@ typing-extensions = {version = "*", markers = "python_version < \"3.12\""}
[[package]]
name = "mypy-boto3-cloudformation"
-version = "1.37.0"
-description = "Type annotations for boto3 CloudFormation 1.37.0 service generated with mypy-boto3-builder 8.9.2"
+version = "1.38.0"
+description = "Type annotations for boto3 CloudFormation 1.38.0 service generated with mypy-boto3-builder 8.10.1"
optional = false
python-versions = ">=3.8"
groups = ["dev"]
files = [
- {file = "mypy_boto3_cloudformation-1.37.0-py3-none-any.whl", hash = "sha256:8e2eee79b431bc47e94b4cdf3c38cc61ec90aca38a153874038b04f17f69a3c8"},
- {file = "mypy_boto3_cloudformation-1.37.0.tar.gz", hash = "sha256:bfc74ba8614671406e11f284609674167533eda220dd635c2fb824cb6023baed"},
+ {file = "mypy_boto3_cloudformation-1.38.0-py3-none-any.whl", hash = "sha256:a1411aa5875b737492aaac5f7e8ce450f034c18f972eb608a9eba6fe35837f6a"},
+ {file = "mypy_boto3_cloudformation-1.38.0.tar.gz", hash = "sha256:563399166c07e91e0695fb1e58103a248b2bee0db5e2c3f07155776dd6311805"},
]
[package.dependencies]
@@ -2843,14 +2796,14 @@ typing-extensions = {version = "*", markers = "python_version < \"3.12\""}
[[package]]
name = "mypy-boto3-cloudwatch"
-version = "1.37.0"
-description = "Type annotations for boto3 CloudWatch 1.37.0 service generated with mypy-boto3-builder 8.9.2"
+version = "1.38.0"
+description = "Type annotations for boto3 CloudWatch 1.38.0 service generated with mypy-boto3-builder 8.10.1"
optional = false
python-versions = ">=3.8"
groups = ["dev"]
files = [
- {file = "mypy_boto3_cloudwatch-1.37.0-py3-none-any.whl", hash = "sha256:6177eab67867697badc45ed9d6ec4a13260c562ddf26dd500f7c2ee44d772ecc"},
- {file = "mypy_boto3_cloudwatch-1.37.0.tar.gz", hash = "sha256:a2917ad8b0eaf6a8f4bcba4052b495488d5a4d1b694a4df3db2889b6f52c9cd2"},
+ {file = "mypy_boto3_cloudwatch-1.38.0-py3-none-any.whl", hash = "sha256:1976daa402ecc95200a9b641f733a5612e72daa883c8ac967443955e61cea6e9"},
+ {file = "mypy_boto3_cloudwatch-1.38.0.tar.gz", hash = "sha256:bb3492af66e94eb20322d73b793050ea54f1742118b18e36e798e4dafe3b167e"},
]
[package.dependencies]
@@ -2858,14 +2811,14 @@ typing-extensions = {version = "*", markers = "python_version < \"3.12\""}
[[package]]
name = "mypy-boto3-dynamodb"
-version = "1.37.12"
-description = "Type annotations for boto3 DynamoDB 1.37.12 service generated with mypy-boto3-builder 8.10.0"
+version = "1.38.0"
+description = "Type annotations for boto3 DynamoDB 1.38.0 service generated with mypy-boto3-builder 8.10.1"
optional = false
python-versions = ">=3.8"
groups = ["dev"]
files = [
- {file = "mypy_boto3_dynamodb-1.37.12-py3-none-any.whl", hash = "sha256:a4b2770ec1f8d6096b5e6d863800f3ff742c86a17dfa5e6b012ed7f7ccd28921"},
- {file = "mypy_boto3_dynamodb-1.37.12.tar.gz", hash = "sha256:0e4d7a16fb9dba7aab7ac9ba8ff3721f9696a8484b1eed693b7949164b7805ba"},
+ {file = "mypy_boto3_dynamodb-1.38.0-py3-none-any.whl", hash = "sha256:ff4b3ad94ba001d1a971e30c82c43b84dde6c211d1ae62671d0c04d1af960e1b"},
+ {file = "mypy_boto3_dynamodb-1.38.0.tar.gz", hash = "sha256:092107032669ea155a6001c3c0d96e2576ae4cfeca8f54566f0ec5e103734028"},
]
[package.dependencies]
@@ -2873,14 +2826,14 @@ typing-extensions = {version = "*", markers = "python_version < \"3.12\""}
[[package]]
name = "mypy-boto3-lambda"
-version = "1.37.0"
-description = "Type annotations for boto3 Lambda 1.37.0 service generated with mypy-boto3-builder 8.9.2"
+version = "1.38.0"
+description = "Type annotations for boto3 Lambda 1.38.0 service generated with mypy-boto3-builder 8.10.1"
optional = false
python-versions = ">=3.8"
groups = ["dev"]
files = [
- {file = "mypy_boto3_lambda-1.37.0-py3-none-any.whl", hash = "sha256:787e69fca3eafda26c03030ef94592e3e3eee74d373c9075962a8fe4c2d7397d"},
- {file = "mypy_boto3_lambda-1.37.0.tar.gz", hash = "sha256:61b1abd5b7dba5d16f9de4d9717f2f67438246d11cde1c2bbe31ea38753333c8"},
+ {file = "mypy_boto3_lambda-1.38.0-py3-none-any.whl", hash = "sha256:0dcb882826f61fd2751f6b98330b0e11085570654db85318aea018374ca88dc9"},
+ {file = "mypy_boto3_lambda-1.38.0.tar.gz", hash = "sha256:ece7b3848c045e1be81c4f2b7482002c17ce7cb70de850661146103a8cb1a3fb"},
]
[package.dependencies]
@@ -2888,14 +2841,14 @@ typing-extensions = {version = "*", markers = "python_version < \"3.12\""}
[[package]]
name = "mypy-boto3-logs"
-version = "1.37.12"
-description = "Type annotations for boto3 CloudWatchLogs 1.37.12 service generated with mypy-boto3-builder 8.10.0"
+version = "1.38.0"
+description = "Type annotations for boto3 CloudWatchLogs 1.38.0 service generated with mypy-boto3-builder 8.10.1"
optional = false
python-versions = ">=3.8"
groups = ["dev"]
files = [
- {file = "mypy_boto3_logs-1.37.12-py3-none-any.whl", hash = "sha256:7861bdde73fb41efa080deb66980727164d582293f49e2cfbc81da45710b459e"},
- {file = "mypy_boto3_logs-1.37.12.tar.gz", hash = "sha256:184547a47deae4d06d3f36b93d18b9742d55bf09b66dc48ec4a9e70c79a9e2da"},
+ {file = "mypy_boto3_logs-1.38.0-py3-none-any.whl", hash = "sha256:114a65b303f4849a63de53ac75a0b10b9bcf8ae681578fccabecc50b79d59608"},
+ {file = "mypy_boto3_logs-1.38.0.tar.gz", hash = "sha256:e636fa6f31b84f6c3d8cd5b85d87fd9bab766631999c4a0c83c2cf0003efe5a7"},
]
[package.dependencies]
@@ -2903,14 +2856,14 @@ typing-extensions = {version = "*", markers = "python_version < \"3.12\""}
[[package]]
name = "mypy-boto3-s3"
-version = "1.37.0"
-description = "Type annotations for boto3 S3 1.37.0 service generated with mypy-boto3-builder 8.9.2"
+version = "1.38.0"
+description = "Type annotations for boto3 S3 1.38.0 service generated with mypy-boto3-builder 8.10.1"
optional = false
python-versions = ">=3.8"
groups = ["dev"]
files = [
- {file = "mypy_boto3_s3-1.37.0-py3-none-any.whl", hash = "sha256:d2b702649d7ebb2bd2b8f574fd51b35fc2a2ec4a8efb590db5eb0d0d9f74be6f"},
- {file = "mypy_boto3_s3-1.37.0.tar.gz", hash = "sha256:bc6ec4cbbd8e0206143d9b1f24927e086a2467a2c6a641feb978599d75954e82"},
+ {file = "mypy_boto3_s3-1.38.0-py3-none-any.whl", hash = "sha256:5cd9449df0ef6cf89e00e6fc9130a0ab641f703a23ab1d2146c394da058e8282"},
+ {file = "mypy_boto3_s3-1.38.0.tar.gz", hash = "sha256:f8fe586e45123ffcd305a0c30847128f3931d888649e2b4c5a52f412183c840a"},
]
[package.dependencies]
@@ -2918,14 +2871,14 @@ typing-extensions = {version = "*", markers = "python_version < \"3.12\""}
[[package]]
name = "mypy-boto3-secretsmanager"
-version = "1.37.0"
-description = "Type annotations for boto3 SecretsManager 1.37.0 service generated with mypy-boto3-builder 8.9.2"
+version = "1.38.0"
+description = "Type annotations for boto3 SecretsManager 1.38.0 service generated with mypy-boto3-builder 8.10.1"
optional = false
python-versions = ">=3.8"
groups = ["dev"]
files = [
- {file = "mypy_boto3_secretsmanager-1.37.0-py3-none-any.whl", hash = "sha256:3975120e7819f53daa02646ea34c3a513115eb6895ec4fefdd4d8389616ddf90"},
- {file = "mypy_boto3_secretsmanager-1.37.0.tar.gz", hash = "sha256:06940d842e7a600fdf542190e2b0fd35ca7914cb118b5a578036ba6ce659a41b"},
+ {file = "mypy_boto3_secretsmanager-1.38.0-py3-none-any.whl", hash = "sha256:48d5057450ee307b132ce2d0976233a2c5331616fabdf423ecbc103f7431dd5e"},
+ {file = "mypy_boto3_secretsmanager-1.38.0.tar.gz", hash = "sha256:1666108e70f03e4dc1de449388d7facb77aba231a026bac0c3240fc27fd31a98"},
]
[package.dependencies]
@@ -2933,14 +2886,14 @@ typing-extensions = {version = "*", markers = "python_version < \"3.12\""}
[[package]]
name = "mypy-boto3-ssm"
-version = "1.37.4"
-description = "Type annotations for boto3 SSM 1.37.4 service generated with mypy-boto3-builder 8.10.0"
+version = "1.38.0"
+description = "Type annotations for boto3 SSM 1.38.0 service generated with mypy-boto3-builder 8.10.1"
optional = false
python-versions = ">=3.8"
groups = ["dev"]
files = [
- {file = "mypy_boto3_ssm-1.37.4-py3-none-any.whl", hash = "sha256:db8097b631ccba425a5bc65c189f2e2933ac908671e4d436195c75f22305cab6"},
- {file = "mypy_boto3_ssm-1.37.4.tar.gz", hash = "sha256:7e7869788aa4db85171278990a115a76425b2de6db28074a1f166b946ecca522"},
+ {file = "mypy_boto3_ssm-1.38.0-py3-none-any.whl", hash = "sha256:b256dae1f73a969cd50b208d537967d14151f8de16c04b335add6e9805e43ab8"},
+ {file = "mypy_boto3_ssm-1.38.0.tar.gz", hash = "sha256:ac6e65cc05aa283233ba8b6b405176f30e4ae3339745e36ed33b55c07a5e3325"},
]
[package.dependencies]
@@ -2948,14 +2901,14 @@ typing-extensions = {version = "*", markers = "python_version < \"3.12\""}
[[package]]
name = "mypy-boto3-xray"
-version = "1.37.0"
-description = "Type annotations for boto3 XRay 1.37.0 service generated with mypy-boto3-builder 8.9.2"
+version = "1.38.0"
+description = "Type annotations for boto3 XRay 1.38.0 service generated with mypy-boto3-builder 8.10.1"
optional = false
python-versions = ">=3.8"
groups = ["dev"]
files = [
- {file = "mypy_boto3_xray-1.37.0-py3-none-any.whl", hash = "sha256:9aac766ed1b963ad79efe4f2860edc3c532a2f2df7882bf5c0bcfcdb09b446c3"},
- {file = "mypy_boto3_xray-1.37.0.tar.gz", hash = "sha256:9c98462e0571edb617a57e628b30d7cb218e494085d2a7f725cb15e8d347ad7c"},
+ {file = "mypy_boto3_xray-1.38.0-py3-none-any.whl", hash = "sha256:6c2eeb4e7e675e978e18761ced7af95866d01df7b5c21548e452c53c255237b8"},
+ {file = "mypy_boto3_xray-1.38.0.tar.gz", hash = "sha256:13dadd42be39f0b3e139d3d3a0021c24e617d67eb8b0d09dac3d7d7795b30384"},
]
[package.dependencies]
@@ -3212,21 +3165,22 @@ markers = {main = "(extra == \"all\" or extra == \"datamasking\") and platform_p
[[package]]
name = "pydantic"
-version = "2.10.6"
+version = "2.11.3"
description = "Data validation using Python type hints"
optional = false
-python-versions = ">=3.8"
+python-versions = ">=3.9"
groups = ["main", "dev"]
files = [
- {file = "pydantic-2.10.6-py3-none-any.whl", hash = "sha256:427d664bf0b8a2b34ff5dd0f5a18df00591adcee7198fbd71981054cef37b584"},
- {file = "pydantic-2.10.6.tar.gz", hash = "sha256:ca5daa827cce33de7a42be142548b0096bf05a7e7b365aebfa5f8eeec7128236"},
+ {file = "pydantic-2.11.3-py3-none-any.whl", hash = "sha256:a082753436a07f9ba1289c6ffa01cd93db3548776088aa917cc43b63f68fa60f"},
+ {file = "pydantic-2.11.3.tar.gz", hash = "sha256:7471657138c16adad9322fe3070c0116dd6c3ad8d649300e3cbdfe91f4db4ec3"},
]
markers = {main = "extra == \"all\" or extra == \"parser\""}
[package.dependencies]
annotated-types = ">=0.6.0"
-pydantic-core = "2.27.2"
+pydantic-core = "2.33.1"
typing-extensions = ">=4.12.2"
+typing-inspection = ">=0.4.0"
[package.extras]
email = ["email-validator (>=2.0.0)"]
@@ -3234,112 +3188,111 @@ timezone = ["tzdata ; python_version >= \"3.9\" and platform_system == \"Windows
[[package]]
name = "pydantic-core"
-version = "2.27.2"
+version = "2.33.1"
description = "Core functionality for Pydantic validation and serialization"
optional = false
-python-versions = ">=3.8"
+python-versions = ">=3.9"
groups = ["main", "dev"]
files = [
- {file = "pydantic_core-2.27.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2d367ca20b2f14095a8f4fa1210f5a7b78b8a20009ecced6b12818f455b1e9fa"},
- {file = "pydantic_core-2.27.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:491a2b73db93fab69731eaee494f320faa4e093dbed776be1a829c2eb222c34c"},
- {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7969e133a6f183be60e9f6f56bfae753585680f3b7307a8e555a948d443cc05a"},
- {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3de9961f2a346257caf0aa508a4da705467f53778e9ef6fe744c038119737ef5"},
- {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e2bb4d3e5873c37bb3dd58714d4cd0b0e6238cebc4177ac8fe878f8b3aa8e74c"},
- {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:280d219beebb0752699480fe8f1dc61ab6615c2046d76b7ab7ee38858de0a4e7"},
- {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47956ae78b6422cbd46f772f1746799cbb862de838fd8d1fbd34a82e05b0983a"},
- {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:14d4a5c49d2f009d62a2a7140d3064f686d17a5d1a268bc641954ba181880236"},
- {file = "pydantic_core-2.27.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:337b443af21d488716f8d0b6164de833e788aa6bd7e3a39c005febc1284f4962"},
- {file = "pydantic_core-2.27.2-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:03d0f86ea3184a12f41a2d23f7ccb79cdb5a18e06993f8a45baa8dfec746f0e9"},
- {file = "pydantic_core-2.27.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7041c36f5680c6e0f08d922aed302e98b3745d97fe1589db0a3eebf6624523af"},
- {file = "pydantic_core-2.27.2-cp310-cp310-win32.whl", hash = "sha256:50a68f3e3819077be2c98110c1f9dcb3817e93f267ba80a2c05bb4f8799e2ff4"},
- {file = "pydantic_core-2.27.2-cp310-cp310-win_amd64.whl", hash = "sha256:e0fd26b16394ead34a424eecf8a31a1f5137094cabe84a1bcb10fa6ba39d3d31"},
- {file = "pydantic_core-2.27.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:8e10c99ef58cfdf2a66fc15d66b16c4a04f62bca39db589ae8cba08bc55331bc"},
- {file = "pydantic_core-2.27.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:26f32e0adf166a84d0cb63be85c562ca8a6fa8de28e5f0d92250c6b7e9e2aff7"},
- {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c19d1ea0673cd13cc2f872f6c9ab42acc4e4f492a7ca9d3795ce2b112dd7e15"},
- {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5e68c4446fe0810e959cdff46ab0a41ce2f2c86d227d96dc3847af0ba7def306"},
- {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d9640b0059ff4f14d1f37321b94061c6db164fbe49b334b31643e0528d100d99"},
- {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:40d02e7d45c9f8af700f3452f329ead92da4c5f4317ca9b896de7ce7199ea459"},
- {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1c1fd185014191700554795c99b347d64f2bb637966c4cfc16998a0ca700d048"},
- {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d81d2068e1c1228a565af076598f9e7451712700b673de8f502f0334f281387d"},
- {file = "pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:1a4207639fb02ec2dbb76227d7c751a20b1a6b4bc52850568e52260cae64ca3b"},
- {file = "pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:3de3ce3c9ddc8bbd88f6e0e304dea0e66d843ec9de1b0042b0911c1663ffd474"},
- {file = "pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:30c5f68ded0c36466acede341551106821043e9afaad516adfb6e8fa80a4e6a6"},
- {file = "pydantic_core-2.27.2-cp311-cp311-win32.whl", hash = "sha256:c70c26d2c99f78b125a3459f8afe1aed4d9687c24fd677c6a4436bc042e50d6c"},
- {file = "pydantic_core-2.27.2-cp311-cp311-win_amd64.whl", hash = "sha256:08e125dbdc505fa69ca7d9c499639ab6407cfa909214d500897d02afb816e7cc"},
- {file = "pydantic_core-2.27.2-cp311-cp311-win_arm64.whl", hash = "sha256:26f0d68d4b235a2bae0c3fc585c585b4ecc51382db0e3ba402a22cbc440915e4"},
- {file = "pydantic_core-2.27.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:9e0c8cfefa0ef83b4da9588448b6d8d2a2bf1a53c3f1ae5fca39eb3061e2f0b0"},
- {file = "pydantic_core-2.27.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:83097677b8e3bd7eaa6775720ec8e0405f1575015a463285a92bfdfe254529ef"},
- {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:172fce187655fece0c90d90a678424b013f8fbb0ca8b036ac266749c09438cb7"},
- {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:519f29f5213271eeeeb3093f662ba2fd512b91c5f188f3bb7b27bc5973816934"},
- {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:05e3a55d124407fffba0dd6b0c0cd056d10e983ceb4e5dbd10dda135c31071d6"},
- {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9c3ed807c7b91de05e63930188f19e921d1fe90de6b4f5cd43ee7fcc3525cb8c"},
- {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6fb4aadc0b9a0c063206846d603b92030eb6f03069151a625667f982887153e2"},
- {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:28ccb213807e037460326424ceb8b5245acb88f32f3d2777427476e1b32c48c4"},
- {file = "pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:de3cd1899e2c279b140adde9357c4495ed9d47131b4a4eaff9052f23398076b3"},
- {file = "pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:220f892729375e2d736b97d0e51466252ad84c51857d4d15f5e9692f9ef12be4"},
- {file = "pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a0fcd29cd6b4e74fe8ddd2c90330fd8edf2e30cb52acda47f06dd615ae72da57"},
- {file = "pydantic_core-2.27.2-cp312-cp312-win32.whl", hash = "sha256:1e2cb691ed9834cd6a8be61228471d0a503731abfb42f82458ff27be7b2186fc"},
- {file = "pydantic_core-2.27.2-cp312-cp312-win_amd64.whl", hash = "sha256:cc3f1a99a4f4f9dd1de4fe0312c114e740b5ddead65bb4102884b384c15d8bc9"},
- {file = "pydantic_core-2.27.2-cp312-cp312-win_arm64.whl", hash = "sha256:3911ac9284cd8a1792d3cb26a2da18f3ca26c6908cc434a18f730dc0db7bfa3b"},
- {file = "pydantic_core-2.27.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:7d14bd329640e63852364c306f4d23eb744e0f8193148d4044dd3dacdaacbd8b"},
- {file = "pydantic_core-2.27.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:82f91663004eb8ed30ff478d77c4d1179b3563df6cdb15c0817cd1cdaf34d154"},
- {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:71b24c7d61131bb83df10cc7e687433609963a944ccf45190cfc21e0887b08c9"},
- {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fa8e459d4954f608fa26116118bb67f56b93b209c39b008277ace29937453dc9"},
- {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ce8918cbebc8da707ba805b7fd0b382816858728ae7fe19a942080c24e5b7cd1"},
- {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eda3f5c2a021bbc5d976107bb302e0131351c2ba54343f8a496dc8783d3d3a6a"},
- {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bd8086fa684c4775c27f03f062cbb9eaa6e17f064307e86b21b9e0abc9c0f02e"},
- {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8d9b3388db186ba0c099a6d20f0604a44eabdeef1777ddd94786cdae158729e4"},
- {file = "pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:7a66efda2387de898c8f38c0cf7f14fca0b51a8ef0b24bfea5849f1b3c95af27"},
- {file = "pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:18a101c168e4e092ab40dbc2503bdc0f62010e95d292b27827871dc85450d7ee"},
- {file = "pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ba5dd002f88b78a4215ed2f8ddbdf85e8513382820ba15ad5ad8955ce0ca19a1"},
- {file = "pydantic_core-2.27.2-cp313-cp313-win32.whl", hash = "sha256:1ebaf1d0481914d004a573394f4be3a7616334be70261007e47c2a6fe7e50130"},
- {file = "pydantic_core-2.27.2-cp313-cp313-win_amd64.whl", hash = "sha256:953101387ecf2f5652883208769a79e48db18c6df442568a0b5ccd8c2723abee"},
- {file = "pydantic_core-2.27.2-cp313-cp313-win_arm64.whl", hash = "sha256:ac4dbfd1691affb8f48c2c13241a2e3b60ff23247cbcf981759c768b6633cf8b"},
- {file = "pydantic_core-2.27.2-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:d3e8d504bdd3f10835468f29008d72fc8359d95c9c415ce6e767203db6127506"},
- {file = "pydantic_core-2.27.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:521eb9b7f036c9b6187f0b47318ab0d7ca14bd87f776240b90b21c1f4f149320"},
- {file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:85210c4d99a0114f5a9481b44560d7d1e35e32cc5634c656bc48e590b669b145"},
- {file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d716e2e30c6f140d7560ef1538953a5cd1a87264c737643d481f2779fc247fe1"},
- {file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f66d89ba397d92f840f8654756196d93804278457b5fbede59598a1f9f90b228"},
- {file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:669e193c1c576a58f132e3158f9dfa9662969edb1a250c54d8fa52590045f046"},
- {file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fdbe7629b996647b99c01b37f11170a57ae675375b14b8c13b8518b8320ced5"},
- {file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d262606bf386a5ba0b0af3b97f37c83d7011439e3dc1a9298f21efb292e42f1a"},
- {file = "pydantic_core-2.27.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:cabb9bcb7e0d97f74df8646f34fc76fbf793b7f6dc2438517d7a9e50eee4f14d"},
- {file = "pydantic_core-2.27.2-cp38-cp38-musllinux_1_1_armv7l.whl", hash = "sha256:d2d63f1215638d28221f664596b1ccb3944f6e25dd18cd3b86b0a4c408d5ebb9"},
- {file = "pydantic_core-2.27.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:bca101c00bff0adb45a833f8451b9105d9df18accb8743b08107d7ada14bd7da"},
- {file = "pydantic_core-2.27.2-cp38-cp38-win32.whl", hash = "sha256:f6f8e111843bbb0dee4cb6594cdc73e79b3329b526037ec242a3e49012495b3b"},
- {file = "pydantic_core-2.27.2-cp38-cp38-win_amd64.whl", hash = "sha256:fd1aea04935a508f62e0d0ef1f5ae968774a32afc306fb8545e06f5ff5cdf3ad"},
- {file = "pydantic_core-2.27.2-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:c10eb4f1659290b523af58fa7cffb452a61ad6ae5613404519aee4bfbf1df993"},
- {file = "pydantic_core-2.27.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ef592d4bad47296fb11f96cd7dc898b92e795032b4894dfb4076cfccd43a9308"},
- {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c61709a844acc6bf0b7dce7daae75195a10aac96a596ea1b776996414791ede4"},
- {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:42c5f762659e47fdb7b16956c71598292f60a03aa92f8b6351504359dbdba6cf"},
- {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4c9775e339e42e79ec99c441d9730fccf07414af63eac2f0e48e08fd38a64d76"},
- {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:57762139821c31847cfb2df63c12f725788bd9f04bc2fb392790959b8f70f118"},
- {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0d1e85068e818c73e048fe28cfc769040bb1f475524f4745a5dc621f75ac7630"},
- {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:097830ed52fd9e427942ff3b9bc17fab52913b2f50f2880dc4a5611446606a54"},
- {file = "pydantic_core-2.27.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:044a50963a614ecfae59bb1eaf7ea7efc4bc62f49ed594e18fa1e5d953c40e9f"},
- {file = "pydantic_core-2.27.2-cp39-cp39-musllinux_1_1_armv7l.whl", hash = "sha256:4e0b4220ba5b40d727c7f879eac379b822eee5d8fff418e9d3381ee45b3b0362"},
- {file = "pydantic_core-2.27.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5e4f4bb20d75e9325cc9696c6802657b58bc1dbbe3022f32cc2b2b632c3fbb96"},
- {file = "pydantic_core-2.27.2-cp39-cp39-win32.whl", hash = "sha256:cca63613e90d001b9f2f9a9ceb276c308bfa2a43fafb75c8031c4f66039e8c6e"},
- {file = "pydantic_core-2.27.2-cp39-cp39-win_amd64.whl", hash = "sha256:77d1bca19b0f7021b3a982e6f903dcd5b2b06076def36a652e3907f596e29f67"},
- {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:2bf14caea37e91198329b828eae1618c068dfb8ef17bb33287a7ad4b61ac314e"},
- {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:b0cb791f5b45307caae8810c2023a184c74605ec3bcbb67d13846c28ff731ff8"},
- {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:688d3fd9fcb71f41c4c015c023d12a79d1c4c0732ec9eb35d96e3388a120dcf3"},
- {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d591580c34f4d731592f0e9fe40f9cc1b430d297eecc70b962e93c5c668f15f"},
- {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:82f986faf4e644ffc189a7f1aafc86e46ef70372bb153e7001e8afccc6e54133"},
- {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:bec317a27290e2537f922639cafd54990551725fc844249e64c523301d0822fc"},
- {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:0296abcb83a797db256b773f45773da397da75a08f5fcaef41f2044adec05f50"},
- {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:0d75070718e369e452075a6017fbf187f788e17ed67a3abd47fa934d001863d9"},
- {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:7e17b560be3c98a8e3aa66ce828bdebb9e9ac6ad5466fba92eb74c4c95cb1151"},
- {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:c33939a82924da9ed65dab5a65d427205a73181d8098e79b6b426bdf8ad4e656"},
- {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:00bad2484fa6bda1e216e7345a798bd37c68fb2d97558edd584942aa41b7d278"},
- {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c817e2b40aba42bac6f457498dacabc568c3b7a986fc9ba7c8d9d260b71485fb"},
- {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:251136cdad0cb722e93732cb45ca5299fb56e1344a833640bf93b2803f8d1bfd"},
- {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d2088237af596f0a524d3afc39ab3b036e8adb054ee57cbb1dcf8e09da5b29cc"},
- {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:d4041c0b966a84b4ae7a09832eb691a35aec90910cd2dbe7a208de59be77965b"},
- {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:8083d4e875ebe0b864ffef72a4304827015cff328a1be6e22cc850753bfb122b"},
- {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f141ee28a0ad2123b6611b6ceff018039df17f32ada8b534e6aa039545a3efb2"},
- {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7d0c8399fcc1848491f00e0314bd59fb34a9c008761bcb422a057670c3f65e35"},
- {file = "pydantic_core-2.27.2.tar.gz", hash = "sha256:eb026e5a4c1fee05726072337ff51d1efb6f59090b7da90d30ea58625b1ffb39"},
+ {file = "pydantic_core-2.33.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:3077cfdb6125cc8dab61b155fdd714663e401f0e6883f9632118ec12cf42df26"},
+ {file = "pydantic_core-2.33.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8ffab8b2908d152e74862d276cf5017c81a2f3719f14e8e3e8d6b83fda863927"},
+ {file = "pydantic_core-2.33.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5183e4f6a2d468787243ebcd70cf4098c247e60d73fb7d68d5bc1e1beaa0c4db"},
+ {file = "pydantic_core-2.33.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:398a38d323f37714023be1e0285765f0a27243a8b1506b7b7de87b647b517e48"},
+ {file = "pydantic_core-2.33.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:87d3776f0001b43acebfa86f8c64019c043b55cc5a6a2e313d728b5c95b46969"},
+ {file = "pydantic_core-2.33.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c566dd9c5f63d22226409553531f89de0cac55397f2ab8d97d6f06cfce6d947e"},
+ {file = "pydantic_core-2.33.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a0d5f3acc81452c56895e90643a625302bd6be351e7010664151cc55b7b97f89"},
+ {file = "pydantic_core-2.33.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d3a07fadec2a13274a8d861d3d37c61e97a816beae717efccaa4b36dfcaadcde"},
+ {file = "pydantic_core-2.33.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:f99aeda58dce827f76963ee87a0ebe75e648c72ff9ba1174a253f6744f518f65"},
+ {file = "pydantic_core-2.33.1-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:902dbc832141aa0ec374f4310f1e4e7febeebc3256f00dc359a9ac3f264a45dc"},
+ {file = "pydantic_core-2.33.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fe44d56aa0b00d66640aa84a3cbe80b7a3ccdc6f0b1ca71090696a6d4777c091"},
+ {file = "pydantic_core-2.33.1-cp310-cp310-win32.whl", hash = "sha256:ed3eb16d51257c763539bde21e011092f127a2202692afaeaccb50db55a31383"},
+ {file = "pydantic_core-2.33.1-cp310-cp310-win_amd64.whl", hash = "sha256:694ad99a7f6718c1a498dc170ca430687a39894a60327f548e02a9c7ee4b6504"},
+ {file = "pydantic_core-2.33.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:6e966fc3caaf9f1d96b349b0341c70c8d6573bf1bac7261f7b0ba88f96c56c24"},
+ {file = "pydantic_core-2.33.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bfd0adeee563d59c598ceabddf2c92eec77abcb3f4a391b19aa7366170bd9e30"},
+ {file = "pydantic_core-2.33.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:91815221101ad3c6b507804178a7bb5cb7b2ead9ecd600041669c8d805ebd595"},
+ {file = "pydantic_core-2.33.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9fea9c1869bb4742d174a57b4700c6dadea951df8b06de40c2fedb4f02931c2e"},
+ {file = "pydantic_core-2.33.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d20eb4861329bb2484c021b9d9a977566ab16d84000a57e28061151c62b349a"},
+ {file = "pydantic_core-2.33.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0fb935c5591573ae3201640579f30128ccc10739b45663f93c06796854405505"},
+ {file = "pydantic_core-2.33.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c964fd24e6166420d18fb53996d8c9fd6eac9bf5ae3ec3d03015be4414ce497f"},
+ {file = "pydantic_core-2.33.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:681d65e9011f7392db5aa002b7423cc442d6a673c635668c227c6c8d0e5a4f77"},
+ {file = "pydantic_core-2.33.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e100c52f7355a48413e2999bfb4e139d2977a904495441b374f3d4fb4a170961"},
+ {file = "pydantic_core-2.33.1-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:048831bd363490be79acdd3232f74a0e9951b11b2b4cc058aeb72b22fdc3abe1"},
+ {file = "pydantic_core-2.33.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:bdc84017d28459c00db6f918a7272a5190bec3090058334e43a76afb279eac7c"},
+ {file = "pydantic_core-2.33.1-cp311-cp311-win32.whl", hash = "sha256:32cd11c5914d1179df70406427097c7dcde19fddf1418c787540f4b730289896"},
+ {file = "pydantic_core-2.33.1-cp311-cp311-win_amd64.whl", hash = "sha256:2ea62419ba8c397e7da28a9170a16219d310d2cf4970dbc65c32faf20d828c83"},
+ {file = "pydantic_core-2.33.1-cp311-cp311-win_arm64.whl", hash = "sha256:fc903512177361e868bc1f5b80ac8c8a6e05fcdd574a5fb5ffeac5a9982b9e89"},
+ {file = "pydantic_core-2.33.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:1293d7febb995e9d3ec3ea09caf1a26214eec45b0f29f6074abb004723fc1de8"},
+ {file = "pydantic_core-2.33.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:99b56acd433386c8f20be5c4000786d1e7ca0523c8eefc995d14d79c7a081498"},
+ {file = "pydantic_core-2.33.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:35a5ec3fa8c2fe6c53e1b2ccc2454398f95d5393ab398478f53e1afbbeb4d939"},
+ {file = "pydantic_core-2.33.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b172f7b9d2f3abc0efd12e3386f7e48b576ef309544ac3a63e5e9cdd2e24585d"},
+ {file = "pydantic_core-2.33.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9097b9f17f91eea659b9ec58148c0747ec354a42f7389b9d50701610d86f812e"},
+ {file = "pydantic_core-2.33.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cc77ec5b7e2118b152b0d886c7514a4653bcb58c6b1d760134a9fab915f777b3"},
+ {file = "pydantic_core-2.33.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d5e3d15245b08fa4a84cefc6c9222e6f37c98111c8679fbd94aa145f9a0ae23d"},
+ {file = "pydantic_core-2.33.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ef99779001d7ac2e2461d8ab55d3373fe7315caefdbecd8ced75304ae5a6fc6b"},
+ {file = "pydantic_core-2.33.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:fc6bf8869e193855e8d91d91f6bf59699a5cdfaa47a404e278e776dd7f168b39"},
+ {file = "pydantic_core-2.33.1-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:b1caa0bc2741b043db7823843e1bde8aaa58a55a58fda06083b0569f8b45693a"},
+ {file = "pydantic_core-2.33.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:ec259f62538e8bf364903a7d0d0239447059f9434b284f5536e8402b7dd198db"},
+ {file = "pydantic_core-2.33.1-cp312-cp312-win32.whl", hash = "sha256:e14f369c98a7c15772b9da98987f58e2b509a93235582838bd0d1d8c08b68fda"},
+ {file = "pydantic_core-2.33.1-cp312-cp312-win_amd64.whl", hash = "sha256:1c607801d85e2e123357b3893f82c97a42856192997b95b4d8325deb1cd0c5f4"},
+ {file = "pydantic_core-2.33.1-cp312-cp312-win_arm64.whl", hash = "sha256:8d13f0276806ee722e70a1c93da19748594f19ac4299c7e41237fc791d1861ea"},
+ {file = "pydantic_core-2.33.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:70af6a21237b53d1fe7b9325b20e65cbf2f0a848cf77bed492b029139701e66a"},
+ {file = "pydantic_core-2.33.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:282b3fe1bbbe5ae35224a0dbd05aed9ccabccd241e8e6b60370484234b456266"},
+ {file = "pydantic_core-2.33.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4b315e596282bbb5822d0c7ee9d255595bd7506d1cb20c2911a4da0b970187d3"},
+ {file = "pydantic_core-2.33.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1dfae24cf9921875ca0ca6a8ecb4bb2f13c855794ed0d468d6abbec6e6dcd44a"},
+ {file = "pydantic_core-2.33.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6dd8ecfde08d8bfadaea669e83c63939af76f4cf5538a72597016edfa3fad516"},
+ {file = "pydantic_core-2.33.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2f593494876eae852dc98c43c6f260f45abdbfeec9e4324e31a481d948214764"},
+ {file = "pydantic_core-2.33.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:948b73114f47fd7016088e5186d13faf5e1b2fe83f5e320e371f035557fd264d"},
+ {file = "pydantic_core-2.33.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e11f3864eb516af21b01e25fac915a82e9ddad3bb0fb9e95a246067398b435a4"},
+ {file = "pydantic_core-2.33.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:549150be302428b56fdad0c23c2741dcdb5572413776826c965619a25d9c6bde"},
+ {file = "pydantic_core-2.33.1-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:495bc156026efafd9ef2d82372bd38afce78ddd82bf28ef5276c469e57c0c83e"},
+ {file = "pydantic_core-2.33.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ec79de2a8680b1a67a07490bddf9636d5c2fab609ba8c57597e855fa5fa4dacd"},
+ {file = "pydantic_core-2.33.1-cp313-cp313-win32.whl", hash = "sha256:ee12a7be1742f81b8a65b36c6921022301d466b82d80315d215c4c691724986f"},
+ {file = "pydantic_core-2.33.1-cp313-cp313-win_amd64.whl", hash = "sha256:ede9b407e39949d2afc46385ce6bd6e11588660c26f80576c11c958e6647bc40"},
+ {file = "pydantic_core-2.33.1-cp313-cp313-win_arm64.whl", hash = "sha256:aa687a23d4b7871a00e03ca96a09cad0f28f443690d300500603bd0adba4b523"},
+ {file = "pydantic_core-2.33.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:401d7b76e1000d0dd5538e6381d28febdcacb097c8d340dde7d7fc6e13e9f95d"},
+ {file = "pydantic_core-2.33.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7aeb055a42d734c0255c9e489ac67e75397d59c6fbe60d155851e9782f276a9c"},
+ {file = "pydantic_core-2.33.1-cp313-cp313t-win_amd64.whl", hash = "sha256:338ea9b73e6e109f15ab439e62cb3b78aa752c7fd9536794112e14bee02c8d18"},
+ {file = "pydantic_core-2.33.1-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:5ab77f45d33d264de66e1884fca158bc920cb5e27fd0764a72f72f5756ae8bdb"},
+ {file = "pydantic_core-2.33.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e7aaba1b4b03aaea7bb59e1b5856d734be011d3e6d98f5bcaa98cb30f375f2ad"},
+ {file = "pydantic_core-2.33.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7fb66263e9ba8fea2aa85e1e5578980d127fb37d7f2e292773e7bc3a38fb0c7b"},
+ {file = "pydantic_core-2.33.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3f2648b9262607a7fb41d782cc263b48032ff7a03a835581abbf7a3bec62bcf5"},
+ {file = "pydantic_core-2.33.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:723c5630c4259400818b4ad096735a829074601805d07f8cafc366d95786d331"},
+ {file = "pydantic_core-2.33.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d100e3ae783d2167782391e0c1c7a20a31f55f8015f3293647544df3f9c67824"},
+ {file = "pydantic_core-2.33.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:177d50460bc976a0369920b6c744d927b0ecb8606fb56858ff542560251b19e5"},
+ {file = "pydantic_core-2.33.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a3edde68d1a1f9af1273b2fe798997b33f90308fb6d44d8550c89fc6a3647cf6"},
+ {file = "pydantic_core-2.33.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:a62c3c3ef6a7e2c45f7853b10b5bc4ddefd6ee3cd31024754a1a5842da7d598d"},
+ {file = "pydantic_core-2.33.1-cp39-cp39-musllinux_1_1_armv7l.whl", hash = "sha256:c91dbb0ab683fa0cd64a6e81907c8ff41d6497c346890e26b23de7ee55353f96"},
+ {file = "pydantic_core-2.33.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9f466e8bf0a62dc43e068c12166281c2eca72121dd2adc1040f3aa1e21ef8599"},
+ {file = "pydantic_core-2.33.1-cp39-cp39-win32.whl", hash = "sha256:ab0277cedb698749caada82e5d099dc9fed3f906a30d4c382d1a21725777a1e5"},
+ {file = "pydantic_core-2.33.1-cp39-cp39-win_amd64.whl", hash = "sha256:5773da0ee2d17136b1f1c6fbde543398d452a6ad2a7b54ea1033e2daa739b8d2"},
+ {file = "pydantic_core-2.33.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5c834f54f8f4640fd7e4b193f80eb25a0602bba9e19b3cd2fc7ffe8199f5ae02"},
+ {file = "pydantic_core-2.33.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:049e0de24cf23766f12cc5cc71d8abc07d4a9deb9061b334b62093dedc7cb068"},
+ {file = "pydantic_core-2.33.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a28239037b3d6f16916a4c831a5a0eadf856bdd6d2e92c10a0da3a59eadcf3e"},
+ {file = "pydantic_core-2.33.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9d3da303ab5f378a268fa7d45f37d7d85c3ec19769f28d2cc0c61826a8de21fe"},
+ {file = "pydantic_core-2.33.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:25626fb37b3c543818c14821afe0fd3830bc327a43953bc88db924b68c5723f1"},
+ {file = "pydantic_core-2.33.1-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:3ab2d36e20fbfcce8f02d73c33a8a7362980cff717926bbae030b93ae46b56c7"},
+ {file = "pydantic_core-2.33.1-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:2f9284e11c751b003fd4215ad92d325d92c9cb19ee6729ebd87e3250072cdcde"},
+ {file = "pydantic_core-2.33.1-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:048c01eee07d37cbd066fc512b9d8b5ea88ceeb4e629ab94b3e56965ad655add"},
+ {file = "pydantic_core-2.33.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:5ccd429694cf26af7997595d627dd2637e7932214486f55b8a357edaac9dae8c"},
+ {file = "pydantic_core-2.33.1-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:3a371dc00282c4b84246509a5ddc808e61b9864aa1eae9ecc92bb1268b82db4a"},
+ {file = "pydantic_core-2.33.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:f59295ecc75a1788af8ba92f2e8c6eeaa5a94c22fc4d151e8d9638814f85c8fc"},
+ {file = "pydantic_core-2.33.1-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:08530b8ac922003033f399128505f513e30ca770527cc8bbacf75a84fcc2c74b"},
+ {file = "pydantic_core-2.33.1-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bae370459da6a5466978c0eacf90690cb57ec9d533f8e63e564ef3822bfa04fe"},
+ {file = "pydantic_core-2.33.1-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e3de2777e3b9f4d603112f78006f4ae0acb936e95f06da6cb1a45fbad6bdb4b5"},
+ {file = "pydantic_core-2.33.1-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:3a64e81e8cba118e108d7126362ea30e021291b7805d47e4896e52c791be2761"},
+ {file = "pydantic_core-2.33.1-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:52928d8c1b6bda03cc6d811e8923dffc87a2d3c8b3bfd2ce16471c7147a24850"},
+ {file = "pydantic_core-2.33.1-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:1b30d92c9412beb5ac6b10a3eb7ef92ccb14e3f2a8d7732e2d739f58b3aa7544"},
+ {file = "pydantic_core-2.33.1-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:f995719707e0e29f0f41a8aa3bcea6e761a36c9136104d3189eafb83f5cec5e5"},
+ {file = "pydantic_core-2.33.1-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:7edbc454a29fc6aeae1e1eecba4f07b63b8d76e76a748532233c4c167b4cb9ea"},
+ {file = "pydantic_core-2.33.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:ad05b683963f69a1d5d2c2bdab1274a31221ca737dbbceaa32bcb67359453cdd"},
+ {file = "pydantic_core-2.33.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:df6a94bf9452c6da9b5d76ed229a5683d0306ccb91cca8e1eea883189780d568"},
+ {file = "pydantic_core-2.33.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7965c13b3967909a09ecc91f21d09cfc4576bf78140b988904e94f130f188396"},
+ {file = "pydantic_core-2.33.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3f1fdb790440a34f6ecf7679e1863b825cb5ffde858a9197f851168ed08371e5"},
+ {file = "pydantic_core-2.33.1-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:5277aec8d879f8d05168fdd17ae811dd313b8ff894aeeaf7cd34ad28b4d77e33"},
+ {file = "pydantic_core-2.33.1-pp39-pypy39_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:8ab581d3530611897d863d1a649fb0644b860286b4718db919bfd51ece41f10b"},
+ {file = "pydantic_core-2.33.1-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:0483847fa9ad5e3412265c1bd72aad35235512d9ce9d27d81a56d935ef489672"},
+ {file = "pydantic_core-2.33.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:de9e06abe3cc5ec6a2d5f75bc99b0bdca4f5c719a5b34026f8c57efbdecd2ee3"},
+ {file = "pydantic_core-2.33.1.tar.gz", hash = "sha256:bcc9c6fdb0ced789245b02b7d6603e17d1563064ddcfc36f046b61c0c05dd9df"},
]
markers = {main = "extra == \"all\" or extra == \"parser\""}
@@ -3348,23 +3301,26 @@ typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0"
[[package]]
name = "pydantic-settings"
-version = "2.8.1"
+version = "2.9.1"
description = "Settings management using Pydantic"
optional = true
-python-versions = ">=3.8"
+python-versions = ">=3.9"
groups = ["main"]
markers = "extra == \"all\""
files = [
- {file = "pydantic_settings-2.8.1-py3-none-any.whl", hash = "sha256:81942d5ac3d905f7f3ee1a70df5dfb62d5569c12f51a5a647defc1c3d9ee2e9c"},
- {file = "pydantic_settings-2.8.1.tar.gz", hash = "sha256:d5c663dfbe9db9d5e1c646b2e161da12f0d734d422ee56f567d0ea2cee4e8585"},
+ {file = "pydantic_settings-2.9.1-py3-none-any.whl", hash = "sha256:59b4f431b1defb26fe620c71a7d3968a710d719f5f4cdbbdb7926edeb770f6ef"},
+ {file = "pydantic_settings-2.9.1.tar.gz", hash = "sha256:c509bf79d27563add44e8446233359004ed85066cd096d8b510f715e6ef5d268"},
]
[package.dependencies]
pydantic = ">=2.7.0"
python-dotenv = ">=0.21.0"
+typing-inspection = ">=0.4.0"
[package.extras]
+aws-secrets-manager = ["boto3 (>=1.35.0)", "boto3-stubs[secretsmanager]"]
azure-key-vault = ["azure-identity (>=1.16.0)", "azure-keyvault-secrets (>=4.8.0)"]
+gcp-secret-manager = ["google-cloud-secret-manager (>=2.23.1)"]
toml = ["tomli (>=2.0.1)"]
yaml = ["pyyaml (>=6.0.1)"]
@@ -3483,14 +3439,14 @@ histogram = ["pygal", "pygaljs", "setuptools"]
[[package]]
name = "pytest-cov"
-version = "6.0.0"
+version = "6.1.1"
description = "Pytest plugin for measuring coverage."
optional = false
python-versions = ">=3.9"
groups = ["dev"]
files = [
- {file = "pytest-cov-6.0.0.tar.gz", hash = "sha256:fde0b595ca248bb8e2d76f020b465f3b107c9632e6a1d1705f17834c89dcadc0"},
- {file = "pytest_cov-6.0.0-py3-none-any.whl", hash = "sha256:eee6f1b9e61008bd34975a4d5bab25801eb31898b032dd55addc93e96fcaaa35"},
+ {file = "pytest_cov-6.1.1-py3-none-any.whl", hash = "sha256:bddf29ed2d0ab6f4df17b4c55b0a657287db8684af9c42ea546b21b1041b3dde"},
+ {file = "pytest_cov-6.1.1.tar.gz", hash = "sha256:46935f7aaefba760e716c2ebfbe1c216240b9592966e7da99ea8292d4d3e2a0a"},
]
[package.dependencies]
@@ -4068,30 +4024,30 @@ files = [
[[package]]
name = "ruff"
-version = "0.11.2"
+version = "0.11.6"
description = "An extremely fast Python linter and code formatter, written in Rust."
optional = false
python-versions = ">=3.7"
groups = ["dev"]
files = [
- {file = "ruff-0.11.2-py3-none-linux_armv6l.whl", hash = "sha256:c69e20ea49e973f3afec2c06376eb56045709f0212615c1adb0eda35e8a4e477"},
- {file = "ruff-0.11.2-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:2c5424cc1c4eb1d8ecabe6d4f1b70470b4f24a0c0171356290b1953ad8f0e272"},
- {file = "ruff-0.11.2-py3-none-macosx_11_0_arm64.whl", hash = "sha256:ecf20854cc73f42171eedb66f006a43d0a21bfb98a2523a809931cda569552d9"},
- {file = "ruff-0.11.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0c543bf65d5d27240321604cee0633a70c6c25c9a2f2492efa9f6d4b8e4199bb"},
- {file = "ruff-0.11.2-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:20967168cc21195db5830b9224be0e964cc9c8ecf3b5a9e3ce19876e8d3a96e3"},
- {file = "ruff-0.11.2-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:955a9ce63483999d9f0b8f0b4a3ad669e53484232853054cc8b9d51ab4c5de74"},
- {file = "ruff-0.11.2-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:86b3a27c38b8fce73bcd262b0de32e9a6801b76d52cdb3ae4c914515f0cef608"},
- {file = "ruff-0.11.2-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a3b66a03b248c9fcd9d64d445bafdf1589326bee6fc5c8e92d7562e58883e30f"},
- {file = "ruff-0.11.2-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0397c2672db015be5aa3d4dac54c69aa012429097ff219392c018e21f5085147"},
- {file = "ruff-0.11.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:869bcf3f9abf6457fbe39b5a37333aa4eecc52a3b99c98827ccc371a8e5b6f1b"},
- {file = "ruff-0.11.2-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:2a2b50ca35457ba785cd8c93ebbe529467594087b527a08d487cf0ee7b3087e9"},
- {file = "ruff-0.11.2-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:7c69c74bf53ddcfbc22e6eb2f31211df7f65054bfc1f72288fc71e5f82db3eab"},
- {file = "ruff-0.11.2-py3-none-musllinux_1_2_i686.whl", hash = "sha256:6e8fb75e14560f7cf53b15bbc55baf5ecbe373dd5f3aab96ff7aa7777edd7630"},
- {file = "ruff-0.11.2-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:842a472d7b4d6f5924e9297aa38149e5dcb1e628773b70e6387ae2c97a63c58f"},
- {file = "ruff-0.11.2-py3-none-win32.whl", hash = "sha256:aca01ccd0eb5eb7156b324cfaa088586f06a86d9e5314b0eb330cb48415097cc"},
- {file = "ruff-0.11.2-py3-none-win_amd64.whl", hash = "sha256:3170150172a8f994136c0c66f494edf199a0bbea7a409f649e4bc8f4d7084080"},
- {file = "ruff-0.11.2-py3-none-win_arm64.whl", hash = "sha256:52933095158ff328f4c77af3d74f0379e34fd52f175144cefc1b192e7ccd32b4"},
- {file = "ruff-0.11.2.tar.gz", hash = "sha256:ec47591497d5a1050175bdf4e1a4e6272cddff7da88a2ad595e1e326041d8d94"},
+ {file = "ruff-0.11.6-py3-none-linux_armv6l.whl", hash = "sha256:d84dcbe74cf9356d1bdb4a78cf74fd47c740bf7bdeb7529068f69b08272239a1"},
+ {file = "ruff-0.11.6-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:9bc583628e1096148011a5d51ff3c836f51899e61112e03e5f2b1573a9b726de"},
+ {file = "ruff-0.11.6-py3-none-macosx_11_0_arm64.whl", hash = "sha256:f2959049faeb5ba5e3b378709e9d1bf0cab06528b306b9dd6ebd2a312127964a"},
+ {file = "ruff-0.11.6-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63c5d4e30d9d0de7fedbfb3e9e20d134b73a30c1e74b596f40f0629d5c28a193"},
+ {file = "ruff-0.11.6-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:26a4b9a4e1439f7d0a091c6763a100cef8fbdc10d68593df6f3cfa5abdd9246e"},
+ {file = "ruff-0.11.6-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b5edf270223dd622218256569636dc3e708c2cb989242262fe378609eccf1308"},
+ {file = "ruff-0.11.6-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:f55844e818206a9dd31ff27f91385afb538067e2dc0beb05f82c293ab84f7d55"},
+ {file = "ruff-0.11.6-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d8f782286c5ff562e4e00344f954b9320026d8e3fae2ba9e6948443fafd9ffc"},
+ {file = "ruff-0.11.6-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:01c63ba219514271cee955cd0adc26a4083df1956d57847978383b0e50ffd7d2"},
+ {file = "ruff-0.11.6-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15adac20ef2ca296dd3d8e2bedc6202ea6de81c091a74661c3666e5c4c223ff6"},
+ {file = "ruff-0.11.6-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:4dd6b09e98144ad7aec026f5588e493c65057d1b387dd937d7787baa531d9bc2"},
+ {file = "ruff-0.11.6-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:45b2e1d6c0eed89c248d024ea95074d0e09988d8e7b1dad8d3ab9a67017a5b03"},
+ {file = "ruff-0.11.6-py3-none-musllinux_1_2_i686.whl", hash = "sha256:bd40de4115b2ec4850302f1a1d8067f42e70b4990b68838ccb9ccd9f110c5e8b"},
+ {file = "ruff-0.11.6-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:77cda2dfbac1ab73aef5e514c4cbfc4ec1fbef4b84a44c736cc26f61b3814cd9"},
+ {file = "ruff-0.11.6-py3-none-win32.whl", hash = "sha256:5151a871554be3036cd6e51d0ec6eef56334d74dfe1702de717a995ee3d5b287"},
+ {file = "ruff-0.11.6-py3-none-win_amd64.whl", hash = "sha256:cce85721d09c51f3b782c331b0abd07e9d7d5f775840379c640606d3159cae0e"},
+ {file = "ruff-0.11.6-py3-none-win_arm64.whl", hash = "sha256:3567ba0d07fb170b1b48d944715e3294b77f5b7679e8ba258199a250383ccb79"},
+ {file = "ruff-0.11.6.tar.gz", hash = "sha256:bec8bcc3ac228a45ccc811e45f7eb61b950dbf4cf31a67fa89352574b01c7d79"},
]
[[package]]
@@ -4130,14 +4086,14 @@ pathspec = ">=0.10.1"
[[package]]
name = "sentry-sdk"
-version = "2.24.1"
+version = "2.26.1"
description = "Python client for Sentry (https://sentry.io)"
optional = false
python-versions = ">=3.6"
groups = ["dev"]
files = [
- {file = "sentry_sdk-2.24.1-py2.py3-none-any.whl", hash = "sha256:36baa6a1128b9d98d2adc5e9b2f887eff0a6af558fc2b96ed51919042413556d"},
- {file = "sentry_sdk-2.24.1.tar.gz", hash = "sha256:8ba3c29990fa48865b908b3b9dc5ae7fa7e72407c7c9e91303e5206b32d7b8b1"},
+ {file = "sentry_sdk-2.26.1-py2.py3-none-any.whl", hash = "sha256:e99390e3f217d13ddcbaeaed08789f1ca614d663b345b9da42e35ad6b60d696a"},
+ {file = "sentry_sdk-2.26.1.tar.gz", hash = "sha256:759e019c41551a21519a95e6cef6d91fb4af1054761923dadaee2e6eca9c02c7"},
]
[package.dependencies]
@@ -4277,14 +4233,14 @@ dev = ["hypothesis (>=6.70.0)", "pytest (>=7.1.0)"]
[[package]]
name = "testcontainers"
-version = "4.9.2"
+version = "4.10.0"
description = "Python library for throwaway instances of anything that can run in a Docker container"
optional = false
python-versions = "<4.0,>=3.9"
groups = ["dev"]
files = [
- {file = "testcontainers-4.9.2-py3-none-any.whl", hash = "sha256:36bd2b58d91f2fc7ac4f4a73c6ec00e5e60c259c10f208dbfe3161029889be92"},
- {file = "testcontainers-4.9.2.tar.gz", hash = "sha256:348c72d369d0bd52b57ab4f07a965ae9562837098c28f0522b969b064b779f10"},
+ {file = "testcontainers-4.10.0-py3-none-any.whl", hash = "sha256:31ed1a81238c7e131a2a29df6db8f23717d892b592fa5a1977fd0dcd0c23fc23"},
+ {file = "testcontainers-4.10.0.tar.gz", hash = "sha256:03f85c3e505d8b4edeb192c72a961cebbcba0dd94344ae778b4a159cb6dcf8d3"},
]
[package.dependencies]
@@ -4515,15 +4471,31 @@ files = [
[[package]]
name = "typing-extensions"
-version = "4.12.2"
+version = "4.13.2"
description = "Backported and Experimental Type Hints for Python 3.8+"
optional = false
python-versions = ">=3.8"
groups = ["main", "dev"]
files = [
- {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"},
- {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"},
+ {file = "typing_extensions-4.13.2-py3-none-any.whl", hash = "sha256:a439e7c04b49fec3e5d3e2beaa21755cadbbdc391694e28ccdd36ca4a1408f8c"},
+ {file = "typing_extensions-4.13.2.tar.gz", hash = "sha256:e6c81219bd689f51865d9e372991c540bda33a0379d5573cddb9a3a23f7caaef"},
+]
+
+[[package]]
+name = "typing-inspection"
+version = "0.4.0"
+description = "Runtime typing introspection tools"
+optional = false
+python-versions = ">=3.9"
+groups = ["main", "dev"]
+files = [
+ {file = "typing_inspection-0.4.0-py3-none-any.whl", hash = "sha256:50e72559fcd2a6367a19f7a7e610e6afcb9fac940c650290eed893d61386832f"},
+ {file = "typing_inspection-0.4.0.tar.gz", hash = "sha256:9765c87de36671694a67904bf2c96e395be9c6439bb6c87b5142569dcdd65122"},
]
+markers = {main = "extra == \"all\" or extra == \"parser\""}
+
+[package.dependencies]
+typing-extensions = ">=4.12.0"
[[package]]
name = "ujson"
@@ -4919,4 +4891,4 @@ validation = ["fastjsonschema"]
[metadata]
lock-version = "2.1"
python-versions = ">=3.9,<4.0.0"
-content-hash = "4eb0bd5fbaa1d7a023151e07f520ebbe2a99675fb2225d6ab9f58e2672548617"
+content-hash = "df6834296cbd5f59576dfc96c953124a9ec4d14b94bbeb5182b6a889ad1d92eb"
diff --git a/provenance/3.10.1a0/multiple.intoto.jsonl b/provenance/3.10.1a0/multiple.intoto.jsonl
new file mode 100644
index 00000000000..94145fbca41
--- /dev/null
+++ b/provenance/3.10.1a0/multiple.intoto.jsonl
@@ -0,0 +1 @@
+{"mediaType":"application/vnd.dev.sigstore.bundle.v0.3+json", "verificationMaterial":{"certificate":{"rawBytes":"MIIHZjCCBuygAwIBAgIUXiAxi49rMzykxqevdhh5XJ1s5bIwCgYIKoZIzj0EAwMwNzEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MR4wHAYDVQQDExVzaWdzdG9yZS1pbnRlcm1lZGlhdGUwHhcNMjUwNDA5MDgwNzU4WhcNMjUwNDA5MDgxNzU4WjAAMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEuXREPXLuyhL1V7gTiJlw9XHYo7zEpGEGEKt8julrZaX8GnLbZJRKvR44YV+0QsSf0c96g9+abwabIs3dWX6TbaOCBgswggYHMA4GA1UdDwEB/wQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcDAzAdBgNVHQ4EFgQUunFpQao9oaDV1kew7HaNx7fFCScwHwYDVR0jBBgwFoAU39Ppz1YkEZb5qNjpKFWixi4YZD8wgYQGA1UdEQEB/wR6MHiGdmh0dHBzOi8vZ2l0aHViLmNvbS9zbHNhLWZyYW1ld29yay9zbHNhLWdpdGh1Yi1nZW5lcmF0b3IvLmdpdGh1Yi93b3JrZmxvd3MvZ2VuZXJhdG9yX2dlbmVyaWNfc2xzYTMueW1sQHJlZnMvdGFncy92Mi4xLjAwOQYKKwYBBAGDvzABAQQraHR0cHM6Ly90b2tlbi5hY3Rpb25zLmdpdGh1YnVzZXJjb250ZW50LmNvbTAWBgorBgEEAYO/MAECBAhzY2hlZHVsZTA2BgorBgEEAYO/MAEDBCg5M2I5OWQyODFlNGQyMjhmMGUwYjRhNzU3MmE3ZDhiYjY3YWYzY2I3MBkGCisGAQQBg78wAQQEC1ByZS1SZWxlYXNlMDUGCisGAQQBg78wAQUEJ2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbjAgBgorBgEEAYO/MAEGBBJyZWZzL2hlYWRzL2RldmVsb3AwOwYKKwYBBAGDvzABCAQtDCtodHRwczovL3Rva2VuLmFjdGlvbnMuZ2l0aHVidXNlcmNvbnRlbnQuY29tMIGGBgorBgEEAYO/MAEJBHgMdmh0dHBzOi8vZ2l0aHViLmNvbS9zbHNhLWZyYW1ld29yay9zbHNhLWdpdGh1Yi1nZW5lcmF0b3IvLmdpdGh1Yi93b3JrZmxvd3MvZ2VuZXJhdG9yX2dlbmVyaWNfc2xzYTMueW1sQHJlZnMvdGFncy92Mi4xLjAwOAYKKwYBBAGDvzABCgQqDChmN2RkOGM1NGMyMDY3YmFmYzEyY2E3YTU1NTk1ZDVlZTliNzUyMDRhMB0GCisGAQQBg78wAQsEDwwNZ2l0aHViLWhvc3RlZDBKBgorBgEEAYO/MAEMBDwMOmh0dHBzOi8vZ2l0aHViLmNvbS9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24wOAYKKwYBBAGDvzABDQQqDCg5M2I5OWQyODFlNGQyMjhmMGUwYjRhNzU3MmE3ZDhiYjY3YWYzY2I3MCIGCisGAQQBg78wAQ4EFAwScmVmcy9oZWFkcy9kZXZlbG9wMBkGCisGAQQBg78wAQ8ECwwJMjIxOTE5Mzc5MDEGCisGAQQBg78wARAEIwwhaHR0cHM6Ly9naXRodWIuY29tL2F3cy1wb3dlcnRvb2xzMBkGCisGAQQBg78wAREECwwJMTI5MTI3NjM4MH8GCisGAQQBg78wARIEcQxvaHR0cHM6Ly9naXRodWIuY29tL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi8uZ2l0aHViL3dvcmtmbG93cy9wcmUtcmVsZWFzZS55bWxAcmVmcy9oZWFkcy9kZXZlbG9wMDgGCisGAQQBg78wARMEKgwoOTNiOTlkMjgxZTRkMjI4ZjBlMGI0YTc1NzJhN2Q4YmI2N2FmM2NiNzAYBgorBgEEAYO/MAEUBAoMCHNjaGVkdWxlMG4GCisGAQQBg78wARUEYAxeaHR0cHM6Ly9naXRodWIuY29tL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9hY3Rpb25zL3J1bnMvMTQzNTIwNjI1NjMvYXR0ZW1wdHMvMTAWBgorBgEEAYO/MAEWBAgMBnB1YmxpYzCBigYKKwYBBAHWeQIEAgR8BHoAeAB2AN09MGrGxxEyYxkeHJlnNwKiSl643jyt/4eKcoAvKe6OAAABlhmXdu0AAAQDAEcwRQIgMjn6bBvSY/lTWJ5XJHRvWkmqzTjyZleqA7PWC5QFxukCIQD2dy0BaP0pAmRSpawHFikwoCMqIjc+3fMH1cNEZd5yLjAKBggqhkjOPQQDAwNoADBlAjEAtMNNlC26qRMbNTUJ2VOMrldiXD5kLXbPmnYkt9UpecTxm5wbe4H4bTLXyXheMQycAjALOGMx2/Ue8r3QIszJWNuNmKwIBZ5DpMVQyShFr62JCW17AtOBtW8/iIbqxZyqwcQ="}, "tlogEntries":[{"logIndex":"194356958", "logId":{"keyId":"wNI9atQGlz+VWfO6LRygH4QUfY/8W4RFwiT5i5WRgB0="}, "kindVersion":{"kind":"dsse", "version":"0.0.1"}, "integratedTime":"1744186079", "inclusionPromise":{"signedEntryTimestamp":"MEQCIH/37/IbxV0Mi4UzzOI6ewUXG3N8WDFPbhkzaH5ClhAWAiAYRMAgOc5i1UF6y+NjkDyrjdnm05AsBXDFM5fxKuSUFg=="}, "inclusionProof":{"logIndex":"72452696", "rootHash":"Z5jut+mFd7i9RNN3SFRAj9GeZpvth+gwBPeWU1HaB50=", "treeSize":"72452697", "hashes":["3nl7oV7bVnZ83Fx06gPqxqnzGeBeNfeoy/2XVJQH30U=", "w0WhQh4eCSSNq/pwtQQjh2eQEpOd9QJ+aHq+hOKsIyk=", "Swm+h9kUxiVITR2hzGgKUZjJSkJcGSUy0KkWjXLBWjE=", "5MiU4qWWEIWTDiSXyHcav0DjdrJctUA9tVp0xMkPZmI=", "5Uf360d/Nzw02EZYpsqVAYlH9QTqK9TdRRFo+2j5iOg=", "57wqQNWm50WWl5q99zh8LMD/Db/3g2ZN4Bfs83JHvfE=", "+Syp5sSVmTsoSA3MGgv/K4DPGHiGRin9Yei5o9OruG8=", "qVmgWhg0f1pqJYoBCEXskX+bB5zIeSjNYmmtoDOSWOM=", "WEm5OgPzJpYROv+4CcrieexCYyQKrLUH3hbxmcQQ+DM=", "7v8qPHNDLerpduaMx06eb/MwgoQwczTn/cYGKX/9wZ4="], "checkpoint":{"envelope":"rekor.sigstore.dev - 1193050959916656506\n72452697\nZ5jut+mFd7i9RNN3SFRAj9GeZpvth+gwBPeWU1HaB50=\n\n— rekor.sigstore.dev wNI9ajBEAiBQlllVYgIfel0F5x7PARYBrQG+MUz48JUen2sMqmDRzwIgCebPh4YWof4qPJC/SqmenkIYhT0glWjEy08R63yG0HY=\n"}}, "canonicalizedBody":"eyJhcGlWZXJzaW9uIjoiMC4wLjEiLCJraW5kIjoiZHNzZSIsInNwZWMiOnsiZW52ZWxvcGVIYXNoIjp7ImFsZ29yaXRobSI6InNoYTI1NiIsInZhbHVlIjoiMjgxNWUyN2IyZGI1MDJhNWVmZDM0NDMyOWU1YjI1NzExOTI0NTU0ZDg1YTU2Yjk1YWI0YTJlY2VhYTVhYTQ4ZiJ9LCJwYXlsb2FkSGFzaCI6eyJhbGdvcml0aG0iOiJzaGEyNTYiLCJ2YWx1ZSI6ImI3ZDJiOTRkYTk4YjlhM2JjZDVkZjU0ZTEwYTQwNTljY2U2Yjk3NmQ0NWMwNzRjYmM4ZDZiN2Y5NmRkY2M2OGMifSwic2lnbmF0dXJlcyI6W3sic2lnbmF0dXJlIjoiTUVVQ0lRQ0hFelljcnpqVFEzM2dKV0tudUlaYklWejYxYVFLT2VEN0tkR3VKQ1h0bEFJZ1BmKy83WTBBUmJjQzVlWnBFamhzY0tOamJLU0ZZbC9WdEEwMm8rZE1GK1E9IiwidmVyaWZpZXIiOiJMUzB0TFMxQ1JVZEpUaUJEUlZKVVNVWkpRMEZVUlMwdExTMHRDazFKU1VoYWFrTkRRblY1WjBGM1NVSkJaMGxWV0dsQmVHazBPWEpOZW5scmVIRmxkbVJvYURWWVNqRnpOV0pKZDBObldVbExiMXBKZW1vd1JVRjNUWGNLVG5wRlZrMUNUVWRCTVZWRlEyaE5UV015Ykc1ak0xSjJZMjFWZFZwSFZqSk5ValIzU0VGWlJGWlJVVVJGZUZaNllWZGtlbVJIT1hsYVV6RndZbTVTYkFwamJURnNXa2RzYUdSSFZYZElhR05PVFdwVmQwNUVRVFZOUkdkM1RucFZORmRvWTA1TmFsVjNUa1JCTlUxRVozaE9lbFUwVjJwQlFVMUdhM2RGZDFsSUNrdHZXa2w2YWpCRFFWRlpTVXR2V2tsNmFqQkVRVkZqUkZGblFVVjFXRkpGVUZoTWRYbG9UREZXTjJkVWFVcHNkemxZU0Zsdk4zcEZjRWRGUjBWTGREZ0thblZzY2xwaFdEaEhia3hpV2twU1MzWlNORFJaVmlzd1VYTlRaakJqT1Rabk9TdGhZbmRoWWtsek0yUlhXRFpVWW1GUFEwSm5jM2RuWjFsSVRVRTBSd3BCTVZWa1JIZEZRaTkzVVVWQmQwbElaMFJCVkVKblRsWklVMVZGUkVSQlMwSm5aM0pDWjBWR1FsRmpSRUY2UVdSQ1owNVdTRkUwUlVablVWVjFia1p3Q2xGaGJ6bHZZVVJXTVd0bGR6ZElZVTU0TjJaR1ExTmpkMGgzV1VSV1VqQnFRa0puZDBadlFWVXpPVkJ3ZWpGWmEwVmFZalZ4VG1wd1MwWlhhWGhwTkZrS1drUTRkMmRaVVVkQk1WVmtSVkZGUWk5M1VqWk5TR2xIWkcxb01HUklRbnBQYVRoMldqSnNNR0ZJVm1sTWJVNTJZbE01ZW1KSVRtaE1WMXA1V1ZjeGJBcGtNamw1WVhrNWVtSklUbWhNVjJSd1pFZG9NVmxwTVc1YVZ6VnNZMjFHTUdJelNYWk1iV1J3WkVkb01WbHBPVE5pTTBweVdtMTRkbVF6VFhaYU1sWjFDbHBZU21oa1J6bDVXREprYkdKdFZubGhWMDVtWXpKNGVsbFVUWFZsVnpGelVVaEtiRnB1VFhaa1IwWnVZM2s1TWsxcE5IaE1ha0YzVDFGWlMwdDNXVUlLUWtGSFJIWjZRVUpCVVZGeVlVaFNNR05JVFRaTWVUa3dZakowYkdKcE5XaFpNMUp3WWpJMWVreHRaSEJrUjJneFdXNVdlbHBZU21waU1qVXdXbGMxTUFwTWJVNTJZbFJCVjBKbmIzSkNaMFZGUVZsUEwwMUJSVU5DUVdoNldUSm9iRnBJVm5OYVZFRXlRbWR2Y2tKblJVVkJXVTh2VFVGRlJFSkRaelZOTWtrMUNrOVhVWGxQUkVac1RrZFJlVTFxYUcxTlIxVjNXV3BTYUU1NlZUTk5iVVV6V2tSb2FWbHFXVE5aVjFsNldUSkpNMDFDYTBkRGFYTkhRVkZSUW1jM09IY0tRVkZSUlVNeFFubGFVekZUV2xkNGJGbFlUbXhOUkZWSFEybHpSMEZSVVVKbk56aDNRVkZWUlVveVJqTmplVEYzWWpOa2JHTnVVblppTW5oNlRETkNkZ3BrTWxaNVpFYzVkbUpJVFhSaVIwWjBXVzFTYUV4WVFqVmtSMmgyWW1wQlowSm5iM0pDWjBWRlFWbFBMMDFCUlVkQ1FrcDVXbGRhZWt3eWFHeFpWMUo2Q2t3eVVteGtiVlp6WWpOQmQwOTNXVXRMZDFsQ1FrRkhSSFo2UVVKRFFWRjBSRU4wYjJSSVVuZGplbTkyVEROU2RtRXlWblZNYlVacVpFZHNkbUp1VFhVS1dqSnNNR0ZJVm1sa1dFNXNZMjFPZG1KdVVteGlibEYxV1RJNWRFMUpSMGRDWjI5eVFtZEZSVUZaVHk5TlFVVktRa2huVFdSdGFEQmtTRUo2VDJrNGRncGFNbXd3WVVoV2FVeHRUblppVXpsNllraE9hRXhYV25sWlZ6RnNaREk1ZVdGNU9YcGlTRTVvVEZka2NHUkhhREZaYVRGdVdsYzFiR050UmpCaU0wbDJDa3h0WkhCa1IyZ3hXV2s1TTJJelNuSmFiWGgyWkROTmRsb3lWblZhV0Vwb1pFYzVlVmd5Wkd4aWJWWjVZVmRPWm1NeWVIcFpWRTExWlZjeGMxRklTbXdLV201TmRtUkhSbTVqZVRreVRXazBlRXhxUVhkUFFWbExTM2RaUWtKQlIwUjJla0ZDUTJkUmNVUkRhRzFPTWxKclQwZE5NVTVIVFhsTlJGa3pXVzFHYlFwWmVrVjVXVEpGTTFsVVZURk9WR3N4V2tSV2JGcFViR2xPZWxWNVRVUlNhRTFDTUVkRGFYTkhRVkZSUW1jM09IZEJVWE5GUkhkM1Rsb3liREJoU0ZacENreFhhSFpqTTFKc1drUkNTMEpuYjNKQ1owVkZRVmxQTDAxQlJVMUNSSGROVDIxb01HUklRbnBQYVRoMldqSnNNR0ZJVm1sTWJVNTJZbE01YUdRelRYUUtZMGM1TTFwWVNqQmlNamx6WTNrNWQySXpaR3hqYmxKMllqSjRla3hYZUdoaVYwcHJXVk14ZDJWWVVtOWlNalIzVDBGWlMwdDNXVUpDUVVkRWRucEJRZ3BFVVZGeFJFTm5OVTB5U1RWUFYxRjVUMFJHYkU1SFVYbE5hbWh0VFVkVmQxbHFVbWhPZWxVelRXMUZNMXBFYUdsWmFsa3pXVmRaZWxreVNUTk5RMGxIQ2tOcGMwZEJVVkZDWnpjNGQwRlJORVZHUVhkVFkyMVdiV041T1c5YVYwWnJZM2s1YTFwWVdteGlSemwzVFVKclIwTnBjMGRCVVZGQ1p6YzRkMEZST0VVS1EzZDNTazFxU1hoUFZFVTFUWHBqTlUxRVJVZERhWE5IUVZGUlFtYzNPSGRCVWtGRlNYZDNhR0ZJVWpCalNFMDJUSGs1Ym1GWVVtOWtWMGwxV1RJNWRBcE1Na1l6WTNreGQySXpaR3hqYmxKMllqSjRlazFDYTBkRGFYTkhRVkZSUW1jM09IZEJVa1ZGUTNkM1NrMVVTVFZOVkVrelRtcE5ORTFJT0VkRGFYTkhDa0ZSVVVKbk56aDNRVkpKUldOUmVIWmhTRkl3WTBoTk5reDVPVzVoV0ZKdlpGZEpkVmt5T1hSTU1rWXpZM2t4ZDJJelpHeGpibEoyWWpKNGVrd3pRbllLWkRKV2VXUkhPWFppU0UxMFlrZEdkRmx0VW1oTVdFSTFaRWRvZG1KcE9IVmFNbXd3WVVoV2FVd3paSFpqYlhSdFlrYzVNMk41T1hkamJWVjBZMjFXY3dwYVYwWjZXbE0xTldKWGVFRmpiVlp0WTNrNWIxcFhSbXRqZVRscldsaGFiR0pIT1hkTlJHZEhRMmx6UjBGUlVVSm5OemgzUVZKTlJVdG5kMjlQVkU1cENrOVViR3ROYW1kNFdsUlNhMDFxU1RSYWFrSnNUVWRKTUZsVVl6Rk9la3BvVGpKUk5GbHRTVEpPTWtadFRUSk9hVTU2UVZsQ1oyOXlRbWRGUlVGWlR5OEtUVUZGVlVKQmIwMURTRTVxWVVkV2EyUlhlR3hOUnpSSFEybHpSMEZSVVVKbk56aDNRVkpWUlZsQmVHVmhTRkl3WTBoTk5reDVPVzVoV0ZKdlpGZEpkUXBaTWpsMFRESkdNMk41TVhkaU0yUnNZMjVTZG1JeWVIcE1NMEoyWkRKV2VXUkhPWFppU0UxMFlrZEdkRmx0VW1oTVdFSTFaRWRvZG1KcE9XaFpNMUp3Q21JeU5YcE1NMG94WW01TmRrMVVVWHBPVkVsM1RtcEpNVTVxVFhaWldGSXdXbGN4ZDJSSVRYWk5WRUZYUW1kdmNrSm5SVVZCV1U4dlRVRkZWMEpCWjAwS1FtNUNNVmx0ZUhCWmVrTkNhV2RaUzB0M1dVSkNRVWhYWlZGSlJVRm5VamhDU0c5QlpVRkNNa0ZPTURsTlIzSkhlSGhGZVZsNGEyVklTbXh1VG5kTGFRcFRiRFkwTTJwNWRDODBaVXRqYjBGMlMyVTJUMEZCUVVKc2FHMVlaSFV3UVVGQlVVUkJSV04zVWxGSlowMXFialppUW5aVFdTOXNWRmRLTlZoS1NGSjJDbGRyYlhGNlZHcDVXbXhsY1VFM1VGZEROVkZHZUhWclEwbFJSREprZVRCQ1lWQXdjRUZ0VWxOd1lYZElSbWxyZDI5RFRYRkphbU1yTTJaTlNERmpUa1VLV21RMWVVeHFRVXRDWjJkeGFHdHFUMUJSVVVSQmQwNXZRVVJDYkVGcVJVRjBUVTVPYkVNeU5uRlNUV0pPVkZWS01sWlBUWEpzWkdsWVJEVnJURmhpVUFwdGJsbHJkRGxWY0dWalZIaHROWGRpWlRSSU5HSlVURmg1V0dobFRWRjVZMEZxUVV4UFIwMTRNaTlWWlRoeU0xRkpjM3BLVjA1MVRtMUxkMGxDV2pWRUNuQk5WbEY1VTJoR2NqWXlTa05YTVRkQmRFOUNkRmM0TDJsSlluRjRXbmx4ZDJOUlBRb3RMUzB0TFVWT1JDQkRSVkpVU1VaSlEwRlVSUzB0TFMwdENnPT0ifV19fQ=="}]}, "dsseEnvelope":{"payload":"eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjAuMSIsInByZWRpY2F0ZVR5cGUiOiJodHRwczovL3Nsc2EuZGV2L3Byb3ZlbmFuY2UvdjAuMiIsInN1YmplY3QiOlt7Im5hbWUiOiIuL2F3c19sYW1iZGFfcG93ZXJ0b29scy0zLjEwLjFhMC1weTMtbm9uZS1hbnkud2hsIiwiZGlnZXN0Ijp7InNoYTI1NiI6IjA2MDU0NTZhNjM1ZTgyYjc0NGNjYzhmMTg0NzhlODcwZjZlMTRkM2IwMzI3M2RhZjNjMDE1Mjk3OTk5MWQxNTYifX0seyJuYW1lIjoiLi9hd3NfbGFtYmRhX3Bvd2VydG9vbHMtMy4xMC4xYTAudGFyLmd6IiwiZGlnZXN0Ijp7InNoYTI1NiI6ImNjY2ZiZDU0ZWNiMmI0M2FiZGIwMDk0MGJkZDgxZWNiYTg1NGY0ZTExNmIxODc3YzE5MjRkNWI2ZDBkYzE3ZWMifX1dLCJwcmVkaWNhdGUiOnsiYnVpbGRlciI6eyJpZCI6Imh0dHBzOi8vZ2l0aHViLmNvbS9zbHNhLWZyYW1ld29yay9zbHNhLWdpdGh1Yi1nZW5lcmF0b3IvLmdpdGh1Yi93b3JrZmxvd3MvZ2VuZXJhdG9yX2dlbmVyaWNfc2xzYTMueW1sQHJlZnMvdGFncy92Mi4xLjAifSwiYnVpbGRUeXBlIjoiaHR0cHM6Ly9naXRodWIuY29tL3Nsc2EtZnJhbWV3b3JrL3Nsc2EtZ2l0aHViLWdlbmVyYXRvci9nZW5lcmljQHYxIiwiaW52b2NhdGlvbiI6eyJjb25maWdTb3VyY2UiOnsidXJpIjoiZ2l0K2h0dHBzOi8vZ2l0aHViLmNvbS9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob25AcmVmcy9oZWFkcy9kZXZlbG9wIiwiZGlnZXN0Ijp7InNoYTEiOiI5M2I5OWQyODFlNGQyMjhmMGUwYjRhNzU3MmE3ZDhiYjY3YWYzY2I3In0sImVudHJ5UG9pbnQiOiIuZ2l0aHViL3dvcmtmbG93cy9wcmUtcmVsZWFzZS55bWwifSwicGFyYW1ldGVycyI6eyJ2YXJzIjp7fX0sImVudmlyb25tZW50Ijp7ImdpdGh1Yl9hY3RvciI6ImxlYW5kcm9kYW1hc2NlbmEiLCJnaXRodWJfYWN0b3JfaWQiOiI0Mjk1MTczIiwiZ2l0aHViX2Jhc2VfcmVmIjoiIiwiZ2l0aHViX2V2ZW50X25hbWUiOiJzY2hlZHVsZSIsImdpdGh1Yl9ldmVudF9wYXlsb2FkIjp7ImVudGVycHJpc2UiOnsiYXZhdGFyX3VybCI6Imh0dHBzOi8vYXZhdGFycy5naXRodWJ1c2VyY29udGVudC5jb20vYi8xMjkwP3Y9NCIsImNyZWF0ZWRfYXQiOiIyMDE5LTExLTEzVDE4OjA1OjQxWiIsImRlc2NyaXB0aW9uIjoiIiwiaHRtbF91cmwiOiJodHRwczovL2dpdGh1Yi5jb20vZW50ZXJwcmlzZXMvYW1hem9uIiwiaWQiOjEyOTAsIm5hbWUiOiJBbWF6b24iLCJub2RlX2lkIjoiTURFd09rVnVkR1Z5Y0hKcGMyVXhNamt3Iiwic2x1ZyI6ImFtYXpvbiIsInVwZGF0ZWRfYXQiOiIyMDI0LTA5LTMwVDIxOjAyOjMwWiIsIndlYnNpdGVfdXJsIjoiaHR0cHM6Ly93d3cuYW1hem9uLmNvbS8ifSwib3JnYW5pemF0aW9uIjp7ImF2YXRhcl91cmwiOiJodHRwczovL2F2YXRhcnMuZ2l0aHVidXNlcmNvbnRlbnQuY29tL3UvMTI5MTI3NjM4P3Y9NCIsImRlc2NyaXB0aW9uIjoiIiwiZXZlbnRzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vb3Jncy9hd3MtcG93ZXJ0b29scy9ldmVudHMiLCJob29rc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL29yZ3MvYXdzLXBvd2VydG9vbHMvaG9va3MiLCJpZCI6MTI5MTI3NjM4LCJpc3N1ZXNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9vcmdzL2F3cy1wb3dlcnRvb2xzL2lzc3VlcyIsImxvZ2luIjoiYXdzLXBvd2VydG9vbHMiLCJtZW1iZXJzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vb3Jncy9hd3MtcG93ZXJ0b29scy9tZW1iZXJzey9tZW1iZXJ9Iiwibm9kZV9pZCI6Ik9fa2dET0I3SlUxZyIsInB1YmxpY19tZW1iZXJzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vb3Jncy9hd3MtcG93ZXJ0b29scy9wdWJsaWNfbWVtYmVyc3svbWVtYmVyfSIsInJlcG9zX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vb3Jncy9hd3MtcG93ZXJ0b29scy9yZXBvcyIsInVybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vb3Jncy9hd3MtcG93ZXJ0b29scyJ9LCJyZXBvc2l0b3J5Ijp7ImFsbG93X2ZvcmtpbmciOnRydWUsImFyY2hpdmVfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24ve2FyY2hpdmVfZm9ybWF0fXsvcmVmfSIsImFyY2hpdmVkIjpmYWxzZSwiYXNzaWduZWVzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2Fzc2lnbmVlc3svdXNlcn0iLCJibG9ic191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9naXQvYmxvYnN7L3NoYX0iLCJicmFuY2hlc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9icmFuY2hlc3svYnJhbmNofSIsImNsb25lX3VybCI6Imh0dHBzOi8vZ2l0aHViLmNvbS9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24uZ2l0IiwiY29sbGFib3JhdG9yc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9jb2xsYWJvcmF0b3Jzey9jb2xsYWJvcmF0b3J9IiwiY29tbWVudHNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vY29tbWVudHN7L251bWJlcn0iLCJjb21taXRzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2NvbW1pdHN7L3NoYX0iLCJjb21wYXJlX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2NvbXBhcmUve2Jhc2V9Li4ue2hlYWR9IiwiY29udGVudHNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vY29udGVudHMveytwYXRofSIsImNvbnRyaWJ1dG9yc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9jb250cmlidXRvcnMiLCJjcmVhdGVkX2F0IjoiMjAxOS0xMS0xNVQxMjoyNjoxMloiLCJjdXN0b21fcHJvcGVydGllcyI6e30sImRlZmF1bHRfYnJhbmNoIjoiZGV2ZWxvcCIsImRlcGxveW1lbnRzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2RlcGxveW1lbnRzIiwiZGVzY3JpcHRpb24iOiJBIGRldmVsb3BlciB0b29sa2l0IHRvIGltcGxlbWVudCBTZXJ2ZXJsZXNzIGJlc3QgcHJhY3RpY2VzIGFuZCBpbmNyZWFzZSBkZXZlbG9wZXIgdmVsb2NpdHkuIiwiZGlzYWJsZWQiOmZhbHNlLCJkb3dubG9hZHNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vZG93bmxvYWRzIiwiZXZlbnRzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2V2ZW50cyIsImZvcmsiOmZhbHNlLCJmb3JrcyI6NDE4LCJmb3Jrc19jb3VudCI6NDE4LCJmb3Jrc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9mb3JrcyIsImZ1bGxfbmFtZSI6ImF3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbiIsImdpdF9jb21taXRzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2dpdC9jb21taXRzey9zaGF9IiwiZ2l0X3JlZnNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vZ2l0L3JlZnN7L3NoYX0iLCJnaXRfdGFnc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9naXQvdGFnc3svc2hhfSIsImdpdF91cmwiOiJnaXQ6Ly9naXRodWIuY29tL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi5naXQiLCJoYXNfZGlzY3Vzc2lvbnMiOnRydWUsImhhc19kb3dubG9hZHMiOnRydWUsImhhc19pc3N1ZXMiOnRydWUsImhhc19wYWdlcyI6ZmFsc2UsImhhc19wcm9qZWN0cyI6dHJ1ZSwiaGFzX3dpa2kiOmZhbHNlLCJob21lcGFnZSI6Imh0dHBzOi8vZG9jcy5wb3dlcnRvb2xzLmF3cy5kZXYvbGFtYmRhL3B5dGhvbi9sYXRlc3QvIiwiaG9va3NfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vaG9va3MiLCJodG1sX3VybCI6Imh0dHBzOi8vZ2l0aHViLmNvbS9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24iLCJpZCI6MjIxOTE5Mzc5LCJpc190ZW1wbGF0ZSI6ZmFsc2UsImlzc3VlX2NvbW1lbnRfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vaXNzdWVzL2NvbW1lbnRzey9udW1iZXJ9IiwiaXNzdWVfZXZlbnRzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2lzc3Vlcy9ldmVudHN7L251bWJlcn0iLCJpc3N1ZXNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vaXNzdWVzey9udW1iZXJ9Iiwia2V5c191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9rZXlzey9rZXlfaWR9IiwibGFiZWxzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2xhYmVsc3svbmFtZX0iLCJsYW5ndWFnZSI6IlB5dGhvbiIsImxhbmd1YWdlc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9sYW5ndWFnZXMiLCJsaWNlbnNlIjp7ImtleSI6Im1pdC0wIiwibmFtZSI6Ik1JVCBObyBBdHRyaWJ1dGlvbiIsIm5vZGVfaWQiOiJNRGM2VEdsalpXNXpaVFF4Iiwic3BkeF9pZCI6Ik1JVC0wIiwidXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9saWNlbnNlcy9taXQtMCJ9LCJtZXJnZXNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vbWVyZ2VzIiwibWlsZXN0b25lc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9taWxlc3RvbmVzey9udW1iZXJ9IiwibWlycm9yX3VybCI6bnVsbCwibmFtZSI6InBvd2VydG9vbHMtbGFtYmRhLXB5dGhvbiIsIm5vZGVfaWQiOiJNREV3T2xKbGNHOXphWFJ2Y25reU1qRTVNVGt6TnprPSIsIm5vdGlmaWNhdGlvbnNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vbm90aWZpY2F0aW9uc3s/c2luY2UsYWxsLHBhcnRpY2lwYXRpbmd9Iiwib3Blbl9pc3N1ZXMiOjUyLCJvcGVuX2lzc3Vlc19jb3VudCI6NTIsIm93bmVyIjp7ImF2YXRhcl91cmwiOiJodHRwczovL2F2YXRhcnMuZ2l0aHVidXNlcmNvbnRlbnQuY29tL3UvMTI5MTI3NjM4P3Y9NCIsImV2ZW50c191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3VzZXJzL2F3cy1wb3dlcnRvb2xzL2V2ZW50c3svcHJpdmFjeX0iLCJmb2xsb3dlcnNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS91c2Vycy9hd3MtcG93ZXJ0b29scy9mb2xsb3dlcnMiLCJmb2xsb3dpbmdfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS91c2Vycy9hd3MtcG93ZXJ0b29scy9mb2xsb3dpbmd7L290aGVyX3VzZXJ9IiwiZ2lzdHNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS91c2Vycy9hd3MtcG93ZXJ0b29scy9naXN0c3svZ2lzdF9pZH0iLCJncmF2YXRhcl9pZCI6IiIsImh0bWxfdXJsIjoiaHR0cHM6Ly9naXRodWIuY29tL2F3cy1wb3dlcnRvb2xzIiwiaWQiOjEyOTEyNzYzOCwibG9naW4iOiJhd3MtcG93ZXJ0b29scyIsIm5vZGVfaWQiOiJPX2tnRE9CN0pVMWciLCJvcmdhbml6YXRpb25zX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vdXNlcnMvYXdzLXBvd2VydG9vbHMvb3JncyIsInJlY2VpdmVkX2V2ZW50c191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3VzZXJzL2F3cy1wb3dlcnRvb2xzL3JlY2VpdmVkX2V2ZW50cyIsInJlcG9zX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vdXNlcnMvYXdzLXBvd2VydG9vbHMvcmVwb3MiLCJzaXRlX2FkbWluIjpmYWxzZSwic3RhcnJlZF91cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3VzZXJzL2F3cy1wb3dlcnRvb2xzL3N0YXJyZWR7L293bmVyfXsvcmVwb30iLCJzdWJzY3JpcHRpb25zX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vdXNlcnMvYXdzLXBvd2VydG9vbHMvc3Vic2NyaXB0aW9ucyIsInR5cGUiOiJPcmdhbml6YXRpb24iLCJ1cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3VzZXJzL2F3cy1wb3dlcnRvb2xzIiwidXNlcl92aWV3X3R5cGUiOiJwdWJsaWMifSwicHJpdmF0ZSI6ZmFsc2UsInB1bGxzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL3B1bGxzey9udW1iZXJ9IiwicHVzaGVkX2F0IjoiMjAyNS0wNC0wOFQyMjowMTo1MloiLCJyZWxlYXNlc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9yZWxlYXNlc3svaWR9Iiwic2l6ZSI6MTA2Mzk0LCJzc2hfdXJsIjoiZ2l0QGdpdGh1Yi5jb206YXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uLmdpdCIsInN0YXJnYXplcnNfY291bnQiOjMwMTgsInN0YXJnYXplcnNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vc3RhcmdhemVycyIsInN0YXR1c2VzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL3N0YXR1c2VzL3tzaGF9Iiwic3Vic2NyaWJlcnNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vc3Vic2NyaWJlcnMiLCJzdWJzY3JpcHRpb25fdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vc3Vic2NyaXB0aW9uIiwic3ZuX3VybCI6Imh0dHBzOi8vZ2l0aHViLmNvbS9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24iLCJ0YWdzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL3RhZ3MiLCJ0ZWFtc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi90ZWFtcyIsInRvcGljcyI6WyJhd3MiLCJhd3MtbGFtYmRhIiwiaGFja3RvYmVyZmVzdCIsImxhbWJkYSIsInB5dGhvbiIsInNlcnZlcmxlc3MiXSwidHJlZXNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vZ2l0L3RyZWVzey9zaGF9IiwidXBkYXRlZF9hdCI6IjIwMjUtMDQtMDhUMjI6MzM6MzlaIiwidXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24iLCJ2aXNpYmlsaXR5IjoicHVibGljIiwid2F0Y2hlcnMiOjMwMTgsIndhdGNoZXJzX2NvdW50IjozMDE4LCJ3ZWJfY29tbWl0X3NpZ25vZmZfcmVxdWlyZWQiOnRydWV9LCJzY2hlZHVsZSI6IjAgOCAqICogMS01Iiwid29ya2Zsb3ciOiIuZ2l0aHViL3dvcmtmbG93cy9wcmUtcmVsZWFzZS55bWwifSwiZ2l0aHViX2hlYWRfcmVmIjoiIiwiZ2l0aHViX3JlZiI6InJlZnMvaGVhZHMvZGV2ZWxvcCIsImdpdGh1Yl9yZWZfdHlwZSI6ImJyYW5jaCIsImdpdGh1Yl9yZXBvc2l0b3J5X2lkIjoiMjIxOTE5Mzc5IiwiZ2l0aHViX3JlcG9zaXRvcnlfb3duZXIiOiJhd3MtcG93ZXJ0b29scyIsImdpdGh1Yl9yZXBvc2l0b3J5X293bmVyX2lkIjoiMTI5MTI3NjM4IiwiZ2l0aHViX3J1bl9hdHRlbXB0IjoiMSIsImdpdGh1Yl9ydW5faWQiOiIxNDM1MjA2MjU2MyIsImdpdGh1Yl9ydW5fbnVtYmVyIjoiMjE1IiwiZ2l0aHViX3NoYTEiOiI5M2I5OWQyODFlNGQyMjhmMGUwYjRhNzU3MmE3ZDhiYjY3YWYzY2I3In19LCJtZXRhZGF0YSI6eyJidWlsZEludm9jYXRpb25JRCI6IjE0MzUyMDYyNTYzLTEiLCJjb21wbGV0ZW5lc3MiOnsicGFyYW1ldGVycyI6dHJ1ZSwiZW52aXJvbm1lbnQiOmZhbHNlLCJtYXRlcmlhbHMiOmZhbHNlfSwicmVwcm9kdWNpYmxlIjpmYWxzZX0sIm1hdGVyaWFscyI6W3sidXJpIjoiZ2l0K2h0dHBzOi8vZ2l0aHViLmNvbS9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob25AcmVmcy9oZWFkcy9kZXZlbG9wIiwiZGlnZXN0Ijp7InNoYTEiOiI5M2I5OWQyODFlNGQyMjhmMGUwYjRhNzU3MmE3ZDhiYjY3YWYzY2I3In19XX19", "payloadType":"application/vnd.in-toto+json", "signatures":[{"sig":"MEUCIQCHEzYcrzjTQ33gJWKnuIZbIVz61aQKOeD7KdGuJCXtlAIgPf+/7Y0ARbcC5eZpEjhscKNjbKSFYl/VtA02o+dMF+Q="}]}}
\ No newline at end of file
diff --git a/provenance/3.10.1a1/multiple.intoto.jsonl b/provenance/3.10.1a1/multiple.intoto.jsonl
new file mode 100644
index 00000000000..e123bfeadba
--- /dev/null
+++ b/provenance/3.10.1a1/multiple.intoto.jsonl
@@ -0,0 +1 @@
+{"mediaType":"application/vnd.dev.sigstore.bundle.v0.3+json", "verificationMaterial":{"certificate":{"rawBytes":"MIIHZzCCBu2gAwIBAgIUGD2yBylBIayG8q+WzwNRVjDDYJowCgYIKoZIzj0EAwMwNzEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MR4wHAYDVQQDExVzaWdzdG9yZS1pbnRlcm1lZGlhdGUwHhcNMjUwNDEwMDgwODAxWhcNMjUwNDEwMDgxODAxWjAAMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEFW7BH1SZnKNhdM8s9dHEDQ19FKHGoauS3omL7JmCGqAWi52snXkZ6LNwbW9kkUBZBsHdPV+2QoeaHFfbQNvb4KOCBgwwggYIMA4GA1UdDwEB/wQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcDAzAdBgNVHQ4EFgQUEKOHVPkik0ROIMPu0sBTZQAjV2UwHwYDVR0jBBgwFoAU39Ppz1YkEZb5qNjpKFWixi4YZD8wgYQGA1UdEQEB/wR6MHiGdmh0dHBzOi8vZ2l0aHViLmNvbS9zbHNhLWZyYW1ld29yay9zbHNhLWdpdGh1Yi1nZW5lcmF0b3IvLmdpdGh1Yi93b3JrZmxvd3MvZ2VuZXJhdG9yX2dlbmVyaWNfc2xzYTMueW1sQHJlZnMvdGFncy92Mi4xLjAwOQYKKwYBBAGDvzABAQQraHR0cHM6Ly90b2tlbi5hY3Rpb25zLmdpdGh1YnVzZXJjb250ZW50LmNvbTAWBgorBgEEAYO/MAECBAhzY2hlZHVsZTA2BgorBgEEAYO/MAEDBCgyN2U0MjI5ODE2ZWU5ZWFlY2ZkYmY2ODI3NDQxMGE0OWQ2ZDM0YTQzMBkGCisGAQQBg78wAQQEC1ByZS1SZWxlYXNlMDUGCisGAQQBg78wAQUEJ2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbjAgBgorBgEEAYO/MAEGBBJyZWZzL2hlYWRzL2RldmVsb3AwOwYKKwYBBAGDvzABCAQtDCtodHRwczovL3Rva2VuLmFjdGlvbnMuZ2l0aHVidXNlcmNvbnRlbnQuY29tMIGGBgorBgEEAYO/MAEJBHgMdmh0dHBzOi8vZ2l0aHViLmNvbS9zbHNhLWZyYW1ld29yay9zbHNhLWdpdGh1Yi1nZW5lcmF0b3IvLmdpdGh1Yi93b3JrZmxvd3MvZ2VuZXJhdG9yX2dlbmVyaWNfc2xzYTMueW1sQHJlZnMvdGFncy92Mi4xLjAwOAYKKwYBBAGDvzABCgQqDChmN2RkOGM1NGMyMDY3YmFmYzEyY2E3YTU1NTk1ZDVlZTliNzUyMDRhMB0GCisGAQQBg78wAQsEDwwNZ2l0aHViLWhvc3RlZDBKBgorBgEEAYO/MAEMBDwMOmh0dHBzOi8vZ2l0aHViLmNvbS9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24wOAYKKwYBBAGDvzABDQQqDCgyN2U0MjI5ODE2ZWU5ZWFlY2ZkYmY2ODI3NDQxMGE0OWQ2ZDM0YTQzMCIGCisGAQQBg78wAQ4EFAwScmVmcy9oZWFkcy9kZXZlbG9wMBkGCisGAQQBg78wAQ8ECwwJMjIxOTE5Mzc5MDEGCisGAQQBg78wARAEIwwhaHR0cHM6Ly9naXRodWIuY29tL2F3cy1wb3dlcnRvb2xzMBkGCisGAQQBg78wAREECwwJMTI5MTI3NjM4MH8GCisGAQQBg78wARIEcQxvaHR0cHM6Ly9naXRodWIuY29tL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi8uZ2l0aHViL3dvcmtmbG93cy9wcmUtcmVsZWFzZS55bWxAcmVmcy9oZWFkcy9kZXZlbG9wMDgGCisGAQQBg78wARMEKgwoMjdlNDIyOTgxNmVlOWVhZWNmZGJmNjgyNzQ0MTBhNDlkNmQzNGE0MzAYBgorBgEEAYO/MAEUBAoMCHNjaGVkdWxlMG4GCisGAQQBg78wARUEYAxeaHR0cHM6Ly9naXRodWIuY29tL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9hY3Rpb25zL3J1bnMvMTQzNzUzNTc4NjQvYXR0ZW1wdHMvMTAWBgorBgEEAYO/MAEWBAgMBnB1YmxpYzCBiwYKKwYBBAHWeQIEAgR9BHsAeQB3AN09MGrGxxEyYxkeHJlnNwKiSl643jyt/4eKcoAvKe6OAAABlh693FcAAAQDAEgwRgIhAPVsA3FlNxFCAmQhWWpMTWtQfJvduiVlsum+WMAQDsZAAiEAkXQ0FKJBe0ljUcrCEvHhMMiGbjp5y+5SKIr9QkBdgmcwCgYIKoZIzj0EAwMDaAAwZQIwTfjhopA1TCGtXi90yEcCKj5sny31NqdsgJ1uoF8hrhwUTDfH1XNsCVZqLYMHZ6U7AjEAlwQi7GoONiO61F1tJ4oWdRMuC9d/6s9GBlUu9iqQZ2ULcDSRytkJsADf4sed+UeM"}, "tlogEntries":[{"logIndex":"194820951", "logId":{"keyId":"wNI9atQGlz+VWfO6LRygH4QUfY/8W4RFwiT5i5WRgB0="}, "kindVersion":{"kind":"dsse", "version":"0.0.1"}, "integratedTime":"1744272481", "inclusionPromise":{"signedEntryTimestamp":"MEUCIBdP6vdXBRFWQdZJH2X7Nxx97XzO5KTmEfLmmZHs6Yb7AiEAsOq8kou5JthWA9xV6IBXVBlCY3iGaGqfN0zYrYX6My8="}, "inclusionProof":{"logIndex":"72916689", "rootHash":"st0pHxcIrdjsuftnKLM/Ut45Ig64O4zRWEB8qYLM8Qo=", "treeSize":"72916690", "hashes":["4r+vdbE+oYBg9TIlVptbiazBMuxhClT/rJ4A4mlgaoE=", "J1DW+lF+X6IhoIj7f+5CkTNVYVC9tqjxFLogDKgbEiM=", "LIY/j3SyAYk+VIR4BCqxCB+MoD21zlOC7aC67rWZx7s=", "tRG9zfvLB/2+2Brho66lWabD4KV7U9G4KIkO26L2YSo=", "12GEqj/2SdACuKcpJ6ET5byubLOY6QJ6gweN7IRF6Dw=", "GxQMWbSyUP05I/yl3/zEnGv9L6Oqrpi0XGUySnyVoe8=", "6wRHhNWmxd8lUeySgd+Uce3RglatZzs04gIWhM8do74=", "3HAjpD2RO6dikWC65exB6ID+vY4deJ0UzXCgxWbQFuM=", "QYgxvQ13J6FmXMxF4TP7sikPOYARuekz2PoIjPwroS0=", "6h22gZfYpQRVkz9rhZj4qY5BdYUc+f6MnadkgBgy1YI=", "qVmgWhg0f1pqJYoBCEXskX+bB5zIeSjNYmmtoDOSWOM=", "WEm5OgPzJpYROv+4CcrieexCYyQKrLUH3hbxmcQQ+DM=", "7v8qPHNDLerpduaMx06eb/MwgoQwczTn/cYGKX/9wZ4="], "checkpoint":{"envelope":"rekor.sigstore.dev - 1193050959916656506\n72916690\nst0pHxcIrdjsuftnKLM/Ut45Ig64O4zRWEB8qYLM8Qo=\n\n— rekor.sigstore.dev wNI9ajBEAiBhYxGVAOdnD8fczjatPvv/xJk8j717xPSShHu2iRQIsgIgFcUFKmjxRZRE3tLlJo/tOi8NE0O1sBi2jRQadAjisn8=\n"}}, "canonicalizedBody":"eyJhcGlWZXJzaW9uIjoiMC4wLjEiLCJraW5kIjoiZHNzZSIsInNwZWMiOnsiZW52ZWxvcGVIYXNoIjp7ImFsZ29yaXRobSI6InNoYTI1NiIsInZhbHVlIjoiMWEyZjhmZDAyNGMzYzY0ZmI1NWRhODFiMjM2NTNjNzA4NGU5MDU1OTBhOTU2ZWM1YTNjMzAyNmVlMmRhMzg3ZiJ9LCJwYXlsb2FkSGFzaCI6eyJhbGdvcml0aG0iOiJzaGEyNTYiLCJ2YWx1ZSI6ImUzYTUxNmIwYmU0ZmYzYmZlN2JmMTIyMjYzMTY0MDRhMzhlOTExMzc3MjZkZTY1MjQxMDVmZGMyZWU1NzBiZDYifSwic2lnbmF0dXJlcyI6W3sic2lnbmF0dXJlIjoiTUVZQ0lRQ0xoYXp4VGZFc1ZnVWlkR3lWR0M1MXFVWUd6eFFEN3BpSUZUMnFvczNURndJaEFMY21sUzExMXlDbHJSZ1Q3L0FaUUdzVGJobEIyOHIxTW8yYUVudWVzdVBBIiwidmVyaWZpZXIiOiJMUzB0TFMxQ1JVZEpUaUJEUlZKVVNVWkpRMEZVUlMwdExTMHRDazFKU1VoYWVrTkRRblV5WjBGM1NVSkJaMGxWUjBReWVVSjViRUpKWVhsSE9IRXJWM3AzVGxKV2FrUkVXVXB2ZDBObldVbExiMXBKZW1vd1JVRjNUWGNLVG5wRlZrMUNUVWRCTVZWRlEyaE5UV015Ykc1ak0xSjJZMjFWZFZwSFZqSk5ValIzU0VGWlJGWlJVVVJGZUZaNllWZGtlbVJIT1hsYVV6RndZbTVTYkFwamJURnNXa2RzYUdSSFZYZElhR05PVFdwVmQwNUVSWGROUkdkM1QwUkJlRmRvWTA1TmFsVjNUa1JGZDAxRVozaFBSRUY0VjJwQlFVMUdhM2RGZDFsSUNrdHZXa2w2YWpCRFFWRlpTVXR2V2tsNmFqQkVRVkZqUkZGblFVVkdWemRDU0RGVFdtNUxUbWhrVFRoek9XUklSVVJSTVRsR1MwaEhiMkYxVXpOdmJVd0tOMHB0UTBkeFFWZHBOVEp6YmxocldqWk1UbmRpVnpscmExVkNXa0p6U0dSUVZpc3lVVzlsWVVoR1ptSlJUblppTkV0UFEwSm5kM2RuWjFsSlRVRTBSd3BCTVZWa1JIZEZRaTkzVVVWQmQwbElaMFJCVkVKblRsWklVMVZGUkVSQlMwSm5aM0pDWjBWR1FsRmpSRUY2UVdSQ1owNVdTRkUwUlVablVWVkZTMDlJQ2xaUWEybHJNRkpQU1UxUWRUQnpRbFJhVVVGcVZqSlZkMGgzV1VSV1VqQnFRa0puZDBadlFWVXpPVkJ3ZWpGWmEwVmFZalZ4VG1wd1MwWlhhWGhwTkZrS1drUTRkMmRaVVVkQk1WVmtSVkZGUWk5M1VqWk5TR2xIWkcxb01HUklRbnBQYVRoMldqSnNNR0ZJVm1sTWJVNTJZbE01ZW1KSVRtaE1WMXA1V1ZjeGJBcGtNamw1WVhrNWVtSklUbWhNVjJSd1pFZG9NVmxwTVc1YVZ6VnNZMjFHTUdJelNYWk1iV1J3WkVkb01WbHBPVE5pTTBweVdtMTRkbVF6VFhaYU1sWjFDbHBZU21oa1J6bDVXREprYkdKdFZubGhWMDVtWXpKNGVsbFVUWFZsVnpGelVVaEtiRnB1VFhaa1IwWnVZM2s1TWsxcE5IaE1ha0YzVDFGWlMwdDNXVUlLUWtGSFJIWjZRVUpCVVZGeVlVaFNNR05JVFRaTWVUa3dZakowYkdKcE5XaFpNMUp3WWpJMWVreHRaSEJrUjJneFdXNVdlbHBZU21waU1qVXdXbGMxTUFwTWJVNTJZbFJCVjBKbmIzSkNaMFZGUVZsUEwwMUJSVU5DUVdoNldUSm9iRnBJVm5OYVZFRXlRbWR2Y2tKblJVVkJXVTh2VFVGRlJFSkRaM2xPTWxVd0NrMXFTVFZQUkVVeVdsZFZOVnBYUm14Wk1scHJXVzFaTWs5RVNUTk9SRkY0VFVkRk1FOVhVVEphUkUwd1dWUlJlazFDYTBkRGFYTkhRVkZSUW1jM09IY0tRVkZSUlVNeFFubGFVekZUV2xkNGJGbFlUbXhOUkZWSFEybHpSMEZSVVVKbk56aDNRVkZWUlVveVJqTmplVEYzWWpOa2JHTnVVblppTW5oNlRETkNkZ3BrTWxaNVpFYzVkbUpJVFhSaVIwWjBXVzFTYUV4WVFqVmtSMmgyWW1wQlowSm5iM0pDWjBWRlFWbFBMMDFCUlVkQ1FrcDVXbGRhZWt3eWFHeFpWMUo2Q2t3eVVteGtiVlp6WWpOQmQwOTNXVXRMZDFsQ1FrRkhSSFo2UVVKRFFWRjBSRU4wYjJSSVVuZGplbTkyVEROU2RtRXlWblZNYlVacVpFZHNkbUp1VFhVS1dqSnNNR0ZJVm1sa1dFNXNZMjFPZG1KdVVteGlibEYxV1RJNWRFMUpSMGRDWjI5eVFtZEZSVUZaVHk5TlFVVktRa2huVFdSdGFEQmtTRUo2VDJrNGRncGFNbXd3WVVoV2FVeHRUblppVXpsNllraE9hRXhYV25sWlZ6RnNaREk1ZVdGNU9YcGlTRTVvVEZka2NHUkhhREZaYVRGdVdsYzFiR050UmpCaU0wbDJDa3h0WkhCa1IyZ3hXV2s1TTJJelNuSmFiWGgyWkROTmRsb3lWblZhV0Vwb1pFYzVlVmd5Wkd4aWJWWjVZVmRPWm1NeWVIcFpWRTExWlZjeGMxRklTbXdLV201TmRtUkhSbTVqZVRreVRXazBlRXhxUVhkUFFWbExTM2RaUWtKQlIwUjJla0ZDUTJkUmNVUkRhRzFPTWxKclQwZE5NVTVIVFhsTlJGa3pXVzFHYlFwWmVrVjVXVEpGTTFsVVZURk9WR3N4V2tSV2JGcFViR2xPZWxWNVRVUlNhRTFDTUVkRGFYTkhRVkZSUW1jM09IZEJVWE5GUkhkM1Rsb3liREJoU0ZacENreFhhSFpqTTFKc1drUkNTMEpuYjNKQ1owVkZRVmxQTDAxQlJVMUNSSGROVDIxb01HUklRbnBQYVRoMldqSnNNR0ZJVm1sTWJVNTJZbE01YUdRelRYUUtZMGM1TTFwWVNqQmlNamx6WTNrNWQySXpaR3hqYmxKMllqSjRla3hYZUdoaVYwcHJXVk14ZDJWWVVtOWlNalIzVDBGWlMwdDNXVUpDUVVkRWRucEJRZ3BFVVZGeFJFTm5lVTR5VlRCTmFrazFUMFJGTWxwWFZUVmFWMFpzV1RKYWExbHRXVEpQUkVrelRrUlJlRTFIUlRCUFYxRXlXa1JOTUZsVVVYcE5RMGxIQ2tOcGMwZEJVVkZDWnpjNGQwRlJORVZHUVhkVFkyMVdiV041T1c5YVYwWnJZM2s1YTFwWVdteGlSemwzVFVKclIwTnBjMGRCVVZGQ1p6YzRkMEZST0VVS1EzZDNTazFxU1hoUFZFVTFUWHBqTlUxRVJVZERhWE5IUVZGUlFtYzNPSGRCVWtGRlNYZDNhR0ZJVWpCalNFMDJUSGs1Ym1GWVVtOWtWMGwxV1RJNWRBcE1Na1l6WTNreGQySXpaR3hqYmxKMllqSjRlazFDYTBkRGFYTkhRVkZSUW1jM09IZEJVa1ZGUTNkM1NrMVVTVFZOVkVrelRtcE5ORTFJT0VkRGFYTkhDa0ZSVVVKbk56aDNRVkpKUldOUmVIWmhTRkl3WTBoTk5reDVPVzVoV0ZKdlpGZEpkVmt5T1hSTU1rWXpZM2t4ZDJJelpHeGpibEoyWWpKNGVrd3pRbllLWkRKV2VXUkhPWFppU0UxMFlrZEdkRmx0VW1oTVdFSTFaRWRvZG1KcE9IVmFNbXd3WVVoV2FVd3paSFpqYlhSdFlrYzVNMk41T1hkamJWVjBZMjFXY3dwYVYwWjZXbE0xTldKWGVFRmpiVlp0WTNrNWIxcFhSbXRqZVRscldsaGFiR0pIT1hkTlJHZEhRMmx6UjBGUlVVSm5OemgzUVZKTlJVdG5kMjlOYW1Sc0NrNUVTWGxQVkdkNFRtMVdiRTlYVm1oYVYwNXRXa2RLYlU1cVozbE9lbEV3VFZSQ2FFNUViR3RPYlZGNlRrZEZNRTE2UVZsQ1oyOXlRbWRGUlVGWlR5OEtUVUZGVlVKQmIwMURTRTVxWVVkV2EyUlhlR3hOUnpSSFEybHpSMEZSVVVKbk56aDNRVkpWUlZsQmVHVmhTRkl3WTBoTk5reDVPVzVoV0ZKdlpGZEpkUXBaTWpsMFRESkdNMk41TVhkaU0yUnNZMjVTZG1JeWVIcE1NMEoyWkRKV2VXUkhPWFppU0UxMFlrZEdkRmx0VW1oTVdFSTFaRWRvZG1KcE9XaFpNMUp3Q21JeU5YcE1NMG94WW01TmRrMVVVWHBPZWxWNlRsUmpORTVxVVhaWldGSXdXbGN4ZDJSSVRYWk5WRUZYUW1kdmNrSm5SVVZCV1U4dlRVRkZWMEpCWjAwS1FtNUNNVmx0ZUhCWmVrTkNhWGRaUzB0M1dVSkNRVWhYWlZGSlJVRm5VamxDU0hOQlpWRkNNMEZPTURsTlIzSkhlSGhGZVZsNGEyVklTbXh1VG5kTGFRcFRiRFkwTTJwNWRDODBaVXRqYjBGMlMyVTJUMEZCUVVKc2FEWTVNMFpqUVVGQlVVUkJSV2QzVW1kSmFFRlFWbk5CTTBac1RuaEdRMEZ0VVdoWFYzQk5DbFJYZEZGbVNuWmtkV2xXYkhOMWJTdFhUVUZSUkhOYVFVRnBSVUZyV0ZFd1JrdEtRbVV3YkdwVlkzSkRSWFpJYUUxTmFVZGlhbkExZVNzMVUwdEpjamtLVVd0Q1pHZHRZM2REWjFsSlMyOWFTWHBxTUVWQmQwMUVZVUZCZDFwUlNYZFVabXBvYjNCQk1WUkRSM1JZYVRrd2VVVmpRMHRxTlhOdWVUTXhUbkZrY3dwblNqRjFiMFk0YUhKb2QxVlVSR1pJTVZoT2MwTldXbkZNV1UxSVdqWlZOMEZxUlVGc2QxRnBOMGR2VDA1cFR6WXhSakYwU2pSdlYyUlNUWFZET1dRdkNqWnpPVWRDYkZWMU9XbHhVVm95VlV4alJGTlNlWFJyU25OQlJHWTBjMlZrSzFWbFRRb3RMUzB0TFVWT1JDQkRSVkpVU1VaSlEwRlVSUzB0TFMwdENnPT0ifV19fQ=="}]}, "dsseEnvelope":{"payload":"eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjAuMSIsInByZWRpY2F0ZVR5cGUiOiJodHRwczovL3Nsc2EuZGV2L3Byb3ZlbmFuY2UvdjAuMiIsInN1YmplY3QiOlt7Im5hbWUiOiIuL2F3c19sYW1iZGFfcG93ZXJ0b29scy0zLjEwLjFhMS1weTMtbm9uZS1hbnkud2hsIiwiZGlnZXN0Ijp7InNoYTI1NiI6ImZiNTIwMzRkZDBmOWEwMWVmNDdjMDdlODUxOWNmNDA0MDc1YTdiYTc0MjA5ZmNhZDc4ZTE4MjA4YzAyY2I1MTgifX0seyJuYW1lIjoiLi9hd3NfbGFtYmRhX3Bvd2VydG9vbHMtMy4xMC4xYTEudGFyLmd6IiwiZGlnZXN0Ijp7InNoYTI1NiI6ImE2YjI4YmE2M2IwM2EzYzg0YjczMjI5OTkyZGMxOTRiNjQxMzA2YjIzNDUyMzg0ZTk3ZWE5Yzg1MTk2YjMzZTcifX1dLCJwcmVkaWNhdGUiOnsiYnVpbGRlciI6eyJpZCI6Imh0dHBzOi8vZ2l0aHViLmNvbS9zbHNhLWZyYW1ld29yay9zbHNhLWdpdGh1Yi1nZW5lcmF0b3IvLmdpdGh1Yi93b3JrZmxvd3MvZ2VuZXJhdG9yX2dlbmVyaWNfc2xzYTMueW1sQHJlZnMvdGFncy92Mi4xLjAifSwiYnVpbGRUeXBlIjoiaHR0cHM6Ly9naXRodWIuY29tL3Nsc2EtZnJhbWV3b3JrL3Nsc2EtZ2l0aHViLWdlbmVyYXRvci9nZW5lcmljQHYxIiwiaW52b2NhdGlvbiI6eyJjb25maWdTb3VyY2UiOnsidXJpIjoiZ2l0K2h0dHBzOi8vZ2l0aHViLmNvbS9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob25AcmVmcy9oZWFkcy9kZXZlbG9wIiwiZGlnZXN0Ijp7InNoYTEiOiIyN2U0MjI5ODE2ZWU5ZWFlY2ZkYmY2ODI3NDQxMGE0OWQ2ZDM0YTQzIn0sImVudHJ5UG9pbnQiOiIuZ2l0aHViL3dvcmtmbG93cy9wcmUtcmVsZWFzZS55bWwifSwicGFyYW1ldGVycyI6eyJ2YXJzIjp7fX0sImVudmlyb25tZW50Ijp7ImdpdGh1Yl9hY3RvciI6ImxlYW5kcm9kYW1hc2NlbmEiLCJnaXRodWJfYWN0b3JfaWQiOiI0Mjk1MTczIiwiZ2l0aHViX2Jhc2VfcmVmIjoiIiwiZ2l0aHViX2V2ZW50X25hbWUiOiJzY2hlZHVsZSIsImdpdGh1Yl9ldmVudF9wYXlsb2FkIjp7ImVudGVycHJpc2UiOnsiYXZhdGFyX3VybCI6Imh0dHBzOi8vYXZhdGFycy5naXRodWJ1c2VyY29udGVudC5jb20vYi8xMjkwP3Y9NCIsImNyZWF0ZWRfYXQiOiIyMDE5LTExLTEzVDE4OjA1OjQxWiIsImRlc2NyaXB0aW9uIjoiIiwiaHRtbF91cmwiOiJodHRwczovL2dpdGh1Yi5jb20vZW50ZXJwcmlzZXMvYW1hem9uIiwiaWQiOjEyOTAsIm5hbWUiOiJBbWF6b24iLCJub2RlX2lkIjoiTURFd09rVnVkR1Z5Y0hKcGMyVXhNamt3Iiwic2x1ZyI6ImFtYXpvbiIsInVwZGF0ZWRfYXQiOiIyMDI0LTA5LTMwVDIxOjAyOjMwWiIsIndlYnNpdGVfdXJsIjoiaHR0cHM6Ly93d3cuYW1hem9uLmNvbS8ifSwib3JnYW5pemF0aW9uIjp7ImF2YXRhcl91cmwiOiJodHRwczovL2F2YXRhcnMuZ2l0aHVidXNlcmNvbnRlbnQuY29tL3UvMTI5MTI3NjM4P3Y9NCIsImRlc2NyaXB0aW9uIjoiIiwiZXZlbnRzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vb3Jncy9hd3MtcG93ZXJ0b29scy9ldmVudHMiLCJob29rc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL29yZ3MvYXdzLXBvd2VydG9vbHMvaG9va3MiLCJpZCI6MTI5MTI3NjM4LCJpc3N1ZXNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9vcmdzL2F3cy1wb3dlcnRvb2xzL2lzc3VlcyIsImxvZ2luIjoiYXdzLXBvd2VydG9vbHMiLCJtZW1iZXJzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vb3Jncy9hd3MtcG93ZXJ0b29scy9tZW1iZXJzey9tZW1iZXJ9Iiwibm9kZV9pZCI6Ik9fa2dET0I3SlUxZyIsInB1YmxpY19tZW1iZXJzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vb3Jncy9hd3MtcG93ZXJ0b29scy9wdWJsaWNfbWVtYmVyc3svbWVtYmVyfSIsInJlcG9zX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vb3Jncy9hd3MtcG93ZXJ0b29scy9yZXBvcyIsInVybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vb3Jncy9hd3MtcG93ZXJ0b29scyJ9LCJyZXBvc2l0b3J5Ijp7ImFsbG93X2ZvcmtpbmciOnRydWUsImFyY2hpdmVfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24ve2FyY2hpdmVfZm9ybWF0fXsvcmVmfSIsImFyY2hpdmVkIjpmYWxzZSwiYXNzaWduZWVzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2Fzc2lnbmVlc3svdXNlcn0iLCJibG9ic191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9naXQvYmxvYnN7L3NoYX0iLCJicmFuY2hlc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9icmFuY2hlc3svYnJhbmNofSIsImNsb25lX3VybCI6Imh0dHBzOi8vZ2l0aHViLmNvbS9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24uZ2l0IiwiY29sbGFib3JhdG9yc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9jb2xsYWJvcmF0b3Jzey9jb2xsYWJvcmF0b3J9IiwiY29tbWVudHNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vY29tbWVudHN7L251bWJlcn0iLCJjb21taXRzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2NvbW1pdHN7L3NoYX0iLCJjb21wYXJlX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2NvbXBhcmUve2Jhc2V9Li4ue2hlYWR9IiwiY29udGVudHNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vY29udGVudHMveytwYXRofSIsImNvbnRyaWJ1dG9yc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9jb250cmlidXRvcnMiLCJjcmVhdGVkX2F0IjoiMjAxOS0xMS0xNVQxMjoyNjoxMloiLCJjdXN0b21fcHJvcGVydGllcyI6e30sImRlZmF1bHRfYnJhbmNoIjoiZGV2ZWxvcCIsImRlcGxveW1lbnRzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2RlcGxveW1lbnRzIiwiZGVzY3JpcHRpb24iOiJBIGRldmVsb3BlciB0b29sa2l0IHRvIGltcGxlbWVudCBTZXJ2ZXJsZXNzIGJlc3QgcHJhY3RpY2VzIGFuZCBpbmNyZWFzZSBkZXZlbG9wZXIgdmVsb2NpdHkuIiwiZGlzYWJsZWQiOmZhbHNlLCJkb3dubG9hZHNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vZG93bmxvYWRzIiwiZXZlbnRzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2V2ZW50cyIsImZvcmsiOmZhbHNlLCJmb3JrcyI6NDE4LCJmb3Jrc19jb3VudCI6NDE4LCJmb3Jrc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9mb3JrcyIsImZ1bGxfbmFtZSI6ImF3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbiIsImdpdF9jb21taXRzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2dpdC9jb21taXRzey9zaGF9IiwiZ2l0X3JlZnNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vZ2l0L3JlZnN7L3NoYX0iLCJnaXRfdGFnc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9naXQvdGFnc3svc2hhfSIsImdpdF91cmwiOiJnaXQ6Ly9naXRodWIuY29tL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi5naXQiLCJoYXNfZGlzY3Vzc2lvbnMiOnRydWUsImhhc19kb3dubG9hZHMiOnRydWUsImhhc19pc3N1ZXMiOnRydWUsImhhc19wYWdlcyI6ZmFsc2UsImhhc19wcm9qZWN0cyI6dHJ1ZSwiaGFzX3dpa2kiOmZhbHNlLCJob21lcGFnZSI6Imh0dHBzOi8vZG9jcy5wb3dlcnRvb2xzLmF3cy5kZXYvbGFtYmRhL3B5dGhvbi9sYXRlc3QvIiwiaG9va3NfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vaG9va3MiLCJodG1sX3VybCI6Imh0dHBzOi8vZ2l0aHViLmNvbS9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24iLCJpZCI6MjIxOTE5Mzc5LCJpc190ZW1wbGF0ZSI6ZmFsc2UsImlzc3VlX2NvbW1lbnRfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vaXNzdWVzL2NvbW1lbnRzey9udW1iZXJ9IiwiaXNzdWVfZXZlbnRzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2lzc3Vlcy9ldmVudHN7L251bWJlcn0iLCJpc3N1ZXNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vaXNzdWVzey9udW1iZXJ9Iiwia2V5c191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9rZXlzey9rZXlfaWR9IiwibGFiZWxzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2xhYmVsc3svbmFtZX0iLCJsYW5ndWFnZSI6IlB5dGhvbiIsImxhbmd1YWdlc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9sYW5ndWFnZXMiLCJsaWNlbnNlIjp7ImtleSI6Im1pdC0wIiwibmFtZSI6Ik1JVCBObyBBdHRyaWJ1dGlvbiIsIm5vZGVfaWQiOiJNRGM2VEdsalpXNXpaVFF4Iiwic3BkeF9pZCI6Ik1JVC0wIiwidXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9saWNlbnNlcy9taXQtMCJ9LCJtZXJnZXNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vbWVyZ2VzIiwibWlsZXN0b25lc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9taWxlc3RvbmVzey9udW1iZXJ9IiwibWlycm9yX3VybCI6bnVsbCwibmFtZSI6InBvd2VydG9vbHMtbGFtYmRhLXB5dGhvbiIsIm5vZGVfaWQiOiJNREV3T2xKbGNHOXphWFJ2Y25reU1qRTVNVGt6TnprPSIsIm5vdGlmaWNhdGlvbnNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vbm90aWZpY2F0aW9uc3s/c2luY2UsYWxsLHBhcnRpY2lwYXRpbmd9Iiwib3Blbl9pc3N1ZXMiOjU0LCJvcGVuX2lzc3Vlc19jb3VudCI6NTQsIm93bmVyIjp7ImF2YXRhcl91cmwiOiJodHRwczovL2F2YXRhcnMuZ2l0aHVidXNlcmNvbnRlbnQuY29tL3UvMTI5MTI3NjM4P3Y9NCIsImV2ZW50c191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3VzZXJzL2F3cy1wb3dlcnRvb2xzL2V2ZW50c3svcHJpdmFjeX0iLCJmb2xsb3dlcnNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS91c2Vycy9hd3MtcG93ZXJ0b29scy9mb2xsb3dlcnMiLCJmb2xsb3dpbmdfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS91c2Vycy9hd3MtcG93ZXJ0b29scy9mb2xsb3dpbmd7L290aGVyX3VzZXJ9IiwiZ2lzdHNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS91c2Vycy9hd3MtcG93ZXJ0b29scy9naXN0c3svZ2lzdF9pZH0iLCJncmF2YXRhcl9pZCI6IiIsImh0bWxfdXJsIjoiaHR0cHM6Ly9naXRodWIuY29tL2F3cy1wb3dlcnRvb2xzIiwiaWQiOjEyOTEyNzYzOCwibG9naW4iOiJhd3MtcG93ZXJ0b29scyIsIm5vZGVfaWQiOiJPX2tnRE9CN0pVMWciLCJvcmdhbml6YXRpb25zX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vdXNlcnMvYXdzLXBvd2VydG9vbHMvb3JncyIsInJlY2VpdmVkX2V2ZW50c191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3VzZXJzL2F3cy1wb3dlcnRvb2xzL3JlY2VpdmVkX2V2ZW50cyIsInJlcG9zX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vdXNlcnMvYXdzLXBvd2VydG9vbHMvcmVwb3MiLCJzaXRlX2FkbWluIjpmYWxzZSwic3RhcnJlZF91cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3VzZXJzL2F3cy1wb3dlcnRvb2xzL3N0YXJyZWR7L293bmVyfXsvcmVwb30iLCJzdWJzY3JpcHRpb25zX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vdXNlcnMvYXdzLXBvd2VydG9vbHMvc3Vic2NyaXB0aW9ucyIsInR5cGUiOiJPcmdhbml6YXRpb24iLCJ1cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3VzZXJzL2F3cy1wb3dlcnRvb2xzIiwidXNlcl92aWV3X3R5cGUiOiJwdWJsaWMifSwicHJpdmF0ZSI6ZmFsc2UsInB1bGxzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL3B1bGxzey9udW1iZXJ9IiwicHVzaGVkX2F0IjoiMjAyNS0wNC0wOVQyMjowODoyOVoiLCJyZWxlYXNlc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9yZWxlYXNlc3svaWR9Iiwic2l6ZSI6MTA1NzgzLCJzc2hfdXJsIjoiZ2l0QGdpdGh1Yi5jb206YXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uLmdpdCIsInN0YXJnYXplcnNfY291bnQiOjMwMjAsInN0YXJnYXplcnNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vc3RhcmdhemVycyIsInN0YXR1c2VzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL3N0YXR1c2VzL3tzaGF9Iiwic3Vic2NyaWJlcnNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vc3Vic2NyaWJlcnMiLCJzdWJzY3JpcHRpb25fdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vc3Vic2NyaXB0aW9uIiwic3ZuX3VybCI6Imh0dHBzOi8vZ2l0aHViLmNvbS9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24iLCJ0YWdzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL3RhZ3MiLCJ0ZWFtc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi90ZWFtcyIsInRvcGljcyI6WyJhd3MiLCJhd3MtbGFtYmRhIiwiaGFja3RvYmVyZmVzdCIsImxhbWJkYSIsInB5dGhvbiIsInNlcnZlcmxlc3MiXSwidHJlZXNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vZ2l0L3RyZWVzey9zaGF9IiwidXBkYXRlZF9hdCI6IjIwMjUtMDQtMDlUMjI6MDY6NDBaIiwidXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24iLCJ2aXNpYmlsaXR5IjoicHVibGljIiwid2F0Y2hlcnMiOjMwMjAsIndhdGNoZXJzX2NvdW50IjozMDIwLCJ3ZWJfY29tbWl0X3NpZ25vZmZfcmVxdWlyZWQiOnRydWV9LCJzY2hlZHVsZSI6IjAgOCAqICogMS01Iiwid29ya2Zsb3ciOiIuZ2l0aHViL3dvcmtmbG93cy9wcmUtcmVsZWFzZS55bWwifSwiZ2l0aHViX2hlYWRfcmVmIjoiIiwiZ2l0aHViX3JlZiI6InJlZnMvaGVhZHMvZGV2ZWxvcCIsImdpdGh1Yl9yZWZfdHlwZSI6ImJyYW5jaCIsImdpdGh1Yl9yZXBvc2l0b3J5X2lkIjoiMjIxOTE5Mzc5IiwiZ2l0aHViX3JlcG9zaXRvcnlfb3duZXIiOiJhd3MtcG93ZXJ0b29scyIsImdpdGh1Yl9yZXBvc2l0b3J5X293bmVyX2lkIjoiMTI5MTI3NjM4IiwiZ2l0aHViX3J1bl9hdHRlbXB0IjoiMSIsImdpdGh1Yl9ydW5faWQiOiIxNDM3NTM1Nzg2NCIsImdpdGh1Yl9ydW5fbnVtYmVyIjoiMjE2IiwiZ2l0aHViX3NoYTEiOiIyN2U0MjI5ODE2ZWU5ZWFlY2ZkYmY2ODI3NDQxMGE0OWQ2ZDM0YTQzIn19LCJtZXRhZGF0YSI6eyJidWlsZEludm9jYXRpb25JRCI6IjE0Mzc1MzU3ODY0LTEiLCJjb21wbGV0ZW5lc3MiOnsicGFyYW1ldGVycyI6dHJ1ZSwiZW52aXJvbm1lbnQiOmZhbHNlLCJtYXRlcmlhbHMiOmZhbHNlfSwicmVwcm9kdWNpYmxlIjpmYWxzZX0sIm1hdGVyaWFscyI6W3sidXJpIjoiZ2l0K2h0dHBzOi8vZ2l0aHViLmNvbS9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob25AcmVmcy9oZWFkcy9kZXZlbG9wIiwiZGlnZXN0Ijp7InNoYTEiOiIyN2U0MjI5ODE2ZWU5ZWFlY2ZkYmY2ODI3NDQxMGE0OWQ2ZDM0YTQzIn19XX19", "payloadType":"application/vnd.in-toto+json", "signatures":[{"sig":"MEYCIQCLhazxTfEsVgUidGyVGC51qUYGzxQD7piIFT2qos3TFwIhALcmlS111yClrRgT7/AZQGsTbhlB28r1Mo2aEnuesuPA"}]}}
\ No newline at end of file
diff --git a/provenance/3.10.1a10/multiple.intoto.jsonl b/provenance/3.10.1a10/multiple.intoto.jsonl
new file mode 100644
index 00000000000..9c127b9774a
--- /dev/null
+++ b/provenance/3.10.1a10/multiple.intoto.jsonl
@@ -0,0 +1 @@
+{"mediaType":"application/vnd.dev.sigstore.bundle.v0.3+json", "verificationMaterial":{"certificate":{"rawBytes":"MIIHZzCCBu2gAwIBAgIUGHl1Iyn/4Z5ry179quTDxOQTUQ8wCgYIKoZIzj0EAwMwNzEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MR4wHAYDVQQDExVzaWdzdG9yZS1pbnRlcm1lZGlhdGUwHhcNMjUwNDIzMDgwNzU2WhcNMjUwNDIzMDgxNzU2WjAAMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEUjJRkJhNhmD77bzOnexs9DuEEqAmymBdE5GcI4/6tmmiZ6uEYaDUhj4e259iHeY0aCpHQWthPDKF5AjR1oFl8KOCBgwwggYIMA4GA1UdDwEB/wQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcDAzAdBgNVHQ4EFgQUHjRKPQN+9rzKo12a+ZpJqHkfLO8wHwYDVR0jBBgwFoAU39Ppz1YkEZb5qNjpKFWixi4YZD8wgYQGA1UdEQEB/wR6MHiGdmh0dHBzOi8vZ2l0aHViLmNvbS9zbHNhLWZyYW1ld29yay9zbHNhLWdpdGh1Yi1nZW5lcmF0b3IvLmdpdGh1Yi93b3JrZmxvd3MvZ2VuZXJhdG9yX2dlbmVyaWNfc2xzYTMueW1sQHJlZnMvdGFncy92Mi4xLjAwOQYKKwYBBAGDvzABAQQraHR0cHM6Ly90b2tlbi5hY3Rpb25zLmdpdGh1YnVzZXJjb250ZW50LmNvbTAWBgorBgEEAYO/MAECBAhzY2hlZHVsZTA2BgorBgEEAYO/MAEDBChkMDJhZjQ2MWYzOGY0NTYzODgyNjQ5MjU5ZGFjM2NjNzIwNzVlYzk0MBkGCisGAQQBg78wAQQEC1ByZS1SZWxlYXNlMDUGCisGAQQBg78wAQUEJ2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbjAgBgorBgEEAYO/MAEGBBJyZWZzL2hlYWRzL2RldmVsb3AwOwYKKwYBBAGDvzABCAQtDCtodHRwczovL3Rva2VuLmFjdGlvbnMuZ2l0aHVidXNlcmNvbnRlbnQuY29tMIGGBgorBgEEAYO/MAEJBHgMdmh0dHBzOi8vZ2l0aHViLmNvbS9zbHNhLWZyYW1ld29yay9zbHNhLWdpdGh1Yi1nZW5lcmF0b3IvLmdpdGh1Yi93b3JrZmxvd3MvZ2VuZXJhdG9yX2dlbmVyaWNfc2xzYTMueW1sQHJlZnMvdGFncy92Mi4xLjAwOAYKKwYBBAGDvzABCgQqDChmN2RkOGM1NGMyMDY3YmFmYzEyY2E3YTU1NTk1ZDVlZTliNzUyMDRhMB0GCisGAQQBg78wAQsEDwwNZ2l0aHViLWhvc3RlZDBKBgorBgEEAYO/MAEMBDwMOmh0dHBzOi8vZ2l0aHViLmNvbS9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24wOAYKKwYBBAGDvzABDQQqDChkMDJhZjQ2MWYzOGY0NTYzODgyNjQ5MjU5ZGFjM2NjNzIwNzVlYzk0MCIGCisGAQQBg78wAQ4EFAwScmVmcy9oZWFkcy9kZXZlbG9wMBkGCisGAQQBg78wAQ8ECwwJMjIxOTE5Mzc5MDEGCisGAQQBg78wARAEIwwhaHR0cHM6Ly9naXRodWIuY29tL2F3cy1wb3dlcnRvb2xzMBkGCisGAQQBg78wAREECwwJMTI5MTI3NjM4MH8GCisGAQQBg78wARIEcQxvaHR0cHM6Ly9naXRodWIuY29tL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi8uZ2l0aHViL3dvcmtmbG93cy9wcmUtcmVsZWFzZS55bWxAcmVmcy9oZWFkcy9kZXZlbG9wMDgGCisGAQQBg78wARMEKgwoZDAyYWY0NjFmMzhmNDU2Mzg4MjY0OTI1OWRhYzNjYzcyMDc1ZWM5NDAYBgorBgEEAYO/MAEUBAoMCHNjaGVkdWxlMG4GCisGAQQBg78wARUEYAxeaHR0cHM6Ly9naXRodWIuY29tL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9hY3Rpb25zL3J1bnMvMTQ2MTMwMTQ2MTEvYXR0ZW1wdHMvMTAWBgorBgEEAYO/MAEWBAgMBnB1YmxpYzCBiwYKKwYBBAHWeQIEAgR9BHsAeQB3AN09MGrGxxEyYxkeHJlnNwKiSl643jyt/4eKcoAvKe6OAAABlmGwdzEAAAQDAEgwRgIhAM9rZszOkgBTzaAtfhsaU9+jVYd4qA8U6cqzvdYS5H9JAiEAvHdE6R5QOxBph/Razwyf650VURuzZem0mwXB6jtJpOYwCgYIKoZIzj0EAwMDaAAwZQIxANAb9cU5O6PzPAQIRmJjdSFoXTKKHRKd+NEG6S1lJMr5rmIIQoLA7JpKi/gj8we+2AIwbmMo3XiLxIOzv7Y6SU4VCRe4bgV9B40aj7xri8X1yrJe7Sn7QJR5V3+7dJxq/OzI"}, "tlogEntries":[{"logIndex":"201270864", "logId":{"keyId":"wNI9atQGlz+VWfO6LRygH4QUfY/8W4RFwiT5i5WRgB0="}, "kindVersion":{"kind":"dsse", "version":"0.0.1"}, "integratedTime":"1745395677", "inclusionPromise":{"signedEntryTimestamp":"MEUCICsKdFn+TudBdPpybw4wfDFUrUoLc1SSsDjhxz+HQ3UGAiEAt3zkqMILNP1elO6HuD88JKeEFGa8WgrequN6b78tGVQ="}, "inclusionProof":{"logIndex":"79366602", "rootHash":"2n/Q4I75XR8Evz9cET5f+w8SAAiX1eH0AGiFTLn0aMM=", "treeSize":"79366603", "hashes":["nM92RaXe/EdAfnM2DGLJgPpFHB5gSZnxGKHtPkJsnv4=", "vlhrDSIf3eg0MLLkuynp8CKpBewtvyrGhFkWRq0GNfI=", "/76VDPsONxH+5JBuFfdb7/qjgSgY3/K0wD60493IQt8=", "2xHbl0YVIxManPkawD8CYhHCzQ2siFXy4fVLHgLQVuU=", "hlxHICftUBX+TK4bNuWqEMdTh1PxNTZ9xErmLaZ0E6k=", "kTzOd1/qFBsUfTwpc7Qk1UDvcEt/1ljcwRrhnghgLO4=", "3O8hgdiWfBGyOtXYndF+4g9i21x/JZlgWNFx4LpwiHE=", "pXw+6NsWRSeRYwAAWOgMeFP4WSX35T0TlfpXaS8xFu4=", "OsAChgkeij4XzumMOAR3IvniZZfw/3rJGBtNS2n4U0c=", "49Z3lxFb8hCrDQgPf5Kc9Zf0fMK9AsYmeQaIoKa2vrc=", "PHJDSL8Ui2OWQsJZ4vZa/V48UosV5lnRgMOoVTBsbDw=", "gGNvqHSiyarbPiEG0lmBLLIhU2F6djF/wmlcFeaQdP8=", "7v8qPHNDLerpduaMx06eb/MwgoQwczTn/cYGKX/9wZ4="], "checkpoint":{"envelope":"rekor.sigstore.dev - 1193050959916656506\n79366603\n2n/Q4I75XR8Evz9cET5f+w8SAAiX1eH0AGiFTLn0aMM=\n\n— rekor.sigstore.dev wNI9ajBFAiAFw91xskiGWQDX3H1GpfJJ79NBiA0Rl1xq4FnXj0JX0wIhAM44VdNbm8BCl9dxDRAIFQ/YIFBySUiid+eeG5F0pctF\n"}}, "canonicalizedBody":"eyJhcGlWZXJzaW9uIjoiMC4wLjEiLCJraW5kIjoiZHNzZSIsInNwZWMiOnsiZW52ZWxvcGVIYXNoIjp7ImFsZ29yaXRobSI6InNoYTI1NiIsInZhbHVlIjoiNTIzMTgxYjEwNDc1ZGU5ZDg1ZWU5MDEwZjU0ODk4ZDk4MTJiMDgzNjEzMTk3ZmU4ZDM4MGUxMDAxZTFjZWU1NSJ9LCJwYXlsb2FkSGFzaCI6eyJhbGdvcml0aG0iOiJzaGEyNTYiLCJ2YWx1ZSI6IjUzOWQ0M2Y4ZTliM2NmYTQ3OWM0OTEyNTA4NDFkZjlhZDg2ZmI5MGY0M2Y4YTBlMjZhMWIyZjlhMTc0Y2Y0NjcifSwic2lnbmF0dXJlcyI6W3sic2lnbmF0dXJlIjoiTUVVQ0lRQzZUTzN1WnBHeXM2TXRaaXNZekcrYkRRb3JEekRnU0lOclIwaGNPNmF0UndJZ0ZtYzgxMFkrL0NyN0Y1VkE1NDUrYXlhK3M5V3ZRajQzVHpJc1MvZ3krcWs9IiwidmVyaWZpZXIiOiJMUzB0TFMxQ1JVZEpUaUJEUlZKVVNVWkpRMEZVUlMwdExTMHRDazFKU1VoYWVrTkRRblV5WjBGM1NVSkJaMGxWUjBoc01VbDViaTgwV2pWeWVURTNPWEYxVkVSNFQxRlVWVkU0ZDBObldVbExiMXBKZW1vd1JVRjNUWGNLVG5wRlZrMUNUVWRCTVZWRlEyaE5UV015Ykc1ak0xSjJZMjFWZFZwSFZqSk5ValIzU0VGWlJGWlJVVVJGZUZaNllWZGtlbVJIT1hsYVV6RndZbTVTYkFwamJURnNXa2RzYUdSSFZYZElhR05PVFdwVmQwNUVTWHBOUkdkM1RucFZNbGRvWTA1TmFsVjNUa1JKZWsxRVozaE9lbFV5VjJwQlFVMUdhM2RGZDFsSUNrdHZXa2w2YWpCRFFWRlpTVXR2V2tsNmFqQkVRVkZqUkZGblFVVlZha3BTYTBwb1RtaHRSRGMzWW5wUGJtVjRjemxFZFVWRmNVRnRlVzFDWkVVMVIyTUtTVFF2Tm5SdGJXbGFOblZGV1dGRVZXaHFOR1V5TlRscFNHVlpNR0ZEY0VoUlYzUm9VRVJMUmpWQmFsSXhiMFpzT0V0UFEwSm5kM2RuWjFsSlRVRTBSd3BCTVZWa1JIZEZRaTkzVVVWQmQwbElaMFJCVkVKblRsWklVMVZGUkVSQlMwSm5aM0pDWjBWR1FsRmpSRUY2UVdSQ1owNVdTRkUwUlVablVWVklhbEpMQ2xCUlRpczVjbnBMYnpFeVlTdGFjRXB4U0d0bVRFODRkMGgzV1VSV1VqQnFRa0puZDBadlFWVXpPVkJ3ZWpGWmEwVmFZalZ4VG1wd1MwWlhhWGhwTkZrS1drUTRkMmRaVVVkQk1WVmtSVkZGUWk5M1VqWk5TR2xIWkcxb01HUklRbnBQYVRoMldqSnNNR0ZJVm1sTWJVNTJZbE01ZW1KSVRtaE1WMXA1V1ZjeGJBcGtNamw1WVhrNWVtSklUbWhNVjJSd1pFZG9NVmxwTVc1YVZ6VnNZMjFHTUdJelNYWk1iV1J3WkVkb01WbHBPVE5pTTBweVdtMTRkbVF6VFhaYU1sWjFDbHBZU21oa1J6bDVXREprYkdKdFZubGhWMDVtWXpKNGVsbFVUWFZsVnpGelVVaEtiRnB1VFhaa1IwWnVZM2s1TWsxcE5IaE1ha0YzVDFGWlMwdDNXVUlLUWtGSFJIWjZRVUpCVVZGeVlVaFNNR05JVFRaTWVUa3dZakowYkdKcE5XaFpNMUp3WWpJMWVreHRaSEJrUjJneFdXNVdlbHBZU21waU1qVXdXbGMxTUFwTWJVNTJZbFJCVjBKbmIzSkNaMFZGUVZsUEwwMUJSVU5DUVdoNldUSm9iRnBJVm5OYVZFRXlRbWR2Y2tKblJVVkJXVTh2VFVGRlJFSkRhR3ROUkVwb0NscHFVVEpOVjFsNlQwZFpNRTVVV1hwUFJHZDVUbXBSTlUxcVZUVmFSMFpxVFRKT2FrNTZTWGRPZWxac1dYcHJNRTFDYTBkRGFYTkhRVkZSUW1jM09IY0tRVkZSUlVNeFFubGFVekZUV2xkNGJGbFlUbXhOUkZWSFEybHpSMEZSVVVKbk56aDNRVkZWUlVveVJqTmplVEYzWWpOa2JHTnVVblppTW5oNlRETkNkZ3BrTWxaNVpFYzVkbUpJVFhSaVIwWjBXVzFTYUV4WVFqVmtSMmgyWW1wQlowSm5iM0pDWjBWRlFWbFBMMDFCUlVkQ1FrcDVXbGRhZWt3eWFHeFpWMUo2Q2t3eVVteGtiVlp6WWpOQmQwOTNXVXRMZDFsQ1FrRkhSSFo2UVVKRFFWRjBSRU4wYjJSSVVuZGplbTkyVEROU2RtRXlWblZNYlVacVpFZHNkbUp1VFhVS1dqSnNNR0ZJVm1sa1dFNXNZMjFPZG1KdVVteGlibEYxV1RJNWRFMUpSMGRDWjI5eVFtZEZSVUZaVHk5TlFVVktRa2huVFdSdGFEQmtTRUo2VDJrNGRncGFNbXd3WVVoV2FVeHRUblppVXpsNllraE9hRXhYV25sWlZ6RnNaREk1ZVdGNU9YcGlTRTVvVEZka2NHUkhhREZaYVRGdVdsYzFiR050UmpCaU0wbDJDa3h0WkhCa1IyZ3hXV2s1TTJJelNuSmFiWGgyWkROTmRsb3lWblZhV0Vwb1pFYzVlVmd5Wkd4aWJWWjVZVmRPWm1NeWVIcFpWRTExWlZjeGMxRklTbXdLV201TmRtUkhSbTVqZVRreVRXazBlRXhxUVhkUFFWbExTM2RaUWtKQlIwUjJla0ZDUTJkUmNVUkRhRzFPTWxKclQwZE5NVTVIVFhsTlJGa3pXVzFHYlFwWmVrVjVXVEpGTTFsVVZURk9WR3N4V2tSV2JGcFViR2xPZWxWNVRVUlNhRTFDTUVkRGFYTkhRVkZSUW1jM09IZEJVWE5GUkhkM1Rsb3liREJoU0ZacENreFhhSFpqTTFKc1drUkNTMEpuYjNKQ1owVkZRVmxQTDAxQlJVMUNSSGROVDIxb01HUklRbnBQYVRoMldqSnNNR0ZJVm1sTWJVNTJZbE01YUdRelRYUUtZMGM1TTFwWVNqQmlNamx6WTNrNWQySXpaR3hqYmxKMllqSjRla3hYZUdoaVYwcHJXVk14ZDJWWVVtOWlNalIzVDBGWlMwdDNXVUpDUVVkRWRucEJRZ3BFVVZGeFJFTm9hMDFFU21oYWFsRXlUVmRaZWs5SFdUQk9WRmw2VDBSbmVVNXFVVFZOYWxVMVdrZEdhazB5VG1wT2VrbDNUbnBXYkZsNmF6Qk5RMGxIQ2tOcGMwZEJVVkZDWnpjNGQwRlJORVZHUVhkVFkyMVdiV041T1c5YVYwWnJZM2s1YTFwWVdteGlSemwzVFVKclIwTnBjMGRCVVZGQ1p6YzRkMEZST0VVS1EzZDNTazFxU1hoUFZFVTFUWHBqTlUxRVJVZERhWE5IUVZGUlFtYzNPSGRCVWtGRlNYZDNhR0ZJVWpCalNFMDJUSGs1Ym1GWVVtOWtWMGwxV1RJNWRBcE1Na1l6WTNreGQySXpaR3hqYmxKMllqSjRlazFDYTBkRGFYTkhRVkZSUW1jM09IZEJVa1ZGUTNkM1NrMVVTVFZOVkVrelRtcE5ORTFJT0VkRGFYTkhDa0ZSVVVKbk56aDNRVkpKUldOUmVIWmhTRkl3WTBoTk5reDVPVzVoV0ZKdlpGZEpkVmt5T1hSTU1rWXpZM2t4ZDJJelpHeGpibEoyWWpKNGVrd3pRbllLWkRKV2VXUkhPWFppU0UxMFlrZEdkRmx0VW1oTVdFSTFaRWRvZG1KcE9IVmFNbXd3WVVoV2FVd3paSFpqYlhSdFlrYzVNMk41T1hkamJWVjBZMjFXY3dwYVYwWjZXbE0xTldKWGVFRmpiVlp0WTNrNWIxcFhSbXRqZVRscldsaGFiR0pIT1hkTlJHZEhRMmx6UjBGUlVVSm5OemgzUVZKTlJVdG5kMjlhUkVGNUNsbFhXVEJPYWtadFRYcG9iVTVFVlRKTmVtYzBUV3BaTUU5VVNURlBWMUpvV1hwT2FsbDZZM2xOUkdNeFdsZE5OVTVFUVZsQ1oyOXlRbWRGUlVGWlR5OEtUVUZGVlVKQmIwMURTRTVxWVVkV2EyUlhlR3hOUnpSSFEybHpSMEZSVVVKbk56aDNRVkpWUlZsQmVHVmhTRkl3WTBoTk5reDVPVzVoV0ZKdlpGZEpkUXBaTWpsMFRESkdNMk41TVhkaU0yUnNZMjVTZG1JeWVIcE1NMEoyWkRKV2VXUkhPWFppU0UxMFlrZEdkRmx0VW1oTVdFSTFaRWRvZG1KcE9XaFpNMUp3Q21JeU5YcE1NMG94WW01TmRrMVVVVEpOVkUxM1RWUlJNazFVUlhaWldGSXdXbGN4ZDJSSVRYWk5WRUZYUW1kdmNrSm5SVVZCV1U4dlRVRkZWMEpCWjAwS1FtNUNNVmx0ZUhCWmVrTkNhWGRaUzB0M1dVSkNRVWhYWlZGSlJVRm5VamxDU0hOQlpWRkNNMEZPTURsTlIzSkhlSGhGZVZsNGEyVklTbXh1VG5kTGFRcFRiRFkwTTJwNWRDODBaVXRqYjBGMlMyVTJUMEZCUVVKc2JVZDNaSHBGUVVGQlVVUkJSV2QzVW1kSmFFRk5PWEphYzNwUGEyZENWSHBoUVhSbWFITmhDbFU1SzJwV1dXUTBjVUU0VlRaamNYcDJaRmxUTlVnNVNrRnBSVUYyU0dSRk5sSTFVVTk0UW5Cb0wxSmhlbmQ1WmpZMU1GWlZVblY2V21WdE1HMTNXRUlLTm1wMFNuQlBXWGREWjFsSlMyOWFTWHBxTUVWQmQwMUVZVUZCZDFwUlNYaEJUa0ZpT1dOVk5VODJVSHBRUVZGSlVtMUthbVJUUm05WVZFdExTRkpMWkFvclRrVkhObE14YkVwTmNqVnliVWxKVVc5TVFUZEtjRXRwTDJkcU9IZGxLekpCU1hkaWJVMXZNMWhwVEhoSlQzcDJOMWsyVTFVMFZrTlNaVFJpWjFZNUNrSTBNR0ZxTjNoeWFUaFlNWGx5U21VM1UyNDNVVXBTTlZZekt6ZGtTbmh4TDA5NlNRb3RMUzB0TFVWT1JDQkRSVkpVU1VaSlEwRlVSUzB0TFMwdENnPT0ifV19fQ=="}]}, "dsseEnvelope":{"payload":"eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjAuMSIsInByZWRpY2F0ZVR5cGUiOiJodHRwczovL3Nsc2EuZGV2L3Byb3ZlbmFuY2UvdjAuMiIsInN1YmplY3QiOlt7Im5hbWUiOiIuL2F3c19sYW1iZGFfcG93ZXJ0b29scy0zLjEwLjFhMTAtcHkzLW5vbmUtYW55LndobCIsImRpZ2VzdCI6eyJzaGEyNTYiOiI1ZTQ1YTBhMGY0NjU3NTRkMGRjZTRhZDM2NzIxMGZiNDlhMjBiNjhlYjhkODFkZWM2MWY5Yjg3NGJmN2M4OGY4In19LHsibmFtZSI6Ii4vYXdzX2xhbWJkYV9wb3dlcnRvb2xzLTMuMTAuMWExMC50YXIuZ3oiLCJkaWdlc3QiOnsic2hhMjU2IjoiNzQxYTg1NDFkYTNlMWMwYWE1MWQxNDUxMTNjMGI1NDI1NzhiNTI0MjE2YjNjM2M5Mzc5YWI5ZDZjNWEyNTE4NCJ9fV0sInByZWRpY2F0ZSI6eyJidWlsZGVyIjp7ImlkIjoiaHR0cHM6Ly9naXRodWIuY29tL3Nsc2EtZnJhbWV3b3JrL3Nsc2EtZ2l0aHViLWdlbmVyYXRvci8uZ2l0aHViL3dvcmtmbG93cy9nZW5lcmF0b3JfZ2VuZXJpY19zbHNhMy55bWxAcmVmcy90YWdzL3YyLjEuMCJ9LCJidWlsZFR5cGUiOiJodHRwczovL2dpdGh1Yi5jb20vc2xzYS1mcmFtZXdvcmsvc2xzYS1naXRodWItZ2VuZXJhdG9yL2dlbmVyaWNAdjEiLCJpbnZvY2F0aW9uIjp7ImNvbmZpZ1NvdXJjZSI6eyJ1cmkiOiJnaXQraHR0cHM6Ly9naXRodWIuY29tL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbkByZWZzL2hlYWRzL2RldmVsb3AiLCJkaWdlc3QiOnsic2hhMSI6ImQwMmFmNDYxZjM4ZjQ1NjM4ODI2NDkyNTlkYWMzY2M3MjA3NWVjOTQifSwiZW50cnlQb2ludCI6Ii5naXRodWIvd29ya2Zsb3dzL3ByZS1yZWxlYXNlLnltbCJ9LCJwYXJhbWV0ZXJzIjp7InZhcnMiOnt9fSwiZW52aXJvbm1lbnQiOnsiZ2l0aHViX2FjdG9yIjoibGVhbmRyb2RhbWFzY2VuYSIsImdpdGh1Yl9hY3Rvcl9pZCI6IjQyOTUxNzMiLCJnaXRodWJfYmFzZV9yZWYiOiIiLCJnaXRodWJfZXZlbnRfbmFtZSI6InNjaGVkdWxlIiwiZ2l0aHViX2V2ZW50X3BheWxvYWQiOnsiZW50ZXJwcmlzZSI6eyJhdmF0YXJfdXJsIjoiaHR0cHM6Ly9hdmF0YXJzLmdpdGh1YnVzZXJjb250ZW50LmNvbS9iLzEyOTA/dj00IiwiY3JlYXRlZF9hdCI6IjIwMTktMTEtMTNUMTg6MDU6NDFaIiwiZGVzY3JpcHRpb24iOiIiLCJodG1sX3VybCI6Imh0dHBzOi8vZ2l0aHViLmNvbS9lbnRlcnByaXNlcy9hbWF6b24iLCJpZCI6MTI5MCwibmFtZSI6IkFtYXpvbiIsIm5vZGVfaWQiOiJNREV3T2tWdWRHVnljSEpwYzJVeE1qa3ciLCJzbHVnIjoiYW1hem9uIiwidXBkYXRlZF9hdCI6IjIwMjQtMDktMzBUMjE6MDI6MzBaIiwid2Vic2l0ZV91cmwiOiJodHRwczovL3d3dy5hbWF6b24uY29tLyJ9LCJvcmdhbml6YXRpb24iOnsiYXZhdGFyX3VybCI6Imh0dHBzOi8vYXZhdGFycy5naXRodWJ1c2VyY29udGVudC5jb20vdS8xMjkxMjc2Mzg/dj00IiwiZGVzY3JpcHRpb24iOiIiLCJldmVudHNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9vcmdzL2F3cy1wb3dlcnRvb2xzL2V2ZW50cyIsImhvb2tzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vb3Jncy9hd3MtcG93ZXJ0b29scy9ob29rcyIsImlkIjoxMjkxMjc2MzgsImlzc3Vlc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL29yZ3MvYXdzLXBvd2VydG9vbHMvaXNzdWVzIiwibG9naW4iOiJhd3MtcG93ZXJ0b29scyIsIm1lbWJlcnNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9vcmdzL2F3cy1wb3dlcnRvb2xzL21lbWJlcnN7L21lbWJlcn0iLCJub2RlX2lkIjoiT19rZ0RPQjdKVTFnIiwicHVibGljX21lbWJlcnNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9vcmdzL2F3cy1wb3dlcnRvb2xzL3B1YmxpY19tZW1iZXJzey9tZW1iZXJ9IiwicmVwb3NfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9vcmdzL2F3cy1wb3dlcnRvb2xzL3JlcG9zIiwidXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9vcmdzL2F3cy1wb3dlcnRvb2xzIn0sInJlcG9zaXRvcnkiOnsiYWxsb3dfZm9ya2luZyI6dHJ1ZSwiYXJjaGl2ZV91cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi97YXJjaGl2ZV9mb3JtYXR9ey9yZWZ9IiwiYXJjaGl2ZWQiOmZhbHNlLCJhc3NpZ25lZXNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vYXNzaWduZWVzey91c2VyfSIsImJsb2JzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2dpdC9ibG9ic3svc2hhfSIsImJyYW5jaGVzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2JyYW5jaGVzey9icmFuY2h9IiwiY2xvbmVfdXJsIjoiaHR0cHM6Ly9naXRodWIuY29tL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi5naXQiLCJjb2xsYWJvcmF0b3JzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2NvbGxhYm9yYXRvcnN7L2NvbGxhYm9yYXRvcn0iLCJjb21tZW50c191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9jb21tZW50c3svbnVtYmVyfSIsImNvbW1pdHNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vY29tbWl0c3svc2hhfSIsImNvbXBhcmVfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vY29tcGFyZS97YmFzZX0uLi57aGVhZH0iLCJjb250ZW50c191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9jb250ZW50cy97K3BhdGh9IiwiY29udHJpYnV0b3JzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2NvbnRyaWJ1dG9ycyIsImNyZWF0ZWRfYXQiOiIyMDE5LTExLTE1VDEyOjI2OjEyWiIsImN1c3RvbV9wcm9wZXJ0aWVzIjp7fSwiZGVmYXVsdF9icmFuY2giOiJkZXZlbG9wIiwiZGVwbG95bWVudHNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vZGVwbG95bWVudHMiLCJkZXNjcmlwdGlvbiI6IkEgZGV2ZWxvcGVyIHRvb2xraXQgdG8gaW1wbGVtZW50IFNlcnZlcmxlc3MgYmVzdCBwcmFjdGljZXMgYW5kIGluY3JlYXNlIGRldmVsb3BlciB2ZWxvY2l0eS4iLCJkaXNhYmxlZCI6ZmFsc2UsImRvd25sb2Fkc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9kb3dubG9hZHMiLCJldmVudHNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vZXZlbnRzIiwiZm9yayI6ZmFsc2UsImZvcmtzIjo0MjEsImZvcmtzX2NvdW50Ijo0MjEsImZvcmtzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2ZvcmtzIiwiZnVsbF9uYW1lIjoiYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uIiwiZ2l0X2NvbW1pdHNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vZ2l0L2NvbW1pdHN7L3NoYX0iLCJnaXRfcmVmc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9naXQvcmVmc3svc2hhfSIsImdpdF90YWdzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2dpdC90YWdzey9zaGF9IiwiZ2l0X3VybCI6ImdpdDovL2dpdGh1Yi5jb20vYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uLmdpdCIsImhhc19kaXNjdXNzaW9ucyI6dHJ1ZSwiaGFzX2Rvd25sb2FkcyI6dHJ1ZSwiaGFzX2lzc3VlcyI6dHJ1ZSwiaGFzX3BhZ2VzIjpmYWxzZSwiaGFzX3Byb2plY3RzIjp0cnVlLCJoYXNfd2lraSI6ZmFsc2UsImhvbWVwYWdlIjoiaHR0cHM6Ly9kb2NzLnBvd2VydG9vbHMuYXdzLmRldi9sYW1iZGEvcHl0aG9uL2xhdGVzdC8iLCJob29rc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9ob29rcyIsImh0bWxfdXJsIjoiaHR0cHM6Ly9naXRodWIuY29tL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbiIsImlkIjoyMjE5MTkzNzksImlzX3RlbXBsYXRlIjpmYWxzZSwiaXNzdWVfY29tbWVudF91cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9pc3N1ZXMvY29tbWVudHN7L251bWJlcn0iLCJpc3N1ZV9ldmVudHNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vaXNzdWVzL2V2ZW50c3svbnVtYmVyfSIsImlzc3Vlc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9pc3N1ZXN7L251bWJlcn0iLCJrZXlzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2tleXN7L2tleV9pZH0iLCJsYWJlbHNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vbGFiZWxzey9uYW1lfSIsImxhbmd1YWdlIjoiUHl0aG9uIiwibGFuZ3VhZ2VzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2xhbmd1YWdlcyIsImxpY2Vuc2UiOnsia2V5IjoibWl0LTAiLCJuYW1lIjoiTUlUIE5vIEF0dHJpYnV0aW9uIiwibm9kZV9pZCI6Ik1EYzZUR2xqWlc1elpUUXgiLCJzcGR4X2lkIjoiTUlULTAiLCJ1cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL2xpY2Vuc2VzL21pdC0wIn0sIm1lcmdlc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9tZXJnZXMiLCJtaWxlc3RvbmVzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL21pbGVzdG9uZXN7L251bWJlcn0iLCJtaXJyb3JfdXJsIjpudWxsLCJuYW1lIjoicG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uIiwibm9kZV9pZCI6Ik1ERXdPbEpsY0c5emFYUnZjbmt5TWpFNU1Ua3pOems9Iiwibm90aWZpY2F0aW9uc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9ub3RpZmljYXRpb25zez9zaW5jZSxhbGwscGFydGljaXBhdGluZ30iLCJvcGVuX2lzc3VlcyI6NDgsIm9wZW5faXNzdWVzX2NvdW50Ijo0OCwib3duZXIiOnsiYXZhdGFyX3VybCI6Imh0dHBzOi8vYXZhdGFycy5naXRodWJ1c2VyY29udGVudC5jb20vdS8xMjkxMjc2Mzg/dj00IiwiZXZlbnRzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vdXNlcnMvYXdzLXBvd2VydG9vbHMvZXZlbnRzey9wcml2YWN5fSIsImZvbGxvd2Vyc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3VzZXJzL2F3cy1wb3dlcnRvb2xzL2ZvbGxvd2VycyIsImZvbGxvd2luZ191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3VzZXJzL2F3cy1wb3dlcnRvb2xzL2ZvbGxvd2luZ3svb3RoZXJfdXNlcn0iLCJnaXN0c191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3VzZXJzL2F3cy1wb3dlcnRvb2xzL2dpc3Rzey9naXN0X2lkfSIsImdyYXZhdGFyX2lkIjoiIiwiaHRtbF91cmwiOiJodHRwczovL2dpdGh1Yi5jb20vYXdzLXBvd2VydG9vbHMiLCJpZCI6MTI5MTI3NjM4LCJsb2dpbiI6ImF3cy1wb3dlcnRvb2xzIiwibm9kZV9pZCI6Ik9fa2dET0I3SlUxZyIsIm9yZ2FuaXphdGlvbnNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS91c2Vycy9hd3MtcG93ZXJ0b29scy9vcmdzIiwicmVjZWl2ZWRfZXZlbnRzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vdXNlcnMvYXdzLXBvd2VydG9vbHMvcmVjZWl2ZWRfZXZlbnRzIiwicmVwb3NfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS91c2Vycy9hd3MtcG93ZXJ0b29scy9yZXBvcyIsInNpdGVfYWRtaW4iOmZhbHNlLCJzdGFycmVkX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vdXNlcnMvYXdzLXBvd2VydG9vbHMvc3RhcnJlZHsvb3duZXJ9ey9yZXBvfSIsInN1YnNjcmlwdGlvbnNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS91c2Vycy9hd3MtcG93ZXJ0b29scy9zdWJzY3JpcHRpb25zIiwidHlwZSI6Ik9yZ2FuaXphdGlvbiIsInVybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vdXNlcnMvYXdzLXBvd2VydG9vbHMiLCJ1c2VyX3ZpZXdfdHlwZSI6InB1YmxpYyJ9LCJwcml2YXRlIjpmYWxzZSwicHVsbHNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vcHVsbHN7L251bWJlcn0iLCJwdXNoZWRfYXQiOiIyMDI1LTA0LTIzVDA0OjEwOjA3WiIsInJlbGVhc2VzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL3JlbGVhc2Vzey9pZH0iLCJzaXplIjoxMTA4MDAsInNzaF91cmwiOiJnaXRAZ2l0aHViLmNvbTphd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24uZ2l0Iiwic3RhcmdhemVyc19jb3VudCI6MzAyNywic3RhcmdhemVyc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9zdGFyZ2F6ZXJzIiwic3RhdHVzZXNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vc3RhdHVzZXMve3NoYX0iLCJzdWJzY3JpYmVyc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9zdWJzY3JpYmVycyIsInN1YnNjcmlwdGlvbl91cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9zdWJzY3JpcHRpb24iLCJzdm5fdXJsIjoiaHR0cHM6Ly9naXRodWIuY29tL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbiIsInRhZ3NfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vdGFncyIsInRlYW1zX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL3RlYW1zIiwidG9waWNzIjpbImF3cyIsImF3cy1sYW1iZGEiLCJoYWNrdG9iZXJmZXN0IiwibGFtYmRhIiwicHl0aG9uIiwic2VydmVybGVzcyJdLCJ0cmVlc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9naXQvdHJlZXN7L3NoYX0iLCJ1cGRhdGVkX2F0IjoiMjAyNS0wNC0yM1QwNDoxMDoxMFoiLCJ1cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbiIsInZpc2liaWxpdHkiOiJwdWJsaWMiLCJ3YXRjaGVycyI6MzAyNywid2F0Y2hlcnNfY291bnQiOjMwMjcsIndlYl9jb21taXRfc2lnbm9mZl9yZXF1aXJlZCI6dHJ1ZX0sInNjaGVkdWxlIjoiMCA4ICogKiAxLTUiLCJ3b3JrZmxvdyI6Ii5naXRodWIvd29ya2Zsb3dzL3ByZS1yZWxlYXNlLnltbCJ9LCJnaXRodWJfaGVhZF9yZWYiOiIiLCJnaXRodWJfcmVmIjoicmVmcy9oZWFkcy9kZXZlbG9wIiwiZ2l0aHViX3JlZl90eXBlIjoiYnJhbmNoIiwiZ2l0aHViX3JlcG9zaXRvcnlfaWQiOiIyMjE5MTkzNzkiLCJnaXRodWJfcmVwb3NpdG9yeV9vd25lciI6ImF3cy1wb3dlcnRvb2xzIiwiZ2l0aHViX3JlcG9zaXRvcnlfb3duZXJfaWQiOiIxMjkxMjc2MzgiLCJnaXRodWJfcnVuX2F0dGVtcHQiOiIxIiwiZ2l0aHViX3J1bl9pZCI6IjE0NjEzMDE0NjExIiwiZ2l0aHViX3J1bl9udW1iZXIiOiIyMjUiLCJnaXRodWJfc2hhMSI6ImQwMmFmNDYxZjM4ZjQ1NjM4ODI2NDkyNTlkYWMzY2M3MjA3NWVjOTQifX0sIm1ldGFkYXRhIjp7ImJ1aWxkSW52b2NhdGlvbklEIjoiMTQ2MTMwMTQ2MTEtMSIsImNvbXBsZXRlbmVzcyI6eyJwYXJhbWV0ZXJzIjp0cnVlLCJlbnZpcm9ubWVudCI6ZmFsc2UsIm1hdGVyaWFscyI6ZmFsc2V9LCJyZXByb2R1Y2libGUiOmZhbHNlfSwibWF0ZXJpYWxzIjpbeyJ1cmkiOiJnaXQraHR0cHM6Ly9naXRodWIuY29tL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbkByZWZzL2hlYWRzL2RldmVsb3AiLCJkaWdlc3QiOnsic2hhMSI6ImQwMmFmNDYxZjM4ZjQ1NjM4ODI2NDkyNTlkYWMzY2M3MjA3NWVjOTQifX1dfX0=", "payloadType":"application/vnd.in-toto+json", "signatures":[{"sig":"MEUCIQC6TO3uZpGys6MtZisYzG+bDQorDzDgSINrR0hcO6atRwIgFmc810Y+/Cr7F5VA545+aya+s9WvQj43TzIsS/gy+qk="}]}}
\ No newline at end of file
diff --git a/provenance/3.10.1a11/multiple.intoto.jsonl b/provenance/3.10.1a11/multiple.intoto.jsonl
new file mode 100644
index 00000000000..bd1911dce8e
--- /dev/null
+++ b/provenance/3.10.1a11/multiple.intoto.jsonl
@@ -0,0 +1 @@
+{"mediaType":"application/vnd.dev.sigstore.bundle.v0.3+json", "verificationMaterial":{"certificate":{"rawBytes":"MIIHZTCCBuygAwIBAgIUaKrB2v4fZM3JQHz9uhEFHlmSJB4wCgYIKoZIzj0EAwMwNzEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MR4wHAYDVQQDExVzaWdzdG9yZS1pbnRlcm1lZGlhdGUwHhcNMjUwNDI0MDgwNzQwWhcNMjUwNDI0MDgxNzQwWjAAMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEM6Riq6wGTkeRnbvsBPqfP4lTIqr7wPqoW66WugOA1tFht3uQry3dHwpL5wmnPEHOQeHIu+I7/prJEvfxMuuxkaOCBgswggYHMA4GA1UdDwEB/wQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcDAzAdBgNVHQ4EFgQUiktxpeFCXdrQfgumMx6jtkeecgQwHwYDVR0jBBgwFoAU39Ppz1YkEZb5qNjpKFWixi4YZD8wgYQGA1UdEQEB/wR6MHiGdmh0dHBzOi8vZ2l0aHViLmNvbS9zbHNhLWZyYW1ld29yay9zbHNhLWdpdGh1Yi1nZW5lcmF0b3IvLmdpdGh1Yi93b3JrZmxvd3MvZ2VuZXJhdG9yX2dlbmVyaWNfc2xzYTMueW1sQHJlZnMvdGFncy92Mi4xLjAwOQYKKwYBBAGDvzABAQQraHR0cHM6Ly90b2tlbi5hY3Rpb25zLmdpdGh1YnVzZXJjb250ZW50LmNvbTAWBgorBgEEAYO/MAECBAhzY2hlZHVsZTA2BgorBgEEAYO/MAEDBCgyNWVhYTE2Njk3MThiNjU4ZmZlZTBiNjI5MzExZTlmYzNhOTdlYjIxMBkGCisGAQQBg78wAQQEC1ByZS1SZWxlYXNlMDUGCisGAQQBg78wAQUEJ2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbjAgBgorBgEEAYO/MAEGBBJyZWZzL2hlYWRzL2RldmVsb3AwOwYKKwYBBAGDvzABCAQtDCtodHRwczovL3Rva2VuLmFjdGlvbnMuZ2l0aHVidXNlcmNvbnRlbnQuY29tMIGGBgorBgEEAYO/MAEJBHgMdmh0dHBzOi8vZ2l0aHViLmNvbS9zbHNhLWZyYW1ld29yay9zbHNhLWdpdGh1Yi1nZW5lcmF0b3IvLmdpdGh1Yi93b3JrZmxvd3MvZ2VuZXJhdG9yX2dlbmVyaWNfc2xzYTMueW1sQHJlZnMvdGFncy92Mi4xLjAwOAYKKwYBBAGDvzABCgQqDChmN2RkOGM1NGMyMDY3YmFmYzEyY2E3YTU1NTk1ZDVlZTliNzUyMDRhMB0GCisGAQQBg78wAQsEDwwNZ2l0aHViLWhvc3RlZDBKBgorBgEEAYO/MAEMBDwMOmh0dHBzOi8vZ2l0aHViLmNvbS9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24wOAYKKwYBBAGDvzABDQQqDCgyNWVhYTE2Njk3MThiNjU4ZmZlZTBiNjI5MzExZTlmYzNhOTdlYjIxMCIGCisGAQQBg78wAQ4EFAwScmVmcy9oZWFkcy9kZXZlbG9wMBkGCisGAQQBg78wAQ8ECwwJMjIxOTE5Mzc5MDEGCisGAQQBg78wARAEIwwhaHR0cHM6Ly9naXRodWIuY29tL2F3cy1wb3dlcnRvb2xzMBkGCisGAQQBg78wAREECwwJMTI5MTI3NjM4MH8GCisGAQQBg78wARIEcQxvaHR0cHM6Ly9naXRodWIuY29tL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi8uZ2l0aHViL3dvcmtmbG93cy9wcmUtcmVsZWFzZS55bWxAcmVmcy9oZWFkcy9kZXZlbG9wMDgGCisGAQQBg78wARMEKgwoMjVlYWExNjY5NzE4YjY1OGZmZWUwYjYyOTMxMWU5ZmMzYTk3ZWIyMTAYBgorBgEEAYO/MAEUBAoMCHNjaGVkdWxlMG4GCisGAQQBg78wARUEYAxeaHR0cHM6Ly9naXRodWIuY29tL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9hY3Rpb25zL3J1bnMvMTQ2MzY1MDc3NjcvYXR0ZW1wdHMvMTAWBgorBgEEAYO/MAEWBAgMBnB1YmxpYzCBigYKKwYBBAHWeQIEAgR8BHoAeAB2AN09MGrGxxEyYxkeHJlnNwKiSl643jyt/4eKcoAvKe6OAAABlmbWkkwAAAQDAEcwRQIhAJ18NyFpXgtP3aWD9d4y2Cx6Qa202D7SoW2NsW9opmsHAiBb5+oPZma+TvLOYOYjd4K/oEFrsFLBO40PIDdIq9ye7jAKBggqhkjOPQQDAwNnADBkAjApy/yGseN/A2yGD6oHglvoKVQ8AV5aQiMg6mm95HbMMlhmtA0DdKzsZUAMzeu8J1ACMD3VrLW4vLOHn88TnUwOKrlcEXzxFU6P6OWRrPkRJFlz9HWbYBtUGyWEHmQUwECWpQ=="}, "tlogEntries":[{"logIndex":"201969876", "logId":{"keyId":"wNI9atQGlz+VWfO6LRygH4QUfY/8W4RFwiT5i5WRgB0="}, "kindVersion":{"kind":"dsse", "version":"0.0.1"}, "integratedTime":"1745482060", "inclusionPromise":{"signedEntryTimestamp":"MEQCIFygExzlMISTdL6ozYf5oxQ4c/kHqOyc89BT3y1TbOgvAiAtoS0eOKBm1KvMsnb8smLweNhbzju1hOt1tHTZEIUzig=="}, "inclusionProof":{"logIndex":"80065614", "rootHash":"Kg1tFbbJMVVSVheTWGpSlEQo40r1zQtyznawZyW96+s=", "treeSize":"80065615", "hashes":["ShEtoatZEhNSD1A6HKHk41c8j6eY+kAFO8ZliAf6fFM=", "SQD26ieT0ASMZ2QEy9q8TGxlD0DY6Ge4Zr1Q1/HH5As=", "J/1q25YgdU3KIcx6fEIo6kfA+Vd0lc7Qd3pMGoGAH9k=", "g6t0jfHcMjKTLRWzixqzt7arAqPb2WiznoKt0O362oM=", "Xlr/Wrbyzy4f0SYAMTtk3re9bowLmosprnOSCrUtX5I=", "xZ3FqOhxbqwG0npS43EHX5Ftn07WAAWcgMkR5y6X65Q=", "BwRL+z5Rj3crk1OxEx+OsPVGY7SR5IZ+nEExBSkfW0U=", "mXDnc4jfjT95qBQGxcbOeiJfOoqxWOC+LNuquEN1FX0=", "R06b5yKhctTUnPLU9C41HaCq0G2m6pH+c6fJPVOZ3io=", "TFZzqXVlkqB0HywtoNLcsLW3GP6kC9360IVVQWwjq80=", "0Km8UrfRhoUuq7G4OPTXTFR20l/6nmxe8V5EfzOhgx4=", "gGNvqHSiyarbPiEG0lmBLLIhU2F6djF/wmlcFeaQdP8=", "7v8qPHNDLerpduaMx06eb/MwgoQwczTn/cYGKX/9wZ4="], "checkpoint":{"envelope":"rekor.sigstore.dev - 1193050959916656506\n80065615\nKg1tFbbJMVVSVheTWGpSlEQo40r1zQtyznawZyW96+s=\n\n— rekor.sigstore.dev wNI9ajBEAiBZIlD022w06/fsFx7cB8BhQwOYNXMgK4e2GMzYZJxGuwIgQmjy7wp24L/Do/Fyqn7D4B/AUc/NHRJb+Z68slY+Rc8=\n"}}, "canonicalizedBody":"eyJhcGlWZXJzaW9uIjoiMC4wLjEiLCJraW5kIjoiZHNzZSIsInNwZWMiOnsiZW52ZWxvcGVIYXNoIjp7ImFsZ29yaXRobSI6InNoYTI1NiIsInZhbHVlIjoiNmU0NGFjNjExYTk3MGJkYjFhMGY4ZDk1NWRmMTg3YTA3OGVjNjdlYTVhZDU3MGNkODJhZjEwZWY2ZDI1MjllMCJ9LCJwYXlsb2FkSGFzaCI6eyJhbGdvcml0aG0iOiJzaGEyNTYiLCJ2YWx1ZSI6IjQ1NjVlMzNlNTlhYzg1Mjk2YzQ0ZDZhN2ViMjc4YmJjYmIwMDk2ZTk2OTVjN2YxYWJjM2IyNTViNzM5MzAwNDQifSwic2lnbmF0dXJlcyI6W3sic2lnbmF0dXJlIjoiTUVVQ0lCNUtLK3BtQ05DRlNOMm1GNUxiNEJMQ3RTNmp6ZnZoOWNTemRPTTBEbGtkQWlFQXNrU1YyS09oRGNlN1gzT2NrcE94amx6Und4SE9sNXE1eGlwcU95QklGMFk9IiwidmVyaWZpZXIiOiJMUzB0TFMxQ1JVZEpUaUJEUlZKVVNVWkpRMEZVUlMwdExTMHRDazFKU1VoYVZFTkRRblY1WjBGM1NVSkJaMGxWWVV0eVFqSjJOR1phVFROS1VVaDZPWFZvUlVaSWJHMVRTa0kwZDBObldVbExiMXBKZW1vd1JVRjNUWGNLVG5wRlZrMUNUVWRCTVZWRlEyaE5UV015Ykc1ak0xSjJZMjFWZFZwSFZqSk5ValIzU0VGWlJGWlJVVVJGZUZaNllWZGtlbVJIT1hsYVV6RndZbTVTYkFwamJURnNXa2RzYUdSSFZYZElhR05PVFdwVmQwNUVTVEJOUkdkM1RucFJkMWRvWTA1TmFsVjNUa1JKTUUxRVozaE9lbEYzVjJwQlFVMUdhM2RGZDFsSUNrdHZXa2w2YWpCRFFWRlpTVXR2V2tsNmFqQkVRVkZqUkZGblFVVk5ObEpwY1RaM1IxUnJaVkp1WW5aelFsQnhabEEwYkZSSmNYSTNkMUJ4YjFjMk5sY0tkV2RQUVRGMFJtaDBNM1ZSY25relpFaDNjRXcxZDIxdVVFVklUMUZsU0VsMUswazNMM0J5U2tWMlpuaE5kWFY0YTJGUFEwSm5jM2RuWjFsSVRVRTBSd3BCTVZWa1JIZEZRaTkzVVVWQmQwbElaMFJCVkVKblRsWklVMVZGUkVSQlMwSm5aM0pDWjBWR1FsRmpSRUY2UVdSQ1owNVdTRkUwUlVablVWVnBhM1I0Q25CbFJrTllaSEpSWm1kMWJVMTRObXAwYTJWbFkyZFJkMGgzV1VSV1VqQnFRa0puZDBadlFWVXpPVkJ3ZWpGWmEwVmFZalZ4VG1wd1MwWlhhWGhwTkZrS1drUTRkMmRaVVVkQk1WVmtSVkZGUWk5M1VqWk5TR2xIWkcxb01HUklRbnBQYVRoMldqSnNNR0ZJVm1sTWJVNTJZbE01ZW1KSVRtaE1WMXA1V1ZjeGJBcGtNamw1WVhrNWVtSklUbWhNVjJSd1pFZG9NVmxwTVc1YVZ6VnNZMjFHTUdJelNYWk1iV1J3WkVkb01WbHBPVE5pTTBweVdtMTRkbVF6VFhaYU1sWjFDbHBZU21oa1J6bDVXREprYkdKdFZubGhWMDVtWXpKNGVsbFVUWFZsVnpGelVVaEtiRnB1VFhaa1IwWnVZM2s1TWsxcE5IaE1ha0YzVDFGWlMwdDNXVUlLUWtGSFJIWjZRVUpCVVZGeVlVaFNNR05JVFRaTWVUa3dZakowYkdKcE5XaFpNMUp3WWpJMWVreHRaSEJrUjJneFdXNVdlbHBZU21waU1qVXdXbGMxTUFwTWJVNTJZbFJCVjBKbmIzSkNaMFZGUVZsUEwwMUJSVU5DUVdoNldUSm9iRnBJVm5OYVZFRXlRbWR2Y2tKblJVVkJXVTh2VFVGRlJFSkRaM2xPVjFab0NsbFVSVEpPYW1zelRWUm9hVTVxVlRSYWJWcHNXbFJDYVU1cVNUVk5la1Y0V2xSc2JWbDZUbWhQVkdSc1dXcEplRTFDYTBkRGFYTkhRVkZSUW1jM09IY0tRVkZSUlVNeFFubGFVekZUV2xkNGJGbFlUbXhOUkZWSFEybHpSMEZSVVVKbk56aDNRVkZWUlVveVJqTmplVEYzWWpOa2JHTnVVblppTW5oNlRETkNkZ3BrTWxaNVpFYzVkbUpJVFhSaVIwWjBXVzFTYUV4WVFqVmtSMmgyWW1wQlowSm5iM0pDWjBWRlFWbFBMMDFCUlVkQ1FrcDVXbGRhZWt3eWFHeFpWMUo2Q2t3eVVteGtiVlp6WWpOQmQwOTNXVXRMZDFsQ1FrRkhSSFo2UVVKRFFWRjBSRU4wYjJSSVVuZGplbTkyVEROU2RtRXlWblZNYlVacVpFZHNkbUp1VFhVS1dqSnNNR0ZJVm1sa1dFNXNZMjFPZG1KdVVteGlibEYxV1RJNWRFMUpSMGRDWjI5eVFtZEZSVUZaVHk5TlFVVktRa2huVFdSdGFEQmtTRUo2VDJrNGRncGFNbXd3WVVoV2FVeHRUblppVXpsNllraE9hRXhYV25sWlZ6RnNaREk1ZVdGNU9YcGlTRTVvVEZka2NHUkhhREZaYVRGdVdsYzFiR050UmpCaU0wbDJDa3h0WkhCa1IyZ3hXV2s1TTJJelNuSmFiWGgyWkROTmRsb3lWblZhV0Vwb1pFYzVlVmd5Wkd4aWJWWjVZVmRPWm1NeWVIcFpWRTExWlZjeGMxRklTbXdLV201TmRtUkhSbTVqZVRreVRXazBlRXhxUVhkUFFWbExTM2RaUWtKQlIwUjJla0ZDUTJkUmNVUkRhRzFPTWxKclQwZE5NVTVIVFhsTlJGa3pXVzFHYlFwWmVrVjVXVEpGTTFsVVZURk9WR3N4V2tSV2JGcFViR2xPZWxWNVRVUlNhRTFDTUVkRGFYTkhRVkZSUW1jM09IZEJVWE5GUkhkM1Rsb3liREJoU0ZacENreFhhSFpqTTFKc1drUkNTMEpuYjNKQ1owVkZRVmxQTDAxQlJVMUNSSGROVDIxb01HUklRbnBQYVRoMldqSnNNR0ZJVm1sTWJVNTJZbE01YUdRelRYUUtZMGM1TTFwWVNqQmlNamx6WTNrNWQySXpaR3hqYmxKMllqSjRla3hYZUdoaVYwcHJXVk14ZDJWWVVtOWlNalIzVDBGWlMwdDNXVUpDUVVkRWRucEJRZ3BFVVZGeFJFTm5lVTVYVm1oWlZFVXlUbXByTTAxVWFHbE9hbFUwV20xYWJGcFVRbWxPYWtrMVRYcEZlRnBVYkcxWmVrNW9UMVJrYkZscVNYaE5RMGxIQ2tOcGMwZEJVVkZDWnpjNGQwRlJORVZHUVhkVFkyMVdiV041T1c5YVYwWnJZM2s1YTFwWVdteGlSemwzVFVKclIwTnBjMGRCVVZGQ1p6YzRkMEZST0VVS1EzZDNTazFxU1hoUFZFVTFUWHBqTlUxRVJVZERhWE5IUVZGUlFtYzNPSGRCVWtGRlNYZDNhR0ZJVWpCalNFMDJUSGs1Ym1GWVVtOWtWMGwxV1RJNWRBcE1Na1l6WTNreGQySXpaR3hqYmxKMllqSjRlazFDYTBkRGFYTkhRVkZSUW1jM09IZEJVa1ZGUTNkM1NrMVVTVFZOVkVrelRtcE5ORTFJT0VkRGFYTkhDa0ZSVVVKbk56aDNRVkpKUldOUmVIWmhTRkl3WTBoTk5reDVPVzVoV0ZKdlpGZEpkVmt5T1hSTU1rWXpZM2t4ZDJJelpHeGpibEoyWWpKNGVrd3pRbllLWkRKV2VXUkhPWFppU0UxMFlrZEdkRmx0VW1oTVdFSTFaRWRvZG1KcE9IVmFNbXd3WVVoV2FVd3paSFpqYlhSdFlrYzVNMk41T1hkamJWVjBZMjFXY3dwYVYwWjZXbE0xTldKWGVFRmpiVlp0WTNrNWIxcFhSbXRqZVRscldsaGFiR0pIT1hkTlJHZEhRMmx6UjBGUlVVSm5OemgzUVZKTlJVdG5kMjlOYWxac0NsbFhSWGhPYWxrMVRucEZORmxxV1RGUFIxcHRXbGRWZDFscVdYbFBWRTE0VFZkVk5WcHRUWHBaVkdzeldsZEplVTFVUVZsQ1oyOXlRbWRGUlVGWlR5OEtUVUZGVlVKQmIwMURTRTVxWVVkV2EyUlhlR3hOUnpSSFEybHpSMEZSVVVKbk56aDNRVkpWUlZsQmVHVmhTRkl3WTBoTk5reDVPVzVoV0ZKdlpGZEpkUXBaTWpsMFRESkdNMk41TVhkaU0yUnNZMjVTZG1JeWVIcE1NMEoyWkRKV2VXUkhPWFppU0UxMFlrZEdkRmx0VW1oTVdFSTFaRWRvZG1KcE9XaFpNMUp3Q21JeU5YcE1NMG94WW01TmRrMVVVVEpOZWxreFRVUmpNMDVxWTNaWldGSXdXbGN4ZDJSSVRYWk5WRUZYUW1kdmNrSm5SVVZCV1U4dlRVRkZWMEpCWjAwS1FtNUNNVmx0ZUhCWmVrTkNhV2RaUzB0M1dVSkNRVWhYWlZGSlJVRm5VamhDU0c5QlpVRkNNa0ZPTURsTlIzSkhlSGhGZVZsNGEyVklTbXh1VG5kTGFRcFRiRFkwTTJwNWRDODBaVXRqYjBGMlMyVTJUMEZCUVVKc2JXSlhhMnQzUVVGQlVVUkJSV04zVWxGSmFFRktNVGhPZVVad1dHZDBVRE5oVjBRNVpEUjVDakpEZURaUllUSXdNa1EzVTI5WE1rNXpWemx2Y0cxelNFRnBRbUkxSzI5UVdtMWhLMVIyVEU5WlQxbHFaRFJMTDI5RlJuSnpSa3hDVHpRd1VFbEVaRWtLY1RsNVpUZHFRVXRDWjJkeGFHdHFUMUJSVVVSQmQwNXVRVVJDYTBGcVFYQjVMM2xIYzJWT0wwRXllVWRFTm05SVoyeDJiMHRXVVRoQlZqVmhVV2xOWndvMmJXMDVOVWhpVFUxc2FHMTBRVEJFWkV0NmMxcFZRVTE2WlhVNFNqRkJRMDFFTTFaeVRGYzBka3hQU0c0NE9GUnVWWGRQUzNKc1kwVlllbmhHVlRaUUNqWlBWMUp5VUd0U1NrWnNlamxJVjJKWlFuUlZSM2xYUlVodFVWVjNSVU5YY0ZFOVBRb3RMUzB0TFVWT1JDQkRSVkpVU1VaSlEwRlVSUzB0TFMwdENnPT0ifV19fQ=="}]}, "dsseEnvelope":{"payload":"eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjAuMSIsInByZWRpY2F0ZVR5cGUiOiJodHRwczovL3Nsc2EuZGV2L3Byb3ZlbmFuY2UvdjAuMiIsInN1YmplY3QiOlt7Im5hbWUiOiIuL2F3c19sYW1iZGFfcG93ZXJ0b29scy0zLjEwLjFhMTEtcHkzLW5vbmUtYW55LndobCIsImRpZ2VzdCI6eyJzaGEyNTYiOiJkZDVjMGI5NDkzZWIxYTE0NWQ4M2I3M2U1OTcxZTkwOTRjNmI4MGIxM2EwMDAwZDI0YWZkNTk4MTVhN2QwODEyIn19LHsibmFtZSI6Ii4vYXdzX2xhbWJkYV9wb3dlcnRvb2xzLTMuMTAuMWExMS50YXIuZ3oiLCJkaWdlc3QiOnsic2hhMjU2IjoiZTQ3MzExMjE0N2I5NGQ4ZmNjNmNkMDk3MDY1NTliMzUzYzI3MjFlZWJhOTAwYjNkZmI1Njg0MTg5N2E1NjY5NSJ9fV0sInByZWRpY2F0ZSI6eyJidWlsZGVyIjp7ImlkIjoiaHR0cHM6Ly9naXRodWIuY29tL3Nsc2EtZnJhbWV3b3JrL3Nsc2EtZ2l0aHViLWdlbmVyYXRvci8uZ2l0aHViL3dvcmtmbG93cy9nZW5lcmF0b3JfZ2VuZXJpY19zbHNhMy55bWxAcmVmcy90YWdzL3YyLjEuMCJ9LCJidWlsZFR5cGUiOiJodHRwczovL2dpdGh1Yi5jb20vc2xzYS1mcmFtZXdvcmsvc2xzYS1naXRodWItZ2VuZXJhdG9yL2dlbmVyaWNAdjEiLCJpbnZvY2F0aW9uIjp7ImNvbmZpZ1NvdXJjZSI6eyJ1cmkiOiJnaXQraHR0cHM6Ly9naXRodWIuY29tL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbkByZWZzL2hlYWRzL2RldmVsb3AiLCJkaWdlc3QiOnsic2hhMSI6IjI1ZWFhMTY2OTcxOGI2NThmZmVlMGI2MjkzMTFlOWZjM2E5N2ViMjEifSwiZW50cnlQb2ludCI6Ii5naXRodWIvd29ya2Zsb3dzL3ByZS1yZWxlYXNlLnltbCJ9LCJwYXJhbWV0ZXJzIjp7InZhcnMiOnt9fSwiZW52aXJvbm1lbnQiOnsiZ2l0aHViX2FjdG9yIjoibGVhbmRyb2RhbWFzY2VuYSIsImdpdGh1Yl9hY3Rvcl9pZCI6IjQyOTUxNzMiLCJnaXRodWJfYmFzZV9yZWYiOiIiLCJnaXRodWJfZXZlbnRfbmFtZSI6InNjaGVkdWxlIiwiZ2l0aHViX2V2ZW50X3BheWxvYWQiOnsiZW50ZXJwcmlzZSI6eyJhdmF0YXJfdXJsIjoiaHR0cHM6Ly9hdmF0YXJzLmdpdGh1YnVzZXJjb250ZW50LmNvbS9iLzEyOTA/dj00IiwiY3JlYXRlZF9hdCI6IjIwMTktMTEtMTNUMTg6MDU6NDFaIiwiZGVzY3JpcHRpb24iOiIiLCJodG1sX3VybCI6Imh0dHBzOi8vZ2l0aHViLmNvbS9lbnRlcnByaXNlcy9hbWF6b24iLCJpZCI6MTI5MCwibmFtZSI6IkFtYXpvbiIsIm5vZGVfaWQiOiJNREV3T2tWdWRHVnljSEpwYzJVeE1qa3ciLCJzbHVnIjoiYW1hem9uIiwidXBkYXRlZF9hdCI6IjIwMjQtMDktMzBUMjE6MDI6MzBaIiwid2Vic2l0ZV91cmwiOiJodHRwczovL3d3dy5hbWF6b24uY29tLyJ9LCJvcmdhbml6YXRpb24iOnsiYXZhdGFyX3VybCI6Imh0dHBzOi8vYXZhdGFycy5naXRodWJ1c2VyY29udGVudC5jb20vdS8xMjkxMjc2Mzg/dj00IiwiZGVzY3JpcHRpb24iOiIiLCJldmVudHNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9vcmdzL2F3cy1wb3dlcnRvb2xzL2V2ZW50cyIsImhvb2tzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vb3Jncy9hd3MtcG93ZXJ0b29scy9ob29rcyIsImlkIjoxMjkxMjc2MzgsImlzc3Vlc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL29yZ3MvYXdzLXBvd2VydG9vbHMvaXNzdWVzIiwibG9naW4iOiJhd3MtcG93ZXJ0b29scyIsIm1lbWJlcnNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9vcmdzL2F3cy1wb3dlcnRvb2xzL21lbWJlcnN7L21lbWJlcn0iLCJub2RlX2lkIjoiT19rZ0RPQjdKVTFnIiwicHVibGljX21lbWJlcnNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9vcmdzL2F3cy1wb3dlcnRvb2xzL3B1YmxpY19tZW1iZXJzey9tZW1iZXJ9IiwicmVwb3NfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9vcmdzL2F3cy1wb3dlcnRvb2xzL3JlcG9zIiwidXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9vcmdzL2F3cy1wb3dlcnRvb2xzIn0sInJlcG9zaXRvcnkiOnsiYWxsb3dfZm9ya2luZyI6dHJ1ZSwiYXJjaGl2ZV91cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi97YXJjaGl2ZV9mb3JtYXR9ey9yZWZ9IiwiYXJjaGl2ZWQiOmZhbHNlLCJhc3NpZ25lZXNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vYXNzaWduZWVzey91c2VyfSIsImJsb2JzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2dpdC9ibG9ic3svc2hhfSIsImJyYW5jaGVzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2JyYW5jaGVzey9icmFuY2h9IiwiY2xvbmVfdXJsIjoiaHR0cHM6Ly9naXRodWIuY29tL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi5naXQiLCJjb2xsYWJvcmF0b3JzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2NvbGxhYm9yYXRvcnN7L2NvbGxhYm9yYXRvcn0iLCJjb21tZW50c191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9jb21tZW50c3svbnVtYmVyfSIsImNvbW1pdHNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vY29tbWl0c3svc2hhfSIsImNvbXBhcmVfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vY29tcGFyZS97YmFzZX0uLi57aGVhZH0iLCJjb250ZW50c191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9jb250ZW50cy97K3BhdGh9IiwiY29udHJpYnV0b3JzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2NvbnRyaWJ1dG9ycyIsImNyZWF0ZWRfYXQiOiIyMDE5LTExLTE1VDEyOjI2OjEyWiIsImN1c3RvbV9wcm9wZXJ0aWVzIjp7fSwiZGVmYXVsdF9icmFuY2giOiJkZXZlbG9wIiwiZGVwbG95bWVudHNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vZGVwbG95bWVudHMiLCJkZXNjcmlwdGlvbiI6IkEgZGV2ZWxvcGVyIHRvb2xraXQgdG8gaW1wbGVtZW50IFNlcnZlcmxlc3MgYmVzdCBwcmFjdGljZXMgYW5kIGluY3JlYXNlIGRldmVsb3BlciB2ZWxvY2l0eS4iLCJkaXNhYmxlZCI6ZmFsc2UsImRvd25sb2Fkc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9kb3dubG9hZHMiLCJldmVudHNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vZXZlbnRzIiwiZm9yayI6ZmFsc2UsImZvcmtzIjo0MjEsImZvcmtzX2NvdW50Ijo0MjEsImZvcmtzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2ZvcmtzIiwiZnVsbF9uYW1lIjoiYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uIiwiZ2l0X2NvbW1pdHNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vZ2l0L2NvbW1pdHN7L3NoYX0iLCJnaXRfcmVmc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9naXQvcmVmc3svc2hhfSIsImdpdF90YWdzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2dpdC90YWdzey9zaGF9IiwiZ2l0X3VybCI6ImdpdDovL2dpdGh1Yi5jb20vYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uLmdpdCIsImhhc19kaXNjdXNzaW9ucyI6dHJ1ZSwiaGFzX2Rvd25sb2FkcyI6dHJ1ZSwiaGFzX2lzc3VlcyI6dHJ1ZSwiaGFzX3BhZ2VzIjpmYWxzZSwiaGFzX3Byb2plY3RzIjp0cnVlLCJoYXNfd2lraSI6ZmFsc2UsImhvbWVwYWdlIjoiaHR0cHM6Ly9kb2NzLnBvd2VydG9vbHMuYXdzLmRldi9sYW1iZGEvcHl0aG9uL2xhdGVzdC8iLCJob29rc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9ob29rcyIsImh0bWxfdXJsIjoiaHR0cHM6Ly9naXRodWIuY29tL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbiIsImlkIjoyMjE5MTkzNzksImlzX3RlbXBsYXRlIjpmYWxzZSwiaXNzdWVfY29tbWVudF91cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9pc3N1ZXMvY29tbWVudHN7L251bWJlcn0iLCJpc3N1ZV9ldmVudHNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vaXNzdWVzL2V2ZW50c3svbnVtYmVyfSIsImlzc3Vlc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9pc3N1ZXN7L251bWJlcn0iLCJrZXlzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2tleXN7L2tleV9pZH0iLCJsYWJlbHNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vbGFiZWxzey9uYW1lfSIsImxhbmd1YWdlIjoiUHl0aG9uIiwibGFuZ3VhZ2VzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2xhbmd1YWdlcyIsImxpY2Vuc2UiOnsia2V5IjoibWl0LTAiLCJuYW1lIjoiTUlUIE5vIEF0dHJpYnV0aW9uIiwibm9kZV9pZCI6Ik1EYzZUR2xqWlc1elpUUXgiLCJzcGR4X2lkIjoiTUlULTAiLCJ1cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL2xpY2Vuc2VzL21pdC0wIn0sIm1lcmdlc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9tZXJnZXMiLCJtaWxlc3RvbmVzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL21pbGVzdG9uZXN7L251bWJlcn0iLCJtaXJyb3JfdXJsIjpudWxsLCJuYW1lIjoicG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uIiwibm9kZV9pZCI6Ik1ERXdPbEpsY0c5emFYUnZjbmt5TWpFNU1Ua3pOems9Iiwibm90aWZpY2F0aW9uc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9ub3RpZmljYXRpb25zez9zaW5jZSxhbGwscGFydGljaXBhdGluZ30iLCJvcGVuX2lzc3VlcyI6NDgsIm9wZW5faXNzdWVzX2NvdW50Ijo0OCwib3duZXIiOnsiYXZhdGFyX3VybCI6Imh0dHBzOi8vYXZhdGFycy5naXRodWJ1c2VyY29udGVudC5jb20vdS8xMjkxMjc2Mzg/dj00IiwiZXZlbnRzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vdXNlcnMvYXdzLXBvd2VydG9vbHMvZXZlbnRzey9wcml2YWN5fSIsImZvbGxvd2Vyc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3VzZXJzL2F3cy1wb3dlcnRvb2xzL2ZvbGxvd2VycyIsImZvbGxvd2luZ191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3VzZXJzL2F3cy1wb3dlcnRvb2xzL2ZvbGxvd2luZ3svb3RoZXJfdXNlcn0iLCJnaXN0c191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3VzZXJzL2F3cy1wb3dlcnRvb2xzL2dpc3Rzey9naXN0X2lkfSIsImdyYXZhdGFyX2lkIjoiIiwiaHRtbF91cmwiOiJodHRwczovL2dpdGh1Yi5jb20vYXdzLXBvd2VydG9vbHMiLCJpZCI6MTI5MTI3NjM4LCJsb2dpbiI6ImF3cy1wb3dlcnRvb2xzIiwibm9kZV9pZCI6Ik9fa2dET0I3SlUxZyIsIm9yZ2FuaXphdGlvbnNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS91c2Vycy9hd3MtcG93ZXJ0b29scy9vcmdzIiwicmVjZWl2ZWRfZXZlbnRzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vdXNlcnMvYXdzLXBvd2VydG9vbHMvcmVjZWl2ZWRfZXZlbnRzIiwicmVwb3NfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS91c2Vycy9hd3MtcG93ZXJ0b29scy9yZXBvcyIsInNpdGVfYWRtaW4iOmZhbHNlLCJzdGFycmVkX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vdXNlcnMvYXdzLXBvd2VydG9vbHMvc3RhcnJlZHsvb3duZXJ9ey9yZXBvfSIsInN1YnNjcmlwdGlvbnNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS91c2Vycy9hd3MtcG93ZXJ0b29scy9zdWJzY3JpcHRpb25zIiwidHlwZSI6Ik9yZ2FuaXphdGlvbiIsInVybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vdXNlcnMvYXdzLXBvd2VydG9vbHMiLCJ1c2VyX3ZpZXdfdHlwZSI6InB1YmxpYyJ9LCJwcml2YXRlIjpmYWxzZSwicHVsbHNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vcHVsbHN7L251bWJlcn0iLCJwdXNoZWRfYXQiOiIyMDI1LTA0LTIzVDIyOjI5OjIwWiIsInJlbGVhc2VzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL3JlbGVhc2Vzey9pZH0iLCJzaXplIjoxMTExNTAsInNzaF91cmwiOiJnaXRAZ2l0aHViLmNvbTphd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24uZ2l0Iiwic3RhcmdhemVyc19jb3VudCI6MzAyNiwic3RhcmdhemVyc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9zdGFyZ2F6ZXJzIiwic3RhdHVzZXNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vc3RhdHVzZXMve3NoYX0iLCJzdWJzY3JpYmVyc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9zdWJzY3JpYmVycyIsInN1YnNjcmlwdGlvbl91cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9zdWJzY3JpcHRpb24iLCJzdm5fdXJsIjoiaHR0cHM6Ly9naXRodWIuY29tL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbiIsInRhZ3NfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vdGFncyIsInRlYW1zX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL3RlYW1zIiwidG9waWNzIjpbImF3cyIsImF3cy1sYW1iZGEiLCJoYWNrdG9iZXJmZXN0IiwibGFtYmRhIiwicHl0aG9uIiwic2VydmVybGVzcyJdLCJ0cmVlc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9naXQvdHJlZXN7L3NoYX0iLCJ1cGRhdGVkX2F0IjoiMjAyNS0wNC0yNFQwMzo0MjoxMVoiLCJ1cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbiIsInZpc2liaWxpdHkiOiJwdWJsaWMiLCJ3YXRjaGVycyI6MzAyNiwid2F0Y2hlcnNfY291bnQiOjMwMjYsIndlYl9jb21taXRfc2lnbm9mZl9yZXF1aXJlZCI6dHJ1ZX0sInNjaGVkdWxlIjoiMCA4ICogKiAxLTUiLCJ3b3JrZmxvdyI6Ii5naXRodWIvd29ya2Zsb3dzL3ByZS1yZWxlYXNlLnltbCJ9LCJnaXRodWJfaGVhZF9yZWYiOiIiLCJnaXRodWJfcmVmIjoicmVmcy9oZWFkcy9kZXZlbG9wIiwiZ2l0aHViX3JlZl90eXBlIjoiYnJhbmNoIiwiZ2l0aHViX3JlcG9zaXRvcnlfaWQiOiIyMjE5MTkzNzkiLCJnaXRodWJfcmVwb3NpdG9yeV9vd25lciI6ImF3cy1wb3dlcnRvb2xzIiwiZ2l0aHViX3JlcG9zaXRvcnlfb3duZXJfaWQiOiIxMjkxMjc2MzgiLCJnaXRodWJfcnVuX2F0dGVtcHQiOiIxIiwiZ2l0aHViX3J1bl9pZCI6IjE0NjM2NTA3NzY3IiwiZ2l0aHViX3J1bl9udW1iZXIiOiIyMjYiLCJnaXRodWJfc2hhMSI6IjI1ZWFhMTY2OTcxOGI2NThmZmVlMGI2MjkzMTFlOWZjM2E5N2ViMjEifX0sIm1ldGFkYXRhIjp7ImJ1aWxkSW52b2NhdGlvbklEIjoiMTQ2MzY1MDc3NjctMSIsImNvbXBsZXRlbmVzcyI6eyJwYXJhbWV0ZXJzIjp0cnVlLCJlbnZpcm9ubWVudCI6ZmFsc2UsIm1hdGVyaWFscyI6ZmFsc2V9LCJyZXByb2R1Y2libGUiOmZhbHNlfSwibWF0ZXJpYWxzIjpbeyJ1cmkiOiJnaXQraHR0cHM6Ly9naXRodWIuY29tL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbkByZWZzL2hlYWRzL2RldmVsb3AiLCJkaWdlc3QiOnsic2hhMSI6IjI1ZWFhMTY2OTcxOGI2NThmZmVlMGI2MjkzMTFlOWZjM2E5N2ViMjEifX1dfX0=", "payloadType":"application/vnd.in-toto+json", "signatures":[{"sig":"MEUCIB5KK+pmCNCFSN2mF5Lb4BLCtS6jzfvh9cSzdOM0DlkdAiEAskSV2KOhDce7X3OckpOxjlzRwxHOl5q5xipqOyBIF0Y="}]}}
\ No newline at end of file
diff --git a/provenance/3.10.1a2/multiple.intoto.jsonl b/provenance/3.10.1a2/multiple.intoto.jsonl
new file mode 100644
index 00000000000..7d3152d0230
--- /dev/null
+++ b/provenance/3.10.1a2/multiple.intoto.jsonl
@@ -0,0 +1 @@
+{"mediaType":"application/vnd.dev.sigstore.bundle.v0.3+json", "verificationMaterial":{"certificate":{"rawBytes":"MIIHZjCCBuygAwIBAgIURWeYzjYwTVKJZr602ZYyFuGyFjcwCgYIKoZIzj0EAwMwNzEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MR4wHAYDVQQDExVzaWdzdG9yZS1pbnRlcm1lZGlhdGUwHhcNMjUwNDExMDgwNzUxWhcNMjUwNDExMDgxNzUxWjAAMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEOpqujVfAB+6MmH+6wL2MUP4DPSsC2GGc6+20eETMFfbjc3wmlkDzJ6coGDLZhyeJHEHml1NMrcxdQNyAf3CPiqOCBgswggYHMA4GA1UdDwEB/wQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcDAzAdBgNVHQ4EFgQURd+uCQ3Aj3rdZvXElEUjR60JGSUwHwYDVR0jBBgwFoAU39Ppz1YkEZb5qNjpKFWixi4YZD8wgYQGA1UdEQEB/wR6MHiGdmh0dHBzOi8vZ2l0aHViLmNvbS9zbHNhLWZyYW1ld29yay9zbHNhLWdpdGh1Yi1nZW5lcmF0b3IvLmdpdGh1Yi93b3JrZmxvd3MvZ2VuZXJhdG9yX2dlbmVyaWNfc2xzYTMueW1sQHJlZnMvdGFncy92Mi4xLjAwOQYKKwYBBAGDvzABAQQraHR0cHM6Ly90b2tlbi5hY3Rpb25zLmdpdGh1YnVzZXJjb250ZW50LmNvbTAWBgorBgEEAYO/MAECBAhzY2hlZHVsZTA2BgorBgEEAYO/MAEDBChiYWJmNDdiZTU1NDJhNzBlMjQwNzJlNGI2YjU2YTA5Y2E5MzZkYWI4MBkGCisGAQQBg78wAQQEC1ByZS1SZWxlYXNlMDUGCisGAQQBg78wAQUEJ2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbjAgBgorBgEEAYO/MAEGBBJyZWZzL2hlYWRzL2RldmVsb3AwOwYKKwYBBAGDvzABCAQtDCtodHRwczovL3Rva2VuLmFjdGlvbnMuZ2l0aHVidXNlcmNvbnRlbnQuY29tMIGGBgorBgEEAYO/MAEJBHgMdmh0dHBzOi8vZ2l0aHViLmNvbS9zbHNhLWZyYW1ld29yay9zbHNhLWdpdGh1Yi1nZW5lcmF0b3IvLmdpdGh1Yi93b3JrZmxvd3MvZ2VuZXJhdG9yX2dlbmVyaWNfc2xzYTMueW1sQHJlZnMvdGFncy92Mi4xLjAwOAYKKwYBBAGDvzABCgQqDChmN2RkOGM1NGMyMDY3YmFmYzEyY2E3YTU1NTk1ZDVlZTliNzUyMDRhMB0GCisGAQQBg78wAQsEDwwNZ2l0aHViLWhvc3RlZDBKBgorBgEEAYO/MAEMBDwMOmh0dHBzOi8vZ2l0aHViLmNvbS9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24wOAYKKwYBBAGDvzABDQQqDChiYWJmNDdiZTU1NDJhNzBlMjQwNzJlNGI2YjU2YTA5Y2E5MzZkYWI4MCIGCisGAQQBg78wAQ4EFAwScmVmcy9oZWFkcy9kZXZlbG9wMBkGCisGAQQBg78wAQ8ECwwJMjIxOTE5Mzc5MDEGCisGAQQBg78wARAEIwwhaHR0cHM6Ly9naXRodWIuY29tL2F3cy1wb3dlcnRvb2xzMBkGCisGAQQBg78wAREECwwJMTI5MTI3NjM4MH8GCisGAQQBg78wARIEcQxvaHR0cHM6Ly9naXRodWIuY29tL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi8uZ2l0aHViL3dvcmtmbG93cy9wcmUtcmVsZWFzZS55bWxAcmVmcy9oZWFkcy9kZXZlbG9wMDgGCisGAQQBg78wARMEKgwoYmFiZjQ3YmU1NTQyYTcwZTI0MDcyZTRiNmI1NmEwOWNhOTM2ZGFiODAYBgorBgEEAYO/MAEUBAoMCHNjaGVkdWxlMG4GCisGAQQBg78wARUEYAxeaHR0cHM6Ly9naXRodWIuY29tL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9hY3Rpb25zL3J1bnMvMTQzOTg0ODg1MzMvYXR0ZW1wdHMvMTAWBgorBgEEAYO/MAEWBAgMBnB1YmxpYzCBigYKKwYBBAHWeQIEAgR8BHoAeAB2AN09MGrGxxEyYxkeHJlnNwKiSl643jyt/4eKcoAvKe6OAAABliPkEf0AAAQDAEcwRQIgfi6PG1pCACtYZxMXd2N/nJg6K6tTL0LS+Ppz/fC4F4kCIQCrVNuGWuoVUjOo8XxSOERg92TUFME37oQwGY6uW3K0lzAKBggqhkjOPQQDAwNoADBlAjAGVbVtVNih6ZoyqvC0z2zInL+zsxtIAKz+5D03Ki+Z4cnNoMBbDZzvS+TBChcBjp4CMQCsuc6HC9a1FdLYufVoXQ+Uc5DAkdrAYnWyboAChYkqKFiGDlQMwF85NKPAuICPLxY="}, "tlogEntries":[{"logIndex":"195375789", "logId":{"keyId":"wNI9atQGlz+VWfO6LRygH4QUfY/8W4RFwiT5i5WRgB0="}, "kindVersion":{"kind":"dsse", "version":"0.0.1"}, "integratedTime":"1744358871", "inclusionPromise":{"signedEntryTimestamp":"MEUCIHwUv7zRFNa/nrNyv6a5GAi9HHulcr+U5Dg3FLS/4DEnAiEAylEH0DBnbRYQOUPng+WjSY4oR0BsBVk/JklONJPMwOY="}, "inclusionProof":{"logIndex":"73471527", "rootHash":"I7YOTR2Dbax1DXPncQbCoEahKY8BDHAE1WrVJi41/Pk=", "treeSize":"73471531", "hashes":["Rz7BmSXG3tcyBBgDoAR1jbQjQEsixYfZ9LPYRL8qwKM=", "mgnhiJCsRPF471UQIApZY/BDWl8FXySygIKf1aXizWk=", "2SttPJV8PVfeWbLpJBz77vEBx49GAuakFmhPzNAJTfw=", "9GHbmEgz8YugNO+ymc/TuD/RXBjZca935UJn/Mj3Xvk=", "VEcwi0kol0BctJ4KLWKbHTjYWDUFI+/usNJzv1N7A5k=", "IzzIPOtCawi59spUdxr1z/2fZkOPQF4ds+VCl3Qlakg=", "WaXXGJWZSBsVXv+9GwLUKmSrAUeJ0QdeQ3bgIh9v+tU=", "IowzifugDu+GURHvwSzwPZBMKCgZyzn561cyzboiLCY=", "g/3E96IZ33zo0R05W3+m2EINKdU0bfvhR6sCpRe6z5o=", "bUMWi9afi8M+WrpEiXczKOIZWruoe38aV/lXN5Z5o9E=", "WEm5OgPzJpYROv+4CcrieexCYyQKrLUH3hbxmcQQ+DM=", "7v8qPHNDLerpduaMx06eb/MwgoQwczTn/cYGKX/9wZ4="], "checkpoint":{"envelope":"rekor.sigstore.dev - 1193050959916656506\n73471531\nI7YOTR2Dbax1DXPncQbCoEahKY8BDHAE1WrVJi41/Pk=\n\n— rekor.sigstore.dev wNI9ajBFAiEAtk6//5hxjZNYtOALk9vWaMbjy9UU/z8/n+mdLvB4yXYCIDm9WuOXjKzNT4G+dIgjc5vLNFVS5wf7RoaTukFBPxjt\n"}}, "canonicalizedBody":"eyJhcGlWZXJzaW9uIjoiMC4wLjEiLCJraW5kIjoiZHNzZSIsInNwZWMiOnsiZW52ZWxvcGVIYXNoIjp7ImFsZ29yaXRobSI6InNoYTI1NiIsInZhbHVlIjoiMzAxMDkwNjBjMTZkMTNlZGQ1NDJjMGJjMzhmODk5NzYwNjY1NTA0YTFkNjFmODkyOTk4ZTAyZDhkYzM3YmU2ZSJ9LCJwYXlsb2FkSGFzaCI6eyJhbGdvcml0aG0iOiJzaGEyNTYiLCJ2YWx1ZSI6IjkxNmZmNDlkNGQyZjkwNWYwZTU5NDdkOTVjOGMyZTA1NzU0NWM5NmViNDMwZjRkN2MwYWQ1ZWQyMTY2NDcyMjkifSwic2lnbmF0dXJlcyI6W3sic2lnbmF0dXJlIjoiTUVZQ0lRQ0E2L1B2UHBCRHFnNi9DRXBsQW1XWXZwcDlPNFJuZWp3L2tPZ2JYamprcHdJaEFPV3FwcDZITi9TR0J1NGFWWVA3OWc5RmxSMkJwNll5dnNSOE5qTS83azlOIiwidmVyaWZpZXIiOiJMUzB0TFMxQ1JVZEpUaUJEUlZKVVNVWkpRMEZVUlMwdExTMHRDazFKU1VoYWFrTkRRblY1WjBGM1NVSkJaMGxWVWxkbFdYcHFXWGRVVmt0S1duSTJNREphV1hsR2RVZDVSbXBqZDBObldVbExiMXBKZW1vd1JVRjNUWGNLVG5wRlZrMUNUVWRCTVZWRlEyaE5UV015Ykc1ak0xSjJZMjFWZFZwSFZqSk5ValIzU0VGWlJGWlJVVVJGZUZaNllWZGtlbVJIT1hsYVV6RndZbTVTYkFwamJURnNXa2RzYUdSSFZYZElhR05PVFdwVmQwNUVSWGhOUkdkM1RucFZlRmRvWTA1TmFsVjNUa1JGZUUxRVozaE9lbFY0VjJwQlFVMUdhM2RGZDFsSUNrdHZXa2w2YWpCRFFWRlpTVXR2V2tsNmFqQkVRVkZqUkZGblFVVlBjSEYxYWxabVFVSXJOazF0U0NzMmQwd3lUVlZRTkVSUVUzTkRNa2RIWXpZck1qQUtaVVZVVFVabVltcGpNM2R0Ykd0RWVrbzJZMjlIUkV4YWFIbGxTa2hGU0cxc01VNU5jbU40WkZGT2VVRm1NME5RYVhGUFEwSm5jM2RuWjFsSVRVRTBSd3BCTVZWa1JIZEZRaTkzVVVWQmQwbElaMFJCVkVKblRsWklVMVZGUkVSQlMwSm5aM0pDWjBWR1FsRmpSRUY2UVdSQ1owNVdTRkUwUlVablVWVlNaQ3QxQ2tOUk0wRnFNM0prV25aWVJXeEZWV3BTTmpCS1IxTlZkMGgzV1VSV1VqQnFRa0puZDBadlFWVXpPVkJ3ZWpGWmEwVmFZalZ4VG1wd1MwWlhhWGhwTkZrS1drUTRkMmRaVVVkQk1WVmtSVkZGUWk5M1VqWk5TR2xIWkcxb01HUklRbnBQYVRoMldqSnNNR0ZJVm1sTWJVNTJZbE01ZW1KSVRtaE1WMXA1V1ZjeGJBcGtNamw1WVhrNWVtSklUbWhNVjJSd1pFZG9NVmxwTVc1YVZ6VnNZMjFHTUdJelNYWk1iV1J3WkVkb01WbHBPVE5pTTBweVdtMTRkbVF6VFhaYU1sWjFDbHBZU21oa1J6bDVXREprYkdKdFZubGhWMDVtWXpKNGVsbFVUWFZsVnpGelVVaEtiRnB1VFhaa1IwWnVZM2s1TWsxcE5IaE1ha0YzVDFGWlMwdDNXVUlLUWtGSFJIWjZRVUpCVVZGeVlVaFNNR05JVFRaTWVUa3dZakowYkdKcE5XaFpNMUp3WWpJMWVreHRaSEJrUjJneFdXNVdlbHBZU21waU1qVXdXbGMxTUFwTWJVNTJZbFJCVjBKbmIzSkNaMFZGUVZsUEwwMUJSVU5DUVdoNldUSm9iRnBJVm5OYVZFRXlRbWR2Y2tKblJVVkJXVTh2VFVGRlJFSkRhR2xaVjBwdENrNUVaR2xhVkZVeFRrUkthRTU2UW14TmFsRjNUbnBLYkU1SFNUSlphbFV5V1ZSQk5Wa3lSVFZOZWxwcldWZEpORTFDYTBkRGFYTkhRVkZSUW1jM09IY0tRVkZSUlVNeFFubGFVekZUV2xkNGJGbFlUbXhOUkZWSFEybHpSMEZSVVVKbk56aDNRVkZWUlVveVJqTmplVEYzWWpOa2JHTnVVblppTW5oNlRETkNkZ3BrTWxaNVpFYzVkbUpJVFhSaVIwWjBXVzFTYUV4WVFqVmtSMmgyWW1wQlowSm5iM0pDWjBWRlFWbFBMMDFCUlVkQ1FrcDVXbGRhZWt3eWFHeFpWMUo2Q2t3eVVteGtiVlp6WWpOQmQwOTNXVXRMZDFsQ1FrRkhSSFo2UVVKRFFWRjBSRU4wYjJSSVVuZGplbTkyVEROU2RtRXlWblZNYlVacVpFZHNkbUp1VFhVS1dqSnNNR0ZJVm1sa1dFNXNZMjFPZG1KdVVteGlibEYxV1RJNWRFMUpSMGRDWjI5eVFtZEZSVUZaVHk5TlFVVktRa2huVFdSdGFEQmtTRUo2VDJrNGRncGFNbXd3WVVoV2FVeHRUblppVXpsNllraE9hRXhYV25sWlZ6RnNaREk1ZVdGNU9YcGlTRTVvVEZka2NHUkhhREZaYVRGdVdsYzFiR050UmpCaU0wbDJDa3h0WkhCa1IyZ3hXV2s1TTJJelNuSmFiWGgyWkROTmRsb3lWblZhV0Vwb1pFYzVlVmd5Wkd4aWJWWjVZVmRPWm1NeWVIcFpWRTExWlZjeGMxRklTbXdLV201TmRtUkhSbTVqZVRreVRXazBlRXhxUVhkUFFWbExTM2RaUWtKQlIwUjJla0ZDUTJkUmNVUkRhRzFPTWxKclQwZE5NVTVIVFhsTlJGa3pXVzFHYlFwWmVrVjVXVEpGTTFsVVZURk9WR3N4V2tSV2JGcFViR2xPZWxWNVRVUlNhRTFDTUVkRGFYTkhRVkZSUW1jM09IZEJVWE5GUkhkM1Rsb3liREJoU0ZacENreFhhSFpqTTFKc1drUkNTMEpuYjNKQ1owVkZRVmxQTDAxQlJVMUNSSGROVDIxb01HUklRbnBQYVRoMldqSnNNR0ZJVm1sTWJVNTJZbE01YUdRelRYUUtZMGM1TTFwWVNqQmlNamx6WTNrNWQySXpaR3hqYmxKMllqSjRla3hYZUdoaVYwcHJXVk14ZDJWWVVtOWlNalIzVDBGWlMwdDNXVUpDUVVkRWRucEJRZ3BFVVZGeFJFTm9hVmxYU20xT1JHUnBXbFJWTVU1RVNtaE9la0pzVFdwUmQwNTZTbXhPUjBreVdXcFZNbGxVUVRWWk1rVTFUWHBhYTFsWFNUUk5RMGxIQ2tOcGMwZEJVVkZDWnpjNGQwRlJORVZHUVhkVFkyMVdiV041T1c5YVYwWnJZM2s1YTFwWVdteGlSemwzVFVKclIwTnBjMGRCVVZGQ1p6YzRkMEZST0VVS1EzZDNTazFxU1hoUFZFVTFUWHBqTlUxRVJVZERhWE5IUVZGUlFtYzNPSGRCVWtGRlNYZDNhR0ZJVWpCalNFMDJUSGs1Ym1GWVVtOWtWMGwxV1RJNWRBcE1Na1l6WTNreGQySXpaR3hqYmxKMllqSjRlazFDYTBkRGFYTkhRVkZSUW1jM09IZEJVa1ZGUTNkM1NrMVVTVFZOVkVrelRtcE5ORTFJT0VkRGFYTkhDa0ZSVVVKbk56aDNRVkpKUldOUmVIWmhTRkl3WTBoTk5reDVPVzVoV0ZKdlpGZEpkVmt5T1hSTU1rWXpZM2t4ZDJJelpHeGpibEoyWWpKNGVrd3pRbllLWkRKV2VXUkhPWFppU0UxMFlrZEdkRmx0VW1oTVdFSTFaRWRvZG1KcE9IVmFNbXd3WVVoV2FVd3paSFpqYlhSdFlrYzVNMk41T1hkamJWVjBZMjFXY3dwYVYwWjZXbE0xTldKWGVFRmpiVlp0WTNrNWIxcFhSbXRqZVRscldsaGFiR0pIT1hkTlJHZEhRMmx6UjBGUlVVSm5OemgzUVZKTlJVdG5kMjlaYlVacENscHFVVE5aYlZVeFRsUlJlVmxVWTNkYVZFa3dUVVJqZVZwVVVtbE9iVWt4VG0xRmQwOVhUbWhQVkUweVdrZEdhVTlFUVZsQ1oyOXlRbWRGUlVGWlR5OEtUVUZGVlVKQmIwMURTRTVxWVVkV2EyUlhlR3hOUnpSSFEybHpSMEZSVVVKbk56aDNRVkpWUlZsQmVHVmhTRkl3WTBoTk5reDVPVzVoV0ZKdlpGZEpkUXBaTWpsMFRESkdNMk41TVhkaU0yUnNZMjVTZG1JeWVIcE1NMEoyWkRKV2VXUkhPWFppU0UxMFlrZEdkRmx0VW1oTVdFSTFaRWRvZG1KcE9XaFpNMUp3Q21JeU5YcE1NMG94WW01TmRrMVVVWHBQVkdjd1QwUm5NVTE2VFhaWldGSXdXbGN4ZDJSSVRYWk5WRUZYUW1kdmNrSm5SVVZCV1U4dlRVRkZWMEpCWjAwS1FtNUNNVmx0ZUhCWmVrTkNhV2RaUzB0M1dVSkNRVWhYWlZGSlJVRm5VamhDU0c5QlpVRkNNa0ZPTURsTlIzSkhlSGhGZVZsNGEyVklTbXh1VG5kTGFRcFRiRFkwTTJwNWRDODBaVXRqYjBGMlMyVTJUMEZCUVVKc2FWQnJSV1l3UVVGQlVVUkJSV04zVWxGSloyWnBObEJITVhCRFFVTjBXVnA0VFZoa01rNHZDbTVLWnpaTE5uUlVUREJNVXl0UWNIb3Zaa00wUmpSclEwbFJRM0pXVG5WSFYzVnZWbFZxVDI4NFdIaFRUMFZTWnpreVZGVkdUVVV6TjI5UmQwZFpOblVLVnpOTE1HeDZRVXRDWjJkeGFHdHFUMUJSVVVSQmQwNXZRVVJDYkVGcVFVZFdZbFowVms1cGFEWmFiM2x4ZGtNd2VqSjZTVzVNSzNwemVIUkpRVXQ2S3dvMVJEQXpTMmtyV2pSamJrNXZUVUppUkZwNmRsTXJWRUpEYUdOQ2FuQTBRMDFSUTNOMVl6WklRemxoTVVaa1RGbDFabFp2V0ZFclZXTTFSRUZyWkhKQkNsbHVWM2xpYjBGRGFGbHJjVXRHYVVkRWJGRk5kMFk0TlU1TFVFRjFTVU5RVEhoWlBRb3RMUzB0TFVWT1JDQkRSVkpVU1VaSlEwRlVSUzB0TFMwdENnPT0ifV19fQ=="}]}, "dsseEnvelope":{"payload":"eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjAuMSIsInByZWRpY2F0ZVR5cGUiOiJodHRwczovL3Nsc2EuZGV2L3Byb3ZlbmFuY2UvdjAuMiIsInN1YmplY3QiOlt7Im5hbWUiOiIuL2F3c19sYW1iZGFfcG93ZXJ0b29scy0zLjEwLjFhMi1weTMtbm9uZS1hbnkud2hsIiwiZGlnZXN0Ijp7InNoYTI1NiI6ImM1MGE0YjkyOWNkMmUzYTI5YWNiMGU4MmUzMTJjYzRjZTk3ZTdiNDU1ZDMyMDZmNzJhZTkxZDY1ZGY3YjQ3M2MifX0seyJuYW1lIjoiLi9hd3NfbGFtYmRhX3Bvd2VydG9vbHMtMy4xMC4xYTIudGFyLmd6IiwiZGlnZXN0Ijp7InNoYTI1NiI6ImE3Y2ZlN2NlMmYxOWYwZTdhZjY4MmQ2OGRjM2RjNzYwZGViMjgyYTg4Mjg4YzdjMWVlYmM1OGQzNGRhMDM2NTQifX1dLCJwcmVkaWNhdGUiOnsiYnVpbGRlciI6eyJpZCI6Imh0dHBzOi8vZ2l0aHViLmNvbS9zbHNhLWZyYW1ld29yay9zbHNhLWdpdGh1Yi1nZW5lcmF0b3IvLmdpdGh1Yi93b3JrZmxvd3MvZ2VuZXJhdG9yX2dlbmVyaWNfc2xzYTMueW1sQHJlZnMvdGFncy92Mi4xLjAifSwiYnVpbGRUeXBlIjoiaHR0cHM6Ly9naXRodWIuY29tL3Nsc2EtZnJhbWV3b3JrL3Nsc2EtZ2l0aHViLWdlbmVyYXRvci9nZW5lcmljQHYxIiwiaW52b2NhdGlvbiI6eyJjb25maWdTb3VyY2UiOnsidXJpIjoiZ2l0K2h0dHBzOi8vZ2l0aHViLmNvbS9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob25AcmVmcy9oZWFkcy9kZXZlbG9wIiwiZGlnZXN0Ijp7InNoYTEiOiJiYWJmNDdiZTU1NDJhNzBlMjQwNzJlNGI2YjU2YTA5Y2E5MzZkYWI4In0sImVudHJ5UG9pbnQiOiIuZ2l0aHViL3dvcmtmbG93cy9wcmUtcmVsZWFzZS55bWwifSwicGFyYW1ldGVycyI6eyJ2YXJzIjp7fX0sImVudmlyb25tZW50Ijp7ImdpdGh1Yl9hY3RvciI6ImxlYW5kcm9kYW1hc2NlbmEiLCJnaXRodWJfYWN0b3JfaWQiOiI0Mjk1MTczIiwiZ2l0aHViX2Jhc2VfcmVmIjoiIiwiZ2l0aHViX2V2ZW50X25hbWUiOiJzY2hlZHVsZSIsImdpdGh1Yl9ldmVudF9wYXlsb2FkIjp7ImVudGVycHJpc2UiOnsiYXZhdGFyX3VybCI6Imh0dHBzOi8vYXZhdGFycy5naXRodWJ1c2VyY29udGVudC5jb20vYi8xMjkwP3Y9NCIsImNyZWF0ZWRfYXQiOiIyMDE5LTExLTEzVDE4OjA1OjQxWiIsImRlc2NyaXB0aW9uIjoiIiwiaHRtbF91cmwiOiJodHRwczovL2dpdGh1Yi5jb20vZW50ZXJwcmlzZXMvYW1hem9uIiwiaWQiOjEyOTAsIm5hbWUiOiJBbWF6b24iLCJub2RlX2lkIjoiTURFd09rVnVkR1Z5Y0hKcGMyVXhNamt3Iiwic2x1ZyI6ImFtYXpvbiIsInVwZGF0ZWRfYXQiOiIyMDI0LTA5LTMwVDIxOjAyOjMwWiIsIndlYnNpdGVfdXJsIjoiaHR0cHM6Ly93d3cuYW1hem9uLmNvbS8ifSwib3JnYW5pemF0aW9uIjp7ImF2YXRhcl91cmwiOiJodHRwczovL2F2YXRhcnMuZ2l0aHVidXNlcmNvbnRlbnQuY29tL3UvMTI5MTI3NjM4P3Y9NCIsImRlc2NyaXB0aW9uIjoiIiwiZXZlbnRzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vb3Jncy9hd3MtcG93ZXJ0b29scy9ldmVudHMiLCJob29rc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL29yZ3MvYXdzLXBvd2VydG9vbHMvaG9va3MiLCJpZCI6MTI5MTI3NjM4LCJpc3N1ZXNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9vcmdzL2F3cy1wb3dlcnRvb2xzL2lzc3VlcyIsImxvZ2luIjoiYXdzLXBvd2VydG9vbHMiLCJtZW1iZXJzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vb3Jncy9hd3MtcG93ZXJ0b29scy9tZW1iZXJzey9tZW1iZXJ9Iiwibm9kZV9pZCI6Ik9fa2dET0I3SlUxZyIsInB1YmxpY19tZW1iZXJzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vb3Jncy9hd3MtcG93ZXJ0b29scy9wdWJsaWNfbWVtYmVyc3svbWVtYmVyfSIsInJlcG9zX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vb3Jncy9hd3MtcG93ZXJ0b29scy9yZXBvcyIsInVybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vb3Jncy9hd3MtcG93ZXJ0b29scyJ9LCJyZXBvc2l0b3J5Ijp7ImFsbG93X2ZvcmtpbmciOnRydWUsImFyY2hpdmVfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24ve2FyY2hpdmVfZm9ybWF0fXsvcmVmfSIsImFyY2hpdmVkIjpmYWxzZSwiYXNzaWduZWVzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2Fzc2lnbmVlc3svdXNlcn0iLCJibG9ic191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9naXQvYmxvYnN7L3NoYX0iLCJicmFuY2hlc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9icmFuY2hlc3svYnJhbmNofSIsImNsb25lX3VybCI6Imh0dHBzOi8vZ2l0aHViLmNvbS9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24uZ2l0IiwiY29sbGFib3JhdG9yc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9jb2xsYWJvcmF0b3Jzey9jb2xsYWJvcmF0b3J9IiwiY29tbWVudHNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vY29tbWVudHN7L251bWJlcn0iLCJjb21taXRzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2NvbW1pdHN7L3NoYX0iLCJjb21wYXJlX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2NvbXBhcmUve2Jhc2V9Li4ue2hlYWR9IiwiY29udGVudHNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vY29udGVudHMveytwYXRofSIsImNvbnRyaWJ1dG9yc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9jb250cmlidXRvcnMiLCJjcmVhdGVkX2F0IjoiMjAxOS0xMS0xNVQxMjoyNjoxMloiLCJjdXN0b21fcHJvcGVydGllcyI6e30sImRlZmF1bHRfYnJhbmNoIjoiZGV2ZWxvcCIsImRlcGxveW1lbnRzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2RlcGxveW1lbnRzIiwiZGVzY3JpcHRpb24iOiJBIGRldmVsb3BlciB0b29sa2l0IHRvIGltcGxlbWVudCBTZXJ2ZXJsZXNzIGJlc3QgcHJhY3RpY2VzIGFuZCBpbmNyZWFzZSBkZXZlbG9wZXIgdmVsb2NpdHkuIiwiZGlzYWJsZWQiOmZhbHNlLCJkb3dubG9hZHNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vZG93bmxvYWRzIiwiZXZlbnRzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2V2ZW50cyIsImZvcmsiOmZhbHNlLCJmb3JrcyI6NDE4LCJmb3Jrc19jb3VudCI6NDE4LCJmb3Jrc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9mb3JrcyIsImZ1bGxfbmFtZSI6ImF3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbiIsImdpdF9jb21taXRzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2dpdC9jb21taXRzey9zaGF9IiwiZ2l0X3JlZnNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vZ2l0L3JlZnN7L3NoYX0iLCJnaXRfdGFnc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9naXQvdGFnc3svc2hhfSIsImdpdF91cmwiOiJnaXQ6Ly9naXRodWIuY29tL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi5naXQiLCJoYXNfZGlzY3Vzc2lvbnMiOnRydWUsImhhc19kb3dubG9hZHMiOnRydWUsImhhc19pc3N1ZXMiOnRydWUsImhhc19wYWdlcyI6ZmFsc2UsImhhc19wcm9qZWN0cyI6dHJ1ZSwiaGFzX3dpa2kiOmZhbHNlLCJob21lcGFnZSI6Imh0dHBzOi8vZG9jcy5wb3dlcnRvb2xzLmF3cy5kZXYvbGFtYmRhL3B5dGhvbi9sYXRlc3QvIiwiaG9va3NfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vaG9va3MiLCJodG1sX3VybCI6Imh0dHBzOi8vZ2l0aHViLmNvbS9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24iLCJpZCI6MjIxOTE5Mzc5LCJpc190ZW1wbGF0ZSI6ZmFsc2UsImlzc3VlX2NvbW1lbnRfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vaXNzdWVzL2NvbW1lbnRzey9udW1iZXJ9IiwiaXNzdWVfZXZlbnRzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2lzc3Vlcy9ldmVudHN7L251bWJlcn0iLCJpc3N1ZXNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vaXNzdWVzey9udW1iZXJ9Iiwia2V5c191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9rZXlzey9rZXlfaWR9IiwibGFiZWxzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2xhYmVsc3svbmFtZX0iLCJsYW5ndWFnZSI6IlB5dGhvbiIsImxhbmd1YWdlc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9sYW5ndWFnZXMiLCJsaWNlbnNlIjp7ImtleSI6Im1pdC0wIiwibmFtZSI6Ik1JVCBObyBBdHRyaWJ1dGlvbiIsIm5vZGVfaWQiOiJNRGM2VEdsalpXNXpaVFF4Iiwic3BkeF9pZCI6Ik1JVC0wIiwidXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9saWNlbnNlcy9taXQtMCJ9LCJtZXJnZXNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vbWVyZ2VzIiwibWlsZXN0b25lc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9taWxlc3RvbmVzey9udW1iZXJ9IiwibWlycm9yX3VybCI6bnVsbCwibmFtZSI6InBvd2VydG9vbHMtbGFtYmRhLXB5dGhvbiIsIm5vZGVfaWQiOiJNREV3T2xKbGNHOXphWFJ2Y25reU1qRTVNVGt6TnprPSIsIm5vdGlmaWNhdGlvbnNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vbm90aWZpY2F0aW9uc3s/c2luY2UsYWxsLHBhcnRpY2lwYXRpbmd9Iiwib3Blbl9pc3N1ZXMiOjU0LCJvcGVuX2lzc3Vlc19jb3VudCI6NTQsIm93bmVyIjp7ImF2YXRhcl91cmwiOiJodHRwczovL2F2YXRhcnMuZ2l0aHVidXNlcmNvbnRlbnQuY29tL3UvMTI5MTI3NjM4P3Y9NCIsImV2ZW50c191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3VzZXJzL2F3cy1wb3dlcnRvb2xzL2V2ZW50c3svcHJpdmFjeX0iLCJmb2xsb3dlcnNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS91c2Vycy9hd3MtcG93ZXJ0b29scy9mb2xsb3dlcnMiLCJmb2xsb3dpbmdfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS91c2Vycy9hd3MtcG93ZXJ0b29scy9mb2xsb3dpbmd7L290aGVyX3VzZXJ9IiwiZ2lzdHNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS91c2Vycy9hd3MtcG93ZXJ0b29scy9naXN0c3svZ2lzdF9pZH0iLCJncmF2YXRhcl9pZCI6IiIsImh0bWxfdXJsIjoiaHR0cHM6Ly9naXRodWIuY29tL2F3cy1wb3dlcnRvb2xzIiwiaWQiOjEyOTEyNzYzOCwibG9naW4iOiJhd3MtcG93ZXJ0b29scyIsIm5vZGVfaWQiOiJPX2tnRE9CN0pVMWciLCJvcmdhbml6YXRpb25zX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vdXNlcnMvYXdzLXBvd2VydG9vbHMvb3JncyIsInJlY2VpdmVkX2V2ZW50c191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3VzZXJzL2F3cy1wb3dlcnRvb2xzL3JlY2VpdmVkX2V2ZW50cyIsInJlcG9zX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vdXNlcnMvYXdzLXBvd2VydG9vbHMvcmVwb3MiLCJzaXRlX2FkbWluIjpmYWxzZSwic3RhcnJlZF91cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3VzZXJzL2F3cy1wb3dlcnRvb2xzL3N0YXJyZWR7L293bmVyfXsvcmVwb30iLCJzdWJzY3JpcHRpb25zX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vdXNlcnMvYXdzLXBvd2VydG9vbHMvc3Vic2NyaXB0aW9ucyIsInR5cGUiOiJPcmdhbml6YXRpb24iLCJ1cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3VzZXJzL2F3cy1wb3dlcnRvb2xzIiwidXNlcl92aWV3X3R5cGUiOiJwdWJsaWMifSwicHJpdmF0ZSI6ZmFsc2UsInB1bGxzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL3B1bGxzey9udW1iZXJ9IiwicHVzaGVkX2F0IjoiMjAyNS0wNC0xMVQwODowMToyMFoiLCJyZWxlYXNlc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9yZWxlYXNlc3svaWR9Iiwic2l6ZSI6MTA2MzM3LCJzc2hfdXJsIjoiZ2l0QGdpdGh1Yi5jb206YXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uLmdpdCIsInN0YXJnYXplcnNfY291bnQiOjMwMjEsInN0YXJnYXplcnNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vc3RhcmdhemVycyIsInN0YXR1c2VzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL3N0YXR1c2VzL3tzaGF9Iiwic3Vic2NyaWJlcnNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vc3Vic2NyaWJlcnMiLCJzdWJzY3JpcHRpb25fdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vc3Vic2NyaXB0aW9uIiwic3ZuX3VybCI6Imh0dHBzOi8vZ2l0aHViLmNvbS9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24iLCJ0YWdzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL3RhZ3MiLCJ0ZWFtc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi90ZWFtcyIsInRvcGljcyI6WyJhd3MiLCJhd3MtbGFtYmRhIiwiaGFja3RvYmVyZmVzdCIsImxhbWJkYSIsInB5dGhvbiIsInNlcnZlcmxlc3MiXSwidHJlZXNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vZ2l0L3RyZWVzey9zaGF9IiwidXBkYXRlZF9hdCI6IjIwMjUtMDQtMTFUMDg6MDE6MjJaIiwidXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24iLCJ2aXNpYmlsaXR5IjoicHVibGljIiwid2F0Y2hlcnMiOjMwMjEsIndhdGNoZXJzX2NvdW50IjozMDIxLCJ3ZWJfY29tbWl0X3NpZ25vZmZfcmVxdWlyZWQiOnRydWV9LCJzY2hlZHVsZSI6IjAgOCAqICogMS01Iiwid29ya2Zsb3ciOiIuZ2l0aHViL3dvcmtmbG93cy9wcmUtcmVsZWFzZS55bWwifSwiZ2l0aHViX2hlYWRfcmVmIjoiIiwiZ2l0aHViX3JlZiI6InJlZnMvaGVhZHMvZGV2ZWxvcCIsImdpdGh1Yl9yZWZfdHlwZSI6ImJyYW5jaCIsImdpdGh1Yl9yZXBvc2l0b3J5X2lkIjoiMjIxOTE5Mzc5IiwiZ2l0aHViX3JlcG9zaXRvcnlfb3duZXIiOiJhd3MtcG93ZXJ0b29scyIsImdpdGh1Yl9yZXBvc2l0b3J5X293bmVyX2lkIjoiMTI5MTI3NjM4IiwiZ2l0aHViX3J1bl9hdHRlbXB0IjoiMSIsImdpdGh1Yl9ydW5faWQiOiIxNDM5ODQ4ODUzMyIsImdpdGh1Yl9ydW5fbnVtYmVyIjoiMjE3IiwiZ2l0aHViX3NoYTEiOiJiYWJmNDdiZTU1NDJhNzBlMjQwNzJlNGI2YjU2YTA5Y2E5MzZkYWI4In19LCJtZXRhZGF0YSI6eyJidWlsZEludm9jYXRpb25JRCI6IjE0Mzk4NDg4NTMzLTEiLCJjb21wbGV0ZW5lc3MiOnsicGFyYW1ldGVycyI6dHJ1ZSwiZW52aXJvbm1lbnQiOmZhbHNlLCJtYXRlcmlhbHMiOmZhbHNlfSwicmVwcm9kdWNpYmxlIjpmYWxzZX0sIm1hdGVyaWFscyI6W3sidXJpIjoiZ2l0K2h0dHBzOi8vZ2l0aHViLmNvbS9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob25AcmVmcy9oZWFkcy9kZXZlbG9wIiwiZGlnZXN0Ijp7InNoYTEiOiJiYWJmNDdiZTU1NDJhNzBlMjQwNzJlNGI2YjU2YTA5Y2E5MzZkYWI4In19XX19", "payloadType":"application/vnd.in-toto+json", "signatures":[{"sig":"MEYCIQCA6/PvPpBDqg6/CEplAmWYvpp9O4Rnejw/kOgbXjjkpwIhAOWqpp6HN/SGBu4aVYP79g9FlR2Bp6YyvsR8NjM/7k9N"}]}}
\ No newline at end of file
diff --git a/provenance/3.10.1a3/multiple.intoto.jsonl b/provenance/3.10.1a3/multiple.intoto.jsonl
new file mode 100644
index 00000000000..66b105244fb
--- /dev/null
+++ b/provenance/3.10.1a3/multiple.intoto.jsonl
@@ -0,0 +1 @@
+{"mediaType":"application/vnd.dev.sigstore.bundle.v0.3+json", "verificationMaterial":{"certificate":{"rawBytes":"MIIHaDCCBu2gAwIBAgIUMxz0a6HA82mTbLP4RH9DmLStVkgwCgYIKoZIzj0EAwMwNzEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MR4wHAYDVQQDExVzaWdzdG9yZS1pbnRlcm1lZGlhdGUwHhcNMjUwNDE0MDgwODA1WhcNMjUwNDE0MDgxODA1WjAAMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEPlt5aEBlBRckG+V5K4JGxRj92E1UZzD05Gzrmnwhc1WRZAv9W+XUqbvFvE8sLYRdnNsPY3lzeBB9qFUD5XsguaOCBgwwggYIMA4GA1UdDwEB/wQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcDAzAdBgNVHQ4EFgQUv4qjbB79QXZQT7Gft56uqQ26qE8wHwYDVR0jBBgwFoAU39Ppz1YkEZb5qNjpKFWixi4YZD8wgYQGA1UdEQEB/wR6MHiGdmh0dHBzOi8vZ2l0aHViLmNvbS9zbHNhLWZyYW1ld29yay9zbHNhLWdpdGh1Yi1nZW5lcmF0b3IvLmdpdGh1Yi93b3JrZmxvd3MvZ2VuZXJhdG9yX2dlbmVyaWNfc2xzYTMueW1sQHJlZnMvdGFncy92Mi4xLjAwOQYKKwYBBAGDvzABAQQraHR0cHM6Ly90b2tlbi5hY3Rpb25zLmdpdGh1YnVzZXJjb250ZW50LmNvbTAWBgorBgEEAYO/MAECBAhzY2hlZHVsZTA2BgorBgEEAYO/MAEDBCg0YmY2M2RiOGU0NmEzMDM2NDlhZjZlOTk1NmM4ZTI5NTI1ZjYxNzk4MBkGCisGAQQBg78wAQQEC1ByZS1SZWxlYXNlMDUGCisGAQQBg78wAQUEJ2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbjAgBgorBgEEAYO/MAEGBBJyZWZzL2hlYWRzL2RldmVsb3AwOwYKKwYBBAGDvzABCAQtDCtodHRwczovL3Rva2VuLmFjdGlvbnMuZ2l0aHVidXNlcmNvbnRlbnQuY29tMIGGBgorBgEEAYO/MAEJBHgMdmh0dHBzOi8vZ2l0aHViLmNvbS9zbHNhLWZyYW1ld29yay9zbHNhLWdpdGh1Yi1nZW5lcmF0b3IvLmdpdGh1Yi93b3JrZmxvd3MvZ2VuZXJhdG9yX2dlbmVyaWNfc2xzYTMueW1sQHJlZnMvdGFncy92Mi4xLjAwOAYKKwYBBAGDvzABCgQqDChmN2RkOGM1NGMyMDY3YmFmYzEyY2E3YTU1NTk1ZDVlZTliNzUyMDRhMB0GCisGAQQBg78wAQsEDwwNZ2l0aHViLWhvc3RlZDBKBgorBgEEAYO/MAEMBDwMOmh0dHBzOi8vZ2l0aHViLmNvbS9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24wOAYKKwYBBAGDvzABDQQqDCg0YmY2M2RiOGU0NmEzMDM2NDlhZjZlOTk1NmM4ZTI5NTI1ZjYxNzk4MCIGCisGAQQBg78wAQ4EFAwScmVmcy9oZWFkcy9kZXZlbG9wMBkGCisGAQQBg78wAQ8ECwwJMjIxOTE5Mzc5MDEGCisGAQQBg78wARAEIwwhaHR0cHM6Ly9naXRodWIuY29tL2F3cy1wb3dlcnRvb2xzMBkGCisGAQQBg78wAREECwwJMTI5MTI3NjM4MH8GCisGAQQBg78wARIEcQxvaHR0cHM6Ly9naXRodWIuY29tL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi8uZ2l0aHViL3dvcmtmbG93cy9wcmUtcmVsZWFzZS55bWxAcmVmcy9oZWFkcy9kZXZlbG9wMDgGCisGAQQBg78wARMEKgwoNGJmNjNkYjhlNDZhMzAzNjQ5YWY2ZTk5NTZjOGUyOTUyNWY2MTc5ODAYBgorBgEEAYO/MAEUBAoMCHNjaGVkdWxlMG4GCisGAQQBg78wARUEYAxeaHR0cHM6Ly9naXRodWIuY29tL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9hY3Rpb25zL3J1bnMvMTQ0NDA1MDkzODcvYXR0ZW1wdHMvMTAWBgorBgEEAYO/MAEWBAgMBnB1YmxpYzCBiwYKKwYBBAHWeQIEAgR9BHsAeQB3AN09MGrGxxEyYxkeHJlnNwKiSl643jyt/4eKcoAvKe6OAAABljNXXGsAAAQDAEgwRgIhALKeEBjD5v12tCsGsan9YLwYeGT2FbondSC/dQ94IxdLAiEAnuhtyNsqImj7A4GKgKFrSEkiXjxO8bwdya6SL7TLLjAwCgYIKoZIzj0EAwMDaQAwZgIxALeOoSz4ISB1vKFH7BvKeCpI2XYN2+1XnqIPcF3/bMTLNqiSpg6Y/rv4W5h68zyh6QIxALGOpONv7uxSyniRzqrJ64YqHZ6OCFrVnOwoS8OtNLcwA1ACCfo+e4pjnp3h/W1CnQ=="}, "tlogEntries":[{"logIndex":"196570490", "logId":{"keyId":"wNI9atQGlz+VWfO6LRygH4QUfY/8W4RFwiT5i5WRgB0="}, "kindVersion":{"kind":"dsse", "version":"0.0.1"}, "integratedTime":"1744618085", "inclusionPromise":{"signedEntryTimestamp":"MEUCIQCOJAxX4/5ZzfOmgyR1iPnlaLa4HOr5g3TrIxM1ohmy2gIga6MI/msxh+XanVNUyHRKNgLzdvcTGVonTsFCwtL1oJk="}, "inclusionProof":{"logIndex":"74666228", "rootHash":"NlqKHxEP49bXAuYFo6XOAlYz++tpO2zEQuyLXctu9NI=", "treeSize":"74666229", "hashes":["JxqnjcsY5GmjWrRMrsZewEnOVZYO96DOxRoMPcHv0ZQ=", "qspu5szCIsU5iIe9USgqBqLjmRx22xJkYDzbDJmoeko=", "MsPIFeXs8LFbEerzjWeQ47ibMjpV3l8I5uSOg4nc5MU=", "XB2zO6cAv9HD6MikNMWqm2PdNVy1kLQJaTKLHktXpAo=", "mWRN+HwR/T02ORx8qJDJgLGHxOjWhj1UO6++Gs1xFCI=", "g2b3WwqnUhwdMq3oXYicnathsxc3Qp0B+TDQFyQ/Qho=", "B+KyBR/phHtCQMpFSZWYqzMeKsmKqJ4pgTKWLvvkn1I=", "1bOdujSIG3crNJ0quGcYukszSJolCYnp4kqosIjSZZQ=", "oeC+QPv8RsnvqUcQqN3O08KUNIKKBM+Ey0H/KQTr2uY=", "p8GZJf0dbUN4F0OOaYZUcmb94boPBS/aWWtldW3eAhU=", "bUMWi9afi8M+WrpEiXczKOIZWruoe38aV/lXN5Z5o9E=", "WEm5OgPzJpYROv+4CcrieexCYyQKrLUH3hbxmcQQ+DM=", "7v8qPHNDLerpduaMx06eb/MwgoQwczTn/cYGKX/9wZ4="], "checkpoint":{"envelope":"rekor.sigstore.dev - 1193050959916656506\n74666229\nNlqKHxEP49bXAuYFo6XOAlYz++tpO2zEQuyLXctu9NI=\n\n— rekor.sigstore.dev wNI9ajBEAiAXlWlvhJXUq0Zw4KV4plv1Ar0f2MwYGpztQR6CvQcT/QIgGSUJtYbyE43Ar9O7/KfZY0uTwa2iyq4if1a0MtmmfuM=\n"}}, "canonicalizedBody":"eyJhcGlWZXJzaW9uIjoiMC4wLjEiLCJraW5kIjoiZHNzZSIsInNwZWMiOnsiZW52ZWxvcGVIYXNoIjp7ImFsZ29yaXRobSI6InNoYTI1NiIsInZhbHVlIjoiMjRkOTkyY2UwOWI4YzNmNjk4Y2FiNDRmNzczMzQyZjc3YTA1OTg2Y2QwZjg1NjFiODZkNGZjZTFjZWY4NmQ0YyJ9LCJwYXlsb2FkSGFzaCI6eyJhbGdvcml0aG0iOiJzaGEyNTYiLCJ2YWx1ZSI6ImNjYTMyMWM2ZjQ4MzAyZGQ2MzlmMjhmYmEzZWY1ZGQ3ZDU4MzRjMjRmNWQ4MDFhYTM4ODkyMDMwMDc1MmM0MWQifSwic2lnbmF0dXJlcyI6W3sic2lnbmF0dXJlIjoiTUVVQ0lRRDRHQ2pNK3RkK0EwT2tablIvWmV5SVN4V2hLZkhLb2RtSC9WMFdGcjh1TXdJZ2NQeXl2NXhweUc3ZzJxZktKUERCRXpRWGQvZWV4QUVHWGJTa1RzN2Q1Y1U9IiwidmVyaWZpZXIiOiJMUzB0TFMxQ1JVZEpUaUJEUlZKVVNVWkpRMEZVUlMwdExTMHRDazFKU1VoaFJFTkRRblV5WjBGM1NVSkJaMGxWVFhoNk1HRTJTRUU0TW0xVVlreFFORkpJT1VSdFRGTjBWbXRuZDBObldVbExiMXBKZW1vd1JVRjNUWGNLVG5wRlZrMUNUVWRCTVZWRlEyaE5UV015Ykc1ak0xSjJZMjFWZFZwSFZqSk5ValIzU0VGWlJGWlJVVVJGZUZaNllWZGtlbVJIT1hsYVV6RndZbTVTYkFwamJURnNXa2RzYUdSSFZYZElhR05PVFdwVmQwNUVSVEJOUkdkM1QwUkJNVmRvWTA1TmFsVjNUa1JGTUUxRVozaFBSRUV4VjJwQlFVMUdhM2RGZDFsSUNrdHZXa2w2YWpCRFFWRlpTVXR2V2tsNmFqQkVRVkZqUkZGblFVVlFiSFExWVVWQ2JFSlNZMnRISzFZMVN6UktSM2hTYWpreVJURlZXbnBFTURWSGVuSUtiVzUzYUdNeFYxSmFRWFk1Vnl0WVZYRmlka1oyUlRoelRGbFNaRzVPYzFCWk0yeDZaVUpDT1hGR1ZVUTFXSE5uZFdGUFEwSm5kM2RuWjFsSlRVRTBSd3BCTVZWa1JIZEZRaTkzVVVWQmQwbElaMFJCVkVKblRsWklVMVZGUkVSQlMwSm5aM0pDWjBWR1FsRmpSRUY2UVdSQ1owNVdTRkUwUlVablVWVjJOSEZxQ21KQ056bFJXRnBSVkRkSFpuUTFOblZ4VVRJMmNVVTRkMGgzV1VSV1VqQnFRa0puZDBadlFWVXpPVkJ3ZWpGWmEwVmFZalZ4VG1wd1MwWlhhWGhwTkZrS1drUTRkMmRaVVVkQk1WVmtSVkZGUWk5M1VqWk5TR2xIWkcxb01HUklRbnBQYVRoMldqSnNNR0ZJVm1sTWJVNTJZbE01ZW1KSVRtaE1WMXA1V1ZjeGJBcGtNamw1WVhrNWVtSklUbWhNVjJSd1pFZG9NVmxwTVc1YVZ6VnNZMjFHTUdJelNYWk1iV1J3WkVkb01WbHBPVE5pTTBweVdtMTRkbVF6VFhaYU1sWjFDbHBZU21oa1J6bDVXREprYkdKdFZubGhWMDVtWXpKNGVsbFVUWFZsVnpGelVVaEtiRnB1VFhaa1IwWnVZM2s1TWsxcE5IaE1ha0YzVDFGWlMwdDNXVUlLUWtGSFJIWjZRVUpCVVZGeVlVaFNNR05JVFRaTWVUa3dZakowYkdKcE5XaFpNMUp3WWpJMWVreHRaSEJrUjJneFdXNVdlbHBZU21waU1qVXdXbGMxTUFwTWJVNTJZbFJCVjBKbmIzSkNaMFZGUVZsUEwwMUJSVU5DUVdoNldUSm9iRnBJVm5OYVZFRXlRbWR2Y2tKblJVVkJXVTh2VFVGRlJFSkRaekJaYlZreUNrMHlVbWxQUjFVd1RtMUZlazFFVFRKT1JHeG9XbXBhYkU5VWF6Rk9iVTAwV2xSSk5VNVVTVEZhYWxsNFRucHJORTFDYTBkRGFYTkhRVkZSUW1jM09IY0tRVkZSUlVNeFFubGFVekZUV2xkNGJGbFlUbXhOUkZWSFEybHpSMEZSVVVKbk56aDNRVkZWUlVveVJqTmplVEYzWWpOa2JHTnVVblppTW5oNlRETkNkZ3BrTWxaNVpFYzVkbUpJVFhSaVIwWjBXVzFTYUV4WVFqVmtSMmgyWW1wQlowSm5iM0pDWjBWRlFWbFBMMDFCUlVkQ1FrcDVXbGRhZWt3eWFHeFpWMUo2Q2t3eVVteGtiVlp6WWpOQmQwOTNXVXRMZDFsQ1FrRkhSSFo2UVVKRFFWRjBSRU4wYjJSSVVuZGplbTkyVEROU2RtRXlWblZNYlVacVpFZHNkbUp1VFhVS1dqSnNNR0ZJVm1sa1dFNXNZMjFPZG1KdVVteGlibEYxV1RJNWRFMUpSMGRDWjI5eVFtZEZSVUZaVHk5TlFVVktRa2huVFdSdGFEQmtTRUo2VDJrNGRncGFNbXd3WVVoV2FVeHRUblppVXpsNllraE9hRXhYV25sWlZ6RnNaREk1ZVdGNU9YcGlTRTVvVEZka2NHUkhhREZaYVRGdVdsYzFiR050UmpCaU0wbDJDa3h0WkhCa1IyZ3hXV2s1TTJJelNuSmFiWGgyWkROTmRsb3lWblZhV0Vwb1pFYzVlVmd5Wkd4aWJWWjVZVmRPWm1NeWVIcFpWRTExWlZjeGMxRklTbXdLV201TmRtUkhSbTVqZVRreVRXazBlRXhxUVhkUFFWbExTM2RaUWtKQlIwUjJla0ZDUTJkUmNVUkRhRzFPTWxKclQwZE5NVTVIVFhsTlJGa3pXVzFHYlFwWmVrVjVXVEpGTTFsVVZURk9WR3N4V2tSV2JGcFViR2xPZWxWNVRVUlNhRTFDTUVkRGFYTkhRVkZSUW1jM09IZEJVWE5GUkhkM1Rsb3liREJoU0ZacENreFhhSFpqTTFKc1drUkNTMEpuYjNKQ1owVkZRVmxQTDAxQlJVMUNSSGROVDIxb01HUklRbnBQYVRoMldqSnNNR0ZJVm1sTWJVNTJZbE01YUdRelRYUUtZMGM1TTFwWVNqQmlNamx6WTNrNWQySXpaR3hqYmxKMllqSjRla3hYZUdoaVYwcHJXVk14ZDJWWVVtOWlNalIzVDBGWlMwdDNXVUpDUVVkRWRucEJRZ3BFVVZGeFJFTm5NRmx0V1RKTk1sSnBUMGRWTUU1dFJYcE5SRTB5VGtSc2FGcHFXbXhQVkdzeFRtMU5ORnBVU1RWT1ZFa3hXbXBaZUU1NmF6Uk5RMGxIQ2tOcGMwZEJVVkZDWnpjNGQwRlJORVZHUVhkVFkyMVdiV041T1c5YVYwWnJZM2s1YTFwWVdteGlSemwzVFVKclIwTnBjMGRCVVZGQ1p6YzRkMEZST0VVS1EzZDNTazFxU1hoUFZFVTFUWHBqTlUxRVJVZERhWE5IUVZGUlFtYzNPSGRCVWtGRlNYZDNhR0ZJVWpCalNFMDJUSGs1Ym1GWVVtOWtWMGwxV1RJNWRBcE1Na1l6WTNreGQySXpaR3hqYmxKMllqSjRlazFDYTBkRGFYTkhRVkZSUW1jM09IZEJVa1ZGUTNkM1NrMVVTVFZOVkVrelRtcE5ORTFJT0VkRGFYTkhDa0ZSVVVKbk56aDNRVkpKUldOUmVIWmhTRkl3WTBoTk5reDVPVzVoV0ZKdlpGZEpkVmt5T1hSTU1rWXpZM2t4ZDJJelpHeGpibEoyWWpKNGVrd3pRbllLWkRKV2VXUkhPWFppU0UxMFlrZEdkRmx0VW1oTVdFSTFaRWRvZG1KcE9IVmFNbXd3WVVoV2FVd3paSFpqYlhSdFlrYzVNMk41T1hkamJWVjBZMjFXY3dwYVYwWjZXbE0xTldKWGVFRmpiVlp0WTNrNWIxcFhSbXRqZVRscldsaGFiR0pIT1hkTlJHZEhRMmx6UjBGUlVVSm5OemgzUVZKTlJVdG5kMjlPUjBwdENrNXFUbXRaYW1oc1RrUmFhRTE2UVhwT2FsRTFXVmRaTWxwVWF6Vk9WRnBxVDBkVmVVOVVWWGxPVjFreVRWUmpOVTlFUVZsQ1oyOXlRbWRGUlVGWlR5OEtUVUZGVlVKQmIwMURTRTVxWVVkV2EyUlhlR3hOUnpSSFEybHpSMEZSVVVKbk56aDNRVkpWUlZsQmVHVmhTRkl3WTBoTk5reDVPVzVoV0ZKdlpGZEpkUXBaTWpsMFRESkdNMk41TVhkaU0yUnNZMjVTZG1JeWVIcE1NMEoyWkRKV2VXUkhPWFppU0UxMFlrZEdkRmx0VW1oTVdFSTFaRWRvZG1KcE9XaFpNMUp3Q21JeU5YcE1NMG94WW01TmRrMVVVVEJPUkVFeFRVUnJlazlFWTNaWldGSXdXbGN4ZDJSSVRYWk5WRUZYUW1kdmNrSm5SVVZCV1U4dlRVRkZWMEpCWjAwS1FtNUNNVmx0ZUhCWmVrTkNhWGRaUzB0M1dVSkNRVWhYWlZGSlJVRm5VamxDU0hOQlpWRkNNMEZPTURsTlIzSkhlSGhGZVZsNGEyVklTbXh1VG5kTGFRcFRiRFkwTTJwNWRDODBaVXRqYjBGMlMyVTJUMEZCUVVKc2FrNVlXRWR6UVVGQlVVUkJSV2QzVW1kSmFFRk1TMlZGUW1wRU5YWXhNblJEYzBkellXNDVDbGxNZDFsbFIxUXlSbUp2Ym1SVFF5OWtVVGswU1hoa1RFRnBSVUZ1ZFdoMGVVNXpjVWx0YWpkQk5FZExaMHRHY2xORmEybFlhbmhQT0dKM1pIbGhObE1LVERkVVRFeHFRWGREWjFsSlMyOWFTWHBxTUVWQmQwMUVZVkZCZDFwblNYaEJUR1ZQYjFONk5FbFRRakYyUzBaSU4wSjJTMlZEY0VreVdGbE9NaXN4V0FwdWNVbFFZMFl6TDJKTlZFeE9jV2xUY0djMldTOXlkalJYTldnMk9IcDVhRFpSU1hoQlRFZFBjRTlPZGpkMWVGTjVibWxTZW5GeVNqWTBXWEZJV2paUENrTkdjbFp1VDNkdlV6aFBkRTVNWTNkQk1VRkRRMlp2SzJVMGNHcHVjRE5vTDFjeFEyNVJQVDBLTFMwdExTMUZUa1FnUTBWU1ZFbEdTVU5CVkVVdExTMHRMUW89In1dfX0="}]}, "dsseEnvelope":{"payload":"eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjAuMSIsInByZWRpY2F0ZVR5cGUiOiJodHRwczovL3Nsc2EuZGV2L3Byb3ZlbmFuY2UvdjAuMiIsInN1YmplY3QiOlt7Im5hbWUiOiIuL2F3c19sYW1iZGFfcG93ZXJ0b29scy0zLjEwLjFhMy1weTMtbm9uZS1hbnkud2hsIiwiZGlnZXN0Ijp7InNoYTI1NiI6Ijk4NGQ5NDhjZjQwMDY5N2RiODczMDUxMjFmNTNkYWViOWJmZWRkYzI0MDc3YmUwNTMxZTY2OGQwYmJkYjJmMTMifX0seyJuYW1lIjoiLi9hd3NfbGFtYmRhX3Bvd2VydG9vbHMtMy4xMC4xYTMudGFyLmd6IiwiZGlnZXN0Ijp7InNoYTI1NiI6ImQ4ZjNiYjgwYjdiMDllZTNhMWI4YTcwMDljZGE3NGJmNGUxNTIzNmEyOGU5ZmUwYjY5NmRkYTVjNTEwN2EzNzYifX1dLCJwcmVkaWNhdGUiOnsiYnVpbGRlciI6eyJpZCI6Imh0dHBzOi8vZ2l0aHViLmNvbS9zbHNhLWZyYW1ld29yay9zbHNhLWdpdGh1Yi1nZW5lcmF0b3IvLmdpdGh1Yi93b3JrZmxvd3MvZ2VuZXJhdG9yX2dlbmVyaWNfc2xzYTMueW1sQHJlZnMvdGFncy92Mi4xLjAifSwiYnVpbGRUeXBlIjoiaHR0cHM6Ly9naXRodWIuY29tL3Nsc2EtZnJhbWV3b3JrL3Nsc2EtZ2l0aHViLWdlbmVyYXRvci9nZW5lcmljQHYxIiwiaW52b2NhdGlvbiI6eyJjb25maWdTb3VyY2UiOnsidXJpIjoiZ2l0K2h0dHBzOi8vZ2l0aHViLmNvbS9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob25AcmVmcy9oZWFkcy9kZXZlbG9wIiwiZGlnZXN0Ijp7InNoYTEiOiI0YmY2M2RiOGU0NmEzMDM2NDlhZjZlOTk1NmM4ZTI5NTI1ZjYxNzk4In0sImVudHJ5UG9pbnQiOiIuZ2l0aHViL3dvcmtmbG93cy9wcmUtcmVsZWFzZS55bWwifSwicGFyYW1ldGVycyI6eyJ2YXJzIjp7fX0sImVudmlyb25tZW50Ijp7ImdpdGh1Yl9hY3RvciI6ImxlYW5kcm9kYW1hc2NlbmEiLCJnaXRodWJfYWN0b3JfaWQiOiI0Mjk1MTczIiwiZ2l0aHViX2Jhc2VfcmVmIjoiIiwiZ2l0aHViX2V2ZW50X25hbWUiOiJzY2hlZHVsZSIsImdpdGh1Yl9ldmVudF9wYXlsb2FkIjp7ImVudGVycHJpc2UiOnsiYXZhdGFyX3VybCI6Imh0dHBzOi8vYXZhdGFycy5naXRodWJ1c2VyY29udGVudC5jb20vYi8xMjkwP3Y9NCIsImNyZWF0ZWRfYXQiOiIyMDE5LTExLTEzVDE4OjA1OjQxWiIsImRlc2NyaXB0aW9uIjoiIiwiaHRtbF91cmwiOiJodHRwczovL2dpdGh1Yi5jb20vZW50ZXJwcmlzZXMvYW1hem9uIiwiaWQiOjEyOTAsIm5hbWUiOiJBbWF6b24iLCJub2RlX2lkIjoiTURFd09rVnVkR1Z5Y0hKcGMyVXhNamt3Iiwic2x1ZyI6ImFtYXpvbiIsInVwZGF0ZWRfYXQiOiIyMDI0LTA5LTMwVDIxOjAyOjMwWiIsIndlYnNpdGVfdXJsIjoiaHR0cHM6Ly93d3cuYW1hem9uLmNvbS8ifSwib3JnYW5pemF0aW9uIjp7ImF2YXRhcl91cmwiOiJodHRwczovL2F2YXRhcnMuZ2l0aHVidXNlcmNvbnRlbnQuY29tL3UvMTI5MTI3NjM4P3Y9NCIsImRlc2NyaXB0aW9uIjoiIiwiZXZlbnRzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vb3Jncy9hd3MtcG93ZXJ0b29scy9ldmVudHMiLCJob29rc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL29yZ3MvYXdzLXBvd2VydG9vbHMvaG9va3MiLCJpZCI6MTI5MTI3NjM4LCJpc3N1ZXNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9vcmdzL2F3cy1wb3dlcnRvb2xzL2lzc3VlcyIsImxvZ2luIjoiYXdzLXBvd2VydG9vbHMiLCJtZW1iZXJzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vb3Jncy9hd3MtcG93ZXJ0b29scy9tZW1iZXJzey9tZW1iZXJ9Iiwibm9kZV9pZCI6Ik9fa2dET0I3SlUxZyIsInB1YmxpY19tZW1iZXJzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vb3Jncy9hd3MtcG93ZXJ0b29scy9wdWJsaWNfbWVtYmVyc3svbWVtYmVyfSIsInJlcG9zX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vb3Jncy9hd3MtcG93ZXJ0b29scy9yZXBvcyIsInVybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vb3Jncy9hd3MtcG93ZXJ0b29scyJ9LCJyZXBvc2l0b3J5Ijp7ImFsbG93X2ZvcmtpbmciOnRydWUsImFyY2hpdmVfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24ve2FyY2hpdmVfZm9ybWF0fXsvcmVmfSIsImFyY2hpdmVkIjpmYWxzZSwiYXNzaWduZWVzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2Fzc2lnbmVlc3svdXNlcn0iLCJibG9ic191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9naXQvYmxvYnN7L3NoYX0iLCJicmFuY2hlc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9icmFuY2hlc3svYnJhbmNofSIsImNsb25lX3VybCI6Imh0dHBzOi8vZ2l0aHViLmNvbS9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24uZ2l0IiwiY29sbGFib3JhdG9yc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9jb2xsYWJvcmF0b3Jzey9jb2xsYWJvcmF0b3J9IiwiY29tbWVudHNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vY29tbWVudHN7L251bWJlcn0iLCJjb21taXRzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2NvbW1pdHN7L3NoYX0iLCJjb21wYXJlX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2NvbXBhcmUve2Jhc2V9Li4ue2hlYWR9IiwiY29udGVudHNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vY29udGVudHMveytwYXRofSIsImNvbnRyaWJ1dG9yc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9jb250cmlidXRvcnMiLCJjcmVhdGVkX2F0IjoiMjAxOS0xMS0xNVQxMjoyNjoxMloiLCJjdXN0b21fcHJvcGVydGllcyI6e30sImRlZmF1bHRfYnJhbmNoIjoiZGV2ZWxvcCIsImRlcGxveW1lbnRzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2RlcGxveW1lbnRzIiwiZGVzY3JpcHRpb24iOiJBIGRldmVsb3BlciB0b29sa2l0IHRvIGltcGxlbWVudCBTZXJ2ZXJsZXNzIGJlc3QgcHJhY3RpY2VzIGFuZCBpbmNyZWFzZSBkZXZlbG9wZXIgdmVsb2NpdHkuIiwiZGlzYWJsZWQiOmZhbHNlLCJkb3dubG9hZHNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vZG93bmxvYWRzIiwiZXZlbnRzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2V2ZW50cyIsImZvcmsiOmZhbHNlLCJmb3JrcyI6NDE3LCJmb3Jrc19jb3VudCI6NDE3LCJmb3Jrc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9mb3JrcyIsImZ1bGxfbmFtZSI6ImF3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbiIsImdpdF9jb21taXRzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2dpdC9jb21taXRzey9zaGF9IiwiZ2l0X3JlZnNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vZ2l0L3JlZnN7L3NoYX0iLCJnaXRfdGFnc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9naXQvdGFnc3svc2hhfSIsImdpdF91cmwiOiJnaXQ6Ly9naXRodWIuY29tL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi5naXQiLCJoYXNfZGlzY3Vzc2lvbnMiOnRydWUsImhhc19kb3dubG9hZHMiOnRydWUsImhhc19pc3N1ZXMiOnRydWUsImhhc19wYWdlcyI6ZmFsc2UsImhhc19wcm9qZWN0cyI6dHJ1ZSwiaGFzX3dpa2kiOmZhbHNlLCJob21lcGFnZSI6Imh0dHBzOi8vZG9jcy5wb3dlcnRvb2xzLmF3cy5kZXYvbGFtYmRhL3B5dGhvbi9sYXRlc3QvIiwiaG9va3NfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vaG9va3MiLCJodG1sX3VybCI6Imh0dHBzOi8vZ2l0aHViLmNvbS9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24iLCJpZCI6MjIxOTE5Mzc5LCJpc190ZW1wbGF0ZSI6ZmFsc2UsImlzc3VlX2NvbW1lbnRfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vaXNzdWVzL2NvbW1lbnRzey9udW1iZXJ9IiwiaXNzdWVfZXZlbnRzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2lzc3Vlcy9ldmVudHN7L251bWJlcn0iLCJpc3N1ZXNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vaXNzdWVzey9udW1iZXJ9Iiwia2V5c191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9rZXlzey9rZXlfaWR9IiwibGFiZWxzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2xhYmVsc3svbmFtZX0iLCJsYW5ndWFnZSI6IlB5dGhvbiIsImxhbmd1YWdlc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9sYW5ndWFnZXMiLCJsaWNlbnNlIjp7ImtleSI6Im1pdC0wIiwibmFtZSI6Ik1JVCBObyBBdHRyaWJ1dGlvbiIsIm5vZGVfaWQiOiJNRGM2VEdsalpXNXpaVFF4Iiwic3BkeF9pZCI6Ik1JVC0wIiwidXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9saWNlbnNlcy9taXQtMCJ9LCJtZXJnZXNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vbWVyZ2VzIiwibWlsZXN0b25lc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9taWxlc3RvbmVzey9udW1iZXJ9IiwibWlycm9yX3VybCI6bnVsbCwibmFtZSI6InBvd2VydG9vbHMtbGFtYmRhLXB5dGhvbiIsIm5vZGVfaWQiOiJNREV3T2xKbGNHOXphWFJ2Y25reU1qRTVNVGt6TnprPSIsIm5vdGlmaWNhdGlvbnNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vbm90aWZpY2F0aW9uc3s/c2luY2UsYWxsLHBhcnRpY2lwYXRpbmd9Iiwib3Blbl9pc3N1ZXMiOjUzLCJvcGVuX2lzc3Vlc19jb3VudCI6NTMsIm93bmVyIjp7ImF2YXRhcl91cmwiOiJodHRwczovL2F2YXRhcnMuZ2l0aHVidXNlcmNvbnRlbnQuY29tL3UvMTI5MTI3NjM4P3Y9NCIsImV2ZW50c191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3VzZXJzL2F3cy1wb3dlcnRvb2xzL2V2ZW50c3svcHJpdmFjeX0iLCJmb2xsb3dlcnNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS91c2Vycy9hd3MtcG93ZXJ0b29scy9mb2xsb3dlcnMiLCJmb2xsb3dpbmdfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS91c2Vycy9hd3MtcG93ZXJ0b29scy9mb2xsb3dpbmd7L290aGVyX3VzZXJ9IiwiZ2lzdHNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS91c2Vycy9hd3MtcG93ZXJ0b29scy9naXN0c3svZ2lzdF9pZH0iLCJncmF2YXRhcl9pZCI6IiIsImh0bWxfdXJsIjoiaHR0cHM6Ly9naXRodWIuY29tL2F3cy1wb3dlcnRvb2xzIiwiaWQiOjEyOTEyNzYzOCwibG9naW4iOiJhd3MtcG93ZXJ0b29scyIsIm5vZGVfaWQiOiJPX2tnRE9CN0pVMWciLCJvcmdhbml6YXRpb25zX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vdXNlcnMvYXdzLXBvd2VydG9vbHMvb3JncyIsInJlY2VpdmVkX2V2ZW50c191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3VzZXJzL2F3cy1wb3dlcnRvb2xzL3JlY2VpdmVkX2V2ZW50cyIsInJlcG9zX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vdXNlcnMvYXdzLXBvd2VydG9vbHMvcmVwb3MiLCJzaXRlX2FkbWluIjpmYWxzZSwic3RhcnJlZF91cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3VzZXJzL2F3cy1wb3dlcnRvb2xzL3N0YXJyZWR7L293bmVyfXsvcmVwb30iLCJzdWJzY3JpcHRpb25zX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vdXNlcnMvYXdzLXBvd2VydG9vbHMvc3Vic2NyaXB0aW9ucyIsInR5cGUiOiJPcmdhbml6YXRpb24iLCJ1cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3VzZXJzL2F3cy1wb3dlcnRvb2xzIiwidXNlcl92aWV3X3R5cGUiOiJwdWJsaWMifSwicHJpdmF0ZSI6ZmFsc2UsInB1bGxzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL3B1bGxzey9udW1iZXJ9IiwicHVzaGVkX2F0IjoiMjAyNS0wNC0xM1QxMDowMzo1N1oiLCJyZWxlYXNlc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9yZWxlYXNlc3svaWR9Iiwic2l6ZSI6MTA2NDQwLCJzc2hfdXJsIjoiZ2l0QGdpdGh1Yi5jb206YXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uLmdpdCIsInN0YXJnYXplcnNfY291bnQiOjMwMjIsInN0YXJnYXplcnNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vc3RhcmdhemVycyIsInN0YXR1c2VzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL3N0YXR1c2VzL3tzaGF9Iiwic3Vic2NyaWJlcnNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vc3Vic2NyaWJlcnMiLCJzdWJzY3JpcHRpb25fdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vc3Vic2NyaXB0aW9uIiwic3ZuX3VybCI6Imh0dHBzOi8vZ2l0aHViLmNvbS9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24iLCJ0YWdzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL3RhZ3MiLCJ0ZWFtc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi90ZWFtcyIsInRvcGljcyI6WyJhd3MiLCJhd3MtbGFtYmRhIiwiaGFja3RvYmVyZmVzdCIsImxhbWJkYSIsInB5dGhvbiIsInNlcnZlcmxlc3MiXSwidHJlZXNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vZ2l0L3RyZWVzey9zaGF9IiwidXBkYXRlZF9hdCI6IjIwMjUtMDQtMTNUMDg6MjA6MDZaIiwidXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24iLCJ2aXNpYmlsaXR5IjoicHVibGljIiwid2F0Y2hlcnMiOjMwMjIsIndhdGNoZXJzX2NvdW50IjozMDIyLCJ3ZWJfY29tbWl0X3NpZ25vZmZfcmVxdWlyZWQiOnRydWV9LCJzY2hlZHVsZSI6IjAgOCAqICogMS01Iiwid29ya2Zsb3ciOiIuZ2l0aHViL3dvcmtmbG93cy9wcmUtcmVsZWFzZS55bWwifSwiZ2l0aHViX2hlYWRfcmVmIjoiIiwiZ2l0aHViX3JlZiI6InJlZnMvaGVhZHMvZGV2ZWxvcCIsImdpdGh1Yl9yZWZfdHlwZSI6ImJyYW5jaCIsImdpdGh1Yl9yZXBvc2l0b3J5X2lkIjoiMjIxOTE5Mzc5IiwiZ2l0aHViX3JlcG9zaXRvcnlfb3duZXIiOiJhd3MtcG93ZXJ0b29scyIsImdpdGh1Yl9yZXBvc2l0b3J5X293bmVyX2lkIjoiMTI5MTI3NjM4IiwiZ2l0aHViX3J1bl9hdHRlbXB0IjoiMSIsImdpdGh1Yl9ydW5faWQiOiIxNDQ0MDUwOTM4NyIsImdpdGh1Yl9ydW5fbnVtYmVyIjoiMjE4IiwiZ2l0aHViX3NoYTEiOiI0YmY2M2RiOGU0NmEzMDM2NDlhZjZlOTk1NmM4ZTI5NTI1ZjYxNzk4In19LCJtZXRhZGF0YSI6eyJidWlsZEludm9jYXRpb25JRCI6IjE0NDQwNTA5Mzg3LTEiLCJjb21wbGV0ZW5lc3MiOnsicGFyYW1ldGVycyI6dHJ1ZSwiZW52aXJvbm1lbnQiOmZhbHNlLCJtYXRlcmlhbHMiOmZhbHNlfSwicmVwcm9kdWNpYmxlIjpmYWxzZX0sIm1hdGVyaWFscyI6W3sidXJpIjoiZ2l0K2h0dHBzOi8vZ2l0aHViLmNvbS9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob25AcmVmcy9oZWFkcy9kZXZlbG9wIiwiZGlnZXN0Ijp7InNoYTEiOiI0YmY2M2RiOGU0NmEzMDM2NDlhZjZlOTk1NmM4ZTI5NTI1ZjYxNzk4In19XX19", "payloadType":"application/vnd.in-toto+json", "signatures":[{"sig":"MEUCIQD4GCjM+td+A0OkZnR/ZeyISxWhKfHKodmH/V0WFr8uMwIgcPyyv5xpyG7g2qfKJPDBEzQXd/eexAEGXbSkTs7d5cU="}]}}
\ No newline at end of file
diff --git a/provenance/3.10.1a4/multiple.intoto.jsonl b/provenance/3.10.1a4/multiple.intoto.jsonl
new file mode 100644
index 00000000000..fbddb0f57f7
--- /dev/null
+++ b/provenance/3.10.1a4/multiple.intoto.jsonl
@@ -0,0 +1 @@
+{"mediaType":"application/vnd.dev.sigstore.bundle.v0.3+json", "verificationMaterial":{"certificate":{"rawBytes":"MIIHaDCCBu2gAwIBAgIUe6qqYPwEfjm8MTlB2FZYiqsmi08wCgYIKoZIzj0EAwMwNzEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MR4wHAYDVQQDExVzaWdzdG9yZS1pbnRlcm1lZGlhdGUwHhcNMjUwNDE1MDgwNzU5WhcNMjUwNDE1MDgxNzU5WjAAMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEJ2OrfemKsujoiTOkfZDHb6lmPTHYanU7OVZB95nLvHgVKcBOZY8WLYsncEJ/URhKrg82isvDY3JcvDijgMLGT6OCBgwwggYIMA4GA1UdDwEB/wQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcDAzAdBgNVHQ4EFgQUUlwvuGuMMi9YYVIczDdDBeafnV8wHwYDVR0jBBgwFoAU39Ppz1YkEZb5qNjpKFWixi4YZD8wgYQGA1UdEQEB/wR6MHiGdmh0dHBzOi8vZ2l0aHViLmNvbS9zbHNhLWZyYW1ld29yay9zbHNhLWdpdGh1Yi1nZW5lcmF0b3IvLmdpdGh1Yi93b3JrZmxvd3MvZ2VuZXJhdG9yX2dlbmVyaWNfc2xzYTMueW1sQHJlZnMvdGFncy92Mi4xLjAwOQYKKwYBBAGDvzABAQQraHR0cHM6Ly90b2tlbi5hY3Rpb25zLmdpdGh1YnVzZXJjb250ZW50LmNvbTAWBgorBgEEAYO/MAECBAhzY2hlZHVsZTA2BgorBgEEAYO/MAEDBChlNDU0MGExOGQxMmYwYmVhNzhlZjE1YzVjMGRkN2YyNDJiMTBmZjE5MBkGCisGAQQBg78wAQQEC1ByZS1SZWxlYXNlMDUGCisGAQQBg78wAQUEJ2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbjAgBgorBgEEAYO/MAEGBBJyZWZzL2hlYWRzL2RldmVsb3AwOwYKKwYBBAGDvzABCAQtDCtodHRwczovL3Rva2VuLmFjdGlvbnMuZ2l0aHVidXNlcmNvbnRlbnQuY29tMIGGBgorBgEEAYO/MAEJBHgMdmh0dHBzOi8vZ2l0aHViLmNvbS9zbHNhLWZyYW1ld29yay9zbHNhLWdpdGh1Yi1nZW5lcmF0b3IvLmdpdGh1Yi93b3JrZmxvd3MvZ2VuZXJhdG9yX2dlbmVyaWNfc2xzYTMueW1sQHJlZnMvdGFncy92Mi4xLjAwOAYKKwYBBAGDvzABCgQqDChmN2RkOGM1NGMyMDY3YmFmYzEyY2E3YTU1NTk1ZDVlZTliNzUyMDRhMB0GCisGAQQBg78wAQsEDwwNZ2l0aHViLWhvc3RlZDBKBgorBgEEAYO/MAEMBDwMOmh0dHBzOi8vZ2l0aHViLmNvbS9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24wOAYKKwYBBAGDvzABDQQqDChlNDU0MGExOGQxMmYwYmVhNzhlZjE1YzVjMGRkN2YyNDJiMTBmZjE5MCIGCisGAQQBg78wAQ4EFAwScmVmcy9oZWFkcy9kZXZlbG9wMBkGCisGAQQBg78wAQ8ECwwJMjIxOTE5Mzc5MDEGCisGAQQBg78wARAEIwwhaHR0cHM6Ly9naXRodWIuY29tL2F3cy1wb3dlcnRvb2xzMBkGCisGAQQBg78wAREECwwJMTI5MTI3NjM4MH8GCisGAQQBg78wARIEcQxvaHR0cHM6Ly9naXRodWIuY29tL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi8uZ2l0aHViL3dvcmtmbG93cy9wcmUtcmVsZWFzZS55bWxAcmVmcy9oZWFkcy9kZXZlbG9wMDgGCisGAQQBg78wARMEKgwoZTQ1NDBhMThkMTJmMGJlYTc4ZWYxNWM1YzBkZDdmMjQyYjEwZmYxOTAYBgorBgEEAYO/MAEUBAoMCHNjaGVkdWxlMG4GCisGAQQBg78wARUEYAxeaHR0cHM6Ly9naXRodWIuY29tL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9hY3Rpb25zL3J1bnMvMTQ0NjQzNDgxMjUvYXR0ZW1wdHMvMTAWBgorBgEEAYO/MAEWBAgMBnB1YmxpYzCBiwYKKwYBBAHWeQIEAgR9BHsAeQB3AN09MGrGxxEyYxkeHJlnNwKiSl643jyt/4eKcoAvKe6OAAABljh9oMMAAAQDAEgwRgIhALtlZvmTtD5usRLa8jO1NioYMxK3kSGJbM6BX34gsF1nAiEAqhzvyPiMPMtWKqavJoce/6zmuBOn6790/vt7yrm9NJgwCgYIKoZIzj0EAwMDaQAwZgIxAP7SzMhm7TQgm+cYS4wpw/G2X+X7U1hRrFKFCUJh7el9H0iBaPOkohM6t1da768VuwIxAIbinUWzzADmjjPJRqRgkyjCzph9wjIGGZfC5gutHNNS2s0s7FaIfcmy2Ho50qx18w=="}, "tlogEntries":[{"logIndex":"197208587", "logId":{"keyId":"wNI9atQGlz+VWfO6LRygH4QUfY/8W4RFwiT5i5WRgB0="}, "kindVersion":{"kind":"dsse", "version":"0.0.1"}, "integratedTime":"1744704479", "inclusionPromise":{"signedEntryTimestamp":"MEYCIQCVUYBFjwnJ18eWLIESSC5Y+gpFNCxhVcAWpYfqTOp/8wIhAPlx8Ei97/0IEPqoMV62dfy3ygzjjm4HVq+cAjB/YRmX"}, "inclusionProof":{"logIndex":"75304325", "rootHash":"0uAM+ruqmDV+LiMKTeF4qded8FwhLtQwtI/Gos5po7Y=", "treeSize":"75304326", "hashes":["vzh8OF22/H50pnNItK0l3HcQilh4xZBuc7arMWdWjkw=", "u0OP8/QAP1pdAgqtpGPLuyYXx7pD7Syo8r90aCOzuh0=", "LUj0vV5taJv3/DYegcYLOE1h3Jba6/0WUmMLwVMzXKU=", "qeSF6we5TRI3a5tjTgo2OZYkG9ZigmB633bN4Dlp/5c=", "4AIB7ZEMe1RH9vPn+RE9jQB63ZP2i7hA9p50TflZhdc=", "qKRfxk68GybBX0MtEDZuXNJsL2bBbIjyDDOmY9K+Q3U=", "1bSMmPVVpdfWzk5xyK9swvIriZ3yjyk8QadeglT563o=", "g6f8DRMTXS647qpdbDr7Du6WQ4RIFqEO3PhQYXuUpBQ=", "3waNC+2E4FxaX4hb45HEaqGgOlG0Q8MlL4DA1czf+Io=", "p8GZJf0dbUN4F0OOaYZUcmb94boPBS/aWWtldW3eAhU=", "bUMWi9afi8M+WrpEiXczKOIZWruoe38aV/lXN5Z5o9E=", "WEm5OgPzJpYROv+4CcrieexCYyQKrLUH3hbxmcQQ+DM=", "7v8qPHNDLerpduaMx06eb/MwgoQwczTn/cYGKX/9wZ4="], "checkpoint":{"envelope":"rekor.sigstore.dev - 1193050959916656506\n75304326\n0uAM+ruqmDV+LiMKTeF4qded8FwhLtQwtI/Gos5po7Y=\n\n— rekor.sigstore.dev wNI9ajBFAiEA6ngQgRdJplZ3tKHf2EQjPru77Q8hnVn3s3IkfsGg0LcCIEUkFGoHRPX/oCOR7khc9EZ34qR+fPgl0LNW+wbWqbu2\n"}}, "canonicalizedBody":"eyJhcGlWZXJzaW9uIjoiMC4wLjEiLCJraW5kIjoiZHNzZSIsInNwZWMiOnsiZW52ZWxvcGVIYXNoIjp7ImFsZ29yaXRobSI6InNoYTI1NiIsInZhbHVlIjoiZDYxNTcwOTcyYWE4YTA3NTA1NTViMjJmYzFjYzNkMDBhODBlZGU1ZjBmNmI4OWQxZjg5NmY0MjFjOWU1ZWRlNiJ9LCJwYXlsb2FkSGFzaCI6eyJhbGdvcml0aG0iOiJzaGEyNTYiLCJ2YWx1ZSI6ImEyZjRiMGUwYTI0Yzc1YjdlMzM2NjVhZWMwNjY1MTI3NzUxZmZiNTgwMTg1MGRiN2RhNzdlMWUwZjhmYzhkN2MifSwic2lnbmF0dXJlcyI6W3sic2lnbmF0dXJlIjoiTUVZQ0lRREVOOXBWem5TQTdjOVA5UytaMWRaVk9iQjdrSUVuZzdnRktZZWVRZks1SXdJaEFML2dlUWR3dkJRbUxCSjFybU5mRmtOeVBHcFJSeHJFRTZoUi9leUR5cDVmIiwidmVyaWZpZXIiOiJMUzB0TFMxQ1JVZEpUaUJEUlZKVVNVWkpRMEZVUlMwdExTMHRDazFKU1VoaFJFTkRRblV5WjBGM1NVSkJaMGxWWlRaeGNWbFFkMFZtYW0wNFRWUnNRakpHV2xscGNYTnRhVEE0ZDBObldVbExiMXBKZW1vd1JVRjNUWGNLVG5wRlZrMUNUVWRCTVZWRlEyaE5UV015Ykc1ak0xSjJZMjFWZFZwSFZqSk5ValIzU0VGWlJGWlJVVVJGZUZaNllWZGtlbVJIT1hsYVV6RndZbTVTYkFwamJURnNXa2RzYUdSSFZYZElhR05PVFdwVmQwNUVSVEZOUkdkM1RucFZOVmRvWTA1TmFsVjNUa1JGTVUxRVozaE9lbFUxVjJwQlFVMUdhM2RGZDFsSUNrdHZXa2w2YWpCRFFWRlpTVXR2V2tsNmFqQkVRVkZqUkZGblFVVktNazl5Wm1WdFMzTjFhbTlwVkU5clpscEVTR0kyYkcxUVZFaFpZVzVWTjA5V1drSUtPVFZ1VEhaSVoxWkxZMEpQV2xrNFYweFpjMjVqUlVvdlZWSm9TM0puT0RKcGMzWkVXVE5LWTNaRWFXcG5UVXhIVkRaUFEwSm5kM2RuWjFsSlRVRTBSd3BCTVZWa1JIZEZRaTkzVVVWQmQwbElaMFJCVkVKblRsWklVMVZGUkVSQlMwSm5aM0pDWjBWR1FsRmpSRUY2UVdSQ1owNVdTRkUwUlVablVWVlZiSGQyQ25WSGRVMU5hVGxaV1ZaSlkzcEVaRVJDWldGbWJsWTRkMGgzV1VSV1VqQnFRa0puZDBadlFWVXpPVkJ3ZWpGWmEwVmFZalZ4VG1wd1MwWlhhWGhwTkZrS1drUTRkMmRaVVVkQk1WVmtSVkZGUWk5M1VqWk5TR2xIWkcxb01HUklRbnBQYVRoMldqSnNNR0ZJVm1sTWJVNTJZbE01ZW1KSVRtaE1WMXA1V1ZjeGJBcGtNamw1WVhrNWVtSklUbWhNVjJSd1pFZG9NVmxwTVc1YVZ6VnNZMjFHTUdJelNYWk1iV1J3WkVkb01WbHBPVE5pTTBweVdtMTRkbVF6VFhaYU1sWjFDbHBZU21oa1J6bDVXREprYkdKdFZubGhWMDVtWXpKNGVsbFVUWFZsVnpGelVVaEtiRnB1VFhaa1IwWnVZM2s1TWsxcE5IaE1ha0YzVDFGWlMwdDNXVUlLUWtGSFJIWjZRVUpCVVZGeVlVaFNNR05JVFRaTWVUa3dZakowYkdKcE5XaFpNMUp3WWpJMWVreHRaSEJrUjJneFdXNVdlbHBZU21waU1qVXdXbGMxTUFwTWJVNTJZbFJCVjBKbmIzSkNaMFZGUVZsUEwwMUJSVU5DUVdoNldUSm9iRnBJVm5OYVZFRXlRbWR2Y2tKblJVVkJXVTh2VFVGRlJFSkRhR3hPUkZVd0NrMUhSWGhQUjFGNFRXMVpkMWx0Vm1oT2VtaHNXbXBGTVZsNlZtcE5SMUpyVGpKWmVVNUVTbWxOVkVKdFdtcEZOVTFDYTBkRGFYTkhRVkZSUW1jM09IY0tRVkZSUlVNeFFubGFVekZUV2xkNGJGbFlUbXhOUkZWSFEybHpSMEZSVVVKbk56aDNRVkZWUlVveVJqTmplVEYzWWpOa2JHTnVVblppTW5oNlRETkNkZ3BrTWxaNVpFYzVkbUpJVFhSaVIwWjBXVzFTYUV4WVFqVmtSMmgyWW1wQlowSm5iM0pDWjBWRlFWbFBMMDFCUlVkQ1FrcDVXbGRhZWt3eWFHeFpWMUo2Q2t3eVVteGtiVlp6WWpOQmQwOTNXVXRMZDFsQ1FrRkhSSFo2UVVKRFFWRjBSRU4wYjJSSVVuZGplbTkyVEROU2RtRXlWblZNYlVacVpFZHNkbUp1VFhVS1dqSnNNR0ZJVm1sa1dFNXNZMjFPZG1KdVVteGlibEYxV1RJNWRFMUpSMGRDWjI5eVFtZEZSVUZaVHk5TlFVVktRa2huVFdSdGFEQmtTRUo2VDJrNGRncGFNbXd3WVVoV2FVeHRUblppVXpsNllraE9hRXhYV25sWlZ6RnNaREk1ZVdGNU9YcGlTRTVvVEZka2NHUkhhREZaYVRGdVdsYzFiR050UmpCaU0wbDJDa3h0WkhCa1IyZ3hXV2s1TTJJelNuSmFiWGgyWkROTmRsb3lWblZhV0Vwb1pFYzVlVmd5Wkd4aWJWWjVZVmRPWm1NeWVIcFpWRTExWlZjeGMxRklTbXdLV201TmRtUkhSbTVqZVRreVRXazBlRXhxUVhkUFFWbExTM2RaUWtKQlIwUjJla0ZDUTJkUmNVUkRhRzFPTWxKclQwZE5NVTVIVFhsTlJGa3pXVzFHYlFwWmVrVjVXVEpGTTFsVVZURk9WR3N4V2tSV2JGcFViR2xPZWxWNVRVUlNhRTFDTUVkRGFYTkhRVkZSUW1jM09IZEJVWE5GUkhkM1Rsb3liREJoU0ZacENreFhhSFpqTTFKc1drUkNTMEpuYjNKQ1owVkZRVmxQTDAxQlJVMUNSSGROVDIxb01HUklRbnBQYVRoMldqSnNNR0ZJVm1sTWJVNTJZbE01YUdRelRYUUtZMGM1TTFwWVNqQmlNamx6WTNrNWQySXpaR3hqYmxKMllqSjRla3hYZUdoaVYwcHJXVk14ZDJWWVVtOWlNalIzVDBGWlMwdDNXVUpDUVVkRWRucEJRZ3BFVVZGeFJFTm9iRTVFVlRCTlIwVjRUMGRSZUUxdFdYZFpiVlpvVG5wb2JGcHFSVEZaZWxacVRVZFNhMDR5V1hsT1JFcHBUVlJDYlZwcVJUVk5RMGxIQ2tOcGMwZEJVVkZDWnpjNGQwRlJORVZHUVhkVFkyMVdiV041T1c5YVYwWnJZM2s1YTFwWVdteGlSemwzVFVKclIwTnBjMGRCVVZGQ1p6YzRkMEZST0VVS1EzZDNTazFxU1hoUFZFVTFUWHBqTlUxRVJVZERhWE5IUVZGUlFtYzNPSGRCVWtGRlNYZDNhR0ZJVWpCalNFMDJUSGs1Ym1GWVVtOWtWMGwxV1RJNWRBcE1Na1l6WTNreGQySXpaR3hqYmxKMllqSjRlazFDYTBkRGFYTkhRVkZSUW1jM09IZEJVa1ZGUTNkM1NrMVVTVFZOVkVrelRtcE5ORTFJT0VkRGFYTkhDa0ZSVVVKbk56aDNRVkpKUldOUmVIWmhTRkl3WTBoTk5reDVPVzVoV0ZKdlpGZEpkVmt5T1hSTU1rWXpZM2t4ZDJJelpHeGpibEoyWWpKNGVrd3pRbllLWkRKV2VXUkhPWFppU0UxMFlrZEdkRmx0VW1oTVdFSTFaRWRvZG1KcE9IVmFNbXd3WVVoV2FVd3paSFpqYlhSdFlrYzVNMk41T1hkamJWVjBZMjFXY3dwYVYwWjZXbE0xTldKWGVFRmpiVlp0WTNrNWIxcFhSbXRqZVRscldsaGFiR0pIT1hkTlJHZEhRMmx6UjBGUlVVSm5OemgzUVZKTlJVdG5kMjlhVkZFeENrNUVRbWhOVkdoclRWUktiVTFIU214WlZHTTBXbGRaZUU1WFRURlpla0pyV2tSa2JVMXFVWGxaYWtWM1dtMVplRTlVUVZsQ1oyOXlRbWRGUlVGWlR5OEtUVUZGVlVKQmIwMURTRTVxWVVkV2EyUlhlR3hOUnpSSFEybHpSMEZSVVVKbk56aDNRVkpWUlZsQmVHVmhTRkl3WTBoTk5reDVPVzVoV0ZKdlpGZEpkUXBaTWpsMFRESkdNMk41TVhkaU0yUnNZMjVTZG1JeWVIcE1NMEoyWkRKV2VXUkhPWFppU0UxMFlrZEdkRmx0VW1oTVdFSTFaRWRvZG1KcE9XaFpNMUp3Q21JeU5YcE1NMG94WW01TmRrMVVVVEJPYWxGNlRrUm5lRTFxVlhaWldGSXdXbGN4ZDJSSVRYWk5WRUZYUW1kdmNrSm5SVVZCV1U4dlRVRkZWMEpCWjAwS1FtNUNNVmx0ZUhCWmVrTkNhWGRaUzB0M1dVSkNRVWhYWlZGSlJVRm5VamxDU0hOQlpWRkNNMEZPTURsTlIzSkhlSGhGZVZsNGEyVklTbXh1VG5kTGFRcFRiRFkwTTJwNWRDODBaVXRqYjBGMlMyVTJUMEZCUVVKc2FtZzViMDFOUVVGQlVVUkJSV2QzVW1kSmFFRk1kR3hhZG0xVWRFUTFkWE5TVEdFNGFrOHhDazVwYjFsTmVFc3phMU5IU21KTk5rSllNelJuYzBZeGJrRnBSVUZ4YUhwMmVWQnBUVkJOZEZkTGNXRjJTbTlqWlM4MmVtMTFRazl1TmpjNU1DOTJkRGNLZVhKdE9VNUtaM2REWjFsSlMyOWFTWHBxTUVWQmQwMUVZVkZCZDFwblNYaEJVRGRUZWsxb2JUZFVVV2R0SzJOWlV6UjNjSGN2UnpKWUsxZzNWVEZvVWdweVJrdEdRMVZLYURkbGJEbElNR2xDWVZCUGEyOW9UVFowTVdSaE56WTRWblYzU1hoQlNXSnBibFZYZW5wQlJHMXFhbEJLVW5GU1oydDVha042Y0dnNUNuZHFTVWRIV21aRE5XZDFkRWhPVGxNeWN6QnpOMFpoU1daamJYa3lTRzgxTUhGNE1UaDNQVDBLTFMwdExTMUZUa1FnUTBWU1ZFbEdTVU5CVkVVdExTMHRMUW89In1dfX0="}]}, "dsseEnvelope":{"payload":"eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjAuMSIsInByZWRpY2F0ZVR5cGUiOiJodHRwczovL3Nsc2EuZGV2L3Byb3ZlbmFuY2UvdjAuMiIsInN1YmplY3QiOlt7Im5hbWUiOiIuL2F3c19sYW1iZGFfcG93ZXJ0b29scy0zLjEwLjFhNC1weTMtbm9uZS1hbnkud2hsIiwiZGlnZXN0Ijp7InNoYTI1NiI6IjRhNzk5M2VjNTk1YTYxMjgzNGMwYWQxYjg5NzEyMmQ5ZDA0NjczMGE1OTZiN2QwZGI4ZDY0MjMxMWYxOWY0ZTcifX0seyJuYW1lIjoiLi9hd3NfbGFtYmRhX3Bvd2VydG9vbHMtMy4xMC4xYTQudGFyLmd6IiwiZGlnZXN0Ijp7InNoYTI1NiI6IjEyY2ZiZmY0MGY5NTdmYTZmNmZiZDY0NWQyNzA2OGNlZWFkNWYyMTllMTA3OWY4YWJkMjhkMGFhM2Q1OWRmODQifX1dLCJwcmVkaWNhdGUiOnsiYnVpbGRlciI6eyJpZCI6Imh0dHBzOi8vZ2l0aHViLmNvbS9zbHNhLWZyYW1ld29yay9zbHNhLWdpdGh1Yi1nZW5lcmF0b3IvLmdpdGh1Yi93b3JrZmxvd3MvZ2VuZXJhdG9yX2dlbmVyaWNfc2xzYTMueW1sQHJlZnMvdGFncy92Mi4xLjAifSwiYnVpbGRUeXBlIjoiaHR0cHM6Ly9naXRodWIuY29tL3Nsc2EtZnJhbWV3b3JrL3Nsc2EtZ2l0aHViLWdlbmVyYXRvci9nZW5lcmljQHYxIiwiaW52b2NhdGlvbiI6eyJjb25maWdTb3VyY2UiOnsidXJpIjoiZ2l0K2h0dHBzOi8vZ2l0aHViLmNvbS9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob25AcmVmcy9oZWFkcy9kZXZlbG9wIiwiZGlnZXN0Ijp7InNoYTEiOiJlNDU0MGExOGQxMmYwYmVhNzhlZjE1YzVjMGRkN2YyNDJiMTBmZjE5In0sImVudHJ5UG9pbnQiOiIuZ2l0aHViL3dvcmtmbG93cy9wcmUtcmVsZWFzZS55bWwifSwicGFyYW1ldGVycyI6eyJ2YXJzIjp7fX0sImVudmlyb25tZW50Ijp7ImdpdGh1Yl9hY3RvciI6ImxlYW5kcm9kYW1hc2NlbmEiLCJnaXRodWJfYWN0b3JfaWQiOiI0Mjk1MTczIiwiZ2l0aHViX2Jhc2VfcmVmIjoiIiwiZ2l0aHViX2V2ZW50X25hbWUiOiJzY2hlZHVsZSIsImdpdGh1Yl9ldmVudF9wYXlsb2FkIjp7ImVudGVycHJpc2UiOnsiYXZhdGFyX3VybCI6Imh0dHBzOi8vYXZhdGFycy5naXRodWJ1c2VyY29udGVudC5jb20vYi8xMjkwP3Y9NCIsImNyZWF0ZWRfYXQiOiIyMDE5LTExLTEzVDE4OjA1OjQxWiIsImRlc2NyaXB0aW9uIjoiIiwiaHRtbF91cmwiOiJodHRwczovL2dpdGh1Yi5jb20vZW50ZXJwcmlzZXMvYW1hem9uIiwiaWQiOjEyOTAsIm5hbWUiOiJBbWF6b24iLCJub2RlX2lkIjoiTURFd09rVnVkR1Z5Y0hKcGMyVXhNamt3Iiwic2x1ZyI6ImFtYXpvbiIsInVwZGF0ZWRfYXQiOiIyMDI0LTA5LTMwVDIxOjAyOjMwWiIsIndlYnNpdGVfdXJsIjoiaHR0cHM6Ly93d3cuYW1hem9uLmNvbS8ifSwib3JnYW5pemF0aW9uIjp7ImF2YXRhcl91cmwiOiJodHRwczovL2F2YXRhcnMuZ2l0aHVidXNlcmNvbnRlbnQuY29tL3UvMTI5MTI3NjM4P3Y9NCIsImRlc2NyaXB0aW9uIjoiIiwiZXZlbnRzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vb3Jncy9hd3MtcG93ZXJ0b29scy9ldmVudHMiLCJob29rc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL29yZ3MvYXdzLXBvd2VydG9vbHMvaG9va3MiLCJpZCI6MTI5MTI3NjM4LCJpc3N1ZXNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9vcmdzL2F3cy1wb3dlcnRvb2xzL2lzc3VlcyIsImxvZ2luIjoiYXdzLXBvd2VydG9vbHMiLCJtZW1iZXJzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vb3Jncy9hd3MtcG93ZXJ0b29scy9tZW1iZXJzey9tZW1iZXJ9Iiwibm9kZV9pZCI6Ik9fa2dET0I3SlUxZyIsInB1YmxpY19tZW1iZXJzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vb3Jncy9hd3MtcG93ZXJ0b29scy9wdWJsaWNfbWVtYmVyc3svbWVtYmVyfSIsInJlcG9zX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vb3Jncy9hd3MtcG93ZXJ0b29scy9yZXBvcyIsInVybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vb3Jncy9hd3MtcG93ZXJ0b29scyJ9LCJyZXBvc2l0b3J5Ijp7ImFsbG93X2ZvcmtpbmciOnRydWUsImFyY2hpdmVfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24ve2FyY2hpdmVfZm9ybWF0fXsvcmVmfSIsImFyY2hpdmVkIjpmYWxzZSwiYXNzaWduZWVzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2Fzc2lnbmVlc3svdXNlcn0iLCJibG9ic191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9naXQvYmxvYnN7L3NoYX0iLCJicmFuY2hlc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9icmFuY2hlc3svYnJhbmNofSIsImNsb25lX3VybCI6Imh0dHBzOi8vZ2l0aHViLmNvbS9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24uZ2l0IiwiY29sbGFib3JhdG9yc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9jb2xsYWJvcmF0b3Jzey9jb2xsYWJvcmF0b3J9IiwiY29tbWVudHNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vY29tbWVudHN7L251bWJlcn0iLCJjb21taXRzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2NvbW1pdHN7L3NoYX0iLCJjb21wYXJlX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2NvbXBhcmUve2Jhc2V9Li4ue2hlYWR9IiwiY29udGVudHNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vY29udGVudHMveytwYXRofSIsImNvbnRyaWJ1dG9yc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9jb250cmlidXRvcnMiLCJjcmVhdGVkX2F0IjoiMjAxOS0xMS0xNVQxMjoyNjoxMloiLCJjdXN0b21fcHJvcGVydGllcyI6e30sImRlZmF1bHRfYnJhbmNoIjoiZGV2ZWxvcCIsImRlcGxveW1lbnRzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2RlcGxveW1lbnRzIiwiZGVzY3JpcHRpb24iOiJBIGRldmVsb3BlciB0b29sa2l0IHRvIGltcGxlbWVudCBTZXJ2ZXJsZXNzIGJlc3QgcHJhY3RpY2VzIGFuZCBpbmNyZWFzZSBkZXZlbG9wZXIgdmVsb2NpdHkuIiwiZGlzYWJsZWQiOmZhbHNlLCJkb3dubG9hZHNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vZG93bmxvYWRzIiwiZXZlbnRzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2V2ZW50cyIsImZvcmsiOmZhbHNlLCJmb3JrcyI6NDE3LCJmb3Jrc19jb3VudCI6NDE3LCJmb3Jrc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9mb3JrcyIsImZ1bGxfbmFtZSI6ImF3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbiIsImdpdF9jb21taXRzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2dpdC9jb21taXRzey9zaGF9IiwiZ2l0X3JlZnNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vZ2l0L3JlZnN7L3NoYX0iLCJnaXRfdGFnc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9naXQvdGFnc3svc2hhfSIsImdpdF91cmwiOiJnaXQ6Ly9naXRodWIuY29tL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi5naXQiLCJoYXNfZGlzY3Vzc2lvbnMiOnRydWUsImhhc19kb3dubG9hZHMiOnRydWUsImhhc19pc3N1ZXMiOnRydWUsImhhc19wYWdlcyI6ZmFsc2UsImhhc19wcm9qZWN0cyI6dHJ1ZSwiaGFzX3dpa2kiOmZhbHNlLCJob21lcGFnZSI6Imh0dHBzOi8vZG9jcy5wb3dlcnRvb2xzLmF3cy5kZXYvbGFtYmRhL3B5dGhvbi9sYXRlc3QvIiwiaG9va3NfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vaG9va3MiLCJodG1sX3VybCI6Imh0dHBzOi8vZ2l0aHViLmNvbS9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24iLCJpZCI6MjIxOTE5Mzc5LCJpc190ZW1wbGF0ZSI6ZmFsc2UsImlzc3VlX2NvbW1lbnRfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vaXNzdWVzL2NvbW1lbnRzey9udW1iZXJ9IiwiaXNzdWVfZXZlbnRzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2lzc3Vlcy9ldmVudHN7L251bWJlcn0iLCJpc3N1ZXNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vaXNzdWVzey9udW1iZXJ9Iiwia2V5c191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9rZXlzey9rZXlfaWR9IiwibGFiZWxzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2xhYmVsc3svbmFtZX0iLCJsYW5ndWFnZSI6IlB5dGhvbiIsImxhbmd1YWdlc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9sYW5ndWFnZXMiLCJsaWNlbnNlIjp7ImtleSI6Im1pdC0wIiwibmFtZSI6Ik1JVCBObyBBdHRyaWJ1dGlvbiIsIm5vZGVfaWQiOiJNRGM2VEdsalpXNXpaVFF4Iiwic3BkeF9pZCI6Ik1JVC0wIiwidXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9saWNlbnNlcy9taXQtMCJ9LCJtZXJnZXNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vbWVyZ2VzIiwibWlsZXN0b25lc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9taWxlc3RvbmVzey9udW1iZXJ9IiwibWlycm9yX3VybCI6bnVsbCwibmFtZSI6InBvd2VydG9vbHMtbGFtYmRhLXB5dGhvbiIsIm5vZGVfaWQiOiJNREV3T2xKbGNHOXphWFJ2Y25reU1qRTVNVGt6TnprPSIsIm5vdGlmaWNhdGlvbnNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vbm90aWZpY2F0aW9uc3s/c2luY2UsYWxsLHBhcnRpY2lwYXRpbmd9Iiwib3Blbl9pc3N1ZXMiOjUxLCJvcGVuX2lzc3Vlc19jb3VudCI6NTEsIm93bmVyIjp7ImF2YXRhcl91cmwiOiJodHRwczovL2F2YXRhcnMuZ2l0aHVidXNlcmNvbnRlbnQuY29tL3UvMTI5MTI3NjM4P3Y9NCIsImV2ZW50c191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3VzZXJzL2F3cy1wb3dlcnRvb2xzL2V2ZW50c3svcHJpdmFjeX0iLCJmb2xsb3dlcnNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS91c2Vycy9hd3MtcG93ZXJ0b29scy9mb2xsb3dlcnMiLCJmb2xsb3dpbmdfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS91c2Vycy9hd3MtcG93ZXJ0b29scy9mb2xsb3dpbmd7L290aGVyX3VzZXJ9IiwiZ2lzdHNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS91c2Vycy9hd3MtcG93ZXJ0b29scy9naXN0c3svZ2lzdF9pZH0iLCJncmF2YXRhcl9pZCI6IiIsImh0bWxfdXJsIjoiaHR0cHM6Ly9naXRodWIuY29tL2F3cy1wb3dlcnRvb2xzIiwiaWQiOjEyOTEyNzYzOCwibG9naW4iOiJhd3MtcG93ZXJ0b29scyIsIm5vZGVfaWQiOiJPX2tnRE9CN0pVMWciLCJvcmdhbml6YXRpb25zX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vdXNlcnMvYXdzLXBvd2VydG9vbHMvb3JncyIsInJlY2VpdmVkX2V2ZW50c191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3VzZXJzL2F3cy1wb3dlcnRvb2xzL3JlY2VpdmVkX2V2ZW50cyIsInJlcG9zX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vdXNlcnMvYXdzLXBvd2VydG9vbHMvcmVwb3MiLCJzaXRlX2FkbWluIjpmYWxzZSwic3RhcnJlZF91cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3VzZXJzL2F3cy1wb3dlcnRvb2xzL3N0YXJyZWR7L293bmVyfXsvcmVwb30iLCJzdWJzY3JpcHRpb25zX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vdXNlcnMvYXdzLXBvd2VydG9vbHMvc3Vic2NyaXB0aW9ucyIsInR5cGUiOiJPcmdhbml6YXRpb24iLCJ1cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3VzZXJzL2F3cy1wb3dlcnRvb2xzIiwidXNlcl92aWV3X3R5cGUiOiJwdWJsaWMifSwicHJpdmF0ZSI6ZmFsc2UsInB1bGxzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL3B1bGxzey9udW1iZXJ9IiwicHVzaGVkX2F0IjoiMjAyNS0wNC0xNVQwNzo0Mjo0NFoiLCJyZWxlYXNlc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9yZWxlYXNlc3svaWR9Iiwic2l6ZSI6MTA2OTMxLCJzc2hfdXJsIjoiZ2l0QGdpdGh1Yi5jb206YXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uLmdpdCIsInN0YXJnYXplcnNfY291bnQiOjMwMjEsInN0YXJnYXplcnNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vc3RhcmdhemVycyIsInN0YXR1c2VzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL3N0YXR1c2VzL3tzaGF9Iiwic3Vic2NyaWJlcnNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vc3Vic2NyaWJlcnMiLCJzdWJzY3JpcHRpb25fdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vc3Vic2NyaXB0aW9uIiwic3ZuX3VybCI6Imh0dHBzOi8vZ2l0aHViLmNvbS9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24iLCJ0YWdzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL3RhZ3MiLCJ0ZWFtc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi90ZWFtcyIsInRvcGljcyI6WyJhd3MiLCJhd3MtbGFtYmRhIiwiaGFja3RvYmVyZmVzdCIsImxhbWJkYSIsInB5dGhvbiIsInNlcnZlcmxlc3MiXSwidHJlZXNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vZ2l0L3RyZWVzey9zaGF9IiwidXBkYXRlZF9hdCI6IjIwMjUtMDQtMTVUMDc6Mzk6NDJaIiwidXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24iLCJ2aXNpYmlsaXR5IjoicHVibGljIiwid2F0Y2hlcnMiOjMwMjEsIndhdGNoZXJzX2NvdW50IjozMDIxLCJ3ZWJfY29tbWl0X3NpZ25vZmZfcmVxdWlyZWQiOnRydWV9LCJzY2hlZHVsZSI6IjAgOCAqICogMS01Iiwid29ya2Zsb3ciOiIuZ2l0aHViL3dvcmtmbG93cy9wcmUtcmVsZWFzZS55bWwifSwiZ2l0aHViX2hlYWRfcmVmIjoiIiwiZ2l0aHViX3JlZiI6InJlZnMvaGVhZHMvZGV2ZWxvcCIsImdpdGh1Yl9yZWZfdHlwZSI6ImJyYW5jaCIsImdpdGh1Yl9yZXBvc2l0b3J5X2lkIjoiMjIxOTE5Mzc5IiwiZ2l0aHViX3JlcG9zaXRvcnlfb3duZXIiOiJhd3MtcG93ZXJ0b29scyIsImdpdGh1Yl9yZXBvc2l0b3J5X293bmVyX2lkIjoiMTI5MTI3NjM4IiwiZ2l0aHViX3J1bl9hdHRlbXB0IjoiMSIsImdpdGh1Yl9ydW5faWQiOiIxNDQ2NDM0ODEyNSIsImdpdGh1Yl9ydW5fbnVtYmVyIjoiMjE5IiwiZ2l0aHViX3NoYTEiOiJlNDU0MGExOGQxMmYwYmVhNzhlZjE1YzVjMGRkN2YyNDJiMTBmZjE5In19LCJtZXRhZGF0YSI6eyJidWlsZEludm9jYXRpb25JRCI6IjE0NDY0MzQ4MTI1LTEiLCJjb21wbGV0ZW5lc3MiOnsicGFyYW1ldGVycyI6dHJ1ZSwiZW52aXJvbm1lbnQiOmZhbHNlLCJtYXRlcmlhbHMiOmZhbHNlfSwicmVwcm9kdWNpYmxlIjpmYWxzZX0sIm1hdGVyaWFscyI6W3sidXJpIjoiZ2l0K2h0dHBzOi8vZ2l0aHViLmNvbS9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob25AcmVmcy9oZWFkcy9kZXZlbG9wIiwiZGlnZXN0Ijp7InNoYTEiOiJlNDU0MGExOGQxMmYwYmVhNzhlZjE1YzVjMGRkN2YyNDJiMTBmZjE5In19XX19", "payloadType":"application/vnd.in-toto+json", "signatures":[{"sig":"MEYCIQDEN9pVznSA7c9P9S+Z1dZVObB7kIEng7gFKYeeQfK5IwIhAL/geQdwvBQmLBJ1rmNfFkNyPGpRRxrEE6hR/eyDyp5f"}]}}
\ No newline at end of file
diff --git a/provenance/3.10.1a5/multiple.intoto.jsonl b/provenance/3.10.1a5/multiple.intoto.jsonl
new file mode 100644
index 00000000000..048a9abe438
--- /dev/null
+++ b/provenance/3.10.1a5/multiple.intoto.jsonl
@@ -0,0 +1 @@
+{"mediaType":"application/vnd.dev.sigstore.bundle.v0.3+json", "verificationMaterial":{"certificate":{"rawBytes":"MIIHZTCCBuugAwIBAgIUBAz2VyDQf/30TQr36IkhyI5/1rIwCgYIKoZIzj0EAwMwNzEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MR4wHAYDVQQDExVzaWdzdG9yZS1pbnRlcm1lZGlhdGUwHhcNMjUwNDE2MDgwNzI0WhcNMjUwNDE2MDgxNzI0WjAAMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEmftrbDRjrktI3V5WoMbHGtPHtInnKE1wrFa9KRqnN/BeTlGyRPZummcUgl+7h7dppRwIyFPHMpgqb+xvzcuXoaOCBgowggYGMA4GA1UdDwEB/wQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcDAzAdBgNVHQ4EFgQUJDTK+oGcwJchLrdakYns/bwzyT0wHwYDVR0jBBgwFoAU39Ppz1YkEZb5qNjpKFWixi4YZD8wgYQGA1UdEQEB/wR6MHiGdmh0dHBzOi8vZ2l0aHViLmNvbS9zbHNhLWZyYW1ld29yay9zbHNhLWdpdGh1Yi1nZW5lcmF0b3IvLmdpdGh1Yi93b3JrZmxvd3MvZ2VuZXJhdG9yX2dlbmVyaWNfc2xzYTMueW1sQHJlZnMvdGFncy92Mi4xLjAwOQYKKwYBBAGDvzABAQQraHR0cHM6Ly90b2tlbi5hY3Rpb25zLmdpdGh1YnVzZXJjb250ZW50LmNvbTAWBgorBgEEAYO/MAECBAhzY2hlZHVsZTA2BgorBgEEAYO/MAEDBChiNjk5ZDgyMGI1M2E3MWE4MWZmMzU5MjAwZDNkMzg1M2VhZjJiOTFlMBkGCisGAQQBg78wAQQEC1ByZS1SZWxlYXNlMDUGCisGAQQBg78wAQUEJ2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbjAgBgorBgEEAYO/MAEGBBJyZWZzL2hlYWRzL2RldmVsb3AwOwYKKwYBBAGDvzABCAQtDCtodHRwczovL3Rva2VuLmFjdGlvbnMuZ2l0aHVidXNlcmNvbnRlbnQuY29tMIGGBgorBgEEAYO/MAEJBHgMdmh0dHBzOi8vZ2l0aHViLmNvbS9zbHNhLWZyYW1ld29yay9zbHNhLWdpdGh1Yi1nZW5lcmF0b3IvLmdpdGh1Yi93b3JrZmxvd3MvZ2VuZXJhdG9yX2dlbmVyaWNfc2xzYTMueW1sQHJlZnMvdGFncy92Mi4xLjAwOAYKKwYBBAGDvzABCgQqDChmN2RkOGM1NGMyMDY3YmFmYzEyY2E3YTU1NTk1ZDVlZTliNzUyMDRhMB0GCisGAQQBg78wAQsEDwwNZ2l0aHViLWhvc3RlZDBKBgorBgEEAYO/MAEMBDwMOmh0dHBzOi8vZ2l0aHViLmNvbS9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24wOAYKKwYBBAGDvzABDQQqDChiNjk5ZDgyMGI1M2E3MWE4MWZmMzU5MjAwZDNkMzg1M2VhZjJiOTFlMCIGCisGAQQBg78wAQ4EFAwScmVmcy9oZWFkcy9kZXZlbG9wMBkGCisGAQQBg78wAQ8ECwwJMjIxOTE5Mzc5MDEGCisGAQQBg78wARAEIwwhaHR0cHM6Ly9naXRodWIuY29tL2F3cy1wb3dlcnRvb2xzMBkGCisGAQQBg78wAREECwwJMTI5MTI3NjM4MH8GCisGAQQBg78wARIEcQxvaHR0cHM6Ly9naXRodWIuY29tL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi8uZ2l0aHViL3dvcmtmbG93cy9wcmUtcmVsZWFzZS55bWxAcmVmcy9oZWFkcy9kZXZlbG9wMDgGCisGAQQBg78wARMEKgwoYjY5OWQ4MjBiNTNhNzFhODFmZjM1OTIwMGQzZDM4NTNlYWYyYjkxZTAYBgorBgEEAYO/MAEUBAoMCHNjaGVkdWxlMG4GCisGAQQBg78wARUEYAxeaHR0cHM6Ly9naXRodWIuY29tL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9hY3Rpb25zL3J1bnMvMTQ0ODc3MjcyODUvYXR0ZW1wdHMvMTAWBgorBgEEAYO/MAEWBAgMBnB1YmxpYzCBiQYKKwYBBAHWeQIEAgR7BHkAdwB1AN09MGrGxxEyYxkeHJlnNwKiSl643jyt/4eKcoAvKe6OAAABlj2jcp4AAAQDAEYwRAIgfjquDltfGY7wUB3+GOBGgCyXXCBQOgH19SfBAUOm+nICIFiCWZ2gcGnO1+KptCpcf/guk+NlDWaz4EETMp/74zziMAoGCCqGSM49BAMDA2gAMGUCMC5EYFDqm2WsxviqP9EaaZJSg16Hzla+pC0ovH3l0bNmGrJhqfxUwvfhIIV/DreuggIxAL2IP944jqXK9mpJ6l+AqalRFgJKxI7aGY5s9JcG52kKzQYcJoglqMktULHER8NEtg=="}, "tlogEntries":[{"logIndex":"197836188", "logId":{"keyId":"wNI9atQGlz+VWfO6LRygH4QUfY/8W4RFwiT5i5WRgB0="}, "kindVersion":{"kind":"dsse", "version":"0.0.1"}, "integratedTime":"1744790844", "inclusionPromise":{"signedEntryTimestamp":"MEUCIH1KFRNYZnhby7VwW4i04YgGG6DQn/24zjz8oW5LNLMwAiEArYEvEPZTyrMcmFz+Va00Xh3lXyuRilbiO7549TS5qWQ="}, "inclusionProof":{"logIndex":"75931926", "rootHash":"Jc++m2lFa5oAmwuojrmRxa5nTXQlZ8gvzTVuwpr2QgI=", "treeSize":"75931927", "hashes":["2MyZoLtbMIM8VXcSTXFLgX3/Za2rtcusySBLRc8hhuU=", "i+veex+a1mzX6P98pawyJlgkzy78y3iw75uE2nBJcXE=", "XeCTD323yi431y8Cumk37CtH2q1+eYiNVzYE7H60vws=", "4m1P7bKn4xXe+9VcqHHUt4Rb97V/smDpjsgq3s7sLrs=", "Hche6eC7LtIZZmBFQ2dEKU8uccAEVwxsSyU0h41iyKE=", "Zs/3SC9tsX2oIbESyQNtotNXRW1ukd42o7spgfaAaT8=", "6JUtjhgzQxxr7g0EZ7AyTLhHq3P/x49Uj4VTUV+ASqI=", "hZFenmXzOSdDvxJMvJ3Uu1Ha+dKkXebQgYPj+/wbqsM=", "gGNvqHSiyarbPiEG0lmBLLIhU2F6djF/wmlcFeaQdP8=", "7v8qPHNDLerpduaMx06eb/MwgoQwczTn/cYGKX/9wZ4="], "checkpoint":{"envelope":"rekor.sigstore.dev - 1193050959916656506\n75931927\nJc++m2lFa5oAmwuojrmRxa5nTXQlZ8gvzTVuwpr2QgI=\n\n— rekor.sigstore.dev wNI9ajBFAiEAmfJPlKln6XoUr0oxmrEBJEEuzZB2iBoA4g0kP54ivn0CICQzTHc9DxceAhGJpx+ja0ameu0sffWbNQ4LVIypaL4K\n"}}, "canonicalizedBody":"eyJhcGlWZXJzaW9uIjoiMC4wLjEiLCJraW5kIjoiZHNzZSIsInNwZWMiOnsiZW52ZWxvcGVIYXNoIjp7ImFsZ29yaXRobSI6InNoYTI1NiIsInZhbHVlIjoiNzBhMmFhYjVmMGQ1ZmVjNTJiMTA1OTM2NWQ5YzMwMDcwN2ZhNzI2Mjc3M2ViZWRjNDM4MGU3MDNkYmRmM2VmMSJ9LCJwYXlsb2FkSGFzaCI6eyJhbGdvcml0aG0iOiJzaGEyNTYiLCJ2YWx1ZSI6IjVkYzYyYWJjZDZkNmE3ODRlNTE1M2QwZDBlNTVjYTk0ZWNkMjE3NTg0M2YyYjIzMjdjMTYzODIxOTIyN2JhYTIifSwic2lnbmF0dXJlcyI6W3sic2lnbmF0dXJlIjoiTUVRQ0lIa2RYYmpJdHNpMzRGaUtpTFU2cWJhZm56SDBESUVRZHRKWUhDVEFkY0diQWlBNzdIdTNyaXFKWVl5NDhhREkydEIyS0V1eDhSdXZwU2lwNnJFWmJ2TXlYQT09IiwidmVyaWZpZXIiOiJMUzB0TFMxQ1JVZEpUaUJEUlZKVVNVWkpRMEZVUlMwdExTMHRDazFKU1VoYVZFTkRRblYxWjBGM1NVSkJaMGxWUWtGNk1sWjVSRkZtTHpNd1ZGRnlNelpKYTJoNVNUVXZNWEpKZDBObldVbExiMXBKZW1vd1JVRjNUWGNLVG5wRlZrMUNUVWRCTVZWRlEyaE5UV015Ykc1ak0xSjJZMjFWZFZwSFZqSk5ValIzU0VGWlJGWlJVVVJGZUZaNllWZGtlbVJIT1hsYVV6RndZbTVTYkFwamJURnNXa2RzYUdSSFZYZElhR05PVFdwVmQwNUVSVEpOUkdkM1RucEpNRmRvWTA1TmFsVjNUa1JGTWsxRVozaE9la2t3VjJwQlFVMUdhM2RGZDFsSUNrdHZXa2w2YWpCRFFWRlpTVXR2V2tsNmFqQkVRVkZqUkZGblFVVnRablJ5WWtSU2FuSnJkRWt6VmpWWGIwMWlTRWQwVUVoMFNXNXVTMFV4ZDNKR1lUa0tTMUp4Yms0dlFtVlViRWQ1VWxCYWRXMXRZMVZuYkNzM2FEZGtjSEJTZDBsNVJsQklUWEJuY1dJcmVIWjZZM1ZZYjJGUFEwSm5iM2RuWjFsSFRVRTBSd3BCTVZWa1JIZEZRaTkzVVVWQmQwbElaMFJCVkVKblRsWklVMVZGUkVSQlMwSm5aM0pDWjBWR1FsRmpSRUY2UVdSQ1owNVdTRkUwUlVablVWVktSRlJMQ2l0dlIyTjNTbU5vVEhKa1lXdFpibk12WW5kNmVWUXdkMGgzV1VSV1VqQnFRa0puZDBadlFWVXpPVkJ3ZWpGWmEwVmFZalZ4VG1wd1MwWlhhWGhwTkZrS1drUTRkMmRaVVVkQk1WVmtSVkZGUWk5M1VqWk5TR2xIWkcxb01HUklRbnBQYVRoMldqSnNNR0ZJVm1sTWJVNTJZbE01ZW1KSVRtaE1WMXA1V1ZjeGJBcGtNamw1WVhrNWVtSklUbWhNVjJSd1pFZG9NVmxwTVc1YVZ6VnNZMjFHTUdJelNYWk1iV1J3WkVkb01WbHBPVE5pTTBweVdtMTRkbVF6VFhaYU1sWjFDbHBZU21oa1J6bDVXREprYkdKdFZubGhWMDVtWXpKNGVsbFVUWFZsVnpGelVVaEtiRnB1VFhaa1IwWnVZM2s1TWsxcE5IaE1ha0YzVDFGWlMwdDNXVUlLUWtGSFJIWjZRVUpCVVZGeVlVaFNNR05JVFRaTWVUa3dZakowYkdKcE5XaFpNMUp3WWpJMWVreHRaSEJrUjJneFdXNVdlbHBZU21waU1qVXdXbGMxTUFwTWJVNTJZbFJCVjBKbmIzSkNaMFZGUVZsUEwwMUJSVU5DUVdoNldUSm9iRnBJVm5OYVZFRXlRbWR2Y2tKblJVVkJXVTh2VFVGRlJFSkRhR2xPYW1zMUNscEVaM2xOUjBreFRUSkZNMDFYUlRSTlYxcHRUWHBWTlUxcVFYZGFSRTVyVFhwbk1VMHlWbWhhYWtwcFQxUkdiRTFDYTBkRGFYTkhRVkZSUW1jM09IY0tRVkZSUlVNeFFubGFVekZUV2xkNGJGbFlUbXhOUkZWSFEybHpSMEZSVVVKbk56aDNRVkZWUlVveVJqTmplVEYzWWpOa2JHTnVVblppTW5oNlRETkNkZ3BrTWxaNVpFYzVkbUpJVFhSaVIwWjBXVzFTYUV4WVFqVmtSMmgyWW1wQlowSm5iM0pDWjBWRlFWbFBMMDFCUlVkQ1FrcDVXbGRhZWt3eWFHeFpWMUo2Q2t3eVVteGtiVlp6WWpOQmQwOTNXVXRMZDFsQ1FrRkhSSFo2UVVKRFFWRjBSRU4wYjJSSVVuZGplbTkyVEROU2RtRXlWblZNYlVacVpFZHNkbUp1VFhVS1dqSnNNR0ZJVm1sa1dFNXNZMjFPZG1KdVVteGlibEYxV1RJNWRFMUpSMGRDWjI5eVFtZEZSVUZaVHk5TlFVVktRa2huVFdSdGFEQmtTRUo2VDJrNGRncGFNbXd3WVVoV2FVeHRUblppVXpsNllraE9hRXhYV25sWlZ6RnNaREk1ZVdGNU9YcGlTRTVvVEZka2NHUkhhREZaYVRGdVdsYzFiR050UmpCaU0wbDJDa3h0WkhCa1IyZ3hXV2s1TTJJelNuSmFiWGgyWkROTmRsb3lWblZhV0Vwb1pFYzVlVmd5Wkd4aWJWWjVZVmRPWm1NeWVIcFpWRTExWlZjeGMxRklTbXdLV201TmRtUkhSbTVqZVRreVRXazBlRXhxUVhkUFFWbExTM2RaUWtKQlIwUjJla0ZDUTJkUmNVUkRhRzFPTWxKclQwZE5NVTVIVFhsTlJGa3pXVzFHYlFwWmVrVjVXVEpGTTFsVVZURk9WR3N4V2tSV2JGcFViR2xPZWxWNVRVUlNhRTFDTUVkRGFYTkhRVkZSUW1jM09IZEJVWE5GUkhkM1Rsb3liREJoU0ZacENreFhhSFpqTTFKc1drUkNTMEpuYjNKQ1owVkZRVmxQTDAxQlJVMUNSSGROVDIxb01HUklRbnBQYVRoMldqSnNNR0ZJVm1sTWJVNTJZbE01YUdRelRYUUtZMGM1TTFwWVNqQmlNamx6WTNrNWQySXpaR3hqYmxKMllqSjRla3hYZUdoaVYwcHJXVk14ZDJWWVVtOWlNalIzVDBGWlMwdDNXVUpDUVVkRWRucEJRZ3BFVVZGeFJFTm9hVTVxYXpWYVJHZDVUVWRKTVUweVJUTk5WMFUwVFZkYWJVMTZWVFZOYWtGM1drUk9hMDE2WnpGTk1sWm9XbXBLYVU5VVJteE5RMGxIQ2tOcGMwZEJVVkZDWnpjNGQwRlJORVZHUVhkVFkyMVdiV041T1c5YVYwWnJZM2s1YTFwWVdteGlSemwzVFVKclIwTnBjMGRCVVZGQ1p6YzRkMEZST0VVS1EzZDNTazFxU1hoUFZFVTFUWHBqTlUxRVJVZERhWE5IUVZGUlFtYzNPSGRCVWtGRlNYZDNhR0ZJVWpCalNFMDJUSGs1Ym1GWVVtOWtWMGwxV1RJNWRBcE1Na1l6WTNreGQySXpaR3hqYmxKMllqSjRlazFDYTBkRGFYTkhRVkZSUW1jM09IZEJVa1ZGUTNkM1NrMVVTVFZOVkVrelRtcE5ORTFJT0VkRGFYTkhDa0ZSVVVKbk56aDNRVkpKUldOUmVIWmhTRkl3WTBoTk5reDVPVzVoV0ZKdlpGZEpkVmt5T1hSTU1rWXpZM2t4ZDJJelpHeGpibEoyWWpKNGVrd3pRbllLWkRKV2VXUkhPWFppU0UxMFlrZEdkRmx0VW1oTVdFSTFaRWRvZG1KcE9IVmFNbXd3WVVoV2FVd3paSFpqYlhSdFlrYzVNMk41T1hkamJWVjBZMjFXY3dwYVYwWjZXbE0xTldKWGVFRmpiVlp0WTNrNWIxcFhSbXRqZVRscldsaGFiR0pIT1hkTlJHZEhRMmx6UjBGUlVVSm5OemgzUVZKTlJVdG5kMjlaYWxrMUNrOVhVVFJOYWtKcFRsUk9hRTU2Um1oUFJFWnRXbXBOTVU5VVNYZE5SMUY2V2tSTk5FNVVUbXhaVjFsNVdXcHJlRnBVUVZsQ1oyOXlRbWRGUlVGWlR5OEtUVUZGVlVKQmIwMURTRTVxWVVkV2EyUlhlR3hOUnpSSFEybHpSMEZSVVVKbk56aDNRVkpWUlZsQmVHVmhTRkl3WTBoTk5reDVPVzVoV0ZKdlpGZEpkUXBaTWpsMFRESkdNMk41TVhkaU0yUnNZMjVTZG1JeWVIcE1NMEoyWkRKV2VXUkhPWFppU0UxMFlrZEdkRmx0VW1oTVdFSTFaRWRvZG1KcE9XaFpNMUp3Q21JeU5YcE1NMG94WW01TmRrMVVVVEJQUkdNelRXcGplVTlFVlhaWldGSXdXbGN4ZDJSSVRYWk5WRUZYUW1kdmNrSm5SVVZCV1U4dlRVRkZWMEpCWjAwS1FtNUNNVmx0ZUhCWmVrTkNhVkZaUzB0M1dVSkNRVWhYWlZGSlJVRm5VamRDU0d0QlpIZENNVUZPTURsTlIzSkhlSGhGZVZsNGEyVklTbXh1VG5kTGFRcFRiRFkwTTJwNWRDODBaVXRqYjBGMlMyVTJUMEZCUVVKc2FqSnFZM0EwUVVGQlVVUkJSVmwzVWtGSloyWnFjWFZFYkhSbVIxazNkMVZDTXl0SFQwSkhDbWREZVZoWVEwSlJUMmRJTVRsVFprSkJWVTl0SzI1SlEwbEdhVU5YV2pKblkwZHVUekVyUzNCMFEzQmpaaTluZFdzclRteEVWMkY2TkVWRlZFMXdMemNLTkhwNmFVMUJiMGREUTNGSFUwMDBPVUpCVFVSQk1tZEJUVWRWUTAxRE5VVlpSa1J4YlRKWGMzaDJhWEZRT1VWaFlWcEtVMmN4TmtoNmJHRXJjRU13YndwMlNETnNNR0pPYlVkeVNtaHhabmhWZDNabWFFbEpWaTlFY21WMVoyZEplRUZNTWtsUU9UUTBhbkZZU3psdGNFbzJiQ3RCY1dGc1VrWm5Ta3Q0U1RkaENrZFpOWE01U21OSE5USnJTM3BSV1dOS2IyZHNjVTFyZEZWTVNFVlNPRTVGZEdjOVBRb3RMUzB0TFVWT1JDQkRSVkpVU1VaSlEwRlVSUzB0TFMwdENnPT0ifV19fQ=="}]}, "dsseEnvelope":{"payload":"eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjAuMSIsInByZWRpY2F0ZVR5cGUiOiJodHRwczovL3Nsc2EuZGV2L3Byb3ZlbmFuY2UvdjAuMiIsInN1YmplY3QiOlt7Im5hbWUiOiIuL2F3c19sYW1iZGFfcG93ZXJ0b29scy0zLjEwLjFhNS1weTMtbm9uZS1hbnkud2hsIiwiZGlnZXN0Ijp7InNoYTI1NiI6ImM0ZTZhNGRiNWQ5MjJjMWU5MWU3ODQwMzU2NWVlOWRkOWI0NDJkOWNjNjAxOWVhZTlkNWE3YTI4MmM4MzAyNDQifX0seyJuYW1lIjoiLi9hd3NfbGFtYmRhX3Bvd2VydG9vbHMtMy4xMC4xYTUudGFyLmd6IiwiZGlnZXN0Ijp7InNoYTI1NiI6Ijg4ZTM4NzM1MzM0OGRmNjBlMzVlYjZhZTliMWY4YTRjNTdhZTlmMWZiZjc2YzVhODQ3YzI4NGQyYjk1ZWEzN2YifX1dLCJwcmVkaWNhdGUiOnsiYnVpbGRlciI6eyJpZCI6Imh0dHBzOi8vZ2l0aHViLmNvbS9zbHNhLWZyYW1ld29yay9zbHNhLWdpdGh1Yi1nZW5lcmF0b3IvLmdpdGh1Yi93b3JrZmxvd3MvZ2VuZXJhdG9yX2dlbmVyaWNfc2xzYTMueW1sQHJlZnMvdGFncy92Mi4xLjAifSwiYnVpbGRUeXBlIjoiaHR0cHM6Ly9naXRodWIuY29tL3Nsc2EtZnJhbWV3b3JrL3Nsc2EtZ2l0aHViLWdlbmVyYXRvci9nZW5lcmljQHYxIiwiaW52b2NhdGlvbiI6eyJjb25maWdTb3VyY2UiOnsidXJpIjoiZ2l0K2h0dHBzOi8vZ2l0aHViLmNvbS9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob25AcmVmcy9oZWFkcy9kZXZlbG9wIiwiZGlnZXN0Ijp7InNoYTEiOiJiNjk5ZDgyMGI1M2E3MWE4MWZmMzU5MjAwZDNkMzg1M2VhZjJiOTFlIn0sImVudHJ5UG9pbnQiOiIuZ2l0aHViL3dvcmtmbG93cy9wcmUtcmVsZWFzZS55bWwifSwicGFyYW1ldGVycyI6eyJ2YXJzIjp7fX0sImVudmlyb25tZW50Ijp7ImdpdGh1Yl9hY3RvciI6ImxlYW5kcm9kYW1hc2NlbmEiLCJnaXRodWJfYWN0b3JfaWQiOiI0Mjk1MTczIiwiZ2l0aHViX2Jhc2VfcmVmIjoiIiwiZ2l0aHViX2V2ZW50X25hbWUiOiJzY2hlZHVsZSIsImdpdGh1Yl9ldmVudF9wYXlsb2FkIjp7ImVudGVycHJpc2UiOnsiYXZhdGFyX3VybCI6Imh0dHBzOi8vYXZhdGFycy5naXRodWJ1c2VyY29udGVudC5jb20vYi8xMjkwP3Y9NCIsImNyZWF0ZWRfYXQiOiIyMDE5LTExLTEzVDE4OjA1OjQxWiIsImRlc2NyaXB0aW9uIjoiIiwiaHRtbF91cmwiOiJodHRwczovL2dpdGh1Yi5jb20vZW50ZXJwcmlzZXMvYW1hem9uIiwiaWQiOjEyOTAsIm5hbWUiOiJBbWF6b24iLCJub2RlX2lkIjoiTURFd09rVnVkR1Z5Y0hKcGMyVXhNamt3Iiwic2x1ZyI6ImFtYXpvbiIsInVwZGF0ZWRfYXQiOiIyMDI0LTA5LTMwVDIxOjAyOjMwWiIsIndlYnNpdGVfdXJsIjoiaHR0cHM6Ly93d3cuYW1hem9uLmNvbS8ifSwib3JnYW5pemF0aW9uIjp7ImF2YXRhcl91cmwiOiJodHRwczovL2F2YXRhcnMuZ2l0aHVidXNlcmNvbnRlbnQuY29tL3UvMTI5MTI3NjM4P3Y9NCIsImRlc2NyaXB0aW9uIjoiIiwiZXZlbnRzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vb3Jncy9hd3MtcG93ZXJ0b29scy9ldmVudHMiLCJob29rc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL29yZ3MvYXdzLXBvd2VydG9vbHMvaG9va3MiLCJpZCI6MTI5MTI3NjM4LCJpc3N1ZXNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9vcmdzL2F3cy1wb3dlcnRvb2xzL2lzc3VlcyIsImxvZ2luIjoiYXdzLXBvd2VydG9vbHMiLCJtZW1iZXJzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vb3Jncy9hd3MtcG93ZXJ0b29scy9tZW1iZXJzey9tZW1iZXJ9Iiwibm9kZV9pZCI6Ik9fa2dET0I3SlUxZyIsInB1YmxpY19tZW1iZXJzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vb3Jncy9hd3MtcG93ZXJ0b29scy9wdWJsaWNfbWVtYmVyc3svbWVtYmVyfSIsInJlcG9zX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vb3Jncy9hd3MtcG93ZXJ0b29scy9yZXBvcyIsInVybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vb3Jncy9hd3MtcG93ZXJ0b29scyJ9LCJyZXBvc2l0b3J5Ijp7ImFsbG93X2ZvcmtpbmciOnRydWUsImFyY2hpdmVfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24ve2FyY2hpdmVfZm9ybWF0fXsvcmVmfSIsImFyY2hpdmVkIjpmYWxzZSwiYXNzaWduZWVzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2Fzc2lnbmVlc3svdXNlcn0iLCJibG9ic191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9naXQvYmxvYnN7L3NoYX0iLCJicmFuY2hlc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9icmFuY2hlc3svYnJhbmNofSIsImNsb25lX3VybCI6Imh0dHBzOi8vZ2l0aHViLmNvbS9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24uZ2l0IiwiY29sbGFib3JhdG9yc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9jb2xsYWJvcmF0b3Jzey9jb2xsYWJvcmF0b3J9IiwiY29tbWVudHNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vY29tbWVudHN7L251bWJlcn0iLCJjb21taXRzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2NvbW1pdHN7L3NoYX0iLCJjb21wYXJlX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2NvbXBhcmUve2Jhc2V9Li4ue2hlYWR9IiwiY29udGVudHNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vY29udGVudHMveytwYXRofSIsImNvbnRyaWJ1dG9yc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9jb250cmlidXRvcnMiLCJjcmVhdGVkX2F0IjoiMjAxOS0xMS0xNVQxMjoyNjoxMloiLCJjdXN0b21fcHJvcGVydGllcyI6e30sImRlZmF1bHRfYnJhbmNoIjoiZGV2ZWxvcCIsImRlcGxveW1lbnRzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2RlcGxveW1lbnRzIiwiZGVzY3JpcHRpb24iOiJBIGRldmVsb3BlciB0b29sa2l0IHRvIGltcGxlbWVudCBTZXJ2ZXJsZXNzIGJlc3QgcHJhY3RpY2VzIGFuZCBpbmNyZWFzZSBkZXZlbG9wZXIgdmVsb2NpdHkuIiwiZGlzYWJsZWQiOmZhbHNlLCJkb3dubG9hZHNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vZG93bmxvYWRzIiwiZXZlbnRzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2V2ZW50cyIsImZvcmsiOmZhbHNlLCJmb3JrcyI6NDE4LCJmb3Jrc19jb3VudCI6NDE4LCJmb3Jrc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9mb3JrcyIsImZ1bGxfbmFtZSI6ImF3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbiIsImdpdF9jb21taXRzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2dpdC9jb21taXRzey9zaGF9IiwiZ2l0X3JlZnNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vZ2l0L3JlZnN7L3NoYX0iLCJnaXRfdGFnc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9naXQvdGFnc3svc2hhfSIsImdpdF91cmwiOiJnaXQ6Ly9naXRodWIuY29tL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi5naXQiLCJoYXNfZGlzY3Vzc2lvbnMiOnRydWUsImhhc19kb3dubG9hZHMiOnRydWUsImhhc19pc3N1ZXMiOnRydWUsImhhc19wYWdlcyI6ZmFsc2UsImhhc19wcm9qZWN0cyI6dHJ1ZSwiaGFzX3dpa2kiOmZhbHNlLCJob21lcGFnZSI6Imh0dHBzOi8vZG9jcy5wb3dlcnRvb2xzLmF3cy5kZXYvbGFtYmRhL3B5dGhvbi9sYXRlc3QvIiwiaG9va3NfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vaG9va3MiLCJodG1sX3VybCI6Imh0dHBzOi8vZ2l0aHViLmNvbS9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24iLCJpZCI6MjIxOTE5Mzc5LCJpc190ZW1wbGF0ZSI6ZmFsc2UsImlzc3VlX2NvbW1lbnRfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vaXNzdWVzL2NvbW1lbnRzey9udW1iZXJ9IiwiaXNzdWVfZXZlbnRzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2lzc3Vlcy9ldmVudHN7L251bWJlcn0iLCJpc3N1ZXNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vaXNzdWVzey9udW1iZXJ9Iiwia2V5c191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9rZXlzey9rZXlfaWR9IiwibGFiZWxzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2xhYmVsc3svbmFtZX0iLCJsYW5ndWFnZSI6IlB5dGhvbiIsImxhbmd1YWdlc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9sYW5ndWFnZXMiLCJsaWNlbnNlIjp7ImtleSI6Im1pdC0wIiwibmFtZSI6Ik1JVCBObyBBdHRyaWJ1dGlvbiIsIm5vZGVfaWQiOiJNRGM2VEdsalpXNXpaVFF4Iiwic3BkeF9pZCI6Ik1JVC0wIiwidXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9saWNlbnNlcy9taXQtMCJ9LCJtZXJnZXNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vbWVyZ2VzIiwibWlsZXN0b25lc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9taWxlc3RvbmVzey9udW1iZXJ9IiwibWlycm9yX3VybCI6bnVsbCwibmFtZSI6InBvd2VydG9vbHMtbGFtYmRhLXB5dGhvbiIsIm5vZGVfaWQiOiJNREV3T2xKbGNHOXphWFJ2Y25reU1qRTVNVGt6TnprPSIsIm5vdGlmaWNhdGlvbnNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vbm90aWZpY2F0aW9uc3s/c2luY2UsYWxsLHBhcnRpY2lwYXRpbmd9Iiwib3Blbl9pc3N1ZXMiOjY4LCJvcGVuX2lzc3Vlc19jb3VudCI6NjgsIm93bmVyIjp7ImF2YXRhcl91cmwiOiJodHRwczovL2F2YXRhcnMuZ2l0aHVidXNlcmNvbnRlbnQuY29tL3UvMTI5MTI3NjM4P3Y9NCIsImV2ZW50c191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3VzZXJzL2F3cy1wb3dlcnRvb2xzL2V2ZW50c3svcHJpdmFjeX0iLCJmb2xsb3dlcnNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS91c2Vycy9hd3MtcG93ZXJ0b29scy9mb2xsb3dlcnMiLCJmb2xsb3dpbmdfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS91c2Vycy9hd3MtcG93ZXJ0b29scy9mb2xsb3dpbmd7L290aGVyX3VzZXJ9IiwiZ2lzdHNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS91c2Vycy9hd3MtcG93ZXJ0b29scy9naXN0c3svZ2lzdF9pZH0iLCJncmF2YXRhcl9pZCI6IiIsImh0bWxfdXJsIjoiaHR0cHM6Ly9naXRodWIuY29tL2F3cy1wb3dlcnRvb2xzIiwiaWQiOjEyOTEyNzYzOCwibG9naW4iOiJhd3MtcG93ZXJ0b29scyIsIm5vZGVfaWQiOiJPX2tnRE9CN0pVMWciLCJvcmdhbml6YXRpb25zX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vdXNlcnMvYXdzLXBvd2VydG9vbHMvb3JncyIsInJlY2VpdmVkX2V2ZW50c191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3VzZXJzL2F3cy1wb3dlcnRvb2xzL3JlY2VpdmVkX2V2ZW50cyIsInJlcG9zX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vdXNlcnMvYXdzLXBvd2VydG9vbHMvcmVwb3MiLCJzaXRlX2FkbWluIjpmYWxzZSwic3RhcnJlZF91cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3VzZXJzL2F3cy1wb3dlcnRvb2xzL3N0YXJyZWR7L293bmVyfXsvcmVwb30iLCJzdWJzY3JpcHRpb25zX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vdXNlcnMvYXdzLXBvd2VydG9vbHMvc3Vic2NyaXB0aW9ucyIsInR5cGUiOiJPcmdhbml6YXRpb24iLCJ1cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3VzZXJzL2F3cy1wb3dlcnRvb2xzIiwidXNlcl92aWV3X3R5cGUiOiJwdWJsaWMifSwicHJpdmF0ZSI6ZmFsc2UsInB1bGxzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL3B1bGxzey9udW1iZXJ9IiwicHVzaGVkX2F0IjoiMjAyNS0wNC0xNlQwNzo1OToyOVoiLCJyZWxlYXNlc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9yZWxlYXNlc3svaWR9Iiwic2l6ZSI6MTA3MzExLCJzc2hfdXJsIjoiZ2l0QGdpdGh1Yi5jb206YXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uLmdpdCIsInN0YXJnYXplcnNfY291bnQiOjMwMjEsInN0YXJnYXplcnNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vc3RhcmdhemVycyIsInN0YXR1c2VzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL3N0YXR1c2VzL3tzaGF9Iiwic3Vic2NyaWJlcnNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vc3Vic2NyaWJlcnMiLCJzdWJzY3JpcHRpb25fdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vc3Vic2NyaXB0aW9uIiwic3ZuX3VybCI6Imh0dHBzOi8vZ2l0aHViLmNvbS9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24iLCJ0YWdzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL3RhZ3MiLCJ0ZWFtc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi90ZWFtcyIsInRvcGljcyI6WyJhd3MiLCJhd3MtbGFtYmRhIiwiaGFja3RvYmVyZmVzdCIsImxhbWJkYSIsInB5dGhvbiIsInNlcnZlcmxlc3MiXSwidHJlZXNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vZ2l0L3RyZWVzey9zaGF9IiwidXBkYXRlZF9hdCI6IjIwMjUtMDQtMTVUMjE6NDg6MThaIiwidXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24iLCJ2aXNpYmlsaXR5IjoicHVibGljIiwid2F0Y2hlcnMiOjMwMjEsIndhdGNoZXJzX2NvdW50IjozMDIxLCJ3ZWJfY29tbWl0X3NpZ25vZmZfcmVxdWlyZWQiOnRydWV9LCJzY2hlZHVsZSI6IjAgOCAqICogMS01Iiwid29ya2Zsb3ciOiIuZ2l0aHViL3dvcmtmbG93cy9wcmUtcmVsZWFzZS55bWwifSwiZ2l0aHViX2hlYWRfcmVmIjoiIiwiZ2l0aHViX3JlZiI6InJlZnMvaGVhZHMvZGV2ZWxvcCIsImdpdGh1Yl9yZWZfdHlwZSI6ImJyYW5jaCIsImdpdGh1Yl9yZXBvc2l0b3J5X2lkIjoiMjIxOTE5Mzc5IiwiZ2l0aHViX3JlcG9zaXRvcnlfb3duZXIiOiJhd3MtcG93ZXJ0b29scyIsImdpdGh1Yl9yZXBvc2l0b3J5X293bmVyX2lkIjoiMTI5MTI3NjM4IiwiZ2l0aHViX3J1bl9hdHRlbXB0IjoiMSIsImdpdGh1Yl9ydW5faWQiOiIxNDQ4NzcyNzI4NSIsImdpdGh1Yl9ydW5fbnVtYmVyIjoiMjIwIiwiZ2l0aHViX3NoYTEiOiJiNjk5ZDgyMGI1M2E3MWE4MWZmMzU5MjAwZDNkMzg1M2VhZjJiOTFlIn19LCJtZXRhZGF0YSI6eyJidWlsZEludm9jYXRpb25JRCI6IjE0NDg3NzI3Mjg1LTEiLCJjb21wbGV0ZW5lc3MiOnsicGFyYW1ldGVycyI6dHJ1ZSwiZW52aXJvbm1lbnQiOmZhbHNlLCJtYXRlcmlhbHMiOmZhbHNlfSwicmVwcm9kdWNpYmxlIjpmYWxzZX0sIm1hdGVyaWFscyI6W3sidXJpIjoiZ2l0K2h0dHBzOi8vZ2l0aHViLmNvbS9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob25AcmVmcy9oZWFkcy9kZXZlbG9wIiwiZGlnZXN0Ijp7InNoYTEiOiJiNjk5ZDgyMGI1M2E3MWE4MWZmMzU5MjAwZDNkMzg1M2VhZjJiOTFlIn19XX19", "payloadType":"application/vnd.in-toto+json", "signatures":[{"sig":"MEQCIHkdXbjItsi34FiKiLU6qbafnzH0DIEQdtJYHCTAdcGbAiA77Hu3riqJYYy48aDI2tB2KEux8RuvpSip6rEZbvMyXA=="}]}}
\ No newline at end of file
diff --git a/provenance/3.10.1a6/multiple.intoto.jsonl b/provenance/3.10.1a6/multiple.intoto.jsonl
new file mode 100644
index 00000000000..c98c99cc0b4
--- /dev/null
+++ b/provenance/3.10.1a6/multiple.intoto.jsonl
@@ -0,0 +1 @@
+{"mediaType":"application/vnd.dev.sigstore.bundle.v0.3+json", "verificationMaterial":{"certificate":{"rawBytes":"MIIHZjCCBuygAwIBAgIUFu3PNZcX136zfNuvGxmR/vqFw7kwCgYIKoZIzj0EAwMwNzEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MR4wHAYDVQQDExVzaWdzdG9yZS1pbnRlcm1lZGlhdGUwHhcNMjUwNDE3MDgwNzQzWhcNMjUwNDE3MDgxNzQzWjAAMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEserN3fsIyKKi17xJToGQGGKTKCf1Sg88DC2Lz2iGU8p7Liz4LInhVhHDUDmTD7kvGB0mIUW8chtyfcZDrtZIJqOCBgswggYHMA4GA1UdDwEB/wQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcDAzAdBgNVHQ4EFgQUwmtlzT+TQX9iGbVuulcRGIJCwAQwHwYDVR0jBBgwFoAU39Ppz1YkEZb5qNjpKFWixi4YZD8wgYQGA1UdEQEB/wR6MHiGdmh0dHBzOi8vZ2l0aHViLmNvbS9zbHNhLWZyYW1ld29yay9zbHNhLWdpdGh1Yi1nZW5lcmF0b3IvLmdpdGh1Yi93b3JrZmxvd3MvZ2VuZXJhdG9yX2dlbmVyaWNfc2xzYTMueW1sQHJlZnMvdGFncy92Mi4xLjAwOQYKKwYBBAGDvzABAQQraHR0cHM6Ly90b2tlbi5hY3Rpb25zLmdpdGh1YnVzZXJjb250ZW50LmNvbTAWBgorBgEEAYO/MAECBAhzY2hlZHVsZTA2BgorBgEEAYO/MAEDBCg2ZmUxODQ4ODYyODcyMDAyMzM1YmYxMGIwMGJiMDQxNWE3N2I4MzBjMBkGCisGAQQBg78wAQQEC1ByZS1SZWxlYXNlMDUGCisGAQQBg78wAQUEJ2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbjAgBgorBgEEAYO/MAEGBBJyZWZzL2hlYWRzL2RldmVsb3AwOwYKKwYBBAGDvzABCAQtDCtodHRwczovL3Rva2VuLmFjdGlvbnMuZ2l0aHVidXNlcmNvbnRlbnQuY29tMIGGBgorBgEEAYO/MAEJBHgMdmh0dHBzOi8vZ2l0aHViLmNvbS9zbHNhLWZyYW1ld29yay9zbHNhLWdpdGh1Yi1nZW5lcmF0b3IvLmdpdGh1Yi93b3JrZmxvd3MvZ2VuZXJhdG9yX2dlbmVyaWNfc2xzYTMueW1sQHJlZnMvdGFncy92Mi4xLjAwOAYKKwYBBAGDvzABCgQqDChmN2RkOGM1NGMyMDY3YmFmYzEyY2E3YTU1NTk1ZDVlZTliNzUyMDRhMB0GCisGAQQBg78wAQsEDwwNZ2l0aHViLWhvc3RlZDBKBgorBgEEAYO/MAEMBDwMOmh0dHBzOi8vZ2l0aHViLmNvbS9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24wOAYKKwYBBAGDvzABDQQqDCg2ZmUxODQ4ODYyODcyMDAyMzM1YmYxMGIwMGJiMDQxNWE3N2I4MzBjMCIGCisGAQQBg78wAQ4EFAwScmVmcy9oZWFkcy9kZXZlbG9wMBkGCisGAQQBg78wAQ8ECwwJMjIxOTE5Mzc5MDEGCisGAQQBg78wARAEIwwhaHR0cHM6Ly9naXRodWIuY29tL2F3cy1wb3dlcnRvb2xzMBkGCisGAQQBg78wAREECwwJMTI5MTI3NjM4MH8GCisGAQQBg78wARIEcQxvaHR0cHM6Ly9naXRodWIuY29tL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi8uZ2l0aHViL3dvcmtmbG93cy9wcmUtcmVsZWFzZS55bWxAcmVmcy9oZWFkcy9kZXZlbG9wMDgGCisGAQQBg78wARMEKgwoNmZlMTg0ODg2Mjg3MjAwMjMzNWJmMTBiMDBiYjA0MTVhNzdiODMwYzAYBgorBgEEAYO/MAEUBAoMCHNjaGVkdWxlMG4GCisGAQQBg78wARUEYAxeaHR0cHM6Ly9naXRodWIuY29tL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9hY3Rpb25zL3J1bnMvMTQ1MTA5NzE2MDgvYXR0ZW1wdHMvMTAWBgorBgEEAYO/MAEWBAgMBnB1YmxpYzCBigYKKwYBBAHWeQIEAgR8BHoAeAB2AN09MGrGxxEyYxkeHJlnNwKiSl643jyt/4eKcoAvKe6OAAABlkLKGnYAAAQDAEcwRQIhAN3/xMU3/FkmKtVwcSZba6SqqyAW8FuUA8FjrDL/vZdiAiB9RO9QITnsEbKQAz3ZXAl59m1WuePV4p8Yq5bRcP5AHTAKBggqhkjOPQQDAwNoADBlAjA0j2MUEz35j8pJA2+K6rCmYQJg5RfHyotZ4sRAZUyrjigqQd4zzW/UZwOTKMWPfKsCMQDTKU9z0ISeXGmchH3/Fes5FQJJlokaNt+0Bsmx+kRlio12tlRehTrtGQ4HaW1h+Pg="}, "tlogEntries":[{"logIndex":"198441403", "logId":{"keyId":"wNI9atQGlz+VWfO6LRygH4QUfY/8W4RFwiT5i5WRgB0="}, "kindVersion":{"kind":"dsse", "version":"0.0.1"}, "integratedTime":"1744877263", "inclusionPromise":{"signedEntryTimestamp":"MEQCIAVUzqKinUgxnKpCsMuw1VvqOxcg0GRohEQjO/aSYilmAiA0QYNnZokHurfycRoKcdo5EXaUES7hjck54pSLdP72IA=="}, "inclusionProof":{"logIndex":"76537141", "rootHash":"XIkRWQkVFbSYNaHIDjLf61YHq60l9CAU5AnEIz/sacY=", "treeSize":"76537142", "hashes":["SPV71te+Dbrm0Cq75OFhXt7EpSaCc8P4R0HWTO7Hzy0=", "tHGqwmIt1xE1Lhv8MVSwp7kh7UAg9nttgHcVfWuinSE=", "xWt1ii3L8TY0Qpb4RWdOMfMOOrA8wbg2CoZRO8pY6sc=", "4afcm7PcHGStfGviINFTpbdYBWAfPRgt3YjGqK7HnIo=", "qoZ0uV61A7ibPGKdy03tf2XT4Ya7dzihWWuPv6fKoa8=", "w2xTCSVySOwgpBOAGrIhUcGpRLaZV26aW4LfQ7LdA5E=", "ATkl33oBj5Ct6QxCVIMWPLT7tnt+nhADaVIVLW5fVKA=", "7S8MXyY4d9oOMM948kNjUb2Q8PGoGAzMz9/YTUDvZUA=", "fT6uAjfjcO26orGNp1LIvkXe7n28Mnh6PFYw4vQC5x0=", "VqFodtEqljdGq7Z+i6xA1NKTdjcSMMU0HJzl18K8pi4=", "B3wsPPaciXRAKEdW6T9+Tn1LnKIFP7eDoKGUzeZ625Q=", "yyAg/BZEfcj+DNA3gAJC2o9T6WbtTBpOaDUZuSnkKUA=", "p0E+H7aTabIUUthZ/5/w8R3XnQk2GsaAWBpgx01Krz8=", "CPo/VrC1a3Jxn8ImS26SgiZhNz2V3UZGXi/ssnY/EYA=", "gGNvqHSiyarbPiEG0lmBLLIhU2F6djF/wmlcFeaQdP8=", "7v8qPHNDLerpduaMx06eb/MwgoQwczTn/cYGKX/9wZ4="], "checkpoint":{"envelope":"rekor.sigstore.dev - 1193050959916656506\n76537142\nXIkRWQkVFbSYNaHIDjLf61YHq60l9CAU5AnEIz/sacY=\n\n— rekor.sigstore.dev wNI9ajBEAiBCYFweqh7ZgYnkP6k0OHs4a6wGoC84TPzf3r3tL3SQOAIgKNNS1JFSTolBFzTSpLk30h9eh9bpzu3TzID7kIvjnpA=\n"}}, "canonicalizedBody":"eyJhcGlWZXJzaW9uIjoiMC4wLjEiLCJraW5kIjoiZHNzZSIsInNwZWMiOnsiZW52ZWxvcGVIYXNoIjp7ImFsZ29yaXRobSI6InNoYTI1NiIsInZhbHVlIjoiZWIzZGE1NTgzNWNkNmQ5MzQ2Y2Q0M2IyMjQzZGEzMDg3OTUzMzEyODdjMTUzZWIxZmUyZjczYmRiMDc5NGQ2NSJ9LCJwYXlsb2FkSGFzaCI6eyJhbGdvcml0aG0iOiJzaGEyNTYiLCJ2YWx1ZSI6ImYwMTNhNmZmN2JkMzUyNWVlMzBhM2M4ZmYwNTA3OTg5YjAwMmRlNDY3ZGQ1YjQ2Yjg1NzFmMzdiY2NlZDIxMGIifSwic2lnbmF0dXJlcyI6W3sic2lnbmF0dXJlIjoiTUVVQ0lRRGxSaEgxaE1EcUNjNitDOE1IMUR3TmpHREw0ZDMyYVlGQ2ZrTkFaV2JNeFFJZ2IzYmRsUTVOb2RVK1dSVkkzSlRIOFNYS0o2czFSSExpTGVBMndhNUJnRjg9IiwidmVyaWZpZXIiOiJMUzB0TFMxQ1JVZEpUaUJEUlZKVVNVWkpRMEZVUlMwdExTMHRDazFKU1VoYWFrTkRRblY1WjBGM1NVSkJaMGxWUm5VelVFNWFZMWd4TXpaNlprNTFka2Q0YlZJdmRuRkdkemRyZDBObldVbExiMXBKZW1vd1JVRjNUWGNLVG5wRlZrMUNUVWRCTVZWRlEyaE5UV015Ykc1ak0xSjJZMjFWZFZwSFZqSk5ValIzU0VGWlJGWlJVVVJGZUZaNllWZGtlbVJIT1hsYVV6RndZbTVTYkFwamJURnNXa2RzYUdSSFZYZElhR05PVFdwVmQwNUVSVE5OUkdkM1RucFJlbGRvWTA1TmFsVjNUa1JGTTAxRVozaE9lbEY2VjJwQlFVMUdhM2RGZDFsSUNrdHZXa2w2YWpCRFFWRlpTVXR2V2tsNmFqQkVRVkZqUkZGblFVVnpaWEpPTTJaelNYbExTMmt4TjNoS1ZHOUhVVWRIUzFSTFEyWXhVMmM0T0VSRE1rd0tlakpwUjFVNGNEZE1hWG8wVEVsdWFGWm9TRVJWUkcxVVJEZHJka2RDTUcxSlZWYzRZMmgwZVdaaldrUnlkRnBKU25GUFEwSm5jM2RuWjFsSVRVRTBSd3BCTVZWa1JIZEZRaTkzVVVWQmQwbElaMFJCVkVKblRsWklVMVZGUkVSQlMwSm5aM0pDWjBWR1FsRmpSRUY2UVdSQ1owNVdTRkUwUlVablVWVjNiWFJzQ25wVUsxUlJXRGxwUjJKV2RYVnNZMUpIU1VwRGQwRlJkMGgzV1VSV1VqQnFRa0puZDBadlFWVXpPVkJ3ZWpGWmEwVmFZalZ4VG1wd1MwWlhhWGhwTkZrS1drUTRkMmRaVVVkQk1WVmtSVkZGUWk5M1VqWk5TR2xIWkcxb01HUklRbnBQYVRoMldqSnNNR0ZJVm1sTWJVNTJZbE01ZW1KSVRtaE1WMXA1V1ZjeGJBcGtNamw1WVhrNWVtSklUbWhNVjJSd1pFZG9NVmxwTVc1YVZ6VnNZMjFHTUdJelNYWk1iV1J3WkVkb01WbHBPVE5pTTBweVdtMTRkbVF6VFhaYU1sWjFDbHBZU21oa1J6bDVXREprYkdKdFZubGhWMDVtWXpKNGVsbFVUWFZsVnpGelVVaEtiRnB1VFhaa1IwWnVZM2s1TWsxcE5IaE1ha0YzVDFGWlMwdDNXVUlLUWtGSFJIWjZRVUpCVVZGeVlVaFNNR05JVFRaTWVUa3dZakowYkdKcE5XaFpNMUp3WWpJMWVreHRaSEJrUjJneFdXNVdlbHBZU21waU1qVXdXbGMxTUFwTWJVNTJZbFJCVjBKbmIzSkNaMFZGUVZsUEwwMUJSVU5DUVdoNldUSm9iRnBJVm5OYVZFRXlRbWR2Y2tKblJVVkJXVTh2VFVGRlJFSkRaekphYlZWNENrOUVVVFJQUkZsNVQwUmplVTFFUVhsTmVrMHhXVzFaZUUxSFNYZE5SMHBwVFVSUmVFNVhSVE5PTWtrMFRYcENhazFDYTBkRGFYTkhRVkZSUW1jM09IY0tRVkZSUlVNeFFubGFVekZUV2xkNGJGbFlUbXhOUkZWSFEybHpSMEZSVVVKbk56aDNRVkZWUlVveVJqTmplVEYzWWpOa2JHTnVVblppTW5oNlRETkNkZ3BrTWxaNVpFYzVkbUpJVFhSaVIwWjBXVzFTYUV4WVFqVmtSMmgyWW1wQlowSm5iM0pDWjBWRlFWbFBMMDFCUlVkQ1FrcDVXbGRhZWt3eWFHeFpWMUo2Q2t3eVVteGtiVlp6WWpOQmQwOTNXVXRMZDFsQ1FrRkhSSFo2UVVKRFFWRjBSRU4wYjJSSVVuZGplbTkyVEROU2RtRXlWblZNYlVacVpFZHNkbUp1VFhVS1dqSnNNR0ZJVm1sa1dFNXNZMjFPZG1KdVVteGlibEYxV1RJNWRFMUpSMGRDWjI5eVFtZEZSVUZaVHk5TlFVVktRa2huVFdSdGFEQmtTRUo2VDJrNGRncGFNbXd3WVVoV2FVeHRUblppVXpsNllraE9hRXhYV25sWlZ6RnNaREk1ZVdGNU9YcGlTRTVvVEZka2NHUkhhREZaYVRGdVdsYzFiR050UmpCaU0wbDJDa3h0WkhCa1IyZ3hXV2s1TTJJelNuSmFiWGgyWkROTmRsb3lWblZhV0Vwb1pFYzVlVmd5Wkd4aWJWWjVZVmRPWm1NeWVIcFpWRTExWlZjeGMxRklTbXdLV201TmRtUkhSbTVqZVRreVRXazBlRXhxUVhkUFFWbExTM2RaUWtKQlIwUjJla0ZDUTJkUmNVUkRhRzFPTWxKclQwZE5NVTVIVFhsTlJGa3pXVzFHYlFwWmVrVjVXVEpGTTFsVVZURk9WR3N4V2tSV2JGcFViR2xPZWxWNVRVUlNhRTFDTUVkRGFYTkhRVkZSUW1jM09IZEJVWE5GUkhkM1Rsb3liREJoU0ZacENreFhhSFpqTTFKc1drUkNTMEpuYjNKQ1owVkZRVmxQTDAxQlJVMUNSSGROVDIxb01HUklRbnBQYVRoMldqSnNNR0ZJVm1sTWJVNTJZbE01YUdRelRYUUtZMGM1TTFwWVNqQmlNamx6WTNrNWQySXpaR3hqYmxKMllqSjRla3hYZUdoaVYwcHJXVk14ZDJWWVVtOWlNalIzVDBGWlMwdDNXVUpDUVVkRWRucEJRZ3BFVVZGeFJFTm5NbHB0VlhoUFJGRTBUMFJaZVU5RVkzbE5SRUY1VFhwTk1WbHRXWGhOUjBsM1RVZEthVTFFVVhoT1YwVXpUakpKTkUxNlFtcE5RMGxIQ2tOcGMwZEJVVkZDWnpjNGQwRlJORVZHUVhkVFkyMVdiV041T1c5YVYwWnJZM2s1YTFwWVdteGlSemwzVFVKclIwTnBjMGRCVVZGQ1p6YzRkMEZST0VVS1EzZDNTazFxU1hoUFZFVTFUWHBqTlUxRVJVZERhWE5IUVZGUlFtYzNPSGRCVWtGRlNYZDNhR0ZJVWpCalNFMDJUSGs1Ym1GWVVtOWtWMGwxV1RJNWRBcE1Na1l6WTNreGQySXpaR3hqYmxKMllqSjRlazFDYTBkRGFYTkhRVkZSUW1jM09IZEJVa1ZGUTNkM1NrMVVTVFZOVkVrelRtcE5ORTFJT0VkRGFYTkhDa0ZSVVVKbk56aDNRVkpKUldOUmVIWmhTRkl3WTBoTk5reDVPVzVoV0ZKdlpGZEpkVmt5T1hSTU1rWXpZM2t4ZDJJelpHeGpibEoyWWpKNGVrd3pRbllLWkRKV2VXUkhPWFppU0UxMFlrZEdkRmx0VW1oTVdFSTFaRWRvZG1KcE9IVmFNbXd3WVVoV2FVd3paSFpqYlhSdFlrYzVNMk41T1hkamJWVjBZMjFXY3dwYVYwWjZXbE0xTldKWGVFRmpiVlp0WTNrNWIxcFhSbXRqZVRscldsaGFiR0pIT1hkTlJHZEhRMmx6UjBGUlVVSm5OemgzUVZKTlJVdG5kMjlPYlZwc0NrMVVaekJQUkdjeVRXcG5NMDFxUVhkTmFrMTZUbGRLYlUxVVFtbE5SRUpwV1dwQk1FMVVWbWhPZW1ScFQwUk5kMWw2UVZsQ1oyOXlRbWRGUlVGWlR5OEtUVUZGVlVKQmIwMURTRTVxWVVkV2EyUlhlR3hOUnpSSFEybHpSMEZSVVVKbk56aDNRVkpWUlZsQmVHVmhTRkl3WTBoTk5reDVPVzVoV0ZKdlpGZEpkUXBaTWpsMFRESkdNMk41TVhkaU0yUnNZMjVTZG1JeWVIcE1NMEoyWkRKV2VXUkhPWFppU0UxMFlrZEdkRmx0VW1oTVdFSTFaRWRvZG1KcE9XaFpNMUp3Q21JeU5YcE1NMG94WW01TmRrMVVVVEZOVkVFMVRucEZNazFFWjNaWldGSXdXbGN4ZDJSSVRYWk5WRUZYUW1kdmNrSm5SVVZCV1U4dlRVRkZWMEpCWjAwS1FtNUNNVmx0ZUhCWmVrTkNhV2RaUzB0M1dVSkNRVWhYWlZGSlJVRm5VamhDU0c5QlpVRkNNa0ZPTURsTlIzSkhlSGhGZVZsNGEyVklTbXh1VG5kTGFRcFRiRFkwTTJwNWRDODBaVXRqYjBGMlMyVTJUMEZCUVVKc2EweExSMjVaUVVGQlVVUkJSV04zVWxGSmFFRk9NeTk0VFZVekwwWnJiVXQwVm5kalUxcGlDbUUyVTNGeGVVRlhPRVoxVlVFNFJtcHlSRXd2ZGxwa2FVRnBRamxTVHpsUlNWUnVjMFZpUzFGQmVqTmFXRUZzTlRsdE1WZDFaVkJXTkhBNFdYRTFZbElLWTFBMVFVaFVRVXRDWjJkeGFHdHFUMUJSVVVSQmQwNXZRVVJDYkVGcVFUQnFNazFWUlhvek5XbzRjRXBCTWl0TE5uSkRiVmxSU21jMVVtWkllVzkwV2dvMGMxSkJXbFY1Y21wcFozRlJaRFI2ZWxjdlZWcDNUMVJMVFZkUVprdHpRMDFSUkZSTFZUbDZNRWxUWlZoSGJXTm9TRE12Um1Wek5VWlJTa3BzYjJ0aENrNTBLekJDYzIxNEsydFNiR2x2TVRKMGJGSmxhRlJ5ZEVkUk5FaGhWekZvSzFCblBRb3RMUzB0TFVWT1JDQkRSVkpVU1VaSlEwRlVSUzB0TFMwdENnPT0ifV19fQ=="}]}, "dsseEnvelope":{"payload":"eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjAuMSIsInByZWRpY2F0ZVR5cGUiOiJodHRwczovL3Nsc2EuZGV2L3Byb3ZlbmFuY2UvdjAuMiIsInN1YmplY3QiOlt7Im5hbWUiOiIuL2F3c19sYW1iZGFfcG93ZXJ0b29scy0zLjEwLjFhNi1weTMtbm9uZS1hbnkud2hsIiwiZGlnZXN0Ijp7InNoYTI1NiI6IjM3NTllMTVkNWFlOTMzYjllNDQzN2Q4YjU3YjQxYTg3NTE2YTY2NzZkYjBiMDk4MGY1OGRjYWQyOTZlYTNmY2MifX0seyJuYW1lIjoiLi9hd3NfbGFtYmRhX3Bvd2VydG9vbHMtMy4xMC4xYTYudGFyLmd6IiwiZGlnZXN0Ijp7InNoYTI1NiI6IjE4YjgxMDQ2MjZhZmJjNGY4ZmE4NjU3YmZiZDQxZmZhODAzNDYyZTFmZjFlZDEyODg3ZDVkZTEyODNhMjNhYmUifX1dLCJwcmVkaWNhdGUiOnsiYnVpbGRlciI6eyJpZCI6Imh0dHBzOi8vZ2l0aHViLmNvbS9zbHNhLWZyYW1ld29yay9zbHNhLWdpdGh1Yi1nZW5lcmF0b3IvLmdpdGh1Yi93b3JrZmxvd3MvZ2VuZXJhdG9yX2dlbmVyaWNfc2xzYTMueW1sQHJlZnMvdGFncy92Mi4xLjAifSwiYnVpbGRUeXBlIjoiaHR0cHM6Ly9naXRodWIuY29tL3Nsc2EtZnJhbWV3b3JrL3Nsc2EtZ2l0aHViLWdlbmVyYXRvci9nZW5lcmljQHYxIiwiaW52b2NhdGlvbiI6eyJjb25maWdTb3VyY2UiOnsidXJpIjoiZ2l0K2h0dHBzOi8vZ2l0aHViLmNvbS9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob25AcmVmcy9oZWFkcy9kZXZlbG9wIiwiZGlnZXN0Ijp7InNoYTEiOiI2ZmUxODQ4ODYyODcyMDAyMzM1YmYxMGIwMGJiMDQxNWE3N2I4MzBjIn0sImVudHJ5UG9pbnQiOiIuZ2l0aHViL3dvcmtmbG93cy9wcmUtcmVsZWFzZS55bWwifSwicGFyYW1ldGVycyI6eyJ2YXJzIjp7fX0sImVudmlyb25tZW50Ijp7ImdpdGh1Yl9hY3RvciI6ImxlYW5kcm9kYW1hc2NlbmEiLCJnaXRodWJfYWN0b3JfaWQiOiI0Mjk1MTczIiwiZ2l0aHViX2Jhc2VfcmVmIjoiIiwiZ2l0aHViX2V2ZW50X25hbWUiOiJzY2hlZHVsZSIsImdpdGh1Yl9ldmVudF9wYXlsb2FkIjp7ImVudGVycHJpc2UiOnsiYXZhdGFyX3VybCI6Imh0dHBzOi8vYXZhdGFycy5naXRodWJ1c2VyY29udGVudC5jb20vYi8xMjkwP3Y9NCIsImNyZWF0ZWRfYXQiOiIyMDE5LTExLTEzVDE4OjA1OjQxWiIsImRlc2NyaXB0aW9uIjoiIiwiaHRtbF91cmwiOiJodHRwczovL2dpdGh1Yi5jb20vZW50ZXJwcmlzZXMvYW1hem9uIiwiaWQiOjEyOTAsIm5hbWUiOiJBbWF6b24iLCJub2RlX2lkIjoiTURFd09rVnVkR1Z5Y0hKcGMyVXhNamt3Iiwic2x1ZyI6ImFtYXpvbiIsInVwZGF0ZWRfYXQiOiIyMDI0LTA5LTMwVDIxOjAyOjMwWiIsIndlYnNpdGVfdXJsIjoiaHR0cHM6Ly93d3cuYW1hem9uLmNvbS8ifSwib3JnYW5pemF0aW9uIjp7ImF2YXRhcl91cmwiOiJodHRwczovL2F2YXRhcnMuZ2l0aHVidXNlcmNvbnRlbnQuY29tL3UvMTI5MTI3NjM4P3Y9NCIsImRlc2NyaXB0aW9uIjoiIiwiZXZlbnRzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vb3Jncy9hd3MtcG93ZXJ0b29scy9ldmVudHMiLCJob29rc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL29yZ3MvYXdzLXBvd2VydG9vbHMvaG9va3MiLCJpZCI6MTI5MTI3NjM4LCJpc3N1ZXNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9vcmdzL2F3cy1wb3dlcnRvb2xzL2lzc3VlcyIsImxvZ2luIjoiYXdzLXBvd2VydG9vbHMiLCJtZW1iZXJzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vb3Jncy9hd3MtcG93ZXJ0b29scy9tZW1iZXJzey9tZW1iZXJ9Iiwibm9kZV9pZCI6Ik9fa2dET0I3SlUxZyIsInB1YmxpY19tZW1iZXJzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vb3Jncy9hd3MtcG93ZXJ0b29scy9wdWJsaWNfbWVtYmVyc3svbWVtYmVyfSIsInJlcG9zX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vb3Jncy9hd3MtcG93ZXJ0b29scy9yZXBvcyIsInVybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vb3Jncy9hd3MtcG93ZXJ0b29scyJ9LCJyZXBvc2l0b3J5Ijp7ImFsbG93X2ZvcmtpbmciOnRydWUsImFyY2hpdmVfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24ve2FyY2hpdmVfZm9ybWF0fXsvcmVmfSIsImFyY2hpdmVkIjpmYWxzZSwiYXNzaWduZWVzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2Fzc2lnbmVlc3svdXNlcn0iLCJibG9ic191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9naXQvYmxvYnN7L3NoYX0iLCJicmFuY2hlc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9icmFuY2hlc3svYnJhbmNofSIsImNsb25lX3VybCI6Imh0dHBzOi8vZ2l0aHViLmNvbS9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24uZ2l0IiwiY29sbGFib3JhdG9yc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9jb2xsYWJvcmF0b3Jzey9jb2xsYWJvcmF0b3J9IiwiY29tbWVudHNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vY29tbWVudHN7L251bWJlcn0iLCJjb21taXRzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2NvbW1pdHN7L3NoYX0iLCJjb21wYXJlX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2NvbXBhcmUve2Jhc2V9Li4ue2hlYWR9IiwiY29udGVudHNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vY29udGVudHMveytwYXRofSIsImNvbnRyaWJ1dG9yc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9jb250cmlidXRvcnMiLCJjcmVhdGVkX2F0IjoiMjAxOS0xMS0xNVQxMjoyNjoxMloiLCJjdXN0b21fcHJvcGVydGllcyI6e30sImRlZmF1bHRfYnJhbmNoIjoiZGV2ZWxvcCIsImRlcGxveW1lbnRzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2RlcGxveW1lbnRzIiwiZGVzY3JpcHRpb24iOiJBIGRldmVsb3BlciB0b29sa2l0IHRvIGltcGxlbWVudCBTZXJ2ZXJsZXNzIGJlc3QgcHJhY3RpY2VzIGFuZCBpbmNyZWFzZSBkZXZlbG9wZXIgdmVsb2NpdHkuIiwiZGlzYWJsZWQiOmZhbHNlLCJkb3dubG9hZHNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vZG93bmxvYWRzIiwiZXZlbnRzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2V2ZW50cyIsImZvcmsiOmZhbHNlLCJmb3JrcyI6NDE5LCJmb3Jrc19jb3VudCI6NDE5LCJmb3Jrc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9mb3JrcyIsImZ1bGxfbmFtZSI6ImF3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbiIsImdpdF9jb21taXRzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2dpdC9jb21taXRzey9zaGF9IiwiZ2l0X3JlZnNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vZ2l0L3JlZnN7L3NoYX0iLCJnaXRfdGFnc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9naXQvdGFnc3svc2hhfSIsImdpdF91cmwiOiJnaXQ6Ly9naXRodWIuY29tL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi5naXQiLCJoYXNfZGlzY3Vzc2lvbnMiOnRydWUsImhhc19kb3dubG9hZHMiOnRydWUsImhhc19pc3N1ZXMiOnRydWUsImhhc19wYWdlcyI6ZmFsc2UsImhhc19wcm9qZWN0cyI6dHJ1ZSwiaGFzX3dpa2kiOmZhbHNlLCJob21lcGFnZSI6Imh0dHBzOi8vZG9jcy5wb3dlcnRvb2xzLmF3cy5kZXYvbGFtYmRhL3B5dGhvbi9sYXRlc3QvIiwiaG9va3NfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vaG9va3MiLCJodG1sX3VybCI6Imh0dHBzOi8vZ2l0aHViLmNvbS9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24iLCJpZCI6MjIxOTE5Mzc5LCJpc190ZW1wbGF0ZSI6ZmFsc2UsImlzc3VlX2NvbW1lbnRfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vaXNzdWVzL2NvbW1lbnRzey9udW1iZXJ9IiwiaXNzdWVfZXZlbnRzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2lzc3Vlcy9ldmVudHN7L251bWJlcn0iLCJpc3N1ZXNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vaXNzdWVzey9udW1iZXJ9Iiwia2V5c191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9rZXlzey9rZXlfaWR9IiwibGFiZWxzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2xhYmVsc3svbmFtZX0iLCJsYW5ndWFnZSI6IlB5dGhvbiIsImxhbmd1YWdlc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9sYW5ndWFnZXMiLCJsaWNlbnNlIjp7ImtleSI6Im1pdC0wIiwibmFtZSI6Ik1JVCBObyBBdHRyaWJ1dGlvbiIsIm5vZGVfaWQiOiJNRGM2VEdsalpXNXpaVFF4Iiwic3BkeF9pZCI6Ik1JVC0wIiwidXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9saWNlbnNlcy9taXQtMCJ9LCJtZXJnZXNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vbWVyZ2VzIiwibWlsZXN0b25lc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9taWxlc3RvbmVzey9udW1iZXJ9IiwibWlycm9yX3VybCI6bnVsbCwibmFtZSI6InBvd2VydG9vbHMtbGFtYmRhLXB5dGhvbiIsIm5vZGVfaWQiOiJNREV3T2xKbGNHOXphWFJ2Y25reU1qRTVNVGt6TnprPSIsIm5vdGlmaWNhdGlvbnNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vbm90aWZpY2F0aW9uc3s/c2luY2UsYWxsLHBhcnRpY2lwYXRpbmd9Iiwib3Blbl9pc3N1ZXMiOjU1LCJvcGVuX2lzc3Vlc19jb3VudCI6NTUsIm93bmVyIjp7ImF2YXRhcl91cmwiOiJodHRwczovL2F2YXRhcnMuZ2l0aHVidXNlcmNvbnRlbnQuY29tL3UvMTI5MTI3NjM4P3Y9NCIsImV2ZW50c191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3VzZXJzL2F3cy1wb3dlcnRvb2xzL2V2ZW50c3svcHJpdmFjeX0iLCJmb2xsb3dlcnNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS91c2Vycy9hd3MtcG93ZXJ0b29scy9mb2xsb3dlcnMiLCJmb2xsb3dpbmdfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS91c2Vycy9hd3MtcG93ZXJ0b29scy9mb2xsb3dpbmd7L290aGVyX3VzZXJ9IiwiZ2lzdHNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS91c2Vycy9hd3MtcG93ZXJ0b29scy9naXN0c3svZ2lzdF9pZH0iLCJncmF2YXRhcl9pZCI6IiIsImh0bWxfdXJsIjoiaHR0cHM6Ly9naXRodWIuY29tL2F3cy1wb3dlcnRvb2xzIiwiaWQiOjEyOTEyNzYzOCwibG9naW4iOiJhd3MtcG93ZXJ0b29scyIsIm5vZGVfaWQiOiJPX2tnRE9CN0pVMWciLCJvcmdhbml6YXRpb25zX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vdXNlcnMvYXdzLXBvd2VydG9vbHMvb3JncyIsInJlY2VpdmVkX2V2ZW50c191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3VzZXJzL2F3cy1wb3dlcnRvb2xzL3JlY2VpdmVkX2V2ZW50cyIsInJlcG9zX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vdXNlcnMvYXdzLXBvd2VydG9vbHMvcmVwb3MiLCJzaXRlX2FkbWluIjpmYWxzZSwic3RhcnJlZF91cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3VzZXJzL2F3cy1wb3dlcnRvb2xzL3N0YXJyZWR7L293bmVyfXsvcmVwb30iLCJzdWJzY3JpcHRpb25zX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vdXNlcnMvYXdzLXBvd2VydG9vbHMvc3Vic2NyaXB0aW9ucyIsInR5cGUiOiJPcmdhbml6YXRpb24iLCJ1cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3VzZXJzL2F3cy1wb3dlcnRvb2xzIiwidXNlcl92aWV3X3R5cGUiOiJwdWJsaWMifSwicHJpdmF0ZSI6ZmFsc2UsInB1bGxzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL3B1bGxzey9udW1iZXJ9IiwicHVzaGVkX2F0IjoiMjAyNS0wNC0xN1QwNzo1Njo0OVoiLCJyZWxlYXNlc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9yZWxlYXNlc3svaWR9Iiwic2l6ZSI6MTA2ODE1LCJzc2hfdXJsIjoiZ2l0QGdpdGh1Yi5jb206YXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uLmdpdCIsInN0YXJnYXplcnNfY291bnQiOjMwMjIsInN0YXJnYXplcnNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vc3RhcmdhemVycyIsInN0YXR1c2VzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL3N0YXR1c2VzL3tzaGF9Iiwic3Vic2NyaWJlcnNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vc3Vic2NyaWJlcnMiLCJzdWJzY3JpcHRpb25fdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vc3Vic2NyaXB0aW9uIiwic3ZuX3VybCI6Imh0dHBzOi8vZ2l0aHViLmNvbS9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24iLCJ0YWdzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL3RhZ3MiLCJ0ZWFtc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi90ZWFtcyIsInRvcGljcyI6WyJhd3MiLCJhd3MtbGFtYmRhIiwiaGFja3RvYmVyZmVzdCIsImxhbWJkYSIsInB5dGhvbiIsInNlcnZlcmxlc3MiXSwidHJlZXNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vZ2l0L3RyZWVzey9zaGF9IiwidXBkYXRlZF9hdCI6IjIwMjUtMDQtMTdUMDc6NTQ6NDFaIiwidXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24iLCJ2aXNpYmlsaXR5IjoicHVibGljIiwid2F0Y2hlcnMiOjMwMjIsIndhdGNoZXJzX2NvdW50IjozMDIyLCJ3ZWJfY29tbWl0X3NpZ25vZmZfcmVxdWlyZWQiOnRydWV9LCJzY2hlZHVsZSI6IjAgOCAqICogMS01Iiwid29ya2Zsb3ciOiIuZ2l0aHViL3dvcmtmbG93cy9wcmUtcmVsZWFzZS55bWwifSwiZ2l0aHViX2hlYWRfcmVmIjoiIiwiZ2l0aHViX3JlZiI6InJlZnMvaGVhZHMvZGV2ZWxvcCIsImdpdGh1Yl9yZWZfdHlwZSI6ImJyYW5jaCIsImdpdGh1Yl9yZXBvc2l0b3J5X2lkIjoiMjIxOTE5Mzc5IiwiZ2l0aHViX3JlcG9zaXRvcnlfb3duZXIiOiJhd3MtcG93ZXJ0b29scyIsImdpdGh1Yl9yZXBvc2l0b3J5X293bmVyX2lkIjoiMTI5MTI3NjM4IiwiZ2l0aHViX3J1bl9hdHRlbXB0IjoiMSIsImdpdGh1Yl9ydW5faWQiOiIxNDUxMDk3MTYwOCIsImdpdGh1Yl9ydW5fbnVtYmVyIjoiMjIxIiwiZ2l0aHViX3NoYTEiOiI2ZmUxODQ4ODYyODcyMDAyMzM1YmYxMGIwMGJiMDQxNWE3N2I4MzBjIn19LCJtZXRhZGF0YSI6eyJidWlsZEludm9jYXRpb25JRCI6IjE0NTEwOTcxNjA4LTEiLCJjb21wbGV0ZW5lc3MiOnsicGFyYW1ldGVycyI6dHJ1ZSwiZW52aXJvbm1lbnQiOmZhbHNlLCJtYXRlcmlhbHMiOmZhbHNlfSwicmVwcm9kdWNpYmxlIjpmYWxzZX0sIm1hdGVyaWFscyI6W3sidXJpIjoiZ2l0K2h0dHBzOi8vZ2l0aHViLmNvbS9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob25AcmVmcy9oZWFkcy9kZXZlbG9wIiwiZGlnZXN0Ijp7InNoYTEiOiI2ZmUxODQ4ODYyODcyMDAyMzM1YmYxMGIwMGJiMDQxNWE3N2I4MzBjIn19XX19", "payloadType":"application/vnd.in-toto+json", "signatures":[{"sig":"MEUCIQDlRhH1hMDqCc6+C8MH1DwNjGDL4d32aYFCfkNAZWbMxQIgb3bdlQ5NodU+WRVI3JTH8SXKJ6s1RHLiLeA2wa5BgF8="}]}}
\ No newline at end of file
diff --git a/provenance/3.10.1a7/multiple.intoto.jsonl b/provenance/3.10.1a7/multiple.intoto.jsonl
new file mode 100644
index 00000000000..a627898b421
--- /dev/null
+++ b/provenance/3.10.1a7/multiple.intoto.jsonl
@@ -0,0 +1 @@
+{"mediaType":"application/vnd.dev.sigstore.bundle.v0.3+json", "verificationMaterial":{"certificate":{"rawBytes":"MIIHZTCCBuygAwIBAgIULjS62OEKTeggl0be+niO1PDAfHYwCgYIKoZIzj0EAwMwNzEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MR4wHAYDVQQDExVzaWdzdG9yZS1pbnRlcm1lZGlhdGUwHhcNMjUwNDE4MDgwNzI3WhcNMjUwNDE4MDgxNzI3WjAAMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEWXx37CLL7MKIest332m3wAZNXqe0vd9num0rzuIrE58igwQpXOwXBS/2B4tWSSb30CQ6mE4xeq2EIAd7htrYZ6OCBgswggYHMA4GA1UdDwEB/wQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcDAzAdBgNVHQ4EFgQUlLAVPInOChTCixgiOPL7v1RcyRowHwYDVR0jBBgwFoAU39Ppz1YkEZb5qNjpKFWixi4YZD8wgYQGA1UdEQEB/wR6MHiGdmh0dHBzOi8vZ2l0aHViLmNvbS9zbHNhLWZyYW1ld29yay9zbHNhLWdpdGh1Yi1nZW5lcmF0b3IvLmdpdGh1Yi93b3JrZmxvd3MvZ2VuZXJhdG9yX2dlbmVyaWNfc2xzYTMueW1sQHJlZnMvdGFncy92Mi4xLjAwOQYKKwYBBAGDvzABAQQraHR0cHM6Ly90b2tlbi5hY3Rpb25zLmdpdGh1YnVzZXJjb250ZW50LmNvbTAWBgorBgEEAYO/MAECBAhzY2hlZHVsZTA2BgorBgEEAYO/MAEDBCgwNGZlZjM0ZmUwYjMzZjUwMTExZTJkOGE4ZGIxYTUzMzg5YzZmY2RjMBkGCisGAQQBg78wAQQEC1ByZS1SZWxlYXNlMDUGCisGAQQBg78wAQUEJ2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbjAgBgorBgEEAYO/MAEGBBJyZWZzL2hlYWRzL2RldmVsb3AwOwYKKwYBBAGDvzABCAQtDCtodHRwczovL3Rva2VuLmFjdGlvbnMuZ2l0aHVidXNlcmNvbnRlbnQuY29tMIGGBgorBgEEAYO/MAEJBHgMdmh0dHBzOi8vZ2l0aHViLmNvbS9zbHNhLWZyYW1ld29yay9zbHNhLWdpdGh1Yi1nZW5lcmF0b3IvLmdpdGh1Yi93b3JrZmxvd3MvZ2VuZXJhdG9yX2dlbmVyaWNfc2xzYTMueW1sQHJlZnMvdGFncy92Mi4xLjAwOAYKKwYBBAGDvzABCgQqDChmN2RkOGM1NGMyMDY3YmFmYzEyY2E3YTU1NTk1ZDVlZTliNzUyMDRhMB0GCisGAQQBg78wAQsEDwwNZ2l0aHViLWhvc3RlZDBKBgorBgEEAYO/MAEMBDwMOmh0dHBzOi8vZ2l0aHViLmNvbS9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24wOAYKKwYBBAGDvzABDQQqDCgwNGZlZjM0ZmUwYjMzZjUwMTExZTJkOGE4ZGIxYTUzMzg5YzZmY2RjMCIGCisGAQQBg78wAQ4EFAwScmVmcy9oZWFkcy9kZXZlbG9wMBkGCisGAQQBg78wAQ8ECwwJMjIxOTE5Mzc5MDEGCisGAQQBg78wARAEIwwhaHR0cHM6Ly9naXRodWIuY29tL2F3cy1wb3dlcnRvb2xzMBkGCisGAQQBg78wAREECwwJMTI5MTI3NjM4MH8GCisGAQQBg78wARIEcQxvaHR0cHM6Ly9naXRodWIuY29tL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi8uZ2l0aHViL3dvcmtmbG93cy9wcmUtcmVsZWFzZS55bWxAcmVmcy9oZWFkcy9kZXZlbG9wMDgGCisGAQQBg78wARMEKgwoMDRmZWYzNGZlMGIzM2Y1MDExMWUyZDhhOGRiMWE1MzM4OWM2ZmNkYzAYBgorBgEEAYO/MAEUBAoMCHNjaGVkdWxlMG4GCisGAQQBg78wARUEYAxeaHR0cHM6Ly9naXRodWIuY29tL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9hY3Rpb25zL3J1bnMvMTQ1MzE5NDAxNTAvYXR0ZW1wdHMvMTAWBgorBgEEAYO/MAEWBAgMBnB1YmxpYzCBigYKKwYBBAHWeQIEAgR8BHoAeAB2AN09MGrGxxEyYxkeHJlnNwKiSl643jyt/4eKcoAvKe6OAAABlkfwNqoAAAQDAEcwRQIgHeOnqPWp7fBX1MD6cs9xKQmuCrx8o3Pcn4C5l2wHXOsCIQDh6rb35LAXO4Opjf37Xi+KSVC0Di6jhMcU9KRpqPJKbjAKBggqhkjOPQQDAwNnADBkAjA5ZAWJOvzVfzQr8FW7ZCVRJdJtc7AtXWfJWM/89Lz7czReK7mq8c3mkEc7nsqLV4UCMEtw9UlBqnT3w7ahQ7FFJskTED0fMBjxK75QmBBO8lNR12b+zUd3OZ2dbydil/Tvgg=="}, "tlogEntries":[{"logIndex":"199123688", "logId":{"keyId":"wNI9atQGlz+VWfO6LRygH4QUfY/8W4RFwiT5i5WRgB0="}, "kindVersion":{"kind":"dsse", "version":"0.0.1"}, "integratedTime":"1744963647", "inclusionPromise":{"signedEntryTimestamp":"MEUCIQCCzSHt1IelN7fH8BPr3/n+2MgYEcskCPaeIRu0d2wTagIgLzF+eBJNAHRu0CrmMnqMls9+b4TVqdsokkuDafvYQyg="}, "inclusionProof":{"logIndex":"77219426", "rootHash":"wX4MrYg07rJ0pAVrvb1vF8BcpMxeOAStyJ/5rYvmUlg=", "treeSize":"77219429", "hashes":["XykS+LfcRlkQAgrBojyYHEyFbfW7fLa1oEgSpTrTrJg=", "qEjL26UVoZhC0ozi+3QwYO0OcSkqMaqwNNqV2TEXeBI=", "ZivBu4gls29qzZ5KeGqvzzxGmvEMdxEDt/wkVeWfcEU=", "Am+yG4b3Y0/V02EYKJ20gHETdZ8iOK+Fc9SRQygPSK0=", "QUJ4l6I2xBlKAkpey8/op1K5kaccsq2vmuj6XfLnKRo=", "/9KCMYGhGTwN0JxwSUaXVLbEEKRn4PyjdQ4Ta7KVlwA=", "XmYyyW076TxwRyN9ULPgVQwhvmqdWmE+n9jB2wjCDR4=", "2YpiRTa6coD2Cq/+px0ybThKbp88JuNciuSz+R3aks0=", "TpYCwsY1w+zkQT/F/+Opr5PtrYnAWnrBUQqZEESV8U0=", "3/03iGdNTy6E9agUn4cO1RZTS91SN+DouTvqmNSOmsY=", "jd3slat1ytl3aw6K5AlZ51XmaFuVSdipdfKvSzzcrSU=", "gGNvqHSiyarbPiEG0lmBLLIhU2F6djF/wmlcFeaQdP8=", "7v8qPHNDLerpduaMx06eb/MwgoQwczTn/cYGKX/9wZ4="], "checkpoint":{"envelope":"rekor.sigstore.dev - 1193050959916656506\n77219429\nwX4MrYg07rJ0pAVrvb1vF8BcpMxeOAStyJ/5rYvmUlg=\n\n— rekor.sigstore.dev wNI9ajBEAiBPc4iXwQw1H364heQefQiu+SV918TzMdTkPkoyZ3bdkAIgasZkU5notrL9FhVukR3QKTkOysIR+ovHqFtFy1ICveU=\n"}}, "canonicalizedBody":"eyJhcGlWZXJzaW9uIjoiMC4wLjEiLCJraW5kIjoiZHNzZSIsInNwZWMiOnsiZW52ZWxvcGVIYXNoIjp7ImFsZ29yaXRobSI6InNoYTI1NiIsInZhbHVlIjoiOWZlOTZiNmMxNDY2YWIwM2Q0YjQxYmYyNjgxNjg1MWM3OGUxMDg5YWNmMzY4YTQ2NjM3YTg0Njc2NDM3ZmE4OSJ9LCJwYXlsb2FkSGFzaCI6eyJhbGdvcml0aG0iOiJzaGEyNTYiLCJ2YWx1ZSI6ImY0YjZjNWVmZjllOThmYjUzM2Q1ZjYwMmVmMmJjZGEyM2M2NTZhNzYzMDliNzI0NWVjODk2YjU2YjY5M2RjNWEifSwic2lnbmF0dXJlcyI6W3sic2lnbmF0dXJlIjoiTUVZQ0lRQ2tzaUtQNXZWcHRvVzAzQlBGTE8rUzB5YnlYeXhic2gwSDliZ2s1cWVpblFJaEFLaGZydXRlU0doeXBVTG9BcjhpZ1k5QWxvcnBCVENHdHNyUjZzQVNHYitXIiwidmVyaWZpZXIiOiJMUzB0TFMxQ1JVZEpUaUJEUlZKVVNVWkpRMEZVUlMwdExTMHRDazFKU1VoYVZFTkRRblY1WjBGM1NVSkJaMGxWVEdwVE5qSlBSVXRVWldkbmJEQmlaU3R1YVU4eFVFUkJaa2haZDBObldVbExiMXBKZW1vd1JVRjNUWGNLVG5wRlZrMUNUVWRCTVZWRlEyaE5UV015Ykc1ak0xSjJZMjFWZFZwSFZqSk5ValIzU0VGWlJGWlJVVVJGZUZaNllWZGtlbVJIT1hsYVV6RndZbTVTYkFwamJURnNXa2RzYUdSSFZYZElhR05PVFdwVmQwNUVSVFJOUkdkM1RucEpNMWRvWTA1TmFsVjNUa1JGTkUxRVozaE9la2t6VjJwQlFVMUdhM2RGZDFsSUNrdHZXa2w2YWpCRFFWRlpTVXR2V2tsNmFqQkVRVkZqUkZGblFVVlhXSGd6TjBOTVREZE5TMGxsYzNRek16SnRNM2RCV2s1WWNXVXdkbVE1Ym5WdE1ISUtlblZKY2tVMU9HbG5kMUZ3V0U5M1dFSlRMekpDTkhSWFUxTmlNekJEVVRadFJUUjRaWEV5UlVsQlpEZG9kSEpaV2paUFEwSm5jM2RuWjFsSVRVRTBSd3BCTVZWa1JIZEZRaTkzVVVWQmQwbElaMFJCVkVKblRsWklVMVZGUkVSQlMwSm5aM0pDWjBWR1FsRmpSRUY2UVdSQ1owNVdTRkUwUlVablVWVnNURUZXQ2xCSmJrOURhRlJEYVhobmFVOVFURGQyTVZKamVWSnZkMGgzV1VSV1VqQnFRa0puZDBadlFWVXpPVkJ3ZWpGWmEwVmFZalZ4VG1wd1MwWlhhWGhwTkZrS1drUTRkMmRaVVVkQk1WVmtSVkZGUWk5M1VqWk5TR2xIWkcxb01HUklRbnBQYVRoMldqSnNNR0ZJVm1sTWJVNTJZbE01ZW1KSVRtaE1WMXA1V1ZjeGJBcGtNamw1WVhrNWVtSklUbWhNVjJSd1pFZG9NVmxwTVc1YVZ6VnNZMjFHTUdJelNYWk1iV1J3WkVkb01WbHBPVE5pTTBweVdtMTRkbVF6VFhaYU1sWjFDbHBZU21oa1J6bDVXREprYkdKdFZubGhWMDVtWXpKNGVsbFVUWFZsVnpGelVVaEtiRnB1VFhaa1IwWnVZM2s1TWsxcE5IaE1ha0YzVDFGWlMwdDNXVUlLUWtGSFJIWjZRVUpCVVZGeVlVaFNNR05JVFRaTWVUa3dZakowYkdKcE5XaFpNMUp3WWpJMWVreHRaSEJrUjJneFdXNVdlbHBZU21waU1qVXdXbGMxTUFwTWJVNTJZbFJCVjBKbmIzSkNaMFZGUVZsUEwwMUJSVU5DUVdoNldUSm9iRnBJVm5OYVZFRXlRbWR2Y2tKblJVVkJXVTh2VFVGRlJFSkRaM2RPUjFwc0NscHFUVEJhYlZWM1dXcE5lbHBxVlhkTlZFVjRXbFJLYTA5SFJUUmFSMGw0V1ZSVmVrMTZaelZaZWxwdFdUSlNhazFDYTBkRGFYTkhRVkZSUW1jM09IY0tRVkZSUlVNeFFubGFVekZUV2xkNGJGbFlUbXhOUkZWSFEybHpSMEZSVVVKbk56aDNRVkZWUlVveVJqTmplVEYzWWpOa2JHTnVVblppTW5oNlRETkNkZ3BrTWxaNVpFYzVkbUpJVFhSaVIwWjBXVzFTYUV4WVFqVmtSMmgyWW1wQlowSm5iM0pDWjBWRlFWbFBMMDFCUlVkQ1FrcDVXbGRhZWt3eWFHeFpWMUo2Q2t3eVVteGtiVlp6WWpOQmQwOTNXVXRMZDFsQ1FrRkhSSFo2UVVKRFFWRjBSRU4wYjJSSVVuZGplbTkyVEROU2RtRXlWblZNYlVacVpFZHNkbUp1VFhVS1dqSnNNR0ZJVm1sa1dFNXNZMjFPZG1KdVVteGlibEYxV1RJNWRFMUpSMGRDWjI5eVFtZEZSVUZaVHk5TlFVVktRa2huVFdSdGFEQmtTRUo2VDJrNGRncGFNbXd3WVVoV2FVeHRUblppVXpsNllraE9hRXhYV25sWlZ6RnNaREk1ZVdGNU9YcGlTRTVvVEZka2NHUkhhREZaYVRGdVdsYzFiR050UmpCaU0wbDJDa3h0WkhCa1IyZ3hXV2s1TTJJelNuSmFiWGgyWkROTmRsb3lWblZhV0Vwb1pFYzVlVmd5Wkd4aWJWWjVZVmRPWm1NeWVIcFpWRTExWlZjeGMxRklTbXdLV201TmRtUkhSbTVqZVRreVRXazBlRXhxUVhkUFFWbExTM2RaUWtKQlIwUjJla0ZDUTJkUmNVUkRhRzFPTWxKclQwZE5NVTVIVFhsTlJGa3pXVzFHYlFwWmVrVjVXVEpGTTFsVVZURk9WR3N4V2tSV2JGcFViR2xPZWxWNVRVUlNhRTFDTUVkRGFYTkhRVkZSUW1jM09IZEJVWE5GUkhkM1Rsb3liREJoU0ZacENreFhhSFpqTTFKc1drUkNTMEpuYjNKQ1owVkZRVmxQTDAxQlJVMUNSSGROVDIxb01HUklRbnBQYVRoMldqSnNNR0ZJVm1sTWJVNTJZbE01YUdRelRYUUtZMGM1TTFwWVNqQmlNamx6WTNrNWQySXpaR3hqYmxKMllqSjRla3hYZUdoaVYwcHJXVk14ZDJWWVVtOWlNalIzVDBGWlMwdDNXVUpDUVVkRWRucEJRZ3BFVVZGeFJFTm5kMDVIV214YWFrMHdXbTFWZDFscVRYcGFhbFYzVFZSRmVGcFVTbXRQUjBVMFdrZEplRmxVVlhwTmVtYzFXWHBhYlZreVVtcE5RMGxIQ2tOcGMwZEJVVkZDWnpjNGQwRlJORVZHUVhkVFkyMVdiV041T1c5YVYwWnJZM2s1YTFwWVdteGlSemwzVFVKclIwTnBjMGRCVVZGQ1p6YzRkMEZST0VVS1EzZDNTazFxU1hoUFZFVTFUWHBqTlUxRVJVZERhWE5IUVZGUlFtYzNPSGRCVWtGRlNYZDNhR0ZJVWpCalNFMDJUSGs1Ym1GWVVtOWtWMGwxV1RJNWRBcE1Na1l6WTNreGQySXpaR3hqYmxKMllqSjRlazFDYTBkRGFYTkhRVkZSUW1jM09IZEJVa1ZGUTNkM1NrMVVTVFZOVkVrelRtcE5ORTFJT0VkRGFYTkhDa0ZSVVVKbk56aDNRVkpKUldOUmVIWmhTRkl3WTBoTk5reDVPVzVoV0ZKdlpGZEpkVmt5T1hSTU1rWXpZM2t4ZDJJelpHeGpibEoyWWpKNGVrd3pRbllLWkRKV2VXUkhPWFppU0UxMFlrZEdkRmx0VW1oTVdFSTFaRWRvZG1KcE9IVmFNbXd3WVVoV2FVd3paSFpqYlhSdFlrYzVNMk41T1hkamJWVjBZMjFXY3dwYVYwWjZXbE0xTldKWGVFRmpiVlp0WTNrNWIxcFhSbXRqZVRscldsaGFiR0pIT1hkTlJHZEhRMmx6UjBGUlVVSm5OemgzUVZKTlJVdG5kMjlOUkZKdENscFhXWHBPUjFwc1RVZEplazB5V1RGTlJFVjRUVmRWZVZwRWFHaFBSMUpwVFZkRk1VMTZUVFJQVjAweVdtMU9hMWw2UVZsQ1oyOXlRbWRGUlVGWlR5OEtUVUZGVlVKQmIwMURTRTVxWVVkV2EyUlhlR3hOUnpSSFEybHpSMEZSVVVKbk56aDNRVkpWUlZsQmVHVmhTRkl3WTBoTk5reDVPVzVoV0ZKdlpGZEpkUXBaTWpsMFRESkdNMk41TVhkaU0yUnNZMjVTZG1JeWVIcE1NMEoyWkRKV2VXUkhPWFppU0UxMFlrZEdkRmx0VW1oTVdFSTFaRWRvZG1KcE9XaFpNMUp3Q21JeU5YcE1NMG94WW01TmRrMVVVVEZOZWtVMVRrUkJlRTVVUVhaWldGSXdXbGN4ZDJSSVRYWk5WRUZYUW1kdmNrSm5SVVZCV1U4dlRVRkZWMEpCWjAwS1FtNUNNVmx0ZUhCWmVrTkNhV2RaUzB0M1dVSkNRVWhYWlZGSlJVRm5VamhDU0c5QlpVRkNNa0ZPTURsTlIzSkhlSGhGZVZsNGEyVklTbXh1VG5kTGFRcFRiRFkwTTJwNWRDODBaVXRqYjBGMlMyVTJUMEZCUVVKc2EyWjNUbkZ2UVVGQlVVUkJSV04zVWxGSlowaGxUMjV4VUZkd04yWkNXREZOUkRaamN6bDRDa3RSYlhWRGNuZzRiek5RWTI0MFF6VnNNbmRJV0U5elEwbFJSR2cyY21Jek5VeEJXRTgwVDNCcVpqTTNXR2tyUzFOV1F6QkVhVFpxYUUxalZUbExVbkFLY1ZCS1MySnFRVXRDWjJkeGFHdHFUMUJSVVVSQmQwNXVRVVJDYTBGcVFUVmFRVmRLVDNaNlZtWjZVWEk0UmxjM1drTldVa3BrU25Sak4wRjBXRmRtU2dwWFRTODRPVXg2TjJONlVtVkxOMjF4T0dNemJXdEZZemR1YzNGTVZqUlZRMDFGZEhjNVZXeENjVzVVTTNjM1lXaFJOMFpHU25OclZFVkVNR1pOUW1wNENrczNOVkZ0UWtKUE9HeE9VakV5WWl0NlZXUXpUMW95WkdKNVpHbHNMMVIyWjJjOVBRb3RMUzB0TFVWT1JDQkRSVkpVU1VaSlEwRlVSUzB0TFMwdENnPT0ifV19fQ=="}]}, "dsseEnvelope":{"payload":"eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjAuMSIsInByZWRpY2F0ZVR5cGUiOiJodHRwczovL3Nsc2EuZGV2L3Byb3ZlbmFuY2UvdjAuMiIsInN1YmplY3QiOlt7Im5hbWUiOiIuL2F3c19sYW1iZGFfcG93ZXJ0b29scy0zLjEwLjFhNy1weTMtbm9uZS1hbnkud2hsIiwiZGlnZXN0Ijp7InNoYTI1NiI6IjllNWU1MmU3N2Q2NmQ3NDc3MDVmZDRlM2M2OTRkYzM3NzJhN2ViZjQyMGU4N2ExYWZlMWE2MWUwMmIwNzNkMmIifX0seyJuYW1lIjoiLi9hd3NfbGFtYmRhX3Bvd2VydG9vbHMtMy4xMC4xYTcudGFyLmd6IiwiZGlnZXN0Ijp7InNoYTI1NiI6ImE1ZDI1Y2EwMzZjYzYyOTBjZmQzODM2ZmY4ZWRjYjRhMWU1YjY2NDIwN2JlMTE4ZjFjNDFkMjRhZWFmZTg2NjIifX1dLCJwcmVkaWNhdGUiOnsiYnVpbGRlciI6eyJpZCI6Imh0dHBzOi8vZ2l0aHViLmNvbS9zbHNhLWZyYW1ld29yay9zbHNhLWdpdGh1Yi1nZW5lcmF0b3IvLmdpdGh1Yi93b3JrZmxvd3MvZ2VuZXJhdG9yX2dlbmVyaWNfc2xzYTMueW1sQHJlZnMvdGFncy92Mi4xLjAifSwiYnVpbGRUeXBlIjoiaHR0cHM6Ly9naXRodWIuY29tL3Nsc2EtZnJhbWV3b3JrL3Nsc2EtZ2l0aHViLWdlbmVyYXRvci9nZW5lcmljQHYxIiwiaW52b2NhdGlvbiI6eyJjb25maWdTb3VyY2UiOnsidXJpIjoiZ2l0K2h0dHBzOi8vZ2l0aHViLmNvbS9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob25AcmVmcy9oZWFkcy9kZXZlbG9wIiwiZGlnZXN0Ijp7InNoYTEiOiIwNGZlZjM0ZmUwYjMzZjUwMTExZTJkOGE4ZGIxYTUzMzg5YzZmY2RjIn0sImVudHJ5UG9pbnQiOiIuZ2l0aHViL3dvcmtmbG93cy9wcmUtcmVsZWFzZS55bWwifSwicGFyYW1ldGVycyI6eyJ2YXJzIjp7fX0sImVudmlyb25tZW50Ijp7ImdpdGh1Yl9hY3RvciI6ImxlYW5kcm9kYW1hc2NlbmEiLCJnaXRodWJfYWN0b3JfaWQiOiI0Mjk1MTczIiwiZ2l0aHViX2Jhc2VfcmVmIjoiIiwiZ2l0aHViX2V2ZW50X25hbWUiOiJzY2hlZHVsZSIsImdpdGh1Yl9ldmVudF9wYXlsb2FkIjp7ImVudGVycHJpc2UiOnsiYXZhdGFyX3VybCI6Imh0dHBzOi8vYXZhdGFycy5naXRodWJ1c2VyY29udGVudC5jb20vYi8xMjkwP3Y9NCIsImNyZWF0ZWRfYXQiOiIyMDE5LTExLTEzVDE4OjA1OjQxWiIsImRlc2NyaXB0aW9uIjoiIiwiaHRtbF91cmwiOiJodHRwczovL2dpdGh1Yi5jb20vZW50ZXJwcmlzZXMvYW1hem9uIiwiaWQiOjEyOTAsIm5hbWUiOiJBbWF6b24iLCJub2RlX2lkIjoiTURFd09rVnVkR1Z5Y0hKcGMyVXhNamt3Iiwic2x1ZyI6ImFtYXpvbiIsInVwZGF0ZWRfYXQiOiIyMDI0LTA5LTMwVDIxOjAyOjMwWiIsIndlYnNpdGVfdXJsIjoiaHR0cHM6Ly93d3cuYW1hem9uLmNvbS8ifSwib3JnYW5pemF0aW9uIjp7ImF2YXRhcl91cmwiOiJodHRwczovL2F2YXRhcnMuZ2l0aHVidXNlcmNvbnRlbnQuY29tL3UvMTI5MTI3NjM4P3Y9NCIsImRlc2NyaXB0aW9uIjoiIiwiZXZlbnRzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vb3Jncy9hd3MtcG93ZXJ0b29scy9ldmVudHMiLCJob29rc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL29yZ3MvYXdzLXBvd2VydG9vbHMvaG9va3MiLCJpZCI6MTI5MTI3NjM4LCJpc3N1ZXNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9vcmdzL2F3cy1wb3dlcnRvb2xzL2lzc3VlcyIsImxvZ2luIjoiYXdzLXBvd2VydG9vbHMiLCJtZW1iZXJzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vb3Jncy9hd3MtcG93ZXJ0b29scy9tZW1iZXJzey9tZW1iZXJ9Iiwibm9kZV9pZCI6Ik9fa2dET0I3SlUxZyIsInB1YmxpY19tZW1iZXJzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vb3Jncy9hd3MtcG93ZXJ0b29scy9wdWJsaWNfbWVtYmVyc3svbWVtYmVyfSIsInJlcG9zX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vb3Jncy9hd3MtcG93ZXJ0b29scy9yZXBvcyIsInVybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vb3Jncy9hd3MtcG93ZXJ0b29scyJ9LCJyZXBvc2l0b3J5Ijp7ImFsbG93X2ZvcmtpbmciOnRydWUsImFyY2hpdmVfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24ve2FyY2hpdmVfZm9ybWF0fXsvcmVmfSIsImFyY2hpdmVkIjpmYWxzZSwiYXNzaWduZWVzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2Fzc2lnbmVlc3svdXNlcn0iLCJibG9ic191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9naXQvYmxvYnN7L3NoYX0iLCJicmFuY2hlc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9icmFuY2hlc3svYnJhbmNofSIsImNsb25lX3VybCI6Imh0dHBzOi8vZ2l0aHViLmNvbS9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24uZ2l0IiwiY29sbGFib3JhdG9yc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9jb2xsYWJvcmF0b3Jzey9jb2xsYWJvcmF0b3J9IiwiY29tbWVudHNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vY29tbWVudHN7L251bWJlcn0iLCJjb21taXRzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2NvbW1pdHN7L3NoYX0iLCJjb21wYXJlX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2NvbXBhcmUve2Jhc2V9Li4ue2hlYWR9IiwiY29udGVudHNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vY29udGVudHMveytwYXRofSIsImNvbnRyaWJ1dG9yc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9jb250cmlidXRvcnMiLCJjcmVhdGVkX2F0IjoiMjAxOS0xMS0xNVQxMjoyNjoxMloiLCJjdXN0b21fcHJvcGVydGllcyI6e30sImRlZmF1bHRfYnJhbmNoIjoiZGV2ZWxvcCIsImRlcGxveW1lbnRzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2RlcGxveW1lbnRzIiwiZGVzY3JpcHRpb24iOiJBIGRldmVsb3BlciB0b29sa2l0IHRvIGltcGxlbWVudCBTZXJ2ZXJsZXNzIGJlc3QgcHJhY3RpY2VzIGFuZCBpbmNyZWFzZSBkZXZlbG9wZXIgdmVsb2NpdHkuIiwiZGlzYWJsZWQiOmZhbHNlLCJkb3dubG9hZHNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vZG93bmxvYWRzIiwiZXZlbnRzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2V2ZW50cyIsImZvcmsiOmZhbHNlLCJmb3JrcyI6NDE4LCJmb3Jrc19jb3VudCI6NDE4LCJmb3Jrc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9mb3JrcyIsImZ1bGxfbmFtZSI6ImF3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbiIsImdpdF9jb21taXRzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2dpdC9jb21taXRzey9zaGF9IiwiZ2l0X3JlZnNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vZ2l0L3JlZnN7L3NoYX0iLCJnaXRfdGFnc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9naXQvdGFnc3svc2hhfSIsImdpdF91cmwiOiJnaXQ6Ly9naXRodWIuY29tL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi5naXQiLCJoYXNfZGlzY3Vzc2lvbnMiOnRydWUsImhhc19kb3dubG9hZHMiOnRydWUsImhhc19pc3N1ZXMiOnRydWUsImhhc19wYWdlcyI6ZmFsc2UsImhhc19wcm9qZWN0cyI6dHJ1ZSwiaGFzX3dpa2kiOmZhbHNlLCJob21lcGFnZSI6Imh0dHBzOi8vZG9jcy5wb3dlcnRvb2xzLmF3cy5kZXYvbGFtYmRhL3B5dGhvbi9sYXRlc3QvIiwiaG9va3NfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vaG9va3MiLCJodG1sX3VybCI6Imh0dHBzOi8vZ2l0aHViLmNvbS9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24iLCJpZCI6MjIxOTE5Mzc5LCJpc190ZW1wbGF0ZSI6ZmFsc2UsImlzc3VlX2NvbW1lbnRfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vaXNzdWVzL2NvbW1lbnRzey9udW1iZXJ9IiwiaXNzdWVfZXZlbnRzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2lzc3Vlcy9ldmVudHN7L251bWJlcn0iLCJpc3N1ZXNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vaXNzdWVzey9udW1iZXJ9Iiwia2V5c191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9rZXlzey9rZXlfaWR9IiwibGFiZWxzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2xhYmVsc3svbmFtZX0iLCJsYW5ndWFnZSI6IlB5dGhvbiIsImxhbmd1YWdlc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9sYW5ndWFnZXMiLCJsaWNlbnNlIjp7ImtleSI6Im1pdC0wIiwibmFtZSI6Ik1JVCBObyBBdHRyaWJ1dGlvbiIsIm5vZGVfaWQiOiJNRGM2VEdsalpXNXpaVFF4Iiwic3BkeF9pZCI6Ik1JVC0wIiwidXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9saWNlbnNlcy9taXQtMCJ9LCJtZXJnZXNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vbWVyZ2VzIiwibWlsZXN0b25lc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9taWxlc3RvbmVzey9udW1iZXJ9IiwibWlycm9yX3VybCI6bnVsbCwibmFtZSI6InBvd2VydG9vbHMtbGFtYmRhLXB5dGhvbiIsIm5vZGVfaWQiOiJNREV3T2xKbGNHOXphWFJ2Y25reU1qRTVNVGt6TnprPSIsIm5vdGlmaWNhdGlvbnNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vbm90aWZpY2F0aW9uc3s/c2luY2UsYWxsLHBhcnRpY2lwYXRpbmd9Iiwib3Blbl9pc3N1ZXMiOjUyLCJvcGVuX2lzc3Vlc19jb3VudCI6NTIsIm93bmVyIjp7ImF2YXRhcl91cmwiOiJodHRwczovL2F2YXRhcnMuZ2l0aHVidXNlcmNvbnRlbnQuY29tL3UvMTI5MTI3NjM4P3Y9NCIsImV2ZW50c191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3VzZXJzL2F3cy1wb3dlcnRvb2xzL2V2ZW50c3svcHJpdmFjeX0iLCJmb2xsb3dlcnNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS91c2Vycy9hd3MtcG93ZXJ0b29scy9mb2xsb3dlcnMiLCJmb2xsb3dpbmdfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS91c2Vycy9hd3MtcG93ZXJ0b29scy9mb2xsb3dpbmd7L290aGVyX3VzZXJ9IiwiZ2lzdHNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS91c2Vycy9hd3MtcG93ZXJ0b29scy9naXN0c3svZ2lzdF9pZH0iLCJncmF2YXRhcl9pZCI6IiIsImh0bWxfdXJsIjoiaHR0cHM6Ly9naXRodWIuY29tL2F3cy1wb3dlcnRvb2xzIiwiaWQiOjEyOTEyNzYzOCwibG9naW4iOiJhd3MtcG93ZXJ0b29scyIsIm5vZGVfaWQiOiJPX2tnRE9CN0pVMWciLCJvcmdhbml6YXRpb25zX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vdXNlcnMvYXdzLXBvd2VydG9vbHMvb3JncyIsInJlY2VpdmVkX2V2ZW50c191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3VzZXJzL2F3cy1wb3dlcnRvb2xzL3JlY2VpdmVkX2V2ZW50cyIsInJlcG9zX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vdXNlcnMvYXdzLXBvd2VydG9vbHMvcmVwb3MiLCJzaXRlX2FkbWluIjpmYWxzZSwic3RhcnJlZF91cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3VzZXJzL2F3cy1wb3dlcnRvb2xzL3N0YXJyZWR7L293bmVyfXsvcmVwb30iLCJzdWJzY3JpcHRpb25zX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vdXNlcnMvYXdzLXBvd2VydG9vbHMvc3Vic2NyaXB0aW9ucyIsInR5cGUiOiJPcmdhbml6YXRpb24iLCJ1cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3VzZXJzL2F3cy1wb3dlcnRvb2xzIiwidXNlcl92aWV3X3R5cGUiOiJwdWJsaWMifSwicHJpdmF0ZSI6ZmFsc2UsInB1bGxzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL3B1bGxzey9udW1iZXJ9IiwicHVzaGVkX2F0IjoiMjAyNS0wNC0xN1QyMToxMDoyMVoiLCJyZWxlYXNlc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9yZWxlYXNlc3svaWR9Iiwic2l6ZSI6MTA4MzQ4LCJzc2hfdXJsIjoiZ2l0QGdpdGh1Yi5jb206YXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uLmdpdCIsInN0YXJnYXplcnNfY291bnQiOjMwMjQsInN0YXJnYXplcnNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vc3RhcmdhemVycyIsInN0YXR1c2VzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL3N0YXR1c2VzL3tzaGF9Iiwic3Vic2NyaWJlcnNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vc3Vic2NyaWJlcnMiLCJzdWJzY3JpcHRpb25fdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vc3Vic2NyaXB0aW9uIiwic3ZuX3VybCI6Imh0dHBzOi8vZ2l0aHViLmNvbS9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24iLCJ0YWdzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL3RhZ3MiLCJ0ZWFtc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi90ZWFtcyIsInRvcGljcyI6WyJhd3MiLCJhd3MtbGFtYmRhIiwiaGFja3RvYmVyZmVzdCIsImxhbWJkYSIsInB5dGhvbiIsInNlcnZlcmxlc3MiXSwidHJlZXNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vZ2l0L3RyZWVzey9zaGF9IiwidXBkYXRlZF9hdCI6IjIwMjUtMDQtMTdUMjA6MzM6NDhaIiwidXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24iLCJ2aXNpYmlsaXR5IjoicHVibGljIiwid2F0Y2hlcnMiOjMwMjQsIndhdGNoZXJzX2NvdW50IjozMDI0LCJ3ZWJfY29tbWl0X3NpZ25vZmZfcmVxdWlyZWQiOnRydWV9LCJzY2hlZHVsZSI6IjAgOCAqICogMS01Iiwid29ya2Zsb3ciOiIuZ2l0aHViL3dvcmtmbG93cy9wcmUtcmVsZWFzZS55bWwifSwiZ2l0aHViX2hlYWRfcmVmIjoiIiwiZ2l0aHViX3JlZiI6InJlZnMvaGVhZHMvZGV2ZWxvcCIsImdpdGh1Yl9yZWZfdHlwZSI6ImJyYW5jaCIsImdpdGh1Yl9yZXBvc2l0b3J5X2lkIjoiMjIxOTE5Mzc5IiwiZ2l0aHViX3JlcG9zaXRvcnlfb3duZXIiOiJhd3MtcG93ZXJ0b29scyIsImdpdGh1Yl9yZXBvc2l0b3J5X293bmVyX2lkIjoiMTI5MTI3NjM4IiwiZ2l0aHViX3J1bl9hdHRlbXB0IjoiMSIsImdpdGh1Yl9ydW5faWQiOiIxNDUzMTk0MDE1MCIsImdpdGh1Yl9ydW5fbnVtYmVyIjoiMjIyIiwiZ2l0aHViX3NoYTEiOiIwNGZlZjM0ZmUwYjMzZjUwMTExZTJkOGE4ZGIxYTUzMzg5YzZmY2RjIn19LCJtZXRhZGF0YSI6eyJidWlsZEludm9jYXRpb25JRCI6IjE0NTMxOTQwMTUwLTEiLCJjb21wbGV0ZW5lc3MiOnsicGFyYW1ldGVycyI6dHJ1ZSwiZW52aXJvbm1lbnQiOmZhbHNlLCJtYXRlcmlhbHMiOmZhbHNlfSwicmVwcm9kdWNpYmxlIjpmYWxzZX0sIm1hdGVyaWFscyI6W3sidXJpIjoiZ2l0K2h0dHBzOi8vZ2l0aHViLmNvbS9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob25AcmVmcy9oZWFkcy9kZXZlbG9wIiwiZGlnZXN0Ijp7InNoYTEiOiIwNGZlZjM0ZmUwYjMzZjUwMTExZTJkOGE4ZGIxYTUzMzg5YzZmY2RjIn19XX19", "payloadType":"application/vnd.in-toto+json", "signatures":[{"sig":"MEYCIQCksiKP5vVptoW03BPFLO+S0ybyXyxbsh0H9bgk5qeinQIhAKhfruteSGhypULoAr8igY9AlorpBTCGtsrR6sASGb+W"}]}}
\ No newline at end of file
diff --git a/provenance/3.10.1a8/multiple.intoto.jsonl b/provenance/3.10.1a8/multiple.intoto.jsonl
new file mode 100644
index 00000000000..103e406918f
--- /dev/null
+++ b/provenance/3.10.1a8/multiple.intoto.jsonl
@@ -0,0 +1 @@
+{"mediaType":"application/vnd.dev.sigstore.bundle.v0.3+json", "verificationMaterial":{"certificate":{"rawBytes":"MIIHZDCCBuugAwIBAgIUbYha5DuPV20gmkd4jHbsFeB6jX0wCgYIKoZIzj0EAwMwNzEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MR4wHAYDVQQDExVzaWdzdG9yZS1pbnRlcm1lZGlhdGUwHhcNMjUwNDIxMDgwNzI0WhcNMjUwNDIxMDgxNzI0WjAAMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAECYGILQXC5MqbpTJUm0G+kAddUOpHVIujIgfALbysGr3FISqvtmCL5kolRmSl1npprO43+ko4WfSA3G+n232Gq6OCBgowggYGMA4GA1UdDwEB/wQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcDAzAdBgNVHQ4EFgQUhf3aLcaiovEW7ZMgs7bbMPSoIaQwHwYDVR0jBBgwFoAU39Ppz1YkEZb5qNjpKFWixi4YZD8wgYQGA1UdEQEB/wR6MHiGdmh0dHBzOi8vZ2l0aHViLmNvbS9zbHNhLWZyYW1ld29yay9zbHNhLWdpdGh1Yi1nZW5lcmF0b3IvLmdpdGh1Yi93b3JrZmxvd3MvZ2VuZXJhdG9yX2dlbmVyaWNfc2xzYTMueW1sQHJlZnMvdGFncy92Mi4xLjAwOQYKKwYBBAGDvzABAQQraHR0cHM6Ly90b2tlbi5hY3Rpb25zLmdpdGh1YnVzZXJjb250ZW50LmNvbTAWBgorBgEEAYO/MAECBAhzY2hlZHVsZTA2BgorBgEEAYO/MAEDBChlOTZlNzNjN2JlNTM5ZTAyZDVkZWMwYmU3YTZjNzBiYjU4MDFmNDkxMBkGCisGAQQBg78wAQQEC1ByZS1SZWxlYXNlMDUGCisGAQQBg78wAQUEJ2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbjAgBgorBgEEAYO/MAEGBBJyZWZzL2hlYWRzL2RldmVsb3AwOwYKKwYBBAGDvzABCAQtDCtodHRwczovL3Rva2VuLmFjdGlvbnMuZ2l0aHVidXNlcmNvbnRlbnQuY29tMIGGBgorBgEEAYO/MAEJBHgMdmh0dHBzOi8vZ2l0aHViLmNvbS9zbHNhLWZyYW1ld29yay9zbHNhLWdpdGh1Yi1nZW5lcmF0b3IvLmdpdGh1Yi93b3JrZmxvd3MvZ2VuZXJhdG9yX2dlbmVyaWNfc2xzYTMueW1sQHJlZnMvdGFncy92Mi4xLjAwOAYKKwYBBAGDvzABCgQqDChmN2RkOGM1NGMyMDY3YmFmYzEyY2E3YTU1NTk1ZDVlZTliNzUyMDRhMB0GCisGAQQBg78wAQsEDwwNZ2l0aHViLWhvc3RlZDBKBgorBgEEAYO/MAEMBDwMOmh0dHBzOi8vZ2l0aHViLmNvbS9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24wOAYKKwYBBAGDvzABDQQqDChlOTZlNzNjN2JlNTM5ZTAyZDVkZWMwYmU3YTZjNzBiYjU4MDFmNDkxMCIGCisGAQQBg78wAQ4EFAwScmVmcy9oZWFkcy9kZXZlbG9wMBkGCisGAQQBg78wAQ8ECwwJMjIxOTE5Mzc5MDEGCisGAQQBg78wARAEIwwhaHR0cHM6Ly9naXRodWIuY29tL2F3cy1wb3dlcnRvb2xzMBkGCisGAQQBg78wAREECwwJMTI5MTI3NjM4MH8GCisGAQQBg78wARIEcQxvaHR0cHM6Ly9naXRodWIuY29tL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi8uZ2l0aHViL3dvcmtmbG93cy9wcmUtcmVsZWFzZS55bWxAcmVmcy9oZWFkcy9kZXZlbG9wMDgGCisGAQQBg78wARMEKgwoZTk2ZTczYzdiZTUzOWUwMmQ1ZGVjMGJlN2E2YzcwYmI1ODAxZjQ5MTAYBgorBgEEAYO/MAEUBAoMCHNjaGVkdWxlMG4GCisGAQQBg78wARUEYAxeaHR0cHM6Ly9naXRodWIuY29tL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9hY3Rpb25zL3J1bnMvMTQ1Njk4Nzg3OTIvYXR0ZW1wdHMvMTAWBgorBgEEAYO/MAEWBAgMBnB1YmxpYzCBiQYKKwYBBAHWeQIEAgR7BHkAdwB1AN09MGrGxxEyYxkeHJlnNwKiSl643jyt/4eKcoAvKe6OAAABlldjQfEAAAQDAEYwRAIgBXJfI+YXM7jSlHGOdVCVFmE3j4pUn/b2nKKWqtZyNFwCIBRCj0fLz/GxsFa1Pq9hrNvOFN5UEx7E/ALJbzukjQj/MAoGCCqGSM49BAMDA2cAMGQCMDLOhBd3bOR/bTwMt+TccPory3RfqAgtKvj1Ksdz6g4DEfrAdmGdiIMLPL0dqQQwIwIwO0Z/Sf7t0RpwNRM25Nu1CRFRUhWInIVn41KO8KVBxLCH/275JWOt/npysedffA0i"}, "tlogEntries":[{"logIndex":"200043713", "logId":{"keyId":"wNI9atQGlz+VWfO6LRygH4QUfY/8W4RFwiT5i5WRgB0="}, "kindVersion":{"kind":"dsse", "version":"0.0.1"}, "integratedTime":"1745222845", "inclusionPromise":{"signedEntryTimestamp":"MEYCIQDT0dzTFEwhdSGhsUoP2pgaxzkLOqePqRixURtUXtsT1QIhAL47QUAnw0ma3I29T3ZXWZ2aFka1kc2YGFDnw5CdCJlW"}, "inclusionProof":{"logIndex":"78139451", "rootHash":"dEyZrChQWEUkwERq7vuGhzRvQ51ucvZC25UIo+3XEu4=", "treeSize":"78139454", "hashes":["wIOtg8ad/zflmNN60FcxWBI/cuvRlCsR3DSQ7NZD7r8=", "me0Tt/KuDfBvL4E7fvB797HdfBhiiSuQXJM1weXbOJs=", "DQAnFheTgIHCiKt6lS/rPS6s5mA+POGfQb8S+hDte2g=", "kZVoxhoHoQtfMYxQcBlMpq9bCTILpfaSaheKRAKOdUY=", "0OQg86sFEm4MB/JWKYYRaCDKMFIpQer2uvbvC8dpQK8=", "AYwrh1WxB7VJ1aO/GbqsJE7o0Dln6UBO1yyJEcTC34o=", "tiFB5uZpBuEFVA+qPoPSlRaF67zgOwQdxZ2o6xeMx2w=", "O/YWAGv+gpvUZnmTapIGMHgyJ9ZoLXF7eWGezde275E=", "2c0Z0VGteoqr0adlQJB2QTdZT3Cn912kMtCAGoE/xW0=", "PHJDSL8Ui2OWQsJZ4vZa/V48UosV5lnRgMOoVTBsbDw=", "gGNvqHSiyarbPiEG0lmBLLIhU2F6djF/wmlcFeaQdP8=", "7v8qPHNDLerpduaMx06eb/MwgoQwczTn/cYGKX/9wZ4="], "checkpoint":{"envelope":"rekor.sigstore.dev - 1193050959916656506\n78139454\ndEyZrChQWEUkwERq7vuGhzRvQ51ucvZC25UIo+3XEu4=\n\n— rekor.sigstore.dev wNI9ajBEAiBWsghP5jvHB3tVOkKvROQbqK+RC44+BJHT/+lOYUc78AIgORa4rcBzy684PMhsOMBovZt36MiIZnzEihTiisDsvn8=\n"}}, "canonicalizedBody":"eyJhcGlWZXJzaW9uIjoiMC4wLjEiLCJraW5kIjoiZHNzZSIsInNwZWMiOnsiZW52ZWxvcGVIYXNoIjp7ImFsZ29yaXRobSI6InNoYTI1NiIsInZhbHVlIjoiNjM5Zjk4MTllN2IwYzFiNjgyZmI0YmFhMDc0ZGZlMGE1YTU1MTc4ZTZkMDYzZmM4M2M5NzZiMGVmMWNmMDEyZCJ9LCJwYXlsb2FkSGFzaCI6eyJhbGdvcml0aG0iOiJzaGEyNTYiLCJ2YWx1ZSI6IjY2Y2VlZDRmNjQ2NWJiNmE2OTM2ODM5NzE1N2RiZjI3NjkxMzgzZGQxZjZiNDZmNjU3ZTBkMmM4YTg4ZTc5ZTgifSwic2lnbmF0dXJlcyI6W3sic2lnbmF0dXJlIjoiTUVVQ0lRQytKY2pQdnBNMzhUbXZNM1dHcWxOOVNqNThJZHZMczV4cEhwUG1QNFloREFJZ0tWL1JOaml1dW91YVBvQW5obHExMUo5RUhQUHA3S2ZqalM4b0w2R20zQ1U9IiwidmVyaWZpZXIiOiJMUzB0TFMxQ1JVZEpUaUJEUlZKVVNVWkpRMEZVUlMwdExTMHRDazFKU1VoYVJFTkRRblYxWjBGM1NVSkJaMGxWWWxsb1lUVkVkVkJXTWpCbmJXdGtOR3BJWW5OR1pVSTJhbGd3ZDBObldVbExiMXBKZW1vd1JVRjNUWGNLVG5wRlZrMUNUVWRCTVZWRlEyaE5UV015Ykc1ak0xSjJZMjFWZFZwSFZqSk5ValIzU0VGWlJGWlJVVVJGZUZaNllWZGtlbVJIT1hsYVV6RndZbTVTYkFwamJURnNXa2RzYUdSSFZYZElhR05PVFdwVmQwNUVTWGhOUkdkM1RucEpNRmRvWTA1TmFsVjNUa1JKZUUxRVozaE9la2t3VjJwQlFVMUdhM2RGZDFsSUNrdHZXa2w2YWpCRFFWRlpTVXR2V2tsNmFqQkVRVkZqUkZGblFVVkRXVWRKVEZGWVF6Vk5jV0p3VkVwVmJUQkhLMnRCWkdSVlQzQklWa2wxYWtsblprRUtUR0o1YzBkeU0wWkpVM0YyZEcxRFREVnJiMnhTYlZOc01XNXdjSEpQTkRNcmEyODBWMlpUUVROSEsyNHlNekpIY1RaUFEwSm5iM2RuWjFsSFRVRTBSd3BCTVZWa1JIZEZRaTkzVVVWQmQwbElaMFJCVkVKblRsWklVMVZGUkVSQlMwSm5aM0pDWjBWR1FsRmpSRUY2UVdSQ1owNVdTRkUwUlVablVWVm9aak5oQ2t4allXbHZka1ZYTjFwTlozTTNZbUpOVUZOdlNXRlJkMGgzV1VSV1VqQnFRa0puZDBadlFWVXpPVkJ3ZWpGWmEwVmFZalZ4VG1wd1MwWlhhWGhwTkZrS1drUTRkMmRaVVVkQk1WVmtSVkZGUWk5M1VqWk5TR2xIWkcxb01HUklRbnBQYVRoMldqSnNNR0ZJVm1sTWJVNTJZbE01ZW1KSVRtaE1WMXA1V1ZjeGJBcGtNamw1WVhrNWVtSklUbWhNVjJSd1pFZG9NVmxwTVc1YVZ6VnNZMjFHTUdJelNYWk1iV1J3WkVkb01WbHBPVE5pTTBweVdtMTRkbVF6VFhaYU1sWjFDbHBZU21oa1J6bDVXREprYkdKdFZubGhWMDVtWXpKNGVsbFVUWFZsVnpGelVVaEtiRnB1VFhaa1IwWnVZM2s1TWsxcE5IaE1ha0YzVDFGWlMwdDNXVUlLUWtGSFJIWjZRVUpCVVZGeVlVaFNNR05JVFRaTWVUa3dZakowYkdKcE5XaFpNMUp3WWpJMWVreHRaSEJrUjJneFdXNVdlbHBZU21waU1qVXdXbGMxTUFwTWJVNTJZbFJCVjBKbmIzSkNaMFZGUVZsUEwwMUJSVU5DUVdoNldUSm9iRnBJVm5OYVZFRXlRbWR2Y2tKblJVVkJXVTh2VFVGRlJFSkRhR3hQVkZwc0NrNTZUbXBPTWtwc1RsUk5OVnBVUVhsYVJGWnJXbGROZDFsdFZUTlpWRnBxVG5wQ2FWbHFWVFJOUkVadFRrUnJlRTFDYTBkRGFYTkhRVkZSUW1jM09IY0tRVkZSUlVNeFFubGFVekZUV2xkNGJGbFlUbXhOUkZWSFEybHpSMEZSVVVKbk56aDNRVkZWUlVveVJqTmplVEYzWWpOa2JHTnVVblppTW5oNlRETkNkZ3BrTWxaNVpFYzVkbUpJVFhSaVIwWjBXVzFTYUV4WVFqVmtSMmgyWW1wQlowSm5iM0pDWjBWRlFWbFBMMDFCUlVkQ1FrcDVXbGRhZWt3eWFHeFpWMUo2Q2t3eVVteGtiVlp6WWpOQmQwOTNXVXRMZDFsQ1FrRkhSSFo2UVVKRFFWRjBSRU4wYjJSSVVuZGplbTkyVEROU2RtRXlWblZNYlVacVpFZHNkbUp1VFhVS1dqSnNNR0ZJVm1sa1dFNXNZMjFPZG1KdVVteGlibEYxV1RJNWRFMUpSMGRDWjI5eVFtZEZSVUZaVHk5TlFVVktRa2huVFdSdGFEQmtTRUo2VDJrNGRncGFNbXd3WVVoV2FVeHRUblppVXpsNllraE9hRXhYV25sWlZ6RnNaREk1ZVdGNU9YcGlTRTVvVEZka2NHUkhhREZaYVRGdVdsYzFiR050UmpCaU0wbDJDa3h0WkhCa1IyZ3hXV2s1TTJJelNuSmFiWGgyWkROTmRsb3lWblZhV0Vwb1pFYzVlVmd5Wkd4aWJWWjVZVmRPWm1NeWVIcFpWRTExWlZjeGMxRklTbXdLV201TmRtUkhSbTVqZVRreVRXazBlRXhxUVhkUFFWbExTM2RaUWtKQlIwUjJla0ZDUTJkUmNVUkRhRzFPTWxKclQwZE5NVTVIVFhsTlJGa3pXVzFHYlFwWmVrVjVXVEpGTTFsVVZURk9WR3N4V2tSV2JGcFViR2xPZWxWNVRVUlNhRTFDTUVkRGFYTkhRVkZSUW1jM09IZEJVWE5GUkhkM1Rsb3liREJoU0ZacENreFhhSFpqTTFKc1drUkNTMEpuYjNKQ1owVkZRVmxQTDAxQlJVMUNSSGROVDIxb01HUklRbnBQYVRoMldqSnNNR0ZJVm1sTWJVNTJZbE01YUdRelRYUUtZMGM1TTFwWVNqQmlNamx6WTNrNWQySXpaR3hqYmxKMllqSjRla3hYZUdoaVYwcHJXVk14ZDJWWVVtOWlNalIzVDBGWlMwdDNXVUpDUVVkRWRucEJRZ3BFVVZGeFJFTm9iRTlVV214T2VrNXFUakpLYkU1VVRUVmFWRUY1V2tSV2ExcFhUWGRaYlZVeldWUmFhazU2UW1sWmFsVTBUVVJHYlU1RWEzaE5RMGxIQ2tOcGMwZEJVVkZDWnpjNGQwRlJORVZHUVhkVFkyMVdiV041T1c5YVYwWnJZM2s1YTFwWVdteGlSemwzVFVKclIwTnBjMGRCVVZGQ1p6YzRkMEZST0VVS1EzZDNTazFxU1hoUFZFVTFUWHBqTlUxRVJVZERhWE5IUVZGUlFtYzNPSGRCVWtGRlNYZDNhR0ZJVWpCalNFMDJUSGs1Ym1GWVVtOWtWMGwxV1RJNWRBcE1Na1l6WTNreGQySXpaR3hqYmxKMllqSjRlazFDYTBkRGFYTkhRVkZSUW1jM09IZEJVa1ZGUTNkM1NrMVVTVFZOVkVrelRtcE5ORTFJT0VkRGFYTkhDa0ZSVVVKbk56aDNRVkpKUldOUmVIWmhTRkl3WTBoTk5reDVPVzVoV0ZKdlpGZEpkVmt5T1hSTU1rWXpZM2t4ZDJJelpHeGpibEoyWWpKNGVrd3pRbllLWkRKV2VXUkhPWFppU0UxMFlrZEdkRmx0VW1oTVdFSTFaRWRvZG1KcE9IVmFNbXd3WVVoV2FVd3paSFpqYlhSdFlrYzVNMk41T1hkamJWVjBZMjFXY3dwYVYwWjZXbE0xTldKWGVFRmpiVlp0WTNrNWIxcFhSbXRqZVRscldsaGFiR0pIT1hkTlJHZEhRMmx6UjBGUlVVSm5OemgzUVZKTlJVdG5kMjlhVkdzeUNscFVZM3BaZW1ScFdsUlZlazlYVlhkTmJWRXhXa2RXYWsxSFNteE9Na1V5V1hwamQxbHRTVEZQUkVGNFdtcFJOVTFVUVZsQ1oyOXlRbWRGUlVGWlR5OEtUVUZGVlVKQmIwMURTRTVxWVVkV2EyUlhlR3hOUnpSSFEybHpSMEZSVVVKbk56aDNRVkpWUlZsQmVHVmhTRkl3WTBoTk5reDVPVzVoV0ZKdlpGZEpkUXBaTWpsMFRESkdNMk41TVhkaU0yUnNZMjVTZG1JeWVIcE1NMEoyWkRKV2VXUkhPWFppU0UxMFlrZEdkRmx0VW1oTVdFSTFaRWRvZG1KcE9XaFpNMUp3Q21JeU5YcE1NMG94WW01TmRrMVVVVEZPYW1zMFRucG5NMDlVU1haWldGSXdXbGN4ZDJSSVRYWk5WRUZYUW1kdmNrSm5SVVZCV1U4dlRVRkZWMEpCWjAwS1FtNUNNVmx0ZUhCWmVrTkNhVkZaUzB0M1dVSkNRVWhYWlZGSlJVRm5VamRDU0d0QlpIZENNVUZPTURsTlIzSkhlSGhGZVZsNGEyVklTbXh1VG5kTGFRcFRiRFkwTTJwNWRDODBaVXRqYjBGMlMyVTJUMEZCUVVKc2JHUnFVV1pGUVVGQlVVUkJSVmwzVWtGSlowSllTbVpKSzFsWVRUZHFVMnhJUjA5a1ZrTldDa1p0UlROcU5IQlZiaTlpTW01TFMxZHhkRnA1VGtaM1EwbENVa05xTUdaTWVpOUhlSE5HWVRGUWNUbG9jazUyVDBaT05WVkZlRGRGTDBGTVNtSjZkV3NLYWxGcUwwMUJiMGREUTNGSFUwMDBPVUpCVFVSQk1tTkJUVWRSUTAxRVRFOW9RbVF6WWs5U0wySlVkMDEwSzFSalkxQnZjbmt6VW1aeFFXZDBTM1pxTVFwTGMyUjZObWMwUkVWbWNrRmtiVWRrYVVsTlRGQk1NR1J4VVZGM1NYZEpkMDh3V2k5VFpqZDBNRkp3ZDA1U1RUSTFUblV4UTFKR1VsVm9WMGx1U1ZadUNqUXhTMDg0UzFaQ2VFeERTQzh5TnpWS1YwOTBMMjV3ZVhObFpHWm1RVEJwQ2kwdExTMHRSVTVFSUVORlVsUkpSa2xEUVZSRkxTMHRMUzBLIn1dfX0="}]}, "dsseEnvelope":{"payload":"eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjAuMSIsInByZWRpY2F0ZVR5cGUiOiJodHRwczovL3Nsc2EuZGV2L3Byb3ZlbmFuY2UvdjAuMiIsInN1YmplY3QiOlt7Im5hbWUiOiIuL2F3c19sYW1iZGFfcG93ZXJ0b29scy0zLjEwLjFhOC1weTMtbm9uZS1hbnkud2hsIiwiZGlnZXN0Ijp7InNoYTI1NiI6ImY2NjE2M2U5MzQyMDUxOWUwMTc4YTgzN2U3NjNkNGU5ZDlhYTFmNjAxZDNhYTRlYTZkNzM0NzI2ZTg3MzRjODYifX0seyJuYW1lIjoiLi9hd3NfbGFtYmRhX3Bvd2VydG9vbHMtMy4xMC4xYTgudGFyLmd6IiwiZGlnZXN0Ijp7InNoYTI1NiI6IjM3MzIyZThhY2FiNWU2MGFlMDdlMThiNTU5ZGEzNWY0NTk0ZDY4MGI3NmZiZWU3MDljNzBiYWJlMWI1Yjc2ZGYifX1dLCJwcmVkaWNhdGUiOnsiYnVpbGRlciI6eyJpZCI6Imh0dHBzOi8vZ2l0aHViLmNvbS9zbHNhLWZyYW1ld29yay9zbHNhLWdpdGh1Yi1nZW5lcmF0b3IvLmdpdGh1Yi93b3JrZmxvd3MvZ2VuZXJhdG9yX2dlbmVyaWNfc2xzYTMueW1sQHJlZnMvdGFncy92Mi4xLjAifSwiYnVpbGRUeXBlIjoiaHR0cHM6Ly9naXRodWIuY29tL3Nsc2EtZnJhbWV3b3JrL3Nsc2EtZ2l0aHViLWdlbmVyYXRvci9nZW5lcmljQHYxIiwiaW52b2NhdGlvbiI6eyJjb25maWdTb3VyY2UiOnsidXJpIjoiZ2l0K2h0dHBzOi8vZ2l0aHViLmNvbS9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob25AcmVmcy9oZWFkcy9kZXZlbG9wIiwiZGlnZXN0Ijp7InNoYTEiOiJlOTZlNzNjN2JlNTM5ZTAyZDVkZWMwYmU3YTZjNzBiYjU4MDFmNDkxIn0sImVudHJ5UG9pbnQiOiIuZ2l0aHViL3dvcmtmbG93cy9wcmUtcmVsZWFzZS55bWwifSwicGFyYW1ldGVycyI6eyJ2YXJzIjp7fX0sImVudmlyb25tZW50Ijp7ImdpdGh1Yl9hY3RvciI6ImxlYW5kcm9kYW1hc2NlbmEiLCJnaXRodWJfYWN0b3JfaWQiOiI0Mjk1MTczIiwiZ2l0aHViX2Jhc2VfcmVmIjoiIiwiZ2l0aHViX2V2ZW50X25hbWUiOiJzY2hlZHVsZSIsImdpdGh1Yl9ldmVudF9wYXlsb2FkIjp7ImVudGVycHJpc2UiOnsiYXZhdGFyX3VybCI6Imh0dHBzOi8vYXZhdGFycy5naXRodWJ1c2VyY29udGVudC5jb20vYi8xMjkwP3Y9NCIsImNyZWF0ZWRfYXQiOiIyMDE5LTExLTEzVDE4OjA1OjQxWiIsImRlc2NyaXB0aW9uIjoiIiwiaHRtbF91cmwiOiJodHRwczovL2dpdGh1Yi5jb20vZW50ZXJwcmlzZXMvYW1hem9uIiwiaWQiOjEyOTAsIm5hbWUiOiJBbWF6b24iLCJub2RlX2lkIjoiTURFd09rVnVkR1Z5Y0hKcGMyVXhNamt3Iiwic2x1ZyI6ImFtYXpvbiIsInVwZGF0ZWRfYXQiOiIyMDI0LTA5LTMwVDIxOjAyOjMwWiIsIndlYnNpdGVfdXJsIjoiaHR0cHM6Ly93d3cuYW1hem9uLmNvbS8ifSwib3JnYW5pemF0aW9uIjp7ImF2YXRhcl91cmwiOiJodHRwczovL2F2YXRhcnMuZ2l0aHVidXNlcmNvbnRlbnQuY29tL3UvMTI5MTI3NjM4P3Y9NCIsImRlc2NyaXB0aW9uIjoiIiwiZXZlbnRzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vb3Jncy9hd3MtcG93ZXJ0b29scy9ldmVudHMiLCJob29rc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL29yZ3MvYXdzLXBvd2VydG9vbHMvaG9va3MiLCJpZCI6MTI5MTI3NjM4LCJpc3N1ZXNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9vcmdzL2F3cy1wb3dlcnRvb2xzL2lzc3VlcyIsImxvZ2luIjoiYXdzLXBvd2VydG9vbHMiLCJtZW1iZXJzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vb3Jncy9hd3MtcG93ZXJ0b29scy9tZW1iZXJzey9tZW1iZXJ9Iiwibm9kZV9pZCI6Ik9fa2dET0I3SlUxZyIsInB1YmxpY19tZW1iZXJzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vb3Jncy9hd3MtcG93ZXJ0b29scy9wdWJsaWNfbWVtYmVyc3svbWVtYmVyfSIsInJlcG9zX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vb3Jncy9hd3MtcG93ZXJ0b29scy9yZXBvcyIsInVybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vb3Jncy9hd3MtcG93ZXJ0b29scyJ9LCJyZXBvc2l0b3J5Ijp7ImFsbG93X2ZvcmtpbmciOnRydWUsImFyY2hpdmVfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24ve2FyY2hpdmVfZm9ybWF0fXsvcmVmfSIsImFyY2hpdmVkIjpmYWxzZSwiYXNzaWduZWVzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2Fzc2lnbmVlc3svdXNlcn0iLCJibG9ic191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9naXQvYmxvYnN7L3NoYX0iLCJicmFuY2hlc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9icmFuY2hlc3svYnJhbmNofSIsImNsb25lX3VybCI6Imh0dHBzOi8vZ2l0aHViLmNvbS9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24uZ2l0IiwiY29sbGFib3JhdG9yc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9jb2xsYWJvcmF0b3Jzey9jb2xsYWJvcmF0b3J9IiwiY29tbWVudHNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vY29tbWVudHN7L251bWJlcn0iLCJjb21taXRzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2NvbW1pdHN7L3NoYX0iLCJjb21wYXJlX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2NvbXBhcmUve2Jhc2V9Li4ue2hlYWR9IiwiY29udGVudHNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vY29udGVudHMveytwYXRofSIsImNvbnRyaWJ1dG9yc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9jb250cmlidXRvcnMiLCJjcmVhdGVkX2F0IjoiMjAxOS0xMS0xNVQxMjoyNjoxMloiLCJjdXN0b21fcHJvcGVydGllcyI6e30sImRlZmF1bHRfYnJhbmNoIjoiZGV2ZWxvcCIsImRlcGxveW1lbnRzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2RlcGxveW1lbnRzIiwiZGVzY3JpcHRpb24iOiJBIGRldmVsb3BlciB0b29sa2l0IHRvIGltcGxlbWVudCBTZXJ2ZXJsZXNzIGJlc3QgcHJhY3RpY2VzIGFuZCBpbmNyZWFzZSBkZXZlbG9wZXIgdmVsb2NpdHkuIiwiZGlzYWJsZWQiOmZhbHNlLCJkb3dubG9hZHNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vZG93bmxvYWRzIiwiZXZlbnRzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2V2ZW50cyIsImZvcmsiOmZhbHNlLCJmb3JrcyI6NDIwLCJmb3Jrc19jb3VudCI6NDIwLCJmb3Jrc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9mb3JrcyIsImZ1bGxfbmFtZSI6ImF3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbiIsImdpdF9jb21taXRzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2dpdC9jb21taXRzey9zaGF9IiwiZ2l0X3JlZnNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vZ2l0L3JlZnN7L3NoYX0iLCJnaXRfdGFnc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9naXQvdGFnc3svc2hhfSIsImdpdF91cmwiOiJnaXQ6Ly9naXRodWIuY29tL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi5naXQiLCJoYXNfZGlzY3Vzc2lvbnMiOnRydWUsImhhc19kb3dubG9hZHMiOnRydWUsImhhc19pc3N1ZXMiOnRydWUsImhhc19wYWdlcyI6ZmFsc2UsImhhc19wcm9qZWN0cyI6dHJ1ZSwiaGFzX3dpa2kiOmZhbHNlLCJob21lcGFnZSI6Imh0dHBzOi8vZG9jcy5wb3dlcnRvb2xzLmF3cy5kZXYvbGFtYmRhL3B5dGhvbi9sYXRlc3QvIiwiaG9va3NfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vaG9va3MiLCJodG1sX3VybCI6Imh0dHBzOi8vZ2l0aHViLmNvbS9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24iLCJpZCI6MjIxOTE5Mzc5LCJpc190ZW1wbGF0ZSI6ZmFsc2UsImlzc3VlX2NvbW1lbnRfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vaXNzdWVzL2NvbW1lbnRzey9udW1iZXJ9IiwiaXNzdWVfZXZlbnRzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2lzc3Vlcy9ldmVudHN7L251bWJlcn0iLCJpc3N1ZXNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vaXNzdWVzey9udW1iZXJ9Iiwia2V5c191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9rZXlzey9rZXlfaWR9IiwibGFiZWxzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2xhYmVsc3svbmFtZX0iLCJsYW5ndWFnZSI6IlB5dGhvbiIsImxhbmd1YWdlc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9sYW5ndWFnZXMiLCJsaWNlbnNlIjp7ImtleSI6Im1pdC0wIiwibmFtZSI6Ik1JVCBObyBBdHRyaWJ1dGlvbiIsIm5vZGVfaWQiOiJNRGM2VEdsalpXNXpaVFF4Iiwic3BkeF9pZCI6Ik1JVC0wIiwidXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9saWNlbnNlcy9taXQtMCJ9LCJtZXJnZXNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vbWVyZ2VzIiwibWlsZXN0b25lc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9taWxlc3RvbmVzey9udW1iZXJ9IiwibWlycm9yX3VybCI6bnVsbCwibmFtZSI6InBvd2VydG9vbHMtbGFtYmRhLXB5dGhvbiIsIm5vZGVfaWQiOiJNREV3T2xKbGNHOXphWFJ2Y25reU1qRTVNVGt6TnprPSIsIm5vdGlmaWNhdGlvbnNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vbm90aWZpY2F0aW9uc3s/c2luY2UsYWxsLHBhcnRpY2lwYXRpbmd9Iiwib3Blbl9pc3N1ZXMiOjQ4LCJvcGVuX2lzc3Vlc19jb3VudCI6NDgsIm93bmVyIjp7ImF2YXRhcl91cmwiOiJodHRwczovL2F2YXRhcnMuZ2l0aHVidXNlcmNvbnRlbnQuY29tL3UvMTI5MTI3NjM4P3Y9NCIsImV2ZW50c191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3VzZXJzL2F3cy1wb3dlcnRvb2xzL2V2ZW50c3svcHJpdmFjeX0iLCJmb2xsb3dlcnNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS91c2Vycy9hd3MtcG93ZXJ0b29scy9mb2xsb3dlcnMiLCJmb2xsb3dpbmdfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS91c2Vycy9hd3MtcG93ZXJ0b29scy9mb2xsb3dpbmd7L290aGVyX3VzZXJ9IiwiZ2lzdHNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS91c2Vycy9hd3MtcG93ZXJ0b29scy9naXN0c3svZ2lzdF9pZH0iLCJncmF2YXRhcl9pZCI6IiIsImh0bWxfdXJsIjoiaHR0cHM6Ly9naXRodWIuY29tL2F3cy1wb3dlcnRvb2xzIiwiaWQiOjEyOTEyNzYzOCwibG9naW4iOiJhd3MtcG93ZXJ0b29scyIsIm5vZGVfaWQiOiJPX2tnRE9CN0pVMWciLCJvcmdhbml6YXRpb25zX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vdXNlcnMvYXdzLXBvd2VydG9vbHMvb3JncyIsInJlY2VpdmVkX2V2ZW50c191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3VzZXJzL2F3cy1wb3dlcnRvb2xzL3JlY2VpdmVkX2V2ZW50cyIsInJlcG9zX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vdXNlcnMvYXdzLXBvd2VydG9vbHMvcmVwb3MiLCJzaXRlX2FkbWluIjpmYWxzZSwic3RhcnJlZF91cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3VzZXJzL2F3cy1wb3dlcnRvb2xzL3N0YXJyZWR7L293bmVyfXsvcmVwb30iLCJzdWJzY3JpcHRpb25zX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vdXNlcnMvYXdzLXBvd2VydG9vbHMvc3Vic2NyaXB0aW9ucyIsInR5cGUiOiJPcmdhbml6YXRpb24iLCJ1cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3VzZXJzL2F3cy1wb3dlcnRvb2xzIiwidXNlcl92aWV3X3R5cGUiOiJwdWJsaWMifSwicHJpdmF0ZSI6ZmFsc2UsInB1bGxzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL3B1bGxzey9udW1iZXJ9IiwicHVzaGVkX2F0IjoiMjAyNS0wNC0yMFQxMTo1MTo0OVoiLCJyZWxlYXNlc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9yZWxlYXNlc3svaWR9Iiwic2l6ZSI6MTA5NzM4LCJzc2hfdXJsIjoiZ2l0QGdpdGh1Yi5jb206YXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uLmdpdCIsInN0YXJnYXplcnNfY291bnQiOjMwMjUsInN0YXJnYXplcnNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vc3RhcmdhemVycyIsInN0YXR1c2VzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL3N0YXR1c2VzL3tzaGF9Iiwic3Vic2NyaWJlcnNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vc3Vic2NyaWJlcnMiLCJzdWJzY3JpcHRpb25fdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vc3Vic2NyaXB0aW9uIiwic3ZuX3VybCI6Imh0dHBzOi8vZ2l0aHViLmNvbS9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24iLCJ0YWdzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL3RhZ3MiLCJ0ZWFtc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi90ZWFtcyIsInRvcGljcyI6WyJhd3MiLCJhd3MtbGFtYmRhIiwiaGFja3RvYmVyZmVzdCIsImxhbWJkYSIsInB5dGhvbiIsInNlcnZlcmxlc3MiXSwidHJlZXNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vZ2l0L3RyZWVzey9zaGF9IiwidXBkYXRlZF9hdCI6IjIwMjUtMDQtMjBUMTg6MjQ6MDNaIiwidXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24iLCJ2aXNpYmlsaXR5IjoicHVibGljIiwid2F0Y2hlcnMiOjMwMjUsIndhdGNoZXJzX2NvdW50IjozMDI1LCJ3ZWJfY29tbWl0X3NpZ25vZmZfcmVxdWlyZWQiOnRydWV9LCJzY2hlZHVsZSI6IjAgOCAqICogMS01Iiwid29ya2Zsb3ciOiIuZ2l0aHViL3dvcmtmbG93cy9wcmUtcmVsZWFzZS55bWwifSwiZ2l0aHViX2hlYWRfcmVmIjoiIiwiZ2l0aHViX3JlZiI6InJlZnMvaGVhZHMvZGV2ZWxvcCIsImdpdGh1Yl9yZWZfdHlwZSI6ImJyYW5jaCIsImdpdGh1Yl9yZXBvc2l0b3J5X2lkIjoiMjIxOTE5Mzc5IiwiZ2l0aHViX3JlcG9zaXRvcnlfb3duZXIiOiJhd3MtcG93ZXJ0b29scyIsImdpdGh1Yl9yZXBvc2l0b3J5X293bmVyX2lkIjoiMTI5MTI3NjM4IiwiZ2l0aHViX3J1bl9hdHRlbXB0IjoiMSIsImdpdGh1Yl9ydW5faWQiOiIxNDU2OTg3ODc5MiIsImdpdGh1Yl9ydW5fbnVtYmVyIjoiMjIzIiwiZ2l0aHViX3NoYTEiOiJlOTZlNzNjN2JlNTM5ZTAyZDVkZWMwYmU3YTZjNzBiYjU4MDFmNDkxIn19LCJtZXRhZGF0YSI6eyJidWlsZEludm9jYXRpb25JRCI6IjE0NTY5ODc4NzkyLTEiLCJjb21wbGV0ZW5lc3MiOnsicGFyYW1ldGVycyI6dHJ1ZSwiZW52aXJvbm1lbnQiOmZhbHNlLCJtYXRlcmlhbHMiOmZhbHNlfSwicmVwcm9kdWNpYmxlIjpmYWxzZX0sIm1hdGVyaWFscyI6W3sidXJpIjoiZ2l0K2h0dHBzOi8vZ2l0aHViLmNvbS9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob25AcmVmcy9oZWFkcy9kZXZlbG9wIiwiZGlnZXN0Ijp7InNoYTEiOiJlOTZlNzNjN2JlNTM5ZTAyZDVkZWMwYmU3YTZjNzBiYjU4MDFmNDkxIn19XX19", "payloadType":"application/vnd.in-toto+json", "signatures":[{"sig":"MEUCIQC+JcjPvpM38TmvM3WGqlN9Sj58IdvLs5xpHpPmP4YhDAIgKV/RNjiuuouaPoAnhlq11J9EHPPp7KfjjS8oL6Gm3CU="}]}}
\ No newline at end of file
diff --git a/provenance/3.10.1a9/multiple.intoto.jsonl b/provenance/3.10.1a9/multiple.intoto.jsonl
new file mode 100644
index 00000000000..3c806f60c3f
--- /dev/null
+++ b/provenance/3.10.1a9/multiple.intoto.jsonl
@@ -0,0 +1 @@
+{"mediaType":"application/vnd.dev.sigstore.bundle.v0.3+json", "verificationMaterial":{"certificate":{"rawBytes":"MIIHZjCCBuygAwIBAgIUOfLNyQhlypjWWgUPaNGHK/5TqIswCgYIKoZIzj0EAwMwNzEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MR4wHAYDVQQDExVzaWdzdG9yZS1pbnRlcm1lZGlhdGUwHhcNMjUwNDIyMDgwNzM3WhcNMjUwNDIyMDgxNzM3WjAAMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEYbdKdVIDTBVwCEDNRE5eKFaq0vmv2SNYsOddR55iJsm3Qe+e306wdOe4ssk9B7DuCowkyZ69iOjT38tzOAGQ8qOCBgswggYHMA4GA1UdDwEB/wQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcDAzAdBgNVHQ4EFgQUB1txM3ZsNYEw46pNLPDggsiWbVIwHwYDVR0jBBgwFoAU39Ppz1YkEZb5qNjpKFWixi4YZD8wgYQGA1UdEQEB/wR6MHiGdmh0dHBzOi8vZ2l0aHViLmNvbS9zbHNhLWZyYW1ld29yay9zbHNhLWdpdGh1Yi1nZW5lcmF0b3IvLmdpdGh1Yi93b3JrZmxvd3MvZ2VuZXJhdG9yX2dlbmVyaWNfc2xzYTMueW1sQHJlZnMvdGFncy92Mi4xLjAwOQYKKwYBBAGDvzABAQQraHR0cHM6Ly90b2tlbi5hY3Rpb25zLmdpdGh1YnVzZXJjb250ZW50LmNvbTAWBgorBgEEAYO/MAECBAhzY2hlZHVsZTA2BgorBgEEAYO/MAEDBCgxZWZjNGI0NWRhODNiMjA5YjlhMzJhZTRiMzZjNzBhNjU2MWQ5MjcyMBkGCisGAQQBg78wAQQEC1ByZS1SZWxlYXNlMDUGCisGAQQBg78wAQUEJ2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbjAgBgorBgEEAYO/MAEGBBJyZWZzL2hlYWRzL2RldmVsb3AwOwYKKwYBBAGDvzABCAQtDCtodHRwczovL3Rva2VuLmFjdGlvbnMuZ2l0aHVidXNlcmNvbnRlbnQuY29tMIGGBgorBgEEAYO/MAEJBHgMdmh0dHBzOi8vZ2l0aHViLmNvbS9zbHNhLWZyYW1ld29yay9zbHNhLWdpdGh1Yi1nZW5lcmF0b3IvLmdpdGh1Yi93b3JrZmxvd3MvZ2VuZXJhdG9yX2dlbmVyaWNfc2xzYTMueW1sQHJlZnMvdGFncy92Mi4xLjAwOAYKKwYBBAGDvzABCgQqDChmN2RkOGM1NGMyMDY3YmFmYzEyY2E3YTU1NTk1ZDVlZTliNzUyMDRhMB0GCisGAQQBg78wAQsEDwwNZ2l0aHViLWhvc3RlZDBKBgorBgEEAYO/MAEMBDwMOmh0dHBzOi8vZ2l0aHViLmNvbS9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24wOAYKKwYBBAGDvzABDQQqDCgxZWZjNGI0NWRhODNiMjA5YjlhMzJhZTRiMzZjNzBhNjU2MWQ5MjcyMCIGCisGAQQBg78wAQ4EFAwScmVmcy9oZWFkcy9kZXZlbG9wMBkGCisGAQQBg78wAQ8ECwwJMjIxOTE5Mzc5MDEGCisGAQQBg78wARAEIwwhaHR0cHM6Ly9naXRodWIuY29tL2F3cy1wb3dlcnRvb2xzMBkGCisGAQQBg78wAREECwwJMTI5MTI3NjM4MH8GCisGAQQBg78wARIEcQxvaHR0cHM6Ly9naXRodWIuY29tL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi8uZ2l0aHViL3dvcmtmbG93cy9wcmUtcmVsZWFzZS55bWxAcmVmcy9oZWFkcy9kZXZlbG9wMDgGCisGAQQBg78wARMEKgwoMWVmYzRiNDVkYTgzYjIwOWI5YTMyYWU0YjM2YzcwYTY1NjFkOTI3MjAYBgorBgEEAYO/MAEUBAoMCHNjaGVkdWxlMG4GCisGAQQBg78wARUEYAxeaHR0cHM6Ly9naXRodWIuY29tL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9hY3Rpb25zL3J1bnMvMTQ1ODk3NzA5MjYvYXR0ZW1wdHMvMTAWBgorBgEEAYO/MAEWBAgMBnB1YmxpYzCBigYKKwYBBAHWeQIEAgR8BHoAeAB2AN09MGrGxxEyYxkeHJlnNwKiSl643jyt/4eKcoAvKe6OAAABllyJ0SYAAAQDAEcwRQIhALOVgyk6VcuGzw9JKvFOugCZQ+gDYrse0erSc7bi5tx6AiBTZLmWfF3KVul6AvHnZ/Lzl+x865cOV+lMF0CaEQojJDAKBggqhkjOPQQDAwNoADBlAjEAhliKGQReQjYhH/pGLeIKW6vS8N6rsyDC25k/FO3ZQzxeFqMZUKjanINBJVho8obHAjBBj3tSbPHX4kQJNErk6qLpeykWPgoReSh3JDT4CiQSViD93ZoBZh4vjZ0ZStcu6fY="}, "tlogEntries":[{"logIndex":"200557589", "logId":{"keyId":"wNI9atQGlz+VWfO6LRygH4QUfY/8W4RFwiT5i5WRgB0="}, "kindVersion":{"kind":"dsse", "version":"0.0.1"}, "integratedTime":"1745309258", "inclusionPromise":{"signedEntryTimestamp":"MEUCIQCUzmH+EKujFtvY6bAu8k8Ylg4B8Hy9ZCZ1qNyxqITo4AIgcl0CgX7X9yGoqYGvPwW6kIImKy/zd63dhSQTNqqVphw="}, "inclusionProof":{"logIndex":"78653327", "rootHash":"lAtfliCioa+x+SjGHMIqSfqZJsv76/uMd4EQJUV11aU=", "treeSize":"78653330", "hashes":["IXkkq/VOF8fjdnI/AJFWOx2mW4yS+wuLP/EDQNBFU5c=", "yPCTqJf5UFikU2Dt1obysm6NDzB2DeJ9kKIdkG5u+yg=", "Q/y0esAzXrab3dgsOEY6lNICpO1bF33X10lX9jdUxLk=", "K3dwwvp1dDwfrl85S3eVJz8sE8x0/EU/CdKTsI8+u6c=", "9cvyjLdWXuwwYsMu3MuOosKTsQG7Vmwddcogv6vaIak=", "K3UNYnzKTrU+dbGBPGmQF75C6TjP6gENtRAjLwO5Upo=", "zDOPAPFtcLzrUjektBm9YxFrGmYlG6JZuVJqrFXiPjg=", "BHdcMt2/qnrmC1GSQH0Cdhrn+X1L/loSKPxLCybehV0=", "svpwXVNGg8CeN5BqnJpqFQRXMxj6a9sD8KHe+VjxBN4=", "S9Utu5FSV07A10lKfUMrGqdmWDPMvFzG2TZcTrLwy/0=", "49Z3lxFb8hCrDQgPf5Kc9Zf0fMK9AsYmeQaIoKa2vrc=", "PHJDSL8Ui2OWQsJZ4vZa/V48UosV5lnRgMOoVTBsbDw=", "gGNvqHSiyarbPiEG0lmBLLIhU2F6djF/wmlcFeaQdP8=", "7v8qPHNDLerpduaMx06eb/MwgoQwczTn/cYGKX/9wZ4="], "checkpoint":{"envelope":"rekor.sigstore.dev - 1193050959916656506\n78653330\nlAtfliCioa+x+SjGHMIqSfqZJsv76/uMd4EQJUV11aU=\n\n— rekor.sigstore.dev wNI9ajBEAiBMLEBzazClC4qNN4JfOAqLreWYoJNonDyfCWs94CIJlAIgPHrA8nagy8Xzq2dnAuoA40ZgdisOoa98Vqqf3aI867c=\n"}}, "canonicalizedBody":"eyJhcGlWZXJzaW9uIjoiMC4wLjEiLCJraW5kIjoiZHNzZSIsInNwZWMiOnsiZW52ZWxvcGVIYXNoIjp7ImFsZ29yaXRobSI6InNoYTI1NiIsInZhbHVlIjoiNjQ2NTlhYzRkMWUzOWNlNzFjMjY1ZGY5Yjc5NzZmM2Q2Nzg0YjE5YjRjNWIwM2EwZThkODUzMGYzMDcwMjA0NCJ9LCJwYXlsb2FkSGFzaCI6eyJhbGdvcml0aG0iOiJzaGEyNTYiLCJ2YWx1ZSI6ImU5ODI5NTRiM2U3NTY0YTkzMjQ4ZTViMWIzMmYxOWE1MjZiN2RmNDM0NzA2NWMzMzQwZWRlZmViMDk3YTEwNjQifSwic2lnbmF0dXJlcyI6W3sic2lnbmF0dXJlIjoiTUVVQ0lIby9YbXNhQXY4dTRiMWpEbzZBQjNCV2VpUnFXaTE1TVhRUlNvVHBnc3JVQWlFQXZYWkR0SCtNS2tDRjFvaGZha1J5VWJqSTRIRXlKOW1KTVBURlJEbTE0Wk09IiwidmVyaWZpZXIiOiJMUzB0TFMxQ1JVZEpUaUJEUlZKVVNVWkpRMEZVUlMwdExTMHRDazFKU1VoYWFrTkRRblY1WjBGM1NVSkJaMGxWVDJaTVRubFJhR3g1Y0dwWFYyZFZVR0ZPUjBoTEx6VlVjVWx6ZDBObldVbExiMXBKZW1vd1JVRjNUWGNLVG5wRlZrMUNUVWRCTVZWRlEyaE5UV015Ykc1ak0xSjJZMjFWZFZwSFZqSk5ValIzU0VGWlJGWlJVVVJGZUZaNllWZGtlbVJIT1hsYVV6RndZbTVTYkFwamJURnNXa2RzYUdSSFZYZElhR05PVFdwVmQwNUVTWGxOUkdkM1RucE5NMWRvWTA1TmFsVjNUa1JKZVUxRVozaE9lazB6VjJwQlFVMUdhM2RGZDFsSUNrdHZXa2w2YWpCRFFWRlpTVXR2V2tsNmFqQkVRVkZqUkZGblFVVlpZbVJMWkZaSlJGUkNWbmREUlVST1VrVTFaVXRHWVhFd2RtMTJNbE5PV1hOUFpHUUtValUxYVVwemJUTlJaU3RsTXpBMmQyUlBaVFJ6YzJzNVFqZEVkVU52ZDJ0NVdqWTVhVTlxVkRNNGRIcFBRVWRST0hGUFEwSm5jM2RuWjFsSVRVRTBSd3BCTVZWa1JIZEZRaTkzVVVWQmQwbElaMFJCVkVKblRsWklVMVZGUkVSQlMwSm5aM0pDWjBWR1FsRmpSRUY2UVdSQ1owNVdTRkUwUlVablVWVkNNWFI0Q2swelduTk9XVVYzTkRad1RreFFSR2RuYzJsWFlsWkpkMGgzV1VSV1VqQnFRa0puZDBadlFWVXpPVkJ3ZWpGWmEwVmFZalZ4VG1wd1MwWlhhWGhwTkZrS1drUTRkMmRaVVVkQk1WVmtSVkZGUWk5M1VqWk5TR2xIWkcxb01HUklRbnBQYVRoMldqSnNNR0ZJVm1sTWJVNTJZbE01ZW1KSVRtaE1WMXA1V1ZjeGJBcGtNamw1WVhrNWVtSklUbWhNVjJSd1pFZG9NVmxwTVc1YVZ6VnNZMjFHTUdJelNYWk1iV1J3WkVkb01WbHBPVE5pTTBweVdtMTRkbVF6VFhaYU1sWjFDbHBZU21oa1J6bDVXREprYkdKdFZubGhWMDVtWXpKNGVsbFVUWFZsVnpGelVVaEtiRnB1VFhaa1IwWnVZM2s1TWsxcE5IaE1ha0YzVDFGWlMwdDNXVUlLUWtGSFJIWjZRVUpCVVZGeVlVaFNNR05JVFRaTWVUa3dZakowYkdKcE5XaFpNMUp3WWpJMWVreHRaSEJrUjJneFdXNVdlbHBZU21waU1qVXdXbGMxTUFwTWJVNTJZbFJCVjBKbmIzSkNaMFZGUVZsUEwwMUJSVU5DUVdoNldUSm9iRnBJVm5OYVZFRXlRbWR2Y2tKblJVVkJXVTh2VFVGRlJFSkRaM2hhVjFwcUNrNUhTVEJPVjFKb1QwUk9hVTFxUVRWWmFteG9UWHBLYUZwVVVtbE5lbHBxVG5wQ2FFNXFWVEpOVjFFMVRXcGplVTFDYTBkRGFYTkhRVkZSUW1jM09IY0tRVkZSUlVNeFFubGFVekZUV2xkNGJGbFlUbXhOUkZWSFEybHpSMEZSVVVKbk56aDNRVkZWUlVveVJqTmplVEYzWWpOa2JHTnVVblppTW5oNlRETkNkZ3BrTWxaNVpFYzVkbUpJVFhSaVIwWjBXVzFTYUV4WVFqVmtSMmgyWW1wQlowSm5iM0pDWjBWRlFWbFBMMDFCUlVkQ1FrcDVXbGRhZWt3eWFHeFpWMUo2Q2t3eVVteGtiVlp6WWpOQmQwOTNXVXRMZDFsQ1FrRkhSSFo2UVVKRFFWRjBSRU4wYjJSSVVuZGplbTkyVEROU2RtRXlWblZNYlVacVpFZHNkbUp1VFhVS1dqSnNNR0ZJVm1sa1dFNXNZMjFPZG1KdVVteGlibEYxV1RJNWRFMUpSMGRDWjI5eVFtZEZSVUZaVHk5TlFVVktRa2huVFdSdGFEQmtTRUo2VDJrNGRncGFNbXd3WVVoV2FVeHRUblppVXpsNllraE9hRXhYV25sWlZ6RnNaREk1ZVdGNU9YcGlTRTVvVEZka2NHUkhhREZaYVRGdVdsYzFiR050UmpCaU0wbDJDa3h0WkhCa1IyZ3hXV2s1TTJJelNuSmFiWGgyWkROTmRsb3lWblZhV0Vwb1pFYzVlVmd5Wkd4aWJWWjVZVmRPWm1NeWVIcFpWRTExWlZjeGMxRklTbXdLV201TmRtUkhSbTVqZVRreVRXazBlRXhxUVhkUFFWbExTM2RaUWtKQlIwUjJla0ZDUTJkUmNVUkRhRzFPTWxKclQwZE5NVTVIVFhsTlJGa3pXVzFHYlFwWmVrVjVXVEpGTTFsVVZURk9WR3N4V2tSV2JGcFViR2xPZWxWNVRVUlNhRTFDTUVkRGFYTkhRVkZSUW1jM09IZEJVWE5GUkhkM1Rsb3liREJoU0ZacENreFhhSFpqTTFKc1drUkNTMEpuYjNKQ1owVkZRVmxQTDAxQlJVMUNSSGROVDIxb01HUklRbnBQYVRoMldqSnNNR0ZJVm1sTWJVNTJZbE01YUdRelRYUUtZMGM1TTFwWVNqQmlNamx6WTNrNWQySXpaR3hqYmxKMllqSjRla3hYZUdoaVYwcHJXVk14ZDJWWVVtOWlNalIzVDBGWlMwdDNXVUpDUVVkRWRucEJRZ3BFVVZGeFJFTm5lRnBYV21wT1Iwa3dUbGRTYUU5RVRtbE5ha0UxV1dwc2FFMTZTbWhhVkZKcFRYcGFhazU2UW1oT2FsVXlUVmRSTlUxcVkzbE5RMGxIQ2tOcGMwZEJVVkZDWnpjNGQwRlJORVZHUVhkVFkyMVdiV041T1c5YVYwWnJZM2s1YTFwWVdteGlSemwzVFVKclIwTnBjMGRCVVZGQ1p6YzRkMEZST0VVS1EzZDNTazFxU1hoUFZFVTFUWHBqTlUxRVJVZERhWE5IUVZGUlFtYzNPSGRCVWtGRlNYZDNhR0ZJVWpCalNFMDJUSGs1Ym1GWVVtOWtWMGwxV1RJNWRBcE1Na1l6WTNreGQySXpaR3hqYmxKMllqSjRlazFDYTBkRGFYTkhRVkZSUW1jM09IZEJVa1ZGUTNkM1NrMVVTVFZOVkVrelRtcE5ORTFJT0VkRGFYTkhDa0ZSVVVKbk56aDNRVkpKUldOUmVIWmhTRkl3WTBoTk5reDVPVzVoV0ZKdlpGZEpkVmt5T1hSTU1rWXpZM2t4ZDJJelpHeGpibEoyWWpKNGVrd3pRbllLWkRKV2VXUkhPWFppU0UxMFlrZEdkRmx0VW1oTVdFSTFaRWRvZG1KcE9IVmFNbXd3WVVoV2FVd3paSFpqYlhSdFlrYzVNMk41T1hkamJWVjBZMjFXY3dwYVYwWjZXbE0xTldKWGVFRmpiVlp0WTNrNWIxcFhSbXRqZVRscldsaGFiR0pIT1hkTlJHZEhRMmx6UjBGUlVVSm5OemgzUVZKTlJVdG5kMjlOVjFadENsbDZVbWxPUkZacldWUm5lbGxxU1hkUFYwazFXVlJOZVZsWFZUQlphazB5V1hwamQxbFVXVEZPYWtaclQxUkpNMDFxUVZsQ1oyOXlRbWRGUlVGWlR5OEtUVUZGVlVKQmIwMURTRTVxWVVkV2EyUlhlR3hOUnpSSFEybHpSMEZSVVVKbk56aDNRVkpWUlZsQmVHVmhTRkl3WTBoTk5reDVPVzVoV0ZKdlpGZEpkUXBaTWpsMFRESkdNMk41TVhkaU0yUnNZMjVTZG1JeWVIcE1NMEoyWkRKV2VXUkhPWFppU0UxMFlrZEdkRmx0VW1oTVdFSTFaRWRvZG1KcE9XaFpNMUp3Q21JeU5YcE1NMG94WW01TmRrMVVVVEZQUkdzelRucEJOVTFxV1haWldGSXdXbGN4ZDJSSVRYWk5WRUZYUW1kdmNrSm5SVVZCV1U4dlRVRkZWMEpCWjAwS1FtNUNNVmx0ZUhCWmVrTkNhV2RaUzB0M1dVSkNRVWhYWlZGSlJVRm5VamhDU0c5QlpVRkNNa0ZPTURsTlIzSkhlSGhGZVZsNGEyVklTbXh1VG5kTGFRcFRiRFkwTTJwNWRDODBaVXRqYjBGMlMyVTJUMEZCUVVKc2JIbEtNRk5aUVVGQlVVUkJSV04zVWxGSmFFRk1UMVpuZVdzMlZtTjFSM3AzT1VwTGRrWlBDblZuUTFwUksyZEVXWEp6WlRCbGNsTmpOMkpwTlhSNE5rRnBRbFJhVEcxWFprWXpTMVoxYkRaQmRraHVXaTlNZW13cmVEZzJOV05QVml0c1RVWXdRMkVLUlZGdmFrcEVRVXRDWjJkeGFHdHFUMUJSVVVSQmQwNXZRVVJDYkVGcVJVRm9iR2xMUjFGU1pWRnFXV2hJTDNCSFRHVkpTMWMyZGxNNFRqWnljM2xFUXdveU5Xc3ZSazh6V2xGNmVHVkdjVTFhVlV0cVlXNUpUa0pLVm1odk9HOWlTRUZxUWtKcU0zUlRZbEJJV0RSclVVcE9SWEpyTm5GTWNHVjVhMWRRWjI5U0NtVlRhRE5LUkZRMFEybFJVMVpwUkRreldtOUNXbWcwZG1wYU1GcFRkR04xTm1aWlBRb3RMUzB0TFVWT1JDQkRSVkpVU1VaSlEwRlVSUzB0TFMwdENnPT0ifV19fQ=="}]}, "dsseEnvelope":{"payload":"eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjAuMSIsInByZWRpY2F0ZVR5cGUiOiJodHRwczovL3Nsc2EuZGV2L3Byb3ZlbmFuY2UvdjAuMiIsInN1YmplY3QiOlt7Im5hbWUiOiIuL2F3c19sYW1iZGFfcG93ZXJ0b29scy0zLjEwLjFhOS1weTMtbm9uZS1hbnkud2hsIiwiZGlnZXN0Ijp7InNoYTI1NiI6ImQ3Y2EzMTQ1MDZjNjA0ZWRkZWM3MmJiOGRhZDg2ZmRkMDQwMjQ5NjgxMjkxYTk3OTdkYWViYTQwOTYwMGUwYzEifX0seyJuYW1lIjoiLi9hd3NfbGFtYmRhX3Bvd2VydG9vbHMtMy4xMC4xYTkudGFyLmd6IiwiZGlnZXN0Ijp7InNoYTI1NiI6IjUxNTM0YTIxYjQ0ZmZjN2VlYzc3YzU2MzZiMzEzN2Y3NjUyZTJjNDE1ZWYzMzEzYmUzY2Y1ODM0Zjc1MTVjNzIifX1dLCJwcmVkaWNhdGUiOnsiYnVpbGRlciI6eyJpZCI6Imh0dHBzOi8vZ2l0aHViLmNvbS9zbHNhLWZyYW1ld29yay9zbHNhLWdpdGh1Yi1nZW5lcmF0b3IvLmdpdGh1Yi93b3JrZmxvd3MvZ2VuZXJhdG9yX2dlbmVyaWNfc2xzYTMueW1sQHJlZnMvdGFncy92Mi4xLjAifSwiYnVpbGRUeXBlIjoiaHR0cHM6Ly9naXRodWIuY29tL3Nsc2EtZnJhbWV3b3JrL3Nsc2EtZ2l0aHViLWdlbmVyYXRvci9nZW5lcmljQHYxIiwiaW52b2NhdGlvbiI6eyJjb25maWdTb3VyY2UiOnsidXJpIjoiZ2l0K2h0dHBzOi8vZ2l0aHViLmNvbS9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob25AcmVmcy9oZWFkcy9kZXZlbG9wIiwiZGlnZXN0Ijp7InNoYTEiOiIxZWZjNGI0NWRhODNiMjA5YjlhMzJhZTRiMzZjNzBhNjU2MWQ5MjcyIn0sImVudHJ5UG9pbnQiOiIuZ2l0aHViL3dvcmtmbG93cy9wcmUtcmVsZWFzZS55bWwifSwicGFyYW1ldGVycyI6eyJ2YXJzIjp7fX0sImVudmlyb25tZW50Ijp7ImdpdGh1Yl9hY3RvciI6ImxlYW5kcm9kYW1hc2NlbmEiLCJnaXRodWJfYWN0b3JfaWQiOiI0Mjk1MTczIiwiZ2l0aHViX2Jhc2VfcmVmIjoiIiwiZ2l0aHViX2V2ZW50X25hbWUiOiJzY2hlZHVsZSIsImdpdGh1Yl9ldmVudF9wYXlsb2FkIjp7ImVudGVycHJpc2UiOnsiYXZhdGFyX3VybCI6Imh0dHBzOi8vYXZhdGFycy5naXRodWJ1c2VyY29udGVudC5jb20vYi8xMjkwP3Y9NCIsImNyZWF0ZWRfYXQiOiIyMDE5LTExLTEzVDE4OjA1OjQxWiIsImRlc2NyaXB0aW9uIjoiIiwiaHRtbF91cmwiOiJodHRwczovL2dpdGh1Yi5jb20vZW50ZXJwcmlzZXMvYW1hem9uIiwiaWQiOjEyOTAsIm5hbWUiOiJBbWF6b24iLCJub2RlX2lkIjoiTURFd09rVnVkR1Z5Y0hKcGMyVXhNamt3Iiwic2x1ZyI6ImFtYXpvbiIsInVwZGF0ZWRfYXQiOiIyMDI0LTA5LTMwVDIxOjAyOjMwWiIsIndlYnNpdGVfdXJsIjoiaHR0cHM6Ly93d3cuYW1hem9uLmNvbS8ifSwib3JnYW5pemF0aW9uIjp7ImF2YXRhcl91cmwiOiJodHRwczovL2F2YXRhcnMuZ2l0aHVidXNlcmNvbnRlbnQuY29tL3UvMTI5MTI3NjM4P3Y9NCIsImRlc2NyaXB0aW9uIjoiIiwiZXZlbnRzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vb3Jncy9hd3MtcG93ZXJ0b29scy9ldmVudHMiLCJob29rc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL29yZ3MvYXdzLXBvd2VydG9vbHMvaG9va3MiLCJpZCI6MTI5MTI3NjM4LCJpc3N1ZXNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9vcmdzL2F3cy1wb3dlcnRvb2xzL2lzc3VlcyIsImxvZ2luIjoiYXdzLXBvd2VydG9vbHMiLCJtZW1iZXJzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vb3Jncy9hd3MtcG93ZXJ0b29scy9tZW1iZXJzey9tZW1iZXJ9Iiwibm9kZV9pZCI6Ik9fa2dET0I3SlUxZyIsInB1YmxpY19tZW1iZXJzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vb3Jncy9hd3MtcG93ZXJ0b29scy9wdWJsaWNfbWVtYmVyc3svbWVtYmVyfSIsInJlcG9zX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vb3Jncy9hd3MtcG93ZXJ0b29scy9yZXBvcyIsInVybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vb3Jncy9hd3MtcG93ZXJ0b29scyJ9LCJyZXBvc2l0b3J5Ijp7ImFsbG93X2ZvcmtpbmciOnRydWUsImFyY2hpdmVfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24ve2FyY2hpdmVfZm9ybWF0fXsvcmVmfSIsImFyY2hpdmVkIjpmYWxzZSwiYXNzaWduZWVzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2Fzc2lnbmVlc3svdXNlcn0iLCJibG9ic191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9naXQvYmxvYnN7L3NoYX0iLCJicmFuY2hlc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9icmFuY2hlc3svYnJhbmNofSIsImNsb25lX3VybCI6Imh0dHBzOi8vZ2l0aHViLmNvbS9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24uZ2l0IiwiY29sbGFib3JhdG9yc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9jb2xsYWJvcmF0b3Jzey9jb2xsYWJvcmF0b3J9IiwiY29tbWVudHNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vY29tbWVudHN7L251bWJlcn0iLCJjb21taXRzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2NvbW1pdHN7L3NoYX0iLCJjb21wYXJlX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2NvbXBhcmUve2Jhc2V9Li4ue2hlYWR9IiwiY29udGVudHNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vY29udGVudHMveytwYXRofSIsImNvbnRyaWJ1dG9yc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9jb250cmlidXRvcnMiLCJjcmVhdGVkX2F0IjoiMjAxOS0xMS0xNVQxMjoyNjoxMloiLCJjdXN0b21fcHJvcGVydGllcyI6e30sImRlZmF1bHRfYnJhbmNoIjoiZGV2ZWxvcCIsImRlcGxveW1lbnRzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2RlcGxveW1lbnRzIiwiZGVzY3JpcHRpb24iOiJBIGRldmVsb3BlciB0b29sa2l0IHRvIGltcGxlbWVudCBTZXJ2ZXJsZXNzIGJlc3QgcHJhY3RpY2VzIGFuZCBpbmNyZWFzZSBkZXZlbG9wZXIgdmVsb2NpdHkuIiwiZGlzYWJsZWQiOmZhbHNlLCJkb3dubG9hZHNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vZG93bmxvYWRzIiwiZXZlbnRzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2V2ZW50cyIsImZvcmsiOmZhbHNlLCJmb3JrcyI6NDIwLCJmb3Jrc19jb3VudCI6NDIwLCJmb3Jrc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9mb3JrcyIsImZ1bGxfbmFtZSI6ImF3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbiIsImdpdF9jb21taXRzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2dpdC9jb21taXRzey9zaGF9IiwiZ2l0X3JlZnNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vZ2l0L3JlZnN7L3NoYX0iLCJnaXRfdGFnc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9naXQvdGFnc3svc2hhfSIsImdpdF91cmwiOiJnaXQ6Ly9naXRodWIuY29tL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi5naXQiLCJoYXNfZGlzY3Vzc2lvbnMiOnRydWUsImhhc19kb3dubG9hZHMiOnRydWUsImhhc19pc3N1ZXMiOnRydWUsImhhc19wYWdlcyI6ZmFsc2UsImhhc19wcm9qZWN0cyI6dHJ1ZSwiaGFzX3dpa2kiOmZhbHNlLCJob21lcGFnZSI6Imh0dHBzOi8vZG9jcy5wb3dlcnRvb2xzLmF3cy5kZXYvbGFtYmRhL3B5dGhvbi9sYXRlc3QvIiwiaG9va3NfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vaG9va3MiLCJodG1sX3VybCI6Imh0dHBzOi8vZ2l0aHViLmNvbS9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24iLCJpZCI6MjIxOTE5Mzc5LCJpc190ZW1wbGF0ZSI6ZmFsc2UsImlzc3VlX2NvbW1lbnRfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vaXNzdWVzL2NvbW1lbnRzey9udW1iZXJ9IiwiaXNzdWVfZXZlbnRzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2lzc3Vlcy9ldmVudHN7L251bWJlcn0iLCJpc3N1ZXNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vaXNzdWVzey9udW1iZXJ9Iiwia2V5c191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9rZXlzey9rZXlfaWR9IiwibGFiZWxzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2xhYmVsc3svbmFtZX0iLCJsYW5ndWFnZSI6IlB5dGhvbiIsImxhbmd1YWdlc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9sYW5ndWFnZXMiLCJsaWNlbnNlIjp7ImtleSI6Im1pdC0wIiwibmFtZSI6Ik1JVCBObyBBdHRyaWJ1dGlvbiIsIm5vZGVfaWQiOiJNRGM2VEdsalpXNXpaVFF4Iiwic3BkeF9pZCI6Ik1JVC0wIiwidXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9saWNlbnNlcy9taXQtMCJ9LCJtZXJnZXNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vbWVyZ2VzIiwibWlsZXN0b25lc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9taWxlc3RvbmVzey9udW1iZXJ9IiwibWlycm9yX3VybCI6bnVsbCwibmFtZSI6InBvd2VydG9vbHMtbGFtYmRhLXB5dGhvbiIsIm5vZGVfaWQiOiJNREV3T2xKbGNHOXphWFJ2Y25reU1qRTVNVGt6TnprPSIsIm5vdGlmaWNhdGlvbnNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vbm90aWZpY2F0aW9uc3s/c2luY2UsYWxsLHBhcnRpY2lwYXRpbmd9Iiwib3Blbl9pc3N1ZXMiOjQ5LCJvcGVuX2lzc3Vlc19jb3VudCI6NDksIm93bmVyIjp7ImF2YXRhcl91cmwiOiJodHRwczovL2F2YXRhcnMuZ2l0aHVidXNlcmNvbnRlbnQuY29tL3UvMTI5MTI3NjM4P3Y9NCIsImV2ZW50c191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3VzZXJzL2F3cy1wb3dlcnRvb2xzL2V2ZW50c3svcHJpdmFjeX0iLCJmb2xsb3dlcnNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS91c2Vycy9hd3MtcG93ZXJ0b29scy9mb2xsb3dlcnMiLCJmb2xsb3dpbmdfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS91c2Vycy9hd3MtcG93ZXJ0b29scy9mb2xsb3dpbmd7L290aGVyX3VzZXJ9IiwiZ2lzdHNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS91c2Vycy9hd3MtcG93ZXJ0b29scy9naXN0c3svZ2lzdF9pZH0iLCJncmF2YXRhcl9pZCI6IiIsImh0bWxfdXJsIjoiaHR0cHM6Ly9naXRodWIuY29tL2F3cy1wb3dlcnRvb2xzIiwiaWQiOjEyOTEyNzYzOCwibG9naW4iOiJhd3MtcG93ZXJ0b29scyIsIm5vZGVfaWQiOiJPX2tnRE9CN0pVMWciLCJvcmdhbml6YXRpb25zX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vdXNlcnMvYXdzLXBvd2VydG9vbHMvb3JncyIsInJlY2VpdmVkX2V2ZW50c191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3VzZXJzL2F3cy1wb3dlcnRvb2xzL3JlY2VpdmVkX2V2ZW50cyIsInJlcG9zX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vdXNlcnMvYXdzLXBvd2VydG9vbHMvcmVwb3MiLCJzaXRlX2FkbWluIjpmYWxzZSwic3RhcnJlZF91cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3VzZXJzL2F3cy1wb3dlcnRvb2xzL3N0YXJyZWR7L293bmVyfXsvcmVwb30iLCJzdWJzY3JpcHRpb25zX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vdXNlcnMvYXdzLXBvd2VydG9vbHMvc3Vic2NyaXB0aW9ucyIsInR5cGUiOiJPcmdhbml6YXRpb24iLCJ1cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3VzZXJzL2F3cy1wb3dlcnRvb2xzIiwidXNlcl92aWV3X3R5cGUiOiJwdWJsaWMifSwicHJpdmF0ZSI6ZmFsc2UsInB1bGxzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL3B1bGxzey9udW1iZXJ9IiwicHVzaGVkX2F0IjoiMjAyNS0wNC0yMVQyMToxNToyNFoiLCJyZWxlYXNlc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9yZWxlYXNlc3svaWR9Iiwic2l6ZSI6MTEwMzEwLCJzc2hfdXJsIjoiZ2l0QGdpdGh1Yi5jb206YXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uLmdpdCIsInN0YXJnYXplcnNfY291bnQiOjMwMjUsInN0YXJnYXplcnNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vc3RhcmdhemVycyIsInN0YXR1c2VzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL3N0YXR1c2VzL3tzaGF9Iiwic3Vic2NyaWJlcnNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vc3Vic2NyaWJlcnMiLCJzdWJzY3JpcHRpb25fdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vc3Vic2NyaXB0aW9uIiwic3ZuX3VybCI6Imh0dHBzOi8vZ2l0aHViLmNvbS9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24iLCJ0YWdzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL3RhZ3MiLCJ0ZWFtc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi90ZWFtcyIsInRvcGljcyI6WyJhd3MiLCJhd3MtbGFtYmRhIiwiaGFja3RvYmVyZmVzdCIsImxhbWJkYSIsInB5dGhvbiIsInNlcnZlcmxlc3MiXSwidHJlZXNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vZ2l0L3RyZWVzey9zaGF9IiwidXBkYXRlZF9hdCI6IjIwMjUtMDQtMjFUMjE6MTM6NTRaIiwidXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24iLCJ2aXNpYmlsaXR5IjoicHVibGljIiwid2F0Y2hlcnMiOjMwMjUsIndhdGNoZXJzX2NvdW50IjozMDI1LCJ3ZWJfY29tbWl0X3NpZ25vZmZfcmVxdWlyZWQiOnRydWV9LCJzY2hlZHVsZSI6IjAgOCAqICogMS01Iiwid29ya2Zsb3ciOiIuZ2l0aHViL3dvcmtmbG93cy9wcmUtcmVsZWFzZS55bWwifSwiZ2l0aHViX2hlYWRfcmVmIjoiIiwiZ2l0aHViX3JlZiI6InJlZnMvaGVhZHMvZGV2ZWxvcCIsImdpdGh1Yl9yZWZfdHlwZSI6ImJyYW5jaCIsImdpdGh1Yl9yZXBvc2l0b3J5X2lkIjoiMjIxOTE5Mzc5IiwiZ2l0aHViX3JlcG9zaXRvcnlfb3duZXIiOiJhd3MtcG93ZXJ0b29scyIsImdpdGh1Yl9yZXBvc2l0b3J5X293bmVyX2lkIjoiMTI5MTI3NjM4IiwiZ2l0aHViX3J1bl9hdHRlbXB0IjoiMSIsImdpdGh1Yl9ydW5faWQiOiIxNDU4OTc3MDkyNiIsImdpdGh1Yl9ydW5fbnVtYmVyIjoiMjI0IiwiZ2l0aHViX3NoYTEiOiIxZWZjNGI0NWRhODNiMjA5YjlhMzJhZTRiMzZjNzBhNjU2MWQ5MjcyIn19LCJtZXRhZGF0YSI6eyJidWlsZEludm9jYXRpb25JRCI6IjE0NTg5NzcwOTI2LTEiLCJjb21wbGV0ZW5lc3MiOnsicGFyYW1ldGVycyI6dHJ1ZSwiZW52aXJvbm1lbnQiOmZhbHNlLCJtYXRlcmlhbHMiOmZhbHNlfSwicmVwcm9kdWNpYmxlIjpmYWxzZX0sIm1hdGVyaWFscyI6W3sidXJpIjoiZ2l0K2h0dHBzOi8vZ2l0aHViLmNvbS9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob25AcmVmcy9oZWFkcy9kZXZlbG9wIiwiZGlnZXN0Ijp7InNoYTEiOiIxZWZjNGI0NWRhODNiMjA5YjlhMzJhZTRiMzZjNzBhNjU2MWQ5MjcyIn19XX19", "payloadType":"application/vnd.in-toto+json", "signatures":[{"sig":"MEUCIHo/XmsaAv8u4b1jDo6AB3BWeiRqWi15MXQRSoTpgsrUAiEAvXZDtH+MKkCF1ohfakRyUbjI4HEyJ9mJMPTFRDm14ZM="}]}}
\ No newline at end of file
diff --git a/provenance/3.9.1a4/multiple.intoto.jsonl b/provenance/3.9.1a4/multiple.intoto.jsonl
new file mode 100644
index 00000000000..9073211bfa4
--- /dev/null
+++ b/provenance/3.9.1a4/multiple.intoto.jsonl
@@ -0,0 +1 @@
+{"mediaType":"application/vnd.dev.sigstore.bundle.v0.3+json", "verificationMaterial":{"certificate":{"rawBytes":"MIIHZzCCBu2gAwIBAgIUWt9yDusq97fU3qBz7HZjZRuUjqQwCgYIKoZIzj0EAwMwNzEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MR4wHAYDVQQDExVzaWdzdG9yZS1pbnRlcm1lZGlhdGUwHhcNMjUwNDAxMDgwNzM3WhcNMjUwNDAxMDgxNzM3WjAAMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEIf1djGmC1NxbPAXYfpI2F5Gaskpabz0rjUgGE/Iigm9jDOgbsTkk72THwoMshrRkzXMMg3nZXN1zikhjhCODB6OCBgwwggYIMA4GA1UdDwEB/wQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcDAzAdBgNVHQ4EFgQUHrQzflxVmxkjszYdfHKR8TYC6BgwHwYDVR0jBBgwFoAU39Ppz1YkEZb5qNjpKFWixi4YZD8wgYQGA1UdEQEB/wR6MHiGdmh0dHBzOi8vZ2l0aHViLmNvbS9zbHNhLWZyYW1ld29yay9zbHNhLWdpdGh1Yi1nZW5lcmF0b3IvLmdpdGh1Yi93b3JrZmxvd3MvZ2VuZXJhdG9yX2dlbmVyaWNfc2xzYTMueW1sQHJlZnMvdGFncy92Mi4xLjAwOQYKKwYBBAGDvzABAQQraHR0cHM6Ly90b2tlbi5hY3Rpb25zLmdpdGh1YnVzZXJjb250ZW50LmNvbTAWBgorBgEEAYO/MAECBAhzY2hlZHVsZTA2BgorBgEEAYO/MAEDBCg1NDAwYzQzNTIwYjRiNDVjMGFhYTU3Y2IwMmRhODNiNzc2MTFkMTBmMBkGCisGAQQBg78wAQQEC1ByZS1SZWxlYXNlMDUGCisGAQQBg78wAQUEJ2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbjAgBgorBgEEAYO/MAEGBBJyZWZzL2hlYWRzL2RldmVsb3AwOwYKKwYBBAGDvzABCAQtDCtodHRwczovL3Rva2VuLmFjdGlvbnMuZ2l0aHVidXNlcmNvbnRlbnQuY29tMIGGBgorBgEEAYO/MAEJBHgMdmh0dHBzOi8vZ2l0aHViLmNvbS9zbHNhLWZyYW1ld29yay9zbHNhLWdpdGh1Yi1nZW5lcmF0b3IvLmdpdGh1Yi93b3JrZmxvd3MvZ2VuZXJhdG9yX2dlbmVyaWNfc2xzYTMueW1sQHJlZnMvdGFncy92Mi4xLjAwOAYKKwYBBAGDvzABCgQqDChmN2RkOGM1NGMyMDY3YmFmYzEyY2E3YTU1NTk1ZDVlZTliNzUyMDRhMB0GCisGAQQBg78wAQsEDwwNZ2l0aHViLWhvc3RlZDBKBgorBgEEAYO/MAEMBDwMOmh0dHBzOi8vZ2l0aHViLmNvbS9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24wOAYKKwYBBAGDvzABDQQqDCg1NDAwYzQzNTIwYjRiNDVjMGFhYTU3Y2IwMmRhODNiNzc2MTFkMTBmMCIGCisGAQQBg78wAQ4EFAwScmVmcy9oZWFkcy9kZXZlbG9wMBkGCisGAQQBg78wAQ8ECwwJMjIxOTE5Mzc5MDEGCisGAQQBg78wARAEIwwhaHR0cHM6Ly9naXRodWIuY29tL2F3cy1wb3dlcnRvb2xzMBkGCisGAQQBg78wAREECwwJMTI5MTI3NjM4MH8GCisGAQQBg78wARIEcQxvaHR0cHM6Ly9naXRodWIuY29tL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi8uZ2l0aHViL3dvcmtmbG93cy9wcmUtcmVsZWFzZS55bWxAcmVmcy9oZWFkcy9kZXZlbG9wMDgGCisGAQQBg78wARMEKgwoNTQwMGM0MzUyMGI0YjQ1YzBhYWE1N2NiMDJkYTgzYjc3NjExZDEwZjAYBgorBgEEAYO/MAEUBAoMCHNjaGVkdWxlMG4GCisGAQQBg78wARUEYAxeaHR0cHM6Ly9naXRodWIuY29tL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9hY3Rpb25zL3J1bnMvMTQxOTAzNzQ2OTAvYXR0ZW1wdHMvMTAWBgorBgEEAYO/MAEWBAgMBnB1YmxpYzCBiwYKKwYBBAHWeQIEAgR9BHsAeQB3AN09MGrGxxEyYxkeHJlnNwKiSl643jyt/4eKcoAvKe6OAAABlfBkQyYAAAQDAEgwRgIhAJD18e2LWHNU8/av+bgpVRmpyQGo+GsybVU8mNt2Kb7eAiEA9uS2/W0auOJl5VKBDgSG3SAkvc6NQrxDpV+EPTA2twswCgYIKoZIzj0EAwMDaAAwZQIwDQ2QsSVHSjA0oZIU6YA2M7JX2t1icwJAzvU+C5Jo4TuIKPeCDCciKXCmOpNaBFdNAjEA0uI5xsTfCeV4rdvDHTN/aSWTg1kpu4VR4El0Oh3lSZsrD2PKFU8IQnqxgxHAWoq3"}, "tlogEntries":[{"logIndex":"190737904", "logId":{"keyId":"wNI9atQGlz+VWfO6LRygH4QUfY/8W4RFwiT5i5WRgB0="}, "kindVersion":{"kind":"dsse", "version":"0.0.1"}, "integratedTime":"1743494857", "inclusionPromise":{"signedEntryTimestamp":"MEQCIAkCL02N+FcjPuniv99jDD6OGgxOuXEASYZZQHi9YBBuAiAbeE/uNTB7LXWrTAGtDUtzhD4zdi8cL/7/ltmYRizghQ=="}, "inclusionProof":{"logIndex":"68833642", "rootHash":"tWUAS4GUbaL6fD8LsXIy9fKUXkfInetauCX88jCdmqY=", "treeSize":"68833645", "hashes":["xDOyLCRwd7lgbRMVrfMQETNMOGlVfZCtRa2mlrkLW0I=", "014/6vfmvaQPNHwXSrRhRhMUzJVjLG5SgTvaG9sT8/0=", "7m5mJOlu0kR0vKlkFVij5gUzxHZhRhpf2DjRlueDZBs=", "ImWi+dkhuPRCSCdGgkU0vXL81/tF8oR+n0lWlVo4f78=", "eIsf2w+1J8lMnE8nI5JfME3WkKfEpGU+bkLOMt+OEbM=", "nbHQznO5WOr/OuiCAAwhbDe7EsHa99E/WombM3I8B+k=", "c3DrNt4HUcZ9+ynuzZOT4w1D3W+Qx3kdR1Rh1qfutAM=", "CykgdYuaiZ1Ve7MkTZMfQrQql87ie1AY5X2G8CzCH1k=", "NKs9YK8T+KZwHSCxE+tT3C7U0ZqtrYEmmu5XMdjFejw=", "C6j3RP9A5bOslRxK+TBdoU0Soc5q8vjXoPE8qpbYx7c=", "ysnDAgw77VgeyZcD6CrxicbZfdaDb+2f07Uej/1sJ2s=", "0h8nhcle5C9UpTvzBlAM62Top+G4DS282xnhunrGDFs=", "7v8qPHNDLerpduaMx06eb/MwgoQwczTn/cYGKX/9wZ4="], "checkpoint":{"envelope":"rekor.sigstore.dev - 1193050959916656506\n68833645\ntWUAS4GUbaL6fD8LsXIy9fKUXkfInetauCX88jCdmqY=\n\n— rekor.sigstore.dev wNI9ajBEAiBKvH6+xiVwfaYlDevinBbfP9p60PwfnlOF5KXj9cGHCgIgQWxSt9CgIMbplwV+CTPNc90C5w9bDT4H2FT8YngNq28=\n"}}, "canonicalizedBody":"eyJhcGlWZXJzaW9uIjoiMC4wLjEiLCJraW5kIjoiZHNzZSIsInNwZWMiOnsiZW52ZWxvcGVIYXNoIjp7ImFsZ29yaXRobSI6InNoYTI1NiIsInZhbHVlIjoiNTM0N2ZmOTg1YWRiMTE1NWU2YWMxZDk4NTJjNThiN2M4MDFjODM0OTMzYjA1NDAzNWMwMzMzYjFjZTVjZmEwMyJ9LCJwYXlsb2FkSGFzaCI6eyJhbGdvcml0aG0iOiJzaGEyNTYiLCJ2YWx1ZSI6ImZlODdkMTRhYzM4MGU0ZTljZTUzMmU4NjQ2NGEzMDg3MjMxNGRiMjQ0MGRjZDEyNTkwNDRmYzJiNGIxNTZiZTQifSwic2lnbmF0dXJlcyI6W3sic2lnbmF0dXJlIjoiTUVZQ0lRRGNhNUVRWnBPUDJvN1gxNWlHYWN0MnB0dE85U3JIN3hNeWpTWGNCeWZ1SGdJaEFMRnQzcmpzZ1RYbW1OZWdsU2pYQWNLUXRscVRZRW9YQ2kraWhHTThuQ1hMIiwidmVyaWZpZXIiOiJMUzB0TFMxQ1JVZEpUaUJEUlZKVVNVWkpRMEZVUlMwdExTMHRDazFKU1VoYWVrTkRRblV5WjBGM1NVSkJaMGxWVjNRNWVVUjFjM0U1TjJaVk0zRkNlamRJV21wYVVuVlZhbkZSZDBObldVbExiMXBKZW1vd1JVRjNUWGNLVG5wRlZrMUNUVWRCTVZWRlEyaE5UV015Ykc1ak0xSjJZMjFWZFZwSFZqSk5ValIzU0VGWlJGWlJVVVJGZUZaNllWZGtlbVJIT1hsYVV6RndZbTVTYkFwamJURnNXa2RzYUdSSFZYZElhR05PVFdwVmQwNUVRWGhOUkdkM1RucE5NMWRvWTA1TmFsVjNUa1JCZUUxRVozaE9lazB6VjJwQlFVMUdhM2RGZDFsSUNrdHZXa2w2YWpCRFFWRlpTVXR2V2tsNmFqQkVRVkZqUkZGblFVVkpaakZrYWtkdFF6Rk9lR0pRUVZoWlpuQkpNa1kxUjJGemEzQmhZbm93Y21wVlowY0tSUzlKYVdkdE9XcEVUMmRpYzFScmF6Y3lWRWgzYjAxemFISlNhM3BZVFUxbk0yNWFXRTR4ZW1scmFHcG9RMDlFUWpaUFEwSm5kM2RuWjFsSlRVRTBSd3BCTVZWa1JIZEZRaTkzVVVWQmQwbElaMFJCVkVKblRsWklVMVZGUkVSQlMwSm5aM0pDWjBWR1FsRmpSRUY2UVdSQ1owNVdTRkUwUlVablVWVkljbEY2Q21ac2VGWnRlR3RxYzNwWlpHWklTMUk0VkZsRE5rSm5kMGgzV1VSV1VqQnFRa0puZDBadlFWVXpPVkJ3ZWpGWmEwVmFZalZ4VG1wd1MwWlhhWGhwTkZrS1drUTRkMmRaVVVkQk1WVmtSVkZGUWk5M1VqWk5TR2xIWkcxb01HUklRbnBQYVRoMldqSnNNR0ZJVm1sTWJVNTJZbE01ZW1KSVRtaE1WMXA1V1ZjeGJBcGtNamw1WVhrNWVtSklUbWhNVjJSd1pFZG9NVmxwTVc1YVZ6VnNZMjFHTUdJelNYWk1iV1J3WkVkb01WbHBPVE5pTTBweVdtMTRkbVF6VFhaYU1sWjFDbHBZU21oa1J6bDVXREprYkdKdFZubGhWMDVtWXpKNGVsbFVUWFZsVnpGelVVaEtiRnB1VFhaa1IwWnVZM2s1TWsxcE5IaE1ha0YzVDFGWlMwdDNXVUlLUWtGSFJIWjZRVUpCVVZGeVlVaFNNR05JVFRaTWVUa3dZakowYkdKcE5XaFpNMUp3WWpJMWVreHRaSEJrUjJneFdXNVdlbHBZU21waU1qVXdXbGMxTUFwTWJVNTJZbFJCVjBKbmIzSkNaMFZGUVZsUEwwMUJSVU5DUVdoNldUSm9iRnBJVm5OYVZFRXlRbWR2Y2tKblJVVkJXVTh2VFVGRlJFSkRaekZPUkVGM0NsbDZVWHBPVkVsM1dXcFNhVTVFVm1wTlIwWm9XVlJWTTFreVNYZE5iVkpvVDBST2FVNTZZekpOVkVaclRWUkNiVTFDYTBkRGFYTkhRVkZSUW1jM09IY0tRVkZSUlVNeFFubGFVekZUV2xkNGJGbFlUbXhOUkZWSFEybHpSMEZSVVVKbk56aDNRVkZWUlVveVJqTmplVEYzWWpOa2JHTnVVblppTW5oNlRETkNkZ3BrTWxaNVpFYzVkbUpJVFhSaVIwWjBXVzFTYUV4WVFqVmtSMmgyWW1wQlowSm5iM0pDWjBWRlFWbFBMMDFCUlVkQ1FrcDVXbGRhZWt3eWFHeFpWMUo2Q2t3eVVteGtiVlp6WWpOQmQwOTNXVXRMZDFsQ1FrRkhSSFo2UVVKRFFWRjBSRU4wYjJSSVVuZGplbTkyVEROU2RtRXlWblZNYlVacVpFZHNkbUp1VFhVS1dqSnNNR0ZJVm1sa1dFNXNZMjFPZG1KdVVteGlibEYxV1RJNWRFMUpSMGRDWjI5eVFtZEZSVUZaVHk5TlFVVktRa2huVFdSdGFEQmtTRUo2VDJrNGRncGFNbXd3WVVoV2FVeHRUblppVXpsNllraE9hRXhYV25sWlZ6RnNaREk1ZVdGNU9YcGlTRTVvVEZka2NHUkhhREZaYVRGdVdsYzFiR050UmpCaU0wbDJDa3h0WkhCa1IyZ3hXV2s1TTJJelNuSmFiWGgyWkROTmRsb3lWblZhV0Vwb1pFYzVlVmd5Wkd4aWJWWjVZVmRPWm1NeWVIcFpWRTExWlZjeGMxRklTbXdLV201TmRtUkhSbTVqZVRreVRXazBlRXhxUVhkUFFWbExTM2RaUWtKQlIwUjJla0ZDUTJkUmNVUkRhRzFPTWxKclQwZE5NVTVIVFhsTlJGa3pXVzFHYlFwWmVrVjVXVEpGTTFsVVZURk9WR3N4V2tSV2JGcFViR2xPZWxWNVRVUlNhRTFDTUVkRGFYTkhRVkZSUW1jM09IZEJVWE5GUkhkM1Rsb3liREJoU0ZacENreFhhSFpqTTFKc1drUkNTMEpuYjNKQ1owVkZRVmxQTDAxQlJVMUNSSGROVDIxb01HUklRbnBQYVRoMldqSnNNR0ZJVm1sTWJVNTJZbE01YUdRelRYUUtZMGM1TTFwWVNqQmlNamx6WTNrNWQySXpaR3hqYmxKMllqSjRla3hYZUdoaVYwcHJXVk14ZDJWWVVtOWlNalIzVDBGWlMwdDNXVUpDUVVkRWRucEJRZ3BFVVZGeFJFTm5NVTVFUVhkWmVsRjZUbFJKZDFscVVtbE9SRlpxVFVkR2FGbFVWVE5aTWtsM1RXMVNhRTlFVG1sT2VtTXlUVlJHYTAxVVFtMU5RMGxIQ2tOcGMwZEJVVkZDWnpjNGQwRlJORVZHUVhkVFkyMVdiV041T1c5YVYwWnJZM2s1YTFwWVdteGlSemwzVFVKclIwTnBjMGRCVVZGQ1p6YzRkMEZST0VVS1EzZDNTazFxU1hoUFZFVTFUWHBqTlUxRVJVZERhWE5IUVZGUlFtYzNPSGRCVWtGRlNYZDNhR0ZJVWpCalNFMDJUSGs1Ym1GWVVtOWtWMGwxV1RJNWRBcE1Na1l6WTNreGQySXpaR3hqYmxKMllqSjRlazFDYTBkRGFYTkhRVkZSUW1jM09IZEJVa1ZGUTNkM1NrMVVTVFZOVkVrelRtcE5ORTFJT0VkRGFYTkhDa0ZSVVVKbk56aDNRVkpKUldOUmVIWmhTRkl3WTBoTk5reDVPVzVoV0ZKdlpGZEpkVmt5T1hSTU1rWXpZM2t4ZDJJelpHeGpibEoyWWpKNGVrd3pRbllLWkRKV2VXUkhPWFppU0UxMFlrZEdkRmx0VW1oTVdFSTFaRWRvZG1KcE9IVmFNbXd3WVVoV2FVd3paSFpqYlhSdFlrYzVNMk41T1hkamJWVjBZMjFXY3dwYVYwWjZXbE0xTldKWGVFRmpiVlp0WTNrNWIxcFhSbXRqZVRscldsaGFiR0pIT1hkTlJHZEhRMmx6UjBGUlVVSm5OemgzUVZKTlJVdG5kMjlPVkZGM0NrMUhUVEJOZWxWNVRVZEpNRmxxVVRGWmVrSm9XVmRGTVU0eVRtbE5SRXByV1ZSbmVsbHFZek5PYWtWNFdrUkZkMXBxUVZsQ1oyOXlRbWRGUlVGWlR5OEtUVUZGVlVKQmIwMURTRTVxWVVkV2EyUlhlR3hOUnpSSFEybHpSMEZSVVVKbk56aDNRVkpWUlZsQmVHVmhTRkl3WTBoTk5reDVPVzVoV0ZKdlpGZEpkUXBaTWpsMFRESkdNMk41TVhkaU0yUnNZMjVTZG1JeWVIcE1NMEoyWkRKV2VXUkhPWFppU0UxMFlrZEdkRmx0VW1oTVdFSTFaRWRvZG1KcE9XaFpNMUp3Q21JeU5YcE1NMG94WW01TmRrMVVVWGhQVkVGNlRucFJNazlVUVhaWldGSXdXbGN4ZDJSSVRYWk5WRUZYUW1kdmNrSm5SVVZCV1U4dlRVRkZWMEpCWjAwS1FtNUNNVmx0ZUhCWmVrTkNhWGRaUzB0M1dVSkNRVWhYWlZGSlJVRm5VamxDU0hOQlpWRkNNMEZPTURsTlIzSkhlSGhGZVZsNGEyVklTbXh1VG5kTGFRcFRiRFkwTTJwNWRDODBaVXRqYjBGMlMyVTJUMEZCUVVKc1prSnJVWGxaUVVGQlVVUkJSV2QzVW1kSmFFRktSREU0WlRKTVYwaE9WVGd2WVhZclltZHdDbFpTYlhCNVVVZHZLMGR6ZVdKV1ZUaHRUblF5UzJJM1pVRnBSVUU1ZFZNeUwxY3dZWFZQU213MVZrdENSR2RUUnpOVFFXdDJZelpPVVhKNFJIQldLMFVLVUZSQk1uUjNjM2REWjFsSlMyOWFTWHBxTUVWQmQwMUVZVUZCZDFwUlNYZEVVVEpSYzFOV1NGTnFRVEJ2V2tsVk5sbEJNazAzU2xneWRERnBZM2RLUVFwNmRsVXJRelZLYnpSVWRVbExVR1ZEUkVOamFVdFlRMjFQY0U1aFFrWmtUa0ZxUlVFd2RVazFlSE5VWmtObFZqUnlaSFpFU0ZST0wyRlRWMVJuTVd0d0NuVTBWbEkwUld3d1QyZ3piRk5hYzNKRU1sQkxSbFU0U1ZGdWNYaG5lRWhCVjI5eE13b3RMUzB0TFVWT1JDQkRSVkpVU1VaSlEwRlVSUzB0TFMwdENnPT0ifV19fQ=="}]}, "dsseEnvelope":{"payload":"eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjAuMSIsInByZWRpY2F0ZVR5cGUiOiJodHRwczovL3Nsc2EuZGV2L3Byb3ZlbmFuY2UvdjAuMiIsInN1YmplY3QiOlt7Im5hbWUiOiIuL2F3c19sYW1iZGFfcG93ZXJ0b29scy0zLjkuMWE0LXB5My1ub25lLWFueS53aGwiLCJkaWdlc3QiOnsic2hhMjU2IjoiYjdlNTEzYTRhMjdkMzRkZGZiNmUzNzFiYzg4NDcyYTVjMzA0OTJhZGI5M2U2MWRhMTczNGRkNjU0MTUxOGVjYiJ9fSx7Im5hbWUiOiIuL2F3c19sYW1iZGFfcG93ZXJ0b29scy0zLjkuMWE0LnRhci5neiIsImRpZ2VzdCI6eyJzaGEyNTYiOiJiODUzYjNkMDI5ZDc5NmRiMjZlNzdmMmRiOWI5ZDA2ZjJlYjMzMjE2NTQzOTJkYjQ4NjNjYzhiZmZiMzRiZTY5In19XSwicHJlZGljYXRlIjp7ImJ1aWxkZXIiOnsiaWQiOiJodHRwczovL2dpdGh1Yi5jb20vc2xzYS1mcmFtZXdvcmsvc2xzYS1naXRodWItZ2VuZXJhdG9yLy5naXRodWIvd29ya2Zsb3dzL2dlbmVyYXRvcl9nZW5lcmljX3Nsc2EzLnltbEByZWZzL3RhZ3MvdjIuMS4wIn0sImJ1aWxkVHlwZSI6Imh0dHBzOi8vZ2l0aHViLmNvbS9zbHNhLWZyYW1ld29yay9zbHNhLWdpdGh1Yi1nZW5lcmF0b3IvZ2VuZXJpY0B2MSIsImludm9jYXRpb24iOnsiY29uZmlnU291cmNlIjp7InVyaSI6ImdpdCtodHRwczovL2dpdGh1Yi5jb20vYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uQHJlZnMvaGVhZHMvZGV2ZWxvcCIsImRpZ2VzdCI6eyJzaGExIjoiNTQwMGM0MzUyMGI0YjQ1YzBhYWE1N2NiMDJkYTgzYjc3NjExZDEwZiJ9LCJlbnRyeVBvaW50IjoiLmdpdGh1Yi93b3JrZmxvd3MvcHJlLXJlbGVhc2UueW1sIn0sImVudmlyb25tZW50Ijp7ImdpdGh1Yl9hY3RvciI6ImxlYW5kcm9kYW1hc2NlbmEiLCJnaXRodWJfYWN0b3JfaWQiOiI0Mjk1MTczIiwiZ2l0aHViX2Jhc2VfcmVmIjoiIiwiZ2l0aHViX2V2ZW50X25hbWUiOiJzY2hlZHVsZSIsImdpdGh1Yl9ldmVudF9wYXlsb2FkIjp7ImVudGVycHJpc2UiOnsiYXZhdGFyX3VybCI6Imh0dHBzOi8vYXZhdGFycy5naXRodWJ1c2VyY29udGVudC5jb20vYi8xMjkwP3Y9NCIsImNyZWF0ZWRfYXQiOiIyMDE5LTExLTEzVDE4OjA1OjQxWiIsImRlc2NyaXB0aW9uIjoiIiwiaHRtbF91cmwiOiJodHRwczovL2dpdGh1Yi5jb20vZW50ZXJwcmlzZXMvYW1hem9uIiwiaWQiOjEyOTAsIm5hbWUiOiJBbWF6b24iLCJub2RlX2lkIjoiTURFd09rVnVkR1Z5Y0hKcGMyVXhNamt3Iiwic2x1ZyI6ImFtYXpvbiIsInVwZGF0ZWRfYXQiOiIyMDI0LTA5LTMwVDIxOjAyOjMwWiIsIndlYnNpdGVfdXJsIjoiaHR0cHM6Ly93d3cuYW1hem9uLmNvbS8ifSwib3JnYW5pemF0aW9uIjp7ImF2YXRhcl91cmwiOiJodHRwczovL2F2YXRhcnMuZ2l0aHVidXNlcmNvbnRlbnQuY29tL3UvMTI5MTI3NjM4P3Y9NCIsImRlc2NyaXB0aW9uIjoiIiwiZXZlbnRzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vb3Jncy9hd3MtcG93ZXJ0b29scy9ldmVudHMiLCJob29rc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL29yZ3MvYXdzLXBvd2VydG9vbHMvaG9va3MiLCJpZCI6MTI5MTI3NjM4LCJpc3N1ZXNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9vcmdzL2F3cy1wb3dlcnRvb2xzL2lzc3VlcyIsImxvZ2luIjoiYXdzLXBvd2VydG9vbHMiLCJtZW1iZXJzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vb3Jncy9hd3MtcG93ZXJ0b29scy9tZW1iZXJzey9tZW1iZXJ9Iiwibm9kZV9pZCI6Ik9fa2dET0I3SlUxZyIsInB1YmxpY19tZW1iZXJzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vb3Jncy9hd3MtcG93ZXJ0b29scy9wdWJsaWNfbWVtYmVyc3svbWVtYmVyfSIsInJlcG9zX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vb3Jncy9hd3MtcG93ZXJ0b29scy9yZXBvcyIsInVybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vb3Jncy9hd3MtcG93ZXJ0b29scyJ9LCJyZXBvc2l0b3J5Ijp7ImFsbG93X2ZvcmtpbmciOnRydWUsImFyY2hpdmVfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24ve2FyY2hpdmVfZm9ybWF0fXsvcmVmfSIsImFyY2hpdmVkIjpmYWxzZSwiYXNzaWduZWVzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2Fzc2lnbmVlc3svdXNlcn0iLCJibG9ic191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9naXQvYmxvYnN7L3NoYX0iLCJicmFuY2hlc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9icmFuY2hlc3svYnJhbmNofSIsImNsb25lX3VybCI6Imh0dHBzOi8vZ2l0aHViLmNvbS9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24uZ2l0IiwiY29sbGFib3JhdG9yc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9jb2xsYWJvcmF0b3Jzey9jb2xsYWJvcmF0b3J9IiwiY29tbWVudHNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vY29tbWVudHN7L251bWJlcn0iLCJjb21taXRzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2NvbW1pdHN7L3NoYX0iLCJjb21wYXJlX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2NvbXBhcmUve2Jhc2V9Li4ue2hlYWR9IiwiY29udGVudHNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vY29udGVudHMveytwYXRofSIsImNvbnRyaWJ1dG9yc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9jb250cmlidXRvcnMiLCJjcmVhdGVkX2F0IjoiMjAxOS0xMS0xNVQxMjoyNjoxMloiLCJjdXN0b21fcHJvcGVydGllcyI6e30sImRlZmF1bHRfYnJhbmNoIjoiZGV2ZWxvcCIsImRlcGxveW1lbnRzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2RlcGxveW1lbnRzIiwiZGVzY3JpcHRpb24iOiJBIGRldmVsb3BlciB0b29sa2l0IHRvIGltcGxlbWVudCBTZXJ2ZXJsZXNzIGJlc3QgcHJhY3RpY2VzIGFuZCBpbmNyZWFzZSBkZXZlbG9wZXIgdmVsb2NpdHkuIiwiZGlzYWJsZWQiOmZhbHNlLCJkb3dubG9hZHNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vZG93bmxvYWRzIiwiZXZlbnRzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2V2ZW50cyIsImZvcmsiOmZhbHNlLCJmb3JrcyI6NDE1LCJmb3Jrc19jb3VudCI6NDE1LCJmb3Jrc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9mb3JrcyIsImZ1bGxfbmFtZSI6ImF3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbiIsImdpdF9jb21taXRzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2dpdC9jb21taXRzey9zaGF9IiwiZ2l0X3JlZnNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vZ2l0L3JlZnN7L3NoYX0iLCJnaXRfdGFnc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9naXQvdGFnc3svc2hhfSIsImdpdF91cmwiOiJnaXQ6Ly9naXRodWIuY29tL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi5naXQiLCJoYXNfZGlzY3Vzc2lvbnMiOnRydWUsImhhc19kb3dubG9hZHMiOnRydWUsImhhc19pc3N1ZXMiOnRydWUsImhhc19wYWdlcyI6ZmFsc2UsImhhc19wcm9qZWN0cyI6dHJ1ZSwiaGFzX3dpa2kiOmZhbHNlLCJob21lcGFnZSI6Imh0dHBzOi8vZG9jcy5wb3dlcnRvb2xzLmF3cy5kZXYvbGFtYmRhL3B5dGhvbi9sYXRlc3QvIiwiaG9va3NfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vaG9va3MiLCJodG1sX3VybCI6Imh0dHBzOi8vZ2l0aHViLmNvbS9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24iLCJpZCI6MjIxOTE5Mzc5LCJpc190ZW1wbGF0ZSI6ZmFsc2UsImlzc3VlX2NvbW1lbnRfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vaXNzdWVzL2NvbW1lbnRzey9udW1iZXJ9IiwiaXNzdWVfZXZlbnRzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2lzc3Vlcy9ldmVudHN7L251bWJlcn0iLCJpc3N1ZXNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vaXNzdWVzey9udW1iZXJ9Iiwia2V5c191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9rZXlzey9rZXlfaWR9IiwibGFiZWxzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2xhYmVsc3svbmFtZX0iLCJsYW5ndWFnZSI6IlB5dGhvbiIsImxhbmd1YWdlc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9sYW5ndWFnZXMiLCJsaWNlbnNlIjp7ImtleSI6Im1pdC0wIiwibmFtZSI6Ik1JVCBObyBBdHRyaWJ1dGlvbiIsIm5vZGVfaWQiOiJNRGM2VEdsalpXNXpaVFF4Iiwic3BkeF9pZCI6Ik1JVC0wIiwidXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9saWNlbnNlcy9taXQtMCJ9LCJtZXJnZXNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vbWVyZ2VzIiwibWlsZXN0b25lc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9taWxlc3RvbmVzey9udW1iZXJ9IiwibWlycm9yX3VybCI6bnVsbCwibmFtZSI6InBvd2VydG9vbHMtbGFtYmRhLXB5dGhvbiIsIm5vZGVfaWQiOiJNREV3T2xKbGNHOXphWFJ2Y25reU1qRTVNVGt6TnprPSIsIm5vdGlmaWNhdGlvbnNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vbm90aWZpY2F0aW9uc3s/c2luY2UsYWxsLHBhcnRpY2lwYXRpbmd9Iiwib3Blbl9pc3N1ZXMiOjU5LCJvcGVuX2lzc3Vlc19jb3VudCI6NTksIm93bmVyIjp7ImF2YXRhcl91cmwiOiJodHRwczovL2F2YXRhcnMuZ2l0aHVidXNlcmNvbnRlbnQuY29tL3UvMTI5MTI3NjM4P3Y9NCIsImV2ZW50c191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3VzZXJzL2F3cy1wb3dlcnRvb2xzL2V2ZW50c3svcHJpdmFjeX0iLCJmb2xsb3dlcnNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS91c2Vycy9hd3MtcG93ZXJ0b29scy9mb2xsb3dlcnMiLCJmb2xsb3dpbmdfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS91c2Vycy9hd3MtcG93ZXJ0b29scy9mb2xsb3dpbmd7L290aGVyX3VzZXJ9IiwiZ2lzdHNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS91c2Vycy9hd3MtcG93ZXJ0b29scy9naXN0c3svZ2lzdF9pZH0iLCJncmF2YXRhcl9pZCI6IiIsImh0bWxfdXJsIjoiaHR0cHM6Ly9naXRodWIuY29tL2F3cy1wb3dlcnRvb2xzIiwiaWQiOjEyOTEyNzYzOCwibG9naW4iOiJhd3MtcG93ZXJ0b29scyIsIm5vZGVfaWQiOiJPX2tnRE9CN0pVMWciLCJvcmdhbml6YXRpb25zX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vdXNlcnMvYXdzLXBvd2VydG9vbHMvb3JncyIsInJlY2VpdmVkX2V2ZW50c191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3VzZXJzL2F3cy1wb3dlcnRvb2xzL3JlY2VpdmVkX2V2ZW50cyIsInJlcG9zX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vdXNlcnMvYXdzLXBvd2VydG9vbHMvcmVwb3MiLCJzaXRlX2FkbWluIjpmYWxzZSwic3RhcnJlZF91cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3VzZXJzL2F3cy1wb3dlcnRvb2xzL3N0YXJyZWR7L293bmVyfXsvcmVwb30iLCJzdWJzY3JpcHRpb25zX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vdXNlcnMvYXdzLXBvd2VydG9vbHMvc3Vic2NyaXB0aW9ucyIsInR5cGUiOiJPcmdhbml6YXRpb24iLCJ1cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3VzZXJzL2F3cy1wb3dlcnRvb2xzIiwidXNlcl92aWV3X3R5cGUiOiJwdWJsaWMifSwicHJpdmF0ZSI6ZmFsc2UsInB1bGxzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL3B1bGxzey9udW1iZXJ9IiwicHVzaGVkX2F0IjoiMjAyNS0wMy0zMVQyMTozOTowNFoiLCJyZWxlYXNlc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9yZWxlYXNlc3svaWR9Iiwic2l6ZSI6MTA0OTA4LCJzc2hfdXJsIjoiZ2l0QGdpdGh1Yi5jb206YXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uLmdpdCIsInN0YXJnYXplcnNfY291bnQiOjMwMTgsInN0YXJnYXplcnNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vc3RhcmdhemVycyIsInN0YXR1c2VzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL3N0YXR1c2VzL3tzaGF9Iiwic3Vic2NyaWJlcnNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vc3Vic2NyaWJlcnMiLCJzdWJzY3JpcHRpb25fdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vc3Vic2NyaXB0aW9uIiwic3ZuX3VybCI6Imh0dHBzOi8vZ2l0aHViLmNvbS9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24iLCJ0YWdzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL3RhZ3MiLCJ0ZWFtc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi90ZWFtcyIsInRvcGljcyI6WyJhd3MiLCJhd3MtbGFtYmRhIiwiaGFja3RvYmVyZmVzdCIsImxhbWJkYSIsInB5dGhvbiIsInNlcnZlcmxlc3MiXSwidHJlZXNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vZ2l0L3RyZWVzey9zaGF9IiwidXBkYXRlZF9hdCI6IjIwMjUtMDMtMzFUMTA6NDg6MzhaIiwidXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24iLCJ2aXNpYmlsaXR5IjoicHVibGljIiwid2F0Y2hlcnMiOjMwMTgsIndhdGNoZXJzX2NvdW50IjozMDE4LCJ3ZWJfY29tbWl0X3NpZ25vZmZfcmVxdWlyZWQiOnRydWV9LCJzY2hlZHVsZSI6IjAgOCAqICogMS01Iiwid29ya2Zsb3ciOiIuZ2l0aHViL3dvcmtmbG93cy9wcmUtcmVsZWFzZS55bWwifSwiZ2l0aHViX2hlYWRfcmVmIjoiIiwiZ2l0aHViX3JlZiI6InJlZnMvaGVhZHMvZGV2ZWxvcCIsImdpdGh1Yl9yZWZfdHlwZSI6ImJyYW5jaCIsImdpdGh1Yl9yZXBvc2l0b3J5X2lkIjoiMjIxOTE5Mzc5IiwiZ2l0aHViX3JlcG9zaXRvcnlfb3duZXIiOiJhd3MtcG93ZXJ0b29scyIsImdpdGh1Yl9yZXBvc2l0b3J5X293bmVyX2lkIjoiMTI5MTI3NjM4IiwiZ2l0aHViX3J1bl9hdHRlbXB0IjoiMSIsImdpdGh1Yl9ydW5faWQiOiIxNDE5MDM3NDY5MCIsImdpdGh1Yl9ydW5fbnVtYmVyIjoiMjA5IiwiZ2l0aHViX3NoYTEiOiI1NDAwYzQzNTIwYjRiNDVjMGFhYTU3Y2IwMmRhODNiNzc2MTFkMTBmIn19LCJtZXRhZGF0YSI6eyJidWlsZEludm9jYXRpb25JRCI6IjE0MTkwMzc0NjkwLTEiLCJjb21wbGV0ZW5lc3MiOnsicGFyYW1ldGVycyI6dHJ1ZSwiZW52aXJvbm1lbnQiOmZhbHNlLCJtYXRlcmlhbHMiOmZhbHNlfSwicmVwcm9kdWNpYmxlIjpmYWxzZX0sIm1hdGVyaWFscyI6W3sidXJpIjoiZ2l0K2h0dHBzOi8vZ2l0aHViLmNvbS9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob25AcmVmcy9oZWFkcy9kZXZlbG9wIiwiZGlnZXN0Ijp7InNoYTEiOiI1NDAwYzQzNTIwYjRiNDVjMGFhYTU3Y2IwMmRhODNiNzc2MTFkMTBmIn19XX19", "payloadType":"application/vnd.in-toto+json", "signatures":[{"sig":"MEYCIQDca5EQZpOP2o7X15iGact2pttO9SrH7xMyjSXcByfuHgIhALFt3rjsgTXmmNeglSjXAcKQtlqTYEoXCi+ihGM8nCXL"}]}}
\ No newline at end of file
diff --git a/provenance/3.9.1a5/multiple.intoto.jsonl b/provenance/3.9.1a5/multiple.intoto.jsonl
new file mode 100644
index 00000000000..33bebb5428b
--- /dev/null
+++ b/provenance/3.9.1a5/multiple.intoto.jsonl
@@ -0,0 +1 @@
+{"mediaType":"application/vnd.dev.sigstore.bundle.v0.3+json", "verificationMaterial":{"certificate":{"rawBytes":"MIIHZzCCBuygAwIBAgIUEHXWty7enGoxRWQo5v8TdI6p57cwCgYIKoZIzj0EAwMwNzEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MR4wHAYDVQQDExVzaWdzdG9yZS1pbnRlcm1lZGlhdGUwHhcNMjUwNDAyMDgwODA3WhcNMjUwNDAyMDgxODA3WjAAMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEZEJvUuy38nKlRT0YoPb1sIJFDK1uF7xFlg/ip+9vC1cTcTjPvhPkIwTOwdcZBoXuO0n9ewob7WWQZRm/91fnjKOCBgswggYHMA4GA1UdDwEB/wQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcDAzAdBgNVHQ4EFgQUvgHGXAf8niS/0On8Lica7r5PoNMwHwYDVR0jBBgwFoAU39Ppz1YkEZb5qNjpKFWixi4YZD8wgYQGA1UdEQEB/wR6MHiGdmh0dHBzOi8vZ2l0aHViLmNvbS9zbHNhLWZyYW1ld29yay9zbHNhLWdpdGh1Yi1nZW5lcmF0b3IvLmdpdGh1Yi93b3JrZmxvd3MvZ2VuZXJhdG9yX2dlbmVyaWNfc2xzYTMueW1sQHJlZnMvdGFncy92Mi4xLjAwOQYKKwYBBAGDvzABAQQraHR0cHM6Ly90b2tlbi5hY3Rpb25zLmdpdGh1YnVzZXJjb250ZW50LmNvbTAWBgorBgEEAYO/MAECBAhzY2hlZHVsZTA2BgorBgEEAYO/MAEDBCgyMmZkNmE4YjNiZWYwOTAyNDI0N2RjZDg3NThlYjhjOTU5OGVhMjY1MBkGCisGAQQBg78wAQQEC1ByZS1SZWxlYXNlMDUGCisGAQQBg78wAQUEJ2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbjAgBgorBgEEAYO/MAEGBBJyZWZzL2hlYWRzL2RldmVsb3AwOwYKKwYBBAGDvzABCAQtDCtodHRwczovL3Rva2VuLmFjdGlvbnMuZ2l0aHVidXNlcmNvbnRlbnQuY29tMIGGBgorBgEEAYO/MAEJBHgMdmh0dHBzOi8vZ2l0aHViLmNvbS9zbHNhLWZyYW1ld29yay9zbHNhLWdpdGh1Yi1nZW5lcmF0b3IvLmdpdGh1Yi93b3JrZmxvd3MvZ2VuZXJhdG9yX2dlbmVyaWNfc2xzYTMueW1sQHJlZnMvdGFncy92Mi4xLjAwOAYKKwYBBAGDvzABCgQqDChmN2RkOGM1NGMyMDY3YmFmYzEyY2E3YTU1NTk1ZDVlZTliNzUyMDRhMB0GCisGAQQBg78wAQsEDwwNZ2l0aHViLWhvc3RlZDBKBgorBgEEAYO/MAEMBDwMOmh0dHBzOi8vZ2l0aHViLmNvbS9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24wOAYKKwYBBAGDvzABDQQqDCgyMmZkNmE4YjNiZWYwOTAyNDI0N2RjZDg3NThlYjhjOTU5OGVhMjY1MCIGCisGAQQBg78wAQ4EFAwScmVmcy9oZWFkcy9kZXZlbG9wMBkGCisGAQQBg78wAQ8ECwwJMjIxOTE5Mzc5MDEGCisGAQQBg78wARAEIwwhaHR0cHM6Ly9naXRodWIuY29tL2F3cy1wb3dlcnRvb2xzMBkGCisGAQQBg78wAREECwwJMTI5MTI3NjM4MH8GCisGAQQBg78wARIEcQxvaHR0cHM6Ly9naXRodWIuY29tL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi8uZ2l0aHViL3dvcmtmbG93cy9wcmUtcmVsZWFzZS55bWxAcmVmcy9oZWFkcy9kZXZlbG9wMDgGCisGAQQBg78wARMEKgwoMjJmZDZhOGIzYmVmMDkwMjQyNDdkY2Q4NzU4ZWI4Yzk1OThlYTI2NTAYBgorBgEEAYO/MAEUBAoMCHNjaGVkdWxlMG4GCisGAQQBg78wARUEYAxeaHR0cHM6Ly9naXRodWIuY29tL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9hY3Rpb25zL3J1bnMvMTQyMTQ1NDQyMDAvYXR0ZW1wdHMvMTAWBgorBgEEAYO/MAEWBAgMBnB1YmxpYzCBigYKKwYBBAHWeQIEAgR8BHoAeAB2AN09MGrGxxEyYxkeHJlnNwKiSl643jyt/4eKcoAvKe6OAAABlfWLFjUAAAQDAEcwRQIhAL+j11/yCdqN14pMqtTMacYfjdDNutaCZs4gp7us5tgBAiAi3sIQ7D0AxHnilXvO3lcbnDY4ed755iuEw+kpwVTQ5zAKBggqhkjOPQQDAwNpADBmAjEA6CxdVP9SC/D5Lf05m9lkbOhA9Sm0R8gkTXLJXBOYGNpWjtV79a1yJF3rLzGxQtkbAjEAkTaggns47PiZvtdBnhtocd21n04tgR8qlbTqiXVlrh68JzU3E3Z2mGzhOFMYfZ2U"}, "tlogEntries":[{"logIndex":"191383083", "logId":{"keyId":"wNI9atQGlz+VWfO6LRygH4QUfY/8W4RFwiT5i5WRgB0="}, "kindVersion":{"kind":"dsse", "version":"0.0.1"}, "integratedTime":"1743581288", "inclusionPromise":{"signedEntryTimestamp":"MEUCIBKfxBU5/hJFs1g/olGHLepb0onVBGYDTeCiV2acGDRDAiEA7nwBtU29sLTeENNsvPo2buIVpCHlhjm8pRFeGagJ1Jk="}, "inclusionProof":{"logIndex":"69478821", "rootHash":"XV0h659m604R+qQN9xGdXp5kuJpUQa9jgtAg7V9k/Ao=", "treeSize":"69478823", "hashes":["ftj6b4diMgvh2nt1JV/WtnxiJ2s7cFhWeD2jb/POScs=", "0NcsfsP0TNT5YiOHjLLGCJSqZgORchmJhUGAjQX5gqg=", "Uksc38r/6aTTIcR/h1qhdvQ2SwBRYaNJP/RBD1PvUdk=", "Ritdy5FMsXBTLBnSa+ZuqYRd2TDjvVHrlrY9HR4bd30=", "ATDG9pcSnVA7IP6pCmZ4eKyuPhH3NWHW2HJgOJi3vmk=", "S4+LzM+z+xnliUdoqlBhcnFw0O3ASQFg2kFEPRRsJcE=", "cgo0tzdn1X4PXp0C8GA5iCCvmtk/h0+nxRo657TcXPk=", "lIF6qN0XmO+64MOL5EQAmy7Y0FTpDIcfBpvQY0gsA8s=", "KoNpl+Q5kvGS9DMofvyg/PQXP2E0JuXgQHg4B8TtRGg=", "C2a68tJEURTNteL5zYmjaa205qVnkObfZhjeUxj5i1g=", "7v8qPHNDLerpduaMx06eb/MwgoQwczTn/cYGKX/9wZ4="], "checkpoint":{"envelope":"rekor.sigstore.dev - 1193050959916656506\n69478823\nXV0h659m604R+qQN9xGdXp5kuJpUQa9jgtAg7V9k/Ao=\n\n— rekor.sigstore.dev wNI9ajBFAiEAs8byGfBO4yXiLwTrLm+o5DQ8p0xRsq0Z4f20llMX1rECIGzPl8PkRqB6F3+6ZpSO4u2335irhYAM/v5vS0uUt0vr\n"}}, "canonicalizedBody":"eyJhcGlWZXJzaW9uIjoiMC4wLjEiLCJraW5kIjoiZHNzZSIsInNwZWMiOnsiZW52ZWxvcGVIYXNoIjp7ImFsZ29yaXRobSI6InNoYTI1NiIsInZhbHVlIjoiYzIxMGEzNjU0ZjNkZjJlNGE0NjUwYTQzNGMwNmRmY2U5MDdmNDM4M2Q5Y2Y3NTUxMDRkZGE5ZWViNzMzYWE1NCJ9LCJwYXlsb2FkSGFzaCI6eyJhbGdvcml0aG0iOiJzaGEyNTYiLCJ2YWx1ZSI6Ijc5ZTFkODg1YmQ4YjEwZGJmNTMxMTA2NzM1MGIyNDgxMTEwNWYwMjU5M2RjYzg4ZTJjNzQwNzVhZTRkNTUxZGMifSwic2lnbmF0dXJlcyI6W3sic2lnbmF0dXJlIjoiTUVVQ0lBTjdDVnphUG1nbUVRejdtWTZ6Nm5nN3ZmZkVGZHAxdUdsWnB3K2hzUkNYQWlFQTVwTTRtbzVDa3RISk5OV2pIRk1LTFNYMGsrRGFMWEMxZlBnQStFRmg4L0E9IiwidmVyaWZpZXIiOiJMUzB0TFMxQ1JVZEpUaUJEUlZKVVNVWkpRMEZVUlMwdExTMHRDazFKU1VoYWVrTkRRblY1WjBGM1NVSkJaMGxWUlVoWVYzUjVOMlZ1UjI5NFVsZFJielYyT0ZSa1NUWndOVGRqZDBObldVbExiMXBKZW1vd1JVRjNUWGNLVG5wRlZrMUNUVWRCTVZWRlEyaE5UV015Ykc1ak0xSjJZMjFWZFZwSFZqSk5ValIzU0VGWlJGWlJVVVJGZUZaNllWZGtlbVJIT1hsYVV6RndZbTVTYkFwamJURnNXa2RzYUdSSFZYZElhR05PVFdwVmQwNUVRWGxOUkdkM1QwUkJNMWRvWTA1TmFsVjNUa1JCZVUxRVozaFBSRUV6VjJwQlFVMUdhM2RGZDFsSUNrdHZXa2w2YWpCRFFWRlpTVXR2V2tsNmFqQkVRVkZqUkZGblFVVmFSVXAyVlhWNU16aHVTMnhTVkRCWmIxQmlNWE5KU2taRVN6RjFSamQ0Um14bkwya0tjQ3M1ZGtNeFkxUmpWR3BRZG1oUWEwbDNWRTkzWkdOYVFtOVlkVTh3YmpsbGQyOWlOMWRYVVZwU2JTODVNV1p1YWt0UFEwSm5jM2RuWjFsSVRVRTBSd3BCTVZWa1JIZEZRaTkzVVVWQmQwbElaMFJCVkVKblRsWklVMVZGUkVSQlMwSm5aM0pDWjBWR1FsRmpSRUY2UVdSQ1owNVdTRkUwUlVablVWVjJaMGhIQ2xoQlpqaHVhVk12TUU5dU9FeHBZMkUzY2pWUWIwNU5kMGgzV1VSV1VqQnFRa0puZDBadlFWVXpPVkJ3ZWpGWmEwVmFZalZ4VG1wd1MwWlhhWGhwTkZrS1drUTRkMmRaVVVkQk1WVmtSVkZGUWk5M1VqWk5TR2xIWkcxb01HUklRbnBQYVRoMldqSnNNR0ZJVm1sTWJVNTJZbE01ZW1KSVRtaE1WMXA1V1ZjeGJBcGtNamw1WVhrNWVtSklUbWhNVjJSd1pFZG9NVmxwTVc1YVZ6VnNZMjFHTUdJelNYWk1iV1J3WkVkb01WbHBPVE5pTTBweVdtMTRkbVF6VFhaYU1sWjFDbHBZU21oa1J6bDVXREprYkdKdFZubGhWMDVtWXpKNGVsbFVUWFZsVnpGelVVaEtiRnB1VFhaa1IwWnVZM2s1TWsxcE5IaE1ha0YzVDFGWlMwdDNXVUlLUWtGSFJIWjZRVUpCVVZGeVlVaFNNR05JVFRaTWVUa3dZakowYkdKcE5XaFpNMUp3WWpJMWVreHRaSEJrUjJneFdXNVdlbHBZU21waU1qVXdXbGMxTUFwTWJVNTJZbFJCVjBKbmIzSkNaMFZGUVZsUEwwMUJSVU5DUVdoNldUSm9iRnBJVm5OYVZFRXlRbWR2Y2tKblJVVkJXVTh2VFVGRlJFSkRaM2xOYlZwckNrNXRSVFJaYWs1cFdsZFpkMDlVUVhsT1JFa3dUakpTYWxwRVp6Tk9WR2hzV1dwb2FrOVVWVFZQUjFab1RXcFpNVTFDYTBkRGFYTkhRVkZSUW1jM09IY0tRVkZSUlVNeFFubGFVekZUV2xkNGJGbFlUbXhOUkZWSFEybHpSMEZSVVVKbk56aDNRVkZWUlVveVJqTmplVEYzWWpOa2JHTnVVblppTW5oNlRETkNkZ3BrTWxaNVpFYzVkbUpJVFhSaVIwWjBXVzFTYUV4WVFqVmtSMmgyWW1wQlowSm5iM0pDWjBWRlFWbFBMMDFCUlVkQ1FrcDVXbGRhZWt3eWFHeFpWMUo2Q2t3eVVteGtiVlp6WWpOQmQwOTNXVXRMZDFsQ1FrRkhSSFo2UVVKRFFWRjBSRU4wYjJSSVVuZGplbTkyVEROU2RtRXlWblZNYlVacVpFZHNkbUp1VFhVS1dqSnNNR0ZJVm1sa1dFNXNZMjFPZG1KdVVteGlibEYxV1RJNWRFMUpSMGRDWjI5eVFtZEZSVUZaVHk5TlFVVktRa2huVFdSdGFEQmtTRUo2VDJrNGRncGFNbXd3WVVoV2FVeHRUblppVXpsNllraE9hRXhYV25sWlZ6RnNaREk1ZVdGNU9YcGlTRTVvVEZka2NHUkhhREZaYVRGdVdsYzFiR050UmpCaU0wbDJDa3h0WkhCa1IyZ3hXV2s1TTJJelNuSmFiWGgyWkROTmRsb3lWblZhV0Vwb1pFYzVlVmd5Wkd4aWJWWjVZVmRPWm1NeWVIcFpWRTExWlZjeGMxRklTbXdLV201TmRtUkhSbTVqZVRreVRXazBlRXhxUVhkUFFWbExTM2RaUWtKQlIwUjJla0ZDUTJkUmNVUkRhRzFPTWxKclQwZE5NVTVIVFhsTlJGa3pXVzFHYlFwWmVrVjVXVEpGTTFsVVZURk9WR3N4V2tSV2JGcFViR2xPZWxWNVRVUlNhRTFDTUVkRGFYTkhRVkZSUW1jM09IZEJVWE5GUkhkM1Rsb3liREJoU0ZacENreFhhSFpqTTFKc1drUkNTMEpuYjNKQ1owVkZRVmxQTDAxQlJVMUNSSGROVDIxb01HUklRbnBQYVRoMldqSnNNR0ZJVm1sTWJVNTJZbE01YUdRelRYUUtZMGM1TTFwWVNqQmlNamx6WTNrNWQySXpaR3hqYmxKMllqSjRla3hYZUdoaVYwcHJXVk14ZDJWWVVtOWlNalIzVDBGWlMwdDNXVUpDUVVkRWRucEJRZ3BFVVZGeFJFTm5lVTF0V210T2JVVTBXV3BPYVZwWFdYZFBWRUY1VGtSSk1FNHlVbXBhUkdjelRsUm9iRmxxYUdwUFZGVTFUMGRXYUUxcVdURk5RMGxIQ2tOcGMwZEJVVkZDWnpjNGQwRlJORVZHUVhkVFkyMVdiV041T1c5YVYwWnJZM2s1YTFwWVdteGlSemwzVFVKclIwTnBjMGRCVVZGQ1p6YzRkMEZST0VVS1EzZDNTazFxU1hoUFZFVTFUWHBqTlUxRVJVZERhWE5IUVZGUlFtYzNPSGRCVWtGRlNYZDNhR0ZJVWpCalNFMDJUSGs1Ym1GWVVtOWtWMGwxV1RJNWRBcE1Na1l6WTNreGQySXpaR3hqYmxKMllqSjRlazFDYTBkRGFYTkhRVkZSUW1jM09IZEJVa1ZGUTNkM1NrMVVTVFZOVkVrelRtcE5ORTFJT0VkRGFYTkhDa0ZSVVVKbk56aDNRVkpKUldOUmVIWmhTRkl3WTBoTk5reDVPVzVoV0ZKdlpGZEpkVmt5T1hSTU1rWXpZM2t4ZDJJelpHeGpibEoyWWpKNGVrd3pRbllLWkRKV2VXUkhPWFppU0UxMFlrZEdkRmx0VW1oTVdFSTFaRWRvZG1KcE9IVmFNbXd3WVVoV2FVd3paSFpqYlhSdFlrYzVNMk41T1hkamJWVjBZMjFXY3dwYVYwWjZXbE0xTldKWGVFRmpiVlp0WTNrNWIxcFhSbXRqZVRscldsaGFiR0pIT1hkTlJHZEhRMmx6UjBGUlVVSm5OemgzUVZKTlJVdG5kMjlOYWtwdENscEVXbWhQUjBsNldXMVdiVTFFYTNkTmFsRjVUa1JrYTFreVVUUk9lbFUwV2xkSk5GbDZhekZQVkdoc1dWUkpNazVVUVZsQ1oyOXlRbWRGUlVGWlR5OEtUVUZGVlVKQmIwMURTRTVxWVVkV2EyUlhlR3hOUnpSSFEybHpSMEZSVVVKbk56aDNRVkpWUlZsQmVHVmhTRkl3WTBoTk5reDVPVzVoV0ZKdlpGZEpkUXBaTWpsMFRESkdNMk41TVhkaU0yUnNZMjVTZG1JeWVIcE1NMEoyWkRKV2VXUkhPWFppU0UxMFlrZEdkRmx0VW1oTVdFSTFaRWRvZG1KcE9XaFpNMUp3Q21JeU5YcE1NMG94WW01TmRrMVVVWGxOVkZFeFRrUlJlVTFFUVhaWldGSXdXbGN4ZDJSSVRYWk5WRUZYUW1kdmNrSm5SVVZCV1U4dlRVRkZWMEpCWjAwS1FtNUNNVmx0ZUhCWmVrTkNhV2RaUzB0M1dVSkNRVWhYWlZGSlJVRm5VamhDU0c5QlpVRkNNa0ZPTURsTlIzSkhlSGhGZVZsNGEyVklTbXh1VG5kTGFRcFRiRFkwTTJwNWRDODBaVXRqYjBGMlMyVTJUMEZCUVVKc1psZE1SbXBWUVVGQlVVUkJSV04zVWxGSmFFRk1LMm94TVM5NVEyUnhUakUwY0UxeGRGUk5DbUZqV1dacVpFUk9kWFJoUTFwek5HZHdOM1Z6TlhSblFrRnBRV2t6YzBsUk4wUXdRWGhJYm1sc1dIWlBNMnhqWW01RVdUUmxaRGMxTldsMVJYY3JhM0FLZDFaVVVUVjZRVXRDWjJkeGFHdHFUMUJSVVVSQmQwNXdRVVJDYlVGcVJVRTJRM2hrVmxBNVUwTXZSRFZNWmpBMWJUbHNhMkpQYUVFNVUyMHdVamhuYXdwVVdFeEtXRUpQV1VkT2NGZHFkRlkzT1dFeGVVcEdNM0pNZWtkNFVYUnJZa0ZxUlVGclZHRm5aMjV6TkRkUWFWcDJkR1JDYm1oMGIyTmtNakZ1TURSMENtZFNPSEZzWWxSeGFWaFdiSEpvTmpoS2VsVXpSVE5hTW0xSGVtaFBSazFaWmxveVZRb3RMUzB0TFVWT1JDQkRSVkpVU1VaSlEwRlVSUzB0TFMwdENnPT0ifV19fQ=="}]}, "dsseEnvelope":{"payload":"eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjAuMSIsInByZWRpY2F0ZVR5cGUiOiJodHRwczovL3Nsc2EuZGV2L3Byb3ZlbmFuY2UvdjAuMiIsInN1YmplY3QiOlt7Im5hbWUiOiIuL2F3c19sYW1iZGFfcG93ZXJ0b29scy0zLjkuMWE1LXB5My1ub25lLWFueS53aGwiLCJkaWdlc3QiOnsic2hhMjU2IjoiYWUyNmUzMjgyY2E5YjY1MTE0NDVmZWE3ZGQ3MjQ4MmI3MDQ1MGIyOWNkODNmYmQxMWFmNjg2ZDdiM2Y3MGE3MiJ9fSx7Im5hbWUiOiIuL2F3c19sYW1iZGFfcG93ZXJ0b29scy0zLjkuMWE1LnRhci5neiIsImRpZ2VzdCI6eyJzaGEyNTYiOiI0NzlkZGJlN2IyM2MzM2JmN2ExMTQ0YmFkZTUwZTkzNjMxNWQ3ZDU2ZDhmOGU0MWUwZGMxNjE4NmE1MTA1YmJlIn19XSwicHJlZGljYXRlIjp7ImJ1aWxkZXIiOnsiaWQiOiJodHRwczovL2dpdGh1Yi5jb20vc2xzYS1mcmFtZXdvcmsvc2xzYS1naXRodWItZ2VuZXJhdG9yLy5naXRodWIvd29ya2Zsb3dzL2dlbmVyYXRvcl9nZW5lcmljX3Nsc2EzLnltbEByZWZzL3RhZ3MvdjIuMS4wIn0sImJ1aWxkVHlwZSI6Imh0dHBzOi8vZ2l0aHViLmNvbS9zbHNhLWZyYW1ld29yay9zbHNhLWdpdGh1Yi1nZW5lcmF0b3IvZ2VuZXJpY0B2MSIsImludm9jYXRpb24iOnsiY29uZmlnU291cmNlIjp7InVyaSI6ImdpdCtodHRwczovL2dpdGh1Yi5jb20vYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uQHJlZnMvaGVhZHMvZGV2ZWxvcCIsImRpZ2VzdCI6eyJzaGExIjoiMjJmZDZhOGIzYmVmMDkwMjQyNDdkY2Q4NzU4ZWI4Yzk1OThlYTI2NSJ9LCJlbnRyeVBvaW50IjoiLmdpdGh1Yi93b3JrZmxvd3MvcHJlLXJlbGVhc2UueW1sIn0sImVudmlyb25tZW50Ijp7ImdpdGh1Yl9hY3RvciI6ImxlYW5kcm9kYW1hc2NlbmEiLCJnaXRodWJfYWN0b3JfaWQiOiI0Mjk1MTczIiwiZ2l0aHViX2Jhc2VfcmVmIjoiIiwiZ2l0aHViX2V2ZW50X25hbWUiOiJzY2hlZHVsZSIsImdpdGh1Yl9ldmVudF9wYXlsb2FkIjp7ImVudGVycHJpc2UiOnsiYXZhdGFyX3VybCI6Imh0dHBzOi8vYXZhdGFycy5naXRodWJ1c2VyY29udGVudC5jb20vYi8xMjkwP3Y9NCIsImNyZWF0ZWRfYXQiOiIyMDE5LTExLTEzVDE4OjA1OjQxWiIsImRlc2NyaXB0aW9uIjoiIiwiaHRtbF91cmwiOiJodHRwczovL2dpdGh1Yi5jb20vZW50ZXJwcmlzZXMvYW1hem9uIiwiaWQiOjEyOTAsIm5hbWUiOiJBbWF6b24iLCJub2RlX2lkIjoiTURFd09rVnVkR1Z5Y0hKcGMyVXhNamt3Iiwic2x1ZyI6ImFtYXpvbiIsInVwZGF0ZWRfYXQiOiIyMDI0LTA5LTMwVDIxOjAyOjMwWiIsIndlYnNpdGVfdXJsIjoiaHR0cHM6Ly93d3cuYW1hem9uLmNvbS8ifSwib3JnYW5pemF0aW9uIjp7ImF2YXRhcl91cmwiOiJodHRwczovL2F2YXRhcnMuZ2l0aHVidXNlcmNvbnRlbnQuY29tL3UvMTI5MTI3NjM4P3Y9NCIsImRlc2NyaXB0aW9uIjoiIiwiZXZlbnRzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vb3Jncy9hd3MtcG93ZXJ0b29scy9ldmVudHMiLCJob29rc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL29yZ3MvYXdzLXBvd2VydG9vbHMvaG9va3MiLCJpZCI6MTI5MTI3NjM4LCJpc3N1ZXNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9vcmdzL2F3cy1wb3dlcnRvb2xzL2lzc3VlcyIsImxvZ2luIjoiYXdzLXBvd2VydG9vbHMiLCJtZW1iZXJzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vb3Jncy9hd3MtcG93ZXJ0b29scy9tZW1iZXJzey9tZW1iZXJ9Iiwibm9kZV9pZCI6Ik9fa2dET0I3SlUxZyIsInB1YmxpY19tZW1iZXJzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vb3Jncy9hd3MtcG93ZXJ0b29scy9wdWJsaWNfbWVtYmVyc3svbWVtYmVyfSIsInJlcG9zX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vb3Jncy9hd3MtcG93ZXJ0b29scy9yZXBvcyIsInVybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vb3Jncy9hd3MtcG93ZXJ0b29scyJ9LCJyZXBvc2l0b3J5Ijp7ImFsbG93X2ZvcmtpbmciOnRydWUsImFyY2hpdmVfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24ve2FyY2hpdmVfZm9ybWF0fXsvcmVmfSIsImFyY2hpdmVkIjpmYWxzZSwiYXNzaWduZWVzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2Fzc2lnbmVlc3svdXNlcn0iLCJibG9ic191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9naXQvYmxvYnN7L3NoYX0iLCJicmFuY2hlc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9icmFuY2hlc3svYnJhbmNofSIsImNsb25lX3VybCI6Imh0dHBzOi8vZ2l0aHViLmNvbS9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24uZ2l0IiwiY29sbGFib3JhdG9yc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9jb2xsYWJvcmF0b3Jzey9jb2xsYWJvcmF0b3J9IiwiY29tbWVudHNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vY29tbWVudHN7L251bWJlcn0iLCJjb21taXRzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2NvbW1pdHN7L3NoYX0iLCJjb21wYXJlX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2NvbXBhcmUve2Jhc2V9Li4ue2hlYWR9IiwiY29udGVudHNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vY29udGVudHMveytwYXRofSIsImNvbnRyaWJ1dG9yc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9jb250cmlidXRvcnMiLCJjcmVhdGVkX2F0IjoiMjAxOS0xMS0xNVQxMjoyNjoxMloiLCJjdXN0b21fcHJvcGVydGllcyI6e30sImRlZmF1bHRfYnJhbmNoIjoiZGV2ZWxvcCIsImRlcGxveW1lbnRzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2RlcGxveW1lbnRzIiwiZGVzY3JpcHRpb24iOiJBIGRldmVsb3BlciB0b29sa2l0IHRvIGltcGxlbWVudCBTZXJ2ZXJsZXNzIGJlc3QgcHJhY3RpY2VzIGFuZCBpbmNyZWFzZSBkZXZlbG9wZXIgdmVsb2NpdHkuIiwiZGlzYWJsZWQiOmZhbHNlLCJkb3dubG9hZHNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vZG93bmxvYWRzIiwiZXZlbnRzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2V2ZW50cyIsImZvcmsiOmZhbHNlLCJmb3JrcyI6NDE2LCJmb3Jrc19jb3VudCI6NDE2LCJmb3Jrc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9mb3JrcyIsImZ1bGxfbmFtZSI6ImF3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbiIsImdpdF9jb21taXRzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2dpdC9jb21taXRzey9zaGF9IiwiZ2l0X3JlZnNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vZ2l0L3JlZnN7L3NoYX0iLCJnaXRfdGFnc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9naXQvdGFnc3svc2hhfSIsImdpdF91cmwiOiJnaXQ6Ly9naXRodWIuY29tL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi5naXQiLCJoYXNfZGlzY3Vzc2lvbnMiOnRydWUsImhhc19kb3dubG9hZHMiOnRydWUsImhhc19pc3N1ZXMiOnRydWUsImhhc19wYWdlcyI6ZmFsc2UsImhhc19wcm9qZWN0cyI6dHJ1ZSwiaGFzX3dpa2kiOmZhbHNlLCJob21lcGFnZSI6Imh0dHBzOi8vZG9jcy5wb3dlcnRvb2xzLmF3cy5kZXYvbGFtYmRhL3B5dGhvbi9sYXRlc3QvIiwiaG9va3NfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vaG9va3MiLCJodG1sX3VybCI6Imh0dHBzOi8vZ2l0aHViLmNvbS9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24iLCJpZCI6MjIxOTE5Mzc5LCJpc190ZW1wbGF0ZSI6ZmFsc2UsImlzc3VlX2NvbW1lbnRfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vaXNzdWVzL2NvbW1lbnRzey9udW1iZXJ9IiwiaXNzdWVfZXZlbnRzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2lzc3Vlcy9ldmVudHN7L251bWJlcn0iLCJpc3N1ZXNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vaXNzdWVzey9udW1iZXJ9Iiwia2V5c191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9rZXlzey9rZXlfaWR9IiwibGFiZWxzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2xhYmVsc3svbmFtZX0iLCJsYW5ndWFnZSI6IlB5dGhvbiIsImxhbmd1YWdlc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9sYW5ndWFnZXMiLCJsaWNlbnNlIjp7ImtleSI6Im1pdC0wIiwibmFtZSI6Ik1JVCBObyBBdHRyaWJ1dGlvbiIsIm5vZGVfaWQiOiJNRGM2VEdsalpXNXpaVFF4Iiwic3BkeF9pZCI6Ik1JVC0wIiwidXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9saWNlbnNlcy9taXQtMCJ9LCJtZXJnZXNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vbWVyZ2VzIiwibWlsZXN0b25lc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9taWxlc3RvbmVzey9udW1iZXJ9IiwibWlycm9yX3VybCI6bnVsbCwibmFtZSI6InBvd2VydG9vbHMtbGFtYmRhLXB5dGhvbiIsIm5vZGVfaWQiOiJNREV3T2xKbGNHOXphWFJ2Y25reU1qRTVNVGt6TnprPSIsIm5vdGlmaWNhdGlvbnNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vbm90aWZpY2F0aW9uc3s/c2luY2UsYWxsLHBhcnRpY2lwYXRpbmd9Iiwib3Blbl9pc3N1ZXMiOjU2LCJvcGVuX2lzc3Vlc19jb3VudCI6NTYsIm93bmVyIjp7ImF2YXRhcl91cmwiOiJodHRwczovL2F2YXRhcnMuZ2l0aHVidXNlcmNvbnRlbnQuY29tL3UvMTI5MTI3NjM4P3Y9NCIsImV2ZW50c191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3VzZXJzL2F3cy1wb3dlcnRvb2xzL2V2ZW50c3svcHJpdmFjeX0iLCJmb2xsb3dlcnNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS91c2Vycy9hd3MtcG93ZXJ0b29scy9mb2xsb3dlcnMiLCJmb2xsb3dpbmdfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS91c2Vycy9hd3MtcG93ZXJ0b29scy9mb2xsb3dpbmd7L290aGVyX3VzZXJ9IiwiZ2lzdHNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS91c2Vycy9hd3MtcG93ZXJ0b29scy9naXN0c3svZ2lzdF9pZH0iLCJncmF2YXRhcl9pZCI6IiIsImh0bWxfdXJsIjoiaHR0cHM6Ly9naXRodWIuY29tL2F3cy1wb3dlcnRvb2xzIiwiaWQiOjEyOTEyNzYzOCwibG9naW4iOiJhd3MtcG93ZXJ0b29scyIsIm5vZGVfaWQiOiJPX2tnRE9CN0pVMWciLCJvcmdhbml6YXRpb25zX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vdXNlcnMvYXdzLXBvd2VydG9vbHMvb3JncyIsInJlY2VpdmVkX2V2ZW50c191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3VzZXJzL2F3cy1wb3dlcnRvb2xzL3JlY2VpdmVkX2V2ZW50cyIsInJlcG9zX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vdXNlcnMvYXdzLXBvd2VydG9vbHMvcmVwb3MiLCJzaXRlX2FkbWluIjpmYWxzZSwic3RhcnJlZF91cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3VzZXJzL2F3cy1wb3dlcnRvb2xzL3N0YXJyZWR7L293bmVyfXsvcmVwb30iLCJzdWJzY3JpcHRpb25zX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vdXNlcnMvYXdzLXBvd2VydG9vbHMvc3Vic2NyaXB0aW9ucyIsInR5cGUiOiJPcmdhbml6YXRpb24iLCJ1cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3VzZXJzL2F3cy1wb3dlcnRvb2xzIiwidXNlcl92aWV3X3R5cGUiOiJwdWJsaWMifSwicHJpdmF0ZSI6ZmFsc2UsInB1bGxzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL3B1bGxzey9udW1iZXJ9IiwicHVzaGVkX2F0IjoiMjAyNS0wNC0wMVQyMTo1NzozMloiLCJyZWxlYXNlc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9yZWxlYXNlc3svaWR9Iiwic2l6ZSI6MTA3MjQxLCJzc2hfdXJsIjoiZ2l0QGdpdGh1Yi5jb206YXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uLmdpdCIsInN0YXJnYXplcnNfY291bnQiOjMwMTgsInN0YXJnYXplcnNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vc3RhcmdhemVycyIsInN0YXR1c2VzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL3N0YXR1c2VzL3tzaGF9Iiwic3Vic2NyaWJlcnNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vc3Vic2NyaWJlcnMiLCJzdWJzY3JpcHRpb25fdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vc3Vic2NyaXB0aW9uIiwic3ZuX3VybCI6Imh0dHBzOi8vZ2l0aHViLmNvbS9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24iLCJ0YWdzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL3RhZ3MiLCJ0ZWFtc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi90ZWFtcyIsInRvcGljcyI6WyJhd3MiLCJhd3MtbGFtYmRhIiwiaGFja3RvYmVyZmVzdCIsImxhbWJkYSIsInB5dGhvbiIsInNlcnZlcmxlc3MiXSwidHJlZXNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vZ2l0L3RyZWVzey9zaGF9IiwidXBkYXRlZF9hdCI6IjIwMjUtMDQtMDFUMjE6NTU6MTZaIiwidXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24iLCJ2aXNpYmlsaXR5IjoicHVibGljIiwid2F0Y2hlcnMiOjMwMTgsIndhdGNoZXJzX2NvdW50IjozMDE4LCJ3ZWJfY29tbWl0X3NpZ25vZmZfcmVxdWlyZWQiOnRydWV9LCJzY2hlZHVsZSI6IjAgOCAqICogMS01Iiwid29ya2Zsb3ciOiIuZ2l0aHViL3dvcmtmbG93cy9wcmUtcmVsZWFzZS55bWwifSwiZ2l0aHViX2hlYWRfcmVmIjoiIiwiZ2l0aHViX3JlZiI6InJlZnMvaGVhZHMvZGV2ZWxvcCIsImdpdGh1Yl9yZWZfdHlwZSI6ImJyYW5jaCIsImdpdGh1Yl9yZXBvc2l0b3J5X2lkIjoiMjIxOTE5Mzc5IiwiZ2l0aHViX3JlcG9zaXRvcnlfb3duZXIiOiJhd3MtcG93ZXJ0b29scyIsImdpdGh1Yl9yZXBvc2l0b3J5X293bmVyX2lkIjoiMTI5MTI3NjM4IiwiZ2l0aHViX3J1bl9hdHRlbXB0IjoiMSIsImdpdGh1Yl9ydW5faWQiOiIxNDIxNDU0NDIwMCIsImdpdGh1Yl9ydW5fbnVtYmVyIjoiMjEwIiwiZ2l0aHViX3NoYTEiOiIyMmZkNmE4YjNiZWYwOTAyNDI0N2RjZDg3NThlYjhjOTU5OGVhMjY1In19LCJtZXRhZGF0YSI6eyJidWlsZEludm9jYXRpb25JRCI6IjE0MjE0NTQ0MjAwLTEiLCJjb21wbGV0ZW5lc3MiOnsicGFyYW1ldGVycyI6dHJ1ZSwiZW52aXJvbm1lbnQiOmZhbHNlLCJtYXRlcmlhbHMiOmZhbHNlfSwicmVwcm9kdWNpYmxlIjpmYWxzZX0sIm1hdGVyaWFscyI6W3sidXJpIjoiZ2l0K2h0dHBzOi8vZ2l0aHViLmNvbS9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob25AcmVmcy9oZWFkcy9kZXZlbG9wIiwiZGlnZXN0Ijp7InNoYTEiOiIyMmZkNmE4YjNiZWYwOTAyNDI0N2RjZDg3NThlYjhjOTU5OGVhMjY1In19XX19", "payloadType":"application/vnd.in-toto+json", "signatures":[{"sig":"MEUCIAN7CVzaPmgmEQz7mY6z6ng7vffEFdp1uGlZpw+hsRCXAiEA5pM4mo5CktHJNNWjHFMKLSX0k+DaLXC1fPgA+EFh8/A="}]}}
\ No newline at end of file
diff --git a/provenance/3.9.1a6/multiple.intoto.jsonl b/provenance/3.9.1a6/multiple.intoto.jsonl
new file mode 100644
index 00000000000..17fbfae8da5
--- /dev/null
+++ b/provenance/3.9.1a6/multiple.intoto.jsonl
@@ -0,0 +1 @@
+{"mediaType":"application/vnd.dev.sigstore.bundle.v0.3+json", "verificationMaterial":{"certificate":{"rawBytes":"MIIHZzCCBu2gAwIBAgIUTp8K0gzJqZR1qwSdNn07F6td/EIwCgYIKoZIzj0EAwMwNzEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MR4wHAYDVQQDExVzaWdzdG9yZS1pbnRlcm1lZGlhdGUwHhcNMjUwNDAzMDgwODE5WhcNMjUwNDAzMDgxODE5WjAAMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE/Rfk32xxy8a7cf1Vs2q4jIe1VTLLUuXaBp6ZFiET6Y0UU7OaNbBZmJTv8T1qwt+51YaO5Rxu2S+2NZ1W36+QFKOCBgwwggYIMA4GA1UdDwEB/wQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcDAzAdBgNVHQ4EFgQUsPfh2NtVWu8oPW99QnEiq0HRmvkwHwYDVR0jBBgwFoAU39Ppz1YkEZb5qNjpKFWixi4YZD8wgYQGA1UdEQEB/wR6MHiGdmh0dHBzOi8vZ2l0aHViLmNvbS9zbHNhLWZyYW1ld29yay9zbHNhLWdpdGh1Yi1nZW5lcmF0b3IvLmdpdGh1Yi93b3JrZmxvd3MvZ2VuZXJhdG9yX2dlbmVyaWNfc2xzYTMueW1sQHJlZnMvdGFncy92Mi4xLjAwOQYKKwYBBAGDvzABAQQraHR0cHM6Ly90b2tlbi5hY3Rpb25zLmdpdGh1YnVzZXJjb250ZW50LmNvbTAWBgorBgEEAYO/MAECBAhzY2hlZHVsZTA2BgorBgEEAYO/MAEDBCg4MmNmYWZiZGVlMDdjNmFiMmI5ZjMzMjA5MzMzNTIwNDBkMzJkNTQ3MBkGCisGAQQBg78wAQQEC1ByZS1SZWxlYXNlMDUGCisGAQQBg78wAQUEJ2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbjAgBgorBgEEAYO/MAEGBBJyZWZzL2hlYWRzL2RldmVsb3AwOwYKKwYBBAGDvzABCAQtDCtodHRwczovL3Rva2VuLmFjdGlvbnMuZ2l0aHVidXNlcmNvbnRlbnQuY29tMIGGBgorBgEEAYO/MAEJBHgMdmh0dHBzOi8vZ2l0aHViLmNvbS9zbHNhLWZyYW1ld29yay9zbHNhLWdpdGh1Yi1nZW5lcmF0b3IvLmdpdGh1Yi93b3JrZmxvd3MvZ2VuZXJhdG9yX2dlbmVyaWNfc2xzYTMueW1sQHJlZnMvdGFncy92Mi4xLjAwOAYKKwYBBAGDvzABCgQqDChmN2RkOGM1NGMyMDY3YmFmYzEyY2E3YTU1NTk1ZDVlZTliNzUyMDRhMB0GCisGAQQBg78wAQsEDwwNZ2l0aHViLWhvc3RlZDBKBgorBgEEAYO/MAEMBDwMOmh0dHBzOi8vZ2l0aHViLmNvbS9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24wOAYKKwYBBAGDvzABDQQqDCg4MmNmYWZiZGVlMDdjNmFiMmI5ZjMzMjA5MzMzNTIwNDBkMzJkNTQ3MCIGCisGAQQBg78wAQ4EFAwScmVmcy9oZWFkcy9kZXZlbG9wMBkGCisGAQQBg78wAQ8ECwwJMjIxOTE5Mzc5MDEGCisGAQQBg78wARAEIwwhaHR0cHM6Ly9naXRodWIuY29tL2F3cy1wb3dlcnRvb2xzMBkGCisGAQQBg78wAREECwwJMTI5MTI3NjM4MH8GCisGAQQBg78wARIEcQxvaHR0cHM6Ly9naXRodWIuY29tL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi8uZ2l0aHViL3dvcmtmbG93cy9wcmUtcmVsZWFzZS55bWxAcmVmcy9oZWFkcy9kZXZlbG9wMDgGCisGAQQBg78wARMEKgwoODJjZmFmYmRlZTA3YzZhYjJiOWYzMzIwOTMzMzUyMDQwZDMyZDU0NzAYBgorBgEEAYO/MAEUBAoMCHNjaGVkdWxlMG4GCisGAQQBg78wARUEYAxeaHR0cHM6Ly9naXRodWIuY29tL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9hY3Rpb25zL3J1bnMvMTQyMzc5NDg4NDMvYXR0ZW1wdHMvMTAWBgorBgEEAYO/MAEWBAgMBnB1YmxpYzCBiwYKKwYBBAHWeQIEAgR9BHsAeQB3AN09MGrGxxEyYxkeHJlnNwKiSl643jyt/4eKcoAvKe6OAAABlfqxn3MAAAQDAEgwRgIhAOpPwYt+8hIaTMRyWnuSTBhj+389vIJ2OIzJXIWz5yRuAiEA3aRITlt7vcWKHAUqWZoFSekPI2O+Go9378ZfdZZZMFAwCgYIKoZIzj0EAwMDaAAwZQIwHdDAylXZB+dIppdbgtOz4ruk9AlsJ9d5xBckyY+roZNusX2tnOBx66wd0zNl6MtKAjEAqwnegXmJngsdf+yTylmRmEtVuO3ib2Vy//BT77iP4aTP+UzMIAiV4NHXcj5duvla"}, "tlogEntries":[{"logIndex":"191819834", "logId":{"keyId":"wNI9atQGlz+VWfO6LRygH4QUfY/8W4RFwiT5i5WRgB0="}, "kindVersion":{"kind":"dsse", "version":"0.0.1"}, "integratedTime":"1743667699", "inclusionPromise":{"signedEntryTimestamp":"MEQCIA5I6nWPV5h6vk2ZLQjV54cyaxpdkKa7LgcNgBYYYMI6AiAX/9F2tQdtMcBmmptS1phIwhfNcsSiF58umG82VkwnDg=="}, "inclusionProof":{"logIndex":"69915572", "rootHash":"gR8rR6nf6ZxFkl7HFC8qGxcl7R425GMXaa/3G8GJ3f8=", "treeSize":"69915574", "hashes":["cmXaazWjUgyrd6JHCCF7YmXN4J5DZD+9qYBB7V2p584=", "j2nQd0T/9qjV6ld5a7iY52bkDTkOkXMGB3dzlAmWYo8=", "YT7sg2seJewfQdmm+2Tyc8KC0QUMKt91T/sDZvW1RTo=", "bS6LFcpKMUUeJTQ/LZls2s3hzjmfJxUxQv68z9PpxtY=", "o+aYUPuPiuV0Slv998Q4JEwTHYkT6xcucSienc75YyI=", "CT2ZgXhAMmRFsCNohCIcwLxKLBF5rlbSajz31GOdl2o=", "TeyKreHQXP0K4GnJcMthcO/S2VqrCaLIZdFFEZ2peuQ=", "PleUuW3d/ib/3doZREkwF/J+6n94fzoSeCeLVLsTlzc=", "rJWdkJn2kH0dTP85bh93P/3CVWSo+HwxQ89twGUdGvU=", "aF1hDppHJMfa+rJNt5t24hxw8jAUVM0tZJ++3emIZQI=", "6GVEDvVFKXAurOM2AADMwmnBolCnzprE229Mv2rTwzc=", "b13MtJVKVUWA3OMmTecSeMT8Zrp/jiH1JQAS5m08iNQ=", "C2a68tJEURTNteL5zYmjaa205qVnkObfZhjeUxj5i1g=", "7v8qPHNDLerpduaMx06eb/MwgoQwczTn/cYGKX/9wZ4="], "checkpoint":{"envelope":"rekor.sigstore.dev - 1193050959916656506\n69915574\ngR8rR6nf6ZxFkl7HFC8qGxcl7R425GMXaa/3G8GJ3f8=\n\n— rekor.sigstore.dev wNI9ajBGAiEA3aGWZ7miLfKx538Ep5eblAUMOv60e+CIQY0Z6p9K4C4CIQCT9B9Y+g+U+65cf9d0uBrSWQHYXAmqDQDvEmr1nqF3VA==\n"}}, "canonicalizedBody":"eyJhcGlWZXJzaW9uIjoiMC4wLjEiLCJraW5kIjoiZHNzZSIsInNwZWMiOnsiZW52ZWxvcGVIYXNoIjp7ImFsZ29yaXRobSI6InNoYTI1NiIsInZhbHVlIjoiNjJkOWJkOGQzYmNhMGI4NjBjOTg3YTM0MmVmZjc5MTY4NTkwYzRkYWM2NGQxYTg3MTdjZmE1YTc3NDM0YTFlYiJ9LCJwYXlsb2FkSGFzaCI6eyJhbGdvcml0aG0iOiJzaGEyNTYiLCJ2YWx1ZSI6IjAzOTNkZjE4NDNiZjNjYjUwMGQ0ZDBkMGRhZDc2MDkyMDJkODUzMzc4MDlmMDJmNjc2OWFhZTUxMjcxMTEwMGMifSwic2lnbmF0dXJlcyI6W3sic2lnbmF0dXJlIjoiTUVVQ0lRRElmRVpKbS9mVHlGSGVOdXFsOFJsM2V3UVZRcW5LRkRMSWpQNmRWbHk5MFFJZ0UxRUhkS2s1aVMxZ2ZpQk1LZ095Z0ErMHV0UHk4VHp5QW5sbnlvOTdKczA9IiwidmVyaWZpZXIiOiJMUzB0TFMxQ1JVZEpUaUJEUlZKVVNVWkpRMEZVUlMwdExTMHRDazFKU1VoYWVrTkRRblV5WjBGM1NVSkJaMGxWVkhBNFN6Qm5la3B4V2xJeGNYZFRaRTV1TURkR05uUmtMMFZKZDBObldVbExiMXBKZW1vd1JVRjNUWGNLVG5wRlZrMUNUVWRCTVZWRlEyaE5UV015Ykc1ak0xSjJZMjFWZFZwSFZqSk5ValIzU0VGWlJGWlJVVVJGZUZaNllWZGtlbVJIT1hsYVV6RndZbTVTYkFwamJURnNXa2RzYUdSSFZYZElhR05PVFdwVmQwNUVRWHBOUkdkM1QwUkZOVmRvWTA1TmFsVjNUa1JCZWsxRVozaFBSRVUxVjJwQlFVMUdhM2RGZDFsSUNrdHZXa2w2YWpCRFFWRlpTVXR2V2tsNmFqQkVRVkZqUkZGblFVVXZVbVpyTXpKNGVIazRZVGRqWmpGV2N6SnhOR3BKWlRGV1ZFeE1WWFZZWVVKd05sb0tSbWxGVkRaWk1GVlZOMDloVG1KQ1dtMUtWSFk0VkRGeGQzUXJOVEZaWVU4MVVuaDFNbE1yTWs1YU1WY3pOaXRSUmt0UFEwSm5kM2RuWjFsSlRVRTBSd3BCTVZWa1JIZEZRaTkzVVVWQmQwbElaMFJCVkVKblRsWklVMVZGUkVSQlMwSm5aM0pDWjBWR1FsRmpSRUY2UVdSQ1owNVdTRkUwUlVablVWVnpVR1pvQ2pKT2RGWlhkVGh2VUZjNU9WRnVSV2x4TUVoU2JYWnJkMGgzV1VSV1VqQnFRa0puZDBadlFWVXpPVkJ3ZWpGWmEwVmFZalZ4VG1wd1MwWlhhWGhwTkZrS1drUTRkMmRaVVVkQk1WVmtSVkZGUWk5M1VqWk5TR2xIWkcxb01HUklRbnBQYVRoMldqSnNNR0ZJVm1sTWJVNTJZbE01ZW1KSVRtaE1WMXA1V1ZjeGJBcGtNamw1WVhrNWVtSklUbWhNVjJSd1pFZG9NVmxwTVc1YVZ6VnNZMjFHTUdJelNYWk1iV1J3WkVkb01WbHBPVE5pTTBweVdtMTRkbVF6VFhaYU1sWjFDbHBZU21oa1J6bDVXREprYkdKdFZubGhWMDVtWXpKNGVsbFVUWFZsVnpGelVVaEtiRnB1VFhaa1IwWnVZM2s1TWsxcE5IaE1ha0YzVDFGWlMwdDNXVUlLUWtGSFJIWjZRVUpCVVZGeVlVaFNNR05JVFRaTWVUa3dZakowYkdKcE5XaFpNMUp3WWpJMWVreHRaSEJrUjJneFdXNVdlbHBZU21waU1qVXdXbGMxTUFwTWJVNTJZbFJCVjBKbmIzSkNaMFZGUVZsUEwwMUJSVU5DUVdoNldUSm9iRnBJVm5OYVZFRXlRbWR2Y2tKblJVVkJXVTh2VFVGRlJFSkRaelJOYlU1dENsbFhXbWxhUjFac1RVUmthazV0Um1sTmJVazFXbXBOZWsxcVFUVk5lazE2VGxSSmQwNUVRbXROZWtwclRsUlJNMDFDYTBkRGFYTkhRVkZSUW1jM09IY0tRVkZSUlVNeFFubGFVekZUV2xkNGJGbFlUbXhOUkZWSFEybHpSMEZSVVVKbk56aDNRVkZWUlVveVJqTmplVEYzWWpOa2JHTnVVblppTW5oNlRETkNkZ3BrTWxaNVpFYzVkbUpJVFhSaVIwWjBXVzFTYUV4WVFqVmtSMmgyWW1wQlowSm5iM0pDWjBWRlFWbFBMMDFCUlVkQ1FrcDVXbGRhZWt3eWFHeFpWMUo2Q2t3eVVteGtiVlp6WWpOQmQwOTNXVXRMZDFsQ1FrRkhSSFo2UVVKRFFWRjBSRU4wYjJSSVVuZGplbTkyVEROU2RtRXlWblZNYlVacVpFZHNkbUp1VFhVS1dqSnNNR0ZJVm1sa1dFNXNZMjFPZG1KdVVteGlibEYxV1RJNWRFMUpSMGRDWjI5eVFtZEZSVUZaVHk5TlFVVktRa2huVFdSdGFEQmtTRUo2VDJrNGRncGFNbXd3WVVoV2FVeHRUblppVXpsNllraE9hRXhYV25sWlZ6RnNaREk1ZVdGNU9YcGlTRTVvVEZka2NHUkhhREZaYVRGdVdsYzFiR050UmpCaU0wbDJDa3h0WkhCa1IyZ3hXV2s1TTJJelNuSmFiWGgyWkROTmRsb3lWblZhV0Vwb1pFYzVlVmd5Wkd4aWJWWjVZVmRPWm1NeWVIcFpWRTExWlZjeGMxRklTbXdLV201TmRtUkhSbTVqZVRreVRXazBlRXhxUVhkUFFWbExTM2RaUWtKQlIwUjJla0ZDUTJkUmNVUkRhRzFPTWxKclQwZE5NVTVIVFhsTlJGa3pXVzFHYlFwWmVrVjVXVEpGTTFsVVZURk9WR3N4V2tSV2JGcFViR2xPZWxWNVRVUlNhRTFDTUVkRGFYTkhRVkZSUW1jM09IZEJVWE5GUkhkM1Rsb3liREJoU0ZacENreFhhSFpqTTFKc1drUkNTMEpuYjNKQ1owVkZRVmxQTDAxQlJVMUNSSGROVDIxb01HUklRbnBQYVRoMldqSnNNR0ZJVm1sTWJVNTJZbE01YUdRelRYUUtZMGM1TTFwWVNqQmlNamx6WTNrNWQySXpaR3hqYmxKMllqSjRla3hYZUdoaVYwcHJXVk14ZDJWWVVtOWlNalIzVDBGWlMwdDNXVUpDUVVkRWRucEJRZ3BFVVZGeFJFTm5ORTF0VG0xWlYxcHBXa2RXYkUxRVpHcE9iVVpwVFcxSk5WcHFUWHBOYWtFMVRYcE5lazVVU1hkT1JFSnJUWHBLYTA1VVVUTk5RMGxIQ2tOcGMwZEJVVkZDWnpjNGQwRlJORVZHUVhkVFkyMVdiV041T1c5YVYwWnJZM2s1YTFwWVdteGlSemwzVFVKclIwTnBjMGRCVVZGQ1p6YzRkMEZST0VVS1EzZDNTazFxU1hoUFZFVTFUWHBqTlUxRVJVZERhWE5IUVZGUlFtYzNPSGRCVWtGRlNYZDNhR0ZJVWpCalNFMDJUSGs1Ym1GWVVtOWtWMGwxV1RJNWRBcE1Na1l6WTNreGQySXpaR3hqYmxKMllqSjRlazFDYTBkRGFYTkhRVkZSUW1jM09IZEJVa1ZGUTNkM1NrMVVTVFZOVkVrelRtcE5ORTFJT0VkRGFYTkhDa0ZSVVVKbk56aDNRVkpKUldOUmVIWmhTRkl3WTBoTk5reDVPVzVoV0ZKdlpGZEpkVmt5T1hSTU1rWXpZM2t4ZDJJelpHeGpibEoyWWpKNGVrd3pRbllLWkRKV2VXUkhPWFppU0UxMFlrZEdkRmx0VW1oTVdFSTFaRWRvZG1KcE9IVmFNbXd3WVVoV2FVd3paSFpqYlhSdFlrYzVNMk41T1hkamJWVjBZMjFXY3dwYVYwWjZXbE0xTldKWGVFRmpiVlp0WTNrNWIxcFhSbXRqZVRscldsaGFiR0pIT1hkTlJHZEhRMmx6UjBGUlVVSm5OemgzUVZKTlJVdG5kMjlQUkVwcUNscHRSbTFaYlZKc1dsUkJNMWw2V21oWmFrcHBUMWRaZWsxNlNYZFBWRTE2VFhwVmVVMUVVWGRhUkUxNVdrUlZNRTU2UVZsQ1oyOXlRbWRGUlVGWlR5OEtUVUZGVlVKQmIwMURTRTVxWVVkV2EyUlhlR3hOUnpSSFEybHpSMEZSVVVKbk56aDNRVkpWUlZsQmVHVmhTRkl3WTBoTk5reDVPVzVoV0ZKdlpGZEpkUXBaTWpsMFRESkdNMk41TVhkaU0yUnNZMjVTZG1JeWVIcE1NMEoyWkRKV2VXUkhPWFppU0UxMFlrZEdkRmx0VW1oTVdFSTFaRWRvZG1KcE9XaFpNMUp3Q21JeU5YcE1NMG94WW01TmRrMVVVWGxOZW1NMVRrUm5ORTVFVFhaWldGSXdXbGN4ZDJSSVRYWk5WRUZYUW1kdmNrSm5SVVZCV1U4dlRVRkZWMEpCWjAwS1FtNUNNVmx0ZUhCWmVrTkNhWGRaUzB0M1dVSkNRVWhYWlZGSlJVRm5VamxDU0hOQlpWRkNNMEZPTURsTlIzSkhlSGhGZVZsNGEyVklTbXh1VG5kTGFRcFRiRFkwTTJwNWRDODBaVXRqYjBGMlMyVTJUMEZCUVVKc1puRjRiak5OUVVGQlVVUkJSV2QzVW1kSmFFRlBjRkIzV1hRck9HaEpZVlJOVW5sWGJuVlRDbFJDYUdvck16ZzVka2xLTWs5SmVrcFlTVmQ2TlhsU2RVRnBSVUV6WVZKSlZHeDBOM1pqVjB0SVFWVnhWMXB2UmxObGExQkpNazhyUjI4NU16YzRXbVlLWkZwYVdrMUdRWGREWjFsSlMyOWFTWHBxTUVWQmQwMUVZVUZCZDFwUlNYZElaRVJCZVd4WVdrSXJaRWx3Y0dSaVozUlBlalJ5ZFdzNVFXeHpTamxrTlFwNFFtTnJlVmtyY205YVRuVnpXREowYms5Q2VEWTJkMlF3ZWs1c05rMTBTMEZxUlVGeGQyNWxaMWh0U201bmMyUm1LM2xVZVd4dFVtMUZkRloxVHpOcENtSXlWbmt2TDBKVU56ZHBVRFJoVkZBclZYcE5TVUZwVmpST1NGaGphalZrZFhac1lRb3RMUzB0TFVWT1JDQkRSVkpVU1VaSlEwRlVSUzB0TFMwdENnPT0ifV19fQ=="}]}, "dsseEnvelope":{"payload":"eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjAuMSIsInByZWRpY2F0ZVR5cGUiOiJodHRwczovL3Nsc2EuZGV2L3Byb3ZlbmFuY2UvdjAuMiIsInN1YmplY3QiOlt7Im5hbWUiOiIuL2F3c19sYW1iZGFfcG93ZXJ0b29scy0zLjkuMWE2LXB5My1ub25lLWFueS53aGwiLCJkaWdlc3QiOnsic2hhMjU2IjoiNTlkZGUyNDJiNzI4MDY3MGFjYjhlMTAxZGQ4MWZkZjdlNDMxMjQyYjYxMTA5Zjk5OWI3YzZlZTMyYmYyYzQ5MiJ9fSx7Im5hbWUiOiIuL2F3c19sYW1iZGFfcG93ZXJ0b29scy0zLjkuMWE2LnRhci5neiIsImRpZ2VzdCI6eyJzaGEyNTYiOiI2MTFmMjQ0MWY0OTFiMGJjNDc2MzMzMDk3YTcxNTQ1NWZmZjE5ZDZlNzVkYWNmMmFiNWFhZmZhNTMwMzUwZWY4In19XSwicHJlZGljYXRlIjp7ImJ1aWxkZXIiOnsiaWQiOiJodHRwczovL2dpdGh1Yi5jb20vc2xzYS1mcmFtZXdvcmsvc2xzYS1naXRodWItZ2VuZXJhdG9yLy5naXRodWIvd29ya2Zsb3dzL2dlbmVyYXRvcl9nZW5lcmljX3Nsc2EzLnltbEByZWZzL3RhZ3MvdjIuMS4wIn0sImJ1aWxkVHlwZSI6Imh0dHBzOi8vZ2l0aHViLmNvbS9zbHNhLWZyYW1ld29yay9zbHNhLWdpdGh1Yi1nZW5lcmF0b3IvZ2VuZXJpY0B2MSIsImludm9jYXRpb24iOnsiY29uZmlnU291cmNlIjp7InVyaSI6ImdpdCtodHRwczovL2dpdGh1Yi5jb20vYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uQHJlZnMvaGVhZHMvZGV2ZWxvcCIsImRpZ2VzdCI6eyJzaGExIjoiODJjZmFmYmRlZTA3YzZhYjJiOWYzMzIwOTMzMzUyMDQwZDMyZDU0NyJ9LCJlbnRyeVBvaW50IjoiLmdpdGh1Yi93b3JrZmxvd3MvcHJlLXJlbGVhc2UueW1sIn0sImVudmlyb25tZW50Ijp7ImdpdGh1Yl9hY3RvciI6ImxlYW5kcm9kYW1hc2NlbmEiLCJnaXRodWJfYWN0b3JfaWQiOiI0Mjk1MTczIiwiZ2l0aHViX2Jhc2VfcmVmIjoiIiwiZ2l0aHViX2V2ZW50X25hbWUiOiJzY2hlZHVsZSIsImdpdGh1Yl9ldmVudF9wYXlsb2FkIjp7ImVudGVycHJpc2UiOnsiYXZhdGFyX3VybCI6Imh0dHBzOi8vYXZhdGFycy5naXRodWJ1c2VyY29udGVudC5jb20vYi8xMjkwP3Y9NCIsImNyZWF0ZWRfYXQiOiIyMDE5LTExLTEzVDE4OjA1OjQxWiIsImRlc2NyaXB0aW9uIjoiIiwiaHRtbF91cmwiOiJodHRwczovL2dpdGh1Yi5jb20vZW50ZXJwcmlzZXMvYW1hem9uIiwiaWQiOjEyOTAsIm5hbWUiOiJBbWF6b24iLCJub2RlX2lkIjoiTURFd09rVnVkR1Z5Y0hKcGMyVXhNamt3Iiwic2x1ZyI6ImFtYXpvbiIsInVwZGF0ZWRfYXQiOiIyMDI0LTA5LTMwVDIxOjAyOjMwWiIsIndlYnNpdGVfdXJsIjoiaHR0cHM6Ly93d3cuYW1hem9uLmNvbS8ifSwib3JnYW5pemF0aW9uIjp7ImF2YXRhcl91cmwiOiJodHRwczovL2F2YXRhcnMuZ2l0aHVidXNlcmNvbnRlbnQuY29tL3UvMTI5MTI3NjM4P3Y9NCIsImRlc2NyaXB0aW9uIjoiIiwiZXZlbnRzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vb3Jncy9hd3MtcG93ZXJ0b29scy9ldmVudHMiLCJob29rc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL29yZ3MvYXdzLXBvd2VydG9vbHMvaG9va3MiLCJpZCI6MTI5MTI3NjM4LCJpc3N1ZXNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9vcmdzL2F3cy1wb3dlcnRvb2xzL2lzc3VlcyIsImxvZ2luIjoiYXdzLXBvd2VydG9vbHMiLCJtZW1iZXJzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vb3Jncy9hd3MtcG93ZXJ0b29scy9tZW1iZXJzey9tZW1iZXJ9Iiwibm9kZV9pZCI6Ik9fa2dET0I3SlUxZyIsInB1YmxpY19tZW1iZXJzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vb3Jncy9hd3MtcG93ZXJ0b29scy9wdWJsaWNfbWVtYmVyc3svbWVtYmVyfSIsInJlcG9zX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vb3Jncy9hd3MtcG93ZXJ0b29scy9yZXBvcyIsInVybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vb3Jncy9hd3MtcG93ZXJ0b29scyJ9LCJyZXBvc2l0b3J5Ijp7ImFsbG93X2ZvcmtpbmciOnRydWUsImFyY2hpdmVfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24ve2FyY2hpdmVfZm9ybWF0fXsvcmVmfSIsImFyY2hpdmVkIjpmYWxzZSwiYXNzaWduZWVzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2Fzc2lnbmVlc3svdXNlcn0iLCJibG9ic191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9naXQvYmxvYnN7L3NoYX0iLCJicmFuY2hlc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9icmFuY2hlc3svYnJhbmNofSIsImNsb25lX3VybCI6Imh0dHBzOi8vZ2l0aHViLmNvbS9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24uZ2l0IiwiY29sbGFib3JhdG9yc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9jb2xsYWJvcmF0b3Jzey9jb2xsYWJvcmF0b3J9IiwiY29tbWVudHNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vY29tbWVudHN7L251bWJlcn0iLCJjb21taXRzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2NvbW1pdHN7L3NoYX0iLCJjb21wYXJlX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2NvbXBhcmUve2Jhc2V9Li4ue2hlYWR9IiwiY29udGVudHNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vY29udGVudHMveytwYXRofSIsImNvbnRyaWJ1dG9yc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9jb250cmlidXRvcnMiLCJjcmVhdGVkX2F0IjoiMjAxOS0xMS0xNVQxMjoyNjoxMloiLCJjdXN0b21fcHJvcGVydGllcyI6e30sImRlZmF1bHRfYnJhbmNoIjoiZGV2ZWxvcCIsImRlcGxveW1lbnRzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2RlcGxveW1lbnRzIiwiZGVzY3JpcHRpb24iOiJBIGRldmVsb3BlciB0b29sa2l0IHRvIGltcGxlbWVudCBTZXJ2ZXJsZXNzIGJlc3QgcHJhY3RpY2VzIGFuZCBpbmNyZWFzZSBkZXZlbG9wZXIgdmVsb2NpdHkuIiwiZGlzYWJsZWQiOmZhbHNlLCJkb3dubG9hZHNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vZG93bmxvYWRzIiwiZXZlbnRzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2V2ZW50cyIsImZvcmsiOmZhbHNlLCJmb3JrcyI6NDE2LCJmb3Jrc19jb3VudCI6NDE2LCJmb3Jrc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9mb3JrcyIsImZ1bGxfbmFtZSI6ImF3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbiIsImdpdF9jb21taXRzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2dpdC9jb21taXRzey9zaGF9IiwiZ2l0X3JlZnNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vZ2l0L3JlZnN7L3NoYX0iLCJnaXRfdGFnc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9naXQvdGFnc3svc2hhfSIsImdpdF91cmwiOiJnaXQ6Ly9naXRodWIuY29tL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi5naXQiLCJoYXNfZGlzY3Vzc2lvbnMiOnRydWUsImhhc19kb3dubG9hZHMiOnRydWUsImhhc19pc3N1ZXMiOnRydWUsImhhc19wYWdlcyI6ZmFsc2UsImhhc19wcm9qZWN0cyI6dHJ1ZSwiaGFzX3dpa2kiOmZhbHNlLCJob21lcGFnZSI6Imh0dHBzOi8vZG9jcy5wb3dlcnRvb2xzLmF3cy5kZXYvbGFtYmRhL3B5dGhvbi9sYXRlc3QvIiwiaG9va3NfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vaG9va3MiLCJodG1sX3VybCI6Imh0dHBzOi8vZ2l0aHViLmNvbS9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24iLCJpZCI6MjIxOTE5Mzc5LCJpc190ZW1wbGF0ZSI6ZmFsc2UsImlzc3VlX2NvbW1lbnRfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vaXNzdWVzL2NvbW1lbnRzey9udW1iZXJ9IiwiaXNzdWVfZXZlbnRzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2lzc3Vlcy9ldmVudHN7L251bWJlcn0iLCJpc3N1ZXNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vaXNzdWVzey9udW1iZXJ9Iiwia2V5c191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9rZXlzey9rZXlfaWR9IiwibGFiZWxzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2xhYmVsc3svbmFtZX0iLCJsYW5ndWFnZSI6IlB5dGhvbiIsImxhbmd1YWdlc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9sYW5ndWFnZXMiLCJsaWNlbnNlIjp7ImtleSI6Im1pdC0wIiwibmFtZSI6Ik1JVCBObyBBdHRyaWJ1dGlvbiIsIm5vZGVfaWQiOiJNRGM2VEdsalpXNXpaVFF4Iiwic3BkeF9pZCI6Ik1JVC0wIiwidXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9saWNlbnNlcy9taXQtMCJ9LCJtZXJnZXNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vbWVyZ2VzIiwibWlsZXN0b25lc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9taWxlc3RvbmVzey9udW1iZXJ9IiwibWlycm9yX3VybCI6bnVsbCwibmFtZSI6InBvd2VydG9vbHMtbGFtYmRhLXB5dGhvbiIsIm5vZGVfaWQiOiJNREV3T2xKbGNHOXphWFJ2Y25reU1qRTVNVGt6TnprPSIsIm5vdGlmaWNhdGlvbnNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vbm90aWZpY2F0aW9uc3s/c2luY2UsYWxsLHBhcnRpY2lwYXRpbmd9Iiwib3Blbl9pc3N1ZXMiOjU0LCJvcGVuX2lzc3Vlc19jb3VudCI6NTQsIm93bmVyIjp7ImF2YXRhcl91cmwiOiJodHRwczovL2F2YXRhcnMuZ2l0aHVidXNlcmNvbnRlbnQuY29tL3UvMTI5MTI3NjM4P3Y9NCIsImV2ZW50c191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3VzZXJzL2F3cy1wb3dlcnRvb2xzL2V2ZW50c3svcHJpdmFjeX0iLCJmb2xsb3dlcnNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS91c2Vycy9hd3MtcG93ZXJ0b29scy9mb2xsb3dlcnMiLCJmb2xsb3dpbmdfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS91c2Vycy9hd3MtcG93ZXJ0b29scy9mb2xsb3dpbmd7L290aGVyX3VzZXJ9IiwiZ2lzdHNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS91c2Vycy9hd3MtcG93ZXJ0b29scy9naXN0c3svZ2lzdF9pZH0iLCJncmF2YXRhcl9pZCI6IiIsImh0bWxfdXJsIjoiaHR0cHM6Ly9naXRodWIuY29tL2F3cy1wb3dlcnRvb2xzIiwiaWQiOjEyOTEyNzYzOCwibG9naW4iOiJhd3MtcG93ZXJ0b29scyIsIm5vZGVfaWQiOiJPX2tnRE9CN0pVMWciLCJvcmdhbml6YXRpb25zX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vdXNlcnMvYXdzLXBvd2VydG9vbHMvb3JncyIsInJlY2VpdmVkX2V2ZW50c191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3VzZXJzL2F3cy1wb3dlcnRvb2xzL3JlY2VpdmVkX2V2ZW50cyIsInJlcG9zX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vdXNlcnMvYXdzLXBvd2VydG9vbHMvcmVwb3MiLCJzaXRlX2FkbWluIjpmYWxzZSwic3RhcnJlZF91cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3VzZXJzL2F3cy1wb3dlcnRvb2xzL3N0YXJyZWR7L293bmVyfXsvcmVwb30iLCJzdWJzY3JpcHRpb25zX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vdXNlcnMvYXdzLXBvd2VydG9vbHMvc3Vic2NyaXB0aW9ucyIsInR5cGUiOiJPcmdhbml6YXRpb24iLCJ1cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3VzZXJzL2F3cy1wb3dlcnRvb2xzIiwidXNlcl92aWV3X3R5cGUiOiJwdWJsaWMifSwicHJpdmF0ZSI6ZmFsc2UsInB1bGxzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL3B1bGxzey9udW1iZXJ9IiwicHVzaGVkX2F0IjoiMjAyNS0wNC0wM1QwNzo1NjozNVoiLCJyZWxlYXNlc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9yZWxlYXNlc3svaWR9Iiwic2l6ZSI6MTA0NzQxLCJzc2hfdXJsIjoiZ2l0QGdpdGh1Yi5jb206YXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uLmdpdCIsInN0YXJnYXplcnNfY291bnQiOjMwMTgsInN0YXJnYXplcnNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vc3RhcmdhemVycyIsInN0YXR1c2VzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL3N0YXR1c2VzL3tzaGF9Iiwic3Vic2NyaWJlcnNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vc3Vic2NyaWJlcnMiLCJzdWJzY3JpcHRpb25fdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vc3Vic2NyaXB0aW9uIiwic3ZuX3VybCI6Imh0dHBzOi8vZ2l0aHViLmNvbS9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24iLCJ0YWdzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL3RhZ3MiLCJ0ZWFtc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi90ZWFtcyIsInRvcGljcyI6WyJhd3MiLCJhd3MtbGFtYmRhIiwiaGFja3RvYmVyZmVzdCIsImxhbWJkYSIsInB5dGhvbiIsInNlcnZlcmxlc3MiXSwidHJlZXNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vZ2l0L3RyZWVzey9zaGF9IiwidXBkYXRlZF9hdCI6IjIwMjUtMDQtMDNUMDc6NTQ6MDNaIiwidXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24iLCJ2aXNpYmlsaXR5IjoicHVibGljIiwid2F0Y2hlcnMiOjMwMTgsIndhdGNoZXJzX2NvdW50IjozMDE4LCJ3ZWJfY29tbWl0X3NpZ25vZmZfcmVxdWlyZWQiOnRydWV9LCJzY2hlZHVsZSI6IjAgOCAqICogMS01Iiwid29ya2Zsb3ciOiIuZ2l0aHViL3dvcmtmbG93cy9wcmUtcmVsZWFzZS55bWwifSwiZ2l0aHViX2hlYWRfcmVmIjoiIiwiZ2l0aHViX3JlZiI6InJlZnMvaGVhZHMvZGV2ZWxvcCIsImdpdGh1Yl9yZWZfdHlwZSI6ImJyYW5jaCIsImdpdGh1Yl9yZXBvc2l0b3J5X2lkIjoiMjIxOTE5Mzc5IiwiZ2l0aHViX3JlcG9zaXRvcnlfb3duZXIiOiJhd3MtcG93ZXJ0b29scyIsImdpdGh1Yl9yZXBvc2l0b3J5X293bmVyX2lkIjoiMTI5MTI3NjM4IiwiZ2l0aHViX3J1bl9hdHRlbXB0IjoiMSIsImdpdGh1Yl9ydW5faWQiOiIxNDIzNzk0ODg0MyIsImdpdGh1Yl9ydW5fbnVtYmVyIjoiMjExIiwiZ2l0aHViX3NoYTEiOiI4MmNmYWZiZGVlMDdjNmFiMmI5ZjMzMjA5MzMzNTIwNDBkMzJkNTQ3In19LCJtZXRhZGF0YSI6eyJidWlsZEludm9jYXRpb25JRCI6IjE0MjM3OTQ4ODQzLTEiLCJjb21wbGV0ZW5lc3MiOnsicGFyYW1ldGVycyI6dHJ1ZSwiZW52aXJvbm1lbnQiOmZhbHNlLCJtYXRlcmlhbHMiOmZhbHNlfSwicmVwcm9kdWNpYmxlIjpmYWxzZX0sIm1hdGVyaWFscyI6W3sidXJpIjoiZ2l0K2h0dHBzOi8vZ2l0aHViLmNvbS9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob25AcmVmcy9oZWFkcy9kZXZlbG9wIiwiZGlnZXN0Ijp7InNoYTEiOiI4MmNmYWZiZGVlMDdjNmFiMmI5ZjMzMjA5MzMzNTIwNDBkMzJkNTQ3In19XX19", "payloadType":"application/vnd.in-toto+json", "signatures":[{"sig":"MEUCIQDIfEZJm/fTyFHeNuql8Rl3ewQVQqnKFDLIjP6dVly90QIgE1EHdKk5iS1gfiBMKgOygA+0utPy8TzyAnlnyo97Js0="}]}}
\ No newline at end of file
diff --git a/provenance/3.9.1a7/multiple.intoto.jsonl b/provenance/3.9.1a7/multiple.intoto.jsonl
new file mode 100644
index 00000000000..eeeef36663d
--- /dev/null
+++ b/provenance/3.9.1a7/multiple.intoto.jsonl
@@ -0,0 +1 @@
+{"mediaType":"application/vnd.dev.sigstore.bundle.v0.3+json", "verificationMaterial":{"certificate":{"rawBytes":"MIIHZDCCBuugAwIBAgIUOlCkm5Ub5G7pSNyUuw5pPkVQ3mAwCgYIKoZIzj0EAwMwNzEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MR4wHAYDVQQDExVzaWdzdG9yZS1pbnRlcm1lZGlhdGUwHhcNMjUwNDA0MDgwODAwWhcNMjUwNDA0MDgxODAwWjAAMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE6AwIucEf4DIfDTBreweItEfsIdhEGrqe8a1pGi4Kd7cziLiq5utaDmjJ4gJBajYSP3GO+IkPJeQNAhcrPNeUXqOCBgowggYGMA4GA1UdDwEB/wQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcDAzAdBgNVHQ4EFgQULKecBoL6e0ESX+itHpy2O3nBjE4wHwYDVR0jBBgwFoAU39Ppz1YkEZb5qNjpKFWixi4YZD8wgYQGA1UdEQEB/wR6MHiGdmh0dHBzOi8vZ2l0aHViLmNvbS9zbHNhLWZyYW1ld29yay9zbHNhLWdpdGh1Yi1nZW5lcmF0b3IvLmdpdGh1Yi93b3JrZmxvd3MvZ2VuZXJhdG9yX2dlbmVyaWNfc2xzYTMueW1sQHJlZnMvdGFncy92Mi4xLjAwOQYKKwYBBAGDvzABAQQraHR0cHM6Ly90b2tlbi5hY3Rpb25zLmdpdGh1YnVzZXJjb250ZW50LmNvbTAWBgorBgEEAYO/MAECBAhzY2hlZHVsZTA2BgorBgEEAYO/MAEDBCg4NzIzNjBiNjMzYmI5NjljNDhiY2E5OTA3NTE3ODQyMzU3MTFmMmE3MBkGCisGAQQBg78wAQQEC1ByZS1SZWxlYXNlMDUGCisGAQQBg78wAQUEJ2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbjAgBgorBgEEAYO/MAEGBBJyZWZzL2hlYWRzL2RldmVsb3AwOwYKKwYBBAGDvzABCAQtDCtodHRwczovL3Rva2VuLmFjdGlvbnMuZ2l0aHVidXNlcmNvbnRlbnQuY29tMIGGBgorBgEEAYO/MAEJBHgMdmh0dHBzOi8vZ2l0aHViLmNvbS9zbHNhLWZyYW1ld29yay9zbHNhLWdpdGh1Yi1nZW5lcmF0b3IvLmdpdGh1Yi93b3JrZmxvd3MvZ2VuZXJhdG9yX2dlbmVyaWNfc2xzYTMueW1sQHJlZnMvdGFncy92Mi4xLjAwOAYKKwYBBAGDvzABCgQqDChmN2RkOGM1NGMyMDY3YmFmYzEyY2E3YTU1NTk1ZDVlZTliNzUyMDRhMB0GCisGAQQBg78wAQsEDwwNZ2l0aHViLWhvc3RlZDBKBgorBgEEAYO/MAEMBDwMOmh0dHBzOi8vZ2l0aHViLmNvbS9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24wOAYKKwYBBAGDvzABDQQqDCg4NzIzNjBiNjMzYmI5NjljNDhiY2E5OTA3NTE3ODQyMzU3MTFmMmE3MCIGCisGAQQBg78wAQ4EFAwScmVmcy9oZWFkcy9kZXZlbG9wMBkGCisGAQQBg78wAQ8ECwwJMjIxOTE5Mzc5MDEGCisGAQQBg78wARAEIwwhaHR0cHM6Ly9naXRodWIuY29tL2F3cy1wb3dlcnRvb2xzMBkGCisGAQQBg78wAREECwwJMTI5MTI3NjM4MH8GCisGAQQBg78wARIEcQxvaHR0cHM6Ly9naXRodWIuY29tL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi8uZ2l0aHViL3dvcmtmbG93cy9wcmUtcmVsZWFzZS55bWxAcmVmcy9oZWFkcy9kZXZlbG9wMDgGCisGAQQBg78wARMEKgwoODcyMzYwYjYzM2JiOTY5YzQ4YmNhOTkwNzUxNzg0MjM1NzExZjJhNzAYBgorBgEEAYO/MAEUBAoMCHNjaGVkdWxlMG4GCisGAQQBg78wARUEYAxeaHR0cHM6Ly9naXRodWIuY29tL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9hY3Rpb25zL3J1bnMvMTQyNjA3OTkyMTIvYXR0ZW1wdHMvMTAWBgorBgEEAYO/MAEWBAgMBnB1YmxpYzCBiQYKKwYBBAHWeQIEAgR7BHkAdwB1AN09MGrGxxEyYxkeHJlnNwKiSl643jyt/4eKcoAvKe6OAAABlf/Xr9MAAAQDAEYwRAIgEOrLvfjNbHjD2szNd0I2iwXfH2b3CKAz16ruHNXwWxACIBZYSJ4QIPxoYU9wxtH4OZUHwn/cTF0A+2VzcGWUQy0zMAoGCCqGSM49BAMDA2cAMGQCL0o8ID4k9gMHMKarJpSKG+tRqdgzC+MIsC+XYu2iD/Zym5zffKumv0qLm10srJufAjEAn0sj3toY9/FHrKAhsca7AZLialy59ytHD9cgtQPeXXmVCof7QDiOLymkEoxq7XJF"}, "tlogEntries":[{"logIndex":"192260504", "logId":{"keyId":"wNI9atQGlz+VWfO6LRygH4QUfY/8W4RFwiT5i5WRgB0="}, "kindVersion":{"kind":"dsse", "version":"0.0.1"}, "integratedTime":"1743754080", "inclusionPromise":{"signedEntryTimestamp":"MEUCIDFAivtnj4XZgJBi//C2mhKzuqCQDUxxkZVd99WqkhQ5AiEAkqD3ckp0VaZGTOxQAntg3pCthVeKGUMcm2H5k56XncI="}, "inclusionProof":{"logIndex":"70356242", "rootHash":"TlKiWoz5aQe13VObCE+vOraGdIfYamy2Jo8P//gdvz4=", "treeSize":"70356243", "hashes":["zPhNkcjMl5EqjP6ZO6OsEfWg3tRu/pBG0nxMCzeuoW8=", "yMN1wnXXZgyOorT4F0jSFtzYgSlI18NVSG+r6d/rvV0=", "Lm6En2QMulEa7zWv5zeUyVN71g8ZfaWZlz3vGc6Hzjk=", "yrzxtz63h65ixliA32iDxpdLLqtaDdq6M0E3Tzoiyec=", "s70cWw81d1Z54njEmYbLE2dozUWCd4OnEjQ4cygRbYQ=", "1a7qEwExZ5cDvl0WZKGt3jd/MvhXbS5+Sz8BuDFNLTU=", "A1o8QsJ9CxtXiqhLTuowfXtlkUdmTh5zA0MN4mEIfTI=", "8k5uuLrcciIjuShVDkTHUWyh1g+zYYW5wml3FH7EdB4=", "C2a68tJEURTNteL5zYmjaa205qVnkObfZhjeUxj5i1g=", "7v8qPHNDLerpduaMx06eb/MwgoQwczTn/cYGKX/9wZ4="], "checkpoint":{"envelope":"rekor.sigstore.dev - 1193050959916656506\n70356243\nTlKiWoz5aQe13VObCE+vOraGdIfYamy2Jo8P//gdvz4=\n\n— rekor.sigstore.dev wNI9ajBGAiEA6ThnwiupS30RcwjyxZGuXrtP5YsKVrIHvFbyB5ioGZkCIQCpm0ohuLmbp5bHu4vkmSKUz94Z/jRXJ1yHA7s//bpr8w==\n"}}, "canonicalizedBody":"eyJhcGlWZXJzaW9uIjoiMC4wLjEiLCJraW5kIjoiZHNzZSIsInNwZWMiOnsiZW52ZWxvcGVIYXNoIjp7ImFsZ29yaXRobSI6InNoYTI1NiIsInZhbHVlIjoiYTgxYzAzYjQzMjY5MDY2Zjg5NGEyMzEyNGJiZDVlOWY5ZmM2ZWU1MDAxNjFkMTRjNTNlMGI4MGQ0ZmIzN2U4NiJ9LCJwYXlsb2FkSGFzaCI6eyJhbGdvcml0aG0iOiJzaGEyNTYiLCJ2YWx1ZSI6ImUwN2UxYTFhN2U0MjQ0OWRjY2VmNDY3NzM3NDJkZWE0MzE3NGE1M2QwZDc3M2RmOTRmNGJjNzhjODQxMWE5MTcifSwic2lnbmF0dXJlcyI6W3sic2lnbmF0dXJlIjoiTUVRQ0lDK0EvRmdLZmNDd2Ira09HQWJ4ZE1EYkg5UEtQRnp0NUlYbkJmNkhrajNiQWlCeE85Y3ByKys5RjVmd2NJSERFS0RhblI1NTVWUjFCcXhVcnZCTExoS2Z3Zz09IiwidmVyaWZpZXIiOiJMUzB0TFMxQ1JVZEpUaUJEUlZKVVNVWkpRMEZVUlMwdExTMHRDazFKU1VoYVJFTkRRblYxWjBGM1NVSkJaMGxWVDJ4RGEyMDFWV0kxUnpkd1UwNTVWWFYzTlhCUWExWlJNMjFCZDBObldVbExiMXBKZW1vd1JVRjNUWGNLVG5wRlZrMUNUVWRCTVZWRlEyaE5UV015Ykc1ak0xSjJZMjFWZFZwSFZqSk5ValIzU0VGWlJGWlJVVVJGZUZaNllWZGtlbVJIT1hsYVV6RndZbTVTYkFwamJURnNXa2RzYUdSSFZYZElhR05PVFdwVmQwNUVRVEJOUkdkM1QwUkJkMWRvWTA1TmFsVjNUa1JCTUUxRVozaFBSRUYzVjJwQlFVMUdhM2RGZDFsSUNrdHZXa2w2YWpCRFFWRlpTVXR2V2tsNmFqQkVRVkZqUkZGblFVVTJRWGRKZFdORlpqUkVTV1pFVkVKeVpYZGxTWFJGWm5OSlpHaEZSM0p4WlRoaE1YQUtSMmswUzJRM1kzcHBUR2x4TlhWMFlVUnRha28wWjBwQ1lXcFpVMUF6UjA4clNXdFFTbVZSVGtGb1kzSlFUbVZWV0hGUFEwSm5iM2RuWjFsSFRVRTBSd3BCTVZWa1JIZEZRaTkzVVVWQmQwbElaMFJCVkVKblRsWklVMVZGUkVSQlMwSm5aM0pDWjBWR1FsRmpSRUY2UVdSQ1owNVdTRkUwUlVablVWVk1TMlZqQ2tKdlREWmxNRVZUV0N0cGRFaHdlVEpQTTI1Q2FrVTBkMGgzV1VSV1VqQnFRa0puZDBadlFWVXpPVkJ3ZWpGWmEwVmFZalZ4VG1wd1MwWlhhWGhwTkZrS1drUTRkMmRaVVVkQk1WVmtSVkZGUWk5M1VqWk5TR2xIWkcxb01HUklRbnBQYVRoMldqSnNNR0ZJVm1sTWJVNTJZbE01ZW1KSVRtaE1WMXA1V1ZjeGJBcGtNamw1WVhrNWVtSklUbWhNVjJSd1pFZG9NVmxwTVc1YVZ6VnNZMjFHTUdJelNYWk1iV1J3WkVkb01WbHBPVE5pTTBweVdtMTRkbVF6VFhaYU1sWjFDbHBZU21oa1J6bDVXREprYkdKdFZubGhWMDVtWXpKNGVsbFVUWFZsVnpGelVVaEtiRnB1VFhaa1IwWnVZM2s1TWsxcE5IaE1ha0YzVDFGWlMwdDNXVUlLUWtGSFJIWjZRVUpCVVZGeVlVaFNNR05JVFRaTWVUa3dZakowYkdKcE5XaFpNMUp3WWpJMWVreHRaSEJrUjJneFdXNVdlbHBZU21waU1qVXdXbGMxTUFwTWJVNTJZbFJCVjBKbmIzSkNaMFZGUVZsUEwwMUJSVU5DUVdoNldUSm9iRnBJVm5OYVZFRXlRbWR2Y2tKblJVVkJXVTh2VFVGRlJFSkRaelJPZWtsNkNrNXFRbWxPYWsxNldXMUpOVTVxYkdwT1JHaHBXVEpGTlU5VVFUTk9WRVV6VDBSUmVVMTZWVE5OVkVadFRXMUZNMDFDYTBkRGFYTkhRVkZSUW1jM09IY0tRVkZSUlVNeFFubGFVekZUV2xkNGJGbFlUbXhOUkZWSFEybHpSMEZSVVVKbk56aDNRVkZWUlVveVJqTmplVEYzWWpOa2JHTnVVblppTW5oNlRETkNkZ3BrTWxaNVpFYzVkbUpJVFhSaVIwWjBXVzFTYUV4WVFqVmtSMmgyWW1wQlowSm5iM0pDWjBWRlFWbFBMMDFCUlVkQ1FrcDVXbGRhZWt3eWFHeFpWMUo2Q2t3eVVteGtiVlp6WWpOQmQwOTNXVXRMZDFsQ1FrRkhSSFo2UVVKRFFWRjBSRU4wYjJSSVVuZGplbTkyVEROU2RtRXlWblZNYlVacVpFZHNkbUp1VFhVS1dqSnNNR0ZJVm1sa1dFNXNZMjFPZG1KdVVteGlibEYxV1RJNWRFMUpSMGRDWjI5eVFtZEZSVUZaVHk5TlFVVktRa2huVFdSdGFEQmtTRUo2VDJrNGRncGFNbXd3WVVoV2FVeHRUblppVXpsNllraE9hRXhYV25sWlZ6RnNaREk1ZVdGNU9YcGlTRTVvVEZka2NHUkhhREZaYVRGdVdsYzFiR050UmpCaU0wbDJDa3h0WkhCa1IyZ3hXV2s1TTJJelNuSmFiWGgyWkROTmRsb3lWblZhV0Vwb1pFYzVlVmd5Wkd4aWJWWjVZVmRPWm1NeWVIcFpWRTExWlZjeGMxRklTbXdLV201TmRtUkhSbTVqZVRreVRXazBlRXhxUVhkUFFWbExTM2RaUWtKQlIwUjJla0ZDUTJkUmNVUkRhRzFPTWxKclQwZE5NVTVIVFhsTlJGa3pXVzFHYlFwWmVrVjVXVEpGTTFsVVZURk9WR3N4V2tSV2JGcFViR2xPZWxWNVRVUlNhRTFDTUVkRGFYTkhRVkZSUW1jM09IZEJVWE5GUkhkM1Rsb3liREJoU0ZacENreFhhSFpqTTFKc1drUkNTMEpuYjNKQ1owVkZRVmxQTDAxQlJVMUNSSGROVDIxb01HUklRbnBQYVRoMldqSnNNR0ZJVm1sTWJVNTJZbE01YUdRelRYUUtZMGM1TTFwWVNqQmlNamx6WTNrNWQySXpaR3hqYmxKMllqSjRla3hYZUdoaVYwcHJXVk14ZDJWWVVtOWlNalIzVDBGWlMwdDNXVUpDUVVkRWRucEJRZ3BFVVZGeFJFTm5ORTU2U1hwT2FrSnBUbXBOZWxsdFNUVk9hbXhxVGtSb2FWa3lSVFZQVkVFelRsUkZNMDlFVVhsTmVsVXpUVlJHYlUxdFJUTk5RMGxIQ2tOcGMwZEJVVkZDWnpjNGQwRlJORVZHUVhkVFkyMVdiV041T1c5YVYwWnJZM2s1YTFwWVdteGlSemwzVFVKclIwTnBjMGRCVVZGQ1p6YzRkMEZST0VVS1EzZDNTazFxU1hoUFZFVTFUWHBqTlUxRVJVZERhWE5IUVZGUlFtYzNPSGRCVWtGRlNYZDNhR0ZJVWpCalNFMDJUSGs1Ym1GWVVtOWtWMGwxV1RJNWRBcE1Na1l6WTNreGQySXpaR3hqYmxKMllqSjRlazFDYTBkRGFYTkhRVkZSUW1jM09IZEJVa1ZGUTNkM1NrMVVTVFZOVkVrelRtcE5ORTFJT0VkRGFYTkhDa0ZSVVVKbk56aDNRVkpKUldOUmVIWmhTRkl3WTBoTk5reDVPVzVoV0ZKdlpGZEpkVmt5T1hSTU1rWXpZM2t4ZDJJelpHeGpibEoyWWpKNGVrd3pRbllLWkRKV2VXUkhPWFppU0UxMFlrZEdkRmx0VW1oTVdFSTFaRWRvZG1KcE9IVmFNbXd3WVVoV2FVd3paSFpqYlhSdFlrYzVNMk41T1hkamJWVjBZMjFXY3dwYVYwWjZXbE0xTldKWGVFRmpiVlp0WTNrNWIxcFhSbXRqZVRscldsaGFiR0pIT1hkTlJHZEhRMmx6UjBGUlVVSm5OemgzUVZKTlJVdG5kMjlQUkdONUNrMTZXWGRaYWxsNlRUSkthVTlVV1RWWmVsRTBXVzFPYUU5VWEzZE9lbFY0VG5wbk1FMXFUVEZPZWtWNFdtcEthRTU2UVZsQ1oyOXlRbWRGUlVGWlR5OEtUVUZGVlVKQmIwMURTRTVxWVVkV2EyUlhlR3hOUnpSSFEybHpSMEZSVVVKbk56aDNRVkpWUlZsQmVHVmhTRkl3WTBoTk5reDVPVzVoV0ZKdlpGZEpkUXBaTWpsMFRESkdNMk41TVhkaU0yUnNZMjVTZG1JeWVIcE1NMEoyWkRKV2VXUkhPWFppU0UxMFlrZEdkRmx0VW1oTVdFSTFaRWRvZG1KcE9XaFpNMUp3Q21JeU5YcE1NMG94WW01TmRrMVVVWGxPYWtFelQxUnJlVTFVU1haWldGSXdXbGN4ZDJSSVRYWk5WRUZYUW1kdmNrSm5SVVZCV1U4dlRVRkZWMEpCWjAwS1FtNUNNVmx0ZUhCWmVrTkNhVkZaUzB0M1dVSkNRVWhYWlZGSlJVRm5VamRDU0d0QlpIZENNVUZPTURsTlIzSkhlSGhGZVZsNGEyVklTbXh1VG5kTGFRcFRiRFkwTTJwNWRDODBaVXRqYjBGMlMyVTJUMEZCUVVKc1ppOVljamxOUVVGQlVVUkJSVmwzVWtGSlowVlBja3gyWm1wT1lraHFSREp6ZWs1a01Fa3lDbWwzV0daSU1tSXpRMHRCZWpFMmNuVklUbGgzVjNoQlEwbENXbGxUU2pSUlNWQjRiMWxWT1hkNGRFZzBUMXBWU0hkdUwyTlVSakJCS3pKV2VtTkhWMVVLVVhrd2VrMUJiMGREUTNGSFUwMDBPVUpCVFVSQk1tTkJUVWRSUTB3d2J6aEpSRFJyT1dkTlNFMUxZWEpLY0ZOTFJ5dDBVbkZrWjNwREswMUpjME1yV0FwWmRUSnBSQzlhZVcwMWVtWm1TM1Z0ZGpCeFRHMHhNSE55U25WbVFXcEZRVzR3YzJvemRHOVpPUzlHU0hKTFFXaHpZMkUzUVZwTWFXRnNlVFU1ZVhSSUNrUTVZMmQwVVZCbFdGaHRWa052WmpkUlJHbFBUSGx0YTBWdmVIRTNXRXBHQ2kwdExTMHRSVTVFSUVORlVsUkpSa2xEUVZSRkxTMHRMUzBLIn1dfX0="}]}, "dsseEnvelope":{"payload":"eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjAuMSIsInByZWRpY2F0ZVR5cGUiOiJodHRwczovL3Nsc2EuZGV2L3Byb3ZlbmFuY2UvdjAuMiIsInN1YmplY3QiOlt7Im5hbWUiOiIuL2F3c19sYW1iZGFfcG93ZXJ0b29scy0zLjkuMWE3LXB5My1ub25lLWFueS53aGwiLCJkaWdlc3QiOnsic2hhMjU2IjoiM2M3OWM0N2IyMDZiZDBiYWIwYzAxYzJlNTZkOGI1MDc1MDk5Y2FmNDZmNTViY2ZkNTEzMDg5Nzg5ODVkYzViYiJ9fSx7Im5hbWUiOiIuL2F3c19sYW1iZGFfcG93ZXJ0b29scy0zLjkuMWE3LnRhci5neiIsImRpZ2VzdCI6eyJzaGEyNTYiOiIzYWFhNjYxMGQ0NjM5MGNkNTgzNGY0NGNjNzI0NWY0MzBjYzA3ZGM2ZTU1YWMzZWExNjM5YWI0MmVkODExZjE2In19XSwicHJlZGljYXRlIjp7ImJ1aWxkZXIiOnsiaWQiOiJodHRwczovL2dpdGh1Yi5jb20vc2xzYS1mcmFtZXdvcmsvc2xzYS1naXRodWItZ2VuZXJhdG9yLy5naXRodWIvd29ya2Zsb3dzL2dlbmVyYXRvcl9nZW5lcmljX3Nsc2EzLnltbEByZWZzL3RhZ3MvdjIuMS4wIn0sImJ1aWxkVHlwZSI6Imh0dHBzOi8vZ2l0aHViLmNvbS9zbHNhLWZyYW1ld29yay9zbHNhLWdpdGh1Yi1nZW5lcmF0b3IvZ2VuZXJpY0B2MSIsImludm9jYXRpb24iOnsiY29uZmlnU291cmNlIjp7InVyaSI6ImdpdCtodHRwczovL2dpdGh1Yi5jb20vYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uQHJlZnMvaGVhZHMvZGV2ZWxvcCIsImRpZ2VzdCI6eyJzaGExIjoiODcyMzYwYjYzM2JiOTY5YzQ4YmNhOTkwNzUxNzg0MjM1NzExZjJhNyJ9LCJlbnRyeVBvaW50IjoiLmdpdGh1Yi93b3JrZmxvd3MvcHJlLXJlbGVhc2UueW1sIn0sImVudmlyb25tZW50Ijp7ImdpdGh1Yl9hY3RvciI6ImxlYW5kcm9kYW1hc2NlbmEiLCJnaXRodWJfYWN0b3JfaWQiOiI0Mjk1MTczIiwiZ2l0aHViX2Jhc2VfcmVmIjoiIiwiZ2l0aHViX2V2ZW50X25hbWUiOiJzY2hlZHVsZSIsImdpdGh1Yl9ldmVudF9wYXlsb2FkIjp7ImVudGVycHJpc2UiOnsiYXZhdGFyX3VybCI6Imh0dHBzOi8vYXZhdGFycy5naXRodWJ1c2VyY29udGVudC5jb20vYi8xMjkwP3Y9NCIsImNyZWF0ZWRfYXQiOiIyMDE5LTExLTEzVDE4OjA1OjQxWiIsImRlc2NyaXB0aW9uIjoiIiwiaHRtbF91cmwiOiJodHRwczovL2dpdGh1Yi5jb20vZW50ZXJwcmlzZXMvYW1hem9uIiwiaWQiOjEyOTAsIm5hbWUiOiJBbWF6b24iLCJub2RlX2lkIjoiTURFd09rVnVkR1Z5Y0hKcGMyVXhNamt3Iiwic2x1ZyI6ImFtYXpvbiIsInVwZGF0ZWRfYXQiOiIyMDI0LTA5LTMwVDIxOjAyOjMwWiIsIndlYnNpdGVfdXJsIjoiaHR0cHM6Ly93d3cuYW1hem9uLmNvbS8ifSwib3JnYW5pemF0aW9uIjp7ImF2YXRhcl91cmwiOiJodHRwczovL2F2YXRhcnMuZ2l0aHVidXNlcmNvbnRlbnQuY29tL3UvMTI5MTI3NjM4P3Y9NCIsImRlc2NyaXB0aW9uIjoiIiwiZXZlbnRzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vb3Jncy9hd3MtcG93ZXJ0b29scy9ldmVudHMiLCJob29rc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL29yZ3MvYXdzLXBvd2VydG9vbHMvaG9va3MiLCJpZCI6MTI5MTI3NjM4LCJpc3N1ZXNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9vcmdzL2F3cy1wb3dlcnRvb2xzL2lzc3VlcyIsImxvZ2luIjoiYXdzLXBvd2VydG9vbHMiLCJtZW1iZXJzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vb3Jncy9hd3MtcG93ZXJ0b29scy9tZW1iZXJzey9tZW1iZXJ9Iiwibm9kZV9pZCI6Ik9fa2dET0I3SlUxZyIsInB1YmxpY19tZW1iZXJzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vb3Jncy9hd3MtcG93ZXJ0b29scy9wdWJsaWNfbWVtYmVyc3svbWVtYmVyfSIsInJlcG9zX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vb3Jncy9hd3MtcG93ZXJ0b29scy9yZXBvcyIsInVybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vb3Jncy9hd3MtcG93ZXJ0b29scyJ9LCJyZXBvc2l0b3J5Ijp7ImFsbG93X2ZvcmtpbmciOnRydWUsImFyY2hpdmVfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24ve2FyY2hpdmVfZm9ybWF0fXsvcmVmfSIsImFyY2hpdmVkIjpmYWxzZSwiYXNzaWduZWVzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2Fzc2lnbmVlc3svdXNlcn0iLCJibG9ic191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9naXQvYmxvYnN7L3NoYX0iLCJicmFuY2hlc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9icmFuY2hlc3svYnJhbmNofSIsImNsb25lX3VybCI6Imh0dHBzOi8vZ2l0aHViLmNvbS9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24uZ2l0IiwiY29sbGFib3JhdG9yc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9jb2xsYWJvcmF0b3Jzey9jb2xsYWJvcmF0b3J9IiwiY29tbWVudHNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vY29tbWVudHN7L251bWJlcn0iLCJjb21taXRzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2NvbW1pdHN7L3NoYX0iLCJjb21wYXJlX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2NvbXBhcmUve2Jhc2V9Li4ue2hlYWR9IiwiY29udGVudHNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vY29udGVudHMveytwYXRofSIsImNvbnRyaWJ1dG9yc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9jb250cmlidXRvcnMiLCJjcmVhdGVkX2F0IjoiMjAxOS0xMS0xNVQxMjoyNjoxMloiLCJjdXN0b21fcHJvcGVydGllcyI6e30sImRlZmF1bHRfYnJhbmNoIjoiZGV2ZWxvcCIsImRlcGxveW1lbnRzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2RlcGxveW1lbnRzIiwiZGVzY3JpcHRpb24iOiJBIGRldmVsb3BlciB0b29sa2l0IHRvIGltcGxlbWVudCBTZXJ2ZXJsZXNzIGJlc3QgcHJhY3RpY2VzIGFuZCBpbmNyZWFzZSBkZXZlbG9wZXIgdmVsb2NpdHkuIiwiZGlzYWJsZWQiOmZhbHNlLCJkb3dubG9hZHNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vZG93bmxvYWRzIiwiZXZlbnRzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2V2ZW50cyIsImZvcmsiOmZhbHNlLCJmb3JrcyI6NDE3LCJmb3Jrc19jb3VudCI6NDE3LCJmb3Jrc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9mb3JrcyIsImZ1bGxfbmFtZSI6ImF3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbiIsImdpdF9jb21taXRzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2dpdC9jb21taXRzey9zaGF9IiwiZ2l0X3JlZnNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vZ2l0L3JlZnN7L3NoYX0iLCJnaXRfdGFnc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9naXQvdGFnc3svc2hhfSIsImdpdF91cmwiOiJnaXQ6Ly9naXRodWIuY29tL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi5naXQiLCJoYXNfZGlzY3Vzc2lvbnMiOnRydWUsImhhc19kb3dubG9hZHMiOnRydWUsImhhc19pc3N1ZXMiOnRydWUsImhhc19wYWdlcyI6ZmFsc2UsImhhc19wcm9qZWN0cyI6dHJ1ZSwiaGFzX3dpa2kiOmZhbHNlLCJob21lcGFnZSI6Imh0dHBzOi8vZG9jcy5wb3dlcnRvb2xzLmF3cy5kZXYvbGFtYmRhL3B5dGhvbi9sYXRlc3QvIiwiaG9va3NfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vaG9va3MiLCJodG1sX3VybCI6Imh0dHBzOi8vZ2l0aHViLmNvbS9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24iLCJpZCI6MjIxOTE5Mzc5LCJpc190ZW1wbGF0ZSI6ZmFsc2UsImlzc3VlX2NvbW1lbnRfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vaXNzdWVzL2NvbW1lbnRzey9udW1iZXJ9IiwiaXNzdWVfZXZlbnRzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2lzc3Vlcy9ldmVudHN7L251bWJlcn0iLCJpc3N1ZXNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vaXNzdWVzey9udW1iZXJ9Iiwia2V5c191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9rZXlzey9rZXlfaWR9IiwibGFiZWxzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2xhYmVsc3svbmFtZX0iLCJsYW5ndWFnZSI6IlB5dGhvbiIsImxhbmd1YWdlc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9sYW5ndWFnZXMiLCJsaWNlbnNlIjp7ImtleSI6Im1pdC0wIiwibmFtZSI6Ik1JVCBObyBBdHRyaWJ1dGlvbiIsIm5vZGVfaWQiOiJNRGM2VEdsalpXNXpaVFF4Iiwic3BkeF9pZCI6Ik1JVC0wIiwidXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9saWNlbnNlcy9taXQtMCJ9LCJtZXJnZXNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vbWVyZ2VzIiwibWlsZXN0b25lc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9taWxlc3RvbmVzey9udW1iZXJ9IiwibWlycm9yX3VybCI6bnVsbCwibmFtZSI6InBvd2VydG9vbHMtbGFtYmRhLXB5dGhvbiIsIm5vZGVfaWQiOiJNREV3T2xKbGNHOXphWFJ2Y25reU1qRTVNVGt6TnprPSIsIm5vdGlmaWNhdGlvbnNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vbm90aWZpY2F0aW9uc3s/c2luY2UsYWxsLHBhcnRpY2lwYXRpbmd9Iiwib3Blbl9pc3N1ZXMiOjU3LCJvcGVuX2lzc3Vlc19jb3VudCI6NTcsIm93bmVyIjp7ImF2YXRhcl91cmwiOiJodHRwczovL2F2YXRhcnMuZ2l0aHVidXNlcmNvbnRlbnQuY29tL3UvMTI5MTI3NjM4P3Y9NCIsImV2ZW50c191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3VzZXJzL2F3cy1wb3dlcnRvb2xzL2V2ZW50c3svcHJpdmFjeX0iLCJmb2xsb3dlcnNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS91c2Vycy9hd3MtcG93ZXJ0b29scy9mb2xsb3dlcnMiLCJmb2xsb3dpbmdfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS91c2Vycy9hd3MtcG93ZXJ0b29scy9mb2xsb3dpbmd7L290aGVyX3VzZXJ9IiwiZ2lzdHNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS91c2Vycy9hd3MtcG93ZXJ0b29scy9naXN0c3svZ2lzdF9pZH0iLCJncmF2YXRhcl9pZCI6IiIsImh0bWxfdXJsIjoiaHR0cHM6Ly9naXRodWIuY29tL2F3cy1wb3dlcnRvb2xzIiwiaWQiOjEyOTEyNzYzOCwibG9naW4iOiJhd3MtcG93ZXJ0b29scyIsIm5vZGVfaWQiOiJPX2tnRE9CN0pVMWciLCJvcmdhbml6YXRpb25zX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vdXNlcnMvYXdzLXBvd2VydG9vbHMvb3JncyIsInJlY2VpdmVkX2V2ZW50c191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3VzZXJzL2F3cy1wb3dlcnRvb2xzL3JlY2VpdmVkX2V2ZW50cyIsInJlcG9zX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vdXNlcnMvYXdzLXBvd2VydG9vbHMvcmVwb3MiLCJzaXRlX2FkbWluIjpmYWxzZSwic3RhcnJlZF91cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3VzZXJzL2F3cy1wb3dlcnRvb2xzL3N0YXJyZWR7L293bmVyfXsvcmVwb30iLCJzdWJzY3JpcHRpb25zX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vdXNlcnMvYXdzLXBvd2VydG9vbHMvc3Vic2NyaXB0aW9ucyIsInR5cGUiOiJPcmdhbml6YXRpb24iLCJ1cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3VzZXJzL2F3cy1wb3dlcnRvb2xzIiwidXNlcl92aWV3X3R5cGUiOiJwdWJsaWMifSwicHJpdmF0ZSI6ZmFsc2UsInB1bGxzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL3B1bGxzey9udW1iZXJ9IiwicHVzaGVkX2F0IjoiMjAyNS0wNC0wNFQwNzo0MTowNVoiLCJyZWxlYXNlc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9yZWxlYXNlc3svaWR9Iiwic2l6ZSI6MTA1NzUzLCJzc2hfdXJsIjoiZ2l0QGdpdGh1Yi5jb206YXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uLmdpdCIsInN0YXJnYXplcnNfY291bnQiOjMwMTgsInN0YXJnYXplcnNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vc3RhcmdhemVycyIsInN0YXR1c2VzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL3N0YXR1c2VzL3tzaGF9Iiwic3Vic2NyaWJlcnNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vc3Vic2NyaWJlcnMiLCJzdWJzY3JpcHRpb25fdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vc3Vic2NyaXB0aW9uIiwic3ZuX3VybCI6Imh0dHBzOi8vZ2l0aHViLmNvbS9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24iLCJ0YWdzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL3RhZ3MiLCJ0ZWFtc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi90ZWFtcyIsInRvcGljcyI6WyJhd3MiLCJhd3MtbGFtYmRhIiwiaGFja3RvYmVyZmVzdCIsImxhbWJkYSIsInB5dGhvbiIsInNlcnZlcmxlc3MiXSwidHJlZXNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vZ2l0L3RyZWVzey9zaGF9IiwidXBkYXRlZF9hdCI6IjIwMjUtMDQtMDRUMDc6Mzk6NDRaIiwidXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24iLCJ2aXNpYmlsaXR5IjoicHVibGljIiwid2F0Y2hlcnMiOjMwMTgsIndhdGNoZXJzX2NvdW50IjozMDE4LCJ3ZWJfY29tbWl0X3NpZ25vZmZfcmVxdWlyZWQiOnRydWV9LCJzY2hlZHVsZSI6IjAgOCAqICogMS01Iiwid29ya2Zsb3ciOiIuZ2l0aHViL3dvcmtmbG93cy9wcmUtcmVsZWFzZS55bWwifSwiZ2l0aHViX2hlYWRfcmVmIjoiIiwiZ2l0aHViX3JlZiI6InJlZnMvaGVhZHMvZGV2ZWxvcCIsImdpdGh1Yl9yZWZfdHlwZSI6ImJyYW5jaCIsImdpdGh1Yl9yZXBvc2l0b3J5X2lkIjoiMjIxOTE5Mzc5IiwiZ2l0aHViX3JlcG9zaXRvcnlfb3duZXIiOiJhd3MtcG93ZXJ0b29scyIsImdpdGh1Yl9yZXBvc2l0b3J5X293bmVyX2lkIjoiMTI5MTI3NjM4IiwiZ2l0aHViX3J1bl9hdHRlbXB0IjoiMSIsImdpdGh1Yl9ydW5faWQiOiIxNDI2MDc5OTIxMiIsImdpdGh1Yl9ydW5fbnVtYmVyIjoiMjEyIiwiZ2l0aHViX3NoYTEiOiI4NzIzNjBiNjMzYmI5NjljNDhiY2E5OTA3NTE3ODQyMzU3MTFmMmE3In19LCJtZXRhZGF0YSI6eyJidWlsZEludm9jYXRpb25JRCI6IjE0MjYwNzk5MjEyLTEiLCJjb21wbGV0ZW5lc3MiOnsicGFyYW1ldGVycyI6dHJ1ZSwiZW52aXJvbm1lbnQiOmZhbHNlLCJtYXRlcmlhbHMiOmZhbHNlfSwicmVwcm9kdWNpYmxlIjpmYWxzZX0sIm1hdGVyaWFscyI6W3sidXJpIjoiZ2l0K2h0dHBzOi8vZ2l0aHViLmNvbS9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob25AcmVmcy9oZWFkcy9kZXZlbG9wIiwiZGlnZXN0Ijp7InNoYTEiOiI4NzIzNjBiNjMzYmI5NjljNDhiY2E5OTA3NTE3ODQyMzU3MTFmMmE3In19XX19", "payloadType":"application/vnd.in-toto+json", "signatures":[{"sig":"MEQCIC+A/FgKfcCwb+kOGAbxdMDbH9PKPFzt5IXnBf6Hkj3bAiBxO9cpr++9F5fwcIHDEKDanR555VR1BqxUrvBLLhKfwg=="}]}}
\ No newline at end of file
diff --git a/provenance/3.9.1a8/multiple.intoto.jsonl b/provenance/3.9.1a8/multiple.intoto.jsonl
new file mode 100644
index 00000000000..eee32730f6c
--- /dev/null
+++ b/provenance/3.9.1a8/multiple.intoto.jsonl
@@ -0,0 +1 @@
+{"mediaType":"application/vnd.dev.sigstore.bundle.v0.3+json", "verificationMaterial":{"certificate":{"rawBytes":"MIIHZzCCBu2gAwIBAgIUHkRrzhWX/GS5R+2hd/sFFE+BxOAwCgYIKoZIzj0EAwMwNzEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MR4wHAYDVQQDExVzaWdzdG9yZS1pbnRlcm1lZGlhdGUwHhcNMjUwNDA3MDgwODEyWhcNMjUwNDA3MDgxODEyWjAAMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEZKX1kjEO+ekYjsBbi1U1L/QDYnkrcql99DFIkLqOlaT4rtBun8/spT0VGhU4D/ETQJFSLBmIBq4PdBmAMlmoXKOCBgwwggYIMA4GA1UdDwEB/wQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcDAzAdBgNVHQ4EFgQUHVQdnCW9nsdgYpGRKK9Pq/8pTe8wHwYDVR0jBBgwFoAU39Ppz1YkEZb5qNjpKFWixi4YZD8wgYQGA1UdEQEB/wR6MHiGdmh0dHBzOi8vZ2l0aHViLmNvbS9zbHNhLWZyYW1ld29yay9zbHNhLWdpdGh1Yi1nZW5lcmF0b3IvLmdpdGh1Yi93b3JrZmxvd3MvZ2VuZXJhdG9yX2dlbmVyaWNfc2xzYTMueW1sQHJlZnMvdGFncy92Mi4xLjAwOQYKKwYBBAGDvzABAQQraHR0cHM6Ly90b2tlbi5hY3Rpb25zLmdpdGh1YnVzZXJjb250ZW50LmNvbTAWBgorBgEEAYO/MAECBAhzY2hlZHVsZTA2BgorBgEEAYO/MAEDBChjYzQ3OWIwMWQxYjIxNTdjZTI4ZDczNmU2NTdhODVjMjNjYjgwNTY1MBkGCisGAQQBg78wAQQEC1ByZS1SZWxlYXNlMDUGCisGAQQBg78wAQUEJ2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbjAgBgorBgEEAYO/MAEGBBJyZWZzL2hlYWRzL2RldmVsb3AwOwYKKwYBBAGDvzABCAQtDCtodHRwczovL3Rva2VuLmFjdGlvbnMuZ2l0aHVidXNlcmNvbnRlbnQuY29tMIGGBgorBgEEAYO/MAEJBHgMdmh0dHBzOi8vZ2l0aHViLmNvbS9zbHNhLWZyYW1ld29yay9zbHNhLWdpdGh1Yi1nZW5lcmF0b3IvLmdpdGh1Yi93b3JrZmxvd3MvZ2VuZXJhdG9yX2dlbmVyaWNfc2xzYTMueW1sQHJlZnMvdGFncy92Mi4xLjAwOAYKKwYBBAGDvzABCgQqDChmN2RkOGM1NGMyMDY3YmFmYzEyY2E3YTU1NTk1ZDVlZTliNzUyMDRhMB0GCisGAQQBg78wAQsEDwwNZ2l0aHViLWhvc3RlZDBKBgorBgEEAYO/MAEMBDwMOmh0dHBzOi8vZ2l0aHViLmNvbS9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24wOAYKKwYBBAGDvzABDQQqDChjYzQ3OWIwMWQxYjIxNTdjZTI4ZDczNmU2NTdhODVjMjNjYjgwNTY1MCIGCisGAQQBg78wAQ4EFAwScmVmcy9oZWFkcy9kZXZlbG9wMBkGCisGAQQBg78wAQ8ECwwJMjIxOTE5Mzc5MDEGCisGAQQBg78wARAEIwwhaHR0cHM6Ly9naXRodWIuY29tL2F3cy1wb3dlcnRvb2xzMBkGCisGAQQBg78wAREECwwJMTI5MTI3NjM4MH8GCisGAQQBg78wARIEcQxvaHR0cHM6Ly9naXRodWIuY29tL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi8uZ2l0aHViL3dvcmtmbG93cy9wcmUtcmVsZWFzZS55bWxAcmVmcy9oZWFkcy9kZXZlbG9wMDgGCisGAQQBg78wARMEKgwoY2M0NzliMDFkMWIyMTU3Y2UyOGQ3MzZlNjU3YTg1YzIzY2I4MDU2NTAYBgorBgEEAYO/MAEUBAoMCHNjaGVkdWxlMG4GCisGAQQBg78wARUEYAxeaHR0cHM6Ly9naXRodWIuY29tL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9hY3Rpb25zL3J1bnMvMTQzMDM5NDUwODAvYXR0ZW1wdHMvMTAWBgorBgEEAYO/MAEWBAgMBnB1YmxpYzCBiwYKKwYBBAHWeQIEAgR9BHsAeQB3AN09MGrGxxEyYxkeHJlnNwKiSl643jyt/4eKcoAvKe6OAAABlg9K9a4AAAQDAEgwRgIhAPQVYg6WhNN9ebd9wecA1BKGJs6ulELGByGcbXOZHoS/AiEAjtflPA+bkgkhkgvVW/syBgfoyPQQqb5by5oBUtTtoZ0wCgYIKoZIzj0EAwMDaAAwZQIxAO0r4KaIUdp6UzUD74rpJomw5NUEjNnr8+TMU8XlOty4pX4O2334t67eqJ4OVFpJRwIwCBRvAeVoeNF0raYbS9KW0ac7BUG37gwrByyAl4nwJsnwT9AiqYC8HVdOK0JSaIGq"}, "tlogEntries":[{"logIndex":"193166369", "logId":{"keyId":"wNI9atQGlz+VWfO6LRygH4QUfY/8W4RFwiT5i5WRgB0="}, "kindVersion":{"kind":"dsse", "version":"0.0.1"}, "integratedTime":"1744013293", "inclusionPromise":{"signedEntryTimestamp":"MEYCIQD6UVX0U4bG2ZLaIpPJlAfpGF+37EtM4huyCCYA448QFwIhAJpt4e5aKaxgfFwQTA0ItkeUWHxW32YedOL2yiuWxWcj"}, "inclusionProof":{"logIndex":"71262107", "rootHash":"RcKR1WU4oysmHF4KXfEeTzPeESGwAxzYJRiaP6jNYs0=", "treeSize":"71262108", "hashes":["MIzgC3jDsrqTqd+jz0ht8+6Wa3vZnFesPrIzVLBDlWQ=", "iHiXstjKSkJxYHnfxcYMyu8FUTSrHrX6VeHew8NcHRw=", "PcmIOXKty9iN2bYkgnVrJ6hUz1eDZtTRPl4/5DEF29g=", "oyNrDLkNI2cAcBxK4U+4bMmgADH3izSRf5vLD6QnWNo=", "wql1qcxY+4grxnGpt8zjVOE86xHFEh3uM4SGpPnjy6Y=", "uAjEEEydgrnfyR+jXZMg9APWfRS2AEje++wxGb39EHA=", "YsI0ZJueOjNd9UY3iMr3um97NPoThSYfwz9aFzsMVK4=", "fShD00Qwbyp/z8rS1ly/DRtbx9YS7CXkOH424RRMfy4=", "heR49QE3j2ZMofhhhKRvwtAnVn+e5FnEachLDJB4bbE=", "7sQMltkHocxAEtSTwfydJT3DwQoNFi2gQVDppukZJkY=", "fbcDvFWxxvvXBFyLKrnYnHFg8qUKHTgY/SMAl9UerpY=", "WeMimyaUVpdLxfKcgHbgyus6ewR2L1dlzdZW7Df5ax8=", "BPcKCT6XFebKRdSgGfXWOSnuMVAoYlKoChg1mAVeDKk=", "6Nxz6uhbPIee9Np3j+GbPrtWcIUMKWV3JVuHKO+lKN8=", "BrIdACjySNUY3ziaNg0dSpzP6w13Lmo3iw11dBQBkXk=", "8k5uuLrcciIjuShVDkTHUWyh1g+zYYW5wml3FH7EdB4=", "C2a68tJEURTNteL5zYmjaa205qVnkObfZhjeUxj5i1g=", "7v8qPHNDLerpduaMx06eb/MwgoQwczTn/cYGKX/9wZ4="], "checkpoint":{"envelope":"rekor.sigstore.dev - 1193050959916656506\n71262108\nRcKR1WU4oysmHF4KXfEeTzPeESGwAxzYJRiaP6jNYs0=\n\n— rekor.sigstore.dev wNI9ajBFAiEA6hf779CMntXDRRDwJEDxEXAsnsfLqN2C222I3jzOquYCIGXuNHVI5hb/B56bN0yaDpOTvCe5OwfDVghlLzz+8H7m\n"}}, "canonicalizedBody":"eyJhcGlWZXJzaW9uIjoiMC4wLjEiLCJraW5kIjoiZHNzZSIsInNwZWMiOnsiZW52ZWxvcGVIYXNoIjp7ImFsZ29yaXRobSI6InNoYTI1NiIsInZhbHVlIjoiMjQ5YjllNDA2NjEwNDAyM2RhYmRhMzQ1NmZlYzUwMGM0Mzg0ZWIwYTlkMDhkNGQzOGQwMmZhN2U3OGIwYWY5MiJ9LCJwYXlsb2FkSGFzaCI6eyJhbGdvcml0aG0iOiJzaGEyNTYiLCJ2YWx1ZSI6ImE2MzZhZGJmNjNhZjViMzJlODE3ZDdkZTZmYzYyZjNhYmQ0MWUzZDIwZTVjMWRjYTExNzNmNDQ5NDhlYzAzMjcifSwic2lnbmF0dXJlcyI6W3sic2lnbmF0dXJlIjoiTUVRQ0lBTjBnb0FxUlJxZTk1djNtSXJlTXZJZkFsaHlvL3NRM3QrTVlZc2NHT2szQWlCeHlIZDJHZFpvcENZMXI1UlM4QVZDWFIybTFHNjNhRzd5cVlPMjBkNnFEdz09IiwidmVyaWZpZXIiOiJMUzB0TFMxQ1JVZEpUaUJEUlZKVVNVWkpRMEZVUlMwdExTMHRDazFKU1VoYWVrTkRRblV5WjBGM1NVSkJaMGxWU0d0U2NucG9WMWd2UjFNMVVpc3lhR1F2YzBaR1JTdENlRTlCZDBObldVbExiMXBKZW1vd1JVRjNUWGNLVG5wRlZrMUNUVWRCTVZWRlEyaE5UV015Ykc1ak0xSjJZMjFWZFZwSFZqSk5ValIzU0VGWlJGWlJVVVJGZUZaNllWZGtlbVJIT1hsYVV6RndZbTVTYkFwamJURnNXa2RzYUdSSFZYZElhR05PVFdwVmQwNUVRVE5OUkdkM1QwUkZlVmRvWTA1TmFsVjNUa1JCTTAxRVozaFBSRVY1VjJwQlFVMUdhM2RGZDFsSUNrdHZXa2w2YWpCRFFWRlpTVXR2V2tsNmFqQkVRVkZqUkZGblFVVmFTMWd4YTJwRlR5dGxhMWxxYzBKaWFURlZNVXd2VVVSWmJtdHlZM0ZzT1RsRVJra0thMHh4VDJ4aFZEUnlkRUoxYmpndmMzQlVNRlpIYUZVMFJDOUZWRkZLUmxOTVFtMUpRbkUwVUdSQ2JVRk5iRzF2V0V0UFEwSm5kM2RuWjFsSlRVRTBSd3BCTVZWa1JIZEZRaTkzVVVWQmQwbElaMFJCVkVKblRsWklVMVZGUkVSQlMwSm5aM0pDWjBWR1FsRmpSRUY2UVdSQ1owNVdTRkUwUlVablVWVklWbEZrQ201RFZ6bHVjMlJuV1hCSFVrdExPVkJ4THpod1ZHVTRkMGgzV1VSV1VqQnFRa0puZDBadlFWVXpPVkJ3ZWpGWmEwVmFZalZ4VG1wd1MwWlhhWGhwTkZrS1drUTRkMmRaVVVkQk1WVmtSVkZGUWk5M1VqWk5TR2xIWkcxb01HUklRbnBQYVRoMldqSnNNR0ZJVm1sTWJVNTJZbE01ZW1KSVRtaE1WMXA1V1ZjeGJBcGtNamw1WVhrNWVtSklUbWhNVjJSd1pFZG9NVmxwTVc1YVZ6VnNZMjFHTUdJelNYWk1iV1J3WkVkb01WbHBPVE5pTTBweVdtMTRkbVF6VFhaYU1sWjFDbHBZU21oa1J6bDVXREprYkdKdFZubGhWMDVtWXpKNGVsbFVUWFZsVnpGelVVaEtiRnB1VFhaa1IwWnVZM2s1TWsxcE5IaE1ha0YzVDFGWlMwdDNXVUlLUWtGSFJIWjZRVUpCVVZGeVlVaFNNR05JVFRaTWVUa3dZakowYkdKcE5XaFpNMUp3WWpJMWVreHRaSEJrUjJneFdXNVdlbHBZU21waU1qVXdXbGMxTUFwTWJVNTJZbFJCVjBKbmIzSkNaMFZGUVZsUEwwMUJSVU5DUVdoNldUSm9iRnBJVm5OYVZFRXlRbWR2Y2tKblJVVkJXVTh2VFVGRlJFSkRhR3BaZWxFekNrOVhTWGROVjFGNFdXcEplRTVVWkdwYVZFazBXa1JqZWs1dFZUSk9WR1JvVDBSV2FrMXFUbXBaYW1kM1RsUlpNVTFDYTBkRGFYTkhRVkZSUW1jM09IY0tRVkZSUlVNeFFubGFVekZUV2xkNGJGbFlUbXhOUkZWSFEybHpSMEZSVVVKbk56aDNRVkZWUlVveVJqTmplVEYzWWpOa2JHTnVVblppTW5oNlRETkNkZ3BrTWxaNVpFYzVkbUpJVFhSaVIwWjBXVzFTYUV4WVFqVmtSMmgyWW1wQlowSm5iM0pDWjBWRlFWbFBMMDFCUlVkQ1FrcDVXbGRhZWt3eWFHeFpWMUo2Q2t3eVVteGtiVlp6WWpOQmQwOTNXVXRMZDFsQ1FrRkhSSFo2UVVKRFFWRjBSRU4wYjJSSVVuZGplbTkyVEROU2RtRXlWblZNYlVacVpFZHNkbUp1VFhVS1dqSnNNR0ZJVm1sa1dFNXNZMjFPZG1KdVVteGlibEYxV1RJNWRFMUpSMGRDWjI5eVFtZEZSVUZaVHk5TlFVVktRa2huVFdSdGFEQmtTRUo2VDJrNGRncGFNbXd3WVVoV2FVeHRUblppVXpsNllraE9hRXhYV25sWlZ6RnNaREk1ZVdGNU9YcGlTRTVvVEZka2NHUkhhREZaYVRGdVdsYzFiR050UmpCaU0wbDJDa3h0WkhCa1IyZ3hXV2s1TTJJelNuSmFiWGgyWkROTmRsb3lWblZhV0Vwb1pFYzVlVmd5Wkd4aWJWWjVZVmRPWm1NeWVIcFpWRTExWlZjeGMxRklTbXdLV201TmRtUkhSbTVqZVRreVRXazBlRXhxUVhkUFFWbExTM2RaUWtKQlIwUjJla0ZDUTJkUmNVUkRhRzFPTWxKclQwZE5NVTVIVFhsTlJGa3pXVzFHYlFwWmVrVjVXVEpGTTFsVVZURk9WR3N4V2tSV2JGcFViR2xPZWxWNVRVUlNhRTFDTUVkRGFYTkhRVkZSUW1jM09IZEJVWE5GUkhkM1Rsb3liREJoU0ZacENreFhhSFpqTTFKc1drUkNTMEpuYjNKQ1owVkZRVmxQTDAxQlJVMUNSSGROVDIxb01HUklRbnBQYVRoMldqSnNNR0ZJVm1sTWJVNTJZbE01YUdRelRYUUtZMGM1TTFwWVNqQmlNamx6WTNrNWQySXpaR3hqYmxKMllqSjRla3hYZUdoaVYwcHJXVk14ZDJWWVVtOWlNalIzVDBGWlMwdDNXVUpDUVVkRWRucEJRZ3BFVVZGeFJFTm9hbGw2VVROUFYwbDNUVmRSZUZscVNYaE9WR1JxV2xSSk5GcEVZM3BPYlZVeVRsUmthRTlFVm1wTmFrNXFXV3BuZDA1VVdURk5RMGxIQ2tOcGMwZEJVVkZDWnpjNGQwRlJORVZHUVhkVFkyMVdiV041T1c5YVYwWnJZM2s1YTFwWVdteGlSemwzVFVKclIwTnBjMGRCVVZGQ1p6YzRkMEZST0VVS1EzZDNTazFxU1hoUFZFVTFUWHBqTlUxRVJVZERhWE5IUVZGUlFtYzNPSGRCVWtGRlNYZDNhR0ZJVWpCalNFMDJUSGs1Ym1GWVVtOWtWMGwxV1RJNWRBcE1Na1l6WTNreGQySXpaR3hqYmxKMllqSjRlazFDYTBkRGFYTkhRVkZSUW1jM09IZEJVa1ZGUTNkM1NrMVVTVFZOVkVrelRtcE5ORTFJT0VkRGFYTkhDa0ZSVVVKbk56aDNRVkpKUldOUmVIWmhTRkl3WTBoTk5reDVPVzVoV0ZKdlpGZEpkVmt5T1hSTU1rWXpZM2t4ZDJJelpHeGpibEoyWWpKNGVrd3pRbllLWkRKV2VXUkhPWFppU0UxMFlrZEdkRmx0VW1oTVdFSTFaRWRvZG1KcE9IVmFNbXd3WVVoV2FVd3paSFpqYlhSdFlrYzVNMk41T1hkamJWVjBZMjFXY3dwYVYwWjZXbE0xTldKWGVFRmpiVlp0WTNrNWIxcFhSbXRqZVRscldsaGFiR0pIT1hkTlJHZEhRMmx6UjBGUlVVSm5OemgzUVZKTlJVdG5kMjlaTWswd0NrNTZiR2xOUkVaclRWZEplVTFVVlROWk1sVjVUMGRSTTAxNldteE9hbFV6V1ZSbk1WbDZTWHBaTWtrMFRVUlZNazVVUVZsQ1oyOXlRbWRGUlVGWlR5OEtUVUZGVlVKQmIwMURTRTVxWVVkV2EyUlhlR3hOUnpSSFEybHpSMEZSVVVKbk56aDNRVkpWUlZsQmVHVmhTRkl3WTBoTk5reDVPVzVoV0ZKdlpGZEpkUXBaTWpsMFRESkdNMk41TVhkaU0yUnNZMjVTZG1JeWVIcE1NMEoyWkRKV2VXUkhPWFppU0UxMFlrZEdkRmx0VW1oTVdFSTFaRWRvZG1KcE9XaFpNMUp3Q21JeU5YcE1NMG94WW01TmRrMVVVWHBOUkUwMVRrUlZkMDlFUVhaWldGSXdXbGN4ZDJSSVRYWk5WRUZYUW1kdmNrSm5SVVZCV1U4dlRVRkZWMEpCWjAwS1FtNUNNVmx0ZUhCWmVrTkNhWGRaUzB0M1dVSkNRVWhYWlZGSlJVRm5VamxDU0hOQlpWRkNNMEZPTURsTlIzSkhlSGhGZVZsNGEyVklTbXh1VG5kTGFRcFRiRFkwTTJwNWRDODBaVXRqYjBGMlMyVTJUMEZCUVVKc1p6bExPV0UwUVVGQlVVUkJSV2QzVW1kSmFFRlFVVlpaWnpaWGFFNU9PV1ZpWkRsM1pXTkJDakZDUzBkS2N6WjFiRVZNUjBKNVIyTmlXRTlhU0c5VEwwRnBSVUZxZEdac1VFRXJZbXRuYTJoclozWldWeTl6ZVVKblptOTVVRkZSY1dJMVluazFiMElLVlhSVWRHOWFNSGREWjFsSlMyOWFTWHBxTUVWQmQwMUVZVUZCZDFwUlNYaEJUekJ5TkV0aFNWVmtjRFpWZWxWRU56UnljRXB2YlhjMVRsVkZhazV1Y2dvNEsxUk5WVGhZYkU5MGVUUndXRFJQTWpNek5IUTJOMlZ4U2pSUFZrWndTbEozU1hkRFFsSjJRV1ZXYjJWT1JqQnlZVmxpVXpsTFZ6QmhZemRDVlVjekNqZG5kM0pDZVhsQmJEUnVkMHB6Ym5kVU9VRnBjVmxET0VoV1pFOUxNRXBUWVVsSGNRb3RMUzB0TFVWT1JDQkRSVkpVU1VaSlEwRlVSUzB0TFMwdENnPT0ifV19fQ=="}]}, "dsseEnvelope":{"payload":"eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjAuMSIsInByZWRpY2F0ZVR5cGUiOiJodHRwczovL3Nsc2EuZGV2L3Byb3ZlbmFuY2UvdjAuMiIsInN1YmplY3QiOlt7Im5hbWUiOiIuL2F3c19sYW1iZGFfcG93ZXJ0b29scy0zLjkuMWE4LXB5My1ub25lLWFueS53aGwiLCJkaWdlc3QiOnsic2hhMjU2IjoiZTJlNzhjYzA0NmE0MjBjMjE4Y2Q2N2Q2YTU4MDc3ODZlODQ2ZGIzMWVhM2JiZjY1Y2EyMTY0MThhOGM3NzMzNyJ9fSx7Im5hbWUiOiIuL2F3c19sYW1iZGFfcG93ZXJ0b29scy0zLjkuMWE4LnRhci5neiIsImRpZ2VzdCI6eyJzaGEyNTYiOiJkMjMwMTU1ZjYyOTI5NjEyZDU1YTU0NTBjMWEzYTI0MmYxMWZkODQwODFkYWRiOWY3NjUyYjMzYzU5YTczYmE2In19XSwicHJlZGljYXRlIjp7ImJ1aWxkZXIiOnsiaWQiOiJodHRwczovL2dpdGh1Yi5jb20vc2xzYS1mcmFtZXdvcmsvc2xzYS1naXRodWItZ2VuZXJhdG9yLy5naXRodWIvd29ya2Zsb3dzL2dlbmVyYXRvcl9nZW5lcmljX3Nsc2EzLnltbEByZWZzL3RhZ3MvdjIuMS4wIn0sImJ1aWxkVHlwZSI6Imh0dHBzOi8vZ2l0aHViLmNvbS9zbHNhLWZyYW1ld29yay9zbHNhLWdpdGh1Yi1nZW5lcmF0b3IvZ2VuZXJpY0B2MSIsImludm9jYXRpb24iOnsiY29uZmlnU291cmNlIjp7InVyaSI6ImdpdCtodHRwczovL2dpdGh1Yi5jb20vYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uQHJlZnMvaGVhZHMvZGV2ZWxvcCIsImRpZ2VzdCI6eyJzaGExIjoiY2M0NzliMDFkMWIyMTU3Y2UyOGQ3MzZlNjU3YTg1YzIzY2I4MDU2NSJ9LCJlbnRyeVBvaW50IjoiLmdpdGh1Yi93b3JrZmxvd3MvcHJlLXJlbGVhc2UueW1sIn0sImVudmlyb25tZW50Ijp7ImdpdGh1Yl9hY3RvciI6ImxlYW5kcm9kYW1hc2NlbmEiLCJnaXRodWJfYWN0b3JfaWQiOiI0Mjk1MTczIiwiZ2l0aHViX2Jhc2VfcmVmIjoiIiwiZ2l0aHViX2V2ZW50X25hbWUiOiJzY2hlZHVsZSIsImdpdGh1Yl9ldmVudF9wYXlsb2FkIjp7ImVudGVycHJpc2UiOnsiYXZhdGFyX3VybCI6Imh0dHBzOi8vYXZhdGFycy5naXRodWJ1c2VyY29udGVudC5jb20vYi8xMjkwP3Y9NCIsImNyZWF0ZWRfYXQiOiIyMDE5LTExLTEzVDE4OjA1OjQxWiIsImRlc2NyaXB0aW9uIjoiIiwiaHRtbF91cmwiOiJodHRwczovL2dpdGh1Yi5jb20vZW50ZXJwcmlzZXMvYW1hem9uIiwiaWQiOjEyOTAsIm5hbWUiOiJBbWF6b24iLCJub2RlX2lkIjoiTURFd09rVnVkR1Z5Y0hKcGMyVXhNamt3Iiwic2x1ZyI6ImFtYXpvbiIsInVwZGF0ZWRfYXQiOiIyMDI0LTA5LTMwVDIxOjAyOjMwWiIsIndlYnNpdGVfdXJsIjoiaHR0cHM6Ly93d3cuYW1hem9uLmNvbS8ifSwib3JnYW5pemF0aW9uIjp7ImF2YXRhcl91cmwiOiJodHRwczovL2F2YXRhcnMuZ2l0aHVidXNlcmNvbnRlbnQuY29tL3UvMTI5MTI3NjM4P3Y9NCIsImRlc2NyaXB0aW9uIjoiIiwiZXZlbnRzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vb3Jncy9hd3MtcG93ZXJ0b29scy9ldmVudHMiLCJob29rc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL29yZ3MvYXdzLXBvd2VydG9vbHMvaG9va3MiLCJpZCI6MTI5MTI3NjM4LCJpc3N1ZXNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9vcmdzL2F3cy1wb3dlcnRvb2xzL2lzc3VlcyIsImxvZ2luIjoiYXdzLXBvd2VydG9vbHMiLCJtZW1iZXJzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vb3Jncy9hd3MtcG93ZXJ0b29scy9tZW1iZXJzey9tZW1iZXJ9Iiwibm9kZV9pZCI6Ik9fa2dET0I3SlUxZyIsInB1YmxpY19tZW1iZXJzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vb3Jncy9hd3MtcG93ZXJ0b29scy9wdWJsaWNfbWVtYmVyc3svbWVtYmVyfSIsInJlcG9zX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vb3Jncy9hd3MtcG93ZXJ0b29scy9yZXBvcyIsInVybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vb3Jncy9hd3MtcG93ZXJ0b29scyJ9LCJyZXBvc2l0b3J5Ijp7ImFsbG93X2ZvcmtpbmciOnRydWUsImFyY2hpdmVfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24ve2FyY2hpdmVfZm9ybWF0fXsvcmVmfSIsImFyY2hpdmVkIjpmYWxzZSwiYXNzaWduZWVzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2Fzc2lnbmVlc3svdXNlcn0iLCJibG9ic191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9naXQvYmxvYnN7L3NoYX0iLCJicmFuY2hlc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9icmFuY2hlc3svYnJhbmNofSIsImNsb25lX3VybCI6Imh0dHBzOi8vZ2l0aHViLmNvbS9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24uZ2l0IiwiY29sbGFib3JhdG9yc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9jb2xsYWJvcmF0b3Jzey9jb2xsYWJvcmF0b3J9IiwiY29tbWVudHNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vY29tbWVudHN7L251bWJlcn0iLCJjb21taXRzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2NvbW1pdHN7L3NoYX0iLCJjb21wYXJlX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2NvbXBhcmUve2Jhc2V9Li4ue2hlYWR9IiwiY29udGVudHNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vY29udGVudHMveytwYXRofSIsImNvbnRyaWJ1dG9yc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9jb250cmlidXRvcnMiLCJjcmVhdGVkX2F0IjoiMjAxOS0xMS0xNVQxMjoyNjoxMloiLCJjdXN0b21fcHJvcGVydGllcyI6e30sImRlZmF1bHRfYnJhbmNoIjoiZGV2ZWxvcCIsImRlcGxveW1lbnRzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2RlcGxveW1lbnRzIiwiZGVzY3JpcHRpb24iOiJBIGRldmVsb3BlciB0b29sa2l0IHRvIGltcGxlbWVudCBTZXJ2ZXJsZXNzIGJlc3QgcHJhY3RpY2VzIGFuZCBpbmNyZWFzZSBkZXZlbG9wZXIgdmVsb2NpdHkuIiwiZGlzYWJsZWQiOmZhbHNlLCJkb3dubG9hZHNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vZG93bmxvYWRzIiwiZXZlbnRzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2V2ZW50cyIsImZvcmsiOmZhbHNlLCJmb3JrcyI6NDE5LCJmb3Jrc19jb3VudCI6NDE5LCJmb3Jrc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9mb3JrcyIsImZ1bGxfbmFtZSI6ImF3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbiIsImdpdF9jb21taXRzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2dpdC9jb21taXRzey9zaGF9IiwiZ2l0X3JlZnNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vZ2l0L3JlZnN7L3NoYX0iLCJnaXRfdGFnc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9naXQvdGFnc3svc2hhfSIsImdpdF91cmwiOiJnaXQ6Ly9naXRodWIuY29tL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi5naXQiLCJoYXNfZGlzY3Vzc2lvbnMiOnRydWUsImhhc19kb3dubG9hZHMiOnRydWUsImhhc19pc3N1ZXMiOnRydWUsImhhc19wYWdlcyI6ZmFsc2UsImhhc19wcm9qZWN0cyI6dHJ1ZSwiaGFzX3dpa2kiOmZhbHNlLCJob21lcGFnZSI6Imh0dHBzOi8vZG9jcy5wb3dlcnRvb2xzLmF3cy5kZXYvbGFtYmRhL3B5dGhvbi9sYXRlc3QvIiwiaG9va3NfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vaG9va3MiLCJodG1sX3VybCI6Imh0dHBzOi8vZ2l0aHViLmNvbS9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24iLCJpZCI6MjIxOTE5Mzc5LCJpc190ZW1wbGF0ZSI6ZmFsc2UsImlzc3VlX2NvbW1lbnRfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vaXNzdWVzL2NvbW1lbnRzey9udW1iZXJ9IiwiaXNzdWVfZXZlbnRzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2lzc3Vlcy9ldmVudHN7L251bWJlcn0iLCJpc3N1ZXNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vaXNzdWVzey9udW1iZXJ9Iiwia2V5c191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9rZXlzey9rZXlfaWR9IiwibGFiZWxzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2xhYmVsc3svbmFtZX0iLCJsYW5ndWFnZSI6IlB5dGhvbiIsImxhbmd1YWdlc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9sYW5ndWFnZXMiLCJsaWNlbnNlIjp7ImtleSI6Im1pdC0wIiwibmFtZSI6Ik1JVCBObyBBdHRyaWJ1dGlvbiIsIm5vZGVfaWQiOiJNRGM2VEdsalpXNXpaVFF4Iiwic3BkeF9pZCI6Ik1JVC0wIiwidXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9saWNlbnNlcy9taXQtMCJ9LCJtZXJnZXNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vbWVyZ2VzIiwibWlsZXN0b25lc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9taWxlc3RvbmVzey9udW1iZXJ9IiwibWlycm9yX3VybCI6bnVsbCwibmFtZSI6InBvd2VydG9vbHMtbGFtYmRhLXB5dGhvbiIsIm5vZGVfaWQiOiJNREV3T2xKbGNHOXphWFJ2Y25reU1qRTVNVGt6TnprPSIsIm5vdGlmaWNhdGlvbnNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vbm90aWZpY2F0aW9uc3s/c2luY2UsYWxsLHBhcnRpY2lwYXRpbmd9Iiwib3Blbl9pc3N1ZXMiOjU1LCJvcGVuX2lzc3Vlc19jb3VudCI6NTUsIm93bmVyIjp7ImF2YXRhcl91cmwiOiJodHRwczovL2F2YXRhcnMuZ2l0aHVidXNlcmNvbnRlbnQuY29tL3UvMTI5MTI3NjM4P3Y9NCIsImV2ZW50c191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3VzZXJzL2F3cy1wb3dlcnRvb2xzL2V2ZW50c3svcHJpdmFjeX0iLCJmb2xsb3dlcnNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS91c2Vycy9hd3MtcG93ZXJ0b29scy9mb2xsb3dlcnMiLCJmb2xsb3dpbmdfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS91c2Vycy9hd3MtcG93ZXJ0b29scy9mb2xsb3dpbmd7L290aGVyX3VzZXJ9IiwiZ2lzdHNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS91c2Vycy9hd3MtcG93ZXJ0b29scy9naXN0c3svZ2lzdF9pZH0iLCJncmF2YXRhcl9pZCI6IiIsImh0bWxfdXJsIjoiaHR0cHM6Ly9naXRodWIuY29tL2F3cy1wb3dlcnRvb2xzIiwiaWQiOjEyOTEyNzYzOCwibG9naW4iOiJhd3MtcG93ZXJ0b29scyIsIm5vZGVfaWQiOiJPX2tnRE9CN0pVMWciLCJvcmdhbml6YXRpb25zX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vdXNlcnMvYXdzLXBvd2VydG9vbHMvb3JncyIsInJlY2VpdmVkX2V2ZW50c191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3VzZXJzL2F3cy1wb3dlcnRvb2xzL3JlY2VpdmVkX2V2ZW50cyIsInJlcG9zX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vdXNlcnMvYXdzLXBvd2VydG9vbHMvcmVwb3MiLCJzaXRlX2FkbWluIjpmYWxzZSwic3RhcnJlZF91cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3VzZXJzL2F3cy1wb3dlcnRvb2xzL3N0YXJyZWR7L293bmVyfXsvcmVwb30iLCJzdWJzY3JpcHRpb25zX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vdXNlcnMvYXdzLXBvd2VydG9vbHMvc3Vic2NyaXB0aW9ucyIsInR5cGUiOiJPcmdhbml6YXRpb24iLCJ1cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3VzZXJzL2F3cy1wb3dlcnRvb2xzIiwidXNlcl92aWV3X3R5cGUiOiJwdWJsaWMifSwicHJpdmF0ZSI6ZmFsc2UsInB1bGxzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL3B1bGxzey9udW1iZXJ9IiwicHVzaGVkX2F0IjoiMjAyNS0wNC0wN1QwNzo0MDoyMFoiLCJyZWxlYXNlc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9yZWxlYXNlc3svaWR9Iiwic2l6ZSI6MTA0MjA0LCJzc2hfdXJsIjoiZ2l0QGdpdGh1Yi5jb206YXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uLmdpdCIsInN0YXJnYXplcnNfY291bnQiOjMwMTgsInN0YXJnYXplcnNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vc3RhcmdhemVycyIsInN0YXR1c2VzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL3N0YXR1c2VzL3tzaGF9Iiwic3Vic2NyaWJlcnNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vc3Vic2NyaWJlcnMiLCJzdWJzY3JpcHRpb25fdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vc3Vic2NyaXB0aW9uIiwic3ZuX3VybCI6Imh0dHBzOi8vZ2l0aHViLmNvbS9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24iLCJ0YWdzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL3RhZ3MiLCJ0ZWFtc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi90ZWFtcyIsInRvcGljcyI6WyJhd3MiLCJhd3MtbGFtYmRhIiwiaGFja3RvYmVyZmVzdCIsImxhbWJkYSIsInB5dGhvbiIsInNlcnZlcmxlc3MiXSwidHJlZXNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vZ2l0L3RyZWVzey9zaGF9IiwidXBkYXRlZF9hdCI6IjIwMjUtMDQtMDdUMDc6NDA6MjVaIiwidXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24iLCJ2aXNpYmlsaXR5IjoicHVibGljIiwid2F0Y2hlcnMiOjMwMTgsIndhdGNoZXJzX2NvdW50IjozMDE4LCJ3ZWJfY29tbWl0X3NpZ25vZmZfcmVxdWlyZWQiOnRydWV9LCJzY2hlZHVsZSI6IjAgOCAqICogMS01Iiwid29ya2Zsb3ciOiIuZ2l0aHViL3dvcmtmbG93cy9wcmUtcmVsZWFzZS55bWwifSwiZ2l0aHViX2hlYWRfcmVmIjoiIiwiZ2l0aHViX3JlZiI6InJlZnMvaGVhZHMvZGV2ZWxvcCIsImdpdGh1Yl9yZWZfdHlwZSI6ImJyYW5jaCIsImdpdGh1Yl9yZXBvc2l0b3J5X2lkIjoiMjIxOTE5Mzc5IiwiZ2l0aHViX3JlcG9zaXRvcnlfb3duZXIiOiJhd3MtcG93ZXJ0b29scyIsImdpdGh1Yl9yZXBvc2l0b3J5X293bmVyX2lkIjoiMTI5MTI3NjM4IiwiZ2l0aHViX3J1bl9hdHRlbXB0IjoiMSIsImdpdGh1Yl9ydW5faWQiOiIxNDMwMzk0NTA4MCIsImdpdGh1Yl9ydW5fbnVtYmVyIjoiMjEzIiwiZ2l0aHViX3NoYTEiOiJjYzQ3OWIwMWQxYjIxNTdjZTI4ZDczNmU2NTdhODVjMjNjYjgwNTY1In19LCJtZXRhZGF0YSI6eyJidWlsZEludm9jYXRpb25JRCI6IjE0MzAzOTQ1MDgwLTEiLCJjb21wbGV0ZW5lc3MiOnsicGFyYW1ldGVycyI6dHJ1ZSwiZW52aXJvbm1lbnQiOmZhbHNlLCJtYXRlcmlhbHMiOmZhbHNlfSwicmVwcm9kdWNpYmxlIjpmYWxzZX0sIm1hdGVyaWFscyI6W3sidXJpIjoiZ2l0K2h0dHBzOi8vZ2l0aHViLmNvbS9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob25AcmVmcy9oZWFkcy9kZXZlbG9wIiwiZGlnZXN0Ijp7InNoYTEiOiJjYzQ3OWIwMWQxYjIxNTdjZTI4ZDczNmU2NTdhODVjMjNjYjgwNTY1In19XX19", "payloadType":"application/vnd.in-toto+json", "signatures":[{"sig":"MEQCIAN0goAqRRqe95v3mIreMvIfAlhyo/sQ3t+MYYscGOk3AiBxyHd2GdZopCY1r5RS8AVCXR2m1G63aG7yqYO20d6qDw=="}]}}
\ No newline at end of file
diff --git a/provenance/3.9.1a9/multiple.intoto.jsonl b/provenance/3.9.1a9/multiple.intoto.jsonl
new file mode 100644
index 00000000000..dd161a9dd1b
--- /dev/null
+++ b/provenance/3.9.1a9/multiple.intoto.jsonl
@@ -0,0 +1 @@
+{"mediaType":"application/vnd.dev.sigstore.bundle.v0.3+json", "verificationMaterial":{"certificate":{"rawBytes":"MIIHZTCCBuugAwIBAgIUMGMatSNNAYV2PYR8/IXVqeH9rNwwCgYIKoZIzj0EAwMwNzEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MR4wHAYDVQQDExVzaWdzdG9yZS1pbnRlcm1lZGlhdGUwHhcNMjUwNDA4MDgwNzU4WhcNMjUwNDA4MDgxNzU4WjAAMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEo7s5Zwns95tkF+rMyZ8R5IAJpyT3hWaiAfN+dZrjJygddNuP7Ac8jHaMuXdAcxoKxyw23UV/jLbkI85vRlPwrKOCBgowggYGMA4GA1UdDwEB/wQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcDAzAdBgNVHQ4EFgQUYYcxWwwj+hJ7NvoN1f9D0U6KIE4wHwYDVR0jBBgwFoAU39Ppz1YkEZb5qNjpKFWixi4YZD8wgYQGA1UdEQEB/wR6MHiGdmh0dHBzOi8vZ2l0aHViLmNvbS9zbHNhLWZyYW1ld29yay9zbHNhLWdpdGh1Yi1nZW5lcmF0b3IvLmdpdGh1Yi93b3JrZmxvd3MvZ2VuZXJhdG9yX2dlbmVyaWNfc2xzYTMueW1sQHJlZnMvdGFncy92Mi4xLjAwOQYKKwYBBAGDvzABAQQraHR0cHM6Ly90b2tlbi5hY3Rpb25zLmdpdGh1YnVzZXJjb250ZW50LmNvbTAWBgorBgEEAYO/MAECBAhzY2hlZHVsZTA2BgorBgEEAYO/MAEDBCg3MWVkZmMxNzAwYWJiMjBmYTlmMjNlODhlY2E2YzdlN2IxZGVkMDgwMBkGCisGAQQBg78wAQQEC1ByZS1SZWxlYXNlMDUGCisGAQQBg78wAQUEJ2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbjAgBgorBgEEAYO/MAEGBBJyZWZzL2hlYWRzL2RldmVsb3AwOwYKKwYBBAGDvzABCAQtDCtodHRwczovL3Rva2VuLmFjdGlvbnMuZ2l0aHVidXNlcmNvbnRlbnQuY29tMIGGBgorBgEEAYO/MAEJBHgMdmh0dHBzOi8vZ2l0aHViLmNvbS9zbHNhLWZyYW1ld29yay9zbHNhLWdpdGh1Yi1nZW5lcmF0b3IvLmdpdGh1Yi93b3JrZmxvd3MvZ2VuZXJhdG9yX2dlbmVyaWNfc2xzYTMueW1sQHJlZnMvdGFncy92Mi4xLjAwOAYKKwYBBAGDvzABCgQqDChmN2RkOGM1NGMyMDY3YmFmYzEyY2E3YTU1NTk1ZDVlZTliNzUyMDRhMB0GCisGAQQBg78wAQsEDwwNZ2l0aHViLWhvc3RlZDBKBgorBgEEAYO/MAEMBDwMOmh0dHBzOi8vZ2l0aHViLmNvbS9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24wOAYKKwYBBAGDvzABDQQqDCg3MWVkZmMxNzAwYWJiMjBmYTlmMjNlODhlY2E2YzdlN2IxZGVkMDgwMCIGCisGAQQBg78wAQ4EFAwScmVmcy9oZWFkcy9kZXZlbG9wMBkGCisGAQQBg78wAQ8ECwwJMjIxOTE5Mzc5MDEGCisGAQQBg78wARAEIwwhaHR0cHM6Ly9naXRodWIuY29tL2F3cy1wb3dlcnRvb2xzMBkGCisGAQQBg78wAREECwwJMTI5MTI3NjM4MH8GCisGAQQBg78wARIEcQxvaHR0cHM6Ly9naXRodWIuY29tL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi8uZ2l0aHViL3dvcmtmbG93cy9wcmUtcmVsZWFzZS55bWxAcmVmcy9oZWFkcy9kZXZlbG9wMDgGCisGAQQBg78wARMEKgwoNzFlZGZjMTcwMGFiYjIwZmE5ZjIzZTg4ZWNhNmM3ZTdiMWRlZDA4MDAYBgorBgEEAYO/MAEUBAoMCHNjaGVkdWxlMG4GCisGAQQBg78wARUEYAxeaHR0cHM6Ly9naXRodWIuY29tL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9hY3Rpb25zL3J1bnMvMTQzMjgyOTIxNjcvYXR0ZW1wdHMvMTAWBgorBgEEAYO/MAEWBAgMBnB1YmxpYzCBiQYKKwYBBAHWeQIEAgR7BHkAdwB1AN09MGrGxxEyYxkeHJlnNwKiSl643jyt/4eKcoAvKe6OAAABlhRxF7cAAAQDAEYwRAIgac/Lw2R/gzJ4YPihdju+gcKiSmB91hgEEcY2z9ny1F0CIA6QJyafi5uzGJ1sT79gzDg1M7zF16foO9CTivaCxJ7yMAoGCCqGSM49BAMDA2gAMGUCMBGIeI4UX+JvfNc6C6q9a1oU6vOVxmp/57LcyVFofemHBqRRy++W+6IN4Ns49eWPzQIxAMCYWhbS+Z+k1i1uzASfKXeJs8M8Yb0mcdqYn1ejAKcSJV5vL1Cgp4OjobgG2AB2zw=="}, "tlogEntries":[{"logIndex":"193722281", "logId":{"keyId":"wNI9atQGlz+VWfO6LRygH4QUfY/8W4RFwiT5i5WRgB0="}, "kindVersion":{"kind":"dsse", "version":"0.0.1"}, "integratedTime":"1744099678", "inclusionPromise":{"signedEntryTimestamp":"MEYCIQDwnvA7VaQyMNCymBR4S873N4gJkviCE/ejLc8JsJnpSAIhAOBUQfEUG77+j8cFBJOadSItyxUukm/mjnKKOMSybq6C"}, "inclusionProof":{"logIndex":"71818019", "rootHash":"ZFgAI2e94CJ8FNK5pIcozjYJfy10N5BK4ng/2aqqKYg=", "treeSize":"71818021", "hashes":["C/EpUwc1hYllg5Jcoa6whdYNZVAWVXaK41vzz2fhFL4=", "2MwZiqJ2mI2QraqMLQ0F3yRYGFnsgp6z/GI7SHeAa1g=", "7u4uIBO/H7OCfaaoNpKcxfA8c/xhFY/AuI01asGBmYc=", "XeMVh5/vMEfpISCmAZByn8lnY/2tqe+n5VU6xWwwyH8=", "/Vs72NIRhm8BHzgCn2nwCPSf/r2++dSfDE1u20G+KiA=", "HoIH6E/tmXI4odZ6NmwAnjwXY8hu6NsuZQSjyq2GDWA=", "QwOQb3sMaIrsB0lGBrEn6uZ8xqg07tp8K551gyd+Sac=", "JXwHMZKXgDUqp0fc20yUui6uf05C+YtjxlKd7DWeBls=", "NrjP8NpnsZ3IJExd+I/H/s6rMTq6/q/3Tnbw8YXgVpQ=", "gt0FLr754lddDve8ynkxanJCBG2Vil//BBl9XBHHCb0=", "aspbBUxDU9Ewp6ZnMf0HZ9X7hlVDfq1rAoWhqG3Tb8g=", "fWKh3MSBlxUwZ7SkRF7MHSp+bDRqDMDciQscq4fa1JU=", "c+sqj0rvjUFq4umOgNgVB35IyWEsCoWBR87gTIIKbm4=", "WEm5OgPzJpYROv+4CcrieexCYyQKrLUH3hbxmcQQ+DM=", "7v8qPHNDLerpduaMx06eb/MwgoQwczTn/cYGKX/9wZ4="], "checkpoint":{"envelope":"rekor.sigstore.dev - 1193050959916656506\n71818021\nZFgAI2e94CJ8FNK5pIcozjYJfy10N5BK4ng/2aqqKYg=\n\n— rekor.sigstore.dev wNI9ajBFAiEA4mZr/eBJb8b4h7go9lbXK+537e/WnkxVO2/JLgink+kCIAFbvX4htFDx/XEk5b8I0qhis2YIHJLky+QXLmnxlUHy\n"}}, "canonicalizedBody":"eyJhcGlWZXJzaW9uIjoiMC4wLjEiLCJraW5kIjoiZHNzZSIsInNwZWMiOnsiZW52ZWxvcGVIYXNoIjp7ImFsZ29yaXRobSI6InNoYTI1NiIsInZhbHVlIjoiZTMxOTU4OTIyNDUzODIyMjhhMDU1OTk2NDMyYjZlMTgyYTIyZGVhY2MwNTVkNGM5ZDI4M2JhZTYwMTBhNmYxOCJ9LCJwYXlsb2FkSGFzaCI6eyJhbGdvcml0aG0iOiJzaGEyNTYiLCJ2YWx1ZSI6ImMxZmI5OTVkNWUxY2RmYThkNmJlZjVmODg3MGMzZWE4NzdhOGUwYTg3YjRkMzM0MDc4Mjg2YWY2MjkyODgyZWUifSwic2lnbmF0dXJlcyI6W3sic2lnbmF0dXJlIjoiTUVVQ0lRREJRQm94STBKdHZQa0xJRit6R2hXMzIyTlJ2bTNSRXU4WlJNamovdzVPbVFJZ0lPS1BsLytPODUvd3dpNmNYSUhKeGZteUJ5MlYvcXY4amZnT2ZNMGpoSjQ9IiwidmVyaWZpZXIiOiJMUzB0TFMxQ1JVZEpUaUJEUlZKVVNVWkpRMEZVUlMwdExTMHRDazFKU1VoYVZFTkRRblYxWjBGM1NVSkJaMGxWVFVkTllYUlRUazVCV1ZZeVVGbFNPQzlKV0ZaeFpVZzVjazUzZDBObldVbExiMXBKZW1vd1JVRjNUWGNLVG5wRlZrMUNUVWRCTVZWRlEyaE5UV015Ykc1ak0xSjJZMjFWZFZwSFZqSk5ValIzU0VGWlJGWlJVVVJGZUZaNllWZGtlbVJIT1hsYVV6RndZbTVTYkFwamJURnNXa2RzYUdSSFZYZElhR05PVFdwVmQwNUVRVFJOUkdkM1RucFZORmRvWTA1TmFsVjNUa1JCTkUxRVozaE9lbFUwVjJwQlFVMUdhM2RGZDFsSUNrdHZXa2w2YWpCRFFWRlpTVXR2V2tsNmFqQkVRVkZqUkZGblFVVnZOM00xV25kdWN6azFkR3RHSzNKTmVWbzRValZKUVVwd2VWUXphRmRoYVVGbVRpc0taRnB5YWtwNVoyUmtUblZRTjBGak9HcElZVTExV0dSQlkzaHZTM2g1ZHpJelZWWXZha3hpYTBrNE5YWlNiRkIzY2t0UFEwSm5iM2RuWjFsSFRVRTBSd3BCTVZWa1JIZEZRaTkzVVVWQmQwbElaMFJCVkVKblRsWklVMVZGUkVSQlMwSm5aM0pDWjBWR1FsRmpSRUY2UVdSQ1owNVdTRkUwUlVablVWVlpXV040Q2xkM2Qyb3JhRW8zVG5adlRqRm1PVVF3VlRaTFNVVTBkMGgzV1VSV1VqQnFRa0puZDBadlFWVXpPVkJ3ZWpGWmEwVmFZalZ4VG1wd1MwWlhhWGhwTkZrS1drUTRkMmRaVVVkQk1WVmtSVkZGUWk5M1VqWk5TR2xIWkcxb01HUklRbnBQYVRoMldqSnNNR0ZJVm1sTWJVNTJZbE01ZW1KSVRtaE1WMXA1V1ZjeGJBcGtNamw1WVhrNWVtSklUbWhNVjJSd1pFZG9NVmxwTVc1YVZ6VnNZMjFHTUdJelNYWk1iV1J3WkVkb01WbHBPVE5pTTBweVdtMTRkbVF6VFhaYU1sWjFDbHBZU21oa1J6bDVXREprYkdKdFZubGhWMDVtWXpKNGVsbFVUWFZsVnpGelVVaEtiRnB1VFhaa1IwWnVZM2s1TWsxcE5IaE1ha0YzVDFGWlMwdDNXVUlLUWtGSFJIWjZRVUpCVVZGeVlVaFNNR05JVFRaTWVUa3dZakowYkdKcE5XaFpNMUp3WWpJMWVreHRaSEJrUjJneFdXNVdlbHBZU21waU1qVXdXbGMxTUFwTWJVNTJZbFJCVjBKbmIzSkNaMFZGUVZsUEwwMUJSVU5DUVdoNldUSm9iRnBJVm5OYVZFRXlRbWR2Y2tKblJVVkJXVTh2VFVGRlJFSkRaek5OVjFackNscHRUWGhPZWtGM1dWZEthVTFxUW0xWlZHeHRUV3BPYkU5RWFHeFpNa1V5V1hwa2JFNHlTWGhhUjFaclRVUm5kMDFDYTBkRGFYTkhRVkZSUW1jM09IY0tRVkZSUlVNeFFubGFVekZUV2xkNGJGbFlUbXhOUkZWSFEybHpSMEZSVVVKbk56aDNRVkZWUlVveVJqTmplVEYzWWpOa2JHTnVVblppTW5oNlRETkNkZ3BrTWxaNVpFYzVkbUpJVFhSaVIwWjBXVzFTYUV4WVFqVmtSMmgyWW1wQlowSm5iM0pDWjBWRlFWbFBMMDFCUlVkQ1FrcDVXbGRhZWt3eWFHeFpWMUo2Q2t3eVVteGtiVlp6WWpOQmQwOTNXVXRMZDFsQ1FrRkhSSFo2UVVKRFFWRjBSRU4wYjJSSVVuZGplbTkyVEROU2RtRXlWblZNYlVacVpFZHNkbUp1VFhVS1dqSnNNR0ZJVm1sa1dFNXNZMjFPZG1KdVVteGlibEYxV1RJNWRFMUpSMGRDWjI5eVFtZEZSVUZaVHk5TlFVVktRa2huVFdSdGFEQmtTRUo2VDJrNGRncGFNbXd3WVVoV2FVeHRUblppVXpsNllraE9hRXhYV25sWlZ6RnNaREk1ZVdGNU9YcGlTRTVvVEZka2NHUkhhREZaYVRGdVdsYzFiR050UmpCaU0wbDJDa3h0WkhCa1IyZ3hXV2s1TTJJelNuSmFiWGgyWkROTmRsb3lWblZhV0Vwb1pFYzVlVmd5Wkd4aWJWWjVZVmRPWm1NeWVIcFpWRTExWlZjeGMxRklTbXdLV201TmRtUkhSbTVqZVRreVRXazBlRXhxUVhkUFFWbExTM2RaUWtKQlIwUjJla0ZDUTJkUmNVUkRhRzFPTWxKclQwZE5NVTVIVFhsTlJGa3pXVzFHYlFwWmVrVjVXVEpGTTFsVVZURk9WR3N4V2tSV2JGcFViR2xPZWxWNVRVUlNhRTFDTUVkRGFYTkhRVkZSUW1jM09IZEJVWE5GUkhkM1Rsb3liREJoU0ZacENreFhhSFpqTTFKc1drUkNTMEpuYjNKQ1owVkZRVmxQTDAxQlJVMUNSSGROVDIxb01HUklRbnBQYVRoMldqSnNNR0ZJVm1sTWJVNTJZbE01YUdRelRYUUtZMGM1TTFwWVNqQmlNamx6WTNrNWQySXpaR3hqYmxKMllqSjRla3hYZUdoaVYwcHJXVk14ZDJWWVVtOWlNalIzVDBGWlMwdDNXVUpDUVVkRWRucEJRZ3BFVVZGeFJFTm5NMDFYVm10YWJVMTRUbnBCZDFsWFNtbE5ha0p0V1ZSc2JVMXFUbXhQUkdoc1dUSkZNbGw2Wkd4T01rbDRXa2RXYTAxRVozZE5RMGxIQ2tOcGMwZEJVVkZDWnpjNGQwRlJORVZHUVhkVFkyMVdiV041T1c5YVYwWnJZM2s1YTFwWVdteGlSemwzVFVKclIwTnBjMGRCVVZGQ1p6YzRkMEZST0VVS1EzZDNTazFxU1hoUFZFVTFUWHBqTlUxRVJVZERhWE5IUVZGUlFtYzNPSGRCVWtGRlNYZDNhR0ZJVWpCalNFMDJUSGs1Ym1GWVVtOWtWMGwxV1RJNWRBcE1Na1l6WTNreGQySXpaR3hqYmxKMllqSjRlazFDYTBkRGFYTkhRVkZSUW1jM09IZEJVa1ZGUTNkM1NrMVVTVFZOVkVrelRtcE5ORTFJT0VkRGFYTkhDa0ZSVVVKbk56aDNRVkpKUldOUmVIWmhTRkl3WTBoTk5reDVPVzVoV0ZKdlpGZEpkVmt5T1hSTU1rWXpZM2t4ZDJJelpHeGpibEoyWWpKNGVrd3pRbllLWkRKV2VXUkhPWFppU0UxMFlrZEdkRmx0VW1oTVdFSTFaRWRvZG1KcE9IVmFNbXd3WVVoV2FVd3paSFpqYlhSdFlrYzVNMk41T1hkamJWVjBZMjFXY3dwYVYwWjZXbE0xTldKWGVFRmpiVlp0WTNrNWIxcFhSbXRqZVRscldsaGFiR0pIT1hkTlJHZEhRMmx6UjBGUlVVSm5OemgzUVZKTlJVdG5kMjlPZWtac0NscEhXbXBOVkdOM1RVZEdhVmxxU1hkYWJVVTFXbXBKZWxwVVp6UmFWMDVvVG0xTk0xcFVaR2xOVjFKc1drUkJORTFFUVZsQ1oyOXlRbWRGUlVGWlR5OEtUVUZGVlVKQmIwMURTRTVxWVVkV2EyUlhlR3hOUnpSSFEybHpSMEZSVVVKbk56aDNRVkpWUlZsQmVHVmhTRkl3WTBoTk5reDVPVzVoV0ZKdlpGZEpkUXBaTWpsMFRESkdNMk41TVhkaU0yUnNZMjVTZG1JeWVIcE1NMEoyWkRKV2VXUkhPWFppU0UxMFlrZEdkRmx0VW1oTVdFSTFaRWRvZG1KcE9XaFpNMUp3Q21JeU5YcE1NMG94WW01TmRrMVVVWHBOYW1kNVQxUkplRTVxWTNaWldGSXdXbGN4ZDJSSVRYWk5WRUZYUW1kdmNrSm5SVVZCV1U4dlRVRkZWMEpCWjAwS1FtNUNNVmx0ZUhCWmVrTkNhVkZaUzB0M1dVSkNRVWhYWlZGSlJVRm5VamRDU0d0QlpIZENNVUZPTURsTlIzSkhlSGhGZVZsNGEyVklTbXh1VG5kTGFRcFRiRFkwTTJwNWRDODBaVXRqYjBGMlMyVTJUMEZCUVVKc2FGSjRSamRqUVVGQlVVUkJSVmwzVWtGSloyRmpMMHgzTWxJdlozcEtORmxRYVdoa2FuVXJDbWRqUzJsVGJVSTVNV2huUlVWaldUSjZPVzU1TVVZd1EwbEJObEZLZVdGbWFUVjFla2RLTVhOVU56bG5la1JuTVUwM2VrWXhObVp2VHpsRFZHbDJZVU1LZUVvM2VVMUJiMGREUTNGSFUwMDBPVUpCVFVSQk1tZEJUVWRWUTAxQ1IwbGxTVFJWV0N0S2RtWk9ZelpETm5FNVlURnZWVFoyVDFaNGJYQXZOVGRNWXdwNVZrWnZabVZ0U0VKeFVsSjVLeXRYS3paSlRqUk9jelE1WlZkUWVsRkplRUZOUTFsWGFHSlRLMW9yYXpGcE1YVjZRVk5tUzFobFNuTTRUVGhaWWpCdENtTmtjVmx1TVdWcVFVdGpVMHBXTlhaTU1VTm5jRFJQYW05aVowY3lRVUl5ZW5jOVBRb3RMUzB0TFVWT1JDQkRSVkpVU1VaSlEwRlVSUzB0TFMwdENnPT0ifV19fQ=="}]}, "dsseEnvelope":{"payload":"eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjAuMSIsInByZWRpY2F0ZVR5cGUiOiJodHRwczovL3Nsc2EuZGV2L3Byb3ZlbmFuY2UvdjAuMiIsInN1YmplY3QiOlt7Im5hbWUiOiIuL2F3c19sYW1iZGFfcG93ZXJ0b29scy0zLjkuMWE5LXB5My1ub25lLWFueS53aGwiLCJkaWdlc3QiOnsic2hhMjU2IjoiYTUwMzlhYjJhZjE0ZDk2N2NhNTc4YmUwMDU0MTA3ZGZkMTM5OTlmNjU3NTU3MWU0ZTM2MzQ0OTVhMmI1YmYxZiJ9fSx7Im5hbWUiOiIuL2F3c19sYW1iZGFfcG93ZXJ0b29scy0zLjkuMWE5LnRhci5neiIsImRpZ2VzdCI6eyJzaGEyNTYiOiIyMTc5MDNmM2Y2NzMwZDU1MmYwNjk1N2QzMDkxODRlNzM5YWVlZThlN2I4MTMzZmQwZWNkNzRhYTA4MjUwMzc4In19XSwicHJlZGljYXRlIjp7ImJ1aWxkZXIiOnsiaWQiOiJodHRwczovL2dpdGh1Yi5jb20vc2xzYS1mcmFtZXdvcmsvc2xzYS1naXRodWItZ2VuZXJhdG9yLy5naXRodWIvd29ya2Zsb3dzL2dlbmVyYXRvcl9nZW5lcmljX3Nsc2EzLnltbEByZWZzL3RhZ3MvdjIuMS4wIn0sImJ1aWxkVHlwZSI6Imh0dHBzOi8vZ2l0aHViLmNvbS9zbHNhLWZyYW1ld29yay9zbHNhLWdpdGh1Yi1nZW5lcmF0b3IvZ2VuZXJpY0B2MSIsImludm9jYXRpb24iOnsiY29uZmlnU291cmNlIjp7InVyaSI6ImdpdCtodHRwczovL2dpdGh1Yi5jb20vYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uQHJlZnMvaGVhZHMvZGV2ZWxvcCIsImRpZ2VzdCI6eyJzaGExIjoiNzFlZGZjMTcwMGFiYjIwZmE5ZjIzZTg4ZWNhNmM3ZTdiMWRlZDA4MCJ9LCJlbnRyeVBvaW50IjoiLmdpdGh1Yi93b3JrZmxvd3MvcHJlLXJlbGVhc2UueW1sIn0sInBhcmFtZXRlcnMiOnsidmFycyI6e319LCJlbnZpcm9ubWVudCI6eyJnaXRodWJfYWN0b3IiOiJsZWFuZHJvZGFtYXNjZW5hIiwiZ2l0aHViX2FjdG9yX2lkIjoiNDI5NTE3MyIsImdpdGh1Yl9iYXNlX3JlZiI6IiIsImdpdGh1Yl9ldmVudF9uYW1lIjoic2NoZWR1bGUiLCJnaXRodWJfZXZlbnRfcGF5bG9hZCI6eyJlbnRlcnByaXNlIjp7ImF2YXRhcl91cmwiOiJodHRwczovL2F2YXRhcnMuZ2l0aHVidXNlcmNvbnRlbnQuY29tL2IvMTI5MD92PTQiLCJjcmVhdGVkX2F0IjoiMjAxOS0xMS0xM1QxODowNTo0MVoiLCJkZXNjcmlwdGlvbiI6IiIsImh0bWxfdXJsIjoiaHR0cHM6Ly9naXRodWIuY29tL2VudGVycHJpc2VzL2FtYXpvbiIsImlkIjoxMjkwLCJuYW1lIjoiQW1hem9uIiwibm9kZV9pZCI6Ik1ERXdPa1Z1ZEdWeWNISnBjMlV4TWprdyIsInNsdWciOiJhbWF6b24iLCJ1cGRhdGVkX2F0IjoiMjAyNC0wOS0zMFQyMTowMjozMFoiLCJ3ZWJzaXRlX3VybCI6Imh0dHBzOi8vd3d3LmFtYXpvbi5jb20vIn0sIm9yZ2FuaXphdGlvbiI6eyJhdmF0YXJfdXJsIjoiaHR0cHM6Ly9hdmF0YXJzLmdpdGh1YnVzZXJjb250ZW50LmNvbS91LzEyOTEyNzYzOD92PTQiLCJkZXNjcmlwdGlvbiI6IiIsImV2ZW50c191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL29yZ3MvYXdzLXBvd2VydG9vbHMvZXZlbnRzIiwiaG9va3NfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9vcmdzL2F3cy1wb3dlcnRvb2xzL2hvb2tzIiwiaWQiOjEyOTEyNzYzOCwiaXNzdWVzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vb3Jncy9hd3MtcG93ZXJ0b29scy9pc3N1ZXMiLCJsb2dpbiI6ImF3cy1wb3dlcnRvb2xzIiwibWVtYmVyc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL29yZ3MvYXdzLXBvd2VydG9vbHMvbWVtYmVyc3svbWVtYmVyfSIsIm5vZGVfaWQiOiJPX2tnRE9CN0pVMWciLCJwdWJsaWNfbWVtYmVyc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL29yZ3MvYXdzLXBvd2VydG9vbHMvcHVibGljX21lbWJlcnN7L21lbWJlcn0iLCJyZXBvc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL29yZ3MvYXdzLXBvd2VydG9vbHMvcmVwb3MiLCJ1cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL29yZ3MvYXdzLXBvd2VydG9vbHMifSwicmVwb3NpdG9yeSI6eyJhbGxvd19mb3JraW5nIjp0cnVlLCJhcmNoaXZlX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL3thcmNoaXZlX2Zvcm1hdH17L3JlZn0iLCJhcmNoaXZlZCI6ZmFsc2UsImFzc2lnbmVlc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9hc3NpZ25lZXN7L3VzZXJ9IiwiYmxvYnNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vZ2l0L2Jsb2Jzey9zaGF9IiwiYnJhbmNoZXNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vYnJhbmNoZXN7L2JyYW5jaH0iLCJjbG9uZV91cmwiOiJodHRwczovL2dpdGh1Yi5jb20vYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uLmdpdCIsImNvbGxhYm9yYXRvcnNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vY29sbGFib3JhdG9yc3svY29sbGFib3JhdG9yfSIsImNvbW1lbnRzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2NvbW1lbnRzey9udW1iZXJ9IiwiY29tbWl0c191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9jb21taXRzey9zaGF9IiwiY29tcGFyZV91cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9jb21wYXJlL3tiYXNlfS4uLntoZWFkfSIsImNvbnRlbnRzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2NvbnRlbnRzL3srcGF0aH0iLCJjb250cmlidXRvcnNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vY29udHJpYnV0b3JzIiwiY3JlYXRlZF9hdCI6IjIwMTktMTEtMTVUMTI6MjY6MTJaIiwiY3VzdG9tX3Byb3BlcnRpZXMiOnt9LCJkZWZhdWx0X2JyYW5jaCI6ImRldmVsb3AiLCJkZXBsb3ltZW50c191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9kZXBsb3ltZW50cyIsImRlc2NyaXB0aW9uIjoiQSBkZXZlbG9wZXIgdG9vbGtpdCB0byBpbXBsZW1lbnQgU2VydmVybGVzcyBiZXN0IHByYWN0aWNlcyBhbmQgaW5jcmVhc2UgZGV2ZWxvcGVyIHZlbG9jaXR5LiIsImRpc2FibGVkIjpmYWxzZSwiZG93bmxvYWRzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2Rvd25sb2FkcyIsImV2ZW50c191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9ldmVudHMiLCJmb3JrIjpmYWxzZSwiZm9ya3MiOjQxOSwiZm9ya3NfY291bnQiOjQxOSwiZm9ya3NfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vZm9ya3MiLCJmdWxsX25hbWUiOiJhd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24iLCJnaXRfY29tbWl0c191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9naXQvY29tbWl0c3svc2hhfSIsImdpdF9yZWZzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2dpdC9yZWZzey9zaGF9IiwiZ2l0X3RhZ3NfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vZ2l0L3RhZ3N7L3NoYX0iLCJnaXRfdXJsIjoiZ2l0Oi8vZ2l0aHViLmNvbS9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24uZ2l0IiwiaGFzX2Rpc2N1c3Npb25zIjp0cnVlLCJoYXNfZG93bmxvYWRzIjp0cnVlLCJoYXNfaXNzdWVzIjp0cnVlLCJoYXNfcGFnZXMiOmZhbHNlLCJoYXNfcHJvamVjdHMiOnRydWUsImhhc193aWtpIjpmYWxzZSwiaG9tZXBhZ2UiOiJodHRwczovL2RvY3MucG93ZXJ0b29scy5hd3MuZGV2L2xhbWJkYS9weXRob24vbGF0ZXN0LyIsImhvb2tzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2hvb2tzIiwiaHRtbF91cmwiOiJodHRwczovL2dpdGh1Yi5jb20vYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uIiwiaWQiOjIyMTkxOTM3OSwiaXNfdGVtcGxhdGUiOmZhbHNlLCJpc3N1ZV9jb21tZW50X3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2lzc3Vlcy9jb21tZW50c3svbnVtYmVyfSIsImlzc3VlX2V2ZW50c191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9pc3N1ZXMvZXZlbnRzey9udW1iZXJ9IiwiaXNzdWVzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2lzc3Vlc3svbnVtYmVyfSIsImtleXNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24va2V5c3sva2V5X2lkfSIsImxhYmVsc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9sYWJlbHN7L25hbWV9IiwibGFuZ3VhZ2UiOiJQeXRob24iLCJsYW5ndWFnZXNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vbGFuZ3VhZ2VzIiwibGljZW5zZSI6eyJrZXkiOiJtaXQtMCIsIm5hbWUiOiJNSVQgTm8gQXR0cmlidXRpb24iLCJub2RlX2lkIjoiTURjNlRHbGpaVzV6WlRReCIsInNwZHhfaWQiOiJNSVQtMCIsInVybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vbGljZW5zZXMvbWl0LTAifSwibWVyZ2VzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL21lcmdlcyIsIm1pbGVzdG9uZXNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vbWlsZXN0b25lc3svbnVtYmVyfSIsIm1pcnJvcl91cmwiOm51bGwsIm5hbWUiOiJwb3dlcnRvb2xzLWxhbWJkYS1weXRob24iLCJub2RlX2lkIjoiTURFd09sSmxjRzl6YVhSdmNua3lNakU1TVRrek56az0iLCJub3RpZmljYXRpb25zX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL25vdGlmaWNhdGlvbnN7P3NpbmNlLGFsbCxwYXJ0aWNpcGF0aW5nfSIsIm9wZW5faXNzdWVzIjo1Niwib3Blbl9pc3N1ZXNfY291bnQiOjU2LCJvd25lciI6eyJhdmF0YXJfdXJsIjoiaHR0cHM6Ly9hdmF0YXJzLmdpdGh1YnVzZXJjb250ZW50LmNvbS91LzEyOTEyNzYzOD92PTQiLCJldmVudHNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS91c2Vycy9hd3MtcG93ZXJ0b29scy9ldmVudHN7L3ByaXZhY3l9IiwiZm9sbG93ZXJzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vdXNlcnMvYXdzLXBvd2VydG9vbHMvZm9sbG93ZXJzIiwiZm9sbG93aW5nX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vdXNlcnMvYXdzLXBvd2VydG9vbHMvZm9sbG93aW5ney9vdGhlcl91c2VyfSIsImdpc3RzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vdXNlcnMvYXdzLXBvd2VydG9vbHMvZ2lzdHN7L2dpc3RfaWR9IiwiZ3JhdmF0YXJfaWQiOiIiLCJodG1sX3VybCI6Imh0dHBzOi8vZ2l0aHViLmNvbS9hd3MtcG93ZXJ0b29scyIsImlkIjoxMjkxMjc2MzgsImxvZ2luIjoiYXdzLXBvd2VydG9vbHMiLCJub2RlX2lkIjoiT19rZ0RPQjdKVTFnIiwib3JnYW5pemF0aW9uc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3VzZXJzL2F3cy1wb3dlcnRvb2xzL29yZ3MiLCJyZWNlaXZlZF9ldmVudHNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS91c2Vycy9hd3MtcG93ZXJ0b29scy9yZWNlaXZlZF9ldmVudHMiLCJyZXBvc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3VzZXJzL2F3cy1wb3dlcnRvb2xzL3JlcG9zIiwic2l0ZV9hZG1pbiI6ZmFsc2UsInN0YXJyZWRfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS91c2Vycy9hd3MtcG93ZXJ0b29scy9zdGFycmVkey9vd25lcn17L3JlcG99Iiwic3Vic2NyaXB0aW9uc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3VzZXJzL2F3cy1wb3dlcnRvb2xzL3N1YnNjcmlwdGlvbnMiLCJ0eXBlIjoiT3JnYW5pemF0aW9uIiwidXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS91c2Vycy9hd3MtcG93ZXJ0b29scyIsInVzZXJfdmlld190eXBlIjoicHVibGljIn0sInByaXZhdGUiOmZhbHNlLCJwdWxsc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9wdWxsc3svbnVtYmVyfSIsInB1c2hlZF9hdCI6IjIwMjUtMDQtMDdUMjI6MzQ6MzFaIiwicmVsZWFzZXNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vcmVsZWFzZXN7L2lkfSIsInNpemUiOjEwNDc3Niwic3NoX3VybCI6ImdpdEBnaXRodWIuY29tOmF3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi5naXQiLCJzdGFyZ2F6ZXJzX2NvdW50IjozMDE5LCJzdGFyZ2F6ZXJzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL3N0YXJnYXplcnMiLCJzdGF0dXNlc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9zdGF0dXNlcy97c2hhfSIsInN1YnNjcmliZXJzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL3N1YnNjcmliZXJzIiwic3Vic2NyaXB0aW9uX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL3N1YnNjcmlwdGlvbiIsInN2bl91cmwiOiJodHRwczovL2dpdGh1Yi5jb20vYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uIiwidGFnc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi90YWdzIiwidGVhbXNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vdGVhbXMiLCJ0b3BpY3MiOlsiYXdzIiwiYXdzLWxhbWJkYSIsImhhY2t0b2JlcmZlc3QiLCJsYW1iZGEiLCJweXRob24iLCJzZXJ2ZXJsZXNzIl0sInRyZWVzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2dpdC90cmVlc3svc2hhfSIsInVwZGF0ZWRfYXQiOiIyMDI1LTA0LTA3VDIzOjE2OjA5WiIsInVybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uIiwidmlzaWJpbGl0eSI6InB1YmxpYyIsIndhdGNoZXJzIjozMDE5LCJ3YXRjaGVyc19jb3VudCI6MzAxOSwid2ViX2NvbW1pdF9zaWdub2ZmX3JlcXVpcmVkIjp0cnVlfSwic2NoZWR1bGUiOiIwIDggKiAqIDEtNSIsIndvcmtmbG93IjoiLmdpdGh1Yi93b3JrZmxvd3MvcHJlLXJlbGVhc2UueW1sIn0sImdpdGh1Yl9oZWFkX3JlZiI6IiIsImdpdGh1Yl9yZWYiOiJyZWZzL2hlYWRzL2RldmVsb3AiLCJnaXRodWJfcmVmX3R5cGUiOiJicmFuY2giLCJnaXRodWJfcmVwb3NpdG9yeV9pZCI6IjIyMTkxOTM3OSIsImdpdGh1Yl9yZXBvc2l0b3J5X293bmVyIjoiYXdzLXBvd2VydG9vbHMiLCJnaXRodWJfcmVwb3NpdG9yeV9vd25lcl9pZCI6IjEyOTEyNzYzOCIsImdpdGh1Yl9ydW5fYXR0ZW1wdCI6IjEiLCJnaXRodWJfcnVuX2lkIjoiMTQzMjgyOTIxNjciLCJnaXRodWJfcnVuX251bWJlciI6IjIxNCIsImdpdGh1Yl9zaGExIjoiNzFlZGZjMTcwMGFiYjIwZmE5ZjIzZTg4ZWNhNmM3ZTdiMWRlZDA4MCJ9fSwibWV0YWRhdGEiOnsiYnVpbGRJbnZvY2F0aW9uSUQiOiIxNDMyODI5MjE2Ny0xIiwiY29tcGxldGVuZXNzIjp7InBhcmFtZXRlcnMiOnRydWUsImVudmlyb25tZW50IjpmYWxzZSwibWF0ZXJpYWxzIjpmYWxzZX0sInJlcHJvZHVjaWJsZSI6ZmFsc2V9LCJtYXRlcmlhbHMiOlt7InVyaSI6ImdpdCtodHRwczovL2dpdGh1Yi5jb20vYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uQHJlZnMvaGVhZHMvZGV2ZWxvcCIsImRpZ2VzdCI6eyJzaGExIjoiNzFlZGZjMTcwMGFiYjIwZmE5ZjIzZTg4ZWNhNmM3ZTdiMWRlZDA4MCJ9fV19fQ==", "payloadType":"application/vnd.in-toto+json", "signatures":[{"sig":"MEUCIQDBQBoxI0JtvPkLIF+zGhW322NRvm3REu8ZRMjj/w5OmQIgIOKPl/+O85/wwi6cXIHJxfmyBy2V/qv8jfgOfM0jhJ4="}]}}
\ No newline at end of file
diff --git a/pyproject.toml b/pyproject.toml
index fc827195c05..c7d589a3191 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
[tool.poetry]
name = "aws_lambda_powertools"
-version = "3.9.1a3"
+version = "3.10.1a11"
description = "Powertools for AWS Lambda (Python) is a developer toolkit to implement Serverless best practices and increase developer velocity."
authors = ["Amazon Web Services"]
include = ["aws_lambda_powertools/py.typed", "THIRD-PARTY-LICENSES"]
@@ -77,7 +77,6 @@ datamasking = ["aws-encryption-sdk", "jsonpath-ng"]
[tool.poetry.group.dev.dependencies]
coverage = { extras = ["toml"], version = "^7.6" }
pytest = "^8.3.4"
-black = ">=24.8,<26.0"
boto3 = "^1.26.164"
isort = ">=5.13.2,<7.0.0"
pytest-cov = ">=5,<7"
@@ -107,13 +106,13 @@ hvac = "^2.3.0"
aws-requests-auth = "^0.4.3"
urllib3 = "<2"
requests = ">=2.32.0"
-cfn-lint = "1.32.1"
+cfn-lint = "1.34.1"
mypy = "^1.1.1"
types-python-dateutil = "^2.8.19.6"
aws-cdk-aws-appsync-alpha = "^2.59.0a0"
httpx = ">=0.23.3,<0.29.0"
sentry-sdk = ">=1.22.2,<3.0.0"
-ruff = ">=0.5.1,<0.11.3"
+ruff = ">=0.5.1,<0.11.7"
retry2 = "^0.9.5"
pytest-socket = ">=0.6,<0.8"
types-redis = "^4.6.0.7"
diff --git a/ruff.toml b/ruff.toml
index b415c63f949..e3a9584f4d3 100644
--- a/ruff.toml
+++ b/ruff.toml
@@ -74,11 +74,11 @@ lint.typing-modules = [
[lint.mccabe]
# Maximum cyclomatic complexity
-max-complexity = 15
+max-complexity = 16
[lint.pylint]
# Maximum number of nested blocks
-max-branches = 15
+max-branches = 16
# Maximum number of if statements in a function
max-statements = 70
@@ -98,5 +98,5 @@ runtime-evaluated-base-classes = ["pydantic.BaseModel"]
# Maintenance: we're keeping EphemeralMetrics code in case of Hyrum's law so we can quickly revert it
"aws_lambda_powertools/metrics/metrics.py" = ["ERA001"]
"examples/*" = ["FA100", "TCH"]
-"tests/*" = ["FA100", "TCH"]
+"tests/*" = ["FA100"]
"aws_lambda_powertools/utilities/parser/models/*" = ["FA100"]
diff --git a/tests/e2e/conftest.py b/tests/e2e/conftest.py
index f59eea9a33b..24d588093f2 100644
--- a/tests/e2e/conftest.py
+++ b/tests/e2e/conftest.py
@@ -1,11 +1,18 @@
+from __future__ import annotations
+
+from typing import TYPE_CHECKING, Any
+
import pytest
from tests.e2e.utils.infrastructure import call_once
from tests.e2e.utils.lambda_layer.powertools_layer import LocalLambdaPowertoolsLayer
+if TYPE_CHECKING:
+ from collections.abc import Generator
+
@pytest.fixture(scope="session", autouse=True)
-def lambda_layer_build(tmp_path_factory: pytest.TempPathFactory, worker_id: str) -> str:
+def lambda_layer_build(tmp_path_factory: pytest.TempPathFactory, worker_id: str) -> Generator[Any, Any, Any]:
"""Build Lambda Layer once before stacks are created
Parameters
diff --git a/tests/e2e/data_masking/conftest.py b/tests/e2e/data_masking/conftest.py
index f1892d7c0c9..d139c075be6 100644
--- a/tests/e2e/data_masking/conftest.py
+++ b/tests/e2e/data_masking/conftest.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import pytest
from tests.e2e.data_masking.infrastructure import DataMaskingStack
diff --git a/tests/e2e/data_masking/handlers/basic_handler.py b/tests/e2e/data_masking/handlers/basic_handler.py
index 6f696391822..03d5fe9b400 100644
--- a/tests/e2e/data_masking/handlers/basic_handler.py
+++ b/tests/e2e/data_masking/handlers/basic_handler.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
from aws_lambda_powertools import Logger
from aws_lambda_powertools.utilities.data_masking import DataMasking
from aws_lambda_powertools.utilities.data_masking.provider.kms.aws_encryption_sdk import AWSEncryptionSDKProvider
@@ -17,7 +19,4 @@ def lambda_handler(event, context):
data_masker = DataMasking(provider=AWSEncryptionSDKProvider(keys=[kms_key]))
value = [1, 2, "string", 4.5]
encrypted_data = data_masker.encrypt(value)
- response = {}
- response["encrypted_data"] = encrypted_data
-
- return response
+ return {"encrypted_data": encrypted_data}
diff --git a/tests/e2e/data_masking/infrastructure.py b/tests/e2e/data_masking/infrastructure.py
index ee18b272450..90d06bbf9be 100644
--- a/tests/e2e/data_masking/infrastructure.py
+++ b/tests/e2e/data_masking/infrastructure.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import aws_cdk.aws_kms as kms
from aws_cdk import CfnOutput, Duration
from aws_cdk import aws_iam as iam
diff --git a/tests/e2e/data_masking/test_e2e_data_masking.py b/tests/e2e/data_masking/test_e2e_data_masking.py
index 3ee2400b5cc..2b121b1890b 100644
--- a/tests/e2e/data_masking/test_e2e_data_masking.py
+++ b/tests/e2e/data_masking/test_e2e_data_masking.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import json
from uuid import uuid4
diff --git a/tests/e2e/event_handler/conftest.py b/tests/e2e/event_handler/conftest.py
index 664c870e1de..921f01e46f7 100644
--- a/tests/e2e/event_handler/conftest.py
+++ b/tests/e2e/event_handler/conftest.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import pytest
from tests.e2e.event_handler.infrastructure import EventHandlerStack
diff --git a/tests/e2e/event_handler/handlers/alb_handler.py b/tests/e2e/event_handler/handlers/alb_handler.py
index ef1af1792ac..beae4f19610 100644
--- a/tests/e2e/event_handler/handlers/alb_handler.py
+++ b/tests/e2e/event_handler/handlers/alb_handler.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
from aws_lambda_powertools.event_handler import (
ALBResolver,
CORSConfig,
diff --git a/tests/e2e/event_handler/handlers/alb_handler_with_body_none.py b/tests/e2e/event_handler/handlers/alb_handler_with_body_none.py
index ec72bfbd5f7..54789b3ede3 100644
--- a/tests/e2e/event_handler/handlers/alb_handler_with_body_none.py
+++ b/tests/e2e/event_handler/handlers/alb_handler_with_body_none.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
from aws_lambda_powertools.event_handler import (
ALBResolver,
Response,
diff --git a/tests/e2e/event_handler/handlers/api_gateway_http_handler.py b/tests/e2e/event_handler/handlers/api_gateway_http_handler.py
index 876d78ef67b..10699433d5b 100644
--- a/tests/e2e/event_handler/handlers/api_gateway_http_handler.py
+++ b/tests/e2e/event_handler/handlers/api_gateway_http_handler.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
from aws_lambda_powertools.event_handler import (
APIGatewayHttpResolver,
CORSConfig,
diff --git a/tests/e2e/event_handler/handlers/api_gateway_rest_handler.py b/tests/e2e/event_handler/handlers/api_gateway_rest_handler.py
index d09bf6b82c9..4ae4dc28a97 100644
--- a/tests/e2e/event_handler/handlers/api_gateway_rest_handler.py
+++ b/tests/e2e/event_handler/handlers/api_gateway_rest_handler.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
from aws_lambda_powertools.event_handler import (
APIGatewayRestResolver,
CORSConfig,
diff --git a/tests/e2e/event_handler/handlers/lambda_function_url_handler.py b/tests/e2e/event_handler/handlers/lambda_function_url_handler.py
index e47035a971d..61b98256664 100644
--- a/tests/e2e/event_handler/handlers/lambda_function_url_handler.py
+++ b/tests/e2e/event_handler/handlers/lambda_function_url_handler.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
from aws_lambda_powertools.event_handler import (
CORSConfig,
LambdaFunctionUrlResolver,
diff --git a/tests/e2e/event_handler/handlers/openapi_handler.py b/tests/e2e/event_handler/handlers/openapi_handler.py
index 13cfb69f016..04fcd39efe7 100644
--- a/tests/e2e/event_handler/handlers/openapi_handler.py
+++ b/tests/e2e/event_handler/handlers/openapi_handler.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
from aws_lambda_powertools.event_handler import (
APIGatewayRestResolver,
)
diff --git a/tests/e2e/event_handler/infrastructure.py b/tests/e2e/event_handler/infrastructure.py
index 142034e89b2..67d370d2340 100644
--- a/tests/e2e/event_handler/infrastructure.py
+++ b/tests/e2e/event_handler/infrastructure.py
@@ -1,4 +1,4 @@
-from typing import Dict, List, Optional
+from __future__ import annotations
from aws_cdk import CfnOutput, Duration
from aws_cdk import aws_apigateway as apigwv1
@@ -28,7 +28,7 @@ def create_resources(self):
self._create_api_gateway_http(function=functions["ApiGatewayHttpHandler"])
self._create_lambda_function_url(function=functions["LambdaFunctionUrlHandler"])
- def _create_alb(self, function: List[Function]):
+ def _create_alb(self, function: list[Function]):
vpc = ec2.Vpc.from_lookup(
self.stack,
"VPC",
@@ -58,7 +58,7 @@ def _create_alb_listener(
name: str,
port: int,
function: Function,
- attributes: Optional[Dict[str, str]] = None,
+ attributes: dict[str, str] | None = None,
):
listener = alb.add_listener(name, port=port, protocol=elbv2.ApplicationProtocol.HTTP)
target = listener.add_targets(f"ALB{name}Target", targets=[targets.LambdaTarget(function)])
@@ -82,7 +82,7 @@ def _create_api_gateway_http(self, function: Function):
CfnOutput(self.stack, "APIGatewayHTTPUrl", value=(apigw.url or ""))
- def _create_api_gateway_rest(self, function: List[Function]):
+ def _create_api_gateway_rest(self, function: list[Function]):
apigw = apigwv1.RestApi(
self.stack,
"APIGatewayRest",
diff --git a/tests/e2e/event_handler/test_cors.py b/tests/e2e/event_handler/test_cors.py
index 921a227e944..fa1e6b1514f 100644
--- a/tests/e2e/event_handler/test_cors.py
+++ b/tests/e2e/event_handler/test_cors.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import pytest
from requests import Request
diff --git a/tests/e2e/event_handler/test_header_serializer.py b/tests/e2e/event_handler/test_header_serializer.py
index 6eb9c6d0fd7..5ced15677cf 100644
--- a/tests/e2e/event_handler/test_header_serializer.py
+++ b/tests/e2e/event_handler/test_header_serializer.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
from uuid import uuid4
import pytest
diff --git a/tests/e2e/event_handler/test_openapi.py b/tests/e2e/event_handler/test_openapi.py
index 3a8d6b3c008..3a91f804d31 100644
--- a/tests/e2e/event_handler/test_openapi.py
+++ b/tests/e2e/event_handler/test_openapi.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import pytest
from requests import Request
diff --git a/tests/e2e/event_handler/test_paths_ending_with_slash.py b/tests/e2e/event_handler/test_paths_ending_with_slash.py
index efbc02cf1ac..7d2ae1192b9 100644
--- a/tests/e2e/event_handler/test_paths_ending_with_slash.py
+++ b/tests/e2e/event_handler/test_paths_ending_with_slash.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import pytest
from requests import HTTPError, Request
diff --git a/tests/e2e/event_handler/test_response_code.py b/tests/e2e/event_handler/test_response_code.py
index 46bf8bcf183..b226c8d8296 100644
--- a/tests/e2e/event_handler/test_response_code.py
+++ b/tests/e2e/event_handler/test_response_code.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import pytest
from requests import Request
diff --git a/tests/e2e/event_handler_appsync/conftest.py b/tests/e2e/event_handler_appsync/conftest.py
index 1f6d8c406de..7b42e529d23 100644
--- a/tests/e2e/event_handler_appsync/conftest.py
+++ b/tests/e2e/event_handler_appsync/conftest.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import pytest
from tests.e2e.event_handler_appsync.infrastructure import EventHandlerAppSyncStack
diff --git a/tests/e2e/event_handler_appsync/handlers/appsync_resolver_handler.py b/tests/e2e/event_handler_appsync/handlers/appsync_resolver_handler.py
index 594290f478d..71e5c887233 100644
--- a/tests/e2e/event_handler_appsync/handlers/appsync_resolver_handler.py
+++ b/tests/e2e/event_handler_appsync/handlers/appsync_resolver_handler.py
@@ -1,10 +1,14 @@
-from typing import List, Optional
+from __future__ import annotations
+
+from typing import TYPE_CHECKING
from pydantic import BaseModel
from aws_lambda_powertools.event_handler import AppSyncResolver
-from aws_lambda_powertools.utilities.data_classes import AppSyncResolverEvent
-from aws_lambda_powertools.utilities.typing import LambdaContext
+
+if TYPE_CHECKING:
+ from aws_lambda_powertools.utilities.data_classes import AppSyncResolverEvent
+ from aws_lambda_powertools.utilities.typing import LambdaContext
app = AppSyncResolver()
@@ -84,29 +88,29 @@ def get_post(post_id: str = "") -> dict:
@app.resolver(type_name="Query", field_name="allPosts")
-def all_posts() -> List[dict]:
+def all_posts() -> list[dict]:
return list(posts.values())
# PROCESSING BATCH WITHOUT AGGREGATION
@app.batch_resolver(type_name="Post", field_name="relatedPosts", aggregate=False)
-def related_posts(event: AppSyncResolverEvent) -> Optional[list]:
+def related_posts(event: AppSyncResolverEvent) -> list | None:
return posts_related[event.source["post_id"]] if event.source else None
@app.async_batch_resolver(type_name="Post", field_name="relatedPostsAsync", aggregate=False)
-async def related_posts_async(event: AppSyncResolverEvent) -> Optional[list]:
+async def related_posts_async(event: AppSyncResolverEvent) -> list | None:
return posts_related[event.source["post_id"]] if event.source else None
# PROCESSING BATCH WITH AGGREGATION
@app.batch_resolver(type_name="Post", field_name="relatedPostsAggregate")
-def related_posts_aggregate(event: List[AppSyncResolverEvent]) -> Optional[list]:
+def related_posts_aggregate(event: list[AppSyncResolverEvent]) -> list | None:
return [posts_related[record.source.get("post_id")] for record in event]
@app.async_batch_resolver(type_name="Post", field_name="relatedPostsAsyncAggregate")
-async def related_posts_async_aggregate(event: List[AppSyncResolverEvent]) -> Optional[list]:
+async def related_posts_async_aggregate(event: list[AppSyncResolverEvent]) -> list | None:
return [posts_related[record.source.get("post_id")] for record in event]
diff --git a/tests/e2e/event_handler_appsync/infrastructure.py b/tests/e2e/event_handler_appsync/infrastructure.py
index 1a07270572a..b84d460d6a7 100644
--- a/tests/e2e/event_handler_appsync/infrastructure.py
+++ b/tests/e2e/event_handler_appsync/infrastructure.py
@@ -1,12 +1,17 @@
+from __future__ import annotations
+
from pathlib import Path
+from typing import TYPE_CHECKING
from aws_cdk import CfnOutput, Duration, Expiration
from aws_cdk import aws_appsync_alpha as appsync
-from aws_cdk.aws_lambda import Function
from tests.e2e.utils.data_builder import build_random_value
from tests.e2e.utils.infrastructure import BaseInfrastructure
+if TYPE_CHECKING:
+ from aws_cdk.aws_lambda import Function
+
class EventHandlerAppSyncStack(BaseInfrastructure):
def create_resources(self):
diff --git a/tests/e2e/event_handler_appsync/test_appsync_resolvers.py b/tests/e2e/event_handler_appsync/test_appsync_resolvers.py
index 35549a1fdef..b0d90b0d63b 100644
--- a/tests/e2e/event_handler_appsync/test_appsync_resolvers.py
+++ b/tests/e2e/event_handler_appsync/test_appsync_resolvers.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import json
import pytest
diff --git a/tests/e2e/idempotency/conftest.py b/tests/e2e/idempotency/conftest.py
index 61578d904a6..c23f33958a7 100644
--- a/tests/e2e/idempotency/conftest.py
+++ b/tests/e2e/idempotency/conftest.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import pytest
from tests.e2e.idempotency.infrastructure import IdempotencyDynamoDBStack
diff --git a/tests/e2e/idempotency/handlers/function_thread_safety_handler.py b/tests/e2e/idempotency/handlers/function_thread_safety_handler.py
index a4644aa61c3..23078adceec 100644
--- a/tests/e2e/idempotency/handlers/function_thread_safety_handler.py
+++ b/tests/e2e/idempotency/handlers/function_thread_safety_handler.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import os
import time
from concurrent.futures import ThreadPoolExecutor, as_completed
diff --git a/tests/e2e/idempotency/handlers/optional_idempotency_key_handler.py b/tests/e2e/idempotency/handlers/optional_idempotency_key_handler.py
index f1b7052041f..fcfada152c4 100644
--- a/tests/e2e/idempotency/handlers/optional_idempotency_key_handler.py
+++ b/tests/e2e/idempotency/handlers/optional_idempotency_key_handler.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import os
import uuid
diff --git a/tests/e2e/idempotency/handlers/parallel_execution_handler.py b/tests/e2e/idempotency/handlers/parallel_execution_handler.py
index cd66be0cd1d..fa63ad04ec3 100644
--- a/tests/e2e/idempotency/handlers/parallel_execution_handler.py
+++ b/tests/e2e/idempotency/handlers/parallel_execution_handler.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import os
import time
diff --git a/tests/e2e/idempotency/handlers/payload_tampering_validation_handler.py b/tests/e2e/idempotency/handlers/payload_tampering_validation_handler.py
index dacb6ce63e0..fdb50566900 100644
--- a/tests/e2e/idempotency/handlers/payload_tampering_validation_handler.py
+++ b/tests/e2e/idempotency/handlers/payload_tampering_validation_handler.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import os
import uuid
diff --git a/tests/e2e/idempotency/handlers/response_hook.py b/tests/e2e/idempotency/handlers/response_hook.py
index b56d09f25b9..843d45595cf 100644
--- a/tests/e2e/idempotency/handlers/response_hook.py
+++ b/tests/e2e/idempotency/handlers/response_hook.py
@@ -1,13 +1,18 @@
+from __future__ import annotations
+
import os
+from typing import TYPE_CHECKING
from aws_lambda_powertools.utilities.idempotency import (
DynamoDBPersistenceLayer,
IdempotencyConfig,
idempotent,
)
-from aws_lambda_powertools.utilities.idempotency.persistence.datarecord import (
- DataRecord,
-)
+
+if TYPE_CHECKING:
+ from aws_lambda_powertools.utilities.idempotency.persistence.datarecord import (
+ DataRecord,
+ )
TABLE_NAME = os.getenv("IdempotencyTable", "")
persistence_layer = DynamoDBPersistenceLayer(table_name=TABLE_NAME)
diff --git a/tests/e2e/idempotency/handlers/ttl_cache_expiration_handler.py b/tests/e2e/idempotency/handlers/ttl_cache_expiration_handler.py
index a9bf4fb2b64..ad94e6f7212 100644
--- a/tests/e2e/idempotency/handlers/ttl_cache_expiration_handler.py
+++ b/tests/e2e/idempotency/handlers/ttl_cache_expiration_handler.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import os
import time
diff --git a/tests/e2e/idempotency/handlers/ttl_cache_timeout_handler.py b/tests/e2e/idempotency/handlers/ttl_cache_timeout_handler.py
index ad1a51b495d..e93e0643f0a 100644
--- a/tests/e2e/idempotency/handlers/ttl_cache_timeout_handler.py
+++ b/tests/e2e/idempotency/handlers/ttl_cache_timeout_handler.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import os
import time
diff --git a/tests/e2e/idempotency/infrastructure.py b/tests/e2e/idempotency/infrastructure.py
index 6ca14d576ef..2696025b57a 100644
--- a/tests/e2e/idempotency/infrastructure.py
+++ b/tests/e2e/idempotency/infrastructure.py
@@ -1,6 +1,12 @@
+from __future__ import annotations
+
+from typing import TYPE_CHECKING
+
from aws_cdk import CfnOutput, Duration, RemovalPolicy
from aws_cdk import aws_dynamodb as dynamodb
-from aws_cdk.aws_dynamodb import Table
+
+if TYPE_CHECKING:
+ from aws_cdk.aws_dynamodb import Table
from tests.e2e.utils.infrastructure import BaseInfrastructure
diff --git a/tests/e2e/idempotency/test_idempotency_dynamodb.py b/tests/e2e/idempotency/test_idempotency_dynamodb.py
index 75d774e702e..0123683f877 100644
--- a/tests/e2e/idempotency/test_idempotency_dynamodb.py
+++ b/tests/e2e/idempotency/test_idempotency_dynamodb.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import json
from copy import deepcopy
from time import sleep
diff --git a/tests/e2e/logger/conftest.py b/tests/e2e/logger/conftest.py
index ad336931a93..a6e6ace94e1 100644
--- a/tests/e2e/logger/conftest.py
+++ b/tests/e2e/logger/conftest.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import pytest
from tests.e2e.logger.infrastructure import LoggerStack
diff --git a/tests/e2e/logger/handlers/basic_handler.py b/tests/e2e/logger/handlers/basic_handler.py
index 0f0dd46b4aa..20471dd80b4 100644
--- a/tests/e2e/logger/handlers/basic_handler.py
+++ b/tests/e2e/logger/handlers/basic_handler.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
from aws_lambda_powertools import Logger
logger = Logger()
diff --git a/tests/e2e/logger/handlers/buffer_logs_with_flush.py b/tests/e2e/logger/handlers/buffer_logs_with_flush.py
index bcf70db3291..125bb352e75 100644
--- a/tests/e2e/logger/handlers/buffer_logs_with_flush.py
+++ b/tests/e2e/logger/handlers/buffer_logs_with_flush.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
from aws_lambda_powertools import Logger
from aws_lambda_powertools.logging.buffer import LoggerBufferConfig
diff --git a/tests/e2e/logger/handlers/buffer_logs_without_flush.py b/tests/e2e/logger/handlers/buffer_logs_without_flush.py
index ef606c0c474..640a987bc82 100644
--- a/tests/e2e/logger/handlers/buffer_logs_without_flush.py
+++ b/tests/e2e/logger/handlers/buffer_logs_without_flush.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
from aws_lambda_powertools import Logger
from aws_lambda_powertools.logging.buffer import LoggerBufferConfig
diff --git a/tests/e2e/logger/handlers/multiple_logger_instances.py b/tests/e2e/logger/handlers/multiple_logger_instances.py
index 07feabde07f..8e9602a3194 100644
--- a/tests/e2e/logger/handlers/multiple_logger_instances.py
+++ b/tests/e2e/logger/handlers/multiple_logger_instances.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
from aws_lambda_powertools import Logger
# Instance 1
diff --git a/tests/e2e/logger/handlers/tz_handler.py b/tests/e2e/logger/handlers/tz_handler.py
index 06f6cfbf846..12add5ea6b4 100644
--- a/tests/e2e/logger/handlers/tz_handler.py
+++ b/tests/e2e/logger/handlers/tz_handler.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import os
import time
diff --git a/tests/e2e/logger/infrastructure.py b/tests/e2e/logger/infrastructure.py
index 242b3c10892..e12d695107b 100644
--- a/tests/e2e/logger/infrastructure.py
+++ b/tests/e2e/logger/infrastructure.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
from tests.e2e.utils.infrastructure import BaseInfrastructure
diff --git a/tests/e2e/logger/test_logger.py b/tests/e2e/logger/test_logger.py
index 94fa40026b5..dddef82eb25 100644
--- a/tests/e2e/logger/test_logger.py
+++ b/tests/e2e/logger/test_logger.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import json
import os
import time
diff --git a/tests/e2e/metrics/conftest.py b/tests/e2e/metrics/conftest.py
index 197aaff847f..fe51288642c 100644
--- a/tests/e2e/metrics/conftest.py
+++ b/tests/e2e/metrics/conftest.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import pytest
from tests.e2e.metrics.infrastructure import MetricsStack
diff --git a/tests/e2e/metrics/handlers/basic_handler.py b/tests/e2e/metrics/handlers/basic_handler.py
index ef5e079e604..178f49454e7 100644
--- a/tests/e2e/metrics/handlers/basic_handler.py
+++ b/tests/e2e/metrics/handlers/basic_handler.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
from aws_lambda_powertools import Metrics
my_metrics = Metrics()
diff --git a/tests/e2e/metrics/handlers/cold_start.py b/tests/e2e/metrics/handlers/cold_start.py
index 20f2ad16f85..63c81b49fe9 100644
--- a/tests/e2e/metrics/handlers/cold_start.py
+++ b/tests/e2e/metrics/handlers/cold_start.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
from aws_lambda_powertools import Metrics
my_metrics = Metrics()
diff --git a/tests/e2e/metrics/infrastructure.py b/tests/e2e/metrics/infrastructure.py
index 7cc1eb8c498..2441e220ff1 100644
--- a/tests/e2e/metrics/infrastructure.py
+++ b/tests/e2e/metrics/infrastructure.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
from tests.e2e.utils.infrastructure import BaseInfrastructure
diff --git a/tests/e2e/metrics/test_metrics.py b/tests/e2e/metrics/test_metrics.py
index 4285d011524..9dded91fafe 100644
--- a/tests/e2e/metrics/test_metrics.py
+++ b/tests/e2e/metrics/test_metrics.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import json
import pytest
diff --git a/tests/e2e/parameters/conftest.py b/tests/e2e/parameters/conftest.py
index 99146607384..7657287c9e9 100644
--- a/tests/e2e/parameters/conftest.py
+++ b/tests/e2e/parameters/conftest.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import pytest
from tests.e2e.parameters.infrastructure import ParametersStack
diff --git a/tests/e2e/parameters/infrastructure.py b/tests/e2e/parameters/infrastructure.py
index 810e0f101d4..f076a2ee702 100644
--- a/tests/e2e/parameters/infrastructure.py
+++ b/tests/e2e/parameters/infrastructure.py
@@ -1,15 +1,19 @@
+from __future__ import annotations
+
import json
-from typing import List
+from typing import TYPE_CHECKING
from aws_cdk import CfnOutput, Duration
from aws_cdk import aws_appconfig as appconfig
from aws_cdk import aws_iam as iam
from aws_cdk import aws_ssm as ssm
-from aws_cdk.aws_lambda import Function
from tests.e2e.utils.data_builder import build_random_value, build_service_name
from tests.e2e.utils.infrastructure import BaseInfrastructure
+if TYPE_CHECKING:
+ from aws_cdk.aws_lambda import Function
+
class ParametersStack(BaseInfrastructure):
def create_resources(self):
@@ -125,8 +129,8 @@ def _create_app_config_freeform(
),
)
- def _create_ssm_parameters(self) -> List[str]:
- parameters: List[str] = []
+ def _create_ssm_parameters(self) -> list[str]:
+ parameters: list[str] = []
for _ in range(10):
param = f"/powertools/e2e/parameters/{build_random_value()}"
diff --git a/tests/e2e/parameters/test_appconfig.py b/tests/e2e/parameters/test_appconfig.py
index 28f50a653f4..96f821a743a 100644
--- a/tests/e2e/parameters/test_appconfig.py
+++ b/tests/e2e/parameters/test_appconfig.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import json
import pytest
diff --git a/tests/e2e/parameters/test_ssm.py b/tests/e2e/parameters/test_ssm.py
index 239813fab51..11e88028157 100644
--- a/tests/e2e/parameters/test_ssm.py
+++ b/tests/e2e/parameters/test_ssm.py
@@ -1,5 +1,7 @@
+from __future__ import annotations
+
import json
-from typing import Any, Dict, List
+from typing import Any
import pytest
@@ -12,7 +14,7 @@ def ssm_get_parameters_by_name_fn_arn(infrastructure: dict) -> str:
@pytest.fixture
-def parameters_list(infrastructure: dict) -> List[str]:
+def parameters_list(infrastructure: dict) -> list[str]:
param_list = infrastructure.get("ParametersNameList", "[]")
return json.loads(param_list)
@@ -24,7 +26,7 @@ def test_get_parameters_by_name(
):
# GIVEN/WHEN
function_response, _ = data_fetcher.get_lambda_response(lambda_arn=ssm_get_parameters_by_name_fn_arn)
- parameter_values: Dict[str, Any] = json.loads(function_response["Payload"].read().decode("utf-8"))
+ parameter_values: dict[str, Any] = json.loads(function_response["Payload"].read().decode("utf-8"))
# THEN
for param in parameters_list:
diff --git a/tests/e2e/parser/conftest.py b/tests/e2e/parser/conftest.py
index d7ef0aa0176..6cba318fa21 100644
--- a/tests/e2e/parser/conftest.py
+++ b/tests/e2e/parser/conftest.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import pytest
from tests.e2e.parser.infrastructure import ParserStack
diff --git a/tests/e2e/parser/handlers/handler_with_basic_model.py b/tests/e2e/parser/handlers/handler_with_basic_model.py
index 7b0d89dda53..c35946ee820 100644
--- a/tests/e2e/parser/handlers/handler_with_basic_model.py
+++ b/tests/e2e/parser/handlers/handler_with_basic_model.py
@@ -1,7 +1,13 @@
+from __future__ import annotations
+
+from typing import TYPE_CHECKING
+
from pydantic import BaseModel
from aws_lambda_powertools.utilities.parser import event_parser
-from aws_lambda_powertools.utilities.typing import LambdaContext
+
+if TYPE_CHECKING:
+ from aws_lambda_powertools.utilities.typing import LambdaContext
class BasicModel(BaseModel):
@@ -9,6 +15,6 @@ class BasicModel(BaseModel):
version: str
-@event_parser
+@event_parser(model=BasicModel)
def lambda_handler(event: BasicModel, context: LambdaContext):
return {"product": event.product}
diff --git a/tests/e2e/parser/handlers/handler_with_dataclass.py b/tests/e2e/parser/handlers/handler_with_dataclass.py
index 7f465fe79ec..b9eed24a163 100644
--- a/tests/e2e/parser/handlers/handler_with_dataclass.py
+++ b/tests/e2e/parser/handlers/handler_with_dataclass.py
@@ -1,7 +1,12 @@
+from __future__ import annotations
+
from dataclasses import dataclass
+from typing import TYPE_CHECKING
from aws_lambda_powertools.utilities.parser import event_parser
-from aws_lambda_powertools.utilities.typing import LambdaContext
+
+if TYPE_CHECKING:
+ from aws_lambda_powertools.utilities.typing import LambdaContext
@dataclass
@@ -10,6 +15,6 @@ class BasicDataclass:
version: str
-@event_parser
+@event_parser(model=BasicDataclass)
def lambda_handler(event: BasicDataclass, context: LambdaContext):
return {"product": event.product}
diff --git a/tests/e2e/parser/handlers/handler_with_model_type_class.py b/tests/e2e/parser/handlers/handler_with_model_type_class.py
index 7e635dee13a..dfa81d9c137 100644
--- a/tests/e2e/parser/handlers/handler_with_model_type_class.py
+++ b/tests/e2e/parser/handlers/handler_with_model_type_class.py
@@ -1,10 +1,14 @@
+from __future__ import annotations
+
import json
-from typing import Any, Dict, Type, Union
+from typing import TYPE_CHECKING, Any, Dict, Type, Union
from pydantic import BaseModel
from aws_lambda_powertools.utilities.parser import parse
-from aws_lambda_powertools.utilities.typing import LambdaContext
+
+if TYPE_CHECKING:
+ from aws_lambda_powertools.utilities.typing import LambdaContext
AnyInheritedModel = Union[Type[BaseModel], BaseModel]
RawDictOrModel = Union[Dict[str, Any], AnyInheritedModel]
diff --git a/tests/e2e/parser/handlers/handler_with_union_tag.py b/tests/e2e/parser/handlers/handler_with_union_tag.py
index e2013251d8f..af43f2fef42 100644
--- a/tests/e2e/parser/handlers/handler_with_union_tag.py
+++ b/tests/e2e/parser/handlers/handler_with_union_tag.py
@@ -1,10 +1,14 @@
-from typing import Literal, Union
+from __future__ import annotations
+
+from typing import TYPE_CHECKING, Literal, Union
from pydantic import BaseModel, Field
from typing_extensions import Annotated
from aws_lambda_powertools.utilities.parser import event_parser
-from aws_lambda_powertools.utilities.typing import LambdaContext
+
+if TYPE_CHECKING:
+ from aws_lambda_powertools.utilities.typing import LambdaContext
class SuccessCallback(BaseModel):
@@ -26,6 +30,6 @@ class PartialFailureCallback(BaseModel):
OrderCallback = Annotated[Union[SuccessCallback, ErrorCallback, PartialFailureCallback], Field(discriminator="status")]
-@event_parser
+@event_parser(model=OrderCallback)
def lambda_handler(event: OrderCallback, context: LambdaContext):
return {"error_msg": event.error_msg}
diff --git a/tests/e2e/parser/infrastructure.py b/tests/e2e/parser/infrastructure.py
index 5d66905e7c7..5bc324f98bb 100644
--- a/tests/e2e/parser/infrastructure.py
+++ b/tests/e2e/parser/infrastructure.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
from tests.e2e.utils.infrastructure import BaseInfrastructure
diff --git a/tests/e2e/parser/test_parser.py b/tests/e2e/parser/test_parser.py
index aa52889aeab..fe1f6123b03 100644
--- a/tests/e2e/parser/test_parser.py
+++ b/tests/e2e/parser/test_parser.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import json
import pytest
diff --git a/tests/e2e/streaming/conftest.py b/tests/e2e/streaming/conftest.py
index 94f7f212af0..35d6ad0d6b8 100644
--- a/tests/e2e/streaming/conftest.py
+++ b/tests/e2e/streaming/conftest.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import pytest
from tests.e2e.streaming.infrastructure import StreamingStack
diff --git a/tests/e2e/streaming/handlers/s3_object_handler.py b/tests/e2e/streaming/handlers/s3_object_handler.py
index 3c47f4ab3b7..42781db0d7e 100644
--- a/tests/e2e/streaming/handlers/s3_object_handler.py
+++ b/tests/e2e/streaming/handlers/s3_object_handler.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import zipfile
import botocore.exceptions
diff --git a/tests/e2e/streaming/infrastructure.py b/tests/e2e/streaming/infrastructure.py
index 31152c69535..927a92973c3 100644
--- a/tests/e2e/streaming/infrastructure.py
+++ b/tests/e2e/streaming/infrastructure.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
from pathlib import Path
from aws_cdk import CfnOutput, Duration, RemovalPolicy
diff --git a/tests/e2e/streaming/test_s3_object.py b/tests/e2e/streaming/test_s3_object.py
index 4a16c58b2b6..3ea50105a2b 100644
--- a/tests/e2e/streaming/test_s3_object.py
+++ b/tests/e2e/streaming/test_s3_object.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import json
import boto3
diff --git a/tests/e2e/tracer/conftest.py b/tests/e2e/tracer/conftest.py
index d3728ab91ba..9381ccff35f 100644
--- a/tests/e2e/tracer/conftest.py
+++ b/tests/e2e/tracer/conftest.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import pytest
from tests.e2e.tracer.infrastructure import TracerStack
diff --git a/tests/e2e/tracer/handlers/async_capture.py b/tests/e2e/tracer/handlers/async_capture.py
index 814e0b92e02..4233eb10d74 100644
--- a/tests/e2e/tracer/handlers/async_capture.py
+++ b/tests/e2e/tracer/handlers/async_capture.py
@@ -1,8 +1,13 @@
+from __future__ import annotations
+
import asyncio
+from typing import TYPE_CHECKING
from uuid import uuid4
from aws_lambda_powertools import Tracer
-from aws_lambda_powertools.utilities.typing import LambdaContext
+
+if TYPE_CHECKING:
+ from aws_lambda_powertools.utilities.typing import LambdaContext
tracer = Tracer()
diff --git a/tests/e2e/tracer/handlers/basic_handler.py b/tests/e2e/tracer/handlers/basic_handler.py
index 89a6b062423..85aa2a58460 100644
--- a/tests/e2e/tracer/handlers/basic_handler.py
+++ b/tests/e2e/tracer/handlers/basic_handler.py
@@ -1,7 +1,12 @@
+from __future__ import annotations
+
+from typing import TYPE_CHECKING
from uuid import uuid4
from aws_lambda_powertools import Tracer
-from aws_lambda_powertools.utilities.typing import LambdaContext
+
+if TYPE_CHECKING:
+ from aws_lambda_powertools.utilities.typing import LambdaContext
tracer = Tracer()
diff --git a/tests/e2e/tracer/handlers/same_function_name.py b/tests/e2e/tracer/handlers/same_function_name.py
index 240e3329bc8..6f37af9eacd 100644
--- a/tests/e2e/tracer/handlers/same_function_name.py
+++ b/tests/e2e/tracer/handlers/same_function_name.py
@@ -1,8 +1,13 @@
+from __future__ import annotations
+
from abc import ABC, abstractmethod
+from typing import TYPE_CHECKING
from uuid import uuid4
from aws_lambda_powertools import Tracer
-from aws_lambda_powertools.utilities.typing import LambdaContext
+
+if TYPE_CHECKING:
+ from aws_lambda_powertools.utilities.typing import LambdaContext
tracer = Tracer()
diff --git a/tests/e2e/tracer/infrastructure.py b/tests/e2e/tracer/infrastructure.py
index 8562359acf0..218481c4bc3 100644
--- a/tests/e2e/tracer/infrastructure.py
+++ b/tests/e2e/tracer/infrastructure.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
from tests.e2e.utils.infrastructure import BaseInfrastructure
diff --git a/tests/e2e/tracer/test_tracer.py b/tests/e2e/tracer/test_tracer.py
index 5dfe68ee08c..07b7cacf6d2 100644
--- a/tests/e2e/tracer/test_tracer.py
+++ b/tests/e2e/tracer/test_tracer.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import json
import pytest
diff --git a/tests/e2e/utils/auth.py b/tests/e2e/utils/auth.py
index 124a2e9a13b..12a291b98d2 100644
--- a/tests/e2e/utils/auth.py
+++ b/tests/e2e/utils/auth.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
from urllib.parse import urlparse
import boto3
diff --git a/tests/e2e/utils/base.py b/tests/e2e/utils/base.py
index 2a6e6032e52..f9789a4c78b 100644
--- a/tests/e2e/utils/base.py
+++ b/tests/e2e/utils/base.py
@@ -1,14 +1,15 @@
+from __future__ import annotations
+
from abc import ABC, abstractmethod
-from typing import Dict, Optional
class InfrastructureProvider(ABC):
@abstractmethod
- def create_lambda_functions(self, function_props: Optional[Dict] = None) -> Dict:
+ def create_lambda_functions(self, function_props: dict | None = None) -> dict:
pass
@abstractmethod
- def deploy(self) -> Dict[str, str]:
+ def deploy(self) -> dict[str, str]:
pass
@abstractmethod
diff --git a/tests/e2e/utils/constants.py b/tests/e2e/utils/constants.py
index 445c9f00113..9978ca2413f 100644
--- a/tests/e2e/utils/constants.py
+++ b/tests/e2e/utils/constants.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import sys
from aws_lambda_powertools import PACKAGE_PATH
diff --git a/tests/e2e/utils/data_builder/__init__.py b/tests/e2e/utils/data_builder/__init__.py
index 72c216faa76..3ef1ce262db 100644
--- a/tests/e2e/utils/data_builder/__init__.py
+++ b/tests/e2e/utils/data_builder/__init__.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
from tests.e2e.utils.data_builder.common import build_random_value, build_service_name
from tests.e2e.utils.data_builder.metrics import (
build_add_dimensions_input,
diff --git a/tests/e2e/utils/data_builder/common.py b/tests/e2e/utils/data_builder/common.py
index f28778ffed3..18c4e7707c5 100644
--- a/tests/e2e/utils/data_builder/common.py
+++ b/tests/e2e/utils/data_builder/common.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import secrets
diff --git a/tests/e2e/utils/data_builder/metrics.py b/tests/e2e/utils/data_builder/metrics.py
index 55d728c3b31..7eaecd8dbfd 100644
--- a/tests/e2e/utils/data_builder/metrics.py
+++ b/tests/e2e/utils/data_builder/metrics.py
@@ -1,18 +1,21 @@
-from typing import Dict, List, Optional
+from __future__ import annotations
-from mypy_boto3_cloudwatch.type_defs import DimensionTypeDef, MetricDataQueryTypeDef
+from typing import TYPE_CHECKING
from aws_lambda_powertools.metrics import MetricUnit
from tests.e2e.utils.data_builder.common import build_random_value
+if TYPE_CHECKING:
+ from mypy_boto3_cloudwatch.type_defs import DimensionTypeDef, MetricDataQueryTypeDef
+
def build_metric_query_data(
namespace: str,
metric_name: str,
period: int = 60,
stat: str = "Sum",
- dimensions: Optional[List[DimensionTypeDef]] = None,
-) -> List[MetricDataQueryTypeDef]:
+ dimensions: list[DimensionTypeDef] | None = None,
+) -> list[MetricDataQueryTypeDef]:
"""Create input for CloudWatch GetMetricData API call
Parameters
@@ -34,7 +37,7 @@ def build_metric_query_data(
_description_
"""
dimensions = dimensions or []
- data_query: List[MetricDataQueryTypeDef] = [
+ data_query: list[MetricDataQueryTypeDef] = [
{
"Id": metric_name.lower(),
"MetricStat": {
@@ -52,7 +55,7 @@ def build_metric_query_data(
return data_query
-def build_add_metric_input(metric_name: str, value: float, unit: str = MetricUnit.Count.value) -> Dict:
+def build_add_metric_input(metric_name: str, value: float, unit: str = MetricUnit.Count.value) -> dict:
"""Create a metric input to be used with Metrics.add_metric()
Parameters
@@ -77,7 +80,7 @@ def build_multiple_add_metric_input(
value: float,
unit: str = MetricUnit.Count.value,
quantity: int = 1,
-) -> List[Dict]:
+) -> list[dict]:
"""Create list of metrics input to be used with Metrics.add_metric()
Parameters
@@ -99,7 +102,7 @@ def build_multiple_add_metric_input(
return [{"name": metric_name, "unit": unit, "value": value} for _ in range(quantity)]
-def build_add_dimensions_input(**dimensions) -> List[DimensionTypeDef]:
+def build_add_dimensions_input(**dimensions) -> list[DimensionTypeDef]:
"""Create dimensions input to be used with either get_metrics or Metrics.add_dimension()
Parameters
diff --git a/tests/e2e/utils/data_builder/traces.py b/tests/e2e/utils/data_builder/traces.py
index e6356582a30..e123640292e 100644
--- a/tests/e2e/utils/data_builder/traces.py
+++ b/tests/e2e/utils/data_builder/traces.py
@@ -1,11 +1,13 @@
-from typing import Any, Dict, List, Optional
+from __future__ import annotations
+
+from typing import Any
def build_trace_default_query(function_name: str) -> str:
return f'service(id(name: "{function_name}"))'
-def build_put_annotations_input(**annotations: str) -> List[Dict]:
+def build_put_annotations_input(**annotations: str) -> list[dict]:
"""Create trace annotations input to be used with Tracer.put_annotation()
Parameters
@@ -21,7 +23,7 @@ def build_put_annotations_input(**annotations: str) -> List[Dict]:
return [{"key": key, "value": value} for key, value in annotations.items()]
-def build_put_metadata_input(namespace: Optional[str] = None, **metadata: Any) -> List[Dict]:
+def build_put_metadata_input(namespace: str | None = None, **metadata: Any) -> list[dict]:
"""Create trace metadata input to be used with Tracer.put_metadata()
All metadata will be under `test` namespace
diff --git a/tests/e2e/utils/data_fetcher/__init__.py b/tests/e2e/utils/data_fetcher/__init__.py
index fdd1de5c515..66e89635bbf 100644
--- a/tests/e2e/utils/data_fetcher/__init__.py
+++ b/tests/e2e/utils/data_fetcher/__init__.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
from tests.e2e.utils.data_fetcher.common import get_http_response, get_lambda_response
from tests.e2e.utils.data_fetcher.idempotency import get_ddb_idempotency_record
from tests.e2e.utils.data_fetcher.logs import get_logs
diff --git a/tests/e2e/utils/data_fetcher/idempotency.py b/tests/e2e/utils/data_fetcher/idempotency.py
index 109e6735d3b..776c68f9cf8 100644
--- a/tests/e2e/utils/data_fetcher/idempotency.py
+++ b/tests/e2e/utils/data_fetcher/idempotency.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import boto3
from retry import retry
diff --git a/tests/e2e/utils/data_fetcher/metrics.py b/tests/e2e/utils/data_fetcher/metrics.py
index 361e7bdaf5d..bf864a35497 100644
--- a/tests/e2e/utils/data_fetcher/metrics.py
+++ b/tests/e2e/utils/data_fetcher/metrics.py
@@ -1,25 +1,29 @@
+from __future__ import annotations
+
from datetime import datetime, timedelta
-from typing import List, Optional
+from typing import TYPE_CHECKING
import boto3
-from mypy_boto3_cloudwatch.client import CloudWatchClient
-from mypy_boto3_cloudwatch.type_defs import DimensionTypeDef
from retry import retry
from tests.e2e.utils.data_builder import build_metric_query_data
+if TYPE_CHECKING:
+ from mypy_boto3_cloudwatch.client import CloudWatchClient
+ from mypy_boto3_cloudwatch.type_defs import DimensionTypeDef
+
@retry(ValueError, delay=2, jitter=1.5, tries=10)
def get_metrics(
namespace: str,
start_date: datetime,
metric_name: str,
- dimensions: Optional[List[DimensionTypeDef]] = None,
- cw_client: Optional[CloudWatchClient] = None,
- end_date: Optional[datetime] = None,
+ dimensions: list[DimensionTypeDef] | None = None,
+ cw_client: CloudWatchClient | None = None,
+ end_date: datetime | None = None,
period: int = 60,
stat: str = "Sum",
-) -> List[float]:
+) -> list[float]:
"""Fetch CloudWatch Metrics
It takes into account eventual consistency with up to 10 retries and 1.5s jitter.
diff --git a/tests/e2e/utils/data_fetcher/traces.py b/tests/e2e/utils/data_fetcher/traces.py
index d4c4dd29868..f8b364fc97c 100644
--- a/tests/e2e/utils/data_fetcher/traces.py
+++ b/tests/e2e/utils/data_fetcher/traces.py
@@ -1,17 +1,19 @@
import json
from datetime import datetime, timedelta
-from typing import Any, Dict, Generator, List, Optional
+from typing import TYPE_CHECKING, Any, Dict, Generator, List, Optional
import boto3
from botocore.paginate import PageIterator
from mypy_boto3_xray.client import XRayClient
-from mypy_boto3_xray.type_defs import TraceSummaryTypeDef
from pydantic import BaseModel
from retry import retry
+if TYPE_CHECKING:
+ from mypy_boto3_xray.type_defs import TraceSummaryTypeDef
+
class TraceSubsegment(BaseModel):
- id: str # noqa: A003 VNE003 # id is a field we can't change
+ id: str # noqa: A003 # id is a field we can't change
name: str
start_time: float
end_time: float
@@ -22,7 +24,7 @@ class TraceSubsegment(BaseModel):
class TraceDocument(BaseModel):
- id: str # noqa: A003 VNE003 # id is a field we can't change
+ id: str # noqa: A003 # id is a field we can't change
name: str
start_time: float
end_time: float
diff --git a/tests/e2e/utils/infrastructure.py b/tests/e2e/utils/infrastructure.py
index 85bfd9fd41c..dc64499d14f 100644
--- a/tests/e2e/utils/infrastructure.py
+++ b/tests/e2e/utils/infrastructure.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import json
import logging
import os
@@ -5,11 +7,10 @@
import sys
import textwrap
from pathlib import Path
-from typing import Callable, Dict, Generator, Optional
+from typing import TYPE_CHECKING
from uuid import uuid4
import boto3
-import pytest
from aws_cdk import App, CfnOutput, Environment, RemovalPolicy, Stack, aws_logs
from aws_cdk.aws_lambda import (
Architecture,
@@ -29,6 +30,12 @@
)
from tests.e2e.utils.lambda_layer.powertools_layer import LocalLambdaPowertoolsLayer
+if TYPE_CHECKING:
+ from collections.abc import Callable, Generator
+
+ import pytest
+
+
logger = logging.getLogger(__name__)
@@ -39,7 +46,7 @@ def __init__(self) -> None:
self.feature_path = Path(sys.modules[self.__class__.__module__].__file__).parent # absolute path to feature
self.feature_name = self.feature_path.parts[-1].replace("_", "-") # logger, tracer, event-handler, etc.
self.stack_name = f"test{PYTHON_RUNTIME_VERSION}-{self.feature_name}-{self.RANDOM_STACK_VALUE}"
- self.stack_outputs: Dict[str, str] = {}
+ self.stack_outputs: dict[str, str] = {}
# NOTE: CDK stack account and region are tokens, we need to resolve earlier
self.session = boto3.session.Session()
@@ -56,7 +63,7 @@ def __init__(self) -> None:
self._feature_infra_file = self.feature_path / "infrastructure.py"
self._handlers_dir = self.feature_path / "handlers"
self._cdk_out_dir: Path = CDK_OUT_PATH / self.feature_name
- self._stack_outputs_file = f'{self._cdk_out_dir / "stack_outputs.json"}'
+ self._stack_outputs_file = f"{self._cdk_out_dir / 'stack_outputs.json'}"
if not self._feature_infra_file.exists():
raise FileNotFoundError(
@@ -65,9 +72,9 @@ def __init__(self) -> None:
def create_lambda_functions(
self,
- function_props: Optional[Dict] = None,
+ function_props: dict | None = None,
architecture: Architecture = Architecture.X86_64,
- ) -> Dict[str, Function]:
+ ) -> dict[str, Function]:
"""Create Lambda functions available under handlers_dir
It creates CloudFormation Outputs for every function found in PascalCase. For example,
@@ -130,7 +137,7 @@ def create_lambda_functions(
logger.debug(f"Creating functions for handlers: {handlers}")
function_settings_override = function_props or {}
- output: Dict[str, Function] = {}
+ output: dict[str, Function] = {}
for fn in handlers:
fn_name = fn.stem
@@ -164,7 +171,7 @@ def create_lambda_functions(
return output
- def deploy(self) -> Dict[str, str]:
+ def deploy(self) -> dict[str, str]:
"""Synthesize and deploy a CDK app, and return its stack outputs
NOTE: It auto-generates a temporary CDK app to benefit from CDK CLI lookup features
@@ -191,7 +198,7 @@ def delete(self) -> None:
logger.debug(f"Deleting stack: {self.stack_name}")
self.cfn.delete_stack(StackName=self.stack_name)
- def _sync_stack_name(self, stack_output: Dict):
+ def _sync_stack_name(self, stack_output: dict):
"""Synchronize initial stack name with CDK final stack name
When using `cdk synth` with context methods (`from_lookup`),
@@ -207,7 +214,7 @@ def _sync_stack_name(self, stack_output: Dict):
def _read_stack_output(self):
content = Path(self._stack_outputs_file).read_text()
- outputs: Dict = json.loads(content)
+ outputs: dict = json.loads(content)
self._sync_stack_name(stack_output=outputs)
# discard stack_name and get outputs as dict
@@ -308,7 +315,7 @@ def call_once(
task: Callable,
tmp_path_factory: pytest.TempPathFactory,
worker_id: str,
- callback: Optional[Callable] = None,
+ callback: Callable | None = None,
) -> Generator[object, None, None]:
"""Call function and serialize results once whether CPU parallelization is enabled or not
@@ -345,7 +352,7 @@ def call_once(
if cache.is_file():
callable_result = json.loads(cache.read_text())
else:
- callable_result: Dict = task()
+ callable_result: dict = task()
cache.write_text(json.dumps(callable_result))
yield callable_result
finally:
diff --git a/tests/e2e/utils/lambda_layer/base.py b/tests/e2e/utils/lambda_layer/base.py
index e38e936eefc..3a03d3bf095 100644
--- a/tests/e2e/utils/lambda_layer/base.py
+++ b/tests/e2e/utils/lambda_layer/base.py
@@ -1,5 +1,10 @@
+from __future__ import annotations
+
from abc import ABC, abstractmethod
-from pathlib import Path
+from typing import TYPE_CHECKING
+
+if TYPE_CHECKING:
+ from pathlib import Path
class BaseLocalLambdaLayer(ABC):
diff --git a/tests/e2e/utils/lambda_layer/powertools_layer.py b/tests/e2e/utils/lambda_layer/powertools_layer.py
index 2ebf9dc7e3c..4fadd94ea74 100644
--- a/tests/e2e/utils/lambda_layer/powertools_layer.py
+++ b/tests/e2e/utils/lambda_layer/powertools_layer.py
@@ -1,6 +1,7 @@
+from __future__ import annotations
+
import subprocess
-from pathlib import Path
-from typing import List
+from typing import TYPE_CHECKING
from aws_cdk.aws_lambda import Architecture
from dirhash import dirhash
@@ -9,6 +10,9 @@
from tests.e2e.utils.constants import CDK_OUT_PATH, SOURCE_CODE_ROOT_PATH
from tests.e2e.utils.lambda_layer.base import BaseLocalLambdaLayer
+if TYPE_CHECKING:
+ from pathlib import Path
+
class LocalLambdaPowertoolsLayer(BaseLocalLambdaLayer):
IGNORE_EXTENSIONS = ["pyc"]
@@ -79,5 +83,5 @@ def _resolve_platform(self, architecture: Architecture) -> str:
return self._build_platform_args(platforms)
- def _build_platform_args(self, platforms: List[str]):
+ def _build_platform_args(self, platforms: list[str]):
return " ".join([f"--platform {platform}" for platform in platforms])
diff --git a/tests/events/apiGatewayWebSocketApiConnect.json b/tests/events/apiGatewayWebSocketApiConnect.json
index 27f8794c9bd..188d5869326 100644
--- a/tests/events/apiGatewayWebSocketApiConnect.json
+++ b/tests/events/apiGatewayWebSocketApiConnect.json
@@ -19,6 +19,15 @@
"X-Forwarded-Port": ["443"],
"X-Forwarded-Proto": ["https"]
},
+ "queryStringParameters": {
+ "userId": "user123",
+ "token": "abc.def.ghi"
+ },
+ "multiValueQueryStringParameters": {
+ "userId": ["123"],
+ "token": ["abc.def.ghi"],
+ "filter": ["new", "unread"]
+ },
"requestContext": {
"routeKey": "$connect",
"eventType": "CONNECT",
diff --git a/tests/events/apiGatewayWebSocketApiDisconnect.json b/tests/events/apiGatewayWebSocketApiDisconnect.json
index f4624562ef6..4c72f44149f 100644
--- a/tests/events/apiGatewayWebSocketApiDisconnect.json
+++ b/tests/events/apiGatewayWebSocketApiDisconnect.json
@@ -11,6 +11,15 @@
"X-Forwarded-For": [""],
"x-restapi": [""]
},
+ "queryStringParameters": {
+ "userId": "user123",
+ "token": "abc.def.ghi"
+ },
+ "multiValueQueryStringParameters": {
+ "userId": ["123"],
+ "token": ["abc.def.ghi"],
+ "filter": ["new", "unread"]
+ },
"requestContext": {
"routeKey": "$disconnect",
"disconnectStatusCode": 1005,
diff --git a/tests/events/appSyncEventsEvent.json b/tests/events/appSyncEventsEvent.json
new file mode 100644
index 00000000000..a6236c6e4df
--- /dev/null
+++ b/tests/events/appSyncEventsEvent.json
@@ -0,0 +1,70 @@
+{
+ "identity":"None",
+ "result":"None",
+ "request":{
+ "headers": {
+ "x-forwarded-for": "1.1.1.1, 2.2.2.2",
+ "cloudfront-viewer-country": "US",
+ "cloudfront-is-tablet-viewer": "false",
+ "via": "2.0 xxxxxxxxxxxxxxxx.cloudfront.net (CloudFront)",
+ "cloudfront-forwarded-proto": "https",
+ "origin": "https://us-west-1.console.aws.amazon.com",
+ "content-length": "217",
+ "accept-language": "en-US,en;q=0.9",
+ "host": "xxxxxxxxxxxxxxxx.appsync-api.us-west-1.amazonaws.com",
+ "x-forwarded-proto": "https",
+ "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.83 Safari/537.36",
+ "accept": "*/*",
+ "cloudfront-is-mobile-viewer": "false",
+ "cloudfront-is-smarttv-viewer": "false",
+ "accept-encoding": "gzip, deflate, br",
+ "referer": "https://us-west-1.console.aws.amazon.com/appsync/home?region=us-west-1",
+ "content-type": "application/json",
+ "sec-fetch-mode": "cors",
+ "x-amz-cf-id": "3aykhqlUwQeANU-HGY7E_guV5EkNeMMtwyOgiA==",
+ "x-amzn-trace-id": "Root=1-5f512f51-fac632066c5e848ae714",
+ "authorization": "eyJraWQiOiJScWFCSlJqYVJlM0hrSnBTUFpIcVRXazNOW...",
+ "sec-fetch-dest": "empty",
+ "x-amz-user-agent": "AWS-Console-AppSync/",
+ "cloudfront-is-desktop-viewer": "true",
+ "sec-fetch-site": "cross-site",
+ "x-forwarded-port": "443"
+ },
+ "domainName":"None"
+ },
+ "info":{
+ "channel":{
+ "path":"/default/channel",
+ "segments":[
+ "default",
+ "channel"
+ ]
+ },
+ "channelNamespace":{
+ "name":"default"
+ },
+ "operation":"PUBLISH"
+ },
+ "error":"None",
+ "prev":"None",
+ "stash":{
+
+ },
+ "outErrors":[
+
+ ],
+ "events":[
+ {
+ "payload":{
+ "event_1":"data_1"
+ },
+ "id":"1"
+ },
+ {
+ "payload":{
+ "event_2":"data_2"
+ },
+ "id":"2"
+ }
+ ]
+}
diff --git a/tests/events/appsync_resolver_event.json b/tests/events/appsync_resolver_event.json
new file mode 100644
index 00000000000..1b56d4dc93c
--- /dev/null
+++ b/tests/events/appsync_resolver_event.json
@@ -0,0 +1,78 @@
+{
+ "typeName": "Merchant",
+ "fieldName": "locations",
+ "arguments": {
+ "page": 2,
+ "size": 1,
+ "name": "value"
+ },
+ "identity": {
+ "claims": {
+ "sub": "07920713-4526-4642-9c88-2953512de441",
+ "iss": "https://cognito-idp.us-east-1.amazonaws.com/us-east-1_POOL_ID",
+ "aud": "58rc9bf5kkti90ctmvioppukm9",
+ "event_id": "7f4c9383-abf6-48b7-b821-91643968b755",
+ "token_use": "id",
+ "auth_time": 1615366261,
+ "name": "Michael Brewer",
+ "exp": 1615369861,
+ "iat": 1615366261
+ },
+ "defaultAuthStrategy": "ALLOW",
+ "groups": null,
+ "issuer": "https://cognito-idp.us-east-1.amazonaws.com/us-east-1_POOL_ID",
+ "sourceIp": ["11.215.2.22"],
+ "sub": "07920713-4526-4642-9c88-2953512de441",
+ "username": "mike"
+ },
+ "source": {
+ "name": "Value",
+ "nested": {
+ "name": "value",
+ "list": []
+ }
+ },
+ "request": {
+ "headers": {
+ "x-forwarded-for": "11.215.2.22, 64.44.173.11",
+ "cloudfront-viewer-country": "US",
+ "cloudfront-is-tablet-viewer": "false",
+ "via": "2.0 SOMETHING.cloudfront.net (CloudFront)",
+ "cloudfront-forwarded-proto": "https",
+ "origin": "https://console.aws.amazon.com",
+ "content-length": "156",
+ "accept-language": "en-US,en;q=0.9",
+ "host": "SOMETHING.appsync-api.us-east-1.amazonaws.com",
+ "x-forwarded-proto": "https",
+ "sec-gpc": "1",
+ "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)",
+ "accept": "*/*",
+ "cloudfront-is-mobile-viewer": "false",
+ "cloudfront-is-smarttv-viewer": "false",
+ "accept-encoding": "gzip, deflate, br",
+ "referer": "https://console.aws.amazon.com/",
+ "content-type": "application/json",
+ "sec-fetch-mode": "cors",
+ "x-amz-cf-id": "Fo5VIuvP6V6anIEt62WzFDCK45mzM4yEdpt5BYxOl9OFqafd-WR0cA==",
+ "x-amzn-trace-id": "Root=1-60488877-0b0c4e6727ab2a1c545babd0",
+ "authorization": "AUTH-HEADER",
+ "sec-fetch-dest": "empty",
+ "x-amz-user-agent": "AWS-Console-AppSync/",
+ "cloudfront-is-desktop-viewer": "true",
+ "sec-fetch-site": "cross-site",
+ "x-forwarded-port": "443"
+ },
+ "domainName": "SOMETHING.appsync-api.us-east-1.amazonaws.com"
+ },
+ "prev": {
+ "result": {}
+ },
+ "info": {
+ "selectionSetList": ["id", "field1", "field2"],
+ "selectionSetGraphQL": "{\n id\n field1\n field2\n}",
+ "parentTypeName": "Merchant",
+ "fieldName": "locations",
+ "variables": {}
+ },
+ "stash": {}
+ }
\ No newline at end of file
diff --git a/tests/events/kafkaEventMsk.json b/tests/events/kafkaEventMsk.json
index 5a35b89680a..f0c7d36c2cf 100644
--- a/tests/events/kafkaEventMsk.json
+++ b/tests/events/kafkaEventMsk.json
@@ -29,6 +29,57 @@
]
}
]
+ },
+ {
+ "topic":"mytopic",
+ "partition":0,
+ "offset":15,
+ "timestamp":1545084650987,
+ "timestampType":"CREATE_TIME",
+ "value":"eyJrZXkiOiJ2YWx1ZSJ9",
+ "headers":[
+ {
+ "headerKey":[
+ 104,
+ 101,
+ 97,
+ 100,
+ 101,
+ 114,
+ 86,
+ 97,
+ 108,
+ 117,
+ 101
+ ]
+ }
+ ]
+ },
+ {
+ "topic":"mytopic",
+ "partition":0,
+ "offset":15,
+ "timestamp":1545084650987,
+ "timestampType":"CREATE_TIME",
+ "key": null,
+ "value":"eyJrZXkiOiJ2YWx1ZSJ9",
+ "headers":[
+ {
+ "headerKey":[
+ 104,
+ 101,
+ 97,
+ 100,
+ 101,
+ 114,
+ 86,
+ 97,
+ 108,
+ 117,
+ 101
+ ]
+ }
+ ]
}
]
}
diff --git a/tests/events/kafkaEventSelfManaged.json b/tests/events/kafkaEventSelfManaged.json
index eaf0bf34cae..f99ca35cc48 100644
--- a/tests/events/kafkaEventSelfManaged.json
+++ b/tests/events/kafkaEventSelfManaged.json
@@ -28,6 +28,57 @@
]
}
]
+ },
+ {
+ "topic": "mytopic",
+ "partition": 0,
+ "offset": 15,
+ "timestamp": 1545084650987,
+ "timestampType": "CREATE_TIME",
+ "value": "eyJrZXkiOiJ2YWx1ZSJ9",
+ "headers": [
+ {
+ "headerKey": [
+ 104,
+ 101,
+ 97,
+ 100,
+ 101,
+ 114,
+ 86,
+ 97,
+ 108,
+ 117,
+ 101
+ ]
+ }
+ ]
+ },
+ {
+ "topic": "mytopic",
+ "partition": 0,
+ "offset": 15,
+ "timestamp": 1545084650987,
+ "timestampType": "CREATE_TIME",
+ "key": null,
+ "value": "eyJrZXkiOiJ2YWx1ZSJ9",
+ "headers": [
+ {
+ "headerKey": [
+ 104,
+ 101,
+ 97,
+ 100,
+ 101,
+ 114,
+ 86,
+ 97,
+ 108,
+ 117,
+ 101
+ ]
+ }
+ ]
}
]
}
diff --git a/tests/functional/batch/required_dependencies/test_utilities_batch.py b/tests/functional/batch/required_dependencies/test_utilities_batch.py
index 4c91dd54a1e..0dfee8e6e0a 100644
--- a/tests/functional/batch/required_dependencies/test_utilities_batch.py
+++ b/tests/functional/batch/required_dependencies/test_utilities_batch.py
@@ -1,7 +1,9 @@
+from __future__ import annotations
+
import json
import uuid
from random import randint
-from typing import Any, Awaitable, Callable, Dict
+from typing import TYPE_CHECKING, Any
import pytest
@@ -16,9 +18,6 @@
process_partial_response,
)
from aws_lambda_powertools.utilities.batch.exceptions import BatchProcessingError, UnexpectedBatchTypeError
-from aws_lambda_powertools.utilities.data_classes.dynamo_db_stream_event import (
- DynamoDBRecord,
-)
from aws_lambda_powertools.utilities.data_classes.kinesis_stream_event import (
KinesisStreamRecord,
)
@@ -26,6 +25,13 @@
from aws_lambda_powertools.warnings import PowertoolsDeprecationWarning
from tests.functional.utils import b64_to_str, str_to_b64
+if TYPE_CHECKING:
+ from collections.abc import Awaitable, Callable
+
+ from aws_lambda_powertools.utilities.data_classes.dynamo_db_stream_event import (
+ DynamoDBRecord,
+ )
+
@pytest.fixture(scope="module")
def sqs_event_fifo_factory() -> Callable:
@@ -169,7 +175,7 @@ def handler(record: DynamoDBRecord):
@pytest.fixture(scope="module")
def order_event_factory() -> Callable:
- def factory(item: Dict) -> str:
+ def factory(item: dict[str, Any]) -> str:
return json.dumps({"item": item})
return factory
diff --git a/tests/functional/data_masking/_aws_encryption_sdk/test_aws_encryption_sdk.py b/tests/functional/data_masking/_aws_encryption_sdk/test_aws_encryption_sdk.py
index 63aca871e44..039e302bf93 100644
--- a/tests/functional/data_masking/_aws_encryption_sdk/test_aws_encryption_sdk.py
+++ b/tests/functional/data_masking/_aws_encryption_sdk/test_aws_encryption_sdk.py
@@ -1,7 +1,9 @@
+from __future__ import annotations
+
import base64
import functools
import json
-from typing import Any, Callable, Union
+from typing import TYPE_CHECKING, Any
import pytest
from aws_encryption_sdk.identifiers import Algorithm
@@ -13,16 +15,21 @@
AWSEncryptionSDKProvider,
)
+if TYPE_CHECKING:
+ from collections.abc import Callable
+
+JSON_DUMPS_CALL = functools.partial(json.dumps, ensure_ascii=False)
+
class FakeEncryptionKeyProvider(BaseProvider):
def __init__(
self,
- json_serializer: Callable = functools.partial(json.dumps, ensure_ascii=False),
+ json_serializer: Callable = JSON_DUMPS_CALL,
json_deserializer: Callable = json.loads,
) -> None:
super().__init__(json_serializer, json_deserializer)
- def encrypt(self, data: Union[bytes, str], **kwargs) -> str:
+ def encrypt(self, data: bytes | str, **kwargs) -> str:
encoded_data: str = self.json_serializer(data)
ciphertext = base64.b64encode(encoded_data.encode("utf-8")).decode()
return ciphertext
diff --git a/tests/functional/data_masking/_pydantic/test_data_masking_with_pydantic.py b/tests/functional/data_masking/_pydantic/test_data_masking_with_pydantic.py
new file mode 100644
index 00000000000..40ccf5280f8
--- /dev/null
+++ b/tests/functional/data_masking/_pydantic/test_data_masking_with_pydantic.py
@@ -0,0 +1,219 @@
+from __future__ import annotations
+
+import dataclasses
+
+import pytest
+from pydantic import BaseModel
+
+from aws_lambda_powertools.utilities.data_masking.base import DataMasking, prepare_data
+from aws_lambda_powertools.utilities.data_masking.constants import DATA_MASKING_STRING
+
+
+@pytest.fixture
+def data_masker() -> DataMasking:
+ return DataMasking()
+
+
+def test_prepare_data_primitive():
+ assert prepare_data("hello") == "hello"
+ assert prepare_data(123) == 123
+ assert prepare_data(3.14) == pytest.approx(3.14)
+ assert prepare_data(True) is True
+ assert prepare_data(None) is None
+
+
+def test_prepare_data_dict_no_change():
+ data = {"x": "y", "z": 10}
+ result = prepare_data(data)
+ assert isinstance(result, dict)
+ assert result == data
+
+
+def test_prepare_data_list():
+ data = [1, "a", {"b": 2}]
+ result = prepare_data(data)
+ assert isinstance(result, list)
+ assert result == [1, "a", {"b": 2}]
+
+
+def test_prepare_data_tuple():
+ data = (1, 2, {"a": 3})
+ result = prepare_data(data)
+ assert isinstance(result, tuple)
+ assert result[2]["a"] == 3
+
+
+def test_prepare_data_set():
+ data = {1, 2, 3}
+ result = prepare_data(data)
+ assert isinstance(result, set)
+ assert result == {1, 2, 3}
+
+
+def test_prepare_data_dataclass():
+ @dataclasses.dataclass
+ class MyDataClass:
+ name: str
+ age: int
+
+ instance = MyDataClass(name="delta", age=50)
+ result = prepare_data(instance)
+ assert isinstance(result, dict)
+ assert result["name"] == "delta"
+ assert result["age"] == 50
+
+
+def test_prepare_data_pydantic():
+ class MyPydanticModel(BaseModel):
+ name: str
+ age: int
+
+ instance = MyPydanticModel(name="alpha", age=30)
+ result = prepare_data(instance)
+ assert isinstance(result, dict)
+ assert result["name"] == "alpha"
+ assert result["age"] == 30
+
+
+def test_prepare_data_custom_class_with_dict():
+ class MyCustom:
+ def __init__(self, name, age):
+ self.name = name
+ self.age = age
+
+ def dict(self):
+ return {"name": self.name, "age": self.age}
+
+ instance = MyCustom("beta", 40)
+ result = prepare_data(instance)
+ assert isinstance(result, dict)
+ assert result["name"] == "beta"
+ assert result["age"] == 40
+
+
+def test_prepare_data_fallback_dict_via_dunder():
+ class WithDict:
+ def __init__(self, value):
+ self.value = value
+
+ instance = WithDict(100)
+ result = prepare_data(instance)
+ assert isinstance(result, dict)
+ assert result["value"] == 100
+
+
+def test_prepare_data_nested_structure():
+ @dataclasses.dataclass
+ class NestedDC:
+ x: int
+ y: str
+
+ class NestedPM(BaseModel):
+ a: int
+ b: str
+
+ class NestedCustom:
+ def __init__(self, z):
+ self.z = z
+
+ def dict(self):
+ return {"z": self.z}
+
+ data = {
+ "dc": NestedDC(x=10, y="foo"),
+ "pm": NestedPM(a=5, b="bar"),
+ "custom": NestedCustom(z="baz"),
+ "nested": {"list": [NestedDC(x=1, y="inner"), NestedPM(a=2, b="inner2")]},
+ }
+ result = prepare_data(data)
+ assert result["dc"]["x"] == 10
+ assert result["dc"]["y"] == "foo"
+ assert result["pm"]["a"] == 5
+ assert result["pm"]["b"] == "bar"
+ assert result["custom"]["z"] == "baz"
+ assert result["nested"]["list"][0]["y"] == "inner"
+ assert result["nested"]["list"][1]["a"] == 2
+
+
+def test_prepare_data_circular_reference():
+ data = {"a": 1}
+ data["self"] = data
+ result = prepare_data(data)
+ assert result["a"] == 1
+ assert "self" in result
+
+
+class MyPydanticModel(BaseModel):
+ name: str
+ age: int
+
+
+@dataclasses.dataclass
+class MyDataClass:
+ name: str
+ age: int
+
+
+class MyCustomClass:
+ def __init__(self, name, age):
+ self.name = name
+ self.age = age
+
+ def dict(self):
+ return {"name": self.name, "age": self.age}
+
+
+def test_erase_on_pydantic_model(data_masker):
+ instance = MyPydanticModel(name="powertools", age=5)
+ result = data_masker.erase(instance, fields=["age"])
+ assert isinstance(result, dict)
+ assert result["age"] == DATA_MASKING_STRING
+ assert result["name"] == "powertools"
+
+
+def test_erase_on_dataclass(data_masker):
+ instance = MyDataClass(name="powertools", age=5)
+ result = data_masker.erase(instance, fields=["age"])
+ assert isinstance(result, dict)
+ assert result["age"] == DATA_MASKING_STRING
+ assert result["name"] == "powertools"
+
+
+def test_erase_on_custom_class(data_masker):
+ instance = MyCustomClass("powertools", 5)
+ result = data_masker.erase(instance, fields=["age"])
+ assert isinstance(result, dict)
+ assert result["age"] == DATA_MASKING_STRING
+ assert result["name"] == "powertools"
+
+
+def test_erase_on_nested_complex_structure(data_masker):
+ @dataclasses.dataclass
+ class NestedDC:
+ value: int
+
+ class NestedPM(BaseModel):
+ value: int
+
+ class MyCustomClass:
+ def __init__(self, name, age):
+ self.name = name
+ self.age = age
+
+ def dict(self):
+ return {"name": self.name, "age": self.age}
+
+ data = {
+ "pydantic": NestedPM(value=10),
+ "dataclass": NestedDC(value=20),
+ "custom": MyCustomClass("example", 30),
+ "plain_dict": {"value": 40},
+ "list": [NestedPM(value=50), {"value": 60}],
+ }
+ result = data_masker.erase(data, fields=["$..value"])
+ assert result["pydantic"]["value"] == DATA_MASKING_STRING
+ assert result["dataclass"]["value"] == DATA_MASKING_STRING
+ assert result["custom"] == {"name": "example", "age": 30}
+ assert result["plain_dict"]["value"] == DATA_MASKING_STRING
+ assert result["list"][0]["value"] == DATA_MASKING_STRING
+ assert result["list"][1]["value"] == DATA_MASKING_STRING
diff --git a/tests/functional/data_masking/conftest.py b/tests/functional/data_masking/conftest.py
index f73ccca4113..15ce865abfa 100644
--- a/tests/functional/data_masking/conftest.py
+++ b/tests/functional/data_masking/conftest.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
from pytest_socket import disable_socket
diff --git a/tests/functional/data_masking/required_dependencies/test_erase_data_masking.py b/tests/functional/data_masking/required_dependencies/test_erase_data_masking.py
index 12ffd054376..6aac48927da 100644
--- a/tests/functional/data_masking/required_dependencies/test_erase_data_masking.py
+++ b/tests/functional/data_masking/required_dependencies/test_erase_data_masking.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import json
import pytest
diff --git a/tests/functional/event_handler/_pydantic/conftest.py b/tests/functional/event_handler/_pydantic/conftest.py
index 6dd0b6d14a0..6ba2b95def9 100644
--- a/tests/functional/event_handler/_pydantic/conftest.py
+++ b/tests/functional/event_handler/_pydantic/conftest.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import json
import fastjsonschema
diff --git a/tests/functional/event_handler/_pydantic/test_api_gateway.py b/tests/functional/event_handler/_pydantic/test_api_gateway.py
index dcd05c4f1f7..ce3fd89e864 100644
--- a/tests/functional/event_handler/_pydantic/test_api_gateway.py
+++ b/tests/functional/event_handler/_pydantic/test_api_gateway.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
from pydantic import BaseModel
from aws_lambda_powertools.event_handler import content_types
diff --git a/tests/functional/event_handler/_pydantic/test_bedrock_agent.py b/tests/functional/event_handler/_pydantic/test_bedrock_agent.py
index 6dcc55c2da5..eaa666b3d36 100644
--- a/tests/functional/event_handler/_pydantic/test_bedrock_agent.py
+++ b/tests/functional/event_handler/_pydantic/test_bedrock_agent.py
@@ -200,3 +200,19 @@ def handler() -> Optional[Dict]:
# THEN the schema must be a valid 3.0.3 version
assert openapi30_schema(schema)
assert schema.get("openapi") == "3.0.3"
+
+
+def test_bedrock_resolver_with_openapi_extensions():
+ # GIVEN BedrockAgentResolver is initialized with enable_validation=True
+ app = BedrockAgentResolver(enable_validation=True)
+
+ # WHEN we have a simple handler with openapi extension
+ @app.get("/", description="Testing", openapi_extensions={"x-requireConfirmation": "ENABLED"})
+ def handler() -> Optional[Dict]:
+ pass
+
+ # WHEN we get the schema
+ schema = json.loads(app.get_openapi_json_schema())
+
+ # THEN the OpenAPI schema must contain the "x-requireConfirmation" extension at the operation level
+ assert schema["paths"]["/"]["get"]["x-requireConfirmation"] == "ENABLED"
diff --git a/tests/functional/event_handler/_pydantic/test_openapi_config.py b/tests/functional/event_handler/_pydantic/test_openapi_config.py
index 9fc2dd1ce7b..f66a82c85d8 100644
--- a/tests/functional/event_handler/_pydantic/test_openapi_config.py
+++ b/tests/functional/event_handler/_pydantic/test_openapi_config.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import json
from aws_lambda_powertools.event_handler import APIGatewayRestResolver
@@ -26,7 +28,6 @@ def handler():
def test_prioritize_direct_parameters_over_stored_configuration():
-
# GIVEN
stored_config = {
"title": "Stored API Title",
diff --git a/tests/functional/event_handler/_pydantic/test_openapi_encoders.py b/tests/functional/event_handler/_pydantic/test_openapi_encoders.py
index 01a595fe810..beac1764064 100644
--- a/tests/functional/event_handler/_pydantic/test_openapi_encoders.py
+++ b/tests/functional/event_handler/_pydantic/test_openapi_encoders.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import math
from collections import deque
from dataclasses import dataclass
diff --git a/tests/functional/event_handler/_pydantic/test_openapi_extensions.py b/tests/functional/event_handler/_pydantic/test_openapi_extensions.py
index 2f0552ffc4c..e7b64cba38b 100644
--- a/tests/functional/event_handler/_pydantic/test_openapi_extensions.py
+++ b/tests/functional/event_handler/_pydantic/test_openapi_extensions.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import json
from aws_lambda_powertools.event_handler.api_gateway import APIGatewayRestResolver, Router
diff --git a/tests/functional/event_handler/_pydantic/test_openapi_params.py b/tests/functional/event_handler/_pydantic/test_openapi_params.py
index 5bcda896858..fdaf23c5a0b 100644
--- a/tests/functional/event_handler/_pydantic/test_openapi_params.py
+++ b/tests/functional/event_handler/_pydantic/test_openapi_params.py
@@ -278,7 +278,7 @@ class User(BaseModel):
@app.get("/")
def handler() -> User:
- return User(name="Ruben Fonseca")
+ return User(name="Powertools")
schema = app.get_openapi_schema()
assert len(schema.paths.keys()) == 1
diff --git a/tests/functional/event_handler/_pydantic/test_openapi_responses.py b/tests/functional/event_handler/_pydantic/test_openapi_responses.py
index c2ab8008b5c..0ac24dcc96b 100644
--- a/tests/functional/event_handler/_pydantic/test_openapi_responses.py
+++ b/tests/functional/event_handler/_pydantic/test_openapi_responses.py
@@ -170,3 +170,51 @@ def handler() -> Response[Union[User, Order]]:
assert 202 in responses.keys()
assert responses[202].description == "202 Response"
assert responses[202].content["application/json"].schema_.ref == "#/components/schemas/Order"
+
+
+def test_openapi_route_with_custom_response_validation():
+ app = APIGatewayRestResolver(enable_validation=True)
+
+ @app.get("/", custom_response_validation_http_code=418)
+ def handler():
+ return {"message": "hello world"}
+
+ schema = app.get_openapi_schema()
+ responses = schema.paths["/"].get.responses
+ assert 418 in responses
+ assert responses[418].description == "Response Validation Error"
+
+
+def test_openapi_resolver_with_custom_response_validation():
+ app = APIGatewayRestResolver(enable_validation=True, response_validation_error_http_code=418)
+
+ @app.get("/")
+ def handler():
+ return {"message": "hello world"}
+
+ schema = app.get_openapi_schema()
+ responses = schema.paths["/"].get.responses
+ assert 418 in responses
+ assert responses[418].description == "Response Validation Error"
+
+
+def test_openapi_route_and_resolver_with_custom_response_validation():
+ app = APIGatewayRestResolver(enable_validation=True, response_validation_error_http_code=417)
+
+ @app.get("/", custom_response_validation_http_code=418)
+ def handler():
+ return {"message": "hello world"}
+
+ @app.get("/hi")
+ def another_handler():
+ return {"message": "hello world"}
+
+ schema = app.get_openapi_schema()
+ responses_with_route_response_validation = schema.paths["/"].get.responses
+ responses_with_resolver_response_validation = schema.paths["/hi"].get.responses
+ assert 418 in responses_with_route_response_validation
+ assert 417 not in responses_with_route_response_validation
+ assert responses_with_route_response_validation[418].description == "Response Validation Error"
+ assert 417 in responses_with_resolver_response_validation
+ assert 418 not in responses_with_resolver_response_validation
+ assert responses_with_resolver_response_validation[417].description == "Response Validation Error"
diff --git a/tests/functional/event_handler/_pydantic/test_openapi_security.py b/tests/functional/event_handler/_pydantic/test_openapi_security.py
index 9f7cc1c536d..0f52af767f7 100644
--- a/tests/functional/event_handler/_pydantic/test_openapi_security.py
+++ b/tests/functional/event_handler/_pydantic/test_openapi_security.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import pytest
from aws_lambda_powertools.event_handler import APIGatewayRestResolver
diff --git a/tests/functional/event_handler/_pydantic/test_openapi_security_schemes.py b/tests/functional/event_handler/_pydantic/test_openapi_security_schemes.py
index 2a123b75be5..ef49a0fe012 100644
--- a/tests/functional/event_handler/_pydantic/test_openapi_security_schemes.py
+++ b/tests/functional/event_handler/_pydantic/test_openapi_security_schemes.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
from aws_lambda_powertools.event_handler import APIGatewayRestResolver
from aws_lambda_powertools.event_handler.openapi.models import (
APIKey,
diff --git a/tests/functional/event_handler/_pydantic/test_openapi_servers.py b/tests/functional/event_handler/_pydantic/test_openapi_servers.py
index a1ae70a1237..a15fc66e4b1 100644
--- a/tests/functional/event_handler/_pydantic/test_openapi_servers.py
+++ b/tests/functional/event_handler/_pydantic/test_openapi_servers.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
from aws_lambda_powertools.event_handler.api_gateway import APIGatewayRestResolver
from aws_lambda_powertools.event_handler.openapi.models import Server
diff --git a/tests/functional/event_handler/_pydantic/test_openapi_swagger.py b/tests/functional/event_handler/_pydantic/test_openapi_swagger.py
index 8cb001f513f..b3a40e0dc8f 100644
--- a/tests/functional/event_handler/_pydantic/test_openapi_swagger.py
+++ b/tests/functional/event_handler/_pydantic/test_openapi_swagger.py
@@ -1,6 +1,7 @@
+from __future__ import annotations
+
import json
import warnings
-from typing import Dict
import pytest
@@ -73,7 +74,7 @@ def test_openapi_swagger_json_view_with_default_path():
assert result["statusCode"] == 200
assert result["multiValueHeaders"]["Content-Type"] == ["application/json"]
- assert isinstance(json.loads(result["body"]), Dict)
+ assert isinstance(json.loads(result["body"]), dict)
assert "OpenAPI JSON View" in result["body"]
@@ -87,7 +88,7 @@ def test_openapi_swagger_json_view_with_custom_path():
assert result["statusCode"] == 200
assert result["multiValueHeaders"]["Content-Type"] == ["application/json"]
- assert isinstance(json.loads(result["body"]), Dict)
+ assert isinstance(json.loads(result["body"]), dict)
assert "OpenAPI JSON View" in result["body"]
diff --git a/tests/functional/event_handler/_pydantic/test_openapi_tags.py b/tests/functional/event_handler/_pydantic/test_openapi_tags.py
index daa30b193ff..32af3ecde1b 100644
--- a/tests/functional/event_handler/_pydantic/test_openapi_tags.py
+++ b/tests/functional/event_handler/_pydantic/test_openapi_tags.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
from aws_lambda_powertools.event_handler import APIGatewayRestResolver
from aws_lambda_powertools.event_handler.openapi.models import Tag
diff --git a/tests/functional/event_handler/_pydantic/test_openapi_validation_middleware.py b/tests/functional/event_handler/_pydantic/test_openapi_validation_middleware.py
index 4103a301020..c1cc0462bf7 100644
--- a/tests/functional/event_handler/_pydantic/test_openapi_validation_middleware.py
+++ b/tests/functional/event_handler/_pydantic/test_openapi_validation_middleware.py
@@ -1378,3 +1378,144 @@ def test_custom_response_validation_error_bad_http_code(response_validation_erro
str(exception_info.value)
== f"'{response_validation_error_http_code}' must be an integer representing an HTTP status code."
)
+
+
+def test_custom_route_response_validation_error_custom_route_and_app_with_default_validation(gw_event):
+ # GIVEN an APIGatewayRestResolver with validation enabled
+ app = APIGatewayRestResolver(enable_validation=True)
+
+ class Model(BaseModel):
+ name: str
+ age: int
+
+ @app.get("/incomplete_model_not_allowed")
+ def handler_incomplete_model_not_allowed() -> Model:
+ return {"age": 18} # type: ignore
+
+ # HAVING route with custom response validation error
+ @app.get(
+ "/custom_incomplete_model_not_allowed",
+ custom_response_validation_http_code=500,
+ )
+ def handler_custom_route_response_validation_error() -> Model:
+ return {"age": 18} # type: ignore
+
+ # WHEN returning incomplete model for a non-Optional type
+ gw_event["path"] = "/incomplete_model_not_allowed"
+ result = app(gw_event, {})
+
+ gw_event["path"] = "/custom_incomplete_model_not_allowed"
+ custom_result = app(gw_event, {})
+
+ # THEN it must return a validation error with the custom status code provided
+ assert result["statusCode"] == 422
+ assert custom_result["statusCode"] == 500
+ assert json.loads(result["body"])["detail"] == json.loads(custom_result["body"])["detail"]
+
+
+def test_custom_route_response_validation_error_sanitized_response(gw_event):
+ # GIVEN an APIGatewayRestResolver with custom response validation enabled
+ app = APIGatewayRestResolver(enable_validation=True)
+
+ class Model(BaseModel):
+ name: str
+ age: int
+
+ @app.get(
+ "/custom_incomplete_model_not_allowed",
+ custom_response_validation_http_code=422,
+ )
+ def handler_custom_route_response_validation_error() -> Model:
+ return {"age": 18} # type: ignore
+
+ # HAVING a sanitized response validation error response
+ @app.exception_handler(ResponseValidationError)
+ def handle_response_validation_error(ex: ResponseValidationError):
+ return Response(
+ status_code=500,
+ body="Unexpected response.",
+ )
+
+ # WHEN returning incomplete model for a non-Optional type
+ gw_event["path"] = "/custom_incomplete_model_not_allowed"
+ result = app(gw_event, {})
+
+ # THEN it must return the sanitized response
+ assert result["statusCode"] == 500
+ assert result["body"] == "Unexpected response."
+
+
+def test_custom_route_response_validation_error_with_app_custom_response_validation(gw_event):
+ # GIVEN an APIGatewayRestResolver with validation and custom response validation enabled
+ app = APIGatewayRestResolver(enable_validation=True, response_validation_error_http_code=500)
+
+ class Model(BaseModel):
+ name: str
+ age: int
+
+ # HAVING a route with custom response validation
+ @app.get(
+ "/custom_incomplete_model_not_allowed",
+ custom_response_validation_http_code=422,
+ )
+ def handler_custom_route_response_validation_error() -> Model:
+ return {"age": 18} # type: ignore
+
+ # WHEN returning incomplete model for a non-Optional type on route with custom response validation
+ gw_event["path"] = "/custom_incomplete_model_not_allowed"
+ result = app(gw_event, {})
+
+ # THEN route's custom response validation must take precedence over the app's.
+ assert result["statusCode"] == 422
+ body = json.loads(result["body"])
+ assert body["detail"][0]["type"] == "missing"
+ assert body["detail"][0]["loc"] == ["response", "name"]
+
+
+def test_custom_route_response_validation_error_no_app_validation():
+ # GIVEN an APIGatewayRestResolver with validation not enabled
+ with pytest.raises(ValueError) as exception_info:
+ app = APIGatewayRestResolver()
+
+ class Model(BaseModel):
+ name: str
+ age: int
+
+ # HAVING a route with custom response validation http code
+ @app.get(
+ "/custom_incomplete_model_not_allowed",
+ custom_response_validation_http_code=422,
+ )
+ def handler_custom_route_response_validation_error() -> Model:
+ return {"age": 18} # type: ignore
+
+ # THEN it must raise ValueError describing the issue
+ assert (
+ str(exception_info.value)
+ == "'custom_response_validation_http_code' cannot be set for route when enable_validation is False on resolver."
+ )
+
+
+@pytest.mark.parametrize("response_validation_error_http_code", [(20), ("hi"), (1.21), (True), (False)])
+def test_custom_route_response_validation_error_bad_http_code(response_validation_error_http_code):
+ # GIVEN an APIGatewayRestResolver with validation enabled
+ with pytest.raises(ValueError) as exception_info:
+ app = APIGatewayRestResolver(enable_validation=True)
+
+ class Model(BaseModel):
+ name: str
+ age: int
+
+ # HAVING a route with custom response validation which is not a valid HTTP code
+ @app.get(
+ "/custom_incomplete_model_not_allowed",
+ custom_response_validation_http_code=response_validation_error_http_code,
+ )
+ def handler_custom_route_response_validation_error() -> Model:
+ return {"age": 18} # type: ignore
+
+ # THEN it must raise ValueError describing the issue
+ assert (
+ str(exception_info.value)
+ == f"'{response_validation_error_http_code}' must be an integer representing an HTTP status code or an enum of type HTTPStatus." # noqa: E501
+ )
diff --git a/tests/functional/event_handler/_pydantic/test_openapi_with_pep563.py b/tests/functional/event_handler/_pydantic/test_openapi_with_pep563.py
index 1855aef45e2..35ce00b8482 100644
--- a/tests/functional/event_handler/_pydantic/test_openapi_with_pep563.py
+++ b/tests/functional/event_handler/_pydantic/test_openapi_with_pep563.py
@@ -1,7 +1,7 @@
from __future__ import annotations
from pydantic import BaseModel, Field
-from typing_extensions import Annotated
+from typing_extensions import Annotated # noqa: TC002
from aws_lambda_powertools.event_handler.api_gateway import APIGatewayRestResolver
from aws_lambda_powertools.event_handler.openapi.models import (
@@ -59,7 +59,6 @@ def handler(
def test_openapi_with_pep563_and_output_model():
-
app = APIGatewayRestResolver()
@app.get("/")
@@ -89,7 +88,6 @@ def handler() -> Todo:
def test_openapi_with_pep563_and_annotated_body():
-
app = APIGatewayRestResolver()
@app.post("/todo")
diff --git a/tests/functional/event_handler/required_dependencies/appsync/test_appsync_batch_resolvers.py b/tests/functional/event_handler/required_dependencies/appsync/test_appsync_batch_resolvers.py
index 59c5ec08a15..2466ac6d6a3 100644
--- a/tests/functional/event_handler/required_dependencies/appsync/test_appsync_batch_resolvers.py
+++ b/tests/functional/event_handler/required_dependencies/appsync/test_appsync_batch_resolvers.py
@@ -1,15 +1,19 @@
-from typing import List, Optional
+from __future__ import annotations
+
+from typing import TYPE_CHECKING
import pytest
from aws_lambda_powertools.event_handler import AppSyncResolver
from aws_lambda_powertools.event_handler.graphql_appsync.exceptions import InvalidBatchResponse, ResolverNotFoundError
from aws_lambda_powertools.event_handler.graphql_appsync.router import Router
-from aws_lambda_powertools.utilities.data_classes import AppSyncResolverEvent
from aws_lambda_powertools.utilities.typing import LambdaContext
from aws_lambda_powertools.warnings import PowertoolsUserWarning
from tests.functional.utils import load_event
+if TYPE_CHECKING:
+ from aws_lambda_powertools.utilities.data_classes import AppSyncResolverEvent
+
# TESTS RECEIVING THE EVENT PARTIALLY AND PROCESS EACH RECORD PER TIME.
def test_resolve_batch_processing_with_related_events_one_at_time():
@@ -95,7 +99,7 @@ def test_resolve_batch_processing_with_related_events_one_at_time():
app = AppSyncResolver()
@app.batch_resolver(type_name="Post", field_name="relatedPosts", aggregate=False)
- def related_posts(event: AppSyncResolverEvent) -> Optional[list]:
+ def related_posts(event: AppSyncResolverEvent) -> list | None:
return posts_related[event.source["post_id"]]
# WHEN related_posts function, which is the batch resolver, is called with the event.
@@ -155,7 +159,7 @@ def test_resolve_batch_processing_with_simple_queries_one_at_time():
# WHEN the batch resolver for the listLocations field is defined
@app.batch_resolver(field_name="listLocations", aggregate=False)
- def create_something(event: AppSyncResolverEvent) -> Optional[list]: # noqa AA03 VNE003
+ def create_something(event: AppSyncResolverEvent) -> list | None: # noqa AA03 VNE003
return event.source["id"] if event.source else None
# THEN the resolver should correctly process the batch of queries
@@ -211,7 +215,7 @@ def test_resolve_batch_processing_with_raise_on_exception_one_at_time():
# WHEN the sync batch resolver for the 'listLocations' field is defined with raise_on_error=True
@app.batch_resolver(field_name="listLocations", raise_on_error=True, aggregate=False)
- def create_something(event: AppSyncResolverEvent) -> Optional[list]: # noqa AA03 VNE003
+ def create_something(event: AppSyncResolverEvent) -> list | None: # noqa AA03 VNE003
raise RuntimeError
# THEN the resolver should raise a RuntimeError when processing the batch of queries
@@ -264,7 +268,7 @@ def test_async_resolve_batch_processing_with_raise_on_exception_one_at_time():
# WHEN the async batch resolver for the 'listLocations' field is defined with raise_on_error=True
@app.async_batch_resolver(field_name="listLocations", raise_on_error=True, aggregate=False)
- async def create_something(event: AppSyncResolverEvent) -> Optional[list]: # noqa AA03 VNE003
+ async def create_something(event: AppSyncResolverEvent) -> list | None: # noqa AA03 VNE003
raise RuntimeError
# THEN the resolver should raise a RuntimeError when processing the batch of queries
@@ -315,7 +319,7 @@ def test_resolve_batch_processing_without_exception_one_at_time():
app = AppSyncResolver()
@app.batch_resolver(field_name="listLocations", raise_on_error=False, aggregate=False)
- def create_something(event: AppSyncResolverEvent) -> Optional[list]: # noqa AA03 VNE003
+ def create_something(event: AppSyncResolverEvent) -> list | None: # noqa AA03 VNE003
raise RuntimeError
# Call the implicit handler
@@ -371,7 +375,7 @@ def test_resolve_async_batch_processing_without_exception_one_at_time():
# WHEN the batch resolver for the 'listLocations' field is defined with raise_on_error=False
@app.async_batch_resolver(field_name="listLocations", raise_on_error=False, aggregate=False)
- async def create_something(event: AppSyncResolverEvent) -> Optional[list]: # noqa AA03 VNE003
+ async def create_something(event: AppSyncResolverEvent) -> list | None: # noqa AA03 VNE003
raise RuntimeError
result = app.resolve(event, LambdaContext())
@@ -548,7 +552,7 @@ def test_resolve_async_batch_processing():
# WHEN the async batch resolver for the 'listLocations' field is defined
@app.async_batch_resolver(field_name="listLocations", aggregate=False)
- async def create_something(event: AppSyncResolverEvent) -> Optional[list]:
+ async def create_something(event: AppSyncResolverEvent) -> list | None:
return event.source["id"] if event.source else None
# THEN the resolver should correctly process the batch of queries asynchronously
@@ -699,7 +703,7 @@ def test_resolve_batch_processing_with_simple_queries_with_aggregate():
# WHEN using an aggregated event
# WHEN function returns a List
@app.batch_resolver(field_name="listLocations")
- def create_something(event: List[AppSyncResolverEvent]) -> List: # noqa AA03 VNE003
+ def create_something(event: list[AppSyncResolverEvent]) -> list: # noqa AA03 VNE003
results = []
for record in event:
results.append(record.source.get("id") if record.source else None)
@@ -760,7 +764,7 @@ def test_resolve_async_batch_processing_with_simple_queries_with_aggregate():
# WHEN using an aggregated event
# WHEN function returns a List
@app.async_batch_resolver(field_name="listLocations")
- async def create_something(event: List[AppSyncResolverEvent]) -> List: # noqa AA03 VNE003
+ async def create_something(event: list[AppSyncResolverEvent]) -> list: # noqa AA03 VNE003
results = []
for record in event:
results.append(record.source.get("id") if record.source else None)
@@ -797,7 +801,7 @@ def test_resolve_batch_processing_with_aggregate_and_returning_a_non_list():
# WHEN using an aggregated event
# WHEN function return something different than a List
@app.batch_resolver(field_name="listLocations")
- def create_something(event: List[AppSyncResolverEvent]) -> Optional[List]: # noqa AA03 VNE003
+ def create_something(event: list[AppSyncResolverEvent]) -> list | None: # noqa AA03 VNE003
return event[0].source.get("id") if event[0].source else None
# THEN the resolver should raise a InvalidBatchResponse when processing the batch of queries
@@ -828,7 +832,7 @@ def test_resolve_async_batch_processing_with_aggregate_and_returning_a_non_list(
# WHEN using an aggregated event
# WHEN function return something different than a List
@app.async_batch_resolver(field_name="listLocations")
- async def create_something(event: List[AppSyncResolverEvent]) -> Optional[List]: # noqa AA03 VNE003
+ async def create_something(event: list[AppSyncResolverEvent]) -> list | None: # noqa AA03 VNE003
return event[0].source.get("id") if event[0].source else None
# THEN the resolver should raise a InvalidBatchResponse when processing the batch of queries
@@ -859,7 +863,7 @@ def test_resolve_sync_batch_processing_with_aggregate_and_without_return():
# WHEN using an aggregated event
# WHEN function there is no return statement
@app.batch_resolver(field_name="listLocations")
- def create_something(event: List[AppSyncResolverEvent]) -> Optional[List]: # noqa AA03 VNE003
+ def create_something(event: list[AppSyncResolverEvent]) -> list | None: # noqa AA03 VNE003
def do_something_with_post_id(post_id): ...
post_id = event[0].source.get("id") if event[0].source else None
@@ -895,7 +899,7 @@ def test_resolve_async_batch_processing_with_aggregate_and_without_return():
# WHEN using an aggregated event
# WHEN function there is no return statement
@app.async_batch_resolver(field_name="listLocations")
- async def create_something(event: List[AppSyncResolverEvent]) -> Optional[List]: # noqa AA03 VNE003
+ async def create_something(event: list[AppSyncResolverEvent]) -> list | None: # noqa AA03 VNE003
def do_something_with_post_id(post_id): ...
post_id = event[0].source.get("id") if event[0].source else None
@@ -916,7 +920,7 @@ def test_include_router_access_batch_current_event():
router = Router()
@router.batch_resolver(field_name="createSomething")
- def get_user(event: List) -> List:
+ def get_user(event: list) -> list:
return [router.current_batch_event[0].identity.sub]
app.include_router(router)
@@ -935,7 +939,7 @@ def test_app_access_batch_current_event():
app = AppSyncResolver()
@app.batch_resolver(field_name="createSomething")
- def get_user(event: List) -> List:
+ def get_user(event: list) -> list:
return [app.current_batch_event[0].identity.sub]
# WHEN we resolve the event
@@ -952,7 +956,7 @@ def test_context_is_accessible_in_sync_batch_resolver():
app = AppSyncResolver()
@app.batch_resolver(field_name="createSomething")
- def get_user(event: List) -> List:
+ def get_user(event: list) -> list:
return [app.context.get("project_name")]
# WHEN we resolve the event
@@ -971,7 +975,7 @@ def test_context_is_accessible_in_async_batch_resolver():
app = AppSyncResolver()
@app.async_batch_resolver(field_name="createSomething")
- async def get_user(event: List) -> List:
+ async def get_user(event: list) -> list:
return [app.context.get("project_name")]
# WHEN we resolve the event
@@ -984,7 +988,6 @@ async def get_user(event: List) -> List:
def test_exception_handler_with_batch_resolver_and_raise_exception():
-
# GIVEN a AppSyncResolver instance
app = AppSyncResolver()
@@ -1034,7 +1037,7 @@ def handle_value_error(ex: ValueError):
# WHEN the sync batch resolver for the 'listLocations' field is defined with raise_on_error=True
@app.batch_resolver(field_name="listLocations", raise_on_error=True, aggregate=False)
- def create_something(event: AppSyncResolverEvent) -> Optional[list]: # noqa AA03 VNE003
+ def create_something(event: AppSyncResolverEvent) -> list | None: # noqa AA03 VNE003
raise ValueError
# Call the implicit handler
@@ -1045,7 +1048,6 @@ def create_something(event: AppSyncResolverEvent) -> Optional[list]: # noqa AA0
def test_exception_handler_with_batch_resolver_and_no_raise_exception():
-
# GIVEN a AppSyncResolver instance
app = AppSyncResolver()
@@ -1095,7 +1097,7 @@ def handle_value_error(ex: ValueError):
# WHEN the sync batch resolver for the 'listLocations' field is defined with raise_on_error=False
@app.batch_resolver(field_name="listLocations", raise_on_error=False, aggregate=False)
- def create_something(event: AppSyncResolverEvent) -> Optional[list]: # noqa AA03 VNE003
+ def create_something(event: AppSyncResolverEvent) -> list | None: # noqa AA03 VNE003
raise ValueError
# Call the implicit handler
diff --git a/tests/functional/event_handler/required_dependencies/appsync/test_appsync_events_resolvers.py b/tests/functional/event_handler/required_dependencies/appsync/test_appsync_events_resolvers.py
new file mode 100644
index 00000000000..887adb08fe8
--- /dev/null
+++ b/tests/functional/event_handler/required_dependencies/appsync/test_appsync_events_resolvers.py
@@ -0,0 +1,1614 @@
+import asyncio
+from copy import deepcopy
+
+import pytest
+
+from aws_lambda_powertools.event_handler import AppSyncEventsResolver
+from aws_lambda_powertools.event_handler.events_appsync.exceptions import UnauthorizedException
+from aws_lambda_powertools.event_handler.events_appsync.router import Router
+from aws_lambda_powertools.warnings import PowertoolsUserWarning
+from tests.functional.utils import load_event
+
+
+class LambdaContext:
+ def __init__(self):
+ self.function_name = "test-func"
+ self.memory_limit_in_mb = 128
+ self.invoked_function_arn = "arn:aws:lambda:eu-west-1:809313241234:function:test-func"
+ self.aws_request_id = "52fdfc07-2182-154f-163f-5f0f9a621d72"
+
+ def get_remaining_time_in_millis(self) -> int:
+ return 1000
+
+
+@pytest.fixture(scope="module")
+def lambda_context() -> LambdaContext:
+ """Create a new LambdaContext instance for each test module."""
+ return LambdaContext()
+
+
+@pytest.fixture(scope="module")
+def mock_event():
+ """Load a sample AppSyncEventsEvent for each test module."""
+ return load_event("appSyncEventsEvent.json")
+
+
+def test_publish_event_with_synchronous_resolver(lambda_context, mock_event):
+ """Test handling a publish event with a synchronous resolver."""
+ # GIVEN a sample publish event
+ mock_event["events"] = [
+ {"id": "123", "payload": {"data": "test data"}},
+ ]
+
+ # GIVEN an AppSyncEventsResolver with a synchronous resolver
+ app = AppSyncEventsResolver()
+
+ @app.on_publish(path="/default/*")
+ def test_handler(payload):
+ return {"processed": True, "data": payload["data"]}
+
+ # WHEN we resolve the event
+ result = app.resolve(mock_event, lambda_context)
+
+ # THEN we should get the correct response
+ expected_result = {
+ "events": [
+ {"id": "123", "payload": {"processed": True, "data": "test data"}},
+ ],
+ }
+ assert result == expected_result
+
+
+def test_publish_event_with_async_resolver(lambda_context, mock_event):
+ """Test handling a publish event with an asynchronous resolver."""
+ # GIVEN a sample publish event
+ mock_event["events"] = [
+ {"id": "123", "payload": {"data": "test data"}},
+ ]
+
+ # GIVEN an AppSyncEventsResolver with an asynchronous resolver
+ app = AppSyncEventsResolver()
+
+ @app.async_on_publish(path="/default/*")
+ async def test_handler(payload):
+ await asyncio.sleep(0.01) # Simulate async work
+ return {"processed": True, "data": payload["data"]}
+
+ # WHEN we resolve the event
+ result = app.resolve(mock_event, lambda_context)
+
+ # THEN we should get the correct response
+ assert "events" in result
+ assert len(result["events"]) == 1
+ assert result["events"][0]["payload"]["processed"] is True
+ assert result["events"][0]["payload"]["data"] == "test data"
+
+
+def test_publish_event_with_error_handling(lambda_context, mock_event):
+ """Test error handling during publish event processing."""
+ # GIVEN a sample publish event
+ mock_event["events"] = [
+ {"id": "123", "payload": {"data": "test data"}},
+ ]
+
+ # GIVEN an AppSyncEventsResolver with a resolver that raises an exception
+ app = AppSyncEventsResolver()
+
+ @app.on_publish(path="/default/*")
+ def test_handler(payload):
+ raise ValueError("Test error")
+
+ # WHEN we resolve the event
+ result = app.resolve(mock_event, lambda_context)
+
+ # THEN we should get an error response
+ assert "events" in result
+ assert "error" in result["events"][0]
+ assert "ValueError - Test error" in result["events"][0]["error"]
+ assert result["events"][0]["id"] == "123"
+
+
+def test_publish_event_with_router_inclusion(lambda_context, mock_event):
+ """Test including a router in the AppSyncEventsResolver."""
+ # GIVEN a sample publish event
+ mock_event["events"] = [
+ {"id": "123", "payload": {"data": "test data", "from_router": True}},
+ ]
+
+ # GIVEN a router with a resolver
+ router = Router()
+
+ @router.on_publish(path="/chat/*")
+ def router_handler(payload):
+ return {"from_router": True, "data": payload["data"]}
+
+ # GIVEN an AppSyncEventsResolver that includes the router
+ app = AppSyncEventsResolver()
+ app.include_router(router)
+
+ # WHEN we resolve the event
+ result = app.resolve(mock_event, lambda_context)
+
+ # THEN we should get the response from the router's handler
+ expected_result = {
+ "events": [
+ {"id": "123", "payload": {"from_router": True, "data": "test data"}},
+ ],
+ }
+ assert result == expected_result
+
+
+def test_publish_event_with_custom_context(lambda_context, mock_event):
+ """Test resolving events with custom context data."""
+ # GIVEN a sample publish event
+ mock_event["events"] = [
+ {"id": "123", "payload": {"data": "test data"}},
+ ]
+
+ # GIVEN an AppSyncEventsResolver with custom context
+ app = AppSyncEventsResolver()
+
+ @app.on_publish(path="/default/*")
+ def test_handler(payload):
+ # Access the context within the handler
+ return {
+ "processed": True,
+ "data": payload["data"],
+ "user_id": app.context.get("user_id"),
+ "role": app.context.get("role"),
+ }
+
+ # WHEN we resolve the event
+ app.append_context(user_id="test-user", role="admin")
+ result = app.resolve(mock_event, lambda_context)
+
+ # THEN we should get the response with context data
+ expected_result = {
+ "events": [
+ {
+ "id": "123",
+ "payload": {
+ "processed": True,
+ "data": "test data",
+ "user_id": "test-user",
+ "role": "admin",
+ },
+ },
+ ],
+ }
+ assert result == expected_result
+
+
+def test_publish_event_with_aggregate_mode(lambda_context, mock_event):
+ """Test handling a publish event with aggregate mode enabled."""
+ # GIVEN a sample publish event with multiple items
+ mock_event["events"] = [
+ {"id": "123", "payload": {"data": "test data 1"}},
+ {"id": "456", "payload": {"data": "test data 2"}},
+ ]
+
+ # GIVEN an AppSyncEventsResolver with an aggregate resolver
+ app = AppSyncEventsResolver()
+
+ @app.on_publish(path="/default/*", aggregate=True)
+ def test_batch_handler(payload):
+ # Process all events at once
+ return [{"batch_processed": True, "data": item["payload"]["data"]} for item in payload]
+
+ # WHEN we resolve the event
+ result = app.resolve(mock_event, lambda_context)
+
+ # THEN we should get the batch processed response
+ expected_result = {
+ "events": [
+ {"batch_processed": True, "data": "test data 1"},
+ {"batch_processed": True, "data": "test data 2"},
+ ],
+ }
+ assert result == expected_result
+
+
+def test_async_publish_event_with_aggregate_mode(lambda_context, mock_event):
+ """Test handling an async publish event with aggregate mode enabled."""
+ # GIVEN a sample publish event with multiple items
+ mock_event["events"] = [
+ {"id": "123", "payload": {"data": "test data 1"}},
+ {"id": "456", "payload": {"data": "test data 2"}},
+ ]
+
+ # GIVEN an AppSyncEventsResolver with an async aggregate resolver
+ app = AppSyncEventsResolver()
+
+ @app.async_on_publish(path="/default/*", aggregate=True)
+ async def test_async_batch_handler(payload):
+ # Simulate async processing of all events
+ await asyncio.sleep(0.01)
+ return [{"async_batch_processed": True, "data": item["payload"]["data"]} for item in payload]
+
+ # WHEN we resolve the event
+ result = app.resolve(mock_event, lambda_context)
+
+ # THEN we should get the batch processed response
+ expected_result = {
+ "events": [
+ {"async_batch_processed": True, "data": "test data 1"},
+ {"async_batch_processed": True, "data": "test data 2"},
+ ],
+ }
+ assert result == expected_result
+
+
+def test_publish_event_no_matching_resolver(lambda_context, mock_event):
+ """Test handling a publish event when no matching resolver is found."""
+ # GIVEN a sample publish event
+ mock_event["info"]["channel"]["path"] = "/unknown/path"
+ mock_event["events"] = [
+ {"id": "123", "payload": {"data": "test data"}},
+ ]
+
+ # GIVEN an AppSyncEventsResolver with no matching resolver
+ app = AppSyncEventsResolver()
+
+ @app.on_publish(path="/default/*")
+ def test_handler(payload):
+ return {"processed": True}
+
+ # WHEN we resolve the event with a warning
+ with pytest.warns(PowertoolsUserWarning, match="No resolvers were found for publish operations"):
+ result = app.resolve(mock_event, lambda_context)
+
+ # THEN we should get the original payload returned as is
+ expected_result = {
+ "events": [
+ {"id": "123", "payload": {"data": "test data"}},
+ ],
+ }
+ assert result == expected_result
+
+
+def test_multiple_resolvers_for_same_path(lambda_context, mock_event):
+ """Test behavior when both sync and async resolvers exist for the same path."""
+ # GIVEN a sample publish event
+ mock_event["info"]["channel"]["path"] = "/default/test"
+ mock_event["events"] = [
+ {"id": "123", "payload": {"sync_processed": True, "data": "test data"}},
+ ]
+
+ # GIVEN an AppSyncEventsResolver with both sync and async resolvers for the same path
+ app = AppSyncEventsResolver()
+
+ @app.on_publish(path="/default/*")
+ def sync_handler(payload):
+ return {"sync_processed": True, "data": payload["data"]}
+
+ @app.async_on_publish(path="/default/*")
+ async def async_handler(event):
+ await asyncio.sleep(0.01)
+ return {"async_processed": True, "data": event["data"]}
+
+ # WHEN we resolve the event, with a warning expected
+ with pytest.warns(PowertoolsUserWarning, match="Both synchronous and asynchronous resolvers found"):
+ result = app.resolve(mock_event, lambda_context)
+
+ # THEN the sync resolver should be used (takes precedence)
+ expected_result = {
+ "events": [
+ {"id": "123", "payload": {"sync_processed": True, "data": "test data"}},
+ ],
+ }
+ assert result == expected_result
+
+
+def test_custom_exception_handling(lambda_context, mock_event):
+ """Test handling custom exceptions during event processing."""
+ # GIVEN a sample publish event
+ mock_event["events"] = [
+ {"id": "123", "payload": {"sync_processed": True, "data": "test data"}},
+ ]
+
+ # GIVEN a custom exception class
+ class NotAuthorized(Exception):
+ pass
+
+ # GIVEN an AppSyncEventsResolver with a resolver that raises a custom exception
+ app = AppSyncEventsResolver()
+
+ @app.on_publish(path="/default/*")
+ def test_handler(payload):
+ if payload["data"] == "test data":
+ raise NotAuthorized("Not authorized")
+ return {"processed": True}
+
+ # WHEN we resolve the event
+ result = app.resolve(mock_event, lambda_context)
+
+ # THEN we should get an error response with our custom exception
+ assert "events" in result
+ assert "error" in result["events"][0]
+ assert "NotAuthorized - Not authorized" in result["events"][0]["error"]
+ assert result["events"][0]["id"] == "123"
+
+
+def test_async_resolver_with_error_handling(lambda_context, mock_event):
+ """Test error handling with async resolvers during publish event processing."""
+ # GIVEN a sample publish event
+ mock_event["events"] = [
+ {"id": "123", "payload": {"sync_processed": True, "data": "test data"}},
+ ]
+
+ # GIVEN an AppSyncEventsResolver with an async resolver that raises an exception
+ app = AppSyncEventsResolver()
+
+ @app.async_on_publish(path="/default/*")
+ async def test_handler(payload):
+ await asyncio.sleep(0.01) # Simulate async work
+ raise ValueError("Async test error")
+
+ # WHEN we resolve the event
+ result = app.resolve(mock_event, lambda_context)
+
+ # THEN we should get an error response
+ assert "events" in result
+ assert len(result["events"]) == 1
+ assert "error" in result["events"][0]
+ assert "ValueError - Async test error" in result["events"][0]["error"]
+
+
+def test_lambda_handler_with_call_method(lambda_context, mock_event):
+ """Test that the lambda handler function properly processes events."""
+ # GIVEN a sample publish event
+ mock_event["events"] = [
+ {"id": "123", "payload": {"sync_processed": True, "data": "test data"}},
+ ]
+
+ # GIVEN an AppSyncEventsResolver setup
+ app = AppSyncEventsResolver()
+
+ @app.on_publish(path="/default/*")
+ def test_handler(payload):
+ return {"lambda_processed": True, "data": payload["data"]}
+
+ # WHEN we use the AppSyncEventsResolver as a Lambda handler
+ result = app(mock_event, lambda_context) # Using __call__ method which calls resolve()
+
+ # THEN we should get the processed response
+ expected_result = {
+ "events": [
+ {"id": "123", "payload": {"lambda_processed": True, "data": "test data"}},
+ ],
+ }
+ assert result == expected_result
+
+
+def test_event_with_mixed_success_and_errors(lambda_context, mock_event):
+ """Test handling a batch of events with mixed success and failure outcomes."""
+ # GIVEN a sample publish event with multiple items
+ mock_event["events"] = [
+ {"id": "123", "payload": {"data": "good data"}},
+ {"id": "456", "payload": {"data": "bad data"}},
+ {"id": "789", "payload": {"data": "good data again"}},
+ ]
+
+ # GIVEN an AppSyncEventsResolver with a resolver that conditionally fails
+ app = AppSyncEventsResolver()
+
+ @app.on_publish(path="/default/*")
+ def test_handler(payload):
+ if payload["data"] == "bad data":
+ raise ValueError("Bad data detected")
+ return {"success": True, "data": payload["data"]}
+
+ # WHEN we resolve the event
+ result = app.resolve(mock_event, lambda_context)
+
+ # THEN we should get mixed results with success and error responses
+ assert "events" in result
+ assert len(result["events"]) == 3
+
+ # First event should be successful
+ assert "payload" in result["events"][0]
+ assert result["events"][0]["payload"]["success"] is True
+ assert result["events"][0]["payload"]["data"] == "good data"
+
+ # Second event should have an error
+ assert "error" in result["events"][1]
+ assert "ValueError - Bad data detected" in result["events"][1]["error"]
+
+ # Third event should be successful
+ assert "payload" in result["events"][2]
+ assert result["events"][2]["payload"]["success"] is True
+ assert result["events"][2]["payload"]["data"] == "good data again"
+
+
+def test_router_with_context_sharing(lambda_context, mock_event):
+ """Test that context is properly shared between routers and the main resolver."""
+ # GIVEN a sample publish event
+ mock_event["info"]["channel"]["path"] = "/chat/message"
+ mock_event["events"] = [
+ {"id": "123", "payload": {"data": "test data"}},
+ ]
+
+ # GIVEN a router with context
+ router = Router()
+ router.append_context(service="chat")
+
+ @router.on_publish(path="/chat/*")
+ def router_handler(payload):
+ # Access shared context
+ return {
+ "from_router": True,
+ "service": router.context.get("service"),
+ "tenant": router.context.get("tenant"),
+ }
+
+ # GIVEN an AppSyncEventsResolver with its own context
+ app = AppSyncEventsResolver()
+ app.append_context(tenant="acme")
+
+ # Include the router and merge contexts
+ app.include_router(router)
+
+ # WHEN we resolve the event
+ result = app.resolve(mock_event, lambda_context)
+
+ # THEN the handler should have access to merged context from both sources
+ expected_result = {
+ "events": [
+ {
+ "id": "123",
+ "payload": {
+ "from_router": True,
+ "service": "chat",
+ "tenant": "acme",
+ },
+ },
+ ],
+ }
+ assert result == expected_result
+
+
+def test_context_cleared_after_resolution(lambda_context, mock_event):
+ """Test that context is properly cleared after event resolution."""
+ # GIVEN a sample publish event
+ mock_event["events"] = [
+ {"id": "123", "payload": {"sync_processed": True, "data": "test data"}},
+ ]
+
+ # GIVEN an AppSyncEventsResolver with context data
+ app = AppSyncEventsResolver()
+ app.append_context(request_id="12345")
+
+ @app.on_publish(path="/default/*")
+ def test_handler(payload):
+ # Verify context exists during handler execution
+ assert app.context.get("request_id") == "12345"
+ return {"processed": True}
+
+ # WHEN we resolve the event
+ app.resolve(mock_event, lambda_context)
+
+ # THEN the context should be cleared afterward
+ assert app.context == {}
+
+
+def test_path_matching_mechanism(mocker, lambda_context, mock_event):
+ """Test the path matching mechanism for resolvers."""
+
+ mock_find_resolver = mocker.patch(
+ "aws_lambda_powertools.event_handler.events_appsync._registry.ResolverEventsRegistry.find_resolver",
+ )
+ # GIVEN a resolver that should be found
+ mock_resolver = {
+ "func": lambda payload: {"matched": True},
+ "aggregate": False,
+ }
+ mock_find_resolver.return_value = mock_resolver
+
+ # GIVEN a sample publish event
+ mock_event["info"]["channel"]["path"] = "/chat/room/123/message"
+ mock_event["events"] = [
+ {"id": "123", "payload": {"data": "test data"}},
+ ]
+
+ # GIVEN an AppSyncEventsResolver
+ app = AppSyncEventsResolver()
+
+ # WHEN we resolve the event
+ app.resolve(mock_event, lambda_context)
+
+ # THEN the registry should be queried with the correct path
+ mock_find_resolver.assert_called_with("/chat/room/123/message")
+
+
+def test_async_aggregate_with_parallel_processing(lambda_context, mock_event):
+ """Test that async aggregate handlers can process events in parallel."""
+ # GIVEN a sample publish event with multiple items
+ mock_event["info"]["channel"]["path"] = "/default/process"
+ mock_event["events"] = [
+ {"id": "123", "payload": {"sync_processed": True, "data": "item 1", "delay": 0.03}},
+ {"id": "456", "payload": {"sync_processed": True, "data": "item 2", "delay": 0.02}},
+ {"id": "789", "payload": {"sync_processed": True, "data": "item 3", "delay": 0.01}},
+ ]
+
+ # GIVEN an AppSyncEventsResolver with an async aggregate handler
+ app = AppSyncEventsResolver()
+
+ @app.async_on_publish(path="/default/*", aggregate=True)
+ async def test_async_handler(payload):
+ # Create tasks for each event with different delays
+ tasks = []
+ for idx_event in payload:
+ tasks.append(process_single_event(idx_event["payload"]))
+
+ # Process all events in parallel
+ results = await asyncio.gather(*tasks)
+ return results
+
+ async def process_single_event(payload):
+ # Simulate variable processing time
+ await asyncio.sleep(payload["delay"])
+ return {"processed": True, "data": payload["data"]}
+
+ # WHEN we resolve the event
+ result = app.resolve(mock_event, lambda_context)
+
+ # THEN all events should be processed
+ assert "events" in result
+ assert len(result["events"]) == 3
+
+ # Check all items were processed
+ processed_data = [item["data"] for item in result["events"]]
+ assert "item 1" in processed_data
+ assert "item 2" in processed_data
+ assert "item 3" in processed_data
+
+
+def test_both_app_and_router_for_same_path(lambda_context, mock_event):
+ """Test precedence when both app and router have resolvers for the same path."""
+ # GIVEN a sample publish event
+ mock_event["info"]["channel"]["path"] = "/default/duplicate"
+ mock_event["events"] = [
+ {"id": "123", "payload": {"data": "test data"}},
+ ]
+
+ # GIVEN a router with a resolver
+ router = Router()
+
+ @router.on_publish(path="/default/duplicate")
+ def router_handler(payload):
+ return {"source": "router"}
+
+ # GIVEN an AppSyncEventsResolver with a resolver for the same path
+ app = AppSyncEventsResolver()
+
+ @app.on_publish(path="/default/duplicate")
+ def app_handler(payload):
+ return {"source": "app"}
+
+ # Include the router after defining the app handler
+ app.include_router(router)
+
+ # WHEN we resolve the event
+ result = app.resolve(mock_event, lambda_context)
+
+ # THEN the router's handler should take precedence as it was registered last
+ expected_result = {
+ "events": [
+ {"id": "123", "payload": {"source": "router"}},
+ ],
+ }
+ assert result == expected_result
+
+
+def test_event_with_real_world_example(lambda_context, mock_event):
+ """Test handling a more complex, real-world-like example."""
+ # GIVEN a more realistic publish event with multiple items
+ mock_event["info"]["channel"]["path"] = "/chat/messages"
+ mock_event["events"] = [
+ {
+ "id": "message-123",
+ "payload": {
+ "type": "text",
+ "content": "Hello, world!",
+ "timestamp": 1636718400000,
+ "sender": "user1",
+ },
+ },
+ {
+ "id": "message-456",
+ "payload": {
+ "type": "image",
+ "content": "https://example.com/image.jpg",
+ "timestamp": 1636718500000,
+ "sender": "user2",
+ },
+ },
+ ]
+
+ # GIVEN a router for chat-related operations
+ chat_router = Router()
+
+ @chat_router.on_publish(path="/chat/*")
+ def process_message(payload):
+ # Process message based on type
+ if payload["type"] == "text":
+ return {
+ "processed": True,
+ "messageType": "text",
+ "displayContent": payload["content"],
+ "timestamp": payload["timestamp"],
+ "sender": payload["sender"],
+ }
+ elif payload["type"] == "image":
+ return {
+ "processed": True,
+ "messageType": "image",
+ "displayContent": f"[Image] {payload['content']}",
+ "timestamp": payload["timestamp"],
+ "sender": payload["sender"],
+ }
+ else:
+ return {
+ "processed": False,
+ "error": "Unsupported message type",
+ }
+
+ # GIVEN an AppSyncEventsResolver that includes the router
+ app = AppSyncEventsResolver()
+ app.include_router(chat_router)
+
+ # WHEN we resolve the event
+ result = app.resolve(mock_event, lambda_context)
+
+ # THEN we should get properly processed messages
+ assert "events" in result
+ assert len(result["events"]) == 2
+
+ # Check text message
+ assert result["events"][0]["id"] == "message-123"
+ assert result["events"][0]["payload"]["processed"] is True
+ assert result["events"][0]["payload"]["messageType"] == "text"
+ assert result["events"][0]["payload"]["displayContent"] == "Hello, world!"
+
+ # Check image message
+ assert result["events"][1]["id"] == "message-456"
+ assert result["events"][1]["payload"]["processed"] is True
+ assert result["events"][1]["payload"]["messageType"] == "image"
+ assert result["events"][1]["payload"]["displayContent"] == "[Image] https://example.com/image.jpg"
+
+
+def test_event_response_with_custom_error_handling(lambda_context, mock_event):
+ """Test handling events with custom error handling logic."""
+ # GIVEN a sample publish event
+ mock_event["info"]["channel"]["path"] = "/default/test"
+ mock_event["events"] = [
+ {"id": "123", "payload": {"data": "sensitive data"}},
+ ]
+
+ # GIVEN a custom exception and a router with an async handler
+ class CustomSecurityException(Exception):
+ pass
+
+ router = Router()
+
+ @router.async_on_publish(path="/default/*")
+ async def security_check(payload):
+ # Simulate a security check that blocks certain IDs
+ blocked_data = ["sensitive data"]
+ if payload["data"] in blocked_data:
+ raise CustomSecurityException("Security check failed: Blocked ID")
+
+ await asyncio.sleep(0.01) # Simulate async work
+ return {"security_verified": True, "data": payload["data"]}
+
+ # GIVEN an AppSyncEventsResolver
+ app = AppSyncEventsResolver()
+ app.include_router(router)
+
+ # WHEN we resolve the event
+ result = app.resolve(mock_event, lambda_context)
+
+ # THEN we should get a security error response
+ assert "events" in result
+ assert len(result["events"]) == 1
+ assert "error" in result["events"][0]
+ assert "CustomSecurityException - Security check failed" in result["events"][0]["error"]
+ assert result["events"][0]["id"] == "123"
+
+
+def test_pattern_matching_no_valid_paths(lambda_context, mock_event):
+ """Test that path pattern matching works correctly with wildcards."""
+ # GIVEN a sample publish event
+ mock_event["info"]["channel"]["path"] = "/users/123/notifications/new"
+ mock_event["events"] = [
+ {"id": "123", "payload": {"data": "user notification data"}},
+ ]
+
+ # GIVEN an AppSyncEventsResolver with wildcard path patterns
+ app = AppSyncEventsResolver()
+
+ # Define multiple resolvers with different path patterns
+ @app.on_publish(path="/users/*/notifications/*") # Should not match
+ def user_notification_handler(payload):
+ return {"handler": "wildcard_match", "data": "modified data 1"}
+
+ @app.on_publish(path="/users/123/messages/*") # Should not match
+ def user_message_handler(payload):
+ return {"handler": "wrong_path", "data": "modified data 2"}
+
+ @app.on_publish(path="/*/*/*") # should not match
+ def generic_handler(payload):
+ return {"handler": "generic", "data": "modified data 3"}
+
+ # WHEN we resolve the event
+ result = app.resolve(mock_event, lambda_context)
+
+ # THEN no resolver is found and we return as is
+ expected_result = {
+ "events": [
+ {"id": "123", "payload": {"data": "user notification data"}},
+ ],
+ }
+ assert result == expected_result
+
+
+def test_nested_async_functions(lambda_context, mock_event):
+ """Test that nested async functions work correctly within resolvers."""
+ # GIVEN a sample publish event
+ mock_event["info"]["channel"]["path"] = "/default/nested"
+ mock_event["events"] = [
+ {"id": "123", "payload": {"data": "test data"}},
+ ]
+
+ # GIVEN an AppSyncEventsResolver with a resolver that uses nested async functions
+ app = AppSyncEventsResolver()
+
+ @app.async_on_publish(path="/default/*")
+ async def outer_handler(payload):
+ # Define nested async functions
+ async def validate_data(data):
+ await asyncio.sleep(0.01) # Simulate validation
+ return data.strip() != ""
+
+ async def transform_data(data):
+ await asyncio.sleep(0.01) # Simulate transformation
+ return data.upper()
+
+ # Use nested async functions
+ is_valid = await validate_data(payload["data"])
+ if not is_valid:
+ return {"error": "Invalid data"}
+
+ transformed = await transform_data(payload["data"])
+ return {"validated": is_valid, "transformed": transformed}
+
+ # WHEN we resolve the event
+ result = app.resolve(mock_event, lambda_context)
+
+ # THEN the nested async functions should execute correctly
+ assert "events" in result
+ assert len(result["events"]) == 1
+ assert result["events"][0]["payload"]["validated"] is True
+ assert result["events"][0]["payload"]["transformed"] == "TEST DATA"
+
+
+def test_concurrent_event_processing(lambda_context, mock_event):
+ """Test that multiple events are processed concurrently with async handlers."""
+ # GIVEN a sample publish event with multiple items that take different times to process
+ mock_event["info"]["channel"]["path"] = "/default/concurrent"
+ mock_event["events"] = [
+ {"id": "123", "payload": {"data": "fast data", "delay": 0.01}},
+ {"id": "456", "payload": {"data": "slow data", "delay": 0.03}},
+ {"id": "789", "payload": {"data": "medium data", "delay": 0.02}},
+ ]
+
+ # GIVEN an AppSyncEventsResolver with an async handler
+ app = AppSyncEventsResolver()
+
+ @app.async_on_publish(path="/default/*")
+ async def process_with_variable_delay(payload):
+ # Simulate processing with different delays
+ await asyncio.sleep(payload["delay"])
+ return {
+ "processed": True,
+ "data": payload["data"],
+ "processing_time": payload["delay"],
+ }
+
+ # WHEN we resolve the event
+ import time
+
+ start_time = time.time()
+ result = app.resolve(mock_event, lambda_context)
+ end_time = time.time()
+
+ # THEN all events should be processed
+ assert "events" in result
+ assert len(result["events"]) == 3
+
+ # The total time should be roughly equal to the longest individual delay
+ # (not the sum of all delays, which would indicate sequential processing)
+ processing_time = end_time - start_time
+ assert processing_time < 0.1 # Should be close to the max delay (0.03) plus overhead
+
+ # Check all events were processed
+ ids = [event.get("id") for event in result["events"]]
+ assert set(ids) == {"123", "456", "789"}
+
+
+def test_handler_with_implicit_call_method_in_lambda_function(lambda_context, mock_event):
+ """Test that the __call__ method works correctly as an implicit Lambda handler."""
+ # GIVEN a sample publish event
+ mock_event["events"] = [
+ {"id": "123", "payload": {"data": "test data"}},
+ ]
+
+ # GIVEN an AppSyncEventsResolver
+ app = AppSyncEventsResolver()
+
+ @app.on_publish(path="/default/*")
+ def test_handler(payload):
+ return {"processed": True, "data": payload["data"]}
+
+ # Define a Lambda handler using the app directly
+ def lambda_handler(event, context):
+ return app(event, context) # Using __call__ method
+
+ # WHEN we call the lambda handler
+ result = lambda_handler(mock_event, lambda_context)
+
+ # THEN we should get the expected result
+ expected_result = {
+ "events": [
+ {"id": "123", "payload": {"processed": True, "data": "test data"}},
+ ],
+ }
+ assert result == expected_result
+
+
+def test_middleware_like_functionality(lambda_context, mock_event):
+ """Test implementing middleware-like functionality with context."""
+ # GIVEN a sample publish event
+ mock_event["events"] = [
+ {"id": "123", "payload": {"data": "test data"}},
+ ]
+
+ # GIVEN an AppSyncEventsResolver
+ app = AppSyncEventsResolver()
+
+ # Simulate middleware by adding context before processing
+ def add_request_metadata(event, context, app):
+ app.append_context(
+ request_id="req-123",
+ timestamp=123456789,
+ user_agent="test-agent",
+ )
+
+ # Handler that uses the context added by middleware
+ @app.on_publish(path="/default/*")
+ def handler_with_middleware_data(payload):
+ return {
+ "processed": True,
+ "data": payload["data"],
+ "metadata": {
+ "request_id": app.context.get("request_id"),
+ "timestamp": app.context.get("timestamp"),
+ "user_agent": app.context.get("user_agent"),
+ },
+ }
+
+ # WHEN we add middleware data and resolve the event
+ add_request_metadata(mock_event, lambda_context, app)
+ result = app.resolve(mock_event, lambda_context)
+
+ # THEN the handler should have access to middleware-added context
+ expected_metadata = {
+ "request_id": "req-123",
+ "timestamp": 123456789,
+ "user_agent": "test-agent",
+ }
+
+ assert result["events"][0]["payload"]["metadata"] == expected_metadata
+
+
+def test_handler_with_event_transformation(lambda_context, mock_event):
+ """Test handlers that transform event data before processing."""
+ # GIVEN a sample publish event
+ mock_event["info"]["channel"]["path"] = "/default/transform"
+ mock_event["events"] = [
+ {"id": "123", "payload": {"user_data": {"name": "John", "age": 30}}},
+ {"id": "456", "payload": {"user_data": {"name": "Jane", "age": 16}}},
+ ]
+
+ # GIVEN an AppSyncEventsResolver with a router
+ router = Router()
+
+ # Add middleware context to transform data
+ @router.on_publish(path="/default/*", aggregate=True)
+ def transform_and_process(payload):
+ # Transform the payload structure
+ transformed = []
+ for item in payload:
+ transformed.append(
+ {
+ "id": item["id"],
+ "payload": {
+ "user_data": {
+ "fullName": item["payload"]["user_data"]["name"],
+ "userAge": item["payload"]["user_data"]["age"],
+ "isAdult": item["payload"]["user_data"]["age"] >= 18,
+ },
+ },
+ },
+ )
+ return transformed
+
+ app = AppSyncEventsResolver()
+ app.include_router(router)
+
+ # WHEN we resolve the event
+ result = app.resolve(mock_event, lambda_context)
+
+ # THEN the data should be transformed
+ assert "events" in result
+ assert len(result["events"]) == 2
+
+ # Check transformation results
+ assert result["events"][0]["id"] == "123"
+ assert result["events"][0]["payload"]["user_data"]["fullName"] == "John"
+ assert result["events"][0]["payload"]["user_data"]["userAge"] == 30
+ assert result["events"][0]["payload"]["user_data"]["isAdult"] is True
+
+ assert result["events"][1]["id"] == "456"
+ assert result["events"][1]["payload"]["user_data"]["fullName"] == "Jane"
+ assert result["events"][1]["payload"]["user_data"]["userAge"] == 16
+ assert result["events"][1]["payload"]["user_data"]["isAdult"] is False
+
+
+def test_empty_events_payload(lambda_context, mock_event):
+ """Test handling events with an empty payload."""
+ # GIVEN a sample publish event with empty events
+ mock_event["events"] = []
+
+ # GIVEN an AppSyncEventsResolver
+ app = AppSyncEventsResolver()
+
+ @app.on_publish(path="/default/*", aggregate=True)
+ def handle_events(payload):
+ # Should handle empty payload gracefully
+ if payload == [{}]:
+ return []
+ return [{"processed": True} for _ in payload]
+
+ # WHEN we resolve the event
+ result = app.resolve(mock_event, lambda_context)
+
+ # THEN we should get an empty events list
+ assert "events" in result
+ assert result["events"] == []
+
+
+def test_multiple_related_routes_with_precedence(lambda_context, mock_event):
+ """Test event routing when multiple paths could match an event."""
+ # GIVEN a sample publish event
+ mock_event["info"]["channel"]["path"] = "/products/electronics/phones/123"
+ mock_event["events"] = [
+ {"id": "123", "payload": {"level": "phones", "data": "product data"}},
+ ]
+
+ # GIVEN an AppSyncEventsResolver with multiple related routes
+ app = AppSyncEventsResolver()
+
+ # Define resolvers with varying specificity
+ @app.on_publish(path="/products/*")
+ def general_product_handler(payload):
+ return {"level": "general", "data": payload["data"]}
+
+ @app.on_publish(path="/products/electronics/*")
+ def electronics_handler(payload):
+ return {"level": "electronics", "data": payload["data"]}
+
+ @app.on_publish(path="/products/electronics/phones/*")
+ def phones_handler(payload):
+ return {"level": "phones", "data": payload["data"]}
+
+ # WHEN we resolve the event
+ result = app.resolve(mock_event, lambda_context)
+
+ # THEN the most specific matching path should be used
+ expected_result = {
+ "events": [
+ {"id": "123", "payload": {"level": "phones", "data": "product data"}},
+ ],
+ }
+ assert result == expected_result
+
+
+def test_integration_with_external_service(lambda_context, mock_event):
+ """Test integration with an external service using mocks."""
+ # GIVEN a sample publish event
+ mock_event["info"]["channel"]["path"] = "/orders/process"
+ mock_event["events"] = [
+ {"id": "123", "payload": {"id": "order-123", "product_id": "prod-456", "quantity": 2}},
+ ]
+
+ # Mock an external service
+ class MockOrderService:
+ @staticmethod
+ async def process_order(order_id, product_id, quantity):
+ # Simulate processing delay
+ await asyncio.sleep(0.01)
+ return {
+ "order_id": order_id,
+ "status": "processed",
+ "total_amount": quantity * 10.99,
+ }
+
+ order_service = MockOrderService()
+
+ # GIVEN an AppSyncEventsResolver with an async resolver using the service
+ app = AppSyncEventsResolver()
+
+ @app.async_on_publish(path="/orders/*")
+ async def process_order(payload):
+ # Call the external service
+ result = await order_service.process_order(
+ order_id=payload["id"],
+ product_id=payload["product_id"],
+ quantity=payload["quantity"],
+ )
+ return {
+ "order_processed": True,
+ "order_details": result,
+ }
+
+ # WHEN we resolve the event
+ result = app.resolve(mock_event, lambda_context)
+
+ # THEN the order should be processed with the external service
+ assert "events" in result
+ assert result["events"][0]["payload"]["order_processed"] is True
+ assert result["events"][0]["payload"]["order_details"]["order_id"] == "order-123"
+ assert result["events"][0]["payload"]["order_details"]["status"] == "processed"
+ assert result["events"][0]["payload"]["order_details"]["total_amount"] == 21.98 # 2 * 10.99
+
+
+def test_complex_resolver_hierarchy(lambda_context, mock_event):
+ """Test a complex setup with multiple routers and nested paths."""
+ # GIVEN a complex event
+ mock_event["info"]["channel"]["path"] = "/api/v1/users/profile/update"
+ mock_event["events"] = [
+ {"id": "123", "payload": {"profile": {"name": "John Doe", "email": "john@example.com"}}},
+ ]
+
+ # GIVEN multiple routers for different API parts
+ base_router = Router()
+ users_router = Router()
+ profiles_router = Router()
+
+ # Add handlers to each router
+ @base_router.on_publish(path="/api/*")
+ def api_base_handler(payload):
+ return {"source": "base", "data": payload}
+
+ @users_router.on_publish(path="/api/v1/users/*")
+ def users_handler(payload):
+ return {"source": "users", "data": payload}
+
+ @profiles_router.on_publish(path="/api/v1/users/profile/*")
+ def profile_handler(payload):
+ # Do some profile-specific processing
+ return {
+ "source": "profiles",
+ "updated": True,
+ "profile": {
+ "fullName": payload["profile"]["name"],
+ "email": payload["profile"]["email"],
+ "timestamp": "2023-01-01T00:00:00Z",
+ },
+ }
+
+ # GIVEN an AppSyncEventsResolver with included routers
+ app = AppSyncEventsResolver()
+ app.include_router(base_router)
+ app.include_router(users_router)
+ app.include_router(profiles_router)
+
+ # WHEN we resolve the event
+ result = app.resolve(mock_event, lambda_context)
+
+ # THEN the most specific router's handler should be used
+ assert "events" in result
+ assert result["events"][0]["id"] == "123"
+ assert result["events"][0]["payload"]["source"] == "profiles"
+ assert result["events"][0]["payload"]["updated"] is True
+ assert "fullName" in result["events"][0]["payload"]["profile"]
+ assert result["events"][0]["payload"]["profile"]["fullName"] == "John Doe"
+
+
+def test_warning_behavior_with_no_matching_resolver(lambda_context, mock_event):
+ """Test warning behavior when no matching resolver is found."""
+ # GIVEN a sample publish event with an unmatched path
+ mock_event["info"]["channel"]["path"] = "/unmatched/path"
+ mock_event["events"] = [
+ {"id": "123", "payload": {"data": "test data"}},
+ ]
+
+ # GIVEN an AppSyncEventsResolver with a resolver for a different path
+ app = AppSyncEventsResolver()
+
+ @app.on_publish(path="/matched/path")
+ def test_handler(payload):
+ return {"processed": True}
+
+ # WHEN we resolve the event
+ # THEN a warning should be generated
+ with pytest.warns(UserWarning, match="No resolvers were found for publish operations with path /unmatched/path"):
+ result = app.resolve(mock_event, lambda_context)
+
+ # AND the payload should be returned as is
+ assert result == {"events": [{"id": "123", "payload": {"data": "test data"}}]}
+
+
+def test_resolver_precedence_with_exact_match(lambda_context, mock_event):
+ """Test that exact path matches have precedence over wildcard matches."""
+ # GIVEN a sample publish event
+ mock_event["info"]["channel"]["path"] = "/notifications/system"
+ mock_event["events"] = [
+ {"id": "123", "payload": {"message": "System notification"}},
+ ]
+
+ # GIVEN an AppSyncEventsResolver with both wildcard and exact path resolvers
+ app = AppSyncEventsResolver()
+
+ @app.on_publish(path="/notifications/*")
+ def wildcard_handler(payload):
+ return {"source": "wildcard", "message": payload["message"]}
+
+ @app.on_publish(path="/notifications/system")
+ def exact_handler(payload):
+ return {"source": "exact", "message": payload["message"]}
+
+ # WHEN we resolve the event
+ result = app.resolve(mock_event, lambda_context)
+
+ # THEN the exact path match should take precedence
+ expected_result = {
+ "events": [
+ {"id": "123", "payload": {"source": "exact", "message": "System notification"}},
+ ],
+ }
+ assert result == expected_result
+
+
+def test_custom_routing_patterns(lambda_context, mock_event):
+ """Test custom routing patterns beyond simple wildcards."""
+ # GIVEN events with different path formats
+ event1 = deepcopy(mock_event)
+ event2 = deepcopy(mock_event)
+
+ event1["info"]["channel"]["path"] = "/users/123/posts/456"
+ event1["events"] = [
+ {"id": "123", "payload": {"data": "user post data"}},
+ ]
+
+ event2["info"]["channel"]["path"] = "/organizations/abc/members/xyz"
+ event2["events"] = [
+ {"id": "123", "payload": {"data": "organization member data"}},
+ ]
+
+ # GIVEN an AppSyncEventsResolver with pattern-based routing
+ app = AppSyncEventsResolver()
+
+ # Define resolvers for different entity patterns
+ @app.on_publish(path="/users/*")
+ def user_resource_handler(payload):
+ path = app.current_event.info.channel_path
+ segments = path.split("/")
+ user_id = segments[2]
+ resource_type = segments[3]
+
+ return {"entity_type": "user", "entity_id": user_id, "resource_type": resource_type, "data": payload["data"]}
+
+ @app.on_publish(path="/organizations/*")
+ def org_resource_handler(payload):
+ path = app.current_event.info.channel_path
+ segments = path.split("/")
+ org_id = segments[2]
+ resource_type = segments[3]
+
+ return {
+ "entity_type": "organization",
+ "entity_id": org_id,
+ "resource_type": resource_type,
+ "data": payload["data"],
+ }
+
+ # WHEN we resolve the events
+ result1 = app.resolve(event1, lambda_context)
+ result2 = app.resolve(event2, lambda_context)
+
+ # THEN each event should be handled by the appropriate pattern-based resolver
+ assert result1["events"][0]["payload"]["entity_type"] == "user"
+ assert result1["events"][0]["payload"]["entity_id"] == "123"
+ assert result1["events"][0]["payload"]["resource_type"] == "posts"
+
+ assert result2["events"][0]["payload"]["entity_type"] == "organization"
+ assert result2["events"][0]["payload"]["entity_id"] == "abc"
+ assert result2["events"][0]["payload"]["resource_type"] == "members"
+
+
+def test_warning_on_invalid_response_format(lambda_context, mock_event):
+ """Test warning generation for invalid response formats."""
+ # GIVEN a sample publish event
+ mock_event["info"]["channel"]["path"] = "/default/test"
+ mock_event["events"] = [
+ {"id": "123", "payload": {"data": "test data"}},
+ {"id": "456", "payload": {"data": "more data"}},
+ ]
+
+ # GIVEN an AppSyncEventsResolver with an aggregate handler that returns non-list
+ app = AppSyncEventsResolver()
+
+ @app.on_publish(path="/default/*", aggregate=True)
+ def invalid_format_handler(payload):
+ # Incorrectly return a dict instead of a list
+ return {"processed": True, "count": len(payload)}
+
+ # WHEN we resolve the event
+ # THEN a warning should be generated about the response format
+ with pytest.warns(UserWarning, match="Response must be a list when using aggregate"):
+ result = app.resolve(mock_event, lambda_context)
+
+ # The result should still contain what was returned
+ assert "events" in result
+ assert result["events"]["processed"] is True
+ assert result["events"]["count"] == 2
+
+
+def test_router_and_resolver_clear_context_after_resolution(lambda_context, mock_event):
+ """Test that both router and resolver's context are cleared after resolution."""
+ # GIVEN a sample publish event
+ mock_event["events"] = [
+ {"id": "123", "payload": {"data": "test data"}},
+ ]
+
+ # GIVEN a router with context data
+ router = Router()
+ router.append_context(router_key="router_value")
+
+ @router.on_publish(path="/default/*")
+ def router_handler(payload):
+ assert router.context["router_key"] == "router_value"
+ assert router.context["test_var"] == "app_value"
+ return {"processed": True}
+
+ # GIVEN an AppSyncEventsResolver with context data
+ app = AppSyncEventsResolver()
+ app.append_context(test_var="app_value")
+
+ # Include the router and merge contexts
+ app.include_router(router)
+
+ # WHEN we resolve the event
+ app.resolve(mock_event, lambda_context)
+
+ # THEN both contexts should be cleared
+ assert app.context == {}
+ assert router.context == {}
+
+
+def test_sync_and_async_router_inclusion(lambda_context, mock_event):
+ """Test including multiple routers with both sync and async handlers."""
+ # GIVEN a sample publish event
+ mock_event["info"]["channel"]["path"] = "/notifications/test"
+ mock_event["events"] = [
+ {"id": "123", "payload": {"message": "test notification"}},
+ ]
+
+ # GIVEN a router with synchronous handlers
+ sync_router = Router()
+
+ @sync_router.on_publish(path="/notifications/*")
+ def sync_handler(payload):
+ return {"sync": True, "message": payload["message"]}
+
+ # GIVEN another router with asynchronous handlers
+ async_router = Router()
+
+ @async_router.async_on_publish(path="/notifications/*")
+ async def async_handler(event):
+ await asyncio.sleep(0.01)
+ return {"async": True, "message": event["message"]}
+
+ # GIVEN an AppSyncEventsResolver that includes both routers
+ app = AppSyncEventsResolver()
+ app.include_router(sync_router)
+ app.include_router(async_router)
+
+ # WHEN we resolve the event
+ with pytest.warns(UserWarning, match="Both synchronous and asynchronous resolvers found"):
+ result = app.resolve(mock_event, lambda_context)
+
+ # THEN the sync handler should take precedence
+ expected_result = {
+ "events": [
+ {"id": "123", "payload": {"sync": True, "message": "test notification"}},
+ ],
+ }
+ assert result == expected_result
+
+
+def test_aws_lambda_context_availability_in_handlers(lambda_context, mock_event):
+ """Test that Lambda context is available in handlers."""
+ # GIVEN a sample publish event
+ mock_event["info"]["channel"]["path"] = "/default/test"
+ mock_event["events"] = [
+ {"id": "123", "payload": {"data": "test data"}},
+ ]
+
+ # GIVEN an AppSyncEventsResolver with a handler that uses Lambda context
+ app = AppSyncEventsResolver()
+
+ @app.on_publish(path="/default/*")
+ def context_aware_handler(payload):
+ # Access Lambda context information
+ return {
+ "processed": True,
+ "function_name": app.lambda_context.function_name,
+ "request_id": app.lambda_context.aws_request_id,
+ "function_arn": app.lambda_context.invoked_function_arn,
+ "payload_data": payload["data"],
+ }
+
+ # WHEN we resolve the event
+ result = app.resolve(mock_event, lambda_context)
+
+ # THEN Lambda context information should be included in the result
+ assert result["events"][0]["payload"]["function_name"] == lambda_context.function_name
+ assert result["events"][0]["payload"]["request_id"] == lambda_context.aws_request_id
+ assert result["events"][0]["payload"]["function_arn"] == lambda_context.invoked_function_arn
+ assert result["events"][0]["payload"]["payload_data"] == "test data"
+
+
+def test_router_lambda_context_shared(lambda_context, mock_event):
+ """Test that Lambda context is shared with included routers."""
+ # GIVEN a sample publish event
+ mock_event["info"]["channel"]["path"] = "/router/test"
+ mock_event["events"] = [
+ {"id": "123", "payload": {"data": "test data"}},
+ ]
+
+ # GIVEN a router with a handler that uses Lambda context
+ router = Router()
+
+ @router.on_publish(path="/router/*")
+ def router_context_handler(payload):
+ # Access Lambda context from the router
+ return {
+ "from_router": True,
+ "function_name": router.lambda_context.function_name,
+ "request_id": router.lambda_context.aws_request_id,
+ "payload_data": payload["data"],
+ }
+
+ # GIVEN an AppSyncEventsResolver that includes the router
+ app = AppSyncEventsResolver()
+ app.include_router(router)
+
+ # WHEN we resolve the event
+ result = app.resolve(mock_event, lambda_context)
+
+ # THEN the router should have access to the same Lambda context
+ assert result["events"][0]["payload"]["from_router"] is True
+ assert result["events"][0]["payload"]["function_name"] == lambda_context.function_name
+ assert result["events"][0]["payload"]["request_id"] == lambda_context.aws_request_id
+ assert result["events"][0]["payload"]["payload_data"] == "test data"
+
+
+def test_current_event_availability(lambda_context, mock_event):
+ """Test that current_event is properly available to handlers."""
+ # GIVEN a sample publish event with extra metadata
+ mock_event["info"]["channel"]["path"] = "/default/test"
+ mock_event["events"] = [
+ {"id": "123", "payload": {"data": "test data"}},
+ ]
+
+ # GIVEN an AppSyncEventsResolver with a handler that accesses current_event
+ app = AppSyncEventsResolver()
+
+ @app.on_publish(path="/default/*")
+ def event_aware_handler(payload):
+ # Access the full event object for additional context
+ return {
+ "processed": True,
+ "x-forwarded-for": app.current_event.request_headers["x-forwarded-for"],
+ "payload_data": payload["data"],
+ }
+
+ # WHEN we resolve the event
+ result = app.resolve(mock_event, lambda_context)
+
+ # THEN the handler should have access to the full event information
+ assert result["events"][0]["payload"]["processed"] is True
+ assert result["events"][0]["payload"]["x-forwarded-for"] == mock_event["request"]["headers"]["x-forwarded-for"]
+ assert result["events"][0]["payload"]["payload_data"] == "test data"
+
+
+def test_router_current_event_shared(lambda_context, mock_event):
+ """Test that current_event is shared with included routers."""
+ # GIVEN a sample publish event with extra metadata
+ mock_event["info"]["channel"]["path"] = "/router/test"
+ mock_event["events"] = [
+ {"id": "123", "payload": {"data": "test data"}},
+ ]
+
+ # GIVEN a router with a handler that accesses current_event
+ router = Router()
+
+ @router.on_publish(path="/router/*")
+ def router_event_handler(payload):
+ # Access event information from the router
+ return {
+ "processed": True,
+ "x-forwarded-for": app.current_event.request_headers["x-forwarded-for"],
+ "payload_data": payload["data"],
+ }
+
+ # GIVEN an AppSyncEventsResolver that includes the router
+ app = AppSyncEventsResolver()
+ app.include_router(router)
+
+ # WHEN we resolve the event
+ result = app.resolve(mock_event, lambda_context)
+
+ # THEN the router should have access to the same event information
+ assert result["events"][0]["payload"]["processed"] is True
+ assert result["events"][0]["payload"]["x-forwarded-for"] == mock_event["request"]["headers"]["x-forwarded-for"]
+ assert result["events"][0]["payload"]["payload_data"] == "test data"
+
+
+@pytest.mark.skip(reason="Not implemented yet")
+def test_channel_path_normalization(lambda_context, mock_event):
+ """Test that channel paths are properly normalized before matching."""
+ # GIVEN sample publish events with different path formats
+ event1 = deepcopy(mock_event)
+ event2 = deepcopy(mock_event)
+
+ event1["info"]["channel"]["path"] = "/test"
+ event1["events"] = [
+ {"id": "123", "payload": {"data": "data1"}},
+ ]
+
+ event2["info"]["channel"]["path"] = "/test/"
+ event2["events"] = [
+ {"id": "456", "payload": {"data": "data2"}},
+ ]
+
+ # GIVEN an AppSyncEventsResolver with a handler
+ app = AppSyncEventsResolver()
+
+ @app.on_publish(path="/test") # Register with path without trailing slash
+ def test_handler(payload):
+ return {"normalized": True, "data": payload["data"]}
+
+ # WHEN we resolve both events
+ result1 = app.resolve(event1, lambda_context)
+ result2 = app.resolve(event2, lambda_context)
+
+ # THEN both events should be handled consistently
+ expected_result1 = {
+ "events": [
+ {"id": "123", "payload": {"normalized": True, "data": "data1"}},
+ ],
+ }
+ assert result1 == expected_result1
+
+ # With proper normalization, this should also match
+ expected_result2 = {
+ "events": [
+ {"id": "456", "payload": {"normalized": True, "data": "data2"}},
+ ],
+ }
+ assert result2 == expected_result2
+
+
+def test_subscribe_event_with_error_handling(lambda_context, mock_event):
+ """Test error handling during publish event processing."""
+ # GIVEN a sample publish event
+ mock_event["info"]["operation"] = "SUBSCRIBE"
+ mock_event["info"]["channel"]["path"] = "/default/powertools"
+ del mock_event["events"] # SUBSCRIBE events are not supported
+
+ # GIVEN an AppSyncEventsResolver with a resolver that raises an exception
+ app = AppSyncEventsResolver()
+
+ @app.on_subscribe(path="/default/*")
+ def test_handler():
+ raise ValueError("Test error")
+
+ # WHEN we resolve the event
+ result = app.resolve(mock_event, lambda_context)
+
+ # THEN we should get an error response
+ assert "error" in result
+ assert "ValueError - Test error" in result["error"]
+
+
+def test_subscribe_event_with_valid_return(lambda_context, mock_event):
+ """Test error handling during publish event processing."""
+ # GIVEN a sample publish event
+ mock_event["info"]["operation"] = "SUBSCRIBE"
+ mock_event["info"]["channel"]["path"] = "/default/powertools"
+
+ # GIVEN an AppSyncEventsResolver with a resolver that returns ok
+ app = AppSyncEventsResolver()
+
+ @app.on_subscribe(path="/default/*")
+ def test_handler():
+ return 1
+
+ # WHEN we resolve the event
+ result = app.resolve(mock_event, lambda_context)
+
+ # THEN we should return None because subscribe always must return None
+ assert result is None
+
+
+def test_subscribe_event_with_no_resolver(lambda_context, mock_event):
+ """Test error handling during publish event processing."""
+ # GIVEN a sample publish event
+ mock_event["info"]["operation"] = "SUBSCRIBE"
+ mock_event["info"]["channel"]["path"] = "/default/powertools"
+
+ # GIVEN an AppSyncEventsResolver with a resolver that returns ok
+ app = AppSyncEventsResolver()
+
+ @app.on_subscribe(path="/test")
+ def test_handler():
+ return 1
+
+ # WHEN we resolve the event
+ result = app.resolve(mock_event, lambda_context)
+
+ # THEN we should get an error response
+ assert not result
+
+
+def test_publish_events_throw_unauthorized_exception(lambda_context, mock_event):
+ """Test handling events with an empty payload."""
+ # GIVEN a sample publish event with empty events
+ mock_event["info"]["operation"] = "PUBLISH"
+ mock_event["info"]["channel"]["path"] = "/default/test"
+ mock_event["events"] = [
+ {"id": "123", "payload": {"data": "test data"}},
+ ]
+
+ # GIVEN an AppSyncEventsResolver
+ app = AppSyncEventsResolver()
+
+ @app.on_publish(path="/default/*", aggregate=True)
+ def handle_events(payload):
+ raise UnauthorizedException
+
+ # WHEN we resolve the event with unauthorized route
+ with pytest.raises(UnauthorizedException):
+ app.resolve(mock_event, lambda_context)
+
+
+def test_subscribe_events_throw_unauthorized_exception(lambda_context, mock_event):
+ """Test handling events with an empty payload."""
+ # GIVEN a sample publish event with empty events
+ mock_event["info"]["operation"] = "SUBSCRIBE"
+ mock_event["info"]["channel"]["path"] = "/default/test"
+
+ # GIVEN an AppSyncEventsResolver
+ app = AppSyncEventsResolver()
+
+ @app.on_subscribe(path="/default/*")
+ def handle_events():
+ raise UnauthorizedException
+
+ # WHEN we resolve the event with unauthorized route
+ with pytest.raises(UnauthorizedException):
+ app.resolve(mock_event, lambda_context)
diff --git a/tests/functional/event_handler/required_dependencies/appsync/test_appsync_single_resolvers.py b/tests/functional/event_handler/required_dependencies/appsync/test_appsync_single_resolvers.py
index 3ac91337002..4ef902c340a 100644
--- a/tests/functional/event_handler/required_dependencies/appsync/test_appsync_single_resolvers.py
+++ b/tests/functional/event_handler/required_dependencies/appsync/test_appsync_single_resolvers.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import asyncio
import pytest
diff --git a/tests/functional/event_handler/required_dependencies/test_api_gateway.py b/tests/functional/event_handler/required_dependencies/test_api_gateway.py
index fdab6080f27..349f220ecde 100644
--- a/tests/functional/event_handler/required_dependencies/test_api_gateway.py
+++ b/tests/functional/event_handler/required_dependencies/test_api_gateway.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import base64
import json
import re
@@ -8,7 +10,6 @@
from enum import Enum
from json import JSONEncoder
from pathlib import Path
-from typing import Dict
import pytest
@@ -26,9 +27,13 @@
)
from aws_lambda_powertools.event_handler.exceptions import (
BadRequestError,
+ ForbiddenError,
InternalServerError,
NotFoundError,
+ RequestEntityTooLargeError,
+ RequestTimeoutError,
ServiceError,
+ ServiceUnavailableError,
UnauthorizedError,
)
from aws_lambda_powertools.shared import constants
@@ -44,7 +49,7 @@
def read_media(file_name: str) -> bytes:
- path = Path(str(Path(__file__).parent.parent.parent.parent) + "/../docs/media/" + file_name)
+ path = Path(f"{str(Path(__file__).parent.parent.parent.parent)}/../docs/media/{file_name}")
return path.read_bytes()
@@ -638,7 +643,7 @@ def test_rest_api():
expected_dict = {"foo": "value", "second": Decimal("100.01")}
@app.get("/my/path")
- def rest_func() -> Dict:
+ def rest_func() -> dict:
return expected_dict
# WHEN calling the event handler
@@ -873,6 +878,21 @@ def unauthorized_error():
expected = {"statusCode": 401, "message": "Unauthorized"}
assert result["body"] == json_dump(expected)
+ # GIVEN a ForbiddenError
+ @app.get(rule="/forbidden-error", cors=False)
+ def forbidden_error():
+ raise ForbiddenError("Access denied")
+
+ # WHEN calling the handler
+ # AND path is /forbidden-error
+ result = app({"path": "/forbidden-error", "httpMethod": "GET"}, None)
+ # THEN return the forbidden error response
+ # AND status code equals 403
+ assert result["statusCode"] == 403
+ assert result["multiValueHeaders"]["Content-Type"] == [content_types.APPLICATION_JSON]
+ expected = {"statusCode": 403, "message": "Access denied"}
+ assert result["body"] == json_dump(expected)
+
# GIVEN an NotFoundError
@app.get(rule="/not-found-error", cors=False)
def not_found_error():
@@ -888,6 +908,36 @@ def not_found_error():
expected = {"statusCode": 404, "message": "Not found"}
assert result["body"] == json_dump(expected)
+ # GIVEN a RequestTimeoutError
+ @app.get(rule="/request-timeout-error", cors=False)
+ def request_timeout_error():
+ raise RequestTimeoutError("Request timed out")
+
+ # WHEN calling the handler
+ # AND path is /request-timeout-error
+ result = app({"path": "/request-timeout-error", "httpMethod": "GET"}, None)
+ # THEN return the request timeout error response
+ # AND status code equals 408
+ assert result["statusCode"] == 408
+ assert result["multiValueHeaders"]["Content-Type"] == [content_types.APPLICATION_JSON]
+ expected = {"statusCode": 408, "message": "Request timed out"}
+ assert result["body"] == json_dump(expected)
+
+ # GIVEN a RequestEntityTooLargeError
+ @app.get(rule="/request-entity-too-large-error", cors=False)
+ def request_entity_too_large_error():
+ raise RequestEntityTooLargeError("Request payload too large")
+
+ # WHEN calling the handler
+ # AND path is /request-entity-too-large-error
+ result = app({"path": "/request-entity-too-large-error", "httpMethod": "GET"}, None)
+ # THEN return the request entity too large error response
+ # AND status code equals 413
+ assert result["statusCode"] == 413
+ assert result["multiValueHeaders"]["Content-Type"] == [content_types.APPLICATION_JSON]
+ expected = {"statusCode": 413, "message": "Request payload too large"}
+ assert result["body"] == json_dump(expected)
+
# GIVEN an InternalServerError
@app.get(rule="/internal-server-error", cors=False)
def internal_server_error():
@@ -903,6 +953,21 @@ def internal_server_error():
expected = {"statusCode": 500, "message": "Internal server error"}
assert result["body"] == json_dump(expected)
+ # GIVEN a ServiceUnavailableError
+ @app.get(rule="/service-unavailable-error", cors=False)
+ def service_unavailable_error():
+ raise ServiceUnavailableError("Service is temporarily unavailable")
+
+ # WHEN calling the handler
+ # AND path is /service-unavailable-error
+ result = app({"path": "/service-unavailable-error", "httpMethod": "GET"}, None)
+ # THEN return the service unavailable error response
+ # AND status code equals 503
+ assert result["statusCode"] == 503
+ assert result["multiValueHeaders"]["Content-Type"] == [content_types.APPLICATION_JSON]
+ expected = {"statusCode": 503, "message": "Service is temporarily unavailable"}
+ assert result["body"] == json_dump(expected)
+
# GIVEN an ServiceError with a custom status code
@app.get(rule="/service-error")
def service_error():
@@ -1123,7 +1188,7 @@ def custom_serializer(data) -> str:
app = ApiGatewayResolver(serializer=custom_serializer)
@app.get("/custom_serializer")
- def get_custom_values() -> Dict:
+ def get_custom_values() -> dict:
return {"values": deque(["name", "age"])}
# WHEN calling handler
diff --git a/tests/functional/event_handler/required_dependencies/test_api_middlewares.py b/tests/functional/event_handler/required_dependencies/test_api_middlewares.py
index f9bc62d5474..3f19500f4a5 100644
--- a/tests/functional/event_handler/required_dependencies/test_api_middlewares.py
+++ b/tests/functional/event_handler/required_dependencies/test_api_middlewares.py
@@ -1,4 +1,6 @@
-from typing import List
+from __future__ import annotations
+
+from typing import TYPE_CHECKING
import pytest
@@ -20,9 +22,12 @@
from aws_lambda_powertools.event_handler.middlewares.schema_validation import (
SchemaValidationMiddleware,
)
-from aws_lambda_powertools.event_handler.types import EventHandlerInstance
from tests.functional.utils import load_event
+if TYPE_CHECKING:
+ from aws_lambda_powertools.event_handler.types import EventHandlerInstance
+
+
API_REST_EVENT = load_event("apiGatewayProxyEvent.json")
API_RESTV2_EVENT = load_event("apiGatewayProxyV2Event_GET.json")
@@ -362,14 +367,14 @@ def test_api_gateway_middleware_order_with_include_router_last(app: EventHandler
router = Router()
def global_app_middleware(app: EventHandlerInstance, next_middleware: NextMiddleware):
- middleware_order: List[str] = router.context.get("middleware_order", [])
+ middleware_order: list[str] = router.context.get("middleware_order", [])
middleware_order.append("app")
app.append_context(middleware_order=middleware_order)
return next_middleware(app)
def global_router_middleware(router: EventHandlerInstance, next_middleware: NextMiddleware):
- middleware_order: List[str] = router.context.get("middleware_order", [])
+ middleware_order: list[str] = router.context.get("middleware_order", [])
middleware_order.append("router")
router.append_context(middleware_order=middleware_order)
@@ -439,14 +444,14 @@ def test_api_gateway_middleware_order_with_include_router_first(app: EventHandle
router = Router()
def global_app_middleware(app: EventHandlerInstance, next_middleware: NextMiddleware):
- middleware_order: List[str] = router.context.get("middleware_order", [])
+ middleware_order: list[str] = router.context.get("middleware_order", [])
middleware_order.append("app")
app.append_context(middleware_order=middleware_order)
return next_middleware(app)
def global_router_middleware(router: EventHandlerInstance, next_middleware: NextMiddleware):
- middleware_order: List[str] = router.context.get("middleware_order", [])
+ middleware_order: list[str] = router.context.get("middleware_order", [])
middleware_order.append("router")
router.append_context(middleware_order=middleware_order)
diff --git a/tests/functional/event_handler/required_dependencies/test_base_path.py b/tests/functional/event_handler/required_dependencies/test_base_path.py
index 7fc5a0eced7..bbb98c0dc46 100644
--- a/tests/functional/event_handler/required_dependencies/test_base_path.py
+++ b/tests/functional/event_handler/required_dependencies/test_base_path.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
from aws_lambda_powertools.event_handler import (
ALBResolver,
APIGatewayHttpResolver,
diff --git a/tests/functional/event_handler/required_dependencies/test_lambda_function_url.py b/tests/functional/event_handler/required_dependencies/test_lambda_function_url.py
index 41baed68a7c..cdb0abb4d91 100644
--- a/tests/functional/event_handler/required_dependencies/test_lambda_function_url.py
+++ b/tests/functional/event_handler/required_dependencies/test_lambda_function_url.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
from aws_lambda_powertools.event_handler import (
LambdaFunctionUrlResolver,
Response,
diff --git a/tests/functional/event_handler/required_dependencies/test_router.py b/tests/functional/event_handler/required_dependencies/test_router.py
index d96f5035114..05c2260c8ee 100644
--- a/tests/functional/event_handler/required_dependencies/test_router.py
+++ b/tests/functional/event_handler/required_dependencies/test_router.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
from aws_lambda_powertools.event_handler import (
ALBResolver,
APIGatewayHttpResolver,
diff --git a/tests/functional/event_handler/required_dependencies/test_vpc_lattice.py b/tests/functional/event_handler/required_dependencies/test_vpc_lattice.py
index 7e752c79274..73168a36408 100644
--- a/tests/functional/event_handler/required_dependencies/test_vpc_lattice.py
+++ b/tests/functional/event_handler/required_dependencies/test_vpc_lattice.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
from aws_lambda_powertools.event_handler import (
Response,
VPCLatticeResolver,
diff --git a/tests/functional/event_handler/required_dependencies/test_vpc_latticev2.py b/tests/functional/event_handler/required_dependencies/test_vpc_latticev2.py
index e249b7d2ba1..a83fcb3c30d 100644
--- a/tests/functional/event_handler/required_dependencies/test_vpc_latticev2.py
+++ b/tests/functional/event_handler/required_dependencies/test_vpc_latticev2.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
from aws_lambda_powertools.event_handler import (
Response,
VPCLatticeV2Resolver,
diff --git a/tests/functional/feature_flags/_boto3/test_feature_flags.py b/tests/functional/feature_flags/_boto3/test_feature_flags.py
index 08035f2989f..0a41f04c1f1 100644
--- a/tests/functional/feature_flags/_boto3/test_feature_flags.py
+++ b/tests/functional/feature_flags/_boto3/test_feature_flags.py
@@ -1,6 +1,7 @@
+from __future__ import annotations
+
from io import BytesIO
from json import dumps
-from typing import Dict, List, Optional
import boto3
import pytest
@@ -37,10 +38,10 @@ def config():
def init_feature_flags(
mocker,
- mock_schema: Dict,
+ mock_schema: dict,
config: Config,
envelope: str = "",
- jmespath_options: Optional[Dict] = None,
+ jmespath_options: dict | None = None,
) -> FeatureFlags:
environment = "test_env"
application = "test_app"
@@ -689,7 +690,7 @@ def test_multiple_features_enabled(mocker, config):
},
}
feature_flags = init_feature_flags(mocker, mocked_app_config_schema, config)
- enabled_list: List[str] = feature_flags.get_enabled_features(context={"tenant_id": "6", "username": "a"})
+ enabled_list: list[str] = feature_flags.get_enabled_features(context={"tenant_id": "6", "username": "a"})
assert enabled_list == expected_value
@@ -1430,7 +1431,7 @@ def test_get_all_enabled_features_boolean_and_non_boolean(mocker, config):
}
feature_flags = init_feature_flags(mocker, mocked_app_config_schema, config)
- enabled_list: List[str] = feature_flags.get_enabled_features(context={"tenant_id": "6", "username": "a"})
+ enabled_list: list[str] = feature_flags.get_enabled_features(context={"tenant_id": "6", "username": "a"})
assert enabled_list == expected_value
@@ -1442,7 +1443,7 @@ def test_get_all_enabled_features_non_boolean_truthy_defaults(mocker, config):
}
feature_flags = init_feature_flags(mocker, mocked_app_config_schema, config)
- enabled_list: List[str] = feature_flags.get_enabled_features(context={"tenant_id": "6", "username": "a"})
+ enabled_list: list[str] = feature_flags.get_enabled_features(context={"tenant_id": "6", "username": "a"})
assert enabled_list == expected_value
diff --git a/tests/functional/feature_flags/_boto3/test_schema_validation.py b/tests/functional/feature_flags/_boto3/test_schema_validation.py
index 45b4c7dbeda..b7bf8392ada 100644
--- a/tests/functional/feature_flags/_boto3/test_schema_validation.py
+++ b/tests/functional/feature_flags/_boto3/test_schema_validation.py
@@ -1,8 +1,9 @@
+from __future__ import annotations
+
import re
import pytest
-from aws_lambda_powertools.logging.logger import Logger # noqa: F401
from aws_lambda_powertools.utilities.feature_flags.exceptions import (
SchemaValidationError,
)
@@ -843,9 +844,7 @@ def test_validate_time_condition_between_days_range_invalid_condition_value(cond
CONDITION_KEY: TimeKeys.CURRENT_DAY_OF_WEEK.value,
}
rule_name = "dummy"
- match_str = (
- f"condition value DAYS must represent a day of the week in 'TimeValues' enum, rule={rule_name}" # noqa: E501
- )
+ match_str = f"condition value DAYS must represent a day of the week in 'TimeValues' enum, rule={rule_name}" # noqa: E501
# WHEN calling validate_condition
# THEN raise SchemaValidationError
with pytest.raises(
diff --git a/tests/functional/feature_flags/_boto3/test_time_based_actions.py b/tests/functional/feature_flags/_boto3/test_time_based_actions.py
index 872f2ac2862..640434f1f46 100644
--- a/tests/functional/feature_flags/_boto3/test_time_based_actions.py
+++ b/tests/functional/feature_flags/_boto3/test_time_based_actions.py
@@ -1,5 +1,7 @@
+from __future__ import annotations
+
import datetime
-from typing import Any, Dict, Optional, Tuple
+from typing import TYPE_CHECKING, Any
from botocore.config import Config
from dateutil.tz import gettz
@@ -18,14 +20,16 @@
TimeKeys,
TimeValues,
)
-from aws_lambda_powertools.utilities.feature_flags.types import JSONType
+
+if TYPE_CHECKING:
+ from aws_lambda_powertools.utilities.feature_flags.types import JSONType
def evaluate_mocked_schema(
mocker,
- rules: Dict[str, Any],
- mocked_time: Tuple[int, int, int, int, int, int, datetime.tzinfo], # year, month, day, hour, minute, second
- context: Optional[Dict[str, Any]] = None,
+ rules: dict[str, Any],
+ mocked_time: tuple[int, int, int, int, int, int, datetime.tzinfo], # year, month, day, hour, minute, second
+ context: dict[str, Any] | None = None,
) -> JSONType:
"""
This helper does the following:
@@ -510,7 +514,7 @@ def test_time_based_multiple_conditions_utc_days_range_and_certain_hours_rule_ma
def test_time_based_multiple_conditions_utc_days_range_and_certain_hours_no_rule_match(mocker):
- def evaluate(mocked_time: Tuple[int, int, int, int, int, int, datetime.tzinfo]):
+ def evaluate(mocked_time: tuple[int, int, int, int, int, int, datetime.tzinfo]):
evaluate_mocked_schema(
mocker=mocker,
rules={
diff --git a/tests/functional/idempotency/_boto3/conftest.py b/tests/functional/idempotency/_boto3/conftest.py
index 0b113c08753..cfc1d994619 100644
--- a/tests/functional/idempotency/_boto3/conftest.py
+++ b/tests/functional/idempotency/_boto3/conftest.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import datetime
import json
from decimal import Decimal
diff --git a/tests/functional/logger/required_dependencies/test_logger.py b/tests/functional/logger/required_dependencies/test_logger.py
index a508209b594..e799dce9b60 100644
--- a/tests/functional/logger/required_dependencies/test_logger.py
+++ b/tests/functional/logger/required_dependencies/test_logger.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import functools
import inspect
import io
@@ -10,7 +12,7 @@
import sys
from collections import namedtuple
from datetime import datetime, timezone
-from typing import Any, Callable, Dict, Iterable, List, Optional, Union
+from typing import TYPE_CHECKING, Any
import pytest
@@ -24,6 +26,9 @@
from aws_lambda_powertools.shared import constants
from aws_lambda_powertools.utilities.data_classes import S3Event, event_source
+if TYPE_CHECKING:
+ from collections.abc import Callable, Iterable
+
@pytest.fixture
def stdout():
@@ -177,12 +182,12 @@ def handler(event, context):
# THEN verify the number of logs falls within the expected range
logs = list(stdout.getvalue().strip().split("\n"))
- assert (
- len(logs) >= minimum_logs_excepted
- ), f"Log count {len(logs)} should be at least {minimum_logs_excepted} for sampling rate {sampling_rate}"
- assert (
- len(logs) <= maximum_logs_excepted
- ), f"Log count {len(logs)} should be at most {maximum_logs_excepted} for sampling rate {sampling_rate}"
+ assert len(logs) >= minimum_logs_excepted, (
+ f"Log count {len(logs)} should be at least {minimum_logs_excepted} for sampling rate {sampling_rate}"
+ )
+ assert len(logs) <= maximum_logs_excepted, (
+ f"Log count {len(logs)} should be at most {maximum_logs_excepted} for sampling rate {sampling_rate}"
+ )
@pytest.mark.parametrize(
@@ -218,12 +223,12 @@ def handler(event, context):
# THEN verify the number of logs falls within the expected range
logs = list(stdout.getvalue().strip().split("\n"))
- assert (
- len(logs) >= minimum_logs_excepted
- ), f"Log count {len(logs)} should be at least {minimum_logs_excepted} for sampling rate {sampling_rate}"
- assert (
- len(logs) <= maximum_logs_excepted
- ), f"Log count {len(logs)} should be at most {maximum_logs_excepted} for sampling rate {sampling_rate}"
+ assert len(logs) >= minimum_logs_excepted, (
+ f"Log count {len(logs)} should be at least {minimum_logs_excepted} for sampling rate {sampling_rate}"
+ )
+ assert len(logs) <= maximum_logs_excepted, (
+ f"Log count {len(logs)} should be at most {maximum_logs_excepted} for sampling rate {sampling_rate}"
+ )
def test_inject_lambda_context(lambda_context, stdout, service_name):
@@ -337,7 +342,6 @@ def handler(event, context):
def test_inject_lambda_cold_start_with_provisioned_concurrency(monkeypatch, lambda_context, stdout, service_name):
-
# GIVEN Provisioned Concurrency is enabled via AWS_LAMBDA_INITIALIZATION_TYPE environment variable
# AND Logger's cold start flag is explicitly set to True (simulating fresh module import)
monkeypatch.setenv("AWS_LAMBDA_INITIALIZATION_TYPE", "provisioned-concurrency")
@@ -638,6 +642,41 @@ def test_logger_exception_extract_exception_name(stdout, service_name):
assert "ValueError" == log["exception_name"]
+@pytest.mark.skipif(sys.version_info < (3, 11), reason="This only works in Python 3.11+")
+def test_logger_exception_extract_exception_notes(stdout, service_name):
+ # GIVEN Logger is initialized
+ logger = Logger(service=service_name, stream=stdout)
+
+ # WHEN calling a logger.exception with a ValueError and notes
+ try:
+ raise ValueError("something went wrong")
+ except Exception as error:
+ error.add_note("something went wrong")
+ error.add_note("something went wrong again")
+ logger.exception("Received an exception")
+
+ # THEN we expect a "exception_name" to be "ValueError"
+ # THEN we except to have exception_notes in the exception
+ log = capture_logging_output(stdout)
+ assert len(log["exception_notes"]) == 2
+ assert log["exception_notes"][0] == "something went wrong"
+ assert log["exception_notes"][1] == "something went wrong again"
+ assert "ValueError" == log["exception_name"]
+
+
+def test_logger_exception_should_not_fail_with_exception_block(stdout, service_name):
+ # GIVEN Logger is initialized
+ logger = Logger(service=service_name, stream=stdout)
+
+ # WHEN calling a logger.exception with a ValueError and outside of a try/except block
+ logger.exception("Received an exception")
+
+ # THEN the log output should not contain "exception_name" or "exception" and not fail
+ log = capture_logging_output(stdout)
+ assert "exception_name" not in log
+ assert "exception" not in log
+
+
def test_logger_set_correlation_id(lambda_context, stdout, service_name):
# GIVEN
logger = Logger(service=service_name, stream=stdout)
@@ -820,12 +859,12 @@ def test_logger_custom_powertools_formatter_clear_state(stdout, service_name, la
class CustomFormatter(LambdaPowertoolsFormatter):
def __init__(
self,
- json_serializer: Optional[Callable[[Dict], str]] = None,
- json_deserializer: Optional[Callable[[Union[Dict, str, bool, int, float]], str]] = None,
- json_default: Optional[Callable[[Any], Any]] = None,
- datefmt: Optional[str] = None,
+ json_serializer: Callable[[dict], str] | None = None,
+ json_deserializer: Callable[[dict, str, bool, int, float], str] | None = None,
+ json_default: Callable[[Any], Any] | None = None,
+ datefmt: str | None = None,
use_datetime_directive: bool = False,
- log_record_order: Optional[List[str]] = None,
+ log_record_order: list[str] | None = None,
utc: bool = False,
**kwargs,
):
diff --git a/tests/functional/logger/required_dependencies/test_logger_powertools_formatter.py b/tests/functional/logger/required_dependencies/test_logger_powertools_formatter.py
index a5189d783d7..2b6f9349340 100644
--- a/tests/functional/logger/required_dependencies/test_logger_powertools_formatter.py
+++ b/tests/functional/logger/required_dependencies/test_logger_powertools_formatter.py
@@ -1,5 +1,7 @@
"""aws_lambda_logging tests."""
+from __future__ import annotations
+
import io
import json
import os
diff --git a/tests/functional/logger/required_dependencies/test_logger_utils.py b/tests/functional/logger/required_dependencies/test_logger_utils.py
index 53a94d612ad..f0a2baf3cf4 100644
--- a/tests/functional/logger/required_dependencies/test_logger_utils.py
+++ b/tests/functional/logger/required_dependencies/test_logger_utils.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import io
import json
import logging
diff --git a/tests/functional/logger/required_dependencies/test_logger_with_package_logger.py b/tests/functional/logger/required_dependencies/test_logger_with_package_logger.py
index 2dfd6016333..e34972b34ad 100644
--- a/tests/functional/logger/required_dependencies/test_logger_with_package_logger.py
+++ b/tests/functional/logger/required_dependencies/test_logger_with_package_logger.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import io
import json
import logging
diff --git a/tests/functional/logger/required_dependencies/test_powertools_logger_buffer.py b/tests/functional/logger/required_dependencies/test_powertools_logger_buffer.py
index 44e87d6102c..7ee3d4c97ff 100644
--- a/tests/functional/logger/required_dependencies/test_powertools_logger_buffer.py
+++ b/tests/functional/logger/required_dependencies/test_powertools_logger_buffer.py
@@ -1,5 +1,7 @@
"""aws_lambda_logging tests."""
+from __future__ import annotations
+
import io
import json
import random
@@ -48,7 +50,6 @@ def capture_multiple_logging_statements_output(stdout):
@pytest.mark.parametrize("log_level", ["DEBUG", "WARNING", "INFO"])
def test_logger_buffer_with_minimum_level_warning(log_level, stdout, service_name, monkeypatch):
-
monkeypatch.setenv(constants.XRAY_TRACE_ID_ENV, "1-67c39786-5908a82a246fb67f3089263f")
# GIVEN A logger configured with a buffer and minimum log level set to WARNING
@@ -523,3 +524,23 @@ def handler(event, context):
# THEN Verify buffer for the original trace ID is cleared
assert not logger._buffer_cache.get("1-67c39786-5908a82a246fb67f3089263f")
+
+
+def test_warning_when_alc_less_verbose_than_buffer(stdout, monkeypatch):
+ # GIVEN Lambda ALC set to INFO
+ monkeypatch.setenv("AWS_LAMBDA_LOG_LEVEL", "INFO")
+ # Set initial trace ID for first Lambda invocation
+ monkeypatch.setenv(constants.XRAY_TRACE_ID_ENV, "1-67c39786-5908a82a246fb67f3089263f")
+
+ # WHEN creating a logger with DEBUG buffer level
+ # THEN a warning should be emitted
+ with pytest.warns(PowertoolsUserWarning, match="Advanced Logging Controls*"):
+ logger = Logger(service="test", level="DEBUG", buffer_config=LoggerBufferConfig(buffer_at_verbosity="DEBUG"))
+
+ # AND logging a debug message
+ logger.debug("This is a debug")
+
+ # AND flushing buffer
+ # THEN another warning should be emitted about ALC and buffer level mismatch
+ with pytest.warns(PowertoolsUserWarning, match="Advanced Logging Controls*"):
+ logger.flush_buffer()
diff --git a/tests/functional/metrics/conftest.py b/tests/functional/metrics/conftest.py
index e9b4edb5935..f0b3766a57d 100644
--- a/tests/functional/metrics/conftest.py
+++ b/tests/functional/metrics/conftest.py
@@ -1,4 +1,6 @@
-from typing import Any, Dict, List, Union
+from __future__ import annotations
+
+from typing import Any
import pytest
@@ -20,22 +22,22 @@ def reset_metric_set():
@pytest.fixture
-def metric_with_resolution() -> Dict[str, Union[str, int]]:
+def metric_with_resolution() -> dict[str, str | int]:
return {"name": "single_metric", "unit": MetricUnit.Count, "value": 1, "resolution": MetricResolution.High}
@pytest.fixture
-def metric() -> Dict[str, str]:
+def metric() -> dict[str, str]:
return {"name": "single_metric", "unit": MetricUnit.Count, "value": 1}
@pytest.fixture
-def metric_datadog() -> Dict[str, str]:
+def metric_datadog() -> dict[str, str]:
return {"name": "single_metric", "value": 1, "timestamp": 1691678198, "powertools": "datadog"}
@pytest.fixture
-def metrics() -> List[Dict[str, str]]:
+def metrics() -> list[dict[str, str]]:
return [
{"name": "metric_one", "unit": MetricUnit.Count, "value": 1},
{"name": "metric_two", "unit": MetricUnit.Count, "value": 1},
@@ -43,7 +45,7 @@ def metrics() -> List[Dict[str, str]]:
@pytest.fixture
-def metrics_same_name() -> List[Dict[str, str]]:
+def metrics_same_name() -> list[dict[str, str]]:
return [
{"name": "metric_one", "unit": MetricUnit.Count, "value": 1},
{"name": "metric_one", "unit": MetricUnit.Count, "value": 5},
@@ -51,12 +53,12 @@ def metrics_same_name() -> List[Dict[str, str]]:
@pytest.fixture
-def dimension() -> Dict[str, str]:
+def dimension() -> dict[str, str]:
return {"name": "test_dimension", "value": "test"}
@pytest.fixture
-def dimensions() -> List[Dict[str, str]]:
+def dimensions() -> list[dict[str, str]]:
return [
{"name": "test_dimension", "value": "test"},
{"name": "test_dimension_2", "value": "test"},
@@ -64,7 +66,7 @@ def dimensions() -> List[Dict[str, str]]:
@pytest.fixture
-def non_str_dimensions() -> List[Dict[str, Any]]:
+def non_str_dimensions() -> list[dict[str, Any]]:
return [
{"name": "test_dimension", "value": True},
{"name": "test_dimension_2", "value": 3},
@@ -82,15 +84,15 @@ def service() -> str:
@pytest.fixture
-def metadata() -> Dict[str, str]:
+def metadata() -> dict[str, str]:
return {"key": "username", "value": "test"}
@pytest.fixture
-def a_hundred_metrics() -> List[Dict[str, str]]:
+def a_hundred_metrics() -> list[dict[str, str]]:
return [{"name": f"metric_{i}", "unit": "Count", "value": 1} for i in range(100)]
@pytest.fixture
-def a_hundred_metric_values() -> List[Dict[str, str]]:
+def a_hundred_metric_values() -> list[dict[str, str]]:
return [{"name": "metric", "unit": "Count", "value": i} for i in range(100)]
diff --git a/tests/functional/metrics/datadog/test_metrics_datadog.py b/tests/functional/metrics/datadog/test_metrics_datadog.py
index 2e677170244..80eb60ad467 100644
--- a/tests/functional/metrics/datadog/test_metrics_datadog.py
+++ b/tests/functional/metrics/datadog/test_metrics_datadog.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import json
import warnings
from collections import namedtuple
diff --git a/tests/functional/metrics/required_dependencies/test_metrics_cloudwatch_emf.py b/tests/functional/metrics/required_dependencies/test_metrics_cloudwatch_emf.py
index 50bee4950af..2e8a866ac10 100644
--- a/tests/functional/metrics/required_dependencies/test_metrics_cloudwatch_emf.py
+++ b/tests/functional/metrics/required_dependencies/test_metrics_cloudwatch_emf.py
@@ -1,8 +1,10 @@
+from __future__ import annotations
+
import datetime
import json
import warnings
from collections import namedtuple
-from typing import Dict, List, Optional, Union
+from typing import TYPE_CHECKING, Any
import pytest
@@ -23,16 +25,18 @@
from aws_lambda_powertools.metrics.provider.cloudwatch_emf.constants import (
MAX_DIMENSIONS,
)
-from aws_lambda_powertools.metrics.provider.cloudwatch_emf.types import (
- CloudWatchEMFOutput,
-)
+
+if TYPE_CHECKING:
+ from aws_lambda_powertools.metrics.provider.cloudwatch_emf.types import (
+ CloudWatchEMFOutput,
+ )
def serialize_metrics(
- metrics: List[Dict],
- dimensions: List[Dict],
+ metrics: list[dict[str, Any]],
+ dimensions: list[dict[str, Any]],
namespace: str,
- metadatas: Optional[List[Dict]] = None,
+ metadatas: list[dict] | None = None,
) -> CloudWatchEMFOutput:
"""Helper function to build EMF object from a list of metrics, dimensions"""
my_metrics = AmazonCloudWatchEMFProvider(namespace=namespace)
@@ -51,11 +55,11 @@ def serialize_metrics(
def serialize_single_metric(
- metric: Dict,
- dimension: Dict,
+ metric: dict[str, Any],
+ dimension: dict[str, Any],
namespace: str,
- metadata: Optional[Dict] = None,
- timestamp: Union[int, datetime.datetime, None] = None,
+ metadata: dict[str, Any] | None = None,
+ timestamp: int | datetime.datetime | None = None,
) -> CloudWatchEMFOutput:
"""Helper function to build EMF object from a given metric, dimension and namespace"""
my_metrics = AmazonCloudWatchEMFProvider(namespace=namespace)
@@ -71,7 +75,7 @@ def serialize_single_metric(
return my_metrics.serialize_metric_set()
-def remove_timestamp(metrics: List):
+def remove_timestamp(metrics: list):
"""Helper function to remove Timestamp key from EMF objects as they're built at serialization"""
for metric in metrics:
del metric["_aws"]["Timestamp"]
@@ -81,7 +85,7 @@ def capture_metrics_output(capsys):
return json.loads(capsys.readouterr().out.strip())
-def capture_metrics_output_multiple_emf_objects(capsys) -> List[CloudWatchEMFOutput]:
+def capture_metrics_output_multiple_emf_objects(capsys) -> list[CloudWatchEMFOutput]:
return [json.loads(line.strip()) for line in capsys.readouterr().out.split("\n") if line]
diff --git a/tests/functional/metrics/required_dependencies/test_metrics_provider.py b/tests/functional/metrics/required_dependencies/test_metrics_provider.py
index e5ff08f3e96..274d9a7c276 100644
--- a/tests/functional/metrics/required_dependencies/test_metrics_provider.py
+++ b/tests/functional/metrics/required_dependencies/test_metrics_provider.py
@@ -1,12 +1,16 @@
+from __future__ import annotations
+
import json
-from typing import Any, List
+from typing import TYPE_CHECKING, Any
from aws_lambda_powertools.metrics import (
SchemaValidationError,
)
from aws_lambda_powertools.metrics.metrics import Metrics
from aws_lambda_powertools.metrics.provider import BaseProvider
-from aws_lambda_powertools.utilities.typing import LambdaContext
+
+if TYPE_CHECKING:
+ from aws_lambda_powertools.utilities.typing import LambdaContext
def capture_metrics_output(capsys):
@@ -15,9 +19,9 @@ def capture_metrics_output(capsys):
class FakeMetricsProvider(BaseProvider):
def __init__(self):
- self.metric_store: List = []
+ self.metric_store: list = []
- def add_metric(self, name: str, value: float, tag: List = None, *args, **kwargs):
+ def add_metric(self, name: str, value: float, tag: list = None, *args, **kwargs):
self.metric_store.append({"name": name, "value": value})
def serialize_metric_set(self, raise_on_empty_metrics: bool = False, *args, **kwargs):
diff --git a/tests/functional/middleware_factory/required_dependencies/test_middleware_factory.py b/tests/functional/middleware_factory/required_dependencies/test_middleware_factory.py
index 7481e2b8f6b..be28523b99e 100644
--- a/tests/functional/middleware_factory/required_dependencies/test_middleware_factory.py
+++ b/tests/functional/middleware_factory/required_dependencies/test_middleware_factory.py
@@ -1,5 +1,7 @@
+from __future__ import annotations
+
import json
-from typing import Callable
+from typing import TYPE_CHECKING
import pytest
@@ -8,6 +10,9 @@
MiddlewareInvalidArgumentError,
)
+if TYPE_CHECKING:
+ from collections.abc import Callable
+
@pytest.fixture
def say_hi_middleware() -> Callable:
diff --git a/tests/functional/parameters/_boto3/test_utilities_parameters.py b/tests/functional/parameters/_boto3/test_utilities_parameters.py
index 42f654cbd1e..f7b7a642e00 100644
--- a/tests/functional/parameters/_boto3/test_utilities_parameters.py
+++ b/tests/functional/parameters/_boto3/test_utilities_parameters.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import base64
import json
import random
@@ -5,7 +7,7 @@
import uuid
from datetime import datetime, timedelta
from io import BytesIO
-from typing import Any, Dict, List, Optional, Tuple, Union
+from typing import Any
import boto3
import pytest
@@ -52,9 +54,9 @@ def mock_binary_value() -> str:
def build_get_parameters_stub(
- params: Dict[str, Any],
- invalid_parameters: Optional[List[str]] = None,
-) -> Dict[str, List]:
+ params: dict[str, Any],
+ invalid_parameters: list[str] | None = None,
+) -> dict[str, list]:
invalid_parameters = invalid_parameters or []
version = random.randrange(1, 1000)
return {
@@ -527,7 +529,7 @@ def set(self, name: str, value: Any, *, overwrite: bool = False, **kwargs) -> st
def _get(self, name: str, **kwargs) -> str:
raise NotImplementedError()
- def _get_multiple(self, path: str, **kwargs) -> Dict[str, str]:
+ def _get_multiple(self, path: str, **kwargs) -> dict[str, str]:
raise NotImplementedError()
monkeypatch.setitem(parameters.base.DEFAULT_PROVIDERS, "ssm", TestProvider())
@@ -685,7 +687,7 @@ def set(self, name: str, value: Any, *, overwrite: bool = False, **kwargs) -> st
def _get(self, name: str, **kwargs) -> str:
raise NotImplementedError()
- def _get_multiple(self, path: str, **kwargs) -> Dict[str, str]:
+ def _get_multiple(self, path: str, **kwargs) -> dict[str, str]:
raise NotImplementedError()
monkeypatch.setitem(parameters.base.DEFAULT_PROVIDERS, "secrets", TestProvider())
@@ -1025,7 +1027,7 @@ class TestProvider(BaseProvider):
def _get(self, name: str, **kwargs) -> str:
return mock_value
- def _get_multiple(self, path: str, **kwargs) -> Dict[str, str]: ...
+ def _get_multiple(self, path: str, **kwargs) -> dict[str, str]: ...
monkeypatch.setitem(parameters.base.DEFAULT_PROVIDERS, "ssm", TestProvider())
monkeypatch.setitem(parameters.base.DEFAULT_PROVIDERS, "secrets", TestProvider())
@@ -1875,7 +1877,7 @@ def _get(self, name: str, **kwargs) -> str:
assert name == mock_name
raise Exception("test exception raised")
- def _get_multiple(self, path: str, **kwargs) -> Dict[str, str]:
+ def _get_multiple(self, path: str, **kwargs) -> dict[str, str]:
raise NotImplementedError()
provider = TestProvider()
@@ -1895,7 +1897,7 @@ class TestProvider(BaseProvider):
def _get(self, name: str, **kwargs) -> str:
raise NotImplementedError()
- def _get_multiple(self, path: str, **kwargs) -> Dict[str, str]:
+ def _get_multiple(self, path: str, **kwargs) -> dict[str, str]:
assert path == mock_name
raise Exception("test exception raised")
@@ -1919,7 +1921,7 @@ def _get(self, name: str, **kwargs) -> str:
assert name == mock_name
return mock_data
- def _get_multiple(self, path: str, **kwargs) -> Dict[str, str]:
+ def _get_multiple(self, path: str, **kwargs) -> dict[str, str]:
raise NotImplementedError()
provider = TestProvider()
@@ -1943,7 +1945,7 @@ def _get(self, name: str, **kwargs) -> str:
assert name == mock_name
return mock_data
- def _get_multiple(self, path: str, **kwargs) -> Dict[str, str]:
+ def _get_multiple(self, path: str, **kwargs) -> dict[str, str]:
raise NotImplementedError()
provider = TestProvider()
@@ -1967,7 +1969,7 @@ def _get(self, name: str, **kwargs) -> str:
assert name == mock_name
return mock_data
- def _get_multiple(self, path: str, **kwargs) -> Dict[str, str]:
+ def _get_multiple(self, path: str, **kwargs) -> dict[str, str]:
raise NotImplementedError()
provider = TestProvider()
@@ -1991,7 +1993,7 @@ def _get(self, name: str, **kwargs) -> str:
assert name == mock_name
return mock_data
- def _get_multiple(self, path: str, **kwargs) -> Dict[str, str]:
+ def _get_multiple(self, path: str, **kwargs) -> dict[str, str]:
raise NotImplementedError()
provider = TestProvider()
@@ -2013,7 +2015,7 @@ class TestProvider(BaseProvider):
def _get(self, name: str, **kwargs) -> str:
raise NotImplementedError()
- def _get_multiple(self, path: str, **kwargs) -> Dict[str, str]:
+ def _get_multiple(self, path: str, **kwargs) -> dict[str, str]:
assert path == mock_name
return {"A": mock_data}
@@ -2036,7 +2038,7 @@ class TestProvider(BaseProvider):
def _get(self, name: str, **kwargs) -> str:
raise NotImplementedError()
- def _get_multiple(self, path: str, **kwargs) -> Dict[str, str]:
+ def _get_multiple(self, path: str, **kwargs) -> dict[str, str]:
assert path == mock_name
return {"A": mock_data, "B": mock_data + "{"}
@@ -2060,7 +2062,7 @@ class TestProvider(BaseProvider):
def _get(self, name: str, **kwargs) -> str:
raise NotImplementedError()
- def _get_multiple(self, path: str, **kwargs) -> Dict[str, str]:
+ def _get_multiple(self, path: str, **kwargs) -> dict[str, str]:
assert path == mock_name
return {"A": mock_data}
@@ -2084,7 +2086,7 @@ class TestProvider(BaseProvider):
def _get(self, name: str, **kwargs) -> str:
raise NotImplementedError()
- def _get_multiple(self, path: str, **kwargs) -> Dict[str, str]:
+ def _get_multiple(self, path: str, **kwargs) -> dict[str, str]:
assert path == mock_name
return {"A": mock_data}
@@ -2109,7 +2111,7 @@ class TestProvider(BaseProvider):
def _get(self, name: str, **kwargs) -> str:
raise NotImplementedError()
- def _get_multiple(self, path: str, **kwargs) -> Dict[str, str]:
+ def _get_multiple(self, path: str, **kwargs) -> dict[str, str]:
assert path == mock_name
return {"A": mock_data_a, "B": mock_data_b}
@@ -2133,7 +2135,7 @@ class TestProvider(BaseProvider):
def _get(self, name: str, **kwargs) -> str:
raise NotImplementedError()
- def _get_multiple(self, path: str, **kwargs) -> Dict[str, str]:
+ def _get_multiple(self, path: str, **kwargs) -> dict[str, str]:
assert path == mock_name
return {"A": mock_data}
@@ -2154,7 +2156,7 @@ class TestProvider(BaseProvider):
def _get(self, name: str, **kwargs) -> str:
raise NotImplementedError()
- def _get_multiple(self, path: str, **kwargs) -> Dict[str, str]:
+ def _get_multiple(self, path: str, **kwargs) -> dict[str, str]:
raise NotImplementedError()
provider = TestProvider()
@@ -2177,7 +2179,7 @@ class TestProvider(BaseProvider):
def _get(self, name: str, **kwargs) -> str:
raise NotImplementedError()
- def _get_multiple(self, path: str, **kwargs) -> Dict[str, str]:
+ def _get_multiple(self, path: str, **kwargs) -> dict[str, str]:
assert path == mock_name
return {"A": mock_value}
@@ -2201,7 +2203,7 @@ def _get(self, name: str, **kwargs) -> str:
assert name == mock_name
return mock_value
- def _get_multiple(self, path: str, **kwargs) -> Dict[str, str]:
+ def _get_multiple(self, path: str, **kwargs) -> dict[str, str]:
raise NotImplementedError()
monkeypatch.setitem(parameters.base.DEFAULT_PROVIDERS, "ssm", TestProvider())
@@ -2218,7 +2220,7 @@ class TestProvider(SSMProvider):
def __init__(self, boto_config: Config = config, **kwargs):
super().__init__(boto_config=boto_config, **kwargs)
- def get_parameters_by_name(self, *args, **kwargs) -> Union[Dict[str, str], Dict[str, bytes], Dict[str, dict]]:
+ def get_parameters_by_name(self, *args, **kwargs) -> dict[str, str] | dict[str, bytes] | dict[str, dict]:
return {mock_name: mock_value}
monkeypatch.setitem(parameters.base.DEFAULT_PROVIDERS, "ssm", TestProvider())
@@ -2247,7 +2249,7 @@ def _get(self, name: str, decrypt: bool = False, **sdk_options) -> str:
assert decrypt
return decrypted_response
- def _get_parameters_by_name(self, *args, **kwargs) -> Tuple[Dict[str, Any], List[str]]:
+ def _get_parameters_by_name(self, *args, **kwargs) -> tuple[dict[str, Any], list[str]]:
return {mock_name: mock_value}, []
monkeypatch.setitem(parameters.base.DEFAULT_PROVIDERS, "ssm", TestProvider())
@@ -2276,10 +2278,10 @@ def __init__(self, boto_config: Config = config, **kwargs):
# def _get_parameters_by_name(self, parameters: Dict[str, Dict], raise_on_error: bool = True) -> Dict[str, Any]:
def _get_parameters_by_name(
self,
- parameters: Dict[str, Dict],
+ parameters: dict[str, dict],
raise_on_error: bool = True,
decrypt: bool = False,
- ) -> Tuple[Dict[str, Any], List[str]]:
+ ) -> tuple[dict[str, Any], list[str]]:
# THEN max_age should use no_cache_param override
assert parameters[mock_name]["max_age"] == 0
assert parameters["no-override"]["max_age"] == default_cache_period
@@ -2302,10 +2304,10 @@ def __init__(self, boto_config: Config = config, **kwargs):
def _get_parameters_by_name(
self,
- parameters: Dict[str, Dict],
+ parameters: dict[str, dict],
raise_on_error: bool = True,
decrypt: bool = False,
- ) -> Tuple[Dict[str, Any], List[str]]:
+ ) -> tuple[dict[str, Any], list[str]]:
# THEN we should always split to respect GetParameters max
assert len(parameters) == self._MAX_GET_PARAMETERS_ITEM
return {}, []
@@ -2325,7 +2327,7 @@ class TestProvider(SSMProvider):
def __init__(self, boto_config: Config = config, **kwargs):
super().__init__(boto_config=boto_config, **kwargs)
- def _get_parameters_by_name(self, *args, **kwargs) -> Tuple[Dict[str, Any], List[str]]:
+ def _get_parameters_by_name(self, *args, **kwargs) -> tuple[dict[str, Any], list[str]]:
raise RuntimeError("Should not be called if it's in cache")
provider = TestProvider()
@@ -2389,7 +2391,7 @@ def _get(self, name: str, **kwargs) -> str:
assert not kwargs["decrypt"]
return mock_value
- def _get_multiple(self, path: str, **kwargs) -> Dict[str, str]:
+ def _get_multiple(self, path: str, **kwargs) -> dict[str, str]:
raise NotImplementedError()
monkeypatch.setattr(parameters.ssm, "DEFAULT_PROVIDERS", {})
@@ -2409,7 +2411,7 @@ class TestProvider(BaseProvider):
def _get(self, name: str, **kwargs) -> str:
raise NotImplementedError()
- def _get_multiple(self, path: str, **kwargs) -> Dict[str, str]:
+ def _get_multiple(self, path: str, **kwargs) -> dict[str, str]:
assert path == mock_name
return {"A": mock_value}
@@ -2430,7 +2432,7 @@ class TestProvider(BaseProvider):
def _get(self, name: str, **kwargs) -> str:
raise NotImplementedError()
- def _get_multiple(self, path: str, **kwargs) -> Dict[str, str]:
+ def _get_multiple(self, path: str, **kwargs) -> dict[str, str]:
assert path == mock_name
assert kwargs["recursive"]
assert not kwargs["decrypt"]
@@ -2454,7 +2456,7 @@ class TestProvider(SSMProvider):
def __init__(self, boto_config: Config = config, **kwargs):
super().__init__(boto_config=boto_config, **kwargs)
- def get_parameters_by_name(self, *args, **kwargs) -> Union[Dict[str, str], Dict[str, bytes], Dict[str, dict]]:
+ def get_parameters_by_name(self, *args, **kwargs) -> dict[str, str] | dict[str, bytes] | dict[str, dict]:
return {mock_name: mock_value}
monkeypatch.setattr(parameters.ssm, "DEFAULT_PROVIDERS", {})
@@ -2475,7 +2477,7 @@ def _get(self, name: str, **kwargs) -> str:
assert name == mock_name
return mock_value
- def _get_multiple(self, path: str, **kwargs) -> Dict[str, str]:
+ def _get_multiple(self, path: str, **kwargs) -> dict[str, str]:
raise NotImplementedError()
monkeypatch.setitem(parameters.base.DEFAULT_PROVIDERS, "secrets", TestProvider())
@@ -2495,7 +2497,7 @@ def _get(self, name: str, **kwargs) -> str:
assert name == mock_name
return mock_value
- def _get_multiple(self, path: str, **kwargs) -> Dict[str, str]:
+ def _get_multiple(self, path: str, **kwargs) -> dict[str, str]:
raise NotImplementedError()
monkeypatch.setattr(parameters.secrets, "DEFAULT_PROVIDERS", {})
@@ -2689,7 +2691,7 @@ def _get(self, name: str, **kwargs) -> bytes:
assert name == mock_name
return mock_body_bytes
- def _get_multiple(self, path: str, **kwargs) -> Dict[str, str]:
+ def _get_multiple(self, path: str, **kwargs) -> dict[str, str]:
raise NotImplementedError()
monkeypatch.setitem(parameters.base.DEFAULT_PROVIDERS, "appconfig", TestProvider())
@@ -2714,7 +2716,7 @@ def _get(self, name: str, **kwargs) -> str:
assert name == mock_name
return mock_body_bytes
- def _get_multiple(self, path: str, **kwargs) -> Dict[str, str]:
+ def _get_multiple(self, path: str, **kwargs) -> dict[str, str]:
raise NotImplementedError()
monkeypatch.setitem(parameters.base.DEFAULT_PROVIDERS, "appconfig", TestProvider())
@@ -2737,7 +2739,7 @@ def get(self, name: str, **kwargs) -> str:
def _get(self, name: str, **kwargs) -> str:
raise NotImplementedError()
- def _get_multiple(self, path: str, **kwargs) -> Dict[str, str]:
+ def _get_multiple(self, path: str, **kwargs) -> dict[str, str]:
raise NotImplementedError()
monkeypatch.setattr(parameters.appconfig, "DEFAULT_PROVIDERS", {})
@@ -2877,7 +2879,7 @@ class TestProvider(BaseProvider):
def _get(self, name: str, **kwargs) -> str:
raise NotImplementedError()
- def _get_multiple(self, path: str, **kwargs) -> Dict[str, str]:
+ def _get_multiple(self, path: str, **kwargs) -> dict[str, str]:
assert path == mock_name
return {"A": mock_value}
@@ -2900,7 +2902,7 @@ class TestProvider(BaseProvider):
def _get(self, name: str, **kwargs) -> str:
return mock_value
- def _get_multiple(self, path: str, **kwargs) -> Dict[str, str]:
+ def _get_multiple(self, path: str, **kwargs) -> dict[str, str]:
raise NotImplementedError()
provider = TestProvider()
@@ -2934,7 +2936,7 @@ class TestProvider(BaseProvider):
def _get(self, name: str, **kwargs) -> str:
raise ValueError("This parameter doesn't exist")
- def _get_multiple(self, path: str, **kwargs) -> Dict[str, str]:
+ def _get_multiple(self, path: str, **kwargs) -> dict[str, str]:
return {"A": mock_value}
provider = TestProvider()
diff --git a/tests/functional/streaming/_boto3/test_s3_object.py b/tests/functional/streaming/_boto3/test_s3_object.py
index e2b482bb732..292ccf3b1fa 100644
--- a/tests/functional/streaming/_boto3/test_s3_object.py
+++ b/tests/functional/streaming/_boto3/test_s3_object.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
from csv import DictReader
from gzip import GzipFile
diff --git a/tests/functional/streaming/_boto3/test_s3_seekable_io.py b/tests/functional/streaming/_boto3/test_s3_seekable_io.py
index 5cf1b0d9ab3..bdcbe1ca5b2 100644
--- a/tests/functional/streaming/_boto3/test_s3_seekable_io.py
+++ b/tests/functional/streaming/_boto3/test_s3_seekable_io.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import io
import boto3
diff --git a/tests/functional/tracer/_aws_xray_sdk/test_tracing.py b/tests/functional/tracer/_aws_xray_sdk/test_tracing.py
index 5f48b233d91..462da7106ab 100644
--- a/tests/functional/tracer/_aws_xray_sdk/test_tracing.py
+++ b/tests/functional/tracer/_aws_xray_sdk/test_tracing.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import contextlib
import pytest
diff --git a/tests/functional/validator/_fastjsonschema/test_validator.py b/tests/functional/validator/_fastjsonschema/test_validator.py
index a3ce3d9e4c8..d29efd09cae 100644
--- a/tests/functional/validator/_fastjsonschema/test_validator.py
+++ b/tests/functional/validator/_fastjsonschema/test_validator.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import re
import jmespath
diff --git a/tests/functional/validator/conftest.py b/tests/functional/validator/conftest.py
index 9ec94934592..66f1c20b3eb 100644
--- a/tests/functional/validator/conftest.py
+++ b/tests/functional/validator/conftest.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import json
import pytest
diff --git a/tests/unit/data_classes/_boto3/test_code_pipeline_job_event.py b/tests/unit/data_classes/_boto3/test_code_pipeline_job_event.py
index 0306096a8c5..9d9c8f82450 100644
--- a/tests/unit/data_classes/_boto3/test_code_pipeline_job_event.py
+++ b/tests/unit/data_classes/_boto3/test_code_pipeline_job_event.py
@@ -1,10 +1,12 @@
+from __future__ import annotations
+
import json
import zipfile
from io import StringIO
+from typing import TYPE_CHECKING
import pytest
from botocore.response import StreamingBody
-from pytest_mock import MockerFixture
from aws_lambda_powertools.utilities.data_classes import CodePipelineJobEvent
from aws_lambda_powertools.utilities.data_classes.code_pipeline_job_event import (
@@ -12,6 +14,9 @@
)
from tests.functional.utils import load_event
+if TYPE_CHECKING:
+ from pytest_mock import MockerFixture
+
def test_code_pipeline_event():
raw_event = load_event("codePipelineEvent.json")
@@ -217,7 +222,6 @@ def get_object(Bucket: str, Key: str):
def test_code_pipeline_put_artifact(mocker: MockerFixture):
-
raw_content = json.dumps({"steve": "french"})
artifact_content_type = "application/json"
event = CodePipelineJobEvent(load_event("codePipelineEventData.json"))
@@ -263,7 +267,6 @@ def put_object(
def test_code_pipeline_put_unencrypted_artifact(mocker: MockerFixture):
-
raw_content = json.dumps({"steve": "french"})
artifact_content_type = "application/json"
event_without_artifact_encryption = load_event("codePipelineEventData.json")
diff --git a/tests/unit/data_classes/required_dependencies/test_active_mq_event.py b/tests/unit/data_classes/required_dependencies/test_active_mq_event.py
index f4e835edce9..adb2d51aae6 100644
--- a/tests/unit/data_classes/required_dependencies/test_active_mq_event.py
+++ b/tests/unit/data_classes/required_dependencies/test_active_mq_event.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import pytest
from aws_lambda_powertools.utilities.data_classes.active_mq_event import (
diff --git a/tests/unit/data_classes/required_dependencies/test_alb_event.py b/tests/unit/data_classes/required_dependencies/test_alb_event.py
index 6945dc67c36..a21e1968613 100644
--- a/tests/unit/data_classes/required_dependencies/test_alb_event.py
+++ b/tests/unit/data_classes/required_dependencies/test_alb_event.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
from aws_lambda_powertools.utilities.data_classes import ALBEvent
from tests.functional.utils import load_event
diff --git a/tests/unit/data_classes/required_dependencies/test_api_gateway_authorizer.py b/tests/unit/data_classes/required_dependencies/test_api_gateway_authorizer.py
index 52ce96f84e2..1fad5176672 100644
--- a/tests/unit/data_classes/required_dependencies/test_api_gateway_authorizer.py
+++ b/tests/unit/data_classes/required_dependencies/test_api_gateway_authorizer.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import pytest
from aws_lambda_powertools.utilities.data_classes.api_gateway_authorizer_event import (
diff --git a/tests/unit/data_classes/required_dependencies/test_api_gateway_authorizer_event.py b/tests/unit/data_classes/required_dependencies/test_api_gateway_authorizer_event.py
index 9362129b8d5..c14cd8db53d 100644
--- a/tests/unit/data_classes/required_dependencies/test_api_gateway_authorizer_event.py
+++ b/tests/unit/data_classes/required_dependencies/test_api_gateway_authorizer_event.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import pytest
from aws_lambda_powertools.utilities.data_classes.api_gateway_authorizer_event import (
diff --git a/tests/unit/data_classes/required_dependencies/test_api_gateway_authorizer_websocket_event.py b/tests/unit/data_classes/required_dependencies/test_api_gateway_authorizer_websocket_event.py
index d1f0d10735b..d4d6abd6ba8 100644
--- a/tests/unit/data_classes/required_dependencies/test_api_gateway_authorizer_websocket_event.py
+++ b/tests/unit/data_classes/required_dependencies/test_api_gateway_authorizer_websocket_event.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import pytest
from aws_lambda_powertools.utilities.data_classes.api_gateway_authorizer_event import (
diff --git a/tests/unit/data_classes/required_dependencies/test_api_gateway_proxy_event.py b/tests/unit/data_classes/required_dependencies/test_api_gateway_proxy_event.py
index 42925ee9c9f..ec71d815a7c 100644
--- a/tests/unit/data_classes/required_dependencies/test_api_gateway_proxy_event.py
+++ b/tests/unit/data_classes/required_dependencies/test_api_gateway_proxy_event.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
from aws_lambda_powertools.utilities.data_classes import (
APIGatewayProxyEvent,
APIGatewayProxyEventV2,
diff --git a/tests/unit/data_classes/required_dependencies/test_api_gateway_websocket_event.py b/tests/unit/data_classes/required_dependencies/test_api_gateway_websocket_event.py
index faee5b17289..4151429ad50 100644
--- a/tests/unit/data_classes/required_dependencies/test_api_gateway_websocket_event.py
+++ b/tests/unit/data_classes/required_dependencies/test_api_gateway_websocket_event.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import json
from aws_lambda_powertools.utilities.data_classes import APIGatewayWebSocketEvent
@@ -14,6 +16,8 @@ def test_connect_api_gateway_websocket_event():
assert parsed_event.json_body is None
assert parsed_event.headers == raw_event["headers"]
assert parsed_event.multi_value_headers == raw_event["multiValueHeaders"]
+ assert parsed_event.query_string_parameters == raw_event["queryStringParameters"]
+ assert parsed_event.multi_value_query_string_parameters == raw_event["multiValueQueryStringParameters"]
request_context = parsed_event.request_context
request_context_raw = raw_event["requestContext"]
@@ -84,6 +88,8 @@ def test_message_api_gateway_websocket_event():
assert parsed_event.json_body == json.loads(raw_event["body"])
assert parsed_event.headers == {}
assert parsed_event.multi_value_headers == {}
+ assert parsed_event.query_string_parameters == {}
+ assert parsed_event.multi_value_query_string_parameters == {}
request_context = parsed_event.request_context
request_context_raw = raw_event["requestContext"]
diff --git a/tests/unit/data_classes/required_dependencies/test_appsync_authorizer_event.py b/tests/unit/data_classes/required_dependencies/test_appsync_authorizer_event.py
index f940d2e9e19..298dd73b1d5 100644
--- a/tests/unit/data_classes/required_dependencies/test_appsync_authorizer_event.py
+++ b/tests/unit/data_classes/required_dependencies/test_appsync_authorizer_event.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
from aws_lambda_powertools.utilities.data_classes.appsync_authorizer_event import (
AppSyncAuthorizerEvent,
AppSyncAuthorizerResponse,
diff --git a/tests/unit/data_classes/required_dependencies/test_appsync_events_event.py b/tests/unit/data_classes/required_dependencies/test_appsync_events_event.py
new file mode 100644
index 00000000000..0e716dca38f
--- /dev/null
+++ b/tests/unit/data_classes/required_dependencies/test_appsync_events_event.py
@@ -0,0 +1,16 @@
+from aws_lambda_powertools.utilities.data_classes import AppSyncResolverEventsEvent
+from tests.functional.utils import load_event
+
+
+def test_appsync_resolver_event():
+ raw_event = load_event("appSyncEventsEvent.json")
+ parsed_event = AppSyncResolverEventsEvent(raw_event)
+
+ assert parsed_event.events == raw_event["events"]
+ assert parsed_event.out_errors == raw_event["outErrors"]
+ assert parsed_event.domain_name == raw_event["request"]["domainName"]
+ assert parsed_event.info.channel == raw_event["info"]["channel"]
+ assert parsed_event.info.channel_path == raw_event["info"]["channel"]["path"]
+ assert parsed_event.info.channel_segments == raw_event["info"]["channel"]["segments"]
+ assert parsed_event.info.channel_namespace == raw_event["info"]["channelNamespace"]
+ assert parsed_event.info.operation == raw_event["info"]["operation"]
diff --git a/tests/unit/data_classes/required_dependencies/test_appsync_resolver_event.py b/tests/unit/data_classes/required_dependencies/test_appsync_resolver_event.py
index 8d753e9a6fb..1d16d5402ca 100644
--- a/tests/unit/data_classes/required_dependencies/test_appsync_resolver_event.py
+++ b/tests/unit/data_classes/required_dependencies/test_appsync_resolver_event.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import pytest
from aws_lambda_powertools.utilities.data_classes import AppSyncResolverEvent
diff --git a/tests/unit/data_classes/required_dependencies/test_aws_config_rule_event.py b/tests/unit/data_classes/required_dependencies/test_aws_config_rule_event.py
index 8c4d9a40b5e..239637a5429 100644
--- a/tests/unit/data_classes/required_dependencies/test_aws_config_rule_event.py
+++ b/tests/unit/data_classes/required_dependencies/test_aws_config_rule_event.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import json
from aws_lambda_powertools.utilities.data_classes import AWSConfigRuleEvent
diff --git a/tests/unit/data_classes/required_dependencies/test_bedrock_agent_event.py b/tests/unit/data_classes/required_dependencies/test_bedrock_agent_event.py
index c4b56695774..3b10b060a8d 100644
--- a/tests/unit/data_classes/required_dependencies/test_bedrock_agent_event.py
+++ b/tests/unit/data_classes/required_dependencies/test_bedrock_agent_event.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
from aws_lambda_powertools.utilities.data_classes import BedrockAgentEvent
from tests.functional.utils import load_event
diff --git a/tests/unit/data_classes/required_dependencies/test_cloud_watch_alarm_event.py b/tests/unit/data_classes/required_dependencies/test_cloud_watch_alarm_event.py
index df72a7ff1e1..c73aa9f6b8b 100644
--- a/tests/unit/data_classes/required_dependencies/test_cloud_watch_alarm_event.py
+++ b/tests/unit/data_classes/required_dependencies/test_cloud_watch_alarm_event.py
@@ -1,5 +1,6 @@
+from __future__ import annotations
+
import json
-from typing import Dict, List
from aws_lambda_powertools.utilities.data_classes import CloudWatchAlarmEvent
from tests.functional.utils import load_event
@@ -36,7 +37,7 @@ def test_cloud_watch_alarm_event_single_metric():
assert parsed_event.alarm_data.configuration.alarm_actions_suppressor_extension_period is None
assert parsed_event.alarm_data.configuration.alarm_actions_suppressor_wait_period is None
- assert isinstance(parsed_event.alarm_data.configuration.metrics, List)
+ assert isinstance(parsed_event.alarm_data.configuration.metrics, list)
# metric position 0
metric_0 = parsed_event.alarm_data.configuration.metrics[0]
raw_metric_0 = raw_event["alarmData"]["configuration"]["metrics"][0]
@@ -53,7 +54,7 @@ def test_cloud_watch_alarm_event_single_metric():
assert metric_1.metric_stat.stat == raw_metric_1["metricStat"]["stat"]
assert metric_1.metric_stat.period == raw_metric_1["metricStat"]["period"]
assert metric_1.metric_stat.unit is None
- assert isinstance(metric_1.metric_stat.metric, Dict)
+ assert isinstance(metric_1.metric_stat.metric, dict)
def test_cloud_watch_alarm_event_composite_metric():
@@ -102,4 +103,4 @@ def test_cloud_watch_alarm_event_composite_metric():
parsed_event.alarm_data.configuration.alarm_actions_suppressor
== raw_event["alarmData"]["configuration"]["actionsSuppressor"]
)
- assert isinstance(parsed_event.alarm_data.configuration.metrics, List)
+ assert isinstance(parsed_event.alarm_data.configuration.metrics, list)
diff --git a/tests/unit/data_classes/required_dependencies/test_cloud_watch_custom_widget_event.py b/tests/unit/data_classes/required_dependencies/test_cloud_watch_custom_widget_event.py
index 6dcb9bf73b6..f37babc3d96 100644
--- a/tests/unit/data_classes/required_dependencies/test_cloud_watch_custom_widget_event.py
+++ b/tests/unit/data_classes/required_dependencies/test_cloud_watch_custom_widget_event.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
from aws_lambda_powertools.utilities.data_classes import (
CloudWatchDashboardCustomWidgetEvent,
)
diff --git a/tests/unit/data_classes/required_dependencies/test_cloud_watch_logs_event.py b/tests/unit/data_classes/required_dependencies/test_cloud_watch_logs_event.py
index 10a3a499dd0..782274df288 100644
--- a/tests/unit/data_classes/required_dependencies/test_cloud_watch_logs_event.py
+++ b/tests/unit/data_classes/required_dependencies/test_cloud_watch_logs_event.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
from aws_lambda_powertools.utilities.data_classes import CloudWatchLogsEvent
from tests.functional.utils import load_event
diff --git a/tests/unit/data_classes/required_dependencies/test_cloudformation_custom_resource_event.py b/tests/unit/data_classes/required_dependencies/test_cloudformation_custom_resource_event.py
index a6b021d61b4..432ea3bdb68 100644
--- a/tests/unit/data_classes/required_dependencies/test_cloudformation_custom_resource_event.py
+++ b/tests/unit/data_classes/required_dependencies/test_cloudformation_custom_resource_event.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import pytest
from aws_lambda_powertools.utilities.data_classes import (
diff --git a/tests/unit/data_classes/required_dependencies/test_code_deploy_lifecycle_hook_event.py b/tests/unit/data_classes/required_dependencies/test_code_deploy_lifecycle_hook_event.py
index 9d532a82a6f..c11b4b52e62 100644
--- a/tests/unit/data_classes/required_dependencies/test_code_deploy_lifecycle_hook_event.py
+++ b/tests/unit/data_classes/required_dependencies/test_code_deploy_lifecycle_hook_event.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import pytest
from aws_lambda_powertools.utilities.data_classes import (
diff --git a/tests/unit/data_classes/required_dependencies/test_cognito_user_pool_event.py b/tests/unit/data_classes/required_dependencies/test_cognito_user_pool_event.py
index 99984a3d87d..41ee52d915e 100644
--- a/tests/unit/data_classes/required_dependencies/test_cognito_user_pool_event.py
+++ b/tests/unit/data_classes/required_dependencies/test_cognito_user_pool_event.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
from secrets import compare_digest
from aws_lambda_powertools.utilities.data_classes.cognito_user_pool_event import (
diff --git a/tests/unit/data_classes/required_dependencies/test_connect_contact_flow_event.py b/tests/unit/data_classes/required_dependencies/test_connect_contact_flow_event.py
index a3237ad20f8..0ad49106fa8 100644
--- a/tests/unit/data_classes/required_dependencies/test_connect_contact_flow_event.py
+++ b/tests/unit/data_classes/required_dependencies/test_connect_contact_flow_event.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
from aws_lambda_powertools.utilities.data_classes.connect_contact_flow_event import (
ConnectContactFlowChannel,
ConnectContactFlowEndpointType,
diff --git a/tests/unit/data_classes/required_dependencies/test_dynamo_db_stream_event.py b/tests/unit/data_classes/required_dependencies/test_dynamo_db_stream_event.py
index ea2c95c8ddd..8c6b62867ae 100644
--- a/tests/unit/data_classes/required_dependencies/test_dynamo_db_stream_event.py
+++ b/tests/unit/data_classes/required_dependencies/test_dynamo_db_stream_event.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
from decimal import Clamped, Context, Inexact, Overflow, Rounded, Underflow
from aws_lambda_powertools.utilities.data_classes.dynamo_db_stream_event import (
@@ -17,7 +19,6 @@
def test_dynamodb_stream_trigger_event():
-
raw_event = load_event("dynamoStreamEvent.json")
parsed_event = DynamoDBStreamEvent(raw_event)
@@ -76,7 +77,6 @@ def test_dynamodb_stream_record_deserialization_large_int_without_trailing_zeros
def test_dynamodb_stream_record_deserialization_zero_value():
-
data = {
"Keys": {"key1": {"attr1": "value1"}},
"NewImage": {
diff --git a/tests/unit/data_classes/required_dependencies/test_event_bridge_event.py b/tests/unit/data_classes/required_dependencies/test_event_bridge_event.py
index b35aeb73d11..6dfc0c82485 100644
--- a/tests/unit/data_classes/required_dependencies/test_event_bridge_event.py
+++ b/tests/unit/data_classes/required_dependencies/test_event_bridge_event.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
from aws_lambda_powertools.utilities.data_classes import EventBridgeEvent
from tests.functional.utils import load_event
diff --git a/tests/unit/data_classes/required_dependencies/test_iot_registry_events.py b/tests/unit/data_classes/required_dependencies/test_iot_registry_events.py
index 20d30bee3a2..a0a9dc95177 100644
--- a/tests/unit/data_classes/required_dependencies/test_iot_registry_events.py
+++ b/tests/unit/data_classes/required_dependencies/test_iot_registry_events.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
from datetime import datetime
from aws_lambda_powertools.utilities.data_classes.iot_registry_event import (
diff --git a/tests/unit/data_classes/required_dependencies/test_kafka_event.py b/tests/unit/data_classes/required_dependencies/test_kafka_event.py
index fc36171da77..8e4480a06d7 100644
--- a/tests/unit/data_classes/required_dependencies/test_kafka_event.py
+++ b/tests/unit/data_classes/required_dependencies/test_kafka_event.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import pytest
from aws_lambda_powertools.utilities.data_classes import KafkaEvent
@@ -19,7 +21,7 @@ def test_kafka_msk_event():
assert parsed_event.decoded_bootstrap_servers == bootstrap_servers_list
records = list(parsed_event.records)
- assert len(records) == 1
+ assert len(records) == 3
record = records[0]
raw_record = raw_event["records"]["mytopic-0"][0]
assert record.topic == raw_record["topic"]
@@ -34,6 +36,9 @@ def test_kafka_msk_event():
assert record.decoded_headers["HeaderKey"] == b"headerValue"
assert parsed_event.record == records[0]
+ for i in range(1, 3):
+ record = records[i]
+ assert record.key is None
def test_kafka_self_managed_event():
@@ -50,7 +55,7 @@ def test_kafka_self_managed_event():
assert parsed_event.decoded_bootstrap_servers == bootstrap_servers_list
records = list(parsed_event.records)
- assert len(records) == 1
+ assert len(records) == 3
record = records[0]
raw_record = raw_event["records"]["mytopic-0"][0]
assert record.topic == raw_record["topic"]
@@ -66,14 +71,18 @@ def test_kafka_self_managed_event():
assert parsed_event.record == records[0]
+ for i in range(1, 3):
+ record = records[i]
+ assert record.key is None
+
def test_kafka_record_property_with_stopiteration_error():
# GIVEN a kafka event with one record
raw_event = load_event("kafkaEventMsk.json")
parsed_event = KafkaEvent(raw_event)
- # WHEN calling record property twice
+ # WHEN calling record property thrice
# THEN raise StopIteration
with pytest.raises(StopIteration):
- assert parsed_event.record.topic is not None
- assert parsed_event.record.partition is not None
+ for _ in range(4):
+ assert parsed_event.record.topic is not None
diff --git a/tests/unit/data_classes/required_dependencies/test_kinesis_firehose_event.py b/tests/unit/data_classes/required_dependencies/test_kinesis_firehose_event.py
index 219356ea392..22146d997e3 100644
--- a/tests/unit/data_classes/required_dependencies/test_kinesis_firehose_event.py
+++ b/tests/unit/data_classes/required_dependencies/test_kinesis_firehose_event.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
from aws_lambda_powertools.utilities.data_classes import KinesisFirehoseEvent
from tests.functional.utils import load_event
diff --git a/tests/unit/data_classes/required_dependencies/test_kinesis_firehose_response.py b/tests/unit/data_classes/required_dependencies/test_kinesis_firehose_response.py
index 0be8d0d3ec0..290961cbcfa 100644
--- a/tests/unit/data_classes/required_dependencies/test_kinesis_firehose_response.py
+++ b/tests/unit/data_classes/required_dependencies/test_kinesis_firehose_response.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
from aws_lambda_powertools.utilities.data_classes import (
KinesisFirehoseDataTransformationRecord,
KinesisFirehoseDataTransformationRecordMetadata,
diff --git a/tests/unit/data_classes/required_dependencies/test_kinesis_stream_event.py b/tests/unit/data_classes/required_dependencies/test_kinesis_stream_event.py
index e97f15a8277..5410ed81974 100644
--- a/tests/unit/data_classes/required_dependencies/test_kinesis_stream_event.py
+++ b/tests/unit/data_classes/required_dependencies/test_kinesis_stream_event.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import base64
import json
diff --git a/tests/unit/data_classes/required_dependencies/test_lambda_function_url.py b/tests/unit/data_classes/required_dependencies/test_lambda_function_url.py
index ca8e3d78c59..eb8ad8e1e57 100644
--- a/tests/unit/data_classes/required_dependencies/test_lambda_function_url.py
+++ b/tests/unit/data_classes/required_dependencies/test_lambda_function_url.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
from aws_lambda_powertools.utilities.data_classes import LambdaFunctionUrlEvent
from aws_lambda_powertools.utilities.data_classes.api_gateway_proxy_event import RequestContextV2Authorizer
from tests.functional.utils import load_event
diff --git a/tests/unit/data_classes/required_dependencies/test_rabbit_mq_event.py b/tests/unit/data_classes/required_dependencies/test_rabbit_mq_event.py
index 09f8f0f2686..3a0fea94010 100644
--- a/tests/unit/data_classes/required_dependencies/test_rabbit_mq_event.py
+++ b/tests/unit/data_classes/required_dependencies/test_rabbit_mq_event.py
@@ -1,4 +1,4 @@
-from typing import Dict
+from __future__ import annotations
from aws_lambda_powertools.utilities.data_classes.rabbit_mq_event import (
BasicProperties,
@@ -29,7 +29,7 @@ def test_rabbit_mq_event():
properties.content_type == raw_event["rmqMessagesByQueue"]["pizzaQueue::/"][0]["basicProperties"]["contentType"]
)
assert properties.content_encoding is None
- assert isinstance(properties.headers, Dict)
+ assert isinstance(properties.headers, dict)
assert properties.headers.get("header1") is not None
assert (
properties.delivery_mode
diff --git a/tests/unit/data_classes/required_dependencies/test_s3_batch_operation_event.py b/tests/unit/data_classes/required_dependencies/test_s3_batch_operation_event.py
index 44dc65df07d..1d749f77cd2 100644
--- a/tests/unit/data_classes/required_dependencies/test_s3_batch_operation_event.py
+++ b/tests/unit/data_classes/required_dependencies/test_s3_batch_operation_event.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
from aws_lambda_powertools.utilities.data_classes import S3BatchOperationEvent
from tests.functional.utils import load_event
diff --git a/tests/unit/data_classes/required_dependencies/test_s3_batch_operation_response.py b/tests/unit/data_classes/required_dependencies/test_s3_batch_operation_response.py
index c7106e0bfb7..ab8562c4a46 100644
--- a/tests/unit/data_classes/required_dependencies/test_s3_batch_operation_response.py
+++ b/tests/unit/data_classes/required_dependencies/test_s3_batch_operation_response.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import pytest
from aws_lambda_powertools.utilities.data_classes import (
diff --git a/tests/unit/data_classes/required_dependencies/test_s3_event.py b/tests/unit/data_classes/required_dependencies/test_s3_event.py
index eaa4cdce6d0..90e94c413a7 100644
--- a/tests/unit/data_classes/required_dependencies/test_s3_event.py
+++ b/tests/unit/data_classes/required_dependencies/test_s3_event.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
from urllib.parse import quote_plus
from aws_lambda_powertools.utilities.data_classes import S3Event
diff --git a/tests/unit/data_classes/required_dependencies/test_s3_eventbridge_notification.py b/tests/unit/data_classes/required_dependencies/test_s3_eventbridge_notification.py
index ae27ad2965f..5416c43ce3f 100644
--- a/tests/unit/data_classes/required_dependencies/test_s3_eventbridge_notification.py
+++ b/tests/unit/data_classes/required_dependencies/test_s3_eventbridge_notification.py
@@ -1,4 +1,4 @@
-from typing import Dict
+from __future__ import annotations
import pytest
@@ -18,7 +18,7 @@
],
ids=["object_created", "object_deleted", "object_expired", "object_restored"],
)
-def test_s3_eventbridge_notification_detail_parsed(raw_event: Dict):
+def test_s3_eventbridge_notification_detail_parsed(raw_event: dict):
parsed_event = S3EventBridgeNotificationEvent(raw_event)
assert parsed_event.version == raw_event["version"]
diff --git a/tests/unit/data_classes/required_dependencies/test_s3_object_event.py b/tests/unit/data_classes/required_dependencies/test_s3_object_event.py
index 09d0f14e5f6..5f895db27d1 100644
--- a/tests/unit/data_classes/required_dependencies/test_s3_object_event.py
+++ b/tests/unit/data_classes/required_dependencies/test_s3_object_event.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
from aws_lambda_powertools.utilities.data_classes.s3_object_event import (
S3ObjectLambdaEvent,
)
diff --git a/tests/unit/data_classes/required_dependencies/test_secrets_manager_event.py b/tests/unit/data_classes/required_dependencies/test_secrets_manager_event.py
index 6bba952aa9b..212d6a6d563 100644
--- a/tests/unit/data_classes/required_dependencies/test_secrets_manager_event.py
+++ b/tests/unit/data_classes/required_dependencies/test_secrets_manager_event.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
from aws_lambda_powertools.utilities.data_classes.secrets_manager_event import SecretsManagerEvent
from tests.functional.utils import load_event
diff --git a/tests/unit/data_classes/required_dependencies/test_ses_event.py b/tests/unit/data_classes/required_dependencies/test_ses_event.py
index e81c546fb1e..374c5f4884e 100644
--- a/tests/unit/data_classes/required_dependencies/test_ses_event.py
+++ b/tests/unit/data_classes/required_dependencies/test_ses_event.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
from aws_lambda_powertools.utilities.data_classes import SESEvent
from tests.functional.utils import load_event
diff --git a/tests/unit/data_classes/required_dependencies/test_sns_event.py b/tests/unit/data_classes/required_dependencies/test_sns_event.py
index d091e1b84ac..58f0b1572d7 100644
--- a/tests/unit/data_classes/required_dependencies/test_sns_event.py
+++ b/tests/unit/data_classes/required_dependencies/test_sns_event.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
from aws_lambda_powertools.utilities.data_classes import SNSEvent
from tests.functional.utils import load_event
diff --git a/tests/unit/data_classes/required_dependencies/test_sqs_event.py b/tests/unit/data_classes/required_dependencies/test_sqs_event.py
index 0cd18bd8a90..ad27b9d14d4 100644
--- a/tests/unit/data_classes/required_dependencies/test_sqs_event.py
+++ b/tests/unit/data_classes/required_dependencies/test_sqs_event.py
@@ -1,10 +1,15 @@
+from __future__ import annotations
+
import json
+from typing import TYPE_CHECKING
from aws_lambda_powertools.utilities.data_classes import S3Event, SQSEvent
-from aws_lambda_powertools.utilities.data_classes.sns_event import SNSMessage
from aws_lambda_powertools.utilities.data_classes.sqs_event import SQSMessageAttributes
from tests.functional.utils import load_event
+if TYPE_CHECKING:
+ from aws_lambda_powertools.utilities.data_classes.sns_event import SNSMessage
+
def test_seq_trigger_event():
raw_event = load_event("sqsEvent.json")
diff --git a/tests/unit/data_classes/required_dependencies/test_transfer_family_event.py b/tests/unit/data_classes/required_dependencies/test_transfer_family_event.py
index 9a74c0398f9..2e74d2d7457 100644
--- a/tests/unit/data_classes/required_dependencies/test_transfer_family_event.py
+++ b/tests/unit/data_classes/required_dependencies/test_transfer_family_event.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import pytest
from aws_lambda_powertools.utilities.data_classes.transfer_family_event import (
@@ -20,7 +22,6 @@ def test_transfer_family_authorizer_event():
@pytest.mark.parametrize("home_directory_type", ["LOGICAL", "PATH"])
def test_build_authentication_response_s3(home_directory_type):
-
# GIVEN a Authorizer response
response = TransferFamilyAuthorizerResponse()
@@ -28,7 +29,7 @@ def test_build_authentication_response_s3(home_directory_type):
policy = '{"Version": "2012-10-17", "Statement": [{"Effect": "Allow", "Action": "s3:*", "Resource": "*"}]}'
home_directory = "/bucket/user" if home_directory_type == "PATH" else None
home_directory_details = (
- {"Entry": "/", "Target": "/bucket/${transfer:UserName}"} if home_directory_type == "LOGICAL" else None
+ [{"Entry": "/", "Target": "/bucket/${transfer:UserName}"}] if home_directory_type == "LOGICAL" else None
)
public_keys = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC0g+Z"
@@ -52,20 +53,19 @@ def test_build_authentication_response_s3(home_directory_type):
assert response.get("HomeDirectory") == home_directory
assert "HomeDirectoryDetails" not in response
else:
- assert response.get("HomeDirectoryDetails") == [home_directory_details]
+ assert response.get("HomeDirectoryDetails") == '[{"Entry": "/", "Target": "/bucket/${transfer:UserName}"}]'
assert "HomeDirectory" not in response
@pytest.mark.parametrize("home_directory_type", ["LOGICAL", "PATH"])
def test_build_authentication_response_efs(home_directory_type):
-
# GIVEN a Authorizer response
response = TransferFamilyAuthorizerResponse()
role_arn = "arn:aws:iam::123456789012:role/S3Access"
home_directory = "/bucket/user" if home_directory_type == "PATH" else None
home_directory_details = (
- {"Entry": "/", "Target": "/bucket/${transfer:UserName}"} if home_directory_type == "LOGICAL" else None
+ [{"Entry": "/", "Target": "/bucket/${transfer:UserName}"}] if home_directory_type == "LOGICAL" else None
)
# WHEN building an authentication response for EFS with different home directory types
@@ -86,18 +86,17 @@ def test_build_authentication_response_efs(home_directory_type):
assert response.get("HomeDirectory") == home_directory
assert "HomeDirectoryDetails" not in response
else:
- assert response.get("HomeDirectoryDetails") == [home_directory_details]
+ assert response.get("HomeDirectoryDetails") == '[{"Entry": "/", "Target": "/bucket/${transfer:UserName}"}]'
assert "HomeDirectory" not in response
def test_build_authentication_missing_home_directory():
-
# GIVEN a Authorizer response
response = TransferFamilyAuthorizerResponse()
# WHEN home_directory_details is empty and type is LOGICAL
role_arn = "arn:aws:iam::123456789012:role/S3Access"
- home_directory_details = {}
+ home_directory_details = []
home_directory_type = "LOGICAL"
# THEN must raise an exception
diff --git a/tests/unit/data_classes/required_dependencies/test_vpc_lattice_event.py b/tests/unit/data_classes/required_dependencies/test_vpc_lattice_event.py
index 9f5ad742557..5445fcea882 100644
--- a/tests/unit/data_classes/required_dependencies/test_vpc_lattice_event.py
+++ b/tests/unit/data_classes/required_dependencies/test_vpc_lattice_event.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
from aws_lambda_powertools.utilities.data_classes.vpc_lattice import VPCLatticeEvent
from tests.functional.utils import load_event
diff --git a/tests/unit/data_classes/required_dependencies/test_vpc_lattice_eventv2.py b/tests/unit/data_classes/required_dependencies/test_vpc_lattice_eventv2.py
index 1824e1ec080..7661abea603 100644
--- a/tests/unit/data_classes/required_dependencies/test_vpc_lattice_eventv2.py
+++ b/tests/unit/data_classes/required_dependencies/test_vpc_lattice_eventv2.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import pytest
from aws_lambda_powertools.utilities.data_classes.vpc_lattice import VPCLatticeEventV2
diff --git a/tests/unit/data_classes/test_cloudformation_custom_resource_event.py b/tests/unit/data_classes/test_cloudformation_custom_resource_event.py
index a6b021d61b4..432ea3bdb68 100644
--- a/tests/unit/data_classes/test_cloudformation_custom_resource_event.py
+++ b/tests/unit/data_classes/test_cloudformation_custom_resource_event.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import pytest
from aws_lambda_powertools.utilities.data_classes import (
diff --git a/tests/unit/data_masking/_aws_encryption_sdk/test_kms_provider.py b/tests/unit/data_masking/_aws_encryption_sdk/test_kms_provider.py
index 5fe9b2e53ed..d736fafe6b6 100644
--- a/tests/unit/data_masking/_aws_encryption_sdk/test_kms_provider.py
+++ b/tests/unit/data_masking/_aws_encryption_sdk/test_kms_provider.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import pytest
from aws_lambda_powertools.utilities.data_masking.exceptions import (
diff --git a/tests/unit/data_masking/required_dependencies/test_base_functions.py b/tests/unit/data_masking/required_dependencies/test_base_functions.py
index 1af532967c7..9ce3de65cfb 100644
--- a/tests/unit/data_masking/required_dependencies/test_base_functions.py
+++ b/tests/unit/data_masking/required_dependencies/test_base_functions.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import pytest
from aws_lambda_powertools.utilities.data_masking.base import DataMasking
diff --git a/tests/unit/event_handler/_pydantic/conftest.py b/tests/unit/event_handler/_pydantic/conftest.py
index d50d4e483ef..b88fc3e157d 100644
--- a/tests/unit/event_handler/_pydantic/conftest.py
+++ b/tests/unit/event_handler/_pydantic/conftest.py
@@ -4,7 +4,6 @@
@pytest.fixture(scope="session")
def pydanticv1_only():
-
version = __version__.split(".")
if version[0] != "1":
pytest.skip("pydanticv1 test only")
@@ -12,7 +11,6 @@ def pydanticv1_only():
@pytest.fixture(scope="session")
def pydanticv2_only():
-
version = __version__.split(".")
if version[0] != "2":
pytest.skip("pydanticv2 test only")
diff --git a/tests/unit/event_handler/_pydantic/test_openapi_models_pydantic_v2.py b/tests/unit/event_handler/_pydantic/test_openapi_models_pydantic_v2.py
index dd6aba913a1..c426309f389 100644
--- a/tests/unit/event_handler/_pydantic/test_openapi_models_pydantic_v2.py
+++ b/tests/unit/event_handler/_pydantic/test_openapi_models_pydantic_v2.py
@@ -20,7 +20,6 @@ def test_openapi_extensions_with_invalid_key():
def test_openapi_extensions_with_proxy_models():
-
# GIVEN we create an models using OpenAPIExtensions as a "Proxy" Model
class MyModelFoo(OpenAPIExtensions):
foo: str
diff --git a/tests/unit/event_handler/_required_dependencies/__init__.py b/tests/unit/event_handler/_required_dependencies/__init__.py
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/tests/unit/event_handler/_required_dependencies/appsync_events/__init__.py b/tests/unit/event_handler/_required_dependencies/appsync_events/__init__.py
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/tests/unit/event_handler/_required_dependencies/appsync_events/test_functions.py b/tests/unit/event_handler/_required_dependencies/appsync_events/test_functions.py
new file mode 100644
index 00000000000..c344f9ad421
--- /dev/null
+++ b/tests/unit/event_handler/_required_dependencies/appsync_events/test_functions.py
@@ -0,0 +1,145 @@
+import pytest
+
+from aws_lambda_powertools.event_handler.events_appsync.functions import find_best_route, is_valid_path
+
+
+@pytest.mark.parametrize(
+ "path,expected,description",
+ [
+ ("/*", True, "Root wildcard path is valid"),
+ ("/users", True, "Simple path with one segment is valid"),
+ ("/users/profile/settings", True, "Path with multiple segments is valid"),
+ ("/users/*", True, "Path ending with /* is valid"),
+ ("/users/*/details", False, "Path with wildcard in the middle is invalid"),
+ ("users/profile", False, "Path without leading slash is invalid"),
+ ("/users/", False, "Path with trailing slash is invalid"),
+ ("", False, "Empty path is invalid"),
+ ("/", False, "Root path / is invalid according to the regex"),
+ ],
+)
+def test_path_validation(path, expected, description):
+ """Test various path validation scenarios."""
+ # Given a path (provided by parametrize)
+
+ # When validating
+ result = is_valid_path(path)
+
+ # Then must match the regexp
+ assert result is expected, description
+
+
+def test_path_with_non_string_input():
+ """Test that non-string input raises an appropriate error."""
+ # Given
+ path = None
+
+ # When/Then
+ with pytest.raises(TypeError):
+ is_valid_path(path)
+
+
+@pytest.mark.parametrize(
+ "routes, path, expected_route, description",
+ [
+ (
+ {
+ "/default/v1/*": {"func": lambda x: x, "aggregate": False},
+ "/default/v1/users/*": {"func": lambda x: x, "aggregate": False},
+ "/default/v1/users/active/*": {"func": lambda x: x, "aggregate": False},
+ },
+ "/default/v1/users/active/123",
+ "/default/v1/users/active/*",
+ "Most specific route with wildcard should be matched",
+ ),
+ ],
+)
+def test_find_best_route_specific_wildcard(routes, path, expected_route, description):
+ """Test that find_best_route selects most specific wildcard path."""
+ # GIVEN
+
+ # WHEN
+ result = find_best_route(routes, path)
+
+ # THEN
+ assert result == expected_route, description
+
+
+@pytest.mark.parametrize(
+ "routes, path, expected_route, description",
+ [
+ (
+ {
+ "/default/v1/users": {"func": lambda x: x, "aggregate": False},
+ "/default/v1/*": {"func": lambda x: x, "aggregate": False},
+ },
+ "/default/v1/users",
+ "/default/v1/users",
+ "Exact match wins over wildcard match",
+ ),
+ ],
+)
+def test_find_best_route_exact_match(routes, path, expected_route, description):
+ """Test that find_best_route prefers exact matches over wildcard matches."""
+ # GIVEN
+
+ # WHEN
+ result = find_best_route(routes, path)
+
+ # THEN
+ assert result == expected_route, description
+
+
+@pytest.mark.parametrize(
+ "routes, path, expected_route, description",
+ [
+ (
+ {
+ "/*": {"func": lambda x: x, "aggregate": False},
+ "/other/*": {"func": lambda x: x, "aggregate": False},
+ },
+ "/default/v1/users",
+ "/*",
+ "Fallback to /* when no specific matches",
+ ),
+ ],
+)
+def test_find_best_route_fallback(routes, path, expected_route, description):
+ """Test that find_best_route falls back to /* when no specific route matches."""
+ # GIVEN
+
+ # WHEN
+ result = find_best_route(routes, path)
+
+ # THEN
+ assert result == expected_route, description
+
+
+@pytest.mark.parametrize(
+ "routes, path, expected_route, description",
+ [
+ (
+ {
+ "/api/v1/users/*": {"func": lambda x: x, "aggregate": False},
+ "/api/v1/posts/*": {"func": lambda x: x, "aggregate": False},
+ },
+ "/api/v2/users/123",
+ None,
+ "No match should return None",
+ ),
+ (
+ {},
+ "/any/path",
+ None,
+ "Empty routes dictionary should return None",
+ ),
+ ],
+)
+def test_find_best_route_no_match(routes, path, expected_route, description):
+ """Test that find_best_route returns None when no routes match."""
+ # GIVEN
+
+ # WHEN
+ result = find_best_route(routes, path)
+
+ # THEN
+ assert result == expected_route, description
diff --git a/tests/unit/event_handler/_required_dependencies/test_exception_handler_manager.py b/tests/unit/event_handler/_required_dependencies/test_exception_handler_manager.py
new file mode 100644
index 00000000000..e1eed042205
--- /dev/null
+++ b/tests/unit/event_handler/_required_dependencies/test_exception_handler_manager.py
@@ -0,0 +1,176 @@
+import pytest
+
+from aws_lambda_powertools.event_handler.exception_handling import (
+ ExceptionHandlerManager, # Assuming the class is in this module
+)
+
+
+@pytest.fixture
+def exception_manager() -> ExceptionHandlerManager:
+ """Fixture to provide a fresh ExceptionHandlerManager instance for each test."""
+ return ExceptionHandlerManager()
+
+
+# ----- Tests for exception_handler decorator -----
+
+
+def test_decorator_registers_single_exception_handler(exception_manager):
+ """
+ WHEN the exception_handler decorator is used with a single exception type
+ GIVEN a function decorated with @manager.exception_handler(ValueError)
+ THEN the function is registered as a handler for ValueError
+ """
+
+ @exception_manager.exception_handler(ValueError)
+ def handle_value_error(e):
+ return "ValueError handled"
+
+ handlers = exception_manager.get_registered_handlers()
+ assert ValueError in handlers
+ assert handlers[ValueError] == handle_value_error
+
+
+def test_decorator_registers_multiple_exception_handlers(exception_manager):
+ """
+ GIVEN a function decorated with @manager.exception_handler([KeyError, TypeError])
+ WHEN the exception_handler decorator is used with multiple exception types
+ THEN the function is registered as a handler for both KeyError and TypeError
+ """
+
+ @exception_manager.exception_handler([KeyError, TypeError])
+ def handle_multiple_errors(e):
+ return f"{type(e).__name__} handled"
+
+ handlers = exception_manager.get_registered_handlers()
+ assert KeyError in handlers
+ assert TypeError in handlers
+ assert handlers[KeyError] == handle_multiple_errors
+ assert handlers[TypeError] == handle_multiple_errors
+
+
+def test_lookup_uses_inheritance_hierarchy(exception_manager):
+ # GIVEN a handler has been registered for a base exception type
+ @exception_manager.exception_handler(Exception)
+ def handle_exception(e):
+ return "Exception handled"
+
+ # WHEN lookup_exception_handler is called with a derived exception type
+ # THEN the handler for the base exception is returned
+ handler = exception_manager.lookup_exception_handler(ValueError)
+ assert handler == handle_exception
+
+
+def test_lookup_returns_none_for_unregistered_handler(exception_manager):
+ """
+ GIVEN no handler has been registered for that type or its base classes
+ WHEN lookup_exception_handler is called with an exception type
+ THEN None is returned
+ """
+ handler = exception_manager.lookup_exception_handler(ValueError)
+ assert handler is None
+
+
+def test_register_handler_for_multiple_exceptions(exception_manager):
+ # GIVEN a valid handler function
+ @exception_manager.exception_handler([ValueError, KeyError])
+ def handle_error(e):
+ return "Error handled"
+
+ # THEN the handler is properly registered for all exceptions in the list
+ handlers = exception_manager.get_registered_handlers()
+ assert KeyError in handlers
+ assert ValueError in handlers
+ assert handlers[KeyError] == handle_error
+ assert handlers[ValueError] == handle_error
+
+
+def test_update_exception_handlers_with_dictionary(exception_manager):
+ """
+ WHEN update_exception_handlers is called with a dictionary
+ GIVEN the dictionary maps exception types to handler functions
+ THEN all handlers in the dictionary are properly registered
+ """
+
+ def handle_value_error(e):
+ return "ValueError handled"
+
+ def handle_key_error(e):
+ return "KeyError handled"
+
+ # Update with a dictionary of handlers
+ exception_manager.update_exception_handlers(
+ {
+ ValueError: handle_value_error,
+ KeyError: handle_key_error,
+ },
+ )
+
+ handlers = exception_manager.get_registered_handlers()
+ assert ValueError in handlers
+ assert KeyError in handlers
+ assert handlers[ValueError] == handle_value_error
+ assert handlers[KeyError] == handle_key_error
+
+
+def test_clear_handlers_removes_all_handlers(exception_manager):
+ # GIVEN handlers have been registered
+ @exception_manager.exception_handler([ValueError, KeyError])
+ def handle_error(e):
+ return "Error handled"
+
+ # Verify handlers are registered
+ assert len(exception_manager.get_registered_handlers()) == 2
+
+ # WHEN clear_handlers is called
+ exception_manager.clear_handlers()
+
+ # THEN all handlers are removed
+ assert len(exception_manager.get_registered_handlers()) == 0
+
+
+def test_get_registered_handlers_returns_copy(exception_manager):
+ # WHEN get_registered_handlers is called
+ @exception_manager.exception_handler(ValueError)
+ def handle_error(e):
+ return "Error handled"
+
+ # GIVEN handlers have been registered
+ handlers_copy = exception_manager.get_registered_handlers()
+
+ # THEN a copy of the handlers dictionary is returned that doesn't affect the original
+ handlers_copy[KeyError] = lambda e: "Not registered properly"
+ assert KeyError not in exception_manager.get_registered_handlers()
+
+
+def test_handler_executes_correctly(exception_manager):
+ # GIVEN a registered handler is executed with an exception
+ @exception_manager.exception_handler(ValueError)
+ def handle_value_error(e):
+ return f"Handled: {str(e)}"
+
+ # WHEN an exception happens
+ # THEN the handler processes the exception correctly
+ try:
+ raise ValueError("Test error")
+ except Exception as e:
+ handler = exception_manager.lookup_exception_handler(type(e))
+ result = handler(e)
+ assert result == "Handled: Test error"
+
+
+def test_registering_new_handler_overrides_previous(exception_manager):
+ # WHEN a new handler is registered for an exception type
+ @exception_manager.exception_handler(ValueError)
+ def first_handler(e):
+ return "First handler"
+
+ # GIVEN a handler was already registered for that type
+ @exception_manager.exception_handler(ValueError)
+ def second_handler(e):
+ return "Second handler"
+
+ # THEN the new handler replaces the previous one
+ # Check that the second handler overrode the first
+ handler = exception_manager.lookup_exception_handler(ValueError)
+ assert handler == second_handler
+ assert handler != first_handler
diff --git a/tests/unit/idempotency/test_dynamodb_persistence.py b/tests/unit/idempotency/test_dynamodb_persistence.py
index b27ef00550c..e9fb5785e44 100644
--- a/tests/unit/idempotency/test_dynamodb_persistence.py
+++ b/tests/unit/idempotency/test_dynamodb_persistence.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
from dataclasses import dataclass
from aws_lambda_powertools.utilities.idempotency import DynamoDBPersistenceLayer
diff --git a/tests/unit/logger/required_dependencies/test_logger_buffer_cache.py b/tests/unit/logger/required_dependencies/test_logger_buffer_cache.py
index 00ae7696281..25ed7ece631 100644
--- a/tests/unit/logger/required_dependencies/test_logger_buffer_cache.py
+++ b/tests/unit/logger/required_dependencies/test_logger_buffer_cache.py
@@ -1,10 +1,11 @@
+from __future__ import annotations
+
import pytest
from aws_lambda_powertools.logging.buffer.cache import LoggerBufferCache
def test_initialization():
-
# GIVEN a new instance of LoggerBufferCache
logger_cache = LoggerBufferCache(1000)
diff --git a/tests/unit/logger/required_dependencies/test_logger_buffer_config.py b/tests/unit/logger/required_dependencies/test_logger_buffer_config.py
index 1cb0f1a5f0d..6c3061b1d87 100644
--- a/tests/unit/logger/required_dependencies/test_logger_buffer_config.py
+++ b/tests/unit/logger/required_dependencies/test_logger_buffer_config.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import pytest
from aws_lambda_powertools.logging.buffer import LoggerBufferConfig
diff --git a/tests/unit/logger/required_dependencies/test_logger_buffer_functions.py b/tests/unit/logger/required_dependencies/test_logger_buffer_functions.py
index 5a714b095d2..c4e80ec3058 100644
--- a/tests/unit/logger/required_dependencies/test_logger_buffer_functions.py
+++ b/tests/unit/logger/required_dependencies/test_logger_buffer_functions.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
from aws_lambda_powertools.logging.buffer.functions import _check_minimum_buffer_log_level
diff --git a/tests/unit/metrics/conftest.py b/tests/unit/metrics/conftest.py
index 8d601e4d13b..3052e378bbd 100644
--- a/tests/unit/metrics/conftest.py
+++ b/tests/unit/metrics/conftest.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import pytest
diff --git a/tests/unit/metrics/test_functions.py b/tests/unit/metrics/test_functions.py
index e7647852a49..93db4913acb 100644
--- a/tests/unit/metrics/test_functions.py
+++ b/tests/unit/metrics/test_functions.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import warnings
import pytest
diff --git a/tests/unit/metrics/test_unit_datadog.py b/tests/unit/metrics/test_unit_datadog.py
index ab54e9730fe..6d900bfef68 100644
--- a/tests/unit/metrics/test_unit_datadog.py
+++ b/tests/unit/metrics/test_unit_datadog.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import pytest
from aws_lambda_powertools.metrics.exceptions import SchemaValidationError
diff --git a/tests/unit/parser/_pydantic/test_apigw_websockets.py b/tests/unit/parser/_pydantic/test_apigw_websockets.py
index 7b8a3c9ba46..36f6355d42f 100644
--- a/tests/unit/parser/_pydantic/test_apigw_websockets.py
+++ b/tests/unit/parser/_pydantic/test_apigw_websockets.py
@@ -31,7 +31,7 @@ def test_apigw_websocket_message_event():
assert request_context.extended_request_id == raw_event["requestContext"]["extendedRequestId"]
identity = request_context.identity
- assert str(identity.source_ip) == f'{raw_event["requestContext"]["identity"]["sourceIp"]}/32'
+ assert str(identity.source_ip) == f"{raw_event['requestContext']['identity']['sourceIp']}/32"
assert request_context.request_id == raw_event["requestContext"]["requestId"]
assert request_context.request_time == raw_event["requestContext"]["requestTime"]
@@ -67,7 +67,7 @@ def test_apigw_websocket_connect_event():
assert request_context.extended_request_id == raw_event["requestContext"]["extendedRequestId"]
identity = request_context.identity
- assert str(identity.source_ip) == f'{raw_event["requestContext"]["identity"]["sourceIp"]}/32'
+ assert str(identity.source_ip) == f"{raw_event['requestContext']['identity']['sourceIp']}/32"
assert request_context.request_id == raw_event["requestContext"]["requestId"]
assert request_context.request_time == raw_event["requestContext"]["requestTime"]
@@ -96,7 +96,7 @@ def test_apigw_websocket_disconnect_event():
assert request_context.extended_request_id == raw_event["requestContext"]["extendedRequestId"]
identity = request_context.identity
- assert str(identity.source_ip) == f'{raw_event["requestContext"]["identity"]["sourceIp"]}/32'
+ assert str(identity.source_ip) == f"{raw_event['requestContext']['identity']['sourceIp']}/32"
assert request_context.request_id == raw_event["requestContext"]["requestId"]
assert request_context.request_time == raw_event["requestContext"]["requestTime"]
diff --git a/tests/unit/parser/_pydantic/test_appsync.py b/tests/unit/parser/_pydantic/test_appsync.py
new file mode 100644
index 00000000000..06b73621445
--- /dev/null
+++ b/tests/unit/parser/_pydantic/test_appsync.py
@@ -0,0 +1,28 @@
+import pytest
+
+from aws_lambda_powertools.utilities.parser import ValidationError, parse
+from aws_lambda_powertools.utilities.parser.models import AppSyncResolverEventModel
+from tests.functional.utils import load_event
+
+
+def test_appsync_event_model_parses_successfully():
+ """
+ Validate that a valid AppSync resolver event is correctly parsed by the model.
+ """
+ event = load_event("appsync_resolver_event.json")
+ parsed_event = parse(event=event, model=AppSyncResolverEventModel)
+
+ assert parsed_event.arguments["page"] == 2
+ assert parsed_event.identity.username == "mike"
+ assert parsed_event.request.headers["host"].endswith("appsync-api.us-east-1.amazonaws.com")
+ assert parsed_event.info.fieldName == "locations"
+ assert parsed_event.info.parentTypeName == "Merchant"
+
+
+def test_appsync_event_model_invalid_payload_raises():
+ """
+ Validate that parsing an invalid AppSync resolver event payload raises a ValidationError.
+ """
+ invalid_event = {"invalid": "event"}
+ with pytest.raises(ValidationError):
+ parse(event=invalid_event, model=AppSyncResolverEventModel)
diff --git a/tests/unit/parser/_pydantic/test_iot_registry_events.py b/tests/unit/parser/_pydantic/test_iot_registry_events.py
index a36e44e2a52..8418b4bddcd 100644
--- a/tests/unit/parser/_pydantic/test_iot_registry_events.py
+++ b/tests/unit/parser/_pydantic/test_iot_registry_events.py
@@ -60,7 +60,6 @@ def test_iot_core_thing_type_association_event():
def test_iot_core_thing_group_event():
-
raw_event = load_event("iotRegistryEventsThingGroupEvent.json")
parsed_event: IoTCoreThingGroupEvent = IoTCoreThingGroupEvent(**raw_event)
@@ -81,7 +80,6 @@ def test_iot_core_thing_group_event():
def test_iot_core_add_or_remove_from_thing_group_event():
-
raw_event = load_event("iotRegistryEventsAddOrRemoveFromThingGroupEvent.json")
parsed_event: IoTCoreAddOrRemoveFromThingGroupEvent = IoTCoreAddOrRemoveFromThingGroupEvent(**raw_event)
@@ -99,7 +97,6 @@ def test_iot_core_add_or_remove_from_thing_group_event():
def test_iot_core_add_or_delete_from_thing_group_event():
-
raw_event = load_event("iotRegistryEventsAddOrDeleteFromThingGroupEvent.json")
parsed_event: IoTCoreAddOrDeleteFromThingGroupEvent = IoTCoreAddOrDeleteFromThingGroupEvent(**raw_event)
diff --git a/tests/unit/parser/_pydantic/test_kafka.py b/tests/unit/parser/_pydantic/test_kafka.py
index 066820c2f11..aabb669b805 100644
--- a/tests/unit/parser/_pydantic/test_kafka.py
+++ b/tests/unit/parser/_pydantic/test_kafka.py
@@ -15,9 +15,9 @@ def test_kafka_msk_event_with_envelope():
model=MyLambdaKafkaBusiness,
envelope=envelopes.KafkaEnvelope,
)
-
- assert parsed_event[0].key == "value"
- assert len(parsed_event) == 1
+ for i in range(3):
+ assert parsed_event[i].key == "value"
+ assert len(parsed_event) == 3
def test_kafka_self_managed_event_with_envelope():
@@ -27,9 +27,9 @@ def test_kafka_self_managed_event_with_envelope():
model=MyLambdaKafkaBusiness,
envelope=envelopes.KafkaEnvelope,
)
-
- assert parsed_event[0].key == "value"
- assert len(parsed_event) == 1
+ for i in range(3):
+ assert parsed_event[i].key == "value"
+ assert len(parsed_event) == 3
def test_self_managed_kafka_event():
@@ -41,7 +41,7 @@ def test_self_managed_kafka_event():
assert parsed_event.bootstrapServers == raw_event["bootstrapServers"].split(",")
records = list(parsed_event.records["mytopic-0"])
- assert len(records) == 1
+ assert len(records) == 3
record: KafkaRecordModel = records[0]
raw_record = raw_event["records"]["mytopic-0"][0]
assert record.topic == raw_record["topic"]
@@ -55,6 +55,8 @@ def test_self_managed_kafka_event():
assert record.value == '{"key":"value"}'
assert len(record.headers) == 1
assert record.headers[0]["headerKey"] == b"headerValue"
+ record: KafkaRecordModel = records[1]
+ assert record.key is None
def test_kafka_msk_event():
@@ -66,7 +68,7 @@ def test_kafka_msk_event():
assert parsed_event.eventSourceArn == raw_event["eventSourceArn"]
records = list(parsed_event.records["mytopic-0"])
- assert len(records) == 1
+ assert len(records) == 3
record: KafkaRecordModel = records[0]
raw_record = raw_event["records"]["mytopic-0"][0]
assert record.topic == raw_record["topic"]
@@ -80,3 +82,6 @@ def test_kafka_msk_event():
assert record.value == '{"key":"value"}'
assert len(record.headers) == 1
assert record.headers[0]["headerKey"] == b"headerValue"
+ for i in range(1, 3):
+ record: KafkaRecordModel = records[i]
+ assert record.key is None
diff --git a/tests/unit/shared/test_dynamodb_deserializer.py b/tests/unit/shared/test_dynamodb_deserializer.py
index 223060d317a..7e6c2cc3885 100644
--- a/tests/unit/shared/test_dynamodb_deserializer.py
+++ b/tests/unit/shared/test_dynamodb_deserializer.py
@@ -1,4 +1,6 @@
-from typing import Any, Dict, Optional
+from __future__ import annotations
+
+from typing import Any
import pytest
@@ -10,14 +12,14 @@ def __init__(self, data: dict):
self._data = data
self._deserializer = TypeDeserializer()
- def _deserialize_dynamodb_dict(self) -> Optional[Dict[str, Any]]:
+ def _deserialize_dynamodb_dict(self) -> dict[str, Any] | None:
if self._data is None:
return None
return {k: self._deserializer.deserialize(v) for k, v in self._data.items()}
@property
- def data(self) -> Optional[Dict[str, Any]]:
+ def data(self) -> dict[str, Any] | None:
"""The primary key attribute(s) for the DynamoDB item that was modified."""
return self._deserialize_dynamodb_dict()
diff --git a/tests/unit/test_cookie_class.py b/tests/unit/test_cookie_class.py
index 2b0aa3a37cb..d1cdfe8e1fa 100644
--- a/tests/unit/test_cookie_class.py
+++ b/tests/unit/test_cookie_class.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
from datetime import datetime
from aws_lambda_powertools.shared.cookies import Cookie, SameSite
diff --git a/tests/unit/test_data_classes.py b/tests/unit/test_data_classes.py
index 63947eade11..91b906f5d9e 100644
--- a/tests/unit/test_data_classes.py
+++ b/tests/unit/test_data_classes.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import base64
import datetime
import json
diff --git a/tests/unit/test_json_encoder.py b/tests/unit/test_json_encoder.py
index 0dad7634df5..74421860c96 100644
--- a/tests/unit/test_json_encoder.py
+++ b/tests/unit/test_json_encoder.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import decimal
import json
from dataclasses import dataclass
diff --git a/tests/unit/test_lru_cache.py b/tests/unit/test_lru_cache.py
index 0f5c44029e6..2424d629533 100644
--- a/tests/unit/test_lru_cache.py
+++ b/tests/unit/test_lru_cache.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import random
import sys
diff --git a/tests/unit/test_shared_functions.py b/tests/unit/test_shared_functions.py
index b286c536249..2cd6a41aa12 100644
--- a/tests/unit/test_shared_functions.py
+++ b/tests/unit/test_shared_functions.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import os
import warnings
from dataclasses import dataclass
diff --git a/tests/unit/test_tracing.py b/tests/unit/test_tracing.py
index 425230380ea..359adb4adff 100644
--- a/tests/unit/test_tracing.py
+++ b/tests/unit/test_tracing.py
@@ -1,12 +1,16 @@
+from __future__ import annotations
+
import contextlib
-from typing import NamedTuple
+from typing import TYPE_CHECKING, NamedTuple
from unittest import mock
-from unittest.mock import MagicMock
import pytest
from aws_lambda_powertools import Tracer
+if TYPE_CHECKING:
+ from unittest.mock import MagicMock
+
# Maintenance: This should move to Functional tests and use Fake over mocks.
MODULE_PREFIX = "tests.unit.test_tracing"