diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml
index 7d1ac0d64ae..2aae50705f1 100644
--- a/.github/workflows/dependency-review.yml
+++ b/.github/workflows/dependency-review.yml
@@ -19,4 +19,4 @@ jobs:
- name: 'Checkout Repository'
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- name: 'Dependency Review'
- uses: actions/dependency-review-action@72eb03d02c7872a771aacd928f3123ac62ad6d3a # v4.3.3
+ uses: actions/dependency-review-action@5a2ce3f5b92ee19cbb1541a4984c76d921601d7c # v4.3.4
diff --git a/.github/workflows/on_closed_issues.yml b/.github/workflows/on_closed_issues.yml
index 61a14b028d4..61f4d20460d 100644
--- a/.github/workflows/on_closed_issues.yml
+++ b/.github/workflows/on_closed_issues.yml
@@ -21,7 +21,7 @@ jobs:
permissions:
issues: write # comment on issues
steps:
- - uses: aws-actions/closed-issue-message@8b6324312193476beecf11f8e8539d73a3553bf4
+ - uses: aws-actions/closed-issue-message@80edfc24bdf1283400eb04d20a8a605ae8bf7d48
with:
repo-token: "${{ secrets.GITHUB_TOKEN }}"
message: |
diff --git a/.github/workflows/publish_v2_layer.yml b/.github/workflows/publish_v2_layer.yml
index 5a4542d3b0d..c2bfefd0cfd 100644
--- a/.github/workflows/publish_v2_layer.yml
+++ b/.github/workflows/publish_v2_layer.yml
@@ -117,14 +117,14 @@ jobs:
pip install --require-hashes -r requirements.txt
- name: Set up QEMU
- uses: docker/setup-qemu-action@5927c834f5b4fdf503fca6f4c7eccda82949e1ee # v2.0.0
+ uses: docker/setup-qemu-action@49b3bc8e6bdd4a60e6116a5414239cba5943d3cf # v2.0.0
with:
platforms: arm64
# NOTE: we need QEMU to build Layer against a different architecture (e.g., ARM)
- name: Set up Docker Buildx
id: builder
- uses: docker/setup-buildx-action@4fd812986e6c8c2a69e18311145f9371337f27d4 # v3.4.0
+ uses: docker/setup-buildx-action@aa33708b10e362ff993539393ff100fa93ed6a27 # v3.5.0
with:
install: true
driver: docker
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5d160fcd0cf..33bda222fad 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,6 +6,55 @@
## Bug Fixes
+* **idempotency:** ensure in_progress_expiration field is set on Lambda timeout. ([#4773](https://github.com/aws-powertools/powertools-lambda-python/issues/4773))
+
+## Features
+
+* **event_handler:** add OpenAPI extensions ([#4703](https://github.com/aws-powertools/powertools-lambda-python/issues/4703))
+
+## Maintenance
+
+* **ci:** new pre-release 2.41.1a2 ([#4758](https://github.com/aws-powertools/powertools-lambda-python/issues/4758))
+* **ci:** new pre-release 2.41.1a1 ([#4756](https://github.com/aws-powertools/powertools-lambda-python/issues/4756))
+* **ci:** new pre-release 2.41.1a5 ([#4777](https://github.com/aws-powertools/powertools-lambda-python/issues/4777))
+* **ci:** new pre-release 2.41.1a8 ([#4802](https://github.com/aws-powertools/powertools-lambda-python/issues/4802))
+* **ci:** new pre-release 2.41.1a7 ([#4792](https://github.com/aws-powertools/powertools-lambda-python/issues/4792))
+* **ci:** new pre-release 2.41.1a0 ([#4749](https://github.com/aws-powertools/powertools-lambda-python/issues/4749))
+* **ci:** new pre-release 2.41.1a4 ([#4772](https://github.com/aws-powertools/powertools-lambda-python/issues/4772))
+* **ci:** new pre-release 2.41.1a6 ([#4783](https://github.com/aws-powertools/powertools-lambda-python/issues/4783))
+* **ci:** new pre-release 2.41.1a3 ([#4766](https://github.com/aws-powertools/powertools-lambda-python/issues/4766))
+* **deps:** bump github.com/aws/aws-sdk-go-v2/config from 1.27.26 to 1.27.27 in /layer/scripts/layer-balancer in the layer-balancer group ([#4779](https://github.com/aws-powertools/powertools-lambda-python/issues/4779))
+* **deps:** bump the layer-balancer group in /layer/scripts/layer-balancer with 3 updates ([#4745](https://github.com/aws-powertools/powertools-lambda-python/issues/4745))
+* **deps:** bump docker/setup-buildx-action from 3.4.0 to 3.5.0 ([#4801](https://github.com/aws-powertools/powertools-lambda-python/issues/4801))
+* **deps:** bump datadog-lambda from 6.96.0 to 6.97.0 ([#4770](https://github.com/aws-powertools/powertools-lambda-python/issues/4770))
+* **deps:** bump docker/setup-qemu-action from 3.1.0 to 3.2.0 ([#4800](https://github.com/aws-powertools/powertools-lambda-python/issues/4800))
+* **deps:** bump actions/dependency-review-action from 4.3.3 to 4.3.4 ([#4753](https://github.com/aws-powertools/powertools-lambda-python/issues/4753))
+* **deps:** bump aws-actions/closed-issue-message from 8b6324312193476beecf11f8e8539d73a3553bf4 to 80edfc24bdf1283400eb04d20a8a605ae8bf7d48 ([#4786](https://github.com/aws-powertools/powertools-lambda-python/issues/4786))
+* **deps-dev:** bump mkdocs-material from 9.5.28 to 9.5.29 ([#4764](https://github.com/aws-powertools/powertools-lambda-python/issues/4764))
+* **deps-dev:** bump aws-cdk from 2.148.0 to 2.149.0 ([#4765](https://github.com/aws-powertools/powertools-lambda-python/issues/4765))
+* **deps-dev:** bump ruff from 0.5.1 to 0.5.2 ([#4762](https://github.com/aws-powertools/powertools-lambda-python/issues/4762))
+* **deps-dev:** bump sentry-sdk from 2.9.0 to 2.10.0 ([#4763](https://github.com/aws-powertools/powertools-lambda-python/issues/4763))
+* **deps-dev:** bump cdklabs-generative-ai-cdk-constructs from 0.1.211 to 0.1.212 ([#4769](https://github.com/aws-powertools/powertools-lambda-python/issues/4769))
+* **deps-dev:** bump cdklabs-generative-ai-cdk-constructs from 0.1.207 to 0.1.211 ([#4760](https://github.com/aws-powertools/powertools-lambda-python/issues/4760))
+* **deps-dev:** bump cfn-lint from 1.6.1 to 1.8.1 ([#4780](https://github.com/aws-powertools/powertools-lambda-python/issues/4780))
+* **deps-dev:** bump cfn-lint from 1.5.3 to 1.6.0 ([#4747](https://github.com/aws-powertools/powertools-lambda-python/issues/4747))
+* **deps-dev:** bump ruff from 0.5.2 to 0.5.3 ([#4781](https://github.com/aws-powertools/powertools-lambda-python/issues/4781))
+* **deps-dev:** bump cfn-lint from 1.6.0 to 1.6.1 ([#4751](https://github.com/aws-powertools/powertools-lambda-python/issues/4751))
+* **deps-dev:** bump pytest from 8.2.2 to 8.3.1 ([#4799](https://github.com/aws-powertools/powertools-lambda-python/issues/4799))
+* **deps-dev:** bump mypy-boto3-secretsmanager from 1.34.128 to 1.34.145 in the boto-typing group ([#4787](https://github.com/aws-powertools/powertools-lambda-python/issues/4787))
+* **deps-dev:** bump cfn-lint from 1.8.1 to 1.8.2 ([#4788](https://github.com/aws-powertools/powertools-lambda-python/issues/4788))
+* **deps-dev:** bump coverage from 7.5.4 to 7.6.0 ([#4746](https://github.com/aws-powertools/powertools-lambda-python/issues/4746))
+* **deps-dev:** bump cdklabs-generative-ai-cdk-constructs from 0.1.206 to 0.1.207 ([#4748](https://github.com/aws-powertools/powertools-lambda-python/issues/4748))
+* **deps-dev:** bump ruff from 0.5.3 to 0.5.4 ([#4798](https://github.com/aws-powertools/powertools-lambda-python/issues/4798))
+* **deps-dev:** bump pytest-asyncio from 0.23.7 to 0.23.8 ([#4776](https://github.com/aws-powertools/powertools-lambda-python/issues/4776))
+* **logger:** use package logger over source logger to reduce noise ([#4793](https://github.com/aws-powertools/powertools-lambda-python/issues/4793))
+
+
+
+## [v2.41.0] - 2024-07-11
+## Bug Fixes
+
+* **event_handler:** make the max_age attribute comply with RFC specification ([#4731](https://github.com/aws-powertools/powertools-lambda-python/issues/4731))
* **event_handler:** disable allow-credentials header when origin allow_origin is * ([#4638](https://github.com/aws-powertools/powertools-lambda-python/issues/4638))
* **event_handler:** convert null body to empty string in ALBResolver to avoid HTTP 502 ([#4683](https://github.com/aws-powertools/powertools-lambda-python/issues/4683))
* **event_handler:** custom serializer recursive values when using data validation ([#4664](https://github.com/aws-powertools/powertools-lambda-python/issues/4664))
@@ -17,53 +66,62 @@
## Features
+* **batch:** add option to not raise `BatchProcessingError` exception when the entire batch fails ([#4719](https://github.com/aws-powertools/powertools-lambda-python/issues/4719))
+* **feature_flags:** allow customers to bring their own boto3 client and session ([#4717](https://github.com/aws-powertools/powertools-lambda-python/issues/4717))
* **parser:** add support for API Gateway Lambda authorizer events ([#4718](https://github.com/aws-powertools/powertools-lambda-python/issues/4718))
## Maintenance
+* version bump
* Add token to codecov action ([#4682](https://github.com/aws-powertools/powertools-lambda-python/issues/4682))
-* **ci:** new pre-release 2.40.2a4 ([#4694](https://github.com/aws-powertools/powertools-lambda-python/issues/4694))
+* **ci:** new pre-release 2.40.2a5 ([#4706](https://github.com/aws-powertools/powertools-lambda-python/issues/4706))
* **ci:** new pre-release 2.40.2a0 ([#4665](https://github.com/aws-powertools/powertools-lambda-python/issues/4665))
+* **ci:** new pre-release 2.40.2a8 ([#4737](https://github.com/aws-powertools/powertools-lambda-python/issues/4737))
* **ci:** new pre-release 2.40.2a7 ([#4726](https://github.com/aws-powertools/powertools-lambda-python/issues/4726))
-* **ci:** new pre-release 2.40.2a6 ([#4715](https://github.com/aws-powertools/powertools-lambda-python/issues/4715))
* **ci:** new pre-release 2.40.2a1 ([#4669](https://github.com/aws-powertools/powertools-lambda-python/issues/4669))
-* **ci:** new pre-release 2.40.2a5 ([#4706](https://github.com/aws-powertools/powertools-lambda-python/issues/4706))
* **ci:** new pre-release 2.40.2a2 ([#4679](https://github.com/aws-powertools/powertools-lambda-python/issues/4679))
* **ci:** new pre-release 2.40.2a3 ([#4688](https://github.com/aws-powertools/powertools-lambda-python/issues/4688))
+* **ci:** new pre-release 2.40.2a6 ([#4715](https://github.com/aws-powertools/powertools-lambda-python/issues/4715))
+* **ci:** new pre-release 2.40.2a4 ([#4694](https://github.com/aws-powertools/powertools-lambda-python/issues/4694))
* **deps:** bump docker/setup-qemu-action from 3.0.0 to 3.1.0 ([#4685](https://github.com/aws-powertools/powertools-lambda-python/issues/4685))
-* **deps:** bump actions/download-artifact from 4.1.7 to 4.1.8 ([#4699](https://github.com/aws-powertools/powertools-lambda-python/issues/4699))
-* **deps:** bump docker/setup-buildx-action from 3.3.0 to 3.4.0 ([#4693](https://github.com/aws-powertools/powertools-lambda-python/issues/4693))
+* **deps:** bump actions/setup-python from 5.1.0 to 5.1.1 ([#4732](https://github.com/aws-powertools/powertools-lambda-python/issues/4732))
+* **deps:** bump the layer-balancer group in /layer/scripts/layer-balancer with 3 updates ([#4733](https://github.com/aws-powertools/powertools-lambda-python/issues/4733))
* **deps:** bump actions/upload-artifact from 4.3.3 to 4.3.4 ([#4698](https://github.com/aws-powertools/powertools-lambda-python/issues/4698))
-* **deps:** bump the layer-balancer group in /layer/scripts/layer-balancer with 3 updates ([#4659](https://github.com/aws-powertools/powertools-lambda-python/issues/4659))
+* **deps:** bump actions/download-artifact from 4.1.7 to 4.1.8 ([#4699](https://github.com/aws-powertools/powertools-lambda-python/issues/4699))
* **deps:** bump actions/setup-node from 4.0.2 to 4.0.3 ([#4725](https://github.com/aws-powertools/powertools-lambda-python/issues/4725))
+* **deps:** bump zgosalvez/github-actions-ensure-sha-pinned-actions from 3.0.9 to 3.0.10 ([#4678](https://github.com/aws-powertools/powertools-lambda-python/issues/4678))
+* **deps:** bump docker/setup-buildx-action from 3.3.0 to 3.4.0 ([#4693](https://github.com/aws-powertools/powertools-lambda-python/issues/4693))
* **deps:** bump zipp from 3.17.0 to 3.19.1 in /docs ([#4720](https://github.com/aws-powertools/powertools-lambda-python/issues/4720))
+* **deps:** bump the layer-balancer group in /layer/scripts/layer-balancer with 3 updates ([#4659](https://github.com/aws-powertools/powertools-lambda-python/issues/4659))
* **deps:** bump certifi from 2024.6.2 to 2024.7.4 ([#4700](https://github.com/aws-powertools/powertools-lambda-python/issues/4700))
-* **deps:** bump zgosalvez/github-actions-ensure-sha-pinned-actions from 3.0.9 to 3.0.10 ([#4678](https://github.com/aws-powertools/powertools-lambda-python/issues/4678))
* **deps:** bump github.com/aws/aws-sdk-go-v2/config from 1.27.23 to 1.27.24 in /layer/scripts/layer-balancer in the layer-balancer group ([#4684](https://github.com/aws-powertools/powertools-lambda-python/issues/4684))
-* **deps-dev:** bump aws-cdk from 2.147.2 to 2.147.3 ([#4672](https://github.com/aws-powertools/powertools-lambda-python/issues/4672))
-* **deps-dev:** bump aws-cdk-lib from 2.147.2 to 2.147.3 ([#4674](https://github.com/aws-powertools/powertools-lambda-python/issues/4674))
-* **deps-dev:** bump cfn-lint from 1.5.0 to 1.5.1 ([#4711](https://github.com/aws-powertools/powertools-lambda-python/issues/4711))
-* **deps-dev:** bump sentry-sdk from 2.7.1 to 2.8.0 ([#4712](https://github.com/aws-powertools/powertools-lambda-python/issues/4712))
* **deps-dev:** bump mkdocs-material from 9.5.27 to 9.5.28 ([#4676](https://github.com/aws-powertools/powertools-lambda-python/issues/4676))
-* **deps-dev:** bump cdklabs-generative-ai-cdk-constructs from 0.1.199 to 0.1.200 ([#4677](https://github.com/aws-powertools/powertools-lambda-python/issues/4677))
* **deps-dev:** bump cfn-lint from 1.4.2 to 1.5.0 ([#4675](https://github.com/aws-powertools/powertools-lambda-python/issues/4675))
-* **deps-dev:** bump ruff from 0.5.0 to 0.5.1 ([#4697](https://github.com/aws-powertools/powertools-lambda-python/issues/4697))
-* **deps-dev:** bump mypy-boto3-s3 from 1.34.120 to 1.34.138 in the boto-typing group ([#4673](https://github.com/aws-powertools/powertools-lambda-python/issues/4673))
-* **deps-dev:** bump aws-cdk from 2.147.3 to 2.148.0 ([#4708](https://github.com/aws-powertools/powertools-lambda-python/issues/4708))
-* **deps-dev:** bump aws-cdk-lib from 2.147.3 to 2.148.0 ([#4710](https://github.com/aws-powertools/powertools-lambda-python/issues/4710))
-* **deps-dev:** bump cfn-lint from 1.4.1 to 1.4.2 ([#4660](https://github.com/aws-powertools/powertools-lambda-python/issues/4660))
-* **deps-dev:** bump cdklabs-generative-ai-cdk-constructs from 0.1.198 to 0.1.199 ([#4668](https://github.com/aws-powertools/powertools-lambda-python/issues/4668))
-* **deps-dev:** bump aws-cdk-aws-lambda-python-alpha from 2.147.1a0 to 2.147.2a0 ([#4667](https://github.com/aws-powertools/powertools-lambda-python/issues/4667))
* **deps-dev:** bump aws-cdk-aws-lambda-python-alpha from 2.147.3a0 to 2.148.0a0 ([#4722](https://github.com/aws-powertools/powertools-lambda-python/issues/4722))
+* **deps-dev:** bump cdklabs-generative-ai-cdk-constructs from 0.1.200 to 0.1.201 ([#4687](https://github.com/aws-powertools/powertools-lambda-python/issues/4687))
+* **deps-dev:** bump aws-cdk-lib from 2.147.2 to 2.147.3 ([#4674](https://github.com/aws-powertools/powertools-lambda-python/issues/4674))
* **deps-dev:** bump zipp from 3.17.0 to 3.19.1 in /layer ([#4721](https://github.com/aws-powertools/powertools-lambda-python/issues/4721))
* **deps-dev:** bump cdklabs-generative-ai-cdk-constructs from 0.1.202 to 0.1.205 ([#4723](https://github.com/aws-powertools/powertools-lambda-python/issues/4723))
-* **deps-dev:** bump cdklabs-generative-ai-cdk-constructs from 0.1.200 to 0.1.201 ([#4687](https://github.com/aws-powertools/powertools-lambda-python/issues/4687))
-* **deps-dev:** bump aws-cdk-lib from 2.147.1 to 2.147.2 ([#4661](https://github.com/aws-powertools/powertools-lambda-python/issues/4661))
* **deps-dev:** bump aws-cdk-aws-lambda-python-alpha from 2.147.2a0 to 2.147.3a0 ([#4686](https://github.com/aws-powertools/powertools-lambda-python/issues/4686))
+* **deps-dev:** bump cdklabs-generative-ai-cdk-constructs from 0.1.199 to 0.1.200 ([#4677](https://github.com/aws-powertools/powertools-lambda-python/issues/4677))
+* **deps-dev:** bump aws-cdk-lib from 2.147.3 to 2.148.0 ([#4710](https://github.com/aws-powertools/powertools-lambda-python/issues/4710))
+* **deps-dev:** bump aws-cdk from 2.147.2 to 2.147.3 ([#4672](https://github.com/aws-powertools/powertools-lambda-python/issues/4672))
+* **deps-dev:** bump mypy-boto3-s3 from 1.34.120 to 1.34.138 in the boto-typing group ([#4673](https://github.com/aws-powertools/powertools-lambda-python/issues/4673))
+* **deps-dev:** bump cdklabs-generative-ai-cdk-constructs from 0.1.201 to 0.1.202 ([#4696](https://github.com/aws-powertools/powertools-lambda-python/issues/4696))
+* **deps-dev:** bump cfn-lint from 1.5.1 to 1.5.2 ([#4724](https://github.com/aws-powertools/powertools-lambda-python/issues/4724))
+* **deps-dev:** bump ruff from 0.5.0 to 0.5.1 ([#4697](https://github.com/aws-powertools/powertools-lambda-python/issues/4697))
+* **deps-dev:** bump cdklabs-generative-ai-cdk-constructs from 0.1.198 to 0.1.199 ([#4668](https://github.com/aws-powertools/powertools-lambda-python/issues/4668))
+* **deps-dev:** bump aws-cdk-aws-lambda-python-alpha from 2.147.1a0 to 2.147.2a0 ([#4667](https://github.com/aws-powertools/powertools-lambda-python/issues/4667))
+* **deps-dev:** bump aws-cdk from 2.147.3 to 2.148.0 ([#4708](https://github.com/aws-powertools/powertools-lambda-python/issues/4708))
+* **deps-dev:** bump cfn-lint from 1.5.2 to 1.5.3 ([#4734](https://github.com/aws-powertools/powertools-lambda-python/issues/4734))
+* **deps-dev:** bump sentry-sdk from 2.8.0 to 2.9.0 ([#4735](https://github.com/aws-powertools/powertools-lambda-python/issues/4735))
+* **deps-dev:** bump cfn-lint from 1.4.1 to 1.4.2 ([#4660](https://github.com/aws-powertools/powertools-lambda-python/issues/4660))
+* **deps-dev:** bump aws-cdk-lib from 2.147.1 to 2.147.2 ([#4661](https://github.com/aws-powertools/powertools-lambda-python/issues/4661))
+* **deps-dev:** bump cfn-lint from 1.5.0 to 1.5.1 ([#4711](https://github.com/aws-powertools/powertools-lambda-python/issues/4711))
* **deps-dev:** bump aws-cdk from 2.147.1 to 2.147.2 ([#4657](https://github.com/aws-powertools/powertools-lambda-python/issues/4657))
* **deps-dev:** bump ruff from 0.4.10 to 0.5.0 ([#4644](https://github.com/aws-powertools/powertools-lambda-python/issues/4644))
-* **deps-dev:** bump cfn-lint from 1.5.1 to 1.5.2 ([#4724](https://github.com/aws-powertools/powertools-lambda-python/issues/4724))
-* **deps-dev:** bump cdklabs-generative-ai-cdk-constructs from 0.1.201 to 0.1.202 ([#4696](https://github.com/aws-powertools/powertools-lambda-python/issues/4696))
+* **deps-dev:** bump sentry-sdk from 2.7.1 to 2.8.0 ([#4712](https://github.com/aws-powertools/powertools-lambda-python/issues/4712))
+* **layers:** downgrade aws cdk to 2.145.0 ([#4739](https://github.com/aws-powertools/powertools-lambda-python/issues/4739))
@@ -5137,7 +5195,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/v2.40.1...HEAD
+[Unreleased]: https://github.com/aws-powertools/powertools-lambda-python/compare/v2.41.0...HEAD
+[v2.41.0]: https://github.com/aws-powertools/powertools-lambda-python/compare/v2.40.1...v2.41.0
[v2.40.1]: https://github.com/aws-powertools/powertools-lambda-python/compare/v2.40.0...v2.40.1
[v2.40.0]: https://github.com/aws-powertools/powertools-lambda-python/compare/v2.39.1...v2.40.0
[v2.39.1]: https://github.com/aws-powertools/powertools-lambda-python/compare/v2.39.0...v2.39.1
diff --git a/aws_lambda_powertools/event_handler/api_gateway.py b/aws_lambda_powertools/event_handler/api_gateway.py
index 2c829789e8c..8e84a74ef60 100644
--- a/aws_lambda_powertools/event_handler/api_gateway.py
+++ b/aws_lambda_powertools/event_handler/api_gateway.py
@@ -323,6 +323,7 @@ def __init__(
operation_id: Optional[str] = None,
include_in_schema: bool = True,
security: Optional[List[Dict[str, List[str]]]] = None,
+ openapi_extensions: Optional[Dict[str, Any]] = None,
middlewares: Optional[List[Callable[..., Response]]] = None,
):
"""
@@ -360,6 +361,8 @@ def __init__(
Whether or not to include this route in the OpenAPI schema
security: List[Dict[str, List[str]]], optional
The OpenAPI security for this route
+ openapi_extensions: Dict[str, Any], optional
+ Additional OpenAPI extensions as a dictionary.
middlewares: Optional[List[Callable[..., Response]]]
The list of route middlewares to be called in order.
"""
@@ -383,6 +386,7 @@ def __init__(
self.tags = tags or []
self.include_in_schema = include_in_schema
self.security = security
+ self.openapi_extensions = openapi_extensions
self.middlewares = middlewares or []
self.operation_id = operation_id or self._generate_operation_id()
@@ -534,6 +538,10 @@ def _get_openapi_path(
if self.security:
operation["security"] = self.security
+ # Add OpenAPI extensions if present
+ if self.openapi_extensions:
+ operation.update(self.openapi_extensions)
+
# Add the parameters to the OpenAPI operation
if parameters:
all_parameters = {(param["in"], param["name"]): param for param in parameters}
@@ -939,6 +947,7 @@ def route(
operation_id: Optional[str] = None,
include_in_schema: bool = True,
security: Optional[List[Dict[str, List[str]]]] = None,
+ openapi_extensions: Optional[Dict[str, Any]] = None,
middlewares: Optional[List[Callable[..., Any]]] = None,
):
raise NotImplementedError()
@@ -998,6 +1007,7 @@ def get(
operation_id: Optional[str] = None,
include_in_schema: bool = True,
security: Optional[List[Dict[str, List[str]]]] = None,
+ openapi_extensions: Optional[Dict[str, Any]] = None,
middlewares: Optional[List[Callable[..., Any]]] = None,
):
"""Get route decorator with GET `method`
@@ -1036,6 +1046,7 @@ def lambda_handler(event, context):
operation_id,
include_in_schema,
security,
+ openapi_extensions,
middlewares,
)
@@ -1053,6 +1064,7 @@ def post(
operation_id: Optional[str] = None,
include_in_schema: bool = True,
security: Optional[List[Dict[str, List[str]]]] = None,
+ openapi_extensions: Optional[Dict[str, Any]] = None,
middlewares: Optional[List[Callable[..., Any]]] = None,
):
"""Post route decorator with POST `method`
@@ -1092,6 +1104,7 @@ def lambda_handler(event, context):
operation_id,
include_in_schema,
security,
+ openapi_extensions,
middlewares,
)
@@ -1109,6 +1122,7 @@ def put(
operation_id: Optional[str] = None,
include_in_schema: bool = True,
security: Optional[List[Dict[str, List[str]]]] = None,
+ openapi_extensions: Optional[Dict[str, Any]] = None,
middlewares: Optional[List[Callable[..., Any]]] = None,
):
"""Put route decorator with PUT `method`
@@ -1148,6 +1162,7 @@ def lambda_handler(event, context):
operation_id,
include_in_schema,
security,
+ openapi_extensions,
middlewares,
)
@@ -1165,6 +1180,7 @@ def delete(
operation_id: Optional[str] = None,
include_in_schema: bool = True,
security: Optional[List[Dict[str, List[str]]]] = None,
+ openapi_extensions: Optional[Dict[str, Any]] = None,
middlewares: Optional[List[Callable[..., Any]]] = None,
):
"""Delete route decorator with DELETE `method`
@@ -1203,6 +1219,7 @@ def lambda_handler(event, context):
operation_id,
include_in_schema,
security,
+ openapi_extensions,
middlewares,
)
@@ -1220,6 +1237,7 @@ def patch(
operation_id: Optional[str] = None,
include_in_schema: bool = True,
security: Optional[List[Dict[str, List[str]]]] = None,
+ openapi_extensions: Optional[Dict[str, Any]] = None,
middlewares: Optional[List[Callable]] = None,
):
"""Patch route decorator with PATCH `method`
@@ -1261,6 +1279,7 @@ def lambda_handler(event, context):
operation_id,
include_in_schema,
security,
+ openapi_extensions,
middlewares,
)
@@ -1278,6 +1297,7 @@ def head(
operation_id: Optional[str] = None,
include_in_schema: bool = True,
security: Optional[List[Dict[str, List[str]]]] = None,
+ openapi_extensions: Optional[Dict[str, Any]] = None,
middlewares: Optional[List[Callable]] = None,
):
"""Head route decorator with HEAD `method`
@@ -1318,6 +1338,7 @@ def lambda_handler(event, context):
operation_id,
include_in_schema,
security,
+ openapi_extensions,
middlewares,
)
@@ -1541,6 +1562,7 @@ def get_openapi_schema(
license_info: Optional["License"] = None,
security_schemes: Optional[Dict[str, "SecurityScheme"]] = None,
security: Optional[List[Dict[str, List[str]]]] = None,
+ openapi_extensions: Optional[Dict[str, Any]] = None,
) -> "OpenAPI":
"""
Returns the OpenAPI schema as a pydantic model.
@@ -1571,6 +1593,8 @@ def get_openapi_schema(
A declaration of the security schemes available to be used in the specification.
security: List[Dict[str, List[str]]], optional
A declaration of which security mechanisms are applied globally across the API.
+ openapi_extensions: Dict[str, Any], optional
+ Additional OpenAPI extensions as a dictionary.
Returns
-------
@@ -1603,11 +1627,15 @@ def get_openapi_schema(
info.update({field: value for field, value in optional_fields.items() if value})
+ if not isinstance(openapi_extensions, Dict):
+ openapi_extensions = {}
+
output: Dict[str, Any] = {
"openapi": openapi_version,
"info": info,
"servers": self._get_openapi_servers(servers),
"security": self._get_openapi_security(security, security_schemes),
+ **openapi_extensions,
}
components: Dict[str, Dict[str, Any]] = {}
@@ -1726,6 +1754,7 @@ def get_openapi_json_schema(
license_info: Optional["License"] = None,
security_schemes: Optional[Dict[str, "SecurityScheme"]] = None,
security: Optional[List[Dict[str, List[str]]]] = None,
+ openapi_extensions: Optional[Dict[str, Any]] = None,
) -> str:
"""
Returns the OpenAPI schema as a JSON serializable dict
@@ -1756,6 +1785,8 @@ def get_openapi_json_schema(
A declaration of the security schemes available to be used in the specification.
security: List[Dict[str, List[str]]], optional
A declaration of which security mechanisms are applied globally across the API.
+ openapi_extensions: Dict[str, Any], optional
+ Additional OpenAPI extensions as a dictionary.
Returns
-------
@@ -1778,6 +1809,7 @@ def get_openapi_json_schema(
license_info=license_info,
security_schemes=security_schemes,
security=security,
+ openapi_extensions=openapi_extensions,
),
by_alias=True,
exclude_none=True,
@@ -1805,6 +1837,7 @@ def enable_swagger(
security: Optional[List[Dict[str, List[str]]]] = None,
oauth2_config: Optional["OAuth2Config"] = None,
persist_authorization: bool = False,
+ openapi_extensions: Optional[Dict[str, Any]] = None,
):
"""
Returns the OpenAPI schema as a JSON serializable dict
@@ -1847,6 +1880,8 @@ def enable_swagger(
The OAuth2 configuration for the Swagger UI.
persist_authorization: bool, optional
Whether to persist authorization data on browser close/refresh.
+ openapi_extensions: Dict[str, Any], optional
+ Additional OpenAPI extensions as a dictionary.
"""
from aws_lambda_powertools.event_handler.openapi.compat import model_json
from aws_lambda_powertools.event_handler.openapi.models import Server
@@ -1896,6 +1931,7 @@ def swagger_handler():
license_info=license_info,
security_schemes=security_schemes,
security=security,
+ openapi_extensions=openapi_extensions,
)
# The .replace('', '<\\/') part is necessary to prevent a potential issue where the JSON string contains
@@ -1949,6 +1985,7 @@ def route(
operation_id: Optional[str] = None,
include_in_schema: bool = True,
security: Optional[List[Dict[str, List[str]]]] = None,
+ openapi_extensions: Optional[Dict[str, Any]] = None,
middlewares: Optional[List[Callable[..., Any]]] = None,
):
"""Route decorator includes parameter `method`"""
@@ -1976,6 +2013,7 @@ def register_resolver(func: Callable):
operation_id,
include_in_schema,
security,
+ openapi_extensions,
middlewares,
)
@@ -2489,6 +2527,7 @@ def route(
operation_id: Optional[str] = None,
include_in_schema: bool = True,
security: Optional[List[Dict[str, List[str]]]] = None,
+ openapi_extensions: Optional[Dict[str, Any]] = None,
middlewares: Optional[List[Callable[..., Any]]] = None,
):
def register_route(func: Callable):
@@ -2497,6 +2536,7 @@ def register_route(func: Callable):
frozen_responses = _FrozenDict(responses) if responses else None
frozen_tags = frozenset(tags) if tags else None
frozen_security = _FrozenListDict(security) if security else None
+ fronzen_openapi_extensions = _FrozenDict(openapi_extensions) if openapi_extensions else None
route_key = (
rule,
@@ -2512,6 +2552,7 @@ def register_route(func: Callable):
operation_id,
include_in_schema,
frozen_security,
+ fronzen_openapi_extensions,
)
# Collate Middleware for routes
@@ -2592,6 +2633,7 @@ def route(
operation_id: Optional[str] = None,
include_in_schema: bool = True,
security: Optional[List[Dict[str, List[str]]]] = None,
+ openapi_extensions: Optional[Dict[str, Any]] = None,
middlewares: Optional[List[Callable[..., Any]]] = None,
):
# NOTE: see #1552 for more context.
@@ -2609,6 +2651,7 @@ def route(
operation_id,
include_in_schema,
security,
+ openapi_extensions,
middlewares,
)
diff --git a/aws_lambda_powertools/event_handler/bedrock_agent.py b/aws_lambda_powertools/event_handler/bedrock_agent.py
index 4d1a6096f32..ac0c8be92b2 100644
--- a/aws_lambda_powertools/event_handler/bedrock_agent.py
+++ b/aws_lambda_powertools/event_handler/bedrock_agent.py
@@ -102,6 +102,8 @@ def get( # type: ignore[override]
include_in_schema: bool = True,
middlewares: Optional[List[Callable[..., Any]]] = None,
) -> Callable[[Callable[..., Any]], Callable[..., Any]]:
+
+ openapi_extensions = None
security = None
return super(BedrockAgentResolver, self).get(
@@ -117,6 +119,7 @@ def get( # type: ignore[override]
operation_id,
include_in_schema,
security,
+ openapi_extensions,
middlewares,
)
@@ -137,6 +140,7 @@ def post( # type: ignore[override]
include_in_schema: bool = True,
middlewares: Optional[List[Callable[..., Any]]] = None,
):
+ openapi_extensions = None
security = None
return super().post(
@@ -152,6 +156,7 @@ def post( # type: ignore[override]
operation_id,
include_in_schema,
security,
+ openapi_extensions,
middlewares,
)
@@ -172,6 +177,7 @@ def put( # type: ignore[override]
include_in_schema: bool = True,
middlewares: Optional[List[Callable[..., Any]]] = None,
):
+ openapi_extensions = None
security = None
return super().put(
@@ -187,6 +193,7 @@ def put( # type: ignore[override]
operation_id,
include_in_schema,
security,
+ openapi_extensions,
middlewares,
)
@@ -207,6 +214,7 @@ def patch( # type: ignore[override]
include_in_schema: bool = True,
middlewares: Optional[List[Callable]] = None,
):
+ openapi_extensions = None
security = None
return super().patch(
@@ -222,6 +230,7 @@ def patch( # type: ignore[override]
operation_id,
include_in_schema,
security,
+ openapi_extensions,
middlewares,
)
@@ -242,6 +251,7 @@ def delete( # type: ignore[override]
include_in_schema: bool = True,
middlewares: Optional[List[Callable[..., Any]]] = None,
):
+ openapi_extensions = None
security = None
return super().delete(
@@ -257,6 +267,7 @@ def delete( # type: ignore[override]
operation_id,
include_in_schema,
security,
+ openapi_extensions,
middlewares,
)
diff --git a/aws_lambda_powertools/event_handler/openapi/compat.py b/aws_lambda_powertools/event_handler/openapi/compat.py
index 060886605ec..df0f8aabab7 100644
--- a/aws_lambda_powertools/event_handler/openapi/compat.py
+++ b/aws_lambda_powertools/event_handler/openapi/compat.py
@@ -41,7 +41,7 @@
RequestErrorModel: Type[BaseModel] = create_model("Request")
if PYDANTIC_V2: # pragma: no cover # false positive; dropping in v3
- from pydantic import TypeAdapter, ValidationError
+ from pydantic import TypeAdapter, ValidationError, model_validator as parser_openapi_extension
from pydantic._internal._typing_extra import eval_type_lenient
from pydantic.fields import FieldInfo
from pydantic._internal._utils import lenient_issubclass
@@ -217,7 +217,7 @@ def model_json(model: BaseModel, **kwargs: Any) -> Any:
return model.model_dump_json(**kwargs)
else:
- from pydantic import BaseModel, ValidationError
+ from pydantic import BaseModel, ValidationError, root_validator as parser_openapi_extension
from pydantic.fields import (
ModelField,
Required,
diff --git a/aws_lambda_powertools/event_handler/openapi/models.py b/aws_lambda_powertools/event_handler/openapi/models.py
index 04345ddaad7..cac3266d254 100644
--- a/aws_lambda_powertools/event_handler/openapi/models.py
+++ b/aws_lambda_powertools/event_handler/openapi/models.py
@@ -3,7 +3,8 @@
from pydantic import AnyUrl, BaseModel, Field
-from aws_lambda_powertools.event_handler.openapi.compat import model_rebuild
+from aws_lambda_powertools.event_handler.openapi.compat import model_rebuild, parser_openapi_extension
+from aws_lambda_powertools.event_handler.openapi.exceptions import SchemaValidationError
from aws_lambda_powertools.event_handler.openapi.pydantic_loader import PYDANTIC_V2
from aws_lambda_powertools.shared.types import Annotated, Literal
@@ -13,6 +14,62 @@
"""
+class OpenAPIExtensions(BaseModel):
+ """
+ This class serves as a Pydantic proxy model to add OpenAPI extensions.
+
+ OpenAPI extensions are arbitrary fields, so we remove openapi_extensions when dumping
+ and add only the provided value in the schema.
+ """
+
+ openapi_extensions: Optional[Dict[str, Any]] = None
+
+ # This rule is valid for Pydantic v1 and v2
+ # If the 'openapi_extensions' field is present in the 'values' dictionary,
+ # And if the extension starts with x- (must respect the RFC)
+ # update the 'values' dictionary with the contents of 'openapi_extensions',
+ # and then remove the 'openapi_extensions' field from the 'values' dictionary
+
+ if PYDANTIC_V2:
+
+ model_config = {"extra": "allow"}
+
+ @parser_openapi_extension(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:
+ if not str(extension_key).startswith("x-"):
+ raise SchemaValidationError("An OpenAPI extension key must start with x-")
+
+ self.update(openapi_extension_value)
+ self.pop("openapi_extensions", None)
+
+ return self
+
+ else:
+
+ @parser_openapi_extension(pre=False, allow_reuse=True)
+ def serialize_openapi_extension_v1(cls, values):
+ openapi_extension_value = values.get("openapi_extensions")
+
+ if openapi_extension_value:
+
+ for extension_key in openapi_extension_value:
+ if not str(extension_key).startswith("x-"):
+ raise SchemaValidationError("An OpenAPI extension key must start with x-")
+
+ values.update(values["openapi_extensions"])
+ del values["openapi_extensions"]
+
+ return values
+
+ class Config:
+ extra = "allow"
+
+
# https://swagger.io/specification/#contact-object
class Contact(BaseModel):
name: Optional[str] = None
@@ -77,7 +134,7 @@ class Config:
# https://swagger.io/specification/#server-object
-class Server(BaseModel):
+class Server(OpenAPIExtensions):
url: Union[AnyUrl, str]
description: Optional[str] = None
variables: Optional[Dict[str, ServerVariable]] = None
@@ -379,7 +436,7 @@ class Config:
# https://swagger.io/specification/#operation-object
-class Operation(BaseModel):
+class Operation(OpenAPIExtensions):
tags: Optional[List[str]] = None
summary: Optional[str] = None
description: Optional[str] = None
@@ -436,12 +493,12 @@ class SecuritySchemeType(Enum):
openIdConnect = "openIdConnect"
-class SecurityBase(BaseModel):
+class SecurityBase(OpenAPIExtensions):
type_: SecuritySchemeType = Field(alias="type")
description: Optional[str] = None
if PYDANTIC_V2:
- model_config = {"extra": "allow", "populate_by_name": True}
+ model_config = {"extra": "allow", "populate_by_name": True} # type: ignore
else:
@@ -557,7 +614,7 @@ class Config:
# https://swagger.io/specification/#openapi-object
-class OpenAPI(BaseModel):
+class OpenAPI(OpenAPIExtensions):
openapi: str
info: Info
jsonSchemaDialect: Optional[str] = None
diff --git a/aws_lambda_powertools/logging/utils.py b/aws_lambda_powertools/logging/utils.py
index 3e1c3c69aed..45819322c28 100644
--- a/aws_lambda_powertools/logging/utils.py
+++ b/aws_lambda_powertools/logging/utils.py
@@ -4,6 +4,7 @@
from .logger import Logger
PACKAGE_LOGGER = "aws_lambda_powertools"
+LOGGER = logging.getLogger(__name__)
def copy_config_to_registered_loggers(
@@ -56,7 +57,7 @@ def copy_config_to_registered_loggers(
loggers = exclude
filter_func = _exclude_registered_loggers_filter
- registered_loggers = _find_registered_loggers(source_logger, loggers, filter_func)
+ registered_loggers = _find_registered_loggers(loggers=loggers, filter_func=filter_func)
for logger in registered_loggers:
_configure_logger(source_logger=source_logger, logger=logger, level=level, ignore_log_level=ignore_log_level)
@@ -72,13 +73,12 @@ def _exclude_registered_loggers_filter(loggers: Set[str]) -> List[logging.Logger
def _find_registered_loggers(
- source_logger: Logger,
loggers: Set[str],
filter_func: Callable[[Set[str]], List[logging.Logger]],
) -> List[logging.Logger]:
"""Filter root loggers based on provided parameters."""
root_loggers = filter_func(loggers)
- source_logger.debug(f"Filtered root loggers: {root_loggers}")
+ LOGGER.debug(f"Filtered root loggers: {root_loggers}")
return root_loggers
@@ -91,7 +91,7 @@ def _configure_logger(
# customers may not want to copy the same log level from Logger to discovered loggers
if not ignore_log_level:
logger.setLevel(level)
- source_logger.debug(f"Logger {logger} reconfigured to use logging level {level}")
+ LOGGER.debug(f"Logger {logger} reconfigured to use logging level {level}")
logger.handlers = []
logger.propagate = False # ensure we don't propagate logs to existing loggers, #1073
@@ -99,4 +99,4 @@ def _configure_logger(
for source_handler in source_logger.handlers:
logger.addHandler(source_handler)
- source_logger.debug(f"Logger {logger} reconfigured to use {source_handler}")
+ LOGGER.debug(f"Logger {logger} reconfigured to use {source_handler}")
diff --git a/aws_lambda_powertools/shared/version.py b/aws_lambda_powertools/shared/version.py
index 2124f14c013..625081d751b 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 = "2.40.2a8"
+VERSION = "2.42.0"
diff --git a/aws_lambda_powertools/utilities/idempotency/persistence/base.py b/aws_lambda_powertools/utilities/idempotency/persistence/base.py
index 95736634ca6..8f41337cf5f 100644
--- a/aws_lambda_powertools/utilities/idempotency/persistence/base.py
+++ b/aws_lambda_powertools/utilities/idempotency/persistence/base.py
@@ -301,7 +301,10 @@ def save_inprogress(self, data: Dict[str, Any], remaining_time_in_millis: Option
payload_hash=self._get_hashed_payload(data=data),
)
- if remaining_time_in_millis:
+ # When Lambda kills the container after timeout, the remaining_time_in_millis is 0, which is considered False.
+ # Therefore, we need to check if remaining_time_in_millis is not None (>=0) to handle this case.
+ # See: https://github.com/aws-powertools/powertools-lambda-python/issues/4759
+ if remaining_time_in_millis is not None:
now = datetime.datetime.now()
period = datetime.timedelta(milliseconds=remaining_time_in_millis)
timestamp = (now + period).timestamp()
diff --git a/docs/core/event_handler/api_gateway.md b/docs/core/event_handler/api_gateway.md
index b3c046e243c..d5fcbd7619e 100644
--- a/docs/core/event_handler/api_gateway.md
+++ b/docs/core/event_handler/api_gateway.md
@@ -1115,6 +1115,22 @@ OpenAPI 3 lets you describe APIs protected using the following security schemes:
--8<-- "examples/event_handler_rest/src/swagger_with_oauth2.py"
```
+#### OpenAPI extensions
+
+For a better experience when working with Lambda and Amazon API Gateway, customers can define extensions using the `openapi_extensions` parameter. We support defining OpenAPI extensions at the following levels of the OpenAPI JSON Schema: Root, Servers, Operation, and Security Schemes.
+
+???+ warning
+ We do not support the `x-amazon-apigateway-any-method` and `x-amazon-apigateway-integrations` extensions.
+
+```python hl_lines="9 15 25 28" title="Adding OpenAPI extensions"
+--8<-- "examples/event_handler_rest/src/working_with_openapi_extensions.py"
+```
+
+1. Server level
+2. Operation level
+3. Security scheme level
+4. Root level
+
### Custom serializer
You can instruct event handler to use a custom serializer to best suit your needs, for example take into account Enums when serializing.
diff --git a/docs/index.md b/docs/index.md
index 0c9c4ed48c6..9972e3f9d7c 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -67,8 +67,8 @@ You can install Powertools for AWS Lambda (Python) using your favorite dependenc
| Architecture | Layer ARN |
| ------------ | --------------------------------------------------------------------------------------------------------- |
- | x86 | __arn:aws:lambda:{region}:017000801446:layer:AWSLambdaPowertoolsPythonV2:75__{: .copyMe}:clipboard: |
- | ARM | __arn:aws:lambda:{region}:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:75__{: .copyMe}:clipboard: |
+ | x86 | __arn:aws:lambda:{region}:017000801446:layer:AWSLambdaPowertoolsPythonV2:76__{: .copyMe}:clipboard: |
+ | ARM | __arn:aws:lambda:{region}:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:76__{: .copyMe}:clipboard: |
=== "AWS Console"
@@ -84,70 +84,70 @@ You can install Powertools for AWS Lambda (Python) using your favorite dependenc
| Region name | Region code | Layer ARN |
| ------------------------- | -------------------- | --------------------------------------------------------------------------------------------------------- |
- | Africa (Cape Town) | **`af-south-1`** | **arn:aws:lambda:af-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:75**{: .copyMe}:clipboard: |
- | Asia Pacific (Hong Kong) | **`ap-east-1`** | **arn:aws:lambda:ap-east-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:75**{: .copyMe}:clipboard: |
- | Asia Pacific (Tokyo) | **`ap-northeast-1`** | **arn:aws:lambda:ap-northeast-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:75**{: .copyMe}:clipboard: |
- | Asia Pacific (Seoul) | **`ap-northeast-2`** | **arn:aws:lambda:ap-northeast-2:017000801446:layer:AWSLambdaPowertoolsPythonV2:75**{: .copyMe}:clipboard: |
- | Asia Pacific (Osaka) | **`ap-northeast-3`** | **arn:aws:lambda:ap-northeast-3:017000801446:layer:AWSLambdaPowertoolsPythonV2:75**{: .copyMe}:clipboard: |
- | Asia Pacific (Mumbai) | **`ap-south-1`** | **arn:aws:lambda:ap-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:75**{: .copyMe}:clipboard: |
- | Asia Pacific (Hyderabad) | **`ap-south-2`** | **arn:aws:lambda:ap-south-2:017000801446:layer:AWSLambdaPowertoolsPythonV2:75**{: .copyMe}:clipboard: |
- | Asia Pacific (Singapore) | **`ap-southeast-1`** | **arn:aws:lambda:ap-southeast-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:75**{: .copyMe}:clipboard: |
- | Asia Pacific (Sydney) | **`ap-southeast-2`** | **arn:aws:lambda:ap-southeast-2:017000801446:layer:AWSLambdaPowertoolsPythonV2:75**{: .copyMe}:clipboard: |
- | Asia Pacific (Jakarta) | **`ap-southeast-3`** | **arn:aws:lambda:ap-southeast-3:017000801446:layer:AWSLambdaPowertoolsPythonV2:75**{: .copyMe}:clipboard: |
- | Asia Pacific (Melbourne) | **`ap-southeast-4`** | **arn:aws:lambda:ap-southeast-4:017000801446:layer:AWSLambdaPowertoolsPythonV2:75**{: .copyMe}:clipboard: |
- | Canada (Central) | **`ca-central-1`** | **arn:aws:lambda:ca-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:75**{: .copyMe}:clipboard: |
- | Canada (West) | **`ca-west-1`** | **arn:aws:lambda:ca-west-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:75**{: .copyMe}:clipboard: |
- | Europe (Frankfurt) | **`eu-central-1`** | **arn:aws:lambda:eu-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:75**{: .copyMe}:clipboard: |
- | Europe (Zurich) | **`eu-central-2`** | **arn:aws:lambda:eu-central-2:017000801446:layer:AWSLambdaPowertoolsPythonV2:75**{: .copyMe}:clipboard: |
- | Europe (Stockholm) | **`eu-north-1`** | **arn:aws:lambda:eu-north-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:75**{: .copyMe}:clipboard: |
- | Europe (Milan) | **`eu-south-1`** | **arn:aws:lambda:eu-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:75**{: .copyMe}:clipboard: |
- | Europe (Spain) | **`eu-south-2`** | **arn:aws:lambda:eu-south-2:017000801446:layer:AWSLambdaPowertoolsPythonV2:75**{: .copyMe}:clipboard: |
- | Europe (Ireland) | **`eu-west-1`** | **arn:aws:lambda:eu-west-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:75**{: .copyMe}:clipboard: |
- | Europe (London) | **`eu-west-2`** | **arn:aws:lambda:eu-west-2:017000801446:layer:AWSLambdaPowertoolsPythonV2:75**{: .copyMe}:clipboard: |
- | Europe (Paris) | **`eu-west-3`** | **arn:aws:lambda:eu-west-3:017000801446:layer:AWSLambdaPowertoolsPythonV2:75**{: .copyMe}:clipboard: |
- | Middle East (Israel) | **`il-central-1`** | **arn:aws:lambda:il-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:75**{: .copyMe}:clipboard: |
- | Middle East (UAE) | **`me-central-1`** | **arn:aws:lambda:me-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:75**{: .copyMe}:clipboard: |
- | Middle East (Bahrain) | **`me-south-1`** | **arn:aws:lambda:me-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:75**{: .copyMe}:clipboard: |
- | South America (São Paulo) | **`sa-east-1`** | **arn:aws:lambda:sa-east-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:75**{: .copyMe}:clipboard: |
- | US East (N. Virginia) | **`us-east-1`** | **arn:aws:lambda:us-east-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:75**{: .copyMe}:clipboard: |
- | US East (Ohio) | **`us-east-2`** | **arn:aws:lambda:us-east-2:017000801446:layer:AWSLambdaPowertoolsPythonV2:75**{: .copyMe}:clipboard: |
- | US West (N. California) | **`us-west-1`** | **arn:aws:lambda:us-west-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:75**{: .copyMe}:clipboard: |
- | US West (Oregon) | **`us-west-2`** | **arn:aws:lambda:us-west-2:017000801446:layer:AWSLambdaPowertoolsPythonV2:75**{: .copyMe}:clipboard: |
+ | Africa (Cape Town) | **`af-south-1`** | **arn:aws:lambda:af-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:76**{: .copyMe}:clipboard: |
+ | Asia Pacific (Hong Kong) | **`ap-east-1`** | **arn:aws:lambda:ap-east-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:76**{: .copyMe}:clipboard: |
+ | Asia Pacific (Tokyo) | **`ap-northeast-1`** | **arn:aws:lambda:ap-northeast-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:76**{: .copyMe}:clipboard: |
+ | Asia Pacific (Seoul) | **`ap-northeast-2`** | **arn:aws:lambda:ap-northeast-2:017000801446:layer:AWSLambdaPowertoolsPythonV2:76**{: .copyMe}:clipboard: |
+ | Asia Pacific (Osaka) | **`ap-northeast-3`** | **arn:aws:lambda:ap-northeast-3:017000801446:layer:AWSLambdaPowertoolsPythonV2:76**{: .copyMe}:clipboard: |
+ | Asia Pacific (Mumbai) | **`ap-south-1`** | **arn:aws:lambda:ap-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:76**{: .copyMe}:clipboard: |
+ | Asia Pacific (Hyderabad) | **`ap-south-2`** | **arn:aws:lambda:ap-south-2:017000801446:layer:AWSLambdaPowertoolsPythonV2:76**{: .copyMe}:clipboard: |
+ | Asia Pacific (Singapore) | **`ap-southeast-1`** | **arn:aws:lambda:ap-southeast-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:76**{: .copyMe}:clipboard: |
+ | Asia Pacific (Sydney) | **`ap-southeast-2`** | **arn:aws:lambda:ap-southeast-2:017000801446:layer:AWSLambdaPowertoolsPythonV2:76**{: .copyMe}:clipboard: |
+ | Asia Pacific (Jakarta) | **`ap-southeast-3`** | **arn:aws:lambda:ap-southeast-3:017000801446:layer:AWSLambdaPowertoolsPythonV2:76**{: .copyMe}:clipboard: |
+ | Asia Pacific (Melbourne) | **`ap-southeast-4`** | **arn:aws:lambda:ap-southeast-4:017000801446:layer:AWSLambdaPowertoolsPythonV2:76**{: .copyMe}:clipboard: |
+ | Canada (Central) | **`ca-central-1`** | **arn:aws:lambda:ca-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:76**{: .copyMe}:clipboard: |
+ | Canada (West) | **`ca-west-1`** | **arn:aws:lambda:ca-west-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:76**{: .copyMe}:clipboard: |
+ | Europe (Frankfurt) | **`eu-central-1`** | **arn:aws:lambda:eu-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:76**{: .copyMe}:clipboard: |
+ | Europe (Zurich) | **`eu-central-2`** | **arn:aws:lambda:eu-central-2:017000801446:layer:AWSLambdaPowertoolsPythonV2:76**{: .copyMe}:clipboard: |
+ | Europe (Stockholm) | **`eu-north-1`** | **arn:aws:lambda:eu-north-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:76**{: .copyMe}:clipboard: |
+ | Europe (Milan) | **`eu-south-1`** | **arn:aws:lambda:eu-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:76**{: .copyMe}:clipboard: |
+ | Europe (Spain) | **`eu-south-2`** | **arn:aws:lambda:eu-south-2:017000801446:layer:AWSLambdaPowertoolsPythonV2:76**{: .copyMe}:clipboard: |
+ | Europe (Ireland) | **`eu-west-1`** | **arn:aws:lambda:eu-west-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:76**{: .copyMe}:clipboard: |
+ | Europe (London) | **`eu-west-2`** | **arn:aws:lambda:eu-west-2:017000801446:layer:AWSLambdaPowertoolsPythonV2:76**{: .copyMe}:clipboard: |
+ | Europe (Paris) | **`eu-west-3`** | **arn:aws:lambda:eu-west-3:017000801446:layer:AWSLambdaPowertoolsPythonV2:76**{: .copyMe}:clipboard: |
+ | Middle East (Israel) | **`il-central-1`** | **arn:aws:lambda:il-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:76**{: .copyMe}:clipboard: |
+ | Middle East (UAE) | **`me-central-1`** | **arn:aws:lambda:me-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:76**{: .copyMe}:clipboard: |
+ | Middle East (Bahrain) | **`me-south-1`** | **arn:aws:lambda:me-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:76**{: .copyMe}:clipboard: |
+ | South America (São Paulo) | **`sa-east-1`** | **arn:aws:lambda:sa-east-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:76**{: .copyMe}:clipboard: |
+ | US East (N. Virginia) | **`us-east-1`** | **arn:aws:lambda:us-east-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:76**{: .copyMe}:clipboard: |
+ | US East (Ohio) | **`us-east-2`** | **arn:aws:lambda:us-east-2:017000801446:layer:AWSLambdaPowertoolsPythonV2:76**{: .copyMe}:clipboard: |
+ | US West (N. California) | **`us-west-1`** | **arn:aws:lambda:us-west-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:76**{: .copyMe}:clipboard: |
+ | US West (Oregon) | **`us-west-2`** | **arn:aws:lambda:us-west-2:017000801446:layer:AWSLambdaPowertoolsPythonV2:76**{: .copyMe}:clipboard: |
=== "arm64"
| Region name | Region code | Layer ARN |
| ------------------------- | -------------------- | --------------------------------------------------------------------------------------------------------------- |
- | Africa (Cape Town) | **`af-south-1`** | **arn:aws:lambda:af-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:75**{: .copyMe}:clipboard: |
- | Asia Pacific (Hong Kong) | **`ap-east-1`** | **arn:aws:lambda:ap-east-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:75**{: .copyMe}:clipboard: |
- | Asia Pacific (Tokyo) | **`ap-northeast-1`** | **arn:aws:lambda:ap-northeast-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:75**{: .copyMe}:clipboard: |
- | Asia Pacific (Seoul) | **`ap-northeast-2`** | **arn:aws:lambda:ap-northeast-2:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:75**{: .copyMe}:clipboard: |
- | Asia Pacific (Osaka) | **`ap-northeast-3`** | **arn:aws:lambda:ap-northeast-3:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:75**{: .copyMe}:clipboard: |
- | Asia Pacific (Mumbai) | **`ap-south-1`** | **arn:aws:lambda:ap-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:75**{: .copyMe}:clipboard: |
- | Asia Pacific (Hyderabad) | **`ap-south-2`** | **arn:aws:lambda:ap-south-2:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:75**{: .copyMe}:clipboard: |
- | Asia Pacific (Singapore) | **`ap-southeast-1`** | **arn:aws:lambda:ap-southeast-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:75**{: .copyMe}:clipboard: |
- | Asia Pacific (Sydney) | **`ap-southeast-2`** | **arn:aws:lambda:ap-southeast-2:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:75**{: .copyMe}:clipboard: |
- | Asia Pacific (Jakarta) | **`ap-southeast-3`** | **arn:aws:lambda:ap-southeast-3:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:75**{: .copyMe}:clipboard: |
- | Asia Pacific (Melbourne) | **`ap-southeast-4`** | **arn:aws:lambda:ap-southeast-4:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:75**{: .copyMe}:clipboard: |
- | Canada (Central) | **`ca-central-1`** | **arn:aws:lambda:ca-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:75**{: .copyMe}:clipboard: |
+ | Africa (Cape Town) | **`af-south-1`** | **arn:aws:lambda:af-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:76**{: .copyMe}:clipboard: |
+ | Asia Pacific (Hong Kong) | **`ap-east-1`** | **arn:aws:lambda:ap-east-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:76**{: .copyMe}:clipboard: |
+ | Asia Pacific (Tokyo) | **`ap-northeast-1`** | **arn:aws:lambda:ap-northeast-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:76**{: .copyMe}:clipboard: |
+ | Asia Pacific (Seoul) | **`ap-northeast-2`** | **arn:aws:lambda:ap-northeast-2:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:76**{: .copyMe}:clipboard: |
+ | Asia Pacific (Osaka) | **`ap-northeast-3`** | **arn:aws:lambda:ap-northeast-3:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:76**{: .copyMe}:clipboard: |
+ | Asia Pacific (Mumbai) | **`ap-south-1`** | **arn:aws:lambda:ap-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:76**{: .copyMe}:clipboard: |
+ | Asia Pacific (Hyderabad) | **`ap-south-2`** | **arn:aws:lambda:ap-south-2:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:76**{: .copyMe}:clipboard: |
+ | Asia Pacific (Singapore) | **`ap-southeast-1`** | **arn:aws:lambda:ap-southeast-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:76**{: .copyMe}:clipboard: |
+ | Asia Pacific (Sydney) | **`ap-southeast-2`** | **arn:aws:lambda:ap-southeast-2:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:76**{: .copyMe}:clipboard: |
+ | Asia Pacific (Jakarta) | **`ap-southeast-3`** | **arn:aws:lambda:ap-southeast-3:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:76**{: .copyMe}:clipboard: |
+ | Asia Pacific (Melbourne) | **`ap-southeast-4`** | **arn:aws:lambda:ap-southeast-4:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:76**{: .copyMe}:clipboard: |
+ | Canada (Central) | **`ca-central-1`** | **arn:aws:lambda:ca-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:76**{: .copyMe}:clipboard: |
| Canada (West) | **`ca-west-1`** | **arn:aws:lambda:ca-west-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:73**{: .copyMe}:clipboard: |
- | Europe (Frankfurt) | **`eu-central-1`** | **arn:aws:lambda:eu-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:75**{: .copyMe}:clipboard: |
- | Europe (Zurich) | **`eu-central-2`** | **arn:aws:lambda:eu-central-2:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:75**{: .copyMe}:clipboard: |
- | Europe (Stockholm) | **`eu-north-1`** | **arn:aws:lambda:eu-north-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:75**{: .copyMe}:clipboard: |
- | Europe (Milan) | **`eu-south-1`** | **arn:aws:lambda:eu-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:75**{: .copyMe}:clipboard: |
- | Europe (Spain) | **`eu-south-2`** | **arn:aws:lambda:eu-south-2:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:75**{: .copyMe}:clipboard: |
- | Europe (Ireland) | **`eu-west-1`** | **arn:aws:lambda:eu-west-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:75**{: .copyMe}:clipboard: |
- | Europe (London) | **`eu-west-2`** | **arn:aws:lambda:eu-west-2:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:75**{: .copyMe}:clipboard: |
- | Europe (Paris) | **`eu-west-3`** | **arn:aws:lambda:eu-west-3:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:75**{: .copyMe}:clipboard: |
- | Middle East (Israel) | **`il-central-1`** | **arn:aws:lambda:il-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:75**{: .copyMe}:clipboard: |
- | Middle East (UAE) | **`me-central-1`** | **arn:aws:lambda:me-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:75**{: .copyMe}:clipboard: |
- | Middle East (Bahrain) | **`me-south-1`** | **arn:aws:lambda:me-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:75**{: .copyMe}:clipboard: |
- | South America (São Paulo) | **`sa-east-1`** | **arn:aws:lambda:sa-east-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:75**{: .copyMe}:clipboard: |
- | US East (N. Virginia) | **`us-east-1`** | **arn:aws:lambda:us-east-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:75**{: .copyMe}:clipboard: |
- | US East (Ohio) | **`us-east-2`** | **arn:aws:lambda:us-east-2:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:75**{: .copyMe}:clipboard: |
- | US West (N. California) | **`us-west-1`** | **arn:aws:lambda:us-west-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:75**{: .copyMe}:clipboard: |
- | US West (Oregon) | **`us-west-2`** | **arn:aws:lambda:us-west-2:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:75**{: .copyMe}:clipboard: |
+ | Europe (Frankfurt) | **`eu-central-1`** | **arn:aws:lambda:eu-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:76**{: .copyMe}:clipboard: |
+ | Europe (Zurich) | **`eu-central-2`** | **arn:aws:lambda:eu-central-2:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:76**{: .copyMe}:clipboard: |
+ | Europe (Stockholm) | **`eu-north-1`** | **arn:aws:lambda:eu-north-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:76**{: .copyMe}:clipboard: |
+ | Europe (Milan) | **`eu-south-1`** | **arn:aws:lambda:eu-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:76**{: .copyMe}:clipboard: |
+ | Europe (Spain) | **`eu-south-2`** | **arn:aws:lambda:eu-south-2:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:76**{: .copyMe}:clipboard: |
+ | Europe (Ireland) | **`eu-west-1`** | **arn:aws:lambda:eu-west-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:76**{: .copyMe}:clipboard: |
+ | Europe (London) | **`eu-west-2`** | **arn:aws:lambda:eu-west-2:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:76**{: .copyMe}:clipboard: |
+ | Europe (Paris) | **`eu-west-3`** | **arn:aws:lambda:eu-west-3:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:76**{: .copyMe}:clipboard: |
+ | Middle East (Israel) | **`il-central-1`** | **arn:aws:lambda:il-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:76**{: .copyMe}:clipboard: |
+ | Middle East (UAE) | **`me-central-1`** | **arn:aws:lambda:me-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:76**{: .copyMe}:clipboard: |
+ | Middle East (Bahrain) | **`me-south-1`** | **arn:aws:lambda:me-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:76**{: .copyMe}:clipboard: |
+ | South America (São Paulo) | **`sa-east-1`** | **arn:aws:lambda:sa-east-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:76**{: .copyMe}:clipboard: |
+ | US East (N. Virginia) | **`us-east-1`** | **arn:aws:lambda:us-east-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:76**{: .copyMe}:clipboard: |
+ | US East (Ohio) | **`us-east-2`** | **arn:aws:lambda:us-east-2:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:76**{: .copyMe}:clipboard: |
+ | US West (N. California) | **`us-west-1`** | **arn:aws:lambda:us-west-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:76**{: .copyMe}:clipboard: |
+ | US West (Oregon) | **`us-west-2`** | **arn:aws:lambda:us-west-2:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:76**{: .copyMe}:clipboard: |
=== "Infrastructure as Code (IaC)"
@@ -237,7 +237,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:AWSLambdaPowertoolsPythonV2:75 --region eu-west-1
+ aws lambda get-layer-version-by-arn --arn arn:aws:lambda:eu-west-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:76 --region eu-west-1
```
You'll find the pre-signed URL under `Location` key as part of the CLI command output.
diff --git a/docs/utilities/idempotency.md b/docs/utilities/idempotency.md
index 12baabd047e..06bf15748cb 100644
--- a/docs/utilities/idempotency.md
+++ b/docs/utilities/idempotency.md
@@ -5,26 +5,26 @@ description: Utility
-The idempotency utility provides a simple solution to convert your Lambda functions into idempotent operations which are safe to retry.
+The idempotency utility allows you to retry operations within a time window with the same input, producing the same output.
## Key features
-* Prevent Lambda handler from executing more than once on the same event payload during a time window
-* Ensure Lambda handler returns the same result when called with the same payload
-* Select a subset of the event as the idempotency key using JMESPath expressions
-* Set a time window in which records with the same payload should be considered duplicates
-* Expires in-progress executions if the Lambda function times out halfway through
-* Support Amazon DynamoDB and Redis as persistence layers
+* Produces the previous successful result when a function is called repeatedly with the same idempotency key
+* Choose your idempotency key from one or more fields, or entire payload
+* Safeguard concurrent requests, timeouts, missing idempotency keys, and payload tampering
+* Support for Amazon DynamoDB, Redis, bring your own persistence layer, and in-memory caching
## Terminology
The property of idempotency means that an operation does not cause additional side effects if it is called more than once with the same input parameters.
-**Idempotent operations will return the same result when they are called multiple times with the same parameters**. This makes idempotent operations safe to retry.
+**Idempotency key** is a combination of **(a)** Lambda function name, **(b)** fully qualified name of your function, and **(c)** a hash of the entire payload or part(s) of the payload you specify.
-**Idempotency key** is a hash representation of either the entire event or a specific configured subset of the event, and invocation results are **JSON serialized** and stored in your persistence storage layer.
+**Idempotent request** is an operation with the same input previously processed that is not expired in your persistent storage or in-memory cache.
-**Idempotency record** is the data representation of an idempotent request saved in your preferred storage layer. We use it to coordinate whether a request is idempotent, whether it's still valid or expired based on timestamps, etc.
+**Persistence layer** is a storage we use to create, read, expire, and delete idempotency records.
+
+**Idempotency record** is the data representation of an idempotent request saved in the persistent layer and in its various status. We use it to coordinate whether **(a)** a request is idempotent, **(b)** it's not expired, **(c)** JSON response to return, and more.
```mermaid
@@ -35,7 +35,7 @@ classDiagram
status Status
expiry_timestamp int
in_progress_expiry_timestamp int
- response_data Json~str~
+ response_data str~JSON~
payload_hash str
}
class Status {
@@ -52,33 +52,64 @@ classDiagram
## Getting started
-???+ note
- This section uses DynamoDB as the default idempotent persistence storage layer. If you are interested in using Redis as the persistence storage layer, check out the [Redis as persistence storage layer](#redis-as-persistent-storage-layer-provider) Section.
+We use Amazon DynamoDB as the default persistence layer in the documentation. If you prefer Redis, you can learn more from [this section](#redis-database).
### IAM Permissions
-Your Lambda function IAM Role must have `dynamodb:GetItem`, `dynamodb:PutItem`, `dynamodb:UpdateItem` and `dynamodb:DeleteItem` IAM permissions before using this feature.
+When using Amazon DynamoDB as the persistence layer, you will need the following IAM permissions:
-???+ note
- If you're using our example [AWS Serverless Application Model (SAM)](#required-resources), [AWS Cloud Development Kit (CDK)](#required-resources), or [Terraform](#required-resources) it already adds the required permissions.
+| IAM Permission | Operation |
+| ------------------------------------ | ------------------------------------------------------------------------ |
+| **`dynamodb:GetItem`**{: .copyMe} | Retrieve idempotent record _(strong consistency)_ |
+| **`dynamodb:PutItem`**{: .copyMe} | New idempotent records, replace expired idempotent records |
+| **`dynamodb:UpdateItem`**{: .copyMe} | Complete idempotency transaction, and/or update idempotent records state |
+| **`dynamodb:DeleteItem`**{: .copyMe} | Delete idempotent records for unsuccessful idempotency transactions |
+
+**First time setting it up?**
+
+We provide Infrastrucure as Code examples with [AWS Serverless Application Model (SAM)](#aws-serverless-application-model-sam-example), [AWS Cloud Development Kit (CDK)](#aws-cloud-development-kit-cdk), and [Terraform](#terraform) with the required permissions.
### Required resources
-Before getting started, you need to create a persistent storage layer where the idempotency utility can store its state - your lambda functions will need read and write access to it.
+To start, you'll need:
+
+
+
+
+* :octicons-database-16:{ .lg .middle } __Persistent storage__
+
+ ---
+
+ [Amazon DynamoDB](#dynamodb-table) or [Redis](#redis-database)
+
+* :simple-awslambda:{ .lg .middle } **AWS Lambda function**
+
+ ---
-We currently support Amazon DynamoDB and Redis as a storage layer. The following example demonstrates how to create a table in DynamoDB. If you prefer to use Redis, refer go to the section [RedisPersistenceLayer](#redispersistencelayer) section.
+ With permissions to use your persistent storage
-**Default table configuration**
+
-If you're not [changing the default configuration for the DynamoDB persistence layer](#dynamodbpersistencelayer), this is the expected default configuration:
+
-| Configuration | Value | Notes |
-| ------------------ | ------------ |-------------------------------------------------------------------------------------|
-| Partition key | `id` | |
-| TTL attribute name | `expiration` | This can only be configured after your table is created if you're using AWS Console |
+!!! note "Primary key for any persistence storage"
+ We combine the Lambda function name and the [fully qualified name](https://peps.python.org/pep-3155/){target="_blank" rel="nofollow"} for classes/functions to
+ prevent accidental reuse for similar code sharing input/output.
-???+ tip "Tip: You can share a single state table for all functions"
- You can reuse the same DynamoDB table to store idempotency state. We add `module_name` and [qualified name for classes and functions](https://peps.python.org/pep-3155/){target="_blank" rel="nofollow"} in addition to the idempotency key as a hash key.
+ Primary key sample: `{lambda_fn_name}.{module_name}.{fn_qualified_name}#{idempotency_key_hash}`
+
+#### DynamoDB table
+
+Unless you're looking to use an [existing table or customize each attribute](#dynamodbpersistencelayer), you only need the following:
+
+| Configuration | Value | Notes |
+| ------------------ | ------------ | ------------------------------------------------------------ |
+| Partition key | `id` | |
+| TTL attribute name | `expiration` | Using AWS Console? This is configurable after table creation |
+
+You **can** use a single DynamoDB table for all functions annotated with Idempotency.
+
+##### DynamoDB IaC examples
=== "AWS Serverless Application Model (SAM) example"
@@ -96,67 +127,82 @@ If you're not [changing the default configuration for the DynamoDB persistence l
```terraform hl_lines="14-26 64-70"
--8<-- "examples/idempotency/templates/terraform.tf"
```
+`
-???+ warning "Warning: Large responses with DynamoDB persistence layer"
- When using this utility with DynamoDB, your function's responses must be [smaller than 400KB](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Limits.html#limits-items){target="_blank"}.
+##### Limitations
- Larger items cannot be written to DynamoDB and will cause exceptions. If your response exceeds 400kb, consider using Redis as your persistence layer.
+* **DynamoDB restricts [item sizes to 400KB](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Limits.html#limits-items){target="_blank"}**. This means that if your annotated function's response must be smaller than 400KB, otherwise your function will fail. Consider [Redis](#redis-database) as an alternative.
-
-???+ info "Info: DynamoDB"
+* **Expect 2 WCU per non-idempotent call**. During the first invocation, we use `PutItem` for locking and `UpdateItem` for completion. Consider reviewing [DynamoDB pricing documentation](https://aws.amazon.com/dynamodb/pricing/){target="_blank"} to estimate cost.
- During the first invocation with a payload, the Lambda function executes both a `PutItem` and an `UpdateItem` operations to store the data in DynamoDB. If the result returned by your Lambda is less than 1kb, you can expect 2 WCUs per Lambda invocation.
+* **Old boto3 versions can increase costs**. For cost optimization, we use a conditional `PutItem` to always lock a new idempotency record. If locking fails, it means we already have an idempotency record saving us an additional `GetItem` call. However, this is only supported in boto3 `1.26.194` and higher _([June 30th 2023](https://aws.amazon.com/about-aws/whats-new/2023/06/amazon-dynamodb-cost-failed-conditional-writes/){target="_blank"})_.
- On subsequent invocations with the same payload, you can expect just 1 `PutItem` request to DynamoDB.
+#### Redis database
+
+We recommend you start with a Redis compatible management services such as [Amazon ElastiCache for Redis](https://aws.amazon.com/elasticache/redis/){target="_blank"} or [Amazon MemoryDB for Redis](https://aws.amazon.com/memorydb/){target="_blank"}.
+
+In both services, you'll need to configure [VPC access](https://docs.aws.amazon.com/lambda/latest/dg/configuration-vpc.html){target="_blank"} to your AWS Lambda.
+
+##### Redis IaC examples
+
+=== "AWS CloudFormation example"
- **Note:** While we try to minimize requests to DynamoDB to 1 per invocation, if your boto3 version is lower than `1.26.194`, you may experience 2 requests in every invocation. Ensure to check your boto3 version and review the [DynamoDB pricing documentation](https://aws.amazon.com/dynamodb/pricing/){target="_blank"} to estimate the cost.
+ !!! tip inline end "Prefer AWS Console/CLI?"
+
+ Follow the official tutorials for [Amazon ElastiCache for Redis](https://docs.aws.amazon.com/AmazonElastiCache/latest/red-ug/LambdaRedis.html) or [Amazon MemoryDB for Redis](https://aws.amazon.com/blogs/database/access-amazon-memorydb-for-redis-from-aws-lambda/)
+
+ ```yaml hl_lines="5 21"
+ --8<-- "examples/idempotency/templates/cfn_redis_serverless.yaml"
+ ```
+
+ 1. Replace the Security Group ID and Subnet ID to match your VPC settings.
+ 2. Replace the Security Group ID and Subnet ID to match your VPC settings.
+
+Once setup, you can find a quick start and advanced examples for Redis in [the persistent layers section](#redispersistencelayer).
-### Idempotent decorator
-You can quickly start by initializing the `DynamoDBPersistenceLayer` class and using it with the `idempotent` decorator on your lambda handler.
+### Idempotent decorator
-???+ note
- In this example, the entire Lambda handler is treated as a single idempotent operation. If your Lambda handler can cause multiple side effects, or you're only interested in making a specific logic idempotent, use [`idempotent_function`](#idempotent_function-decorator) instead.
+For simple use cases, you can use the `idempotent` decorator on your Lambda handler function.
-!!! tip "See [Choosing a payload subset for idempotency](#choosing-a-payload-subset-for-idempotency) for more elaborate use cases."
+It will treat the entire event as an idempotency key. That is, the same event will return the previously stored result within a [configurable time window](#adjusting-expiration-window) _(1 hour, by default)_.
=== "Idempotent decorator"
- ```python hl_lines="4-7 10 24"
+ !!! tip "You can also choose [one or more fields](#choosing-a-payload-subset) as an idempotency key."
+
+ ```python title="getting_started_with_idempotency.py" hl_lines="5-8 12 25"
--8<-- "examples/idempotency/src/getting_started_with_idempotency.py"
```
=== "Sample event"
- ```json
+ ```json title="getting_started_with_idempotency_payload.json"
--8<-- "examples/idempotency/src/getting_started_with_idempotency_payload.json"
```
-After processing this request successfully, a second request containing the exact same payload above will now return the same response, ensuring our customer isn't charged twice.
-
-!!! question "New to idempotency concept? Please review our [Terminology](#terminology) section if you haven't yet."
-
### Idempotent_function decorator
-Similar to [idempotent decorator](#idempotent-decorator), you can use `idempotent_function` decorator for any synchronous Python function.
-
-When using `idempotent_function`, you must tell us which keyword parameter in your function signature has the data we should use via **`data_keyword_argument`**.
+For full flexibility, you can use the `idempotent_function` decorator for any synchronous Python function.
-!!! tip "We support JSON serializable data, [Python Dataclasses](https://docs.python.org/3.12/library/dataclasses.html){target="_blank" rel="nofollow"}, [Parser/Pydantic Models](parser.md){target="_blank"}, and our [Event Source Data Classes](./data_classes.md){target="_blank"}."
+When using this decorator, you **must** call your decorated function using keyword arguments.
-???+ warning "Limitation"
- Make sure to call your decorated function using keyword arguments.
+You can use `data_keyword_argument` to tell us the argument to extract an idempotency key. We support JSON serializable data, [Dataclasses](https://docs.python.org/3.12/library/dataclasses.html){target="_blank" rel="nofollow"}, Pydantic Models, and [Event Source Data Classes](./data_classes.md){target="_blank"}
=== "Using Dataclasses"
- ```python hl_lines="3-7 11 26 37"
+ ```python title="working_with_idempotent_function_dataclass.py" hl_lines="4-8 12 28 41"
--8<-- "examples/idempotency/src/working_with_idempotent_function_dataclass.py"
```
+ 1. Notice how **`data_keyword_argument`** matches the name of the parameter.
+
This allows us to extract one or all fields as idempotency key.
+ 2. Different from `idempotent` decorator, we must explicitly register the Lambda context to [protect against timeouts](#lambda-timeouts).
+
=== "Using Pydantic"
- ```python hl_lines="1-5 10 23 34"
+ ```python title="working_with_idempotent_function_pydantic.py" hl_lines="3-7 12 26 37"
--8<-- "examples/idempotency/src/working_with_idempotent_function_pydantic.py"
```
@@ -166,15 +212,15 @@ By default, `idempotent_function` serializes, stores, and returns your annotated
The output serializer supports any JSON serializable data, **Python Dataclasses** and **Pydantic Models**.
-!!! info "When using the `output_serializer` parameter, the data will continue to be stored in DynamoDB as a JSON object."
+!!! info "When using the `output_serializer` parameter, the data will continue to be stored in your persistent storage as a JSON string."
=== "Pydantic"
- You can use `PydanticSerializer` to automatically serialize what's retrieved from the persistent storage based on the return type annotated.
+ Use `PydanticSerializer` to automatically serialize what's retrieved from the persistent storage based on the return type annotated.
=== "Inferring via the return type"
- ```python hl_lines="6 24 25 32 36 45"
+ ```python hl_lines="8 27 35 38 48"
--8<-- "examples/idempotency/src/working_with_pydantic_deduced_output_serializer.py"
```
@@ -184,17 +230,17 @@ The output serializer supports any JSON serializable data, **Python Dataclasses*
Alternatively, you can provide an explicit model as an input to `PydanticSerializer`.
- ```python hl_lines="6 24 25 32 35 44"
+ ```python hl_lines="8 27 35 35 47"
--8<-- "examples/idempotency/src/working_with_pydantic_explicitly_output_serializer.py"
```
=== "Dataclasses"
- You can use `DataclassSerializer` to automatically serialize what's retrieved from the persistent storage based on the return type annotated.
+ Use `DataclassSerializer` to automatically serialize what's retrieved from the persistent storage based on the return type annotated.
=== "Inferring via the return type"
- ```python hl_lines="8 27-29 36 40 49"
+ ```python hl_lines="9 30 38 41 51"
--8<-- "examples/idempotency/src/working_with_dataclass_deduced_output_serializer.py"
```
@@ -204,18 +250,18 @@ The output serializer supports any JSON serializable data, **Python Dataclasses*
Alternatively, you can provide an explicit model as an input to `DataclassSerializer`.
- ```python hl_lines="8 27-29 36 39 48"
+ ```python hl_lines="8 30 38 40 50"
--8<-- "examples/idempotency/src/working_with_dataclass_explicitly_output_serializer.py"
```
=== "Any type"
- You can use `CustomDictSerializer` to have full control over the serialization process for any type. It expects two functions:
+ Use `CustomDictSerializer` to have full control over the serialization process for any type. It expects two functions:
* **to_dict**. Function to convert any type to a JSON serializable dictionary before it saves into the persistent storage.
* **from_dict**. Function to convert from a dictionary retrieved from persistent storage and serialize in its original form.
- ```python hl_lines="8 32 36 40 50 53"
+ ```python hl_lines="9 34 38 42 52 54 64"
--8<-- "examples/idempotency/src/working_with_idempotent_function_custom_output_serializer.py"
```
@@ -223,42 +269,42 @@ The output serializer supports any JSON serializable data, **Python Dataclasses*
2. This function does the following
**1**. Receives the dictionary saved into the persistent storage
**1** Serializes to `OrderOutput` before `@idempotent` returns back to the caller.
3. This serializer receives both functions so it knows who to call when to serialize to and from dictionary.
-#### Batch integration
+### Using in-memory cache
-You can can easily integrate with [Batch utility](batch.md){target="_blank"} via context manager. This ensures that you process each record in an idempotent manner, and guard against a [Lambda timeout](#lambda-timeouts) idempotent situation.
+!!! note "In-memory cache is local to each Lambda execution environment."
-???+ "Choosing an unique batch record attribute"
- In this example, we choose `messageId` as our idempotency key since we know it'll be unique.
+You can enable caching with the `use_local_cache` parameter in `IdempotencyConfig`. When enabled, you can adjust cache capacity _(256)_ with `local_cache_max_items`.
- Depending on your use case, it might be more accurate [to choose another field](#choosing-a-payload-subset-for-idempotency) your producer intentionally set to define uniqueness.
+By default, caching is disabled since we don't know how big your response could be in relation to your configured memory size.
-=== "Integration with Batch Processor"
+=== "Enabling cache"
- ```python hl_lines="2 12 16 20 31 35 37"
- --8<-- "examples/idempotency/src/integrate_idempotency_with_batch_processor.py"
+ ```python hl_lines="15"
+ --8<-- "examples/idempotency/src/working_with_local_cache.py"
```
+ 1. You can adjust cache capacity with [`local_cache_max_items`](#customizing-the-default-behavior) parameter.
+
=== "Sample event"
- ```json hl_lines="4"
- --8<-- "examples/idempotency/src/integrate_idempotency_with_batch_processor_payload.json"
+ ```json
+ --8<-- "examples/idempotency/src/working_with_local_cache_payload.json"
```
-### Choosing a payload subset for idempotency
+### Choosing a payload subset
???+ tip "Tip: Dealing with always changing payloads"
When dealing with a more elaborate payload, where parts of the payload always change, you should use **`event_key_jmespath`** parameter.
-Use [`IdempotencyConfig`](#customizing-the-default-behavior) to instruct the idempotent decorator to only use a portion of your payload to verify whether a request is idempotent, and therefore it should not be retried.
+Use **`event_key_jmespath`** parameter in [`IdempotencyConfig`](#customizing-the-default-behavior) to select one or more payload parts as your idempotency key.
-> **Payment scenario**
+> **Example scenario**
In this example, we have a Lambda handler that creates a payment for a user subscribing to a product. We want to ensure that we don't accidentally charge our customer by subscribing them more than once.
-Imagine the function executes successfully, but the client never receives the response due to a connection issue. It is safe to retry in this instance, as the idempotent decorator will return a previously saved response.
+Imagine the function runs successfully, but the client never receives the response due to a connection issue. It is safe to immediately retry in this instance, as the idempotent decorator will return a previously saved response.
-**What we want here** is to instruct Idempotency to use `user_id` and `product_id` fields from our incoming payload as our idempotency key.
-If we were to treat the entire request as our idempotency key, a simple HTTP header change would cause our customer to be charged twice.
+We want to use `user_id` and `product_id` fields as our idempotency key. **If we were** to treat the entire request as our idempotency key, a simple HTTP header change would cause our function to run again.
???+ tip "Deserializing JSON strings in payloads for increased accuracy."
The payload extracted by the `event_key_jmespath` is treated as a string by default.
@@ -268,7 +314,7 @@ If we were to treat the entire request as our idempotency key, a simple HTTP hea
=== "Payment function"
- ```python hl_lines="5-9 16 30"
+ ```python hl_lines="6-10 18 31"
--8<-- "examples/idempotency/src/working_with_payload_subset.py"
```
@@ -278,68 +324,64 @@ If we were to treat the entire request as our idempotency key, a simple HTTP hea
--8<-- "examples/idempotency/src/working_with_payload_subset_payload.json"
```
-### Lambda timeouts
+### Adjusting expiration window
-???+ note
- This is automatically done when you decorate your Lambda handler with [@idempotent decorator](#idempotent-decorator).
+!!! note "By default, we expire idempotency records after **an hour** (3600 seconds). After that, a transaction with the same payload [will not be considered idempotent](#expired-idempotency-records)."
-To prevent against extended failed retries when a [Lambda function times out](https://aws.amazon.com/premiumsupport/knowledge-center/lambda-verify-invocation-timeouts/){target="_blank"},
-Powertools for AWS Lambda (Python) calculates and includes the remaining invocation available time as part of the idempotency record.
+You can change this expiration window with the **`expires_after_seconds`** parameter. There is no limit on how long this expiration window can be set to.
-???+ example
- If a second invocation happens **after** this timestamp, and the record is marked as `INPROGRESS`, we will execute the invocation again as if it was in the `EXPIRED` state (e.g, `expire_seconds` field elapsed).
+=== "Adjusting expiration window"
- This means that if an invocation expired during execution, it will be quickly executed again on the next retry.
+ ```python hl_lines="14"
+ --8<-- "examples/idempotency/src/working_with_record_expiration.py"
+ ```
-???+ important
- If you are only using the [@idempotent_function decorator](#idempotent_function-decorator) to guard isolated parts of your code,
- you must use `register_lambda_context` available in the [idempotency config object](#customizing-the-default-behavior) to benefit from this protection.
+=== "Sample event"
-Here is an example on how you register the Lambda context in your handler:
+ ```json
+ --8<-- "examples/idempotency/src/working_with_record_expiration_payload.json"
+ ```
-=== "Registering the Lambda context"
+???+ important "Idempotency record expiration vs DynamoDB time-to-live (TTL)"
+ [DynamoDB TTL is a feature](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/howitworks-ttl.html){target="_blank"} to remove items after a certain period of time, it may occur within 48 hours of expiration.
- ```python hl_lines="11 20"
- --8<-- "examples/idempotency/src/working_with_lambda_timeout.py"
- ```
+ We don't rely on DynamoDB or any persistence storage layer to determine whether a record is expired to avoid eventual inconsistency states.
-### Handling exceptions
+ Instead, Idempotency records saved in the storage layer contain timestamps that can be verified upon retrieval and double checked within Idempotency feature.
-If you are using the `idempotent` decorator on your Lambda handler, any unhandled exceptions that are raised during the code execution will cause **the record in the persistence layer to be deleted**.
-This means that new invocations will execute your code again despite having the same payload. If you don't want the record to be deleted, you need to catch exceptions within the idempotent function and return a successful response.
+ **Why?**
-
-```mermaid
-sequenceDiagram
- participant Client
- participant Lambda
- participant Persistence Layer
- Client->>Lambda: Invoke (event)
- Lambda->>Persistence Layer: Get or set (id=event.search(payload))
- activate Persistence Layer
- Note right of Persistence Layer: Locked during this time. Prevents multiple
Lambda invocations with the same
payload running concurrently.
- Lambda--xLambda: Call handler (event).
Raises exception
- Lambda->>Persistence Layer: Delete record (id=event.search(payload))
- deactivate Persistence Layer
- Lambda-->>Client: Return error response
+ A record might still be valid (`COMPLETE`) when we retrieved, but in some rare cases it might expire a second later. A record could also be [cached in memory](#using-in-memory-cache). You might also want to have idempotent transactions that should expire in seconds.
+
+### Lambda timeouts
+
+!!! note "You can skip this section if you are using the [`@idempotent` decorator](#idempotent-decorator)"
+
+By default, we protect against [concurrent executions](#handling-concurrent-executions-with-the-same-payload) with the same payload using a locking mechanism. However, if your Lambda function times out before completing the first invocation it will only accept the same request when the [idempotency record expire](#adjusting-expiration-window).
+
+To prevent extended failures, use **`register_lambda_context`** function from your idempotency config to calculate and include the remaining invocation time in your idempotency record.
+
+```python title="working_with_lambda_timeout.py" hl_lines="14 23"
+--8<-- "examples/idempotency/src/working_with_lambda_timeout.py"
```
-Idempotent sequence exception
-
-If you are using `idempotent_function`, any unhandled exceptions that are raised _inside_ the decorated function will cause the record in the persistence layer to be deleted, and allow the function to be executed again if retried.
+???+ example "Mechanics"
+ If a second invocation happens **after** this timestamp, and the record is marked as `INPROGRESS`, we will run the invocation again as if it was in the `EXPIRED` state.
-If an Exception is raised _outside_ the scope of the decorated function and after your function has been called, the persistent record will not be affected. In this case, idempotency will be maintained for your decorated function. Example:
+ This means that if an invocation expired during execution, it will be quickly executed again on the next retry.
-=== "Handling exceptions"
+### Handling exceptions
- ```python hl_lines="18-22 28 31"
- --8<-- "examples/idempotency/src/working_with_exceptions.py"
- ```
+There are two failure modes that can cause new invocations to execute your code again despite having the same payload:
-???+ warning
- **We will raise `IdempotencyPersistenceLayerError`** if any of the calls to the persistence layer fail unexpectedly.
+* **Unhandled exception**. We catch them to delete the idempotency record to prevent inconsistencies, then propagate them.
+* **Persistent layer errors**. We raise **`IdempotencyPersistenceLayerError`** for any persistence layer errors _e.g., remove idempotency record_.
- As this happens outside the scope of your decorated function, you are not able to catch it if you're using the `idempotent` decorator on your Lambda handler.
+If an exception is handled or raised **outside** your decorated function, then idempotency will be maintained.
+
+```python title="working_with_exceptions.py" hl_lines="21 32 38"
+--8<-- "examples/idempotency/src/working_with_exceptions.py"
+```
### Persistence layers
@@ -347,13 +389,41 @@ If an Exception is raised _outside_ the scope of the decorated function and afte
This persistence layer is built-in, allowing you to use an existing DynamoDB table or create a new one dedicated to idempotency state (recommended).
-=== "Customizing DynamoDBPersistenceLayer to suit your table structure"
+```python title="customize_persistence_layer.py" hl_lines="10-18"
+--8<-- "examples/idempotency/src/customize_persistence_layer.py"
+```
- ```python hl_lines="7-15"
- --8<-- "examples/idempotency/src/customize_persistence_layer.py"
+##### Using a composite primary key
+
+Use `sort_key_attr` parameter when your table is configured with a composite primary key _(hash+range key)_.
+
+When enabled, we will save the idempotency key in the sort key instead. By default, the primary key will now be set to `idempotency#{LAMBDA_FUNCTION_NAME}`.
+
+You can optionally set a static value for the partition key using the `static_pk_value` parameter.
+
+=== "Reusing a DynamoDB table that uses a composite primary key"
+
+ ```python hl_lines="10"
+ --8<-- "examples/idempotency/src/working_with_composite_key.py"
```
-When using DynamoDB as the persistence layer, you can customize the attribute names by passing the following parameters during the initialization of the persistence layer:
+=== "Sample Event"
+
+ ```json
+ --8<-- "examples/idempotency/src/working_with_composite_key_payload.json"
+ ```
+
+??? note "Click to expand and learn how table items would look like"
+
+ | id | sort_key | expiration | status | data |
+ | ---------------------------- | -------------------------------- | ---------- | ----------- | ----------------------------------------- |
+ | idempotency#MyLambdaFunction | 1e956ef7da78d0cb890be999aecc0c9e | 1636549553 | COMPLETED | {"user_id": 12391, "message": "success"} |
+ | idempotency#MyLambdaFunction | 2b2cdb5f86361e97b4383087c1ffdf27 | 1636549571 | COMPLETED | {"user_id": 527212, "message": "success"} |
+ | idempotency#MyLambdaFunction | f091d2527ad1c78f05d54cc3f363be80 | 1636549585 | IN_PROGRESS | |
+
+##### DynamoDB attributes
+
+You can customize the attribute names during initialization:
| Parameter | Required | Default | Description |
| --------------------------- | ------------------ | ------------------------------------ | -------------------------------------------------------------------------------------------------------- |
@@ -369,22 +439,102 @@ When using DynamoDB as the persistence layer, you can customize the attribute na
#### RedisPersistenceLayer
-This persistence layer is built-in, allowing you to use an existing Redis service. For optimal performance and compatibility, it is strongly recommended to use a Redis service version 7 or higher.
+!!! info "We recommend Redis version 7 or higher for optimal performance."
+
+For simple setups, initialize `RedisCachePersistenceLayer` with your Redis endpoint and port to connect.
+
+For security, we enforce SSL connections by default; to disable it, set `ssl=False`.
-=== "Customizing RedisPersistenceLayer to suit your data structure"
+=== "Redis quick start"
+ ```python title="getting_started_with_idempotency_redis_config.py" hl_lines="8-10 14 27"
+ --8<-- "examples/idempotency/src/getting_started_with_idempotency_redis_config.py"
+ ```
- ```python hl_lines="9-16"
- --8<-- "examples/idempotency/src/customize_persistence_layer_redis.py"
+=== "Using an existing Redis client"
+ ```python title="getting_started_with_idempotency_redis_client.py" hl_lines="5 10-11 16 24 38"
+ --8<-- "examples/idempotency/src/getting_started_with_idempotency_redis_client.py"
```
-When using Redis as the persistence layer, you can customize the attribute names by providing the following parameters upon initialization of the persistence layer:
+=== "Sample event"
-| Parameter | Required | Default | Description |
-| --------------------------- | ------------------ | ------------------------------------ | -------------------------------------------------------------------------------------------------------- |
-| **in_progress_expiry_attr** | | `in_progress_expiration` | Unix timestamp of when record expires while in progress (in case of the invocation times out) |
-| **status_attr** | | `status` | Stores status of the Lambda execution during and after invocation |
-| **data_attr** | | `data` | Stores results of successfully executed Lambda handlers |
-| **validation_key_attr** | | `validation` | Hashed representation of the parts of the event used for validation |
+ ```json title="getting_started_with_idempotency_payload.json"
+ --8<-- "examples/idempotency/src/getting_started_with_idempotency_payload.json"
+ ```
+
+##### Redis SSL connections
+
+We recommend using AWS Secrets Manager to store and rotate certificates safely, and the [Parameters feature](./parameters.md){target="_blank"} to fetch and cache optimally.
+
+For advanced configurations, we recommend using an existing Redis client for optimal compatibility like SSL certificates and timeout.
+
+=== "Advanced configuration using AWS Secrets"
+ ```python title="using_redis_client_with_aws_secrets.py" hl_lines="9-11 13 15 25"
+ --8<-- "examples/idempotency/src/using_redis_client_with_aws_secrets.py"
+ ```
+
+ 1. JSON stored:
+ ```json
+ {
+ "REDIS_ENDPOINT": "127.0.0.1",
+ "REDIS_PORT": "6379",
+ "REDIS_PASSWORD": "redis-secret"
+ }
+ ```
+
+=== "Advanced configuration with local certificates"
+ ```python title="using_redis_client_with_local_certs.py" hl_lines="14 25-27"
+ --8<-- "examples/idempotency/src/using_redis_client_with_local_certs.py"
+ ```
+
+ 1. JSON stored:
+ ```json
+ {
+ "REDIS_ENDPOINT": "127.0.0.1",
+ "REDIS_PORT": "6379",
+ "REDIS_PASSWORD": "redis-secret"
+ }
+ ```
+ 2. redis_user.crt file stored in the "certs" directory of your Lambda function
+ 3. redis_user_private.key file stored in the "certs" directory of your Lambda function
+ 4. redis_ca.pem file stored in the "certs" directory of your Lambda function
+
+##### Redis attributes
+
+You can customize the attribute names during initialization:
+
+| Parameter | Required | Default | Description |
+| --------------------------- | -------- | ------------------------ | --------------------------------------------------------------------------------------------- |
+| **in_progress_expiry_attr** | | `in_progress_expiration` | Unix timestamp of when record expires while in progress (in case of the invocation times out) |
+| **status_attr** | | `status` | Stores status of the Lambda execution during and after invocation |
+| **data_attr** | | `data` | Stores results of successfully executed Lambda handlers |
+| **validation_key_attr** | | `validation` | Hashed representation of the parts of the event used for validation |
+
+```python title="customize_persistence_layer_redis.py" hl_lines="15-18"
+--8<-- "examples/idempotency/src/customize_persistence_layer_redis.py"
+```
+
+### Common use cases
+
+#### Batch processing
+
+You can can easily integrate with [Batch](batch.md){target="_blank"} using the [idempotent_function decorator](#idempotent_function-decorator) to handle idempotency per message/record in a given batch.
+
+???+ "Choosing an unique batch record attribute"
+ In this example, we choose `messageId` as our idempotency key since we know it'll be unique.
+
+ Depending on your use case, it might be more accurate [to choose another field](#choosing-a-payload-subset) your producer intentionally set to define uniqueness.
+
+=== "Integration with Batch Processor"
+
+ ```python title="integrate_idempotency_with_batch_processor.py" hl_lines="3 16 19 25 27"
+ --8<-- "examples/idempotency/src/integrate_idempotency_with_batch_processor.py"
+ ```
+
+=== "Sample event"
+
+ ```json title="integrate_idempotency_with_batch_processor_payload.json" hl_lines="4"
+ --8<-- "examples/idempotency/src/integrate_idempotency_with_batch_processor_payload.json"
+ ```
### Idempotency request flow
@@ -551,6 +701,26 @@ sequenceDiagram
Concurrent identical in-flight requests
+#### Unhandled exception
+
+
+```mermaid
+sequenceDiagram
+ participant Client
+ participant Lambda
+ participant Persistence Layer
+ Client->>Lambda: Invoke (event)
+ Lambda->>Persistence Layer: Get or set (id=event.search(payload))
+ activate Persistence Layer
+ Note right of Persistence Layer: Locked during this time. Prevents multiple
Lambda invocations with the same
payload running concurrently.
+ Lambda--xLambda: Call handler (event).
Raises exception
+ Lambda->>Persistence Layer: Delete record (id=event.search(payload))
+ deactivate Persistence Layer
+ Lambda-->>Client: Return error response
+```
+Idempotent sequence exception
+
+
#### Lambda request timeout
@@ -638,110 +808,21 @@ graph TD;
Race condition with Redis
-## Redis as persistent storage layer provider
-
-### Redis resources
-
-Before setting up Redis as the persistent storage layer provider, you must have an existing Redis service. We recommend you to use Redis compatible services such as [Amazon ElastiCache for Redis](https://aws.amazon.com/elasticache/redis/){target="_blank"} or [Amazon MemoryDB for Redis](https://aws.amazon.com/memorydb/){target="_blank"} as your persistent storage layer provider.
-
-???+ tip "No existing Redis service?"
- If you don't have an existing Redis service, we recommend using [DynamoDB](#dynamodbpersistencelayer) as the persistent storage layer provider.
-
-=== "AWS CloudFormation example"
-
- ```yaml hl_lines="5"
- --8<-- "examples/idempotency/templates/cfn_redis_serverless.yaml"
- ```
-
- 1. Replace the Security Group ID and Subnet ID to match your VPC settings.
-
-### VPC Access
-
-Your Lambda Function must have network access to the Redis endpoint before using it as the idempotency persistent storage layer. In most cases, you will need to [configure VPC access](https://docs.aws.amazon.com/lambda/latest/dg/configuration-vpc.html){target="_blank"} for your Lambda Function.
-
-???+ tip "Amazon ElastiCache/MemoryDB for Redis as persistent storage layer provider"
- If you plan to use Amazon ElastiCache for Redis as the idempotency persistent storage layer, you may find [this AWS tutorial](https://docs.aws.amazon.com/lambda/latest/dg/services-elasticache-tutorial.html){target="_blank"} helpful.
- For those using Amazon MemoryDB for Redis, refer to [this AWS tutorial](https://aws.amazon.com/blogs/database/access-amazon-memorydb-for-redis-from-aws-lambda/){target="_blank"} specifically for the VPC setup guidance.
-
-After completing the VPC setup, you can use the templates provided below to set up Lambda functions with access to VPC internal subnets.
-
-=== "AWS Serverless Application Model (SAM) example"
-
- ```yaml hl_lines="9"
- --8<-- "examples/idempotency/templates/sam_redis_vpc.yaml"
- ```
-
- 1. Replace the Security Group ID and Subnet ID to match your VPC settings.
-
-### Configuring Redis persistence layer
-
-You can quickly get started by initializing the `RedisCachePersistenceLayer` class and applying the `idempotent` decorator to your Lambda handler. For a detailed example of using the `RedisCachePersistenceLayer`, refer to the [Persistence layers section](#redispersistencelayer).
-
-???+ info
- We enforce security best practices by using SSL connections in the `RedisCachePersistenceLayer`; to disable it, set `ssl=False`
-
-=== "Use Persistence Layer with Redis config variables"
- ```python hl_lines="7-9 12 26"
- --8<-- "examples/idempotency/src/getting_started_with_idempotency_redis_config.py"
- ```
-
-=== "Use established Redis Client"
- ```python hl_lines="4 9-11 14 22 36"
- --8<-- "examples/idempotency/src/getting_started_with_idempotency_redis_client.py"
- ```
-
-=== "Sample event"
-
- ```json
- --8<-- "examples/idempotency/src/getting_started_with_idempotency_payload.json"
- ```
-
-### Custom advanced settings
-
-For advanced configurations, such as setting up SSL certificates or customizing parameters like a custom timeout, you can utilize the Redis client to tailor these specific settings to your needs.
-
-=== "Advanced configuration using AWS Secrets"
- ```python hl_lines="7-9 11 13 23"
- --8<-- "examples/idempotency/src/using_redis_client_with_aws_secrets.py"
- ```
-
- 1. JSON stored:
- {
- "REDIS_ENDPOINT": "127.0.0.1",
- "REDIS_PORT": "6379",
- "REDIS_PASSWORD": "redis-secret"
- }
-
-=== "Advanced configuration with local certificates"
- ```python hl_lines="12 23-25"
- --8<-- "examples/idempotency/src/using_redis_client_with_local_certs.py"
- ```
-
- 1. JSON stored:
- {
- "REDIS_ENDPOINT": "127.0.0.1",
- "REDIS_PORT": "6379",
- "REDIS_PASSWORD": "redis-secret"
- }
- 2. redis_user.crt file stored in the "certs" directory of your Lambda function
- 3. redis_user_private.key file stored in the "certs" directory of your Lambda function
- 4. redis_ca.pem file stored in the "certs" directory of your Lambda function
-
## Advanced
### Customizing the default behavior
-Idempotent decorator can be further configured with **`IdempotencyConfig`** as seen in the previous example. These are the available options for further configuration
-
-| Parameter | Default | Description |
-|---------------------------------|---------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
-| **event_key_jmespath** | `""` | JMESPath expression to extract the idempotency key from the event record using [built-in functions](./jmespath_functions.md#built-in-jmespath-functions){target="_blank"} |
-| **payload_validation_jmespath** | `""` | JMESPath expression to validate whether certain parameters have changed in the event while the event payload |
-| **raise_on_no_idempotency_key** | `False` | Raise exception if no idempotency key was found in the request |
-| **expires_after_seconds** | 3600 | The number of seconds to wait before a record is expired |
-| **use_local_cache** | `False` | Whether to locally cache idempotency results |
-| **local_cache_max_items** | 256 | Max number of items to store in local cache |
-| **hash_function** | `md5` | Function to use for calculating hashes, as provided by [hashlib](https://docs.python.org/3/library/hashlib.html){target="_blank" rel="nofollow"} in the standard library. |
+You can override and further extend idempotency behavior via **`IdempotencyConfig`** with the following options:
+
+| Parameter | Default | Description |
+| ------------------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
+| **event_key_jmespath** | `""` | JMESPath expression to extract the idempotency key from the event record using [built-in functions](./jmespath_functions.md#built-in-jmespath-functions){target="_blank"} |
+| **payload_validation_jmespath** | `""` | JMESPath expression to validate whether certain parameters have changed in the event while the event payload _e.g., payload tampering._ |
+| **raise_on_no_idempotency_key** | `False` | Raise exception if no idempotency key was found in the request |
+| **expires_after_seconds** | 3600 | The number of seconds to wait before a record is expired, allowing a new transaction with the same idempotency key |
+| **use_local_cache** | `False` | Whether to cache idempotency results in-memory to save on persistence storage latency and costs |
+| **local_cache_max_items** | 256 | Max number of items to store in local cache |
+| **hash_function** | `md5` | Function to use for calculating hashes, as provided by [hashlib](https://docs.python.org/3/library/hashlib.html){target="_blank" rel="nofollow"} in the standard library. |
| **response_hook** | `None` | Function to use for processing the stored Idempotent response. This function hook is called when an existing idempotent response is found. See [Manipulating The Idempotent Response](idempotency.md#manipulating-the-idempotent-response) |
### Handling concurrent executions with the same payload
@@ -753,62 +834,6 @@ This utility will raise an **`IdempotencyAlreadyInProgressError`** exception if
This is a locking mechanism for correctness. Since we don't know the result from the first invocation yet, we can't safely allow another concurrent execution.
-### Using in-memory cache
-
-**By default, in-memory local caching is disabled**, since we don't know how much memory you consume per invocation compared to the maximum configured in your Lambda function.
-
-???+ note "Note: This in-memory cache is local to each Lambda execution environment"
- This means it will be effective in cases where your function's concurrency is low in comparison to the number of "retry" invocations with the same payload, because cache might be empty.
-
-You can enable in-memory caching with the **`use_local_cache`** parameter:
-
-=== "Caching idempotent transactions in-memory to prevent multiple calls to storage"
-
- ```python hl_lines="11"
- --8<-- "examples/idempotency/src/working_with_local_cache.py"
- ```
-
-=== "Sample event"
-
- ```json
- --8<-- "examples/idempotency/src/working_with_local_cache_payload.json"
- ```
-
-When enabled, the default is to cache a maximum of 256 records in each Lambda execution environment - You can change it with the **`local_cache_max_items`** parameter.
-
-### Expiring idempotency records
-
-!!! note "By default, we expire idempotency records after **an hour** (3600 seconds)."
-
-In most cases, it is not desirable to store the idempotency records forever. Rather, you want to guarantee that the same payload won't be executed within a period of time.
-
-You can change this window with the **`expires_after_seconds`** parameter:
-
-=== "Adjusting idempotency record expiration"
-
- ```python hl_lines="11"
- --8<-- "examples/idempotency/src/working_with_record_expiration.py"
- ```
-
-=== "Sample event"
-
- ```json
- --8<-- "examples/idempotency/src/working_with_record_expiration_payload.json"
- ```
-
-This will mark any records older than 5 minutes as expired, and [your function will be executed as normal if it is invoked with a matching payload](#expired-idempotency-records).
-
-???+ important "Idempotency record expiration vs DynamoDB time-to-live (TTL)"
- [DynamoDB TTL is a feature](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/howitworks-ttl.html){target="_blank"} to remove items after a certain period of time, it may occur within 48 hours of expiration.
-
- We don't rely on DynamoDB or any persistence storage layer to determine whether a record is expired to avoid eventual inconsistency states.
-
- Instead, Idempotency records saved in the storage layer contain timestamps that can be verified upon retrieval and double checked within Idempotency feature.
-
- **Why?**
-
- A record might still be valid (`COMPLETE`) when we retrieved, but in some rare cases it might expire a second later. A record could also be [cached in memory](#using-in-memory-cache). You might also want to have idempotent transactions that should expire in seconds.
-
### Payload validation
???+ question "Question: What if your function is invoked with the same payload except some outer parameters have changed?"
@@ -820,7 +845,7 @@ With **`payload_validation_jmespath`**, you can provide an additional JMESPath e
=== "Payload validation"
- ```python hl_lines="12 20 28"
+ ```python hl_lines="20 29 36"
--8<-- "examples/idempotency/src/working_with_validation_payload.py"
```
@@ -856,7 +881,7 @@ This means that we will raise **`IdempotencyKeyError`** if the evaluation of **`
=== "Idempotency key required"
- ```python hl_lines="11"
+ ```python hl_lines="14"
--8<-- "examples/idempotency/src/working_with_idempotency_key_required.py"
```
@@ -878,13 +903,13 @@ The **`boto_config`** and **`boto3_session`** parameters enable you to pass in a
=== "Custom session"
- ```python hl_lines="1 11 13"
+ ```python hl_lines="3 13 16"
--8<-- "examples/idempotency/src/working_with_custom_session.py"
```
=== "Custom config"
- ```python hl_lines="1 11 13"
+ ```python hl_lines="3 13 16"
--8<-- "examples/idempotency/src/working_with_custom_config.py"
```
@@ -894,34 +919,6 @@ The **`boto_config`** and **`boto3_session`** parameters enable you to pass in a
--8<-- "examples/idempotency/src/working_with_custom_config_payload.json"
```
-### Using a DynamoDB table with a composite primary key
-
-When using a composite primary key table (hash+range key), use `sort_key_attr` parameter when initializing your persistence layer.
-
-With this setting, we will save the idempotency key in the sort key instead of the primary key. By default, the primary key will now be set to `idempotency#{LAMBDA_FUNCTION_NAME}`.
-
-You can optionally set a static value for the partition key using the `static_pk_value` parameter.
-
-=== "Reusing a DynamoDB table that uses a composite primary key"
-
- ```python hl_lines="7"
- --8<-- "examples/idempotency/src/working_with_composite_key.py"
- ```
-
-=== "Sample Event"
-
- ```json
- --8<-- "examples/idempotency/src/working_with_composite_key_payload.json"
- ```
-
-The example function above would cause data to be stored in DynamoDB like this:
-
-| id | sort_key | expiration | status | data |
-| ---------------------------- | -------------------------------- | ---------- | ----------- | ----------------------------------------- |
-| idempotency#MyLambdaFunction | 1e956ef7da78d0cb890be999aecc0c9e | 1636549553 | COMPLETED | {"user_id": 12391, "message": "success"} |
-| idempotency#MyLambdaFunction | 2b2cdb5f86361e97b4383087c1ffdf27 | 1636549571 | COMPLETED | {"user_id": 527212, "message": "success"} |
-| idempotency#MyLambdaFunction | f091d2527ad1c78f05d54cc3f363be80 | 1636549585 | IN_PROGRESS | |
-
### Bring your own persistent store
This utility provides an abstract base class (ABC), so that you can implement your choice of persistent storage layer.
@@ -933,11 +930,9 @@ You can create your own persistent store from scratch by inheriting the `BasePer
* **`_update_record()`** – Updates an item in the persistence store.
* **`_delete_record()`** – Removes an item from the persistence store.
-=== "Bring your own persistent store"
-
- ```python hl_lines="8 18 65 74 96 124"
- --8<-- "examples/idempotency/src/bring_your_own_persistent_store.py"
- ```
+```python title="bring_your_own_persistent_store.py" hl_lines="8 18 65 74 96 124"
+--8<-- "examples/idempotency/src/bring_your_own_persistent_store.py"
+```
???+ danger
Pay attention to the documentation for each - you may need to perform additional checks inside these methods to ensure the idempotency guarantees remain intact.
@@ -950,7 +945,7 @@ You can set up a `response_hook` in the `IdempotentConfig` class to manipulate t
=== "Using an Idempotent Response Hook"
- ```python hl_lines="19 21 27 34"
+ ```python hl_lines="20 22 28 36"
--8<-- "examples/idempotency/src/working_with_response_hook.py"
```
@@ -976,11 +971,7 @@ When using response hooks to manipulate returned data from idempotent operations
## Compatibility with other utilities
-### Batch
-
-See [Batch integration](#batch-integration) above.
-
-### Validation utility
+### JSON Schema Validation
The idempotency utility can be used with the `validator` decorator. Ensure that idempotency is the innermost decorator.
@@ -990,9 +981,9 @@ The idempotency utility can be used with the `validator` decorator. Ensure that
Make sure to account for this behavior, if you set the `event_key_jmespath`.
-=== "Using Idempotency with JSONSchema Validation utility"
+=== "Using Idempotency with validation utility"
- ```python hl_lines="13"
+ ```python hl_lines="16"
--8<-- "examples/idempotency/src/integrate_idempotency_with_validator.py"
```
diff --git a/docs/versioning.md b/docs/versioning.md
index ca092c897e8..30499d7981a 100644
--- a/docs/versioning.md
+++ b/docs/versioning.md
@@ -45,6 +45,16 @@ Powertools for AWS Lambda follows the [AWS Lambda Runtime deprecation policy cyc
!!! note "AWS reserves the right to stop support for an underlying dependency without increasing the major SDK version"
+### Lambda layer lifecycle
+
+Powertools for AWS Lambda provides public Lambda layers as an alternative method for including the Powertools SDK into your Lambda functions.
+
+Unlike package indexers such as PyPi and NPMJS, which use semantic versioning (e.g., v1.2.3, v1.3.0), Lambda layers employs incrementing sequential versions (e.g., 1, 2, 3, 4). With each new release of the SDK, Powertools for AWS Lambda publishes an updated layer, including the SDK version in the layer description.
+
+Powertools for AWS Lambda layers are immutable and remain available beyond their end-of-life dates.
+
+Each Powertools for AWS Lambda layer adheres to the versioning policy outlined above.
+
### Communication methods
Maintenance announcements are communicated in several ways:
diff --git a/examples/event_handler_rest/src/working_with_openapi_extensions.py b/examples/event_handler_rest/src/working_with_openapi_extensions.py
new file mode 100644
index 00000000000..03489c6f7b8
--- /dev/null
+++ b/examples/event_handler_rest/src/working_with_openapi_extensions.py
@@ -0,0 +1,33 @@
+from aws_lambda_powertools.event_handler import APIGatewayRestResolver
+from aws_lambda_powertools.event_handler.openapi.models import APIKey, APIKeyIn, Server
+
+app = APIGatewayRestResolver(enable_validation=True)
+
+servers = Server(
+ url="http://example.com",
+ description="Example server",
+ openapi_extensions={"x-amazon-apigateway-endpoint-configuration": {"vpcEndpoint": "myendpointid"}}, # (1)!
+)
+
+
+@app.get(
+ "/hello",
+ openapi_extensions={"x-amazon-apigateway-integration": {"type": "aws", "uri": "my_lambda_arn"}}, # (2)!
+)
+def hello():
+ return app.get_openapi_json_schema(
+ servers=[servers],
+ security_schemes={
+ "apikey": APIKey(
+ name="X-API-KEY",
+ description="API KeY",
+ in_=APIKeyIn.header,
+ openapi_extensions={"x-amazon-apigateway-authorizer": "custom"}, # (3)!
+ ),
+ },
+ openapi_extensions={"x-amazon-apigateway-gateway-responses": {"DEFAULT_4XX"}}, # (4)!
+ )
+
+
+def lambda_handler(event, context):
+ return app.resolve(event, context)
diff --git a/examples/idempotency/src/customize_persistence_layer.py b/examples/idempotency/src/customize_persistence_layer.py
index 26409191ca9..a4e9aa6993e 100644
--- a/examples/idempotency/src/customize_persistence_layer.py
+++ b/examples/idempotency/src/customize_persistence_layer.py
@@ -1,11 +1,14 @@
+import os
+
from aws_lambda_powertools.utilities.idempotency import (
DynamoDBPersistenceLayer,
idempotent,
)
from aws_lambda_powertools.utilities.typing import LambdaContext
+table = os.getenv("IDEMPOTENCY_TABLE", "")
persistence_layer = DynamoDBPersistenceLayer(
- table_name="IdempotencyTable",
+ table_name=table,
key_attr="idempotency_key",
expiry_attr="expires_at",
in_progress_expiry_attr="in_progress_expires_at",
diff --git a/examples/idempotency/src/customize_persistence_layer_redis.py b/examples/idempotency/src/customize_persistence_layer_redis.py
index 7db3d1b53ea..40aef433396 100644
--- a/examples/idempotency/src/customize_persistence_layer_redis.py
+++ b/examples/idempotency/src/customize_persistence_layer_redis.py
@@ -1,3 +1,5 @@
+import os
+
from aws_lambda_powertools.utilities.idempotency import (
idempotent,
)
@@ -6,8 +8,9 @@
)
from aws_lambda_powertools.utilities.typing import LambdaContext
+redis_endpoint = os.getenv("REDIS_CLUSTER_ENDPOINT", "localhost")
persistence_layer = RedisCachePersistenceLayer(
- host="localhost",
+ host=redis_endpoint,
port=6379,
in_progress_expiry_attr="in_progress_expiration",
status_attr="status",
diff --git a/examples/idempotency/src/getting_started_with_idempotency.py b/examples/idempotency/src/getting_started_with_idempotency.py
index 0754f42c6b3..b17426c06f2 100644
--- a/examples/idempotency/src/getting_started_with_idempotency.py
+++ b/examples/idempotency/src/getting_started_with_idempotency.py
@@ -1,3 +1,4 @@
+import os
from dataclasses import dataclass, field
from uuid import uuid4
@@ -7,7 +8,8 @@
)
from aws_lambda_powertools.utilities.typing import LambdaContext
-persistence_layer = DynamoDBPersistenceLayer(table_name="IdempotencyTable")
+table = os.getenv("IDEMPOTENCY_TABLE", "")
+persistence_layer = DynamoDBPersistenceLayer(table_name=table)
@dataclass
@@ -17,8 +19,7 @@ class Payment:
payment_id: str = field(default_factory=lambda: f"{uuid4()}")
-class PaymentError(Exception):
- ...
+class PaymentError(Exception): ...
@idempotent(persistence_store=persistence_layer)
diff --git a/examples/idempotency/src/getting_started_with_idempotency_redis_client.py b/examples/idempotency/src/getting_started_with_idempotency_redis_client.py
index f06d059fad4..24dfe1be117 100644
--- a/examples/idempotency/src/getting_started_with_idempotency_redis_client.py
+++ b/examples/idempotency/src/getting_started_with_idempotency_redis_client.py
@@ -1,3 +1,4 @@
+import os
from dataclasses import dataclass, field
from uuid import uuid4
@@ -11,8 +12,9 @@
)
from aws_lambda_powertools.utilities.typing import LambdaContext
+redis_endpoint = os.getenv("REDIS_CLUSTER_ENDPOINT", "localhost")
client = Redis(
- host="localhost",
+ host=redis_endpoint,
port=6379,
socket_connect_timeout=5,
socket_timeout=5,
@@ -29,8 +31,7 @@ class Payment:
payment_id: str = field(default_factory=lambda: f"{uuid4()}")
-class PaymentError(Exception):
- ...
+class PaymentError(Exception): ...
@idempotent(persistence_store=persistence_layer)
diff --git a/examples/idempotency/src/getting_started_with_idempotency_redis_config.py b/examples/idempotency/src/getting_started_with_idempotency_redis_config.py
index de9c6526059..f3917042b28 100644
--- a/examples/idempotency/src/getting_started_with_idempotency_redis_config.py
+++ b/examples/idempotency/src/getting_started_with_idempotency_redis_config.py
@@ -1,3 +1,4 @@
+import os
from dataclasses import dataclass, field
from uuid import uuid4
@@ -9,7 +10,8 @@
)
from aws_lambda_powertools.utilities.typing import LambdaContext
-persistence_layer = RedisCachePersistenceLayer(host="localhost", port=6379)
+redis_endpoint = os.getenv("REDIS_CLUSTER_ENDPOINT", "localhost")
+persistence_layer = RedisCachePersistenceLayer(host=redis_endpoint, port=6379)
@dataclass
@@ -19,8 +21,7 @@ class Payment:
payment_id: str = field(default_factory=lambda: f"{uuid4()}")
-class PaymentError(Exception):
- ...
+class PaymentError(Exception): ...
@idempotent(persistence_store=persistence_layer)
diff --git a/examples/idempotency/src/integrate_idempotency_with_batch_processor.py b/examples/idempotency/src/integrate_idempotency_with_batch_processor.py
index 957cefb3202..120c8f12da9 100644
--- a/examples/idempotency/src/integrate_idempotency_with_batch_processor.py
+++ b/examples/idempotency/src/integrate_idempotency_with_batch_processor.py
@@ -1,5 +1,7 @@
-from aws_lambda_powertools import Logger
-from aws_lambda_powertools.utilities.batch import BatchProcessor, EventType
+import os
+from typing import Any, Dict
+
+from aws_lambda_powertools.utilities.batch import BatchProcessor, EventType, process_partial_response
from aws_lambda_powertools.utilities.data_classes.sqs_event import SQSRecord
from aws_lambda_powertools.utilities.idempotency import (
DynamoDBPersistenceLayer,
@@ -8,13 +10,11 @@
)
from aws_lambda_powertools.utilities.typing import LambdaContext
-logger = Logger()
processor = BatchProcessor(event_type=EventType.SQS)
-dynamodb = DynamoDBPersistenceLayer(table_name="IdempotencyTable")
-config = IdempotencyConfig(
- event_key_jmespath="messageId", # see Choosing a payload subset section
-)
+table = os.getenv("IDEMPOTENCY_TABLE", "")
+dynamodb = DynamoDBPersistenceLayer(table_name=table)
+config = IdempotencyConfig(event_key_jmespath="messageId")
@idempotent_function(data_keyword_argument="record", config=config, persistence_store=dynamodb)
@@ -22,16 +22,12 @@ def record_handler(record: SQSRecord):
return {"message": record.body}
-def lambda_handler(event: SQSRecord, context: LambdaContext):
+def lambda_handler(event: Dict[str, Any], context: LambdaContext):
config.register_lambda_context(context) # see Lambda timeouts section
- # with Lambda context registered for Idempotency
- # we can now kick in the Bach processing logic
- batch = event["Records"]
- with processor(records=batch, handler=record_handler):
- # in case you want to access each record processed by your record_handler
- # otherwise ignore the result variable assignment
- processed_messages = processor.process()
- logger.info(processed_messages)
-
- return processor.response()
+ return process_partial_response(
+ event=event,
+ context=context,
+ processor=processor,
+ record_handler=record_handler,
+ )
diff --git a/examples/idempotency/src/integrate_idempotency_with_validator.py b/examples/idempotency/src/integrate_idempotency_with_validator.py
index af833951446..675dbd249a9 100644
--- a/examples/idempotency/src/integrate_idempotency_with_validator.py
+++ b/examples/idempotency/src/integrate_idempotency_with_validator.py
@@ -1,3 +1,5 @@
+import os
+
from aws_lambda_powertools.utilities.idempotency import (
DynamoDBPersistenceLayer,
IdempotencyConfig,
@@ -6,8 +8,9 @@
from aws_lambda_powertools.utilities.typing import LambdaContext
from aws_lambda_powertools.utilities.validation import envelopes, validator
+table = os.getenv("IDEMPOTENCY_TABLE", "")
config = IdempotencyConfig(event_key_jmespath='["message", "username"]')
-persistence_layer = DynamoDBPersistenceLayer(table_name="IdempotencyTable")
+persistence_layer = DynamoDBPersistenceLayer(table_name=table)
@validator(envelope=envelopes.API_GATEWAY_HTTP)
diff --git a/examples/idempotency/src/using_redis_client_with_aws_secrets.py b/examples/idempotency/src/using_redis_client_with_aws_secrets.py
index f30751c8808..ee9e6d78c45 100644
--- a/examples/idempotency/src/using_redis_client_with_aws_secrets.py
+++ b/examples/idempotency/src/using_redis_client_with_aws_secrets.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
from typing import Any
from redis import Redis
@@ -8,11 +10,11 @@
RedisCachePersistenceLayer,
)
-redis_values: Any = parameters.get_secret("redis_info", transform="json") # (1)!
+redis_values: dict[str, Any] = parameters.get_secret("redis_info", transform="json") # (1)!
redis_client = Redis(
- host=redis_values.get("REDIS_HOST"),
- port=redis_values.get("REDIS_PORT"),
+ host=redis_values.get("REDIS_HOST", "localhost"),
+ port=redis_values.get("REDIS_PORT", 6379),
password=redis_values.get("REDIS_PASSWORD"),
decode_responses=True,
socket_timeout=10.0,
diff --git a/examples/idempotency/src/using_redis_client_with_local_certs.py b/examples/idempotency/src/using_redis_client_with_local_certs.py
index cbad1cc92f4..2b6a5892c5b 100644
--- a/examples/idempotency/src/using_redis_client_with_local_certs.py
+++ b/examples/idempotency/src/using_redis_client_with_local_certs.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
from typing import Any
from redis import Redis
@@ -9,12 +11,12 @@
RedisCachePersistenceLayer,
)
-redis_values: Any = parameters.get_secret("redis_info", transform="json") # (1)!
+redis_values: dict[str, Any] = parameters.get_secret("redis_info", transform="json") # (1)!
redis_client = Redis(
- host=redis_values.get("REDIS_HOST"),
- port=redis_values.get("REDIS_PORT"),
+ host=redis_values.get("REDIS_HOST", "localhost"),
+ port=redis_values.get("REDIS_PORT", 6379),
password=redis_values.get("REDIS_PASSWORD"),
decode_responses=True,
socket_timeout=10.0,
diff --git a/examples/idempotency/src/working_with_composite_key.py b/examples/idempotency/src/working_with_composite_key.py
index f1b70cba99a..92bf1e6ec9a 100644
--- a/examples/idempotency/src/working_with_composite_key.py
+++ b/examples/idempotency/src/working_with_composite_key.py
@@ -1,10 +1,13 @@
+import os
+
from aws_lambda_powertools.utilities.idempotency import (
DynamoDBPersistenceLayer,
idempotent,
)
from aws_lambda_powertools.utilities.typing import LambdaContext
-persistence_layer = DynamoDBPersistenceLayer(table_name="IdempotencyTable", sort_key_attr="sort_key")
+table = os.getenv("IDEMPOTENCY_TABLE", "")
+persistence_layer = DynamoDBPersistenceLayer(table_name=table, sort_key_attr="sort_key")
@idempotent(persistence_store=persistence_layer)
diff --git a/examples/idempotency/src/working_with_custom_config.py b/examples/idempotency/src/working_with_custom_config.py
index 30539f88f3c..3d0f464a1dd 100644
--- a/examples/idempotency/src/working_with_custom_config.py
+++ b/examples/idempotency/src/working_with_custom_config.py
@@ -1,3 +1,5 @@
+import os
+
from botocore.config import Config
from aws_lambda_powertools.utilities.idempotency import (
@@ -10,7 +12,8 @@
# See: https://botocore.amazonaws.com/v1/documentation/api/latest/reference/config.html#botocore-config
boto_config = Config()
-persistence_layer = DynamoDBPersistenceLayer(table_name="IdempotencyTable", boto_config=boto_config)
+table = os.getenv("IDEMPOTENCY_TABLE", "")
+persistence_layer = DynamoDBPersistenceLayer(table_name=table, boto_config=boto_config)
config = IdempotencyConfig(event_key_jmespath="body")
diff --git a/examples/idempotency/src/working_with_custom_session.py b/examples/idempotency/src/working_with_custom_session.py
index aae89f8a3fe..af414c829de 100644
--- a/examples/idempotency/src/working_with_custom_session.py
+++ b/examples/idempotency/src/working_with_custom_session.py
@@ -1,3 +1,5 @@
+import os
+
import boto3
from aws_lambda_powertools.utilities.idempotency import (
@@ -10,7 +12,8 @@
# See: https://boto3.amazonaws.com/v1/documentation/api/latest/reference/core/session.html#module-boto3.session
boto3_session = boto3.session.Session()
-persistence_layer = DynamoDBPersistenceLayer(table_name="IdempotencyTable", boto3_session=boto3_session)
+table = os.getenv("IDEMPOTENCY_TABLE", "")
+persistence_layer = DynamoDBPersistenceLayer(table_name=table, boto3_session=boto3_session)
config = IdempotencyConfig(event_key_jmespath="body")
diff --git a/examples/idempotency/src/working_with_dataclass_deduced_output_serializer.py b/examples/idempotency/src/working_with_dataclass_deduced_output_serializer.py
index c59c8b078f7..e6f74cb8f9a 100644
--- a/examples/idempotency/src/working_with_dataclass_deduced_output_serializer.py
+++ b/examples/idempotency/src/working_with_dataclass_deduced_output_serializer.py
@@ -1,3 +1,4 @@
+import os
from dataclasses import dataclass
from aws_lambda_powertools.utilities.idempotency import (
@@ -8,7 +9,8 @@
from aws_lambda_powertools.utilities.idempotency.serialization.dataclass import DataclassSerializer
from aws_lambda_powertools.utilities.typing import LambdaContext
-dynamodb = DynamoDBPersistenceLayer(table_name="IdempotencyTable")
+table = os.getenv("IDEMPOTENCY_TABLE", "")
+dynamodb = DynamoDBPersistenceLayer(table_name=table)
config = IdempotencyConfig(event_key_jmespath="order_id") # see Choosing a payload subset section
diff --git a/examples/idempotency/src/working_with_dataclass_explicitly_output_serializer.py b/examples/idempotency/src/working_with_dataclass_explicitly_output_serializer.py
index fc2412fb1a2..05ea956d696 100644
--- a/examples/idempotency/src/working_with_dataclass_explicitly_output_serializer.py
+++ b/examples/idempotency/src/working_with_dataclass_explicitly_output_serializer.py
@@ -1,3 +1,4 @@
+import os
from dataclasses import dataclass
from aws_lambda_powertools.utilities.idempotency import (
@@ -8,7 +9,8 @@
from aws_lambda_powertools.utilities.idempotency.serialization.dataclass import DataclassSerializer
from aws_lambda_powertools.utilities.typing import LambdaContext
-dynamodb = DynamoDBPersistenceLayer(table_name="IdempotencyTable")
+table = os.getenv("IDEMPOTENCY_TABLE", "")
+dynamodb = DynamoDBPersistenceLayer(table_name=table)
config = IdempotencyConfig(event_key_jmespath="order_id") # see Choosing a payload subset section
diff --git a/examples/idempotency/src/working_with_exceptions.py b/examples/idempotency/src/working_with_exceptions.py
index ff282d5a601..b416a61b60a 100644
--- a/examples/idempotency/src/working_with_exceptions.py
+++ b/examples/idempotency/src/working_with_exceptions.py
@@ -1,3 +1,5 @@
+import os
+
import requests
from aws_lambda_powertools.utilities.idempotency import (
@@ -5,33 +7,32 @@
IdempotencyConfig,
idempotent_function,
)
+from aws_lambda_powertools.utilities.idempotency.exceptions import IdempotencyPersistenceLayerError
from aws_lambda_powertools.utilities.typing import LambdaContext
-persistence_layer = DynamoDBPersistenceLayer(table_name="IdempotencyTable")
+table = os.getenv("IDEMPOTENCY_TABLE", "")
+persistence_layer = DynamoDBPersistenceLayer(table_name=table)
config = IdempotencyConfig()
-def lambda_handler(event: dict, context: LambdaContext):
- # If an exception is raised here, no idempotent record will ever get created as the
- # idempotent function does not get called
- try:
- endpoint = "https://jsonplaceholder.typicode.com/comments/" # change this endpoint to force an exception
- requests.get(endpoint)
- except Exception as exc:
- return str(exc)
-
- call_external_service(data={"user": "user1", "id": 5})
-
- # This exception will not cause the idempotent record to be deleted, since it
- # happens after the decorated function has been successfully called
- raise Exception
-
-
@idempotent_function(data_keyword_argument="data", config=config, persistence_store=persistence_layer)
def call_external_service(data: dict):
+ # Any exception raised will lead to idempotency record to be deleted
result: requests.Response = requests.post(
"https://jsonplaceholder.typicode.com/comments/",
- json={"user": data["user"], "transaction_id": data["id"]},
+ json=data,
)
return result.json()
+
+
+def lambda_handler(event: dict, context: LambdaContext):
+ try:
+ call_external_service(data=event)
+ except IdempotencyPersistenceLayerError as e:
+ # No idempotency, but you can decide to error differently.
+ raise RuntimeError(f"Oops, can't talk to persistence layer. Permissions? error: {e}")
+
+ # This exception will not impact the idempotency of 'call_external_service'
+ # because it happens in isolation, or outside their scope.
+ raise SyntaxError("Oops, this shouldn't be here.")
diff --git a/examples/idempotency/src/working_with_idempotency_key_required.py b/examples/idempotency/src/working_with_idempotency_key_required.py
index 347740ab4a3..465a7d47e0a 100644
--- a/examples/idempotency/src/working_with_idempotency_key_required.py
+++ b/examples/idempotency/src/working_with_idempotency_key_required.py
@@ -1,3 +1,5 @@
+import os
+
from aws_lambda_powertools.utilities.idempotency import (
DynamoDBPersistenceLayer,
IdempotencyConfig,
@@ -5,7 +7,8 @@
)
from aws_lambda_powertools.utilities.typing import LambdaContext
-persistence_layer = DynamoDBPersistenceLayer(table_name="IdempotencyTable")
+table = os.getenv("IDEMPOTENCY_TABLE", "")
+persistence_layer = DynamoDBPersistenceLayer(table_name=table)
config = IdempotencyConfig(
event_key_jmespath='["user.uid", "order_id"]',
raise_on_no_idempotency_key=True,
diff --git a/examples/idempotency/src/working_with_idempotent_function_custom_output_serializer.py b/examples/idempotency/src/working_with_idempotent_function_custom_output_serializer.py
index a62961fa5f3..5d6c1ea3b99 100644
--- a/examples/idempotency/src/working_with_idempotent_function_custom_output_serializer.py
+++ b/examples/idempotency/src/working_with_idempotent_function_custom_output_serializer.py
@@ -1,3 +1,4 @@
+import os
from typing import Dict, Type
from aws_lambda_powertools.utilities.idempotency import (
@@ -8,7 +9,8 @@
from aws_lambda_powertools.utilities.idempotency.serialization.custom_dict import CustomDictSerializer
from aws_lambda_powertools.utilities.typing import LambdaContext
-dynamodb = DynamoDBPersistenceLayer(table_name="IdempotencyTable")
+table = os.getenv("IDEMPOTENCY_TABLE", "")
+dynamodb = DynamoDBPersistenceLayer(table_name=table)
config = IdempotencyConfig(event_key_jmespath="order_id") # see Choosing a payload subset section
diff --git a/examples/idempotency/src/working_with_idempotent_function_dataclass.py b/examples/idempotency/src/working_with_idempotent_function_dataclass.py
index e56c0b42029..3a4e347b22a 100644
--- a/examples/idempotency/src/working_with_idempotent_function_dataclass.py
+++ b/examples/idempotency/src/working_with_idempotent_function_dataclass.py
@@ -1,3 +1,4 @@
+import os
from dataclasses import dataclass
from aws_lambda_powertools.utilities.idempotency import (
@@ -7,7 +8,8 @@
)
from aws_lambda_powertools.utilities.typing import LambdaContext
-dynamodb = DynamoDBPersistenceLayer(table_name="IdempotencyTable")
+table = os.getenv("IDEMPOTENCY_TABLE", "")
+dynamodb = DynamoDBPersistenceLayer(table_name=table)
config = IdempotencyConfig(event_key_jmespath="order_id") # see Choosing a payload subset section
@@ -24,12 +26,14 @@ class Order:
@idempotent_function(data_keyword_argument="order", config=config, persistence_store=dynamodb)
-def process_order(order: Order):
+def process_order(order: Order): # (1)!
return f"processed order {order.order_id}"
def lambda_handler(event: dict, context: LambdaContext):
- config.register_lambda_context(context) # see Lambda timeouts section
+ # see Lambda timeouts section
+ config.register_lambda_context(context) # (2)!
+
order_item = OrderItem(sku="fake", description="sample")
order = Order(item=order_item, order_id=1)
diff --git a/examples/idempotency/src/working_with_idempotent_function_pydantic.py b/examples/idempotency/src/working_with_idempotent_function_pydantic.py
index 5dfd42ae0a8..45b57499a29 100644
--- a/examples/idempotency/src/working_with_idempotent_function_pydantic.py
+++ b/examples/idempotency/src/working_with_idempotent_function_pydantic.py
@@ -1,3 +1,5 @@
+import os
+
from aws_lambda_powertools.utilities.idempotency import (
DynamoDBPersistenceLayer,
IdempotencyConfig,
@@ -6,7 +8,8 @@
from aws_lambda_powertools.utilities.parser import BaseModel
from aws_lambda_powertools.utilities.typing import LambdaContext
-dynamodb = DynamoDBPersistenceLayer(table_name="IdempotencyTable")
+table = os.getenv("IDEMPOTENCY_TABLE", "")
+dynamodb = DynamoDBPersistenceLayer(table_name=table)
config = IdempotencyConfig(event_key_jmespath="order_id") # see Choosing a payload subset section
diff --git a/examples/idempotency/src/working_with_lambda_timeout.py b/examples/idempotency/src/working_with_lambda_timeout.py
index 82b8130b6b7..eac423607ad 100644
--- a/examples/idempotency/src/working_with_lambda_timeout.py
+++ b/examples/idempotency/src/working_with_lambda_timeout.py
@@ -1,3 +1,5 @@
+import os
+
from aws_lambda_powertools.utilities.data_classes.sqs_event import SQSRecord
from aws_lambda_powertools.utilities.idempotency import (
DynamoDBPersistenceLayer,
@@ -6,7 +8,8 @@
)
from aws_lambda_powertools.utilities.typing import LambdaContext
-persistence_layer = DynamoDBPersistenceLayer(table_name="IdempotencyTable")
+table = os.getenv("IDEMPOTENCY_TABLE", "")
+persistence_layer = DynamoDBPersistenceLayer(table_name=table)
config = IdempotencyConfig()
diff --git a/examples/idempotency/src/working_with_local_cache.py b/examples/idempotency/src/working_with_local_cache.py
index 82f39dff2ef..571098715f7 100644
--- a/examples/idempotency/src/working_with_local_cache.py
+++ b/examples/idempotency/src/working_with_local_cache.py
@@ -1,3 +1,5 @@
+import os
+
from aws_lambda_powertools.utilities.idempotency import (
DynamoDBPersistenceLayer,
IdempotencyConfig,
@@ -5,10 +7,12 @@
)
from aws_lambda_powertools.utilities.typing import LambdaContext
-persistence_layer = DynamoDBPersistenceLayer(table_name="IdempotencyTable")
+table = os.getenv("IDEMPOTENCY_TABLE", "")
+persistence_layer = DynamoDBPersistenceLayer(table_name=table)
config = IdempotencyConfig(
- event_key_jmespath="body",
- use_local_cache=True,
+ event_key_jmespath="powertools_json(body)",
+ # by default, it holds 256 items in a Least-Recently-Used (LRU) manner
+ use_local_cache=True, # (1)!
)
diff --git a/examples/idempotency/src/working_with_payload_subset.py b/examples/idempotency/src/working_with_payload_subset.py
index 9fcc828fe1d..c16508cbbb2 100644
--- a/examples/idempotency/src/working_with_payload_subset.py
+++ b/examples/idempotency/src/working_with_payload_subset.py
@@ -1,4 +1,5 @@
import json
+import os
from dataclasses import dataclass, field
from uuid import uuid4
@@ -9,7 +10,8 @@
)
from aws_lambda_powertools.utilities.typing import LambdaContext
-persistence_layer = DynamoDBPersistenceLayer(table_name="IdempotencyTable")
+table = os.getenv("IDEMPOTENCY_TABLE", "")
+persistence_layer = DynamoDBPersistenceLayer(table_name=table)
# Deserialize JSON string under the "body" key
# then extract "user" and "product_id" data
@@ -23,8 +25,7 @@ class Payment:
payment_id: str = field(default_factory=lambda: f"{uuid4()}")
-class PaymentError(Exception):
- ...
+class PaymentError(Exception): ...
@idempotent(config=config, persistence_store=persistence_layer)
diff --git a/examples/idempotency/src/working_with_pydantic_deduced_output_serializer.py b/examples/idempotency/src/working_with_pydantic_deduced_output_serializer.py
index f24fda81e86..b904a5ad670 100644
--- a/examples/idempotency/src/working_with_pydantic_deduced_output_serializer.py
+++ b/examples/idempotency/src/working_with_pydantic_deduced_output_serializer.py
@@ -1,3 +1,5 @@
+import os
+
from aws_lambda_powertools.utilities.idempotency import (
DynamoDBPersistenceLayer,
IdempotencyConfig,
@@ -7,7 +9,8 @@
from aws_lambda_powertools.utilities.parser import BaseModel
from aws_lambda_powertools.utilities.typing import LambdaContext
-dynamodb = DynamoDBPersistenceLayer(table_name="IdempotencyTable")
+table = os.getenv("IDEMPOTENCY_TABLE", "")
+dynamodb = DynamoDBPersistenceLayer(table_name=table)
config = IdempotencyConfig(event_key_jmespath="order_id") # see Choosing a payload subset section
diff --git a/examples/idempotency/src/working_with_pydantic_explicitly_output_serializer.py b/examples/idempotency/src/working_with_pydantic_explicitly_output_serializer.py
index 7bd63dfcd9f..b888b58a87c 100644
--- a/examples/idempotency/src/working_with_pydantic_explicitly_output_serializer.py
+++ b/examples/idempotency/src/working_with_pydantic_explicitly_output_serializer.py
@@ -1,3 +1,5 @@
+import os
+
from aws_lambda_powertools.utilities.idempotency import (
DynamoDBPersistenceLayer,
IdempotencyConfig,
@@ -7,7 +9,8 @@
from aws_lambda_powertools.utilities.parser import BaseModel
from aws_lambda_powertools.utilities.typing import LambdaContext
-dynamodb = DynamoDBPersistenceLayer(table_name="IdempotencyTable")
+table = os.getenv("IDEMPOTENCY_TABLE", "")
+dynamodb = DynamoDBPersistenceLayer(table_name=table)
config = IdempotencyConfig(event_key_jmespath="order_id") # see Choosing a payload subset section
diff --git a/examples/idempotency/src/working_with_record_expiration.py b/examples/idempotency/src/working_with_record_expiration.py
index 738b4749ebc..e1696ee7bbf 100644
--- a/examples/idempotency/src/working_with_record_expiration.py
+++ b/examples/idempotency/src/working_with_record_expiration.py
@@ -1,3 +1,5 @@
+import os
+
from aws_lambda_powertools.utilities.idempotency import (
DynamoDBPersistenceLayer,
IdempotencyConfig,
@@ -5,10 +7,11 @@
)
from aws_lambda_powertools.utilities.typing import LambdaContext
-persistence_layer = DynamoDBPersistenceLayer(table_name="IdempotencyTable")
+table = os.getenv("IDEMPOTENCY_TABLE", "")
+persistence_layer = DynamoDBPersistenceLayer(table_name=table)
config = IdempotencyConfig(
event_key_jmespath="body",
- expires_after_seconds=5 * 60, # 5 minutes
+ expires_after_seconds=24 * 60 * 60, # 24 hours
)
diff --git a/examples/idempotency/src/working_with_response_hook.py b/examples/idempotency/src/working_with_response_hook.py
index 2c2208d25a5..e800f3a0356 100644
--- a/examples/idempotency/src/working_with_response_hook.py
+++ b/examples/idempotency/src/working_with_response_hook.py
@@ -1,4 +1,5 @@
import datetime
+import os
import uuid
from typing import Dict
@@ -30,7 +31,8 @@ def my_response_hook(response: Dict, idempotent_data: DataRecord) -> Dict:
return response
-dynamodb = DynamoDBPersistenceLayer(table_name="IdempotencyTable")
+table = os.getenv("IDEMPOTENCY_TABLE", "")
+dynamodb = DynamoDBPersistenceLayer(table_name=table)
config = IdempotencyConfig(response_hook=my_response_hook)
diff --git a/examples/idempotency/src/working_with_validation_payload.py b/examples/idempotency/src/working_with_validation_payload.py
index d81e7d183bd..12b8423e7c4 100644
--- a/examples/idempotency/src/working_with_validation_payload.py
+++ b/examples/idempotency/src/working_with_validation_payload.py
@@ -1,15 +1,24 @@
+import os
from dataclasses import dataclass, field
from uuid import uuid4
+from aws_lambda_powertools import Logger
from aws_lambda_powertools.utilities.idempotency import (
DynamoDBPersistenceLayer,
IdempotencyConfig,
idempotent,
)
+from aws_lambda_powertools.utilities.idempotency.exceptions import IdempotencyValidationError
from aws_lambda_powertools.utilities.typing import LambdaContext
-persistence_layer = DynamoDBPersistenceLayer(table_name="IdempotencyTable")
-config = IdempotencyConfig(event_key_jmespath='["user_id", "product_id"]', payload_validation_jmespath="amount")
+logger = Logger()
+
+table = os.getenv("IDEMPOTENCY_TABLE", "")
+persistence_layer = DynamoDBPersistenceLayer(table_name=table)
+config = IdempotencyConfig(
+ event_key_jmespath='["user_id", "product_id"]',
+ payload_validation_jmespath="amount",
+)
@dataclass
@@ -21,8 +30,7 @@ class Payment:
payment_id: str = field(default_factory=lambda: f"{uuid4()}")
-class PaymentError(Exception):
- ...
+class PaymentError(Exception): ...
@idempotent(config=config, persistence_store=persistence_layer)
@@ -34,6 +42,12 @@ def lambda_handler(event: dict, context: LambdaContext):
"message": "success",
"statusCode": 200,
}
+ except IdempotencyValidationError:
+ logger.exception("Payload tampering detected", payment=payment, failure_type="validation")
+ return {
+ "message": "Unable to process payment at this time. Try again later.",
+ "statusCode": 500,
+ }
except Exception as exc:
raise PaymentError(f"Error creating payment {str(exc)}")
diff --git a/examples/idempotency/templates/cfn_redis_serverless.yaml b/examples/idempotency/templates/cfn_redis_serverless.yaml
index 9087efce6f9..8ce9d67f3cb 100644
--- a/examples/idempotency/templates/cfn_redis_serverless.yaml
+++ b/examples/idempotency/templates/cfn_redis_serverless.yaml
@@ -1,4 +1,5 @@
-AWSTemplateFormatVersion: '2010-09-09'
+AWSTemplateFormatVersion: "2010-09-09"
+Transform: AWS::Serverless-2016-10-31
Resources:
RedisServerlessIdempotency:
@@ -7,7 +8,24 @@ Resources:
Engine: redis
ServerlessCacheName: redis-cache
SecurityGroupIds: # (1)!
- - security-{your_sg_id}
+ - security-{your_sg_id}
SubnetIds:
+ - subnet-{your_subnet_id_1}
+ - subnet-{your_subnet_id_2}
+
+ HelloWorldFunction:
+ Type: AWS::Serverless::Function
+ Properties:
+ Runtime: python3.12
+ Handler: app.py
+ VpcConfig: # (1)!
+ SecurityGroupIds:
+ - security-{your_sg_id}
+ SubnetIds:
- subnet-{your_subnet_id_1}
- subnet-{your_subnet_id_2}
+ Environment:
+ Variables:
+ POWERTOOLS_SERVICE_NAME: sample
+ REDIS_HOST: !GetAtt RedisServerlessIdempotency.Endpoint.Address
+ REDIS_PORT: !GetAtt RedisServerlessIdempotency.Endpoint.Port
diff --git a/examples/idempotency/templates/sam.yaml b/examples/idempotency/templates/sam.yaml
index c4eaf766c23..4faab5c4225 100644
--- a/examples/idempotency/templates/sam.yaml
+++ b/examples/idempotency/templates/sam.yaml
@@ -21,11 +21,14 @@ Resources:
Handler: app.py
Policies:
- Statement:
- - Sid: AllowDynamodbReadWrite
- Effect: Allow
- Action:
- - dynamodb:PutItem
- - dynamodb:GetItem
- - dynamodb:UpdateItem
- - dynamodb:DeleteItem
- Resource: !GetAtt IdempotencyTable.Arn
+ - Sid: AllowDynamodbReadWrite
+ Effect: Allow
+ Action:
+ - dynamodb:PutItem
+ - dynamodb:GetItem
+ - dynamodb:UpdateItem
+ - dynamodb:DeleteItem
+ Resource: !GetAtt IdempotencyTable.Arn
+ Environment:
+ Variables:
+ IDEMPOTENCY_TABLE: !Ref IdempotencyTable
diff --git a/examples/idempotency/templates/sam_redis_vpc.yaml b/examples/idempotency/templates/sam_redis_vpc.yaml
deleted file mode 100644
index 921b1e75b84..00000000000
--- a/examples/idempotency/templates/sam_redis_vpc.yaml
+++ /dev/null
@@ -1,14 +0,0 @@
-AWSTemplateFormatVersion: '2010-09-09'
-Transform: AWS::Serverless-2016-10-31
-Resources:
- HelloWorldFunction:
- Type: AWS::Serverless::Function
- Properties:
- Runtime: python3.11
- Handler: app.py
- VpcConfig: # (1)!
- SecurityGroupIds:
- - security-{your_sg_id}
- SubnetIds:
- - subnet-{your_subnet_id_1}
- - subnet-{your_subnet_id_2}
diff --git a/examples/logger/sam/template.yaml b/examples/logger/sam/template.yaml
index b6d71137cba..c33c7621975 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:AWSLambdaPowertoolsPythonV2:75
+ - !Sub arn:aws:lambda:${AWS::Region}:017000801446:layer:AWSLambdaPowertoolsPythonV2:76
Resources:
LoggerLambdaHandlerExample:
diff --git a/examples/metrics/sam/template.yaml b/examples/metrics/sam/template.yaml
index cc00ffa242c..fd262c328d2 100644
--- a/examples/metrics/sam/template.yaml
+++ b/examples/metrics/sam/template.yaml
@@ -15,7 +15,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:AWSLambdaPowertoolsPythonV2:75
+ - !Sub arn:aws:lambda:${AWS::Region}:017000801446:layer:AWSLambdaPowertoolsPythonV2:76
Resources:
CaptureLambdaHandlerExample:
diff --git a/examples/tracer/sam/template.yaml b/examples/tracer/sam/template.yaml
index ca64aeae86c..a1ff2257c8a 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:AWSLambdaPowertoolsPythonV2:75
+ - !Sub arn:aws:lambda:${AWS::Region}:017000801446:layer:AWSLambdaPowertoolsPythonV2:76
Resources:
CaptureLambdaHandlerExample:
diff --git a/includes/abbreviations.md b/includes/abbreviations.md
index ed52b93fe64..5e0db4dcb27 100644
--- a/includes/abbreviations.md
+++ b/includes/abbreviations.md
@@ -1 +1,2 @@
*[observability provider]: An AWS Lambda Observability Partner
+*[unhandled exception]: An exception that is not caught by any explicit try/except block
diff --git a/layer/scripts/layer-balancer/go.mod b/layer/scripts/layer-balancer/go.mod
index 5627db9b784..573ca08711f 100644
--- a/layer/scripts/layer-balancer/go.mod
+++ b/layer/scripts/layer-balancer/go.mod
@@ -3,25 +3,25 @@ module layerbalancer
go 1.18
require (
- github.com/aws/aws-sdk-go-v2 v1.30.2
- github.com/aws/aws-sdk-go-v2/config v1.27.25
- github.com/aws/aws-sdk-go-v2/service/lambda v1.56.2
+ github.com/aws/aws-sdk-go-v2 v1.30.3
+ github.com/aws/aws-sdk-go-v2/config v1.27.27
+ github.com/aws/aws-sdk-go-v2/service/lambda v1.56.3
golang.org/x/exp v0.0.0-20230321023759-10a507213a29
golang.org/x/sync v0.7.0
)
require (
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.3 // indirect
- github.com/aws/aws-sdk-go-v2/credentials v1.17.25 // indirect
- github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.10 // indirect
- github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.14 // indirect
- github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.14 // indirect
+ github.com/aws/aws-sdk-go-v2/credentials v1.17.27 // indirect
+ github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.11 // indirect
+ github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.15 // indirect
+ github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.15 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.3 // indirect
- github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.16 // indirect
- github.com/aws/aws-sdk-go-v2/service/sso v1.22.2 // indirect
- github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.3 // indirect
- github.com/aws/aws-sdk-go-v2/service/sts v1.30.2 // indirect
+ github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.17 // indirect
+ github.com/aws/aws-sdk-go-v2/service/sso v1.22.4 // indirect
+ github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.4 // indirect
+ github.com/aws/aws-sdk-go-v2/service/sts v1.30.3 // indirect
github.com/aws/smithy-go v1.20.3 // indirect
github.com/jmespath/go-jmespath v0.4.0 // indirect
)
diff --git a/layer/scripts/layer-balancer/go.sum b/layer/scripts/layer-balancer/go.sum
index 6217df53008..665bc38333a 100644
--- a/layer/scripts/layer-balancer/go.sum
+++ b/layer/scripts/layer-balancer/go.sum
@@ -1,31 +1,31 @@
-github.com/aws/aws-sdk-go-v2 v1.30.2 h1:4xL6l0M1fbGWqRqfm5xXsnPkzzvtR4nEUDOZNjTuTvc=
-github.com/aws/aws-sdk-go-v2 v1.30.2/go.mod h1:ElN9h07Hy7l2xZounYhqIv1TxPy+31GGr4sEEZlOfDc=
+github.com/aws/aws-sdk-go-v2 v1.30.3 h1:jUeBtG0Ih+ZIFH0F4UkmL9w3cSpaMv9tYYDbzILP8dY=
+github.com/aws/aws-sdk-go-v2 v1.30.3/go.mod h1:nIQjQVp5sfpQcTc9mPSr1B0PaWK5ByX9MOoDadSN4lc=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.3 h1:tW1/Rkad38LA15X4UQtjXZXNKsCgkshC3EbmcUmghTg=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.3/go.mod h1:UbnqO+zjqk3uIt9yCACHJ9IVNhyhOCnYk8yA19SAWrM=
-github.com/aws/aws-sdk-go-v2/config v1.27.25 h1:SmIsUvwcz1AgvRszLMEazqtynjIqYx7PKWXvTpKEaJc=
-github.com/aws/aws-sdk-go-v2/config v1.27.25/go.mod h1:MO0vtISERCZZTKbxTNEKQ/2R7X7ddHMX5WkdfRh74nU=
-github.com/aws/aws-sdk-go-v2/credentials v1.17.25 h1:lnZq41/zNeKX42fYZEgZ21tp+2H3wD9dRvKHVQoQVoA=
-github.com/aws/aws-sdk-go-v2/credentials v1.17.25/go.mod h1:nusEt4GmUeIRun/DSygR+SNo4ZDPwSazRLHB/JL+ZXI=
-github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.10 h1:HHTpwyMcGI+ueBc1zYc8vNDrcuyShOGVomgqv+MiPUY=
-github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.10/go.mod h1:ynaPKdMFUPJWGq2ilDQqFQVyBF7LPTu131QL6rE+Dmk=
-github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.14 h1:VQaovRAzif3gv8A/PpTHHiuIxlvAyDwBQNgiiZ+uXnA=
-github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.14/go.mod h1:fVGBeMoBKNCjcVPPmxDq7mDqK66IdsNNAWCuNYQE65g=
-github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.14 h1:H3mJaGAsqZZvPm+n0u3yABuO4MjXqAp/cxceVByPKaM=
-github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.14/go.mod h1:llg6cnW4R8iWCCUS+Q5oOWQTkVHpTJY93TkOX3eKNxg=
+github.com/aws/aws-sdk-go-v2/config v1.27.27 h1:HdqgGt1OAP0HkEDDShEl0oSYa9ZZBSOmKpdpsDMdO90=
+github.com/aws/aws-sdk-go-v2/config v1.27.27/go.mod h1:MVYamCg76dFNINkZFu4n4RjDixhVr51HLj4ErWzrVwg=
+github.com/aws/aws-sdk-go-v2/credentials v1.17.27 h1:2raNba6gr2IfA0eqqiP2XiQ0UVOpGPgDSi0I9iAP+UI=
+github.com/aws/aws-sdk-go-v2/credentials v1.17.27/go.mod h1:gniiwbGahQByxan6YjQUMcW4Aov6bLC3m+evgcoN4r4=
+github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.11 h1:KreluoV8FZDEtI6Co2xuNk/UqI9iwMrOx/87PBNIKqw=
+github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.11/go.mod h1:SeSUYBLsMYFoRvHE0Tjvn7kbxaUhl75CJi1sbfhMxkU=
+github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.15 h1:SoNJ4RlFEQEbtDcCEt+QG56MY4fm4W8rYirAmq+/DdU=
+github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.15/go.mod h1:U9ke74k1n2bf+RIgoX1SXFed1HLs51OgUSs+Ph0KJP8=
+github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.15 h1:C6WHdGnTDIYETAm5iErQUiVNsclNx9qbJVPIt03B6bI=
+github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.15/go.mod h1:ZQLZqhcu+JhSrA9/NXRm8SkDvsycE+JkV3WGY41e+IM=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 h1:hT8rVHwugYE2lEfdFE0QWVo81lF7jMrYJVDWI+f+VxU=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0/go.mod h1:8tu/lYfQfFe6IGnaOdrpVgEL2IrrDOf6/m9RQum4NkY=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.3 h1:dT3MqvGhSoaIhRseqw2I0yH81l7wiR2vjs57O51EAm8=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.3/go.mod h1:GlAeCkHwugxdHaueRr4nhPuY+WW+gR8UjlcqzPr1SPI=
-github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.16 h1:QX1NdlZt2GmEAi76bYp0X1SSCOWz9twCzm+uffc4EkU=
-github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.16/go.mod h1:VW61XQxhMsEQi1JVKl8tSPDZ6AuBhVjEwFJlU85DaIs=
-github.com/aws/aws-sdk-go-v2/service/lambda v1.56.2 h1:PNNyvM6PwCfn98+LJJWlKTsUcvGvX/saIXIBdDvlgqg=
-github.com/aws/aws-sdk-go-v2/service/lambda v1.56.2/go.mod h1:L/l6dG7bG+iZQriop5Zmwd2waxzq9r3ykVr08+efWBg=
-github.com/aws/aws-sdk-go-v2/service/sso v1.22.2 h1:z6ybwREfmhRqn5iIUMrmqoqOpp4zrTriKsq1x5oHSQw=
-github.com/aws/aws-sdk-go-v2/service/sso v1.22.2/go.mod h1:+MgTXxlIwb3HsMEFB9bkNDLDh3eo2f6vXY9X9D6DTCM=
-github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.3 h1:Yh29Mmg27xR8vMxibWF9JWrI4L5Go5zxOtdk7PzcrqM=
-github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.3/go.mod h1:yJ6Iqas4iFzb83n+pPw5dABjwKj1DEf19qWGEKXICao=
-github.com/aws/aws-sdk-go-v2/service/sts v1.30.2 h1:CbmNoc0P72e9zwBLJBvBDb3vKJXKNehcfNmkRjAkts8=
-github.com/aws/aws-sdk-go-v2/service/sts v1.30.2/go.mod h1:2ocYJoYQZN8ad4+E8ut5xE9J2ld9wrMvmpADRYw4rs4=
+github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.17 h1:HGErhhrxZlQ044RiM+WdoZxp0p+EGM62y3L6pwA4olE=
+github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.17/go.mod h1:RkZEx4l0EHYDJpWppMJ3nD9wZJAa8/0lq9aVC+r2UII=
+github.com/aws/aws-sdk-go-v2/service/lambda v1.56.3 h1:r/y4nQOln25cbjrD8Wmzhhvnvr2ObPjgcPvPdoU9yHs=
+github.com/aws/aws-sdk-go-v2/service/lambda v1.56.3/go.mod h1:/4Vaddp+wJc1AA8ViAqwWKAcYykPV+ZplhmLQuq3RbQ=
+github.com/aws/aws-sdk-go-v2/service/sso v1.22.4 h1:BXx0ZIxvrJdSgSvKTZ+yRBeSqqgPM89VPlulEcl37tM=
+github.com/aws/aws-sdk-go-v2/service/sso v1.22.4/go.mod h1:ooyCOXjvJEsUw7x+ZDHeISPMhtwI3ZCB7ggFMcFfWLU=
+github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.4 h1:yiwVzJW2ZxZTurVbYWA7QOrAaCYQR72t0wrSBfoesUE=
+github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.4/go.mod h1:0oxfLkpz3rQ/CHlx5hB7H69YUpFiI1tql6Q6Ne+1bCw=
+github.com/aws/aws-sdk-go-v2/service/sts v1.30.3 h1:ZsDKRLXGWHk8WdtyYMoGNO7bTudrvuKpDKgMVRlepGE=
+github.com/aws/aws-sdk-go-v2/service/sts v1.30.3/go.mod h1:zwySh8fpFyXp9yOr/KVzxOl8SRqgf/IDw5aUt9UKFcQ=
github.com/aws/smithy-go v1.20.3 h1:ryHwveWzPV5BIof6fyDvor6V3iUL7nTfiTKXHiW05nE=
github.com/aws/smithy-go v1.20.3/go.mod h1:krry+ya/rV9RDcV/Q16kpu6ypI4K2czasz0NC3qS14E=
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
diff --git a/mkdocs.yml b/mkdocs.yml
index d2bab86cd22..988f7dc5f06 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -147,3 +147,13 @@ extra:
version:
provider: mike
default: latest
+ social:
+ - icon: fontawesome/brands/discord
+ link: https://discord.gg/B8zZKbbyET
+ name: Discord Server for Powertools for AWS
+ - icon: material/web
+ link: https://powertools.aws.dev/
+ name: Official website for Powertools for AWS
+ - icon: simple/python
+ link: https://pypi.org/project/aws-lambda-powertools/
+ name: PyPi package
diff --git a/noxfile.py b/noxfile.py
index 7023f45a2b7..4e53bcb816a 100644
--- a/noxfile.py
+++ b/noxfile.py
@@ -163,6 +163,7 @@ def test_with_pydantic_required_package(session: nox.Session, pydantic: str):
f"{PREFIX_TESTS_FUNCTIONAL}/event_handler/_pydantic/",
f"{PREFIX_TESTS_FUNCTIONAL}/batch/_pydantic/",
f"{PREFIX_TESTS_UNIT}/parser/_pydantic/",
+ f"{PREFIX_TESTS_UNIT}/event_handler/_pydantic/",
],
)
diff --git a/package-lock.json b/package-lock.json
index 39562268246..5e12259c487 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -11,13 +11,13 @@
"package-lock.json": "^1.0.0"
},
"devDependencies": {
- "aws-cdk": "^2.145.0"
+ "aws-cdk": "^2.150.0"
}
},
"node_modules/aws-cdk": {
- "version": "2.148.0",
- "resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.148.0.tgz",
- "integrity": "sha512-nuCWY8I0xkIz7B2LjIL4h/xLHWTFhINL8i2fdA0BPq8t0byhLaNw5wggvztDjWH5xq5I1DM3laiv4/q5S21Xrg==",
+ "version": "2.150.0",
+ "resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.150.0.tgz",
+ "integrity": "sha512-leo4J70QrJp+SYm/87VuoOVfALsW11F7JpkAGu5TLL/qd2k/CbovZ8k9/3Ov+jCVsvAgdn9DeHL01Sn6hSl6Zg==",
"dev": true,
"bin": {
"cdk": "bin/cdk"
diff --git a/package.json b/package.json
index 68e7c458f59..9b4a2b9a92f 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.145.0"
+ "aws-cdk": "^2.150.0"
},
"dependencies": {
"package-lock.json": "^1.0.0"
diff --git a/poetry.lock b/poetry.lock
index 75c8060f8ff..d6412b14dc7 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -190,17 +190,17 @@ typeguard = ">=2.13.3,<2.14.0"
[[package]]
name = "aws-cdk-aws-lambda-python-alpha"
-version = "2.148.1a0"
+version = "2.150.0a0"
description = "The CDK Construct Library for AWS Lambda in Python"
optional = false
python-versions = "~=3.8"
files = [
- {file = "aws-cdk.aws-lambda-python-alpha-2.148.1a0.tar.gz", hash = "sha256:601dd765314bf62c8e77568e6b11a9a6700a11adbbce87307e7aa55e5fdb7cb3"},
- {file = "aws_cdk.aws_lambda_python_alpha-2.148.1a0-py3-none-any.whl", hash = "sha256:ad5d08b85d72258c6d725d1cad239ba4e6e17df82226d2bfa5b8c1fc0507f472"},
+ {file = "aws-cdk.aws-lambda-python-alpha-2.150.0a0.tar.gz", hash = "sha256:a40b379f5eff871552cb4ffd7ff69cfac174d7add9e69dae99292f7e93543a55"},
+ {file = "aws_cdk.aws_lambda_python_alpha-2.150.0a0-py3-none-any.whl", hash = "sha256:b358b12037a279e75cf637477117ff4e7978511bcfd74b533a14c35f55982166"},
]
[package.dependencies]
-aws-cdk-lib = ">=2.148.1,<3.0.0"
+aws-cdk-lib = ">=2.150.0,<3.0.0"
constructs = ">=10.0.0,<11.0.0"
jsii = ">=1.101.0,<2.0.0"
publication = ">=0.0.3"
@@ -208,13 +208,13 @@ typeguard = ">=2.13.3,<2.14.0"
[[package]]
name = "aws-cdk-lib"
-version = "2.148.1"
+version = "2.150.0"
description = "Version 2 of the AWS Cloud Development Kit library"
optional = false
python-versions = "~=3.8"
files = [
- {file = "aws-cdk-lib-2.148.1.tar.gz", hash = "sha256:a9d35bc511e5d241a610e337ca9f4af00b49b57fee1e1b871e9a22eeb220e289"},
- {file = "aws_cdk_lib-2.148.1-py3-none-any.whl", hash = "sha256:7168e206c9dcacdfcdb0898aa2f49dd1a5add130b852e331a7808b011e116e81"},
+ {file = "aws-cdk-lib-2.150.0.tar.gz", hash = "sha256:dbf8043a84c07297bd6d7158a597ed2b4be76605e7734c7c5b8e38697bdfa138"},
+ {file = "aws_cdk_lib-2.150.0-py3-none-any.whl", hash = "sha256:300641052119988a4bdab9649c58199f45acb0ec9dfba65691075c3bc5dcad07"},
]
[package.dependencies]
@@ -461,13 +461,13 @@ ujson = ["ujson (>=5.7.0)"]
[[package]]
name = "cdk-nag"
-version = "2.28.159"
+version = "2.28.164"
description = "Check CDK v2 applications for best practices using a combination on available rule packs."
optional = false
python-versions = "~=3.8"
files = [
- {file = "cdk-nag-2.28.159.tar.gz", hash = "sha256:5c6c96698000bb19a244cb85cca6d8e83939b8e19f3cfd6613912cc4b2887ec7"},
- {file = "cdk_nag-2.28.159-py3-none-any.whl", hash = "sha256:c534a65ffc41566bc14798990fb306e04fa103f08b3ad3ab6f9d502fe1ff0235"},
+ {file = "cdk-nag-2.28.164.tar.gz", hash = "sha256:90c4987d303afbe4e2763931dde271f876ae429c72acdbfe33db0deda019a629"},
+ {file = "cdk_nag-2.28.164-py3-none-any.whl", hash = "sha256:09aa5c7154922e9eeb472ad7b2d49915495603f8b4baede857ce371b7059b4d3"},
]
[package.dependencies]
@@ -479,18 +479,18 @@ typeguard = ">=2.13.3,<2.14.0"
[[package]]
name = "cdklabs-generative-ai-cdk-constructs"
-version = "0.1.206"
+version = "0.1.219"
description = "AWS Generative AI CDK Constructs is a library for well-architected generative AI patterns."
optional = false
python-versions = "~=3.8"
files = [
- {file = "cdklabs.generative-ai-cdk-constructs-0.1.206.tar.gz", hash = "sha256:dbddc7a3f0de82422d2cfaef6bacf51f70b5ce9eac432472ce00ffc3ad7247ab"},
- {file = "cdklabs.generative_ai_cdk_constructs-0.1.206-py3-none-any.whl", hash = "sha256:6138df615aa760251e865fc1e6dbe96de9ef07370e22593ba8463ec9814eef04"},
+ {file = "cdklabs.generative-ai-cdk-constructs-0.1.219.tar.gz", hash = "sha256:c0b46e1f99fe9ecd39284956036ae1cd188774f350d66aa0cf7bb930478b01f8"},
+ {file = "cdklabs.generative_ai_cdk_constructs-0.1.219-py3-none-any.whl", hash = "sha256:7f5c138893c65a12b91b8199fe6e0ec5a513d2aded7bfd00dd074d5085ad0647"},
]
[package.dependencies]
-aws-cdk-lib = ">=2.143.0,<3.0.0"
-cdk-nag = ">=2.28.157,<3.0.0"
+aws-cdk-lib = ">=2.149.0,<3.0.0"
+cdk-nag = ">=2.28.163,<3.0.0"
constructs = ">=10.3.0,<11.0.0"
jsii = ">=1.101.0,<2.0.0"
publication = ">=0.0.3"
@@ -573,13 +573,13 @@ pycparser = "*"
[[package]]
name = "cfn-lint"
-version = "1.5.3"
+version = "1.8.2"
description = "Checks CloudFormation templates for practices and behaviour that could potentially be improved"
optional = false
python-versions = ">=3.8"
files = [
- {file = "cfn_lint-1.5.3-py3-none-any.whl", hash = "sha256:9376bc1e7b09fc40ad74a312e23ac0ff81ea041581e7f50f200bf1218fa533b2"},
- {file = "cfn_lint-1.5.3.tar.gz", hash = "sha256:1120546d068d5ad3b2f1a646f0a26f797d4eb273583dba7c13e335ee1a2332ed"},
+ {file = "cfn_lint-1.8.2-py3-none-any.whl", hash = "sha256:5bab320be08d8906f2535cae851e96ac35b046a2da54de1d9e28d8bbac7d34d9"},
+ {file = "cfn_lint-1.8.2.tar.gz", hash = "sha256:8c3f8a6946d7829c4e4858bb646c15b0b20c8290c2200c69d9e008b5e5bcadaf"},
]
[package.dependencies]
@@ -767,63 +767,63 @@ typeguard = ">=2.13.3,<2.14.0"
[[package]]
name = "coverage"
-version = "7.5.4"
+version = "7.6.0"
description = "Code coverage measurement for Python"
optional = false
python-versions = ">=3.8"
files = [
- {file = "coverage-7.5.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6cfb5a4f556bb51aba274588200a46e4dd6b505fb1a5f8c5ae408222eb416f99"},
- {file = "coverage-7.5.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2174e7c23e0a454ffe12267a10732c273243b4f2d50d07544a91198f05c48f47"},
- {file = "coverage-7.5.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2214ee920787d85db1b6a0bd9da5f8503ccc8fcd5814d90796c2f2493a2f4d2e"},
- {file = "coverage-7.5.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1137f46adb28e3813dec8c01fefadcb8c614f33576f672962e323b5128d9a68d"},
- {file = "coverage-7.5.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b385d49609f8e9efc885790a5a0e89f2e3ae042cdf12958b6034cc442de428d3"},
- {file = "coverage-7.5.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b4a474f799456e0eb46d78ab07303286a84a3140e9700b9e154cfebc8f527016"},
- {file = "coverage-7.5.4-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:5cd64adedf3be66f8ccee418473c2916492d53cbafbfcff851cbec5a8454b136"},
- {file = "coverage-7.5.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e564c2cf45d2f44a9da56f4e3a26b2236504a496eb4cb0ca7221cd4cc7a9aca9"},
- {file = "coverage-7.5.4-cp310-cp310-win32.whl", hash = "sha256:7076b4b3a5f6d2b5d7f1185fde25b1e54eb66e647a1dfef0e2c2bfaf9b4c88c8"},
- {file = "coverage-7.5.4-cp310-cp310-win_amd64.whl", hash = "sha256:018a12985185038a5b2bcafab04ab833a9a0f2c59995b3cec07e10074c78635f"},
- {file = "coverage-7.5.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:db14f552ac38f10758ad14dd7b983dbab424e731588d300c7db25b6f89e335b5"},
- {file = "coverage-7.5.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3257fdd8e574805f27bb5342b77bc65578e98cbc004a92232106344053f319ba"},
- {file = "coverage-7.5.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3a6612c99081d8d6134005b1354191e103ec9705d7ba2754e848211ac8cacc6b"},
- {file = "coverage-7.5.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d45d3cbd94159c468b9b8c5a556e3f6b81a8d1af2a92b77320e887c3e7a5d080"},
- {file = "coverage-7.5.4-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ed550e7442f278af76d9d65af48069f1fb84c9f745ae249c1a183c1e9d1b025c"},
- {file = "coverage-7.5.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7a892be37ca35eb5019ec85402c3371b0f7cda5ab5056023a7f13da0961e60da"},
- {file = "coverage-7.5.4-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8192794d120167e2a64721d88dbd688584675e86e15d0569599257566dec9bf0"},
- {file = "coverage-7.5.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:820bc841faa502e727a48311948e0461132a9c8baa42f6b2b84a29ced24cc078"},
- {file = "coverage-7.5.4-cp311-cp311-win32.whl", hash = "sha256:6aae5cce399a0f065da65c7bb1e8abd5c7a3043da9dceb429ebe1b289bc07806"},
- {file = "coverage-7.5.4-cp311-cp311-win_amd64.whl", hash = "sha256:d2e344d6adc8ef81c5a233d3a57b3c7d5181f40e79e05e1c143da143ccb6377d"},
- {file = "coverage-7.5.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:54317c2b806354cbb2dc7ac27e2b93f97096912cc16b18289c5d4e44fc663233"},
- {file = "coverage-7.5.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:042183de01f8b6d531e10c197f7f0315a61e8d805ab29c5f7b51a01d62782747"},
- {file = "coverage-7.5.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a6bb74ed465d5fb204b2ec41d79bcd28afccf817de721e8a807d5141c3426638"},
- {file = "coverage-7.5.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b3d45ff86efb129c599a3b287ae2e44c1e281ae0f9a9bad0edc202179bcc3a2e"},
- {file = "coverage-7.5.4-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5013ed890dc917cef2c9f765c4c6a8ae9df983cd60dbb635df8ed9f4ebc9f555"},
- {file = "coverage-7.5.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1014fbf665fef86cdfd6cb5b7371496ce35e4d2a00cda501cf9f5b9e6fced69f"},
- {file = "coverage-7.5.4-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:3684bc2ff328f935981847082ba4fdc950d58906a40eafa93510d1b54c08a66c"},
- {file = "coverage-7.5.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:581ea96f92bf71a5ec0974001f900db495488434a6928a2ca7f01eee20c23805"},
- {file = "coverage-7.5.4-cp312-cp312-win32.whl", hash = "sha256:73ca8fbc5bc622e54627314c1a6f1dfdd8db69788f3443e752c215f29fa87a0b"},
- {file = "coverage-7.5.4-cp312-cp312-win_amd64.whl", hash = "sha256:cef4649ec906ea7ea5e9e796e68b987f83fa9a718514fe147f538cfeda76d7a7"},
- {file = "coverage-7.5.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:cdd31315fc20868c194130de9ee6bfd99755cc9565edff98ecc12585b90be882"},
- {file = "coverage-7.5.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:02ff6e898197cc1e9fa375581382b72498eb2e6d5fc0b53f03e496cfee3fac6d"},
- {file = "coverage-7.5.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d05c16cf4b4c2fc880cb12ba4c9b526e9e5d5bb1d81313d4d732a5b9fe2b9d53"},
- {file = "coverage-7.5.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c5986ee7ea0795a4095ac4d113cbb3448601efca7f158ec7f7087a6c705304e4"},
- {file = "coverage-7.5.4-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5df54843b88901fdc2f598ac06737f03d71168fd1175728054c8f5a2739ac3e4"},
- {file = "coverage-7.5.4-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:ab73b35e8d109bffbda9a3e91c64e29fe26e03e49addf5b43d85fc426dde11f9"},
- {file = "coverage-7.5.4-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:aea072a941b033813f5e4814541fc265a5c12ed9720daef11ca516aeacd3bd7f"},
- {file = "coverage-7.5.4-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:16852febd96acd953b0d55fc842ce2dac1710f26729b31c80b940b9afcd9896f"},
- {file = "coverage-7.5.4-cp38-cp38-win32.whl", hash = "sha256:8f894208794b164e6bd4bba61fc98bf6b06be4d390cf2daacfa6eca0a6d2bb4f"},
- {file = "coverage-7.5.4-cp38-cp38-win_amd64.whl", hash = "sha256:e2afe743289273209c992075a5a4913e8d007d569a406ffed0bd080ea02b0633"},
- {file = "coverage-7.5.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b95c3a8cb0463ba9f77383d0fa8c9194cf91f64445a63fc26fb2327e1e1eb088"},
- {file = "coverage-7.5.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3d7564cc09dd91b5a6001754a5b3c6ecc4aba6323baf33a12bd751036c998be4"},
- {file = "coverage-7.5.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:44da56a2589b684813f86d07597fdf8a9c6ce77f58976727329272f5a01f99f7"},
- {file = "coverage-7.5.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e16f3d6b491c48c5ae726308e6ab1e18ee830b4cdd6913f2d7f77354b33f91c8"},
- {file = "coverage-7.5.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dbc5958cb471e5a5af41b0ddaea96a37e74ed289535e8deca404811f6cb0bc3d"},
- {file = "coverage-7.5.4-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:a04e990a2a41740b02d6182b498ee9796cf60eefe40cf859b016650147908029"},
- {file = "coverage-7.5.4-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:ddbd2f9713a79e8e7242d7c51f1929611e991d855f414ca9996c20e44a895f7c"},
- {file = "coverage-7.5.4-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:b1ccf5e728ccf83acd313c89f07c22d70d6c375a9c6f339233dcf792094bcbf7"},
- {file = "coverage-7.5.4-cp39-cp39-win32.whl", hash = "sha256:56b4eafa21c6c175b3ede004ca12c653a88b6f922494b023aeb1e836df953ace"},
- {file = "coverage-7.5.4-cp39-cp39-win_amd64.whl", hash = "sha256:65e528e2e921ba8fd67d9055e6b9f9e34b21ebd6768ae1c1723f4ea6ace1234d"},
- {file = "coverage-7.5.4-pp38.pp39.pp310-none-any.whl", hash = "sha256:79b356f3dd5b26f3ad23b35c75dbdaf1f9e2450b6bcefc6d0825ea0aa3f86ca5"},
- {file = "coverage-7.5.4.tar.gz", hash = "sha256:a44963520b069e12789d0faea4e9fdb1e410cdc4aab89d94f7f55cbb7fef0353"},
+ {file = "coverage-7.6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:dff044f661f59dace805eedb4a7404c573b6ff0cdba4a524141bc63d7be5c7fd"},
+ {file = "coverage-7.6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a8659fd33ee9e6ca03950cfdcdf271d645cf681609153f218826dd9805ab585c"},
+ {file = "coverage-7.6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7792f0ab20df8071d669d929c75c97fecfa6bcab82c10ee4adb91c7a54055463"},
+ {file = "coverage-7.6.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d4b3cd1ca7cd73d229487fa5caca9e4bc1f0bca96526b922d61053ea751fe791"},
+ {file = "coverage-7.6.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e7e128f85c0b419907d1f38e616c4f1e9f1d1b37a7949f44df9a73d5da5cd53c"},
+ {file = "coverage-7.6.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a94925102c89247530ae1dab7dc02c690942566f22e189cbd53579b0693c0783"},
+ {file = "coverage-7.6.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:dcd070b5b585b50e6617e8972f3fbbee786afca71b1936ac06257f7e178f00f6"},
+ {file = "coverage-7.6.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:d50a252b23b9b4dfeefc1f663c568a221092cbaded20a05a11665d0dbec9b8fb"},
+ {file = "coverage-7.6.0-cp310-cp310-win32.whl", hash = "sha256:0e7b27d04131c46e6894f23a4ae186a6a2207209a05df5b6ad4caee6d54a222c"},
+ {file = "coverage-7.6.0-cp310-cp310-win_amd64.whl", hash = "sha256:54dece71673b3187c86226c3ca793c5f891f9fc3d8aa183f2e3653da18566169"},
+ {file = "coverage-7.6.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c7b525ab52ce18c57ae232ba6f7010297a87ced82a2383b1afd238849c1ff933"},
+ {file = "coverage-7.6.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4bea27c4269234e06f621f3fac3925f56ff34bc14521484b8f66a580aacc2e7d"},
+ {file = "coverage-7.6.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ed8d1d1821ba5fc88d4a4f45387b65de52382fa3ef1f0115a4f7a20cdfab0e94"},
+ {file = "coverage-7.6.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:01c322ef2bbe15057bc4bf132b525b7e3f7206f071799eb8aa6ad1940bcf5fb1"},
+ {file = "coverage-7.6.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:03cafe82c1b32b770a29fd6de923625ccac3185a54a5e66606da26d105f37dac"},
+ {file = "coverage-7.6.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0d1b923fc4a40c5832be4f35a5dab0e5ff89cddf83bb4174499e02ea089daf57"},
+ {file = "coverage-7.6.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:4b03741e70fb811d1a9a1d75355cf391f274ed85847f4b78e35459899f57af4d"},
+ {file = "coverage-7.6.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a73d18625f6a8a1cbb11eadc1d03929f9510f4131879288e3f7922097a429f63"},
+ {file = "coverage-7.6.0-cp311-cp311-win32.whl", hash = "sha256:65fa405b837060db569a61ec368b74688f429b32fa47a8929a7a2f9b47183713"},
+ {file = "coverage-7.6.0-cp311-cp311-win_amd64.whl", hash = "sha256:6379688fb4cfa921ae349c76eb1a9ab26b65f32b03d46bb0eed841fd4cb6afb1"},
+ {file = "coverage-7.6.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f7db0b6ae1f96ae41afe626095149ecd1b212b424626175a6633c2999eaad45b"},
+ {file = "coverage-7.6.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:bbdf9a72403110a3bdae77948b8011f644571311c2fb35ee15f0f10a8fc082e8"},
+ {file = "coverage-7.6.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cc44bf0315268e253bf563f3560e6c004efe38f76db03a1558274a6e04bf5d5"},
+ {file = "coverage-7.6.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:da8549d17489cd52f85a9829d0e1d91059359b3c54a26f28bec2c5d369524807"},
+ {file = "coverage-7.6.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0086cd4fc71b7d485ac93ca4239c8f75732c2ae3ba83f6be1c9be59d9e2c6382"},
+ {file = "coverage-7.6.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1fad32ee9b27350687035cb5fdf9145bc9cf0a094a9577d43e909948ebcfa27b"},
+ {file = "coverage-7.6.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:044a0985a4f25b335882b0966625270a8d9db3d3409ddc49a4eb00b0ef5e8cee"},
+ {file = "coverage-7.6.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:76d5f82213aa78098b9b964ea89de4617e70e0d43e97900c2778a50856dac605"},
+ {file = "coverage-7.6.0-cp312-cp312-win32.whl", hash = "sha256:3c59105f8d58ce500f348c5b56163a4113a440dad6daa2294b5052a10db866da"},
+ {file = "coverage-7.6.0-cp312-cp312-win_amd64.whl", hash = "sha256:ca5d79cfdae420a1d52bf177de4bc2289c321d6c961ae321503b2ca59c17ae67"},
+ {file = "coverage-7.6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d39bd10f0ae453554798b125d2f39884290c480f56e8a02ba7a6ed552005243b"},
+ {file = "coverage-7.6.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:beb08e8508e53a568811016e59f3234d29c2583f6b6e28572f0954a6b4f7e03d"},
+ {file = "coverage-7.6.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b2e16f4cd2bc4d88ba30ca2d3bbf2f21f00f382cf4e1ce3b1ddc96c634bc48ca"},
+ {file = "coverage-7.6.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6616d1c9bf1e3faea78711ee42a8b972367d82ceae233ec0ac61cc7fec09fa6b"},
+ {file = "coverage-7.6.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad4567d6c334c46046d1c4c20024de2a1c3abc626817ae21ae3da600f5779b44"},
+ {file = "coverage-7.6.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:d17c6a415d68cfe1091d3296ba5749d3d8696e42c37fca5d4860c5bf7b729f03"},
+ {file = "coverage-7.6.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:9146579352d7b5f6412735d0f203bbd8d00113a680b66565e205bc605ef81bc6"},
+ {file = "coverage-7.6.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:cdab02a0a941af190df8782aafc591ef3ad08824f97850b015c8c6a8b3877b0b"},
+ {file = "coverage-7.6.0-cp38-cp38-win32.whl", hash = "sha256:df423f351b162a702c053d5dddc0fc0ef9a9e27ea3f449781ace5f906b664428"},
+ {file = "coverage-7.6.0-cp38-cp38-win_amd64.whl", hash = "sha256:f2501d60d7497fd55e391f423f965bbe9e650e9ffc3c627d5f0ac516026000b8"},
+ {file = "coverage-7.6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7221f9ac9dad9492cecab6f676b3eaf9185141539d5c9689d13fd6b0d7de840c"},
+ {file = "coverage-7.6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ddaaa91bfc4477d2871442bbf30a125e8fe6b05da8a0015507bfbf4718228ab2"},
+ {file = "coverage-7.6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c4cbe651f3904e28f3a55d6f371203049034b4ddbce65a54527a3f189ca3b390"},
+ {file = "coverage-7.6.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:831b476d79408ab6ccfadaaf199906c833f02fdb32c9ab907b1d4aa0713cfa3b"},
+ {file = "coverage-7.6.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:46c3d091059ad0b9c59d1034de74a7f36dcfa7f6d3bde782c49deb42438f2450"},
+ {file = "coverage-7.6.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:4d5fae0a22dc86259dee66f2cc6c1d3e490c4a1214d7daa2a93d07491c5c04b6"},
+ {file = "coverage-7.6.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:07ed352205574aad067482e53dd606926afebcb5590653121063fbf4e2175166"},
+ {file = "coverage-7.6.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:49c76cdfa13015c4560702574bad67f0e15ca5a2872c6a125f6327ead2b731dd"},
+ {file = "coverage-7.6.0-cp39-cp39-win32.whl", hash = "sha256:482855914928c8175735a2a59c8dc5806cf7d8f032e4820d52e845d1f731dca2"},
+ {file = "coverage-7.6.0-cp39-cp39-win_amd64.whl", hash = "sha256:543ef9179bc55edfd895154a51792b01c017c87af0ebaae092720152e19e42ca"},
+ {file = "coverage-7.6.0-pp38.pp39.pp310-none-any.whl", hash = "sha256:6fe885135c8a479d3e37a7aae61cbd3a0fb2deccb4dda3c25f92a49189f766d6"},
+ {file = "coverage-7.6.0.tar.gz", hash = "sha256:289cc803fa1dc901f84701ac10c9ee873619320f2f9aff38794db4a4a0268d51"},
]
[package.dependencies]
@@ -902,18 +902,18 @@ requests = ">=2.6.0"
[[package]]
name = "datadog-lambda"
-version = "6.96.0"
+version = "6.97.0"
description = "The Datadog AWS Lambda Library"
optional = false
python-versions = "<4,>=3.8.0"
files = [
- {file = "datadog_lambda-6.96.0-py3-none-any.whl", hash = "sha256:3cfac9c113bf455f539ec32f4fbbf3573b63a9d3ac45d7bc679407fddb810846"},
- {file = "datadog_lambda-6.96.0.tar.gz", hash = "sha256:35d68d78c04cb3e8f9a5f07456f876cc0e2f0f05a7e0906d3a3eafd208e890e7"},
+ {file = "datadog_lambda-6.97.0-py3-none-any.whl", hash = "sha256:ad48a69961f874b52c25d0f6d9df186f4ab5ce44368d36ad29486100e855e2b1"},
+ {file = "datadog_lambda-6.97.0.tar.gz", hash = "sha256:71da41d4cf62205cf757b4270ff5af6172a3460542e7236596dca6ddcd2fcbba"},
]
[package.dependencies]
datadog = ">=0.41.0,<1.0.0"
-ddtrace = ">=2.9.0"
+ddtrace = ">=2.9.2"
ujson = ">=5.9.0"
urllib3 = [
{version = "<2.0.0", markers = "python_version < \"3.11\""},
@@ -1875,13 +1875,13 @@ mkdocs = ">=0.17"
[[package]]
name = "mkdocs-material"
-version = "9.5.28"
+version = "9.5.30"
description = "Documentation that simply works"
optional = false
python-versions = ">=3.8"
files = [
- {file = "mkdocs_material-9.5.28-py3-none-any.whl", hash = "sha256:ff48b11b2a9f705dd210409ec3b418ab443dd36d96915bcba45a41f10ea27bfd"},
- {file = "mkdocs_material-9.5.28.tar.gz", hash = "sha256:9cba305283ad1600e3d0a67abe72d7a058b54793b47be39930911a588fe0336b"},
+ {file = "mkdocs_material-9.5.30-py3-none-any.whl", hash = "sha256:fc070689c5250a180e9b9d79d8491ef9a3a7acb240db0728728d6c31eeb131d4"},
+ {file = "mkdocs_material-9.5.30.tar.gz", hash = "sha256:3fd417dd42d679e3ba08b9e2d72cd8b8af142cc4a3969676ad6b00993dd182ec"},
]
[package.dependencies]
@@ -2059,13 +2059,13 @@ typing-extensions = {version = ">=4.1.0", markers = "python_version < \"3.12\""}
[[package]]
name = "mypy-boto3-dynamodb"
-version = "1.34.131"
-description = "Type annotations for boto3.DynamoDB 1.34.131 service generated with mypy-boto3-builder 7.24.0"
+version = "1.34.148"
+description = "Type annotations for boto3.DynamoDB 1.34.148 service generated with mypy-boto3-builder 7.25.0"
optional = false
python-versions = ">=3.8"
files = [
- {file = "mypy_boto3_dynamodb-1.34.131-py3-none-any.whl", hash = "sha256:62e4fd85c621561f145828de14752a51380182d492cb039043d7f46bef872c34"},
- {file = "mypy_boto3_dynamodb-1.34.131.tar.gz", hash = "sha256:d23c857568ae7c8c8fc1fbd96709a1dd00c140f917d74e09423fd44677097abf"},
+ {file = "mypy_boto3_dynamodb-1.34.148-py3-none-any.whl", hash = "sha256:f1a7aabff5c6e926b9b272df87251c9d6dfceb4c1fb159fb5a2df52062cd7e87"},
+ {file = "mypy_boto3_dynamodb-1.34.148.tar.gz", hash = "sha256:c85489b92cbbbe4f6997070372022df914d4cb8eb707fdc73aa18ce6ba25c578"},
]
[package.dependencies]
@@ -2115,13 +2115,13 @@ typing-extensions = {version = ">=4.1.0", markers = "python_version < \"3.12\""}
[[package]]
name = "mypy-boto3-secretsmanager"
-version = "1.34.128"
-description = "Type annotations for boto3.SecretsManager 1.34.128 service generated with mypy-boto3-builder 7.24.0"
+version = "1.34.145"
+description = "Type annotations for boto3.SecretsManager 1.34.145 service generated with mypy-boto3-builder 7.25.0"
optional = false
python-versions = ">=3.8"
files = [
- {file = "mypy_boto3_secretsmanager-1.34.128-py3-none-any.whl", hash = "sha256:7ce9815d116fa1749971691355b1e1c8f462d46e7eaa9d84133b8db96dd3515f"},
- {file = "mypy_boto3_secretsmanager-1.34.128.tar.gz", hash = "sha256:ae2b398efa1a32214c3eddb6901efa67cfc24a893b113f549a06bb70bb43b402"},
+ {file = "mypy_boto3_secretsmanager-1.34.145-py3-none-any.whl", hash = "sha256:986511caa6626edfed7eb11b63c929801e9468c58e15927dc6fc0339c4eb34cb"},
+ {file = "mypy_boto3_secretsmanager-1.34.145.tar.gz", hash = "sha256:e5a82c05cce68168a3709e5f0d35066cf250961db1d8670f0111da66206814c7"},
]
[package.dependencies]
@@ -2491,13 +2491,13 @@ diagrams = ["jinja2", "railroad-diagrams"]
[[package]]
name = "pytest"
-version = "8.2.2"
+version = "8.3.1"
description = "pytest: simple powerful testing with Python"
optional = false
python-versions = ">=3.8"
files = [
- {file = "pytest-8.2.2-py3-none-any.whl", hash = "sha256:c434598117762e2bd304e526244f67bf66bbd7b5d6cf22138be51ff661980343"},
- {file = "pytest-8.2.2.tar.gz", hash = "sha256:de4bb8104e201939ccdc688b27a89a7be2079b22e2bd2b07f806b6ba71117977"},
+ {file = "pytest-8.3.1-py3-none-any.whl", hash = "sha256:e9600ccf4f563976e2c99fa02c7624ab938296551f280835ee6516df8bc4ae8c"},
+ {file = "pytest-8.3.1.tar.gz", hash = "sha256:7e8e5c5abd6e93cb1cc151f23e57adc31fcf8cfd2a3ff2da63e23f732de35db6"},
]
[package.dependencies]
@@ -2505,7 +2505,7 @@ colorama = {version = "*", markers = "sys_platform == \"win32\""}
exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""}
iniconfig = "*"
packaging = "*"
-pluggy = ">=1.5,<2.0"
+pluggy = ">=1.5,<2"
tomli = {version = ">=1", markers = "python_version < \"3.11\""}
[package.extras]
@@ -2513,13 +2513,13 @@ dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments
[[package]]
name = "pytest-asyncio"
-version = "0.23.7"
+version = "0.23.8"
description = "Pytest support for asyncio"
optional = false
python-versions = ">=3.8"
files = [
- {file = "pytest_asyncio-0.23.7-py3-none-any.whl", hash = "sha256:009b48127fbe44518a547bddd25611551b0e43ccdbf1e67d12479f569832c20b"},
- {file = "pytest_asyncio-0.23.7.tar.gz", hash = "sha256:5f5c72948f4c49e7db4f29f2521d4031f1c27f86e57b046126654083d4770268"},
+ {file = "pytest_asyncio-0.23.8-py3-none-any.whl", hash = "sha256:50265d892689a5faefb84df80819d1ecef566eb3549cf915dfb33569359d1ce2"},
+ {file = "pytest_asyncio-0.23.8.tar.gz", hash = "sha256:759b10b33a6dc61cce40a8bd5205e302978bbbcc00e279a8b61d9a6a3c82e4d3"},
]
[package.dependencies]
@@ -3042,29 +3042,29 @@ files = [
[[package]]
name = "ruff"
-version = "0.5.1"
+version = "0.5.4"
description = "An extremely fast Python linter and code formatter, written in Rust."
optional = false
python-versions = ">=3.7"
files = [
- {file = "ruff-0.5.1-py3-none-linux_armv6l.whl", hash = "sha256:6ecf968fcf94d942d42b700af18ede94b07521bd188aaf2cd7bc898dd8cb63b6"},
- {file = "ruff-0.5.1-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:204fb0a472f00f2e6280a7c8c7c066e11e20e23a37557d63045bf27a616ba61c"},
- {file = "ruff-0.5.1-py3-none-macosx_11_0_arm64.whl", hash = "sha256:d235968460e8758d1e1297e1de59a38d94102f60cafb4d5382033c324404ee9d"},
- {file = "ruff-0.5.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:38beace10b8d5f9b6bdc91619310af6d63dd2019f3fb2d17a2da26360d7962fa"},
- {file = "ruff-0.5.1-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5e478d2f09cf06add143cf8c4540ef77b6599191e0c50ed976582f06e588c994"},
- {file = "ruff-0.5.1-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f0368d765eec8247b8550251c49ebb20554cc4e812f383ff9f5bf0d5d94190b0"},
- {file = "ruff-0.5.1-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:3a9a9a1b582e37669b0138b7c1d9d60b9edac880b80eb2baba6d0e566bdeca4d"},
- {file = "ruff-0.5.1-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bdd9f723e16003623423affabcc0a807a66552ee6a29f90eddad87a40c750b78"},
- {file = "ruff-0.5.1-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:be9fd62c1e99539da05fcdc1e90d20f74aec1b7a1613463ed77870057cd6bd96"},
- {file = "ruff-0.5.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e216fc75a80ea1fbd96af94a6233d90190d5b65cc3d5dfacf2bd48c3e067d3e1"},
- {file = "ruff-0.5.1-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:c4c2112e9883a40967827d5c24803525145e7dab315497fae149764979ac7929"},
- {file = "ruff-0.5.1-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:dfaf11c8a116394da3b65cd4b36de30d8552fa45b8119b9ef5ca6638ab964fa3"},
- {file = "ruff-0.5.1-py3-none-musllinux_1_2_i686.whl", hash = "sha256:d7ceb9b2fe700ee09a0c6b192c5ef03c56eb82a0514218d8ff700f6ade004108"},
- {file = "ruff-0.5.1-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:bac6288e82f6296f82ed5285f597713acb2a6ae26618ffc6b429c597b392535c"},
- {file = "ruff-0.5.1-py3-none-win32.whl", hash = "sha256:5c441d9c24ec09e1cb190a04535c5379b36b73c4bc20aa180c54812c27d1cca4"},
- {file = "ruff-0.5.1-py3-none-win_amd64.whl", hash = "sha256:b1789bf2cd3d1b5a7d38397cac1398ddf3ad7f73f4de01b1e913e2abc7dfc51d"},
- {file = "ruff-0.5.1-py3-none-win_arm64.whl", hash = "sha256:2875b7596a740cbbd492f32d24be73e545a4ce0a3daf51e4f4e609962bfd3cd2"},
- {file = "ruff-0.5.1.tar.gz", hash = "sha256:3164488aebd89b1745b47fd00604fb4358d774465f20d1fcd907f9c0fc1b0655"},
+ {file = "ruff-0.5.4-py3-none-linux_armv6l.whl", hash = "sha256:82acef724fc639699b4d3177ed5cc14c2a5aacd92edd578a9e846d5b5ec18ddf"},
+ {file = "ruff-0.5.4-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:da62e87637c8838b325e65beee485f71eb36202ce8e3cdbc24b9fcb8b99a37be"},
+ {file = "ruff-0.5.4-py3-none-macosx_11_0_arm64.whl", hash = "sha256:e98ad088edfe2f3b85a925ee96da652028f093d6b9b56b76fc242d8abb8e2059"},
+ {file = "ruff-0.5.4-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4c55efbecc3152d614cfe6c2247a3054cfe358cefbf794f8c79c8575456efe19"},
+ {file = "ruff-0.5.4-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f9b85eaa1f653abd0a70603b8b7008d9e00c9fa1bbd0bf40dad3f0c0bdd06793"},
+ {file = "ruff-0.5.4-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0cf497a47751be8c883059c4613ba2f50dd06ec672692de2811f039432875278"},
+ {file = "ruff-0.5.4-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:09c14ed6a72af9ccc8d2e313d7acf7037f0faff43cde4b507e66f14e812e37f7"},
+ {file = "ruff-0.5.4-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:628f6b8f97b8bad2490240aa84f3e68f390e13fabc9af5c0d3b96b485921cd60"},
+ {file = "ruff-0.5.4-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3520a00c0563d7a7a7c324ad7e2cde2355733dafa9592c671fb2e9e3cd8194c1"},
+ {file = "ruff-0.5.4-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:93789f14ca2244fb91ed481456f6d0bb8af1f75a330e133b67d08f06ad85b516"},
+ {file = "ruff-0.5.4-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:029454e2824eafa25b9df46882f7f7844d36fd8ce51c1b7f6d97e2615a57bbcc"},
+ {file = "ruff-0.5.4-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:9492320eed573a13a0bc09a2957f17aa733fff9ce5bf00e66e6d4a88ec33813f"},
+ {file = "ruff-0.5.4-py3-none-musllinux_1_2_i686.whl", hash = "sha256:a6e1f62a92c645e2919b65c02e79d1f61e78a58eddaebca6c23659e7c7cb4ac7"},
+ {file = "ruff-0.5.4-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:768fa9208df2bec4b2ce61dbc7c2ddd6b1be9fb48f1f8d3b78b3332c7d71c1ff"},
+ {file = "ruff-0.5.4-py3-none-win32.whl", hash = "sha256:e1e7393e9c56128e870b233c82ceb42164966f25b30f68acbb24ed69ce9c3a4e"},
+ {file = "ruff-0.5.4-py3-none-win_amd64.whl", hash = "sha256:58b54459221fd3f661a7329f177f091eb35cf7a603f01d9eb3eb11cc348d38c4"},
+ {file = "ruff-0.5.4-py3-none-win_arm64.whl", hash = "sha256:bd53da65f1085fb5b307c38fd3c0829e76acf7b2a912d8d79cadcdb4875c1eb7"},
+ {file = "ruff-0.5.4.tar.gz", hash = "sha256:2795726d5f71c4f4e70653273d1c23a8182f07dd8e48c12de5d867bfb7557eed"},
]
[[package]]
@@ -3086,13 +3086,13 @@ crt = ["botocore[crt] (>=1.33.2,<2.0a.0)"]
[[package]]
name = "sentry-sdk"
-version = "2.9.0"
+version = "2.11.0"
description = "Python client for Sentry (https://sentry.io)"
optional = false
python-versions = ">=3.6"
files = [
- {file = "sentry_sdk-2.9.0-py2.py3-none-any.whl", hash = "sha256:0bea5fa8b564cc0d09f2e6f55893e8f70286048b0ffb3a341d5b695d1af0e6ee"},
- {file = "sentry_sdk-2.9.0.tar.gz", hash = "sha256:4c85bad74df9767976afb3eeddc33e0e153300e887d637775a753a35ef99bee6"},
+ {file = "sentry_sdk-2.11.0-py2.py3-none-any.whl", hash = "sha256:d964710e2dbe015d9dc4ff0ad16225d68c3b36936b742a6fe0504565b760a3b7"},
+ {file = "sentry_sdk-2.11.0.tar.gz", hash = "sha256:4ca16e9f5c7c6bc2fb2d5c956219f4926b148e511fffdbbde711dc94f1e0468f"},
]
[package.dependencies]
@@ -3342,20 +3342,6 @@ files = [
[package.dependencies]
types-urllib3 = "*"
-[[package]]
-name = "types-requests"
-version = "2.32.0.20240622"
-description = "Typing stubs for requests"
-optional = false
-python-versions = ">=3.8"
-files = [
- {file = "types-requests-2.32.0.20240622.tar.gz", hash = "sha256:ed5e8a412fcc39159d6319385c009d642845f250c63902718f605cd90faade31"},
- {file = "types_requests-2.32.0.20240622-py3-none-any.whl", hash = "sha256:97bac6b54b5bd4cf91d407e62f0932a74821bc2211f22116d9ee1dd643826caf"},
-]
-
-[package.dependencies]
-urllib3 = ">=2"
-
[[package]]
name = "types-setuptools"
version = "70.3.0.20240710"
@@ -3721,4 +3707,4 @@ validation = ["fastjsonschema"]
[metadata]
lock-version = "2.0"
python-versions = ">=3.8,<4.0.0"
-content-hash = "c2365606d64d2808aa22f96b8690b4fafa4de0953fdeacfdd68c4ba0311a81c0"
+content-hash = "b0a22f1e3117db309d0835c94e0fc6d12063ddc9e16fd99230bff6745442727e"
diff --git a/provenance/2.41.1a0/multiple.intoto.jsonl b/provenance/2.41.1a0/multiple.intoto.jsonl
new file mode 100644
index 00000000000..d4e436166a8
--- /dev/null
+++ b/provenance/2.41.1a0/multiple.intoto.jsonl
@@ -0,0 +1 @@
+{"payloadType":"application/vnd.in-toto+json","payload":"eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjAuMSIsInByZWRpY2F0ZVR5cGUiOiJodHRwczovL3Nsc2EuZGV2L3Byb3ZlbmFuY2UvdjAuMiIsInN1YmplY3QiOlt7Im5hbWUiOiIuL2F3c19sYW1iZGFfcG93ZXJ0b29scy0yLjQxLjFhMC1weTMtbm9uZS1hbnkud2hsIiwiZGlnZXN0Ijp7InNoYTI1NiI6IjQzNGU5ZjVhMTJmNGY0YWZlYmVhYjFiM2FkNzY2NDIwYWY5N2RjY2VjZjI2YmQ4NmU3ZDUxYzQ0OTY2NTA2MWEifX0seyJuYW1lIjoiLi9hd3NfbGFtYmRhX3Bvd2VydG9vbHMtMi40MS4xYTAudGFyLmd6IiwiZGlnZXN0Ijp7InNoYTI1NiI6IjFiMzM5NjFlNTQzOWYyY2UzOGVhNzA2N2JlM2VlZjlkODg3YjY3NDk2NjI2OTYyNTM4NmM5MzU4NzY2MWJiY2MifX1dLCJwcmVkaWNhdGUiOnsiYnVpbGRlciI6eyJpZCI6Imh0dHBzOi8vZ2l0aHViLmNvbS9zbHNhLWZyYW1ld29yay9zbHNhLWdpdGh1Yi1nZW5lcmF0b3IvLmdpdGh1Yi93b3JrZmxvd3MvZ2VuZXJhdG9yX2dlbmVyaWNfc2xzYTMueW1sQHJlZnMvdGFncy92Mi4wLjAifSwiYnVpbGRUeXBlIjoiaHR0cHM6Ly9naXRodWIuY29tL3Nsc2EtZnJhbWV3b3JrL3Nsc2EtZ2l0aHViLWdlbmVyYXRvci9nZW5lcmljQHYxIiwiaW52b2NhdGlvbiI6eyJjb25maWdTb3VyY2UiOnsidXJpIjoiZ2l0K2h0dHBzOi8vZ2l0aHViLmNvbS9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob25AcmVmcy9oZWFkcy9kZXZlbG9wIiwiZGlnZXN0Ijp7InNoYTEiOiIxMjUxODZkOGE5NDBlZTgzNmQ4MjM4ZTI2Y2JhMTNhMTYzNDYzYjRmIn0sImVudHJ5UG9pbnQiOiIuZ2l0aHViL3dvcmtmbG93cy9wcmUtcmVsZWFzZS55bWwifSwicGFyYW1ldGVycyI6e30sImVudmlyb25tZW50Ijp7ImdpdGh1Yl9hY3RvciI6ImxlYW5kcm9kYW1hc2NlbmEiLCJnaXRodWJfYWN0b3JfaWQiOiI0Mjk1MTczIiwiZ2l0aHViX2Jhc2VfcmVmIjoiIiwiZ2l0aHViX2V2ZW50X25hbWUiOiJzY2hlZHVsZSIsImdpdGh1Yl9ldmVudF9wYXlsb2FkIjp7ImVudGVycHJpc2UiOnsiYXZhdGFyX3VybCI6Imh0dHBzOi8vYXZhdGFycy5naXRodWJ1c2VyY29udGVudC5jb20vYi8xMjkwP3Y9NCIsImNyZWF0ZWRfYXQiOiIyMDE5LTExLTEzVDE4OjA1OjQxWiIsImRlc2NyaXB0aW9uIjoiIiwiaHRtbF91cmwiOiJodHRwczovL2dpdGh1Yi5jb20vZW50ZXJwcmlzZXMvYW1hem9uIiwiaWQiOjEyOTAsIm5hbWUiOiJBbWF6b24iLCJub2RlX2lkIjoiTURFd09rVnVkR1Z5Y0hKcGMyVXhNamt3Iiwic2x1ZyI6ImFtYXpvbiIsInVwZGF0ZWRfYXQiOiIyMDIzLTAxLTI3VDE0OjU2OjEwWiIsIndlYnNpdGVfdXJsIjoiaHR0cHM6Ly93d3cuYW1hem9uLmNvbS8ifSwib3JnYW5pemF0aW9uIjp7ImF2YXRhcl91cmwiOiJodHRwczovL2F2YXRhcnMuZ2l0aHVidXNlcmNvbnRlbnQuY29tL3UvMTI5MTI3NjM4P3Y9NCIsImRlc2NyaXB0aW9uIjoiIiwiZXZlbnRzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vb3Jncy9hd3MtcG93ZXJ0b29scy9ldmVudHMiLCJob29rc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL29yZ3MvYXdzLXBvd2VydG9vbHMvaG9va3MiLCJpZCI6MTI5MTI3NjM4LCJpc3N1ZXNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9vcmdzL2F3cy1wb3dlcnRvb2xzL2lzc3VlcyIsImxvZ2luIjoiYXdzLXBvd2VydG9vbHMiLCJtZW1iZXJzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vb3Jncy9hd3MtcG93ZXJ0b29scy9tZW1iZXJzey9tZW1iZXJ9Iiwibm9kZV9pZCI6Ik9fa2dET0I3SlUxZyIsInB1YmxpY19tZW1iZXJzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vb3Jncy9hd3MtcG93ZXJ0b29scy9wdWJsaWNfbWVtYmVyc3svbWVtYmVyfSIsInJlcG9zX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vb3Jncy9hd3MtcG93ZXJ0b29scy9yZXBvcyIsInVybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vb3Jncy9hd3MtcG93ZXJ0b29scyJ9LCJyZXBvc2l0b3J5Ijp7ImFsbG93X2ZvcmtpbmciOnRydWUsImFyY2hpdmVfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24ve2FyY2hpdmVfZm9ybWF0fXsvcmVmfSIsImFyY2hpdmVkIjpmYWxzZSwiYXNzaWduZWVzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2Fzc2lnbmVlc3svdXNlcn0iLCJibG9ic191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9naXQvYmxvYnN7L3NoYX0iLCJicmFuY2hlc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9icmFuY2hlc3svYnJhbmNofSIsImNsb25lX3VybCI6Imh0dHBzOi8vZ2l0aHViLmNvbS9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24uZ2l0IiwiY29sbGFib3JhdG9yc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9jb2xsYWJvcmF0b3Jzey9jb2xsYWJvcmF0b3J9IiwiY29tbWVudHNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vY29tbWVudHN7L251bWJlcn0iLCJjb21taXRzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2NvbW1pdHN7L3NoYX0iLCJjb21wYXJlX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2NvbXBhcmUve2Jhc2V9Li4ue2hlYWR9IiwiY29udGVudHNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vY29udGVudHMveytwYXRofSIsImNvbnRyaWJ1dG9yc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9jb250cmlidXRvcnMiLCJjcmVhdGVkX2F0IjoiMjAxOS0xMS0xNVQxMjoyNjoxMloiLCJkZWZhdWx0X2JyYW5jaCI6ImRldmVsb3AiLCJkZXBsb3ltZW50c191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9kZXBsb3ltZW50cyIsImRlc2NyaXB0aW9uIjoiQSBkZXZlbG9wZXIgdG9vbGtpdCB0byBpbXBsZW1lbnQgU2VydmVybGVzcyBiZXN0IHByYWN0aWNlcyBhbmQgaW5jcmVhc2UgZGV2ZWxvcGVyIHZlbG9jaXR5LiIsImRpc2FibGVkIjpmYWxzZSwiZG93bmxvYWRzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2Rvd25sb2FkcyIsImV2ZW50c191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9ldmVudHMiLCJmb3JrIjpmYWxzZSwiZm9ya3MiOjM3OCwiZm9ya3NfY291bnQiOjM3OCwiZm9ya3NfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vZm9ya3MiLCJmdWxsX25hbWUiOiJhd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24iLCJnaXRfY29tbWl0c191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9naXQvY29tbWl0c3svc2hhfSIsImdpdF9yZWZzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2dpdC9yZWZzey9zaGF9IiwiZ2l0X3RhZ3NfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vZ2l0L3RhZ3N7L3NoYX0iLCJnaXRfdXJsIjoiZ2l0Oi8vZ2l0aHViLmNvbS9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24uZ2l0IiwiaGFzX2Rpc2N1c3Npb25zIjp0cnVlLCJoYXNfZG93bmxvYWRzIjp0cnVlLCJoYXNfaXNzdWVzIjp0cnVlLCJoYXNfcGFnZXMiOmZhbHNlLCJoYXNfcHJvamVjdHMiOnRydWUsImhhc193aWtpIjpmYWxzZSwiaG9tZXBhZ2UiOiJodHRwczovL2RvY3MucG93ZXJ0b29scy5hd3MuZGV2L2xhbWJkYS9weXRob24vbGF0ZXN0LyIsImhvb2tzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2hvb2tzIiwiaHRtbF91cmwiOiJodHRwczovL2dpdGh1Yi5jb20vYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uIiwiaWQiOjIyMTkxOTM3OSwiaXNfdGVtcGxhdGUiOmZhbHNlLCJpc3N1ZV9jb21tZW50X3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2lzc3Vlcy9jb21tZW50c3svbnVtYmVyfSIsImlzc3VlX2V2ZW50c191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9pc3N1ZXMvZXZlbnRzey9udW1iZXJ9IiwiaXNzdWVzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2lzc3Vlc3svbnVtYmVyfSIsImtleXNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24va2V5c3sva2V5X2lkfSIsImxhYmVsc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9sYWJlbHN7L25hbWV9IiwibGFuZ3VhZ2UiOiJQeXRob24iLCJsYW5ndWFnZXNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vbGFuZ3VhZ2VzIiwibGljZW5zZSI6eyJrZXkiOiJtaXQtMCIsIm5hbWUiOiJNSVQgTm8gQXR0cmlidXRpb24iLCJub2RlX2lkIjoiTURjNlRHbGpaVzV6WlRReCIsInNwZHhfaWQiOiJNSVQtMCIsInVybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vbGljZW5zZXMvbWl0LTAifSwibWVyZ2VzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL21lcmdlcyIsIm1pbGVzdG9uZXNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vbWlsZXN0b25lc3svbnVtYmVyfSIsIm1pcnJvcl91cmwiOm51bGwsIm5hbWUiOiJwb3dlcnRvb2xzLWxhbWJkYS1weXRob24iLCJub2RlX2lkIjoiTURFd09sSmxjRzl6YVhSdmNua3lNakU1TVRrek56az0iLCJub3RpZmljYXRpb25zX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL25vdGlmaWNhdGlvbnN7P3NpbmNlLGFsbCxwYXJ0aWNpcGF0aW5nfSIsIm9wZW5faXNzdWVzIjo5Nywib3Blbl9pc3N1ZXNfY291bnQiOjk3LCJvd25lciI6eyJhdmF0YXJfdXJsIjoiaHR0cHM6Ly9hdmF0YXJzLmdpdGh1YnVzZXJjb250ZW50LmNvbS91LzEyOTEyNzYzOD92PTQiLCJldmVudHNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS91c2Vycy9hd3MtcG93ZXJ0b29scy9ldmVudHN7L3ByaXZhY3l9IiwiZm9sbG93ZXJzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vdXNlcnMvYXdzLXBvd2VydG9vbHMvZm9sbG93ZXJzIiwiZm9sbG93aW5nX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vdXNlcnMvYXdzLXBvd2VydG9vbHMvZm9sbG93aW5ney9vdGhlcl91c2VyfSIsImdpc3RzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vdXNlcnMvYXdzLXBvd2VydG9vbHMvZ2lzdHN7L2dpc3RfaWR9IiwiZ3JhdmF0YXJfaWQiOiIiLCJodG1sX3VybCI6Imh0dHBzOi8vZ2l0aHViLmNvbS9hd3MtcG93ZXJ0b29scyIsImlkIjoxMjkxMjc2MzgsImxvZ2luIjoiYXdzLXBvd2VydG9vbHMiLCJub2RlX2lkIjoiT19rZ0RPQjdKVTFnIiwib3JnYW5pemF0aW9uc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3VzZXJzL2F3cy1wb3dlcnRvb2xzL29yZ3MiLCJyZWNlaXZlZF9ldmVudHNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS91c2Vycy9hd3MtcG93ZXJ0b29scy9yZWNlaXZlZF9ldmVudHMiLCJyZXBvc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3VzZXJzL2F3cy1wb3dlcnRvb2xzL3JlcG9zIiwic2l0ZV9hZG1pbiI6ZmFsc2UsInN0YXJyZWRfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS91c2Vycy9hd3MtcG93ZXJ0b29scy9zdGFycmVkey9vd25lcn17L3JlcG99Iiwic3Vic2NyaXB0aW9uc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3VzZXJzL2F3cy1wb3dlcnRvb2xzL3N1YnNjcmlwdGlvbnMiLCJ0eXBlIjoiT3JnYW5pemF0aW9uIiwidXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS91c2Vycy9hd3MtcG93ZXJ0b29scyJ9LCJwcml2YXRlIjpmYWxzZSwicHVsbHNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vcHVsbHN7L251bWJlcn0iLCJwdXNoZWRfYXQiOiIyMDI0LTA3LTEyVDA3OjA5OjE0WiIsInJlbGVhc2VzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL3JlbGVhc2Vzey9pZH0iLCJzaXplIjo0NzE0NSwic3NoX3VybCI6ImdpdEBnaXRodWIuY29tOmF3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi5naXQiLCJzdGFyZ2F6ZXJzX2NvdW50IjoyNzI3LCJzdGFyZ2F6ZXJzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL3N0YXJnYXplcnMiLCJzdGF0dXNlc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9zdGF0dXNlcy97c2hhfSIsInN1YnNjcmliZXJzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL3N1YnNjcmliZXJzIiwic3Vic2NyaXB0aW9uX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL3N1YnNjcmlwdGlvbiIsInN2bl91cmwiOiJodHRwczovL2dpdGh1Yi5jb20vYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uIiwidGFnc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi90YWdzIiwidGVhbXNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vdGVhbXMiLCJ0b3BpY3MiOlsiYXdzIiwiYXdzLWxhbWJkYSIsImhhY2t0b2JlcmZlc3QiLCJsYW1iZGEiLCJweXRob24iLCJzZXJ2ZXJsZXNzIl0sInRyZWVzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2dpdC90cmVlc3svc2hhfSIsInVwZGF0ZWRfYXQiOiIyMDI0LTA3LTEyVDA3OjA5OjE3WiIsInVybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uIiwidmlzaWJpbGl0eSI6InB1YmxpYyIsIndhdGNoZXJzIjoyNzI3LCJ3YXRjaGVyc19jb3VudCI6MjcyNywid2ViX2NvbW1pdF9zaWdub2ZmX3JlcXVpcmVkIjp0cnVlfSwic2NoZWR1bGUiOiIwIDggKiAqIDEtNSIsIndvcmtmbG93IjoiLmdpdGh1Yi93b3JrZmxvd3MvcHJlLXJlbGVhc2UueW1sIn0sImdpdGh1Yl9oZWFkX3JlZiI6IiIsImdpdGh1Yl9yZWYiOiJyZWZzL2hlYWRzL2RldmVsb3AiLCJnaXRodWJfcmVmX3R5cGUiOiJicmFuY2giLCJnaXRodWJfcmVwb3NpdG9yeV9pZCI6IjIyMTkxOTM3OSIsImdpdGh1Yl9yZXBvc2l0b3J5X293bmVyIjoiYXdzLXBvd2VydG9vbHMiLCJnaXRodWJfcmVwb3NpdG9yeV9vd25lcl9pZCI6IjEyOTEyNzYzOCIsImdpdGh1Yl9ydW5fYXR0ZW1wdCI6IjEiLCJnaXRodWJfcnVuX2lkIjoiOTkwNDUwODQ0NyIsImdpdGh1Yl9ydW5fbnVtYmVyIjoiMTgiLCJnaXRodWJfc2hhMSI6IjEyNTE4NmQ4YTk0MGVlODM2ZDgyMzhlMjZjYmExM2ExNjM0NjNiNGYifX0sIm1ldGFkYXRhIjp7ImJ1aWxkSW52b2NhdGlvbklEIjoiOTkwNDUwODQ0Ny0xIiwiY29tcGxldGVuZXNzIjp7InBhcmFtZXRlcnMiOnRydWUsImVudmlyb25tZW50IjpmYWxzZSwibWF0ZXJpYWxzIjpmYWxzZX0sInJlcHJvZHVjaWJsZSI6ZmFsc2V9LCJtYXRlcmlhbHMiOlt7InVyaSI6ImdpdCtodHRwczovL2dpdGh1Yi5jb20vYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uQHJlZnMvaGVhZHMvZGV2ZWxvcCIsImRpZ2VzdCI6eyJzaGExIjoiMTI1MTg2ZDhhOTQwZWU4MzZkODIzOGUyNmNiYTEzYTE2MzQ2M2I0ZiJ9fV19fQ==","signatures":[{"keyid":"","sig":"MEUCIQDFqVGDhF90hADdjNahViXXqcSDRWXm+m5oSK2oB+LdEwIgPj7T9+uBvpxFiFdzvkQUSkkfCLkpLGUs5++uVxOZlFY=","cert":"-----BEGIN CERTIFICATE-----\nMIIHZzCCBuygAwIBAgIUbda7Rl/tx+p3UNcp97eOkR2OAGAwCgYIKoZIzj0EAwMw\nNzEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MR4wHAYDVQQDExVzaWdzdG9yZS1pbnRl\ncm1lZGlhdGUwHhcNMjQwNzEyMDgwNzAxWhcNMjQwNzEyMDgxNzAxWjAAMFkwEwYH\nKoZIzj0CAQYIKoZIzj0DAQcDQgAEWKF+8IKD4GHB4iVv5fgXq8eugve2eee1MLEf\nZ9R0op2yw7NsnpPeSxfRGeeezMWlRqgXpHmAqwGH965hyuAd3KOCBgswggYHMA4G\nA1UdDwEB/wQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcDAzAdBgNVHQ4EFgQUL0yo\n+dyFZgiIjVY3pXs4Z4jwZ+swHwYDVR0jBBgwFoAU39Ppz1YkEZb5qNjpKFWixi4Y\nZD8wgYQGA1UdEQEB/wR6MHiGdmh0dHBzOi8vZ2l0aHViLmNvbS9zbHNhLWZyYW1l\nd29yay9zbHNhLWdpdGh1Yi1nZW5lcmF0b3IvLmdpdGh1Yi93b3JrZmxvd3MvZ2Vu\nZXJhdG9yX2dlbmVyaWNfc2xzYTMueW1sQHJlZnMvdGFncy92Mi4wLjAwOQYKKwYB\nBAGDvzABAQQraHR0cHM6Ly90b2tlbi5hY3Rpb25zLmdpdGh1YnVzZXJjb250ZW50\nLmNvbTAWBgorBgEEAYO/MAECBAhzY2hlZHVsZTA2BgorBgEEAYO/MAEDBCgxMjUx\nODZkOGE5NDBlZTgzNmQ4MjM4ZTI2Y2JhMTNhMTYzNDYzYjRmMBkGCisGAQQBg78w\nAQQEC1ByZS1SZWxlYXNlMDUGCisGAQQBg78wAQUEJ2F3cy1wb3dlcnRvb2xzL3Bv\nd2VydG9vbHMtbGFtYmRhLXB5dGhvbjAgBgorBgEEAYO/MAEGBBJyZWZzL2hlYWRz\nL2RldmVsb3AwOwYKKwYBBAGDvzABCAQtDCtodHRwczovL3Rva2VuLmFjdGlvbnMu\nZ2l0aHVidXNlcmNvbnRlbnQuY29tMIGGBgorBgEEAYO/MAEJBHgMdmh0dHBzOi8v\nZ2l0aHViLmNvbS9zbHNhLWZyYW1ld29yay9zbHNhLWdpdGh1Yi1nZW5lcmF0b3Iv\nLmdpdGh1Yi93b3JrZmxvd3MvZ2VuZXJhdG9yX2dlbmVyaWNfc2xzYTMueW1sQHJl\nZnMvdGFncy92Mi4wLjAwOAYKKwYBBAGDvzABCgQqDCg1YTc3NWIzNjdhNTZkNWJk\nMTE4YTIyNGE4MTFiYmEyODgxNTBhNTYzMB0GCisGAQQBg78wAQsEDwwNZ2l0aHVi\nLWhvc3RlZDBKBgorBgEEAYO/MAEMBDwMOmh0dHBzOi8vZ2l0aHViLmNvbS9hd3Mt\ncG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24wOAYKKwYBBAGDvzAB\nDQQqDCgxMjUxODZkOGE5NDBlZTgzNmQ4MjM4ZTI2Y2JhMTNhMTYzNDYzYjRmMCIG\nCisGAQQBg78wAQ4EFAwScmVmcy9oZWFkcy9kZXZlbG9wMBkGCisGAQQBg78wAQ8E\nCwwJMjIxOTE5Mzc5MDEGCisGAQQBg78wARAEIwwhaHR0cHM6Ly9naXRodWIuY29t\nL2F3cy1wb3dlcnRvb2xzMBkGCisGAQQBg78wAREECwwJMTI5MTI3NjM4MH8GCisG\nAQQBg78wARIEcQxvaHR0cHM6Ly9naXRodWIuY29tL2F3cy1wb3dlcnRvb2xzL3Bv\nd2VydG9vbHMtbGFtYmRhLXB5dGhvbi8uZ2l0aHViL3dvcmtmbG93cy9wcmUtcmVs\nZWFzZS55bWxAcmVmcy9oZWFkcy9kZXZlbG9wMDgGCisGAQQBg78wARMEKgwoMTI1\nMTg2ZDhhOTQwZWU4MzZkODIzOGUyNmNiYTEzYTE2MzQ2M2I0ZjAYBgorBgEEAYO/\nMAEUBAoMCHNjaGVkdWxlMG0GCisGAQQBg78wARUEXwxdaHR0cHM6Ly9naXRodWIu\nY29tL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9hY3Rp\nb25zL3J1bnMvOTkwNDUwODQ0Ny9hdHRlbXB0cy8xMBYGCisGAQQBg78wARYECAwG\ncHVibGljMIGLBgorBgEEAdZ5AgQCBH0EewB5AHcA3T0wasbHETJjGR4cmWc3AqJK\nXrjePK3/h4pygC8p7o4AAAGQpfsxywAABAMASDBGAiEA//Coo8ld7vHEtG0FqOWB\nyDmoBgV4NHYbdUZM1zwZRdQCIQCyR2mfFNGrZV+H10PgOs7HDoS0AeaqKCTJ01xM\nVgSAADAKBggqhkjOPQQDAwNpADBmAjEAgF/uryFjSIu8CoHUaE/aBo+qbBdCLtTM\niYAzHP43WOxkst3LBsYu9OKrA8wwZkTiAjEA033XOFHLGDJu8uARxF+24m4zLuIt\nQX8gP3YfRMSzC9eNvD2/R2huXEf9IMqBmRov\n-----END CERTIFICATE-----\n"}]}
\ No newline at end of file
diff --git a/provenance/2.41.1a1/multiple.intoto.jsonl b/provenance/2.41.1a1/multiple.intoto.jsonl
new file mode 100644
index 00000000000..a943222f129
--- /dev/null
+++ b/provenance/2.41.1a1/multiple.intoto.jsonl
@@ -0,0 +1 @@
+{"payloadType":"application/vnd.in-toto+json","payload":"eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjAuMSIsInByZWRpY2F0ZVR5cGUiOiJodHRwczovL3Nsc2EuZGV2L3Byb3ZlbmFuY2UvdjAuMiIsInN1YmplY3QiOlt7Im5hbWUiOiIuL2F3c19sYW1iZGFfcG93ZXJ0b29scy0yLjQxLjFhMS1weTMtbm9uZS1hbnkud2hsIiwiZGlnZXN0Ijp7InNoYTI1NiI6ImFjOGNiNTlhZDRjMTY4OWQzMjJmODIwNmZmY2IyMTJjZWFlNzA1ZmEyZjg4ZWQwMDQ2NTVhZTFhZmVlNWY4NzcifX0seyJuYW1lIjoiLi9hd3NfbGFtYmRhX3Bvd2VydG9vbHMtMi40MS4xYTEudGFyLmd6IiwiZGlnZXN0Ijp7InNoYTI1NiI6IjkzN2I3MGI2MzJmMjNlZjYzYTBlYjRkNjBmNTY1NzU4MjM5Y2Y0ZDBmYWU3NDUzMGI4M2Y1Y2I5YTliZWU3YmUifX1dLCJwcmVkaWNhdGUiOnsiYnVpbGRlciI6eyJpZCI6Imh0dHBzOi8vZ2l0aHViLmNvbS9zbHNhLWZyYW1ld29yay9zbHNhLWdpdGh1Yi1nZW5lcmF0b3IvLmdpdGh1Yi93b3JrZmxvd3MvZ2VuZXJhdG9yX2dlbmVyaWNfc2xzYTMueW1sQHJlZnMvdGFncy92Mi4wLjAifSwiYnVpbGRUeXBlIjoiaHR0cHM6Ly9naXRodWIuY29tL3Nsc2EtZnJhbWV3b3JrL3Nsc2EtZ2l0aHViLWdlbmVyYXRvci9nZW5lcmljQHYxIiwiaW52b2NhdGlvbiI6eyJjb25maWdTb3VyY2UiOnsidXJpIjoiZ2l0K2h0dHBzOi8vZ2l0aHViLmNvbS9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob25AcmVmcy9oZWFkcy9kZXZlbG9wIiwiZGlnZXN0Ijp7InNoYTEiOiI0M2QxMzc2ZjNhOTlhNjJlOTQ3MWQxMzQ0NjJlOWI3M2Y5NjRlMjdkIn0sImVudHJ5UG9pbnQiOiIuZ2l0aHViL3dvcmtmbG93cy9wcmUtcmVsZWFzZS55bWwifSwicGFyYW1ldGVycyI6e30sImVudmlyb25tZW50Ijp7ImdpdGh1Yl9hY3RvciI6ImxlYW5kcm9kYW1hc2NlbmEiLCJnaXRodWJfYWN0b3JfaWQiOiI0Mjk1MTczIiwiZ2l0aHViX2Jhc2VfcmVmIjoiIiwiZ2l0aHViX2V2ZW50X25hbWUiOiJzY2hlZHVsZSIsImdpdGh1Yl9ldmVudF9wYXlsb2FkIjp7ImVudGVycHJpc2UiOnsiYXZhdGFyX3VybCI6Imh0dHBzOi8vYXZhdGFycy5naXRodWJ1c2VyY29udGVudC5jb20vYi8xMjkwP3Y9NCIsImNyZWF0ZWRfYXQiOiIyMDE5LTExLTEzVDE4OjA1OjQxWiIsImRlc2NyaXB0aW9uIjoiIiwiaHRtbF91cmwiOiJodHRwczovL2dpdGh1Yi5jb20vZW50ZXJwcmlzZXMvYW1hem9uIiwiaWQiOjEyOTAsIm5hbWUiOiJBbWF6b24iLCJub2RlX2lkIjoiTURFd09rVnVkR1Z5Y0hKcGMyVXhNamt3Iiwic2x1ZyI6ImFtYXpvbiIsInVwZGF0ZWRfYXQiOiIyMDIzLTAxLTI3VDE0OjU2OjEwWiIsIndlYnNpdGVfdXJsIjoiaHR0cHM6Ly93d3cuYW1hem9uLmNvbS8ifSwib3JnYW5pemF0aW9uIjp7ImF2YXRhcl91cmwiOiJodHRwczovL2F2YXRhcnMuZ2l0aHVidXNlcmNvbnRlbnQuY29tL3UvMTI5MTI3NjM4P3Y9NCIsImRlc2NyaXB0aW9uIjoiIiwiZXZlbnRzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vb3Jncy9hd3MtcG93ZXJ0b29scy9ldmVudHMiLCJob29rc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL29yZ3MvYXdzLXBvd2VydG9vbHMvaG9va3MiLCJpZCI6MTI5MTI3NjM4LCJpc3N1ZXNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9vcmdzL2F3cy1wb3dlcnRvb2xzL2lzc3VlcyIsImxvZ2luIjoiYXdzLXBvd2VydG9vbHMiLCJtZW1iZXJzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vb3Jncy9hd3MtcG93ZXJ0b29scy9tZW1iZXJzey9tZW1iZXJ9Iiwibm9kZV9pZCI6Ik9fa2dET0I3SlUxZyIsInB1YmxpY19tZW1iZXJzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vb3Jncy9hd3MtcG93ZXJ0b29scy9wdWJsaWNfbWVtYmVyc3svbWVtYmVyfSIsInJlcG9zX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vb3Jncy9hd3MtcG93ZXJ0b29scy9yZXBvcyIsInVybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vb3Jncy9hd3MtcG93ZXJ0b29scyJ9LCJyZXBvc2l0b3J5Ijp7ImFsbG93X2ZvcmtpbmciOnRydWUsImFyY2hpdmVfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24ve2FyY2hpdmVfZm9ybWF0fXsvcmVmfSIsImFyY2hpdmVkIjpmYWxzZSwiYXNzaWduZWVzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2Fzc2lnbmVlc3svdXNlcn0iLCJibG9ic191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9naXQvYmxvYnN7L3NoYX0iLCJicmFuY2hlc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9icmFuY2hlc3svYnJhbmNofSIsImNsb25lX3VybCI6Imh0dHBzOi8vZ2l0aHViLmNvbS9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24uZ2l0IiwiY29sbGFib3JhdG9yc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9jb2xsYWJvcmF0b3Jzey9jb2xsYWJvcmF0b3J9IiwiY29tbWVudHNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vY29tbWVudHN7L251bWJlcn0iLCJjb21taXRzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2NvbW1pdHN7L3NoYX0iLCJjb21wYXJlX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2NvbXBhcmUve2Jhc2V9Li4ue2hlYWR9IiwiY29udGVudHNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vY29udGVudHMveytwYXRofSIsImNvbnRyaWJ1dG9yc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9jb250cmlidXRvcnMiLCJjcmVhdGVkX2F0IjoiMjAxOS0xMS0xNVQxMjoyNjoxMloiLCJkZWZhdWx0X2JyYW5jaCI6ImRldmVsb3AiLCJkZXBsb3ltZW50c191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9kZXBsb3ltZW50cyIsImRlc2NyaXB0aW9uIjoiQSBkZXZlbG9wZXIgdG9vbGtpdCB0byBpbXBsZW1lbnQgU2VydmVybGVzcyBiZXN0IHByYWN0aWNlcyBhbmQgaW5jcmVhc2UgZGV2ZWxvcGVyIHZlbG9jaXR5LiIsImRpc2FibGVkIjpmYWxzZSwiZG93bmxvYWRzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2Rvd25sb2FkcyIsImV2ZW50c191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9ldmVudHMiLCJmb3JrIjpmYWxzZSwiZm9ya3MiOjM3OCwiZm9ya3NfY291bnQiOjM3OCwiZm9ya3NfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vZm9ya3MiLCJmdWxsX25hbWUiOiJhd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24iLCJnaXRfY29tbWl0c191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9naXQvY29tbWl0c3svc2hhfSIsImdpdF9yZWZzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2dpdC9yZWZzey9zaGF9IiwiZ2l0X3RhZ3NfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vZ2l0L3RhZ3N7L3NoYX0iLCJnaXRfdXJsIjoiZ2l0Oi8vZ2l0aHViLmNvbS9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24uZ2l0IiwiaGFzX2Rpc2N1c3Npb25zIjp0cnVlLCJoYXNfZG93bmxvYWRzIjp0cnVlLCJoYXNfaXNzdWVzIjp0cnVlLCJoYXNfcGFnZXMiOmZhbHNlLCJoYXNfcHJvamVjdHMiOnRydWUsImhhc193aWtpIjpmYWxzZSwiaG9tZXBhZ2UiOiJodHRwczovL2RvY3MucG93ZXJ0b29scy5hd3MuZGV2L2xhbWJkYS9weXRob24vbGF0ZXN0LyIsImhvb2tzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2hvb2tzIiwiaHRtbF91cmwiOiJodHRwczovL2dpdGh1Yi5jb20vYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uIiwiaWQiOjIyMTkxOTM3OSwiaXNfdGVtcGxhdGUiOmZhbHNlLCJpc3N1ZV9jb21tZW50X3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2lzc3Vlcy9jb21tZW50c3svbnVtYmVyfSIsImlzc3VlX2V2ZW50c191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9pc3N1ZXMvZXZlbnRzey9udW1iZXJ9IiwiaXNzdWVzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2lzc3Vlc3svbnVtYmVyfSIsImtleXNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24va2V5c3sva2V5X2lkfSIsImxhYmVsc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9sYWJlbHN7L25hbWV9IiwibGFuZ3VhZ2UiOiJQeXRob24iLCJsYW5ndWFnZXNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vbGFuZ3VhZ2VzIiwibGljZW5zZSI6eyJrZXkiOiJtaXQtMCIsIm5hbWUiOiJNSVQgTm8gQXR0cmlidXRpb24iLCJub2RlX2lkIjoiTURjNlRHbGpaVzV6WlRReCIsInNwZHhfaWQiOiJNSVQtMCIsInVybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vbGljZW5zZXMvbWl0LTAifSwibWVyZ2VzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL21lcmdlcyIsIm1pbGVzdG9uZXNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vbWlsZXN0b25lc3svbnVtYmVyfSIsIm1pcnJvcl91cmwiOm51bGwsIm5hbWUiOiJwb3dlcnRvb2xzLWxhbWJkYS1weXRob24iLCJub2RlX2lkIjoiTURFd09sSmxjRzl6YVhSdmNua3lNakU1TVRrek56az0iLCJub3RpZmljYXRpb25zX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL25vdGlmaWNhdGlvbnN7P3NpbmNlLGFsbCxwYXJ0aWNpcGF0aW5nfSIsIm9wZW5faXNzdWVzIjoxMDAsIm9wZW5faXNzdWVzX2NvdW50IjoxMDAsIm93bmVyIjp7ImF2YXRhcl91cmwiOiJodHRwczovL2F2YXRhcnMuZ2l0aHVidXNlcmNvbnRlbnQuY29tL3UvMTI5MTI3NjM4P3Y9NCIsImV2ZW50c191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3VzZXJzL2F3cy1wb3dlcnRvb2xzL2V2ZW50c3svcHJpdmFjeX0iLCJmb2xsb3dlcnNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS91c2Vycy9hd3MtcG93ZXJ0b29scy9mb2xsb3dlcnMiLCJmb2xsb3dpbmdfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS91c2Vycy9hd3MtcG93ZXJ0b29scy9mb2xsb3dpbmd7L290aGVyX3VzZXJ9IiwiZ2lzdHNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS91c2Vycy9hd3MtcG93ZXJ0b29scy9naXN0c3svZ2lzdF9pZH0iLCJncmF2YXRhcl9pZCI6IiIsImh0bWxfdXJsIjoiaHR0cHM6Ly9naXRodWIuY29tL2F3cy1wb3dlcnRvb2xzIiwiaWQiOjEyOTEyNzYzOCwibG9naW4iOiJhd3MtcG93ZXJ0b29scyIsIm5vZGVfaWQiOiJPX2tnRE9CN0pVMWciLCJvcmdhbml6YXRpb25zX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vdXNlcnMvYXdzLXBvd2VydG9vbHMvb3JncyIsInJlY2VpdmVkX2V2ZW50c191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3VzZXJzL2F3cy1wb3dlcnRvb2xzL3JlY2VpdmVkX2V2ZW50cyIsInJlcG9zX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vdXNlcnMvYXdzLXBvd2VydG9vbHMvcmVwb3MiLCJzaXRlX2FkbWluIjpmYWxzZSwic3RhcnJlZF91cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3VzZXJzL2F3cy1wb3dlcnRvb2xzL3N0YXJyZWR7L293bmVyfXsvcmVwb30iLCJzdWJzY3JpcHRpb25zX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vdXNlcnMvYXdzLXBvd2VydG9vbHMvc3Vic2NyaXB0aW9ucyIsInR5cGUiOiJPcmdhbml6YXRpb24iLCJ1cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3VzZXJzL2F3cy1wb3dlcnRvb2xzIn0sInByaXZhdGUiOmZhbHNlLCJwdWxsc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9wdWxsc3svbnVtYmVyfSIsInB1c2hlZF9hdCI6IjIwMjQtMDctMTRUMTA6MDQ6MDdaIiwicmVsZWFzZXNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vcmVsZWFzZXN7L2lkfSIsInNpemUiOjQ3NDk5LCJzc2hfdXJsIjoiZ2l0QGdpdGh1Yi5jb206YXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uLmdpdCIsInN0YXJnYXplcnNfY291bnQiOjI3MjgsInN0YXJnYXplcnNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vc3RhcmdhemVycyIsInN0YXR1c2VzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL3N0YXR1c2VzL3tzaGF9Iiwic3Vic2NyaWJlcnNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vc3Vic2NyaWJlcnMiLCJzdWJzY3JpcHRpb25fdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vc3Vic2NyaXB0aW9uIiwic3ZuX3VybCI6Imh0dHBzOi8vZ2l0aHViLmNvbS9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24iLCJ0YWdzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL3RhZ3MiLCJ0ZWFtc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi90ZWFtcyIsInRvcGljcyI6WyJhd3MiLCJhd3MtbGFtYmRhIiwiaGFja3RvYmVyZmVzdCIsImxhbWJkYSIsInB5dGhvbiIsInNlcnZlcmxlc3MiXSwidHJlZXNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vZ2l0L3RyZWVzey9zaGF9IiwidXBkYXRlZF9hdCI6IjIwMjQtMDctMTNUMDI6MDc6MThaIiwidXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24iLCJ2aXNpYmlsaXR5IjoicHVibGljIiwid2F0Y2hlcnMiOjI3MjgsIndhdGNoZXJzX2NvdW50IjoyNzI4LCJ3ZWJfY29tbWl0X3NpZ25vZmZfcmVxdWlyZWQiOnRydWV9LCJzY2hlZHVsZSI6IjAgOCAqICogMS01Iiwid29ya2Zsb3ciOiIuZ2l0aHViL3dvcmtmbG93cy9wcmUtcmVsZWFzZS55bWwifSwiZ2l0aHViX2hlYWRfcmVmIjoiIiwiZ2l0aHViX3JlZiI6InJlZnMvaGVhZHMvZGV2ZWxvcCIsImdpdGh1Yl9yZWZfdHlwZSI6ImJyYW5jaCIsImdpdGh1Yl9yZXBvc2l0b3J5X2lkIjoiMjIxOTE5Mzc5IiwiZ2l0aHViX3JlcG9zaXRvcnlfb3duZXIiOiJhd3MtcG93ZXJ0b29scyIsImdpdGh1Yl9yZXBvc2l0b3J5X293bmVyX2lkIjoiMTI5MTI3NjM4IiwiZ2l0aHViX3J1bl9hdHRlbXB0IjoiMSIsImdpdGh1Yl9ydW5faWQiOiI5OTM1Nzc3NjQ5IiwiZ2l0aHViX3J1bl9udW1iZXIiOiIxOSIsImdpdGh1Yl9zaGExIjoiNDNkMTM3NmYzYTk5YTYyZTk0NzFkMTM0NDYyZTliNzNmOTY0ZTI3ZCJ9fSwibWV0YWRhdGEiOnsiYnVpbGRJbnZvY2F0aW9uSUQiOiI5OTM1Nzc3NjQ5LTEiLCJjb21wbGV0ZW5lc3MiOnsicGFyYW1ldGVycyI6dHJ1ZSwiZW52aXJvbm1lbnQiOmZhbHNlLCJtYXRlcmlhbHMiOmZhbHNlfSwicmVwcm9kdWNpYmxlIjpmYWxzZX0sIm1hdGVyaWFscyI6W3sidXJpIjoiZ2l0K2h0dHBzOi8vZ2l0aHViLmNvbS9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob25AcmVmcy9oZWFkcy9kZXZlbG9wIiwiZGlnZXN0Ijp7InNoYTEiOiI0M2QxMzc2ZjNhOTlhNjJlOTQ3MWQxMzQ0NjJlOWI3M2Y5NjRlMjdkIn19XX19","signatures":[{"keyid":"","sig":"MEUCIQCLODxam0Wezso8YplbO3+61xRxrKS3CHaIIFdrTH41sQIgM47+dX4nyouYrGUDQRuVBWR7LZ94hKZN+Y2HrJVeTKg=","cert":"-----BEGIN CERTIFICATE-----\nMIIHZTCCBuqgAwIBAgIUDUH5ey2xI7OWaz6A/C+zusVj1ggwCgYIKoZIzj0EAwMw\nNzEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MR4wHAYDVQQDExVzaWdzdG9yZS1pbnRl\ncm1lZGlhdGUwHhcNMjQwNzE1MDgwNzQ3WhcNMjQwNzE1MDgxNzQ3WjAAMFkwEwYH\nKoZIzj0CAQYIKoZIzj0DAQcDQgAE42xmecC8PeHBbWcs62w6GvfKN6l4DOb2OK6l\ndRJkumaLhoRBCeUr2Vepg0Ctb6F4IlUseEbs73Ok5jjFsPP2d6OCBgkwggYFMA4G\nA1UdDwEB/wQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcDAzAdBgNVHQ4EFgQUtGqb\nUSYMw5H3UeHGQ9Ok7OpzPLowHwYDVR0jBBgwFoAU39Ppz1YkEZb5qNjpKFWixi4Y\nZD8wgYQGA1UdEQEB/wR6MHiGdmh0dHBzOi8vZ2l0aHViLmNvbS9zbHNhLWZyYW1l\nd29yay9zbHNhLWdpdGh1Yi1nZW5lcmF0b3IvLmdpdGh1Yi93b3JrZmxvd3MvZ2Vu\nZXJhdG9yX2dlbmVyaWNfc2xzYTMueW1sQHJlZnMvdGFncy92Mi4wLjAwOQYKKwYB\nBAGDvzABAQQraHR0cHM6Ly90b2tlbi5hY3Rpb25zLmdpdGh1YnVzZXJjb250ZW50\nLmNvbTAWBgorBgEEAYO/MAECBAhzY2hlZHVsZTA2BgorBgEEAYO/MAEDBCg0M2Qx\nMzc2ZjNhOTlhNjJlOTQ3MWQxMzQ0NjJlOWI3M2Y5NjRlMjdkMBkGCisGAQQBg78w\nAQQEC1ByZS1SZWxlYXNlMDUGCisGAQQBg78wAQUEJ2F3cy1wb3dlcnRvb2xzL3Bv\nd2VydG9vbHMtbGFtYmRhLXB5dGhvbjAgBgorBgEEAYO/MAEGBBJyZWZzL2hlYWRz\nL2RldmVsb3AwOwYKKwYBBAGDvzABCAQtDCtodHRwczovL3Rva2VuLmFjdGlvbnMu\nZ2l0aHVidXNlcmNvbnRlbnQuY29tMIGGBgorBgEEAYO/MAEJBHgMdmh0dHBzOi8v\nZ2l0aHViLmNvbS9zbHNhLWZyYW1ld29yay9zbHNhLWdpdGh1Yi1nZW5lcmF0b3Iv\nLmdpdGh1Yi93b3JrZmxvd3MvZ2VuZXJhdG9yX2dlbmVyaWNfc2xzYTMueW1sQHJl\nZnMvdGFncy92Mi4wLjAwOAYKKwYBBAGDvzABCgQqDCg1YTc3NWIzNjdhNTZkNWJk\nMTE4YTIyNGE4MTFiYmEyODgxNTBhNTYzMB0GCisGAQQBg78wAQsEDwwNZ2l0aHVi\nLWhvc3RlZDBKBgorBgEEAYO/MAEMBDwMOmh0dHBzOi8vZ2l0aHViLmNvbS9hd3Mt\ncG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24wOAYKKwYBBAGDvzAB\nDQQqDCg0M2QxMzc2ZjNhOTlhNjJlOTQ3MWQxMzQ0NjJlOWI3M2Y5NjRlMjdkMCIG\nCisGAQQBg78wAQ4EFAwScmVmcy9oZWFkcy9kZXZlbG9wMBkGCisGAQQBg78wAQ8E\nCwwJMjIxOTE5Mzc5MDEGCisGAQQBg78wARAEIwwhaHR0cHM6Ly9naXRodWIuY29t\nL2F3cy1wb3dlcnRvb2xzMBkGCisGAQQBg78wAREECwwJMTI5MTI3NjM4MH8GCisG\nAQQBg78wARIEcQxvaHR0cHM6Ly9naXRodWIuY29tL2F3cy1wb3dlcnRvb2xzL3Bv\nd2VydG9vbHMtbGFtYmRhLXB5dGhvbi8uZ2l0aHViL3dvcmtmbG93cy9wcmUtcmVs\nZWFzZS55bWxAcmVmcy9oZWFkcy9kZXZlbG9wMDgGCisGAQQBg78wARMEKgwoNDNk\nMTM3NmYzYTk5YTYyZTk0NzFkMTM0NDYyZTliNzNmOTY0ZTI3ZDAYBgorBgEEAYO/\nMAEUBAoMCHNjaGVkdWxlMG0GCisGAQQBg78wARUEXwxdaHR0cHM6Ly9naXRodWIu\nY29tL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9hY3Rp\nb25zL3J1bnMvOTkzNTc3NzY0OS9hdHRlbXB0cy8xMBYGCisGAQQBg78wARYECAwG\ncHVibGljMIGJBgorBgEEAdZ5AgQCBHsEeQB3AHUA3T0wasbHETJjGR4cmWc3AqJK\nXrjePK3/h4pygC8p7o4AAAGQtW77kwAABAMARjBEAiASKE9P5H/xw2HBqzRG3GEk\nwMD/9id2R1+QetSJVBQcCwIgV2F0enzjQAW0xOtrV/vLUytPKnZjLTFft6HyxUTa\nUwMwCgYIKoZIzj0EAwMDaQAwZgIxAJcETokJIlBhFdiyaP1qMIt40Z16zjUHD4Ov\nXi22zBHRVyMRqR8V3kJODYdYEmgU4AIxAP2q8B7gl7dWbYGl6KZ5P/2nPyop1b0Q\nnIZQTrPVamJlzB3wpvwP78qLF8rpDwsHPQ==\n-----END CERTIFICATE-----\n"}]}
\ No newline at end of file
diff --git a/provenance/2.41.1a2/multiple.intoto.jsonl b/provenance/2.41.1a2/multiple.intoto.jsonl
new file mode 100644
index 00000000000..a79fd50204c
--- /dev/null
+++ b/provenance/2.41.1a2/multiple.intoto.jsonl
@@ -0,0 +1 @@
+{"payloadType":"application/vnd.in-toto+json","payload":"eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjAuMSIsInByZWRpY2F0ZVR5cGUiOiJodHRwczovL3Nsc2EuZGV2L3Byb3ZlbmFuY2UvdjAuMiIsInN1YmplY3QiOlt7Im5hbWUiOiIuL2F3c19sYW1iZGFfcG93ZXJ0b29scy0yLjQxLjFhMi1weTMtbm9uZS1hbnkud2hsIiwiZGlnZXN0Ijp7InNoYTI1NiI6IjIxYzFmNjJlOGVlN2FmYzg1MzkzZDU3M2FlODVjZDkyNDY3ODU2MWJiMmYxZWEyMzljY2ZlNTMyMGY1NTc4YTYifX0seyJuYW1lIjoiLi9hd3NfbGFtYmRhX3Bvd2VydG9vbHMtMi40MS4xYTIudGFyLmd6IiwiZGlnZXN0Ijp7InNoYTI1NiI6ImIxZGJkZTUyMDZlZTExNjhmYzFmMDdiNTkyYTkwMDAzMGUwMWMwY2Y4NmE1MmNkMjI3N2YyMTNkYTg5NjQ0NWUifX1dLCJwcmVkaWNhdGUiOnsiYnVpbGRlciI6eyJpZCI6Imh0dHBzOi8vZ2l0aHViLmNvbS9zbHNhLWZyYW1ld29yay9zbHNhLWdpdGh1Yi1nZW5lcmF0b3IvLmdpdGh1Yi93b3JrZmxvd3MvZ2VuZXJhdG9yX2dlbmVyaWNfc2xzYTMueW1sQHJlZnMvdGFncy92Mi4wLjAifSwiYnVpbGRUeXBlIjoiaHR0cHM6Ly9naXRodWIuY29tL3Nsc2EtZnJhbWV3b3JrL3Nsc2EtZ2l0aHViLWdlbmVyYXRvci9nZW5lcmljQHYxIiwiaW52b2NhdGlvbiI6eyJjb25maWdTb3VyY2UiOnsidXJpIjoiZ2l0K2h0dHBzOi8vZ2l0aHViLmNvbS9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob25AcmVmcy9oZWFkcy9kZXZlbG9wIiwiZGlnZXN0Ijp7InNoYTEiOiJiOGI0NjMyZjdkY2ZmMWI0NWFiZDc2YjYxMTIwMjEzZGMyN2QxY2VhIn0sImVudHJ5UG9pbnQiOiIuZ2l0aHViL3dvcmtmbG93cy9wcmUtcmVsZWFzZS55bWwifSwicGFyYW1ldGVycyI6eyJldmVudF9pbnB1dHMiOnsic2tpcF9jb2RlX3F1YWxpdHkiOiJmYWxzZSIsInNraXBfcHlwaSI6ImZhbHNlIn19LCJlbnZpcm9ubWVudCI6eyJnaXRodWJfYWN0b3IiOiJsZWFuZHJvZGFtYXNjZW5hIiwiZ2l0aHViX2FjdG9yX2lkIjoiNDI5NTE3MyIsImdpdGh1Yl9iYXNlX3JlZiI6IiIsImdpdGh1Yl9ldmVudF9uYW1lIjoid29ya2Zsb3dfZGlzcGF0Y2giLCJnaXRodWJfZXZlbnRfcGF5bG9hZCI6eyJlbnRlcnByaXNlIjp7ImF2YXRhcl91cmwiOiJodHRwczovL2F2YXRhcnMuZ2l0aHVidXNlcmNvbnRlbnQuY29tL2IvMTI5MD92PTQiLCJjcmVhdGVkX2F0IjoiMjAxOS0xMS0xM1QxODowNTo0MVoiLCJkZXNjcmlwdGlvbiI6IiIsImh0bWxfdXJsIjoiaHR0cHM6Ly9naXRodWIuY29tL2VudGVycHJpc2VzL2FtYXpvbiIsImlkIjoxMjkwLCJuYW1lIjoiQW1hem9uIiwibm9kZV9pZCI6Ik1ERXdPa1Z1ZEdWeWNISnBjMlV4TWprdyIsInNsdWciOiJhbWF6b24iLCJ1cGRhdGVkX2F0IjoiMjAyMy0wMS0yN1QxNDo1NjoxMFoiLCJ3ZWJzaXRlX3VybCI6Imh0dHBzOi8vd3d3LmFtYXpvbi5jb20vIn0sImlucHV0cyI6eyJza2lwX2NvZGVfcXVhbGl0eSI6ImZhbHNlIiwic2tpcF9weXBpIjoiZmFsc2UifSwib3JnYW5pemF0aW9uIjp7ImF2YXRhcl91cmwiOiJodHRwczovL2F2YXRhcnMuZ2l0aHVidXNlcmNvbnRlbnQuY29tL3UvMTI5MTI3NjM4P3Y9NCIsImRlc2NyaXB0aW9uIjoiIiwiZXZlbnRzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vb3Jncy9hd3MtcG93ZXJ0b29scy9ldmVudHMiLCJob29rc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL29yZ3MvYXdzLXBvd2VydG9vbHMvaG9va3MiLCJpZCI6MTI5MTI3NjM4LCJpc3N1ZXNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9vcmdzL2F3cy1wb3dlcnRvb2xzL2lzc3VlcyIsImxvZ2luIjoiYXdzLXBvd2VydG9vbHMiLCJtZW1iZXJzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vb3Jncy9hd3MtcG93ZXJ0b29scy9tZW1iZXJzey9tZW1iZXJ9Iiwibm9kZV9pZCI6Ik9fa2dET0I3SlUxZyIsInB1YmxpY19tZW1iZXJzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vb3Jncy9hd3MtcG93ZXJ0b29scy9wdWJsaWNfbWVtYmVyc3svbWVtYmVyfSIsInJlcG9zX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vb3Jncy9hd3MtcG93ZXJ0b29scy9yZXBvcyIsInVybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vb3Jncy9hd3MtcG93ZXJ0b29scyJ9LCJyZWYiOiJyZWZzL2hlYWRzL2RldmVsb3AiLCJyZXBvc2l0b3J5Ijp7ImFsbG93X2ZvcmtpbmciOnRydWUsImFyY2hpdmVfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24ve2FyY2hpdmVfZm9ybWF0fXsvcmVmfSIsImFyY2hpdmVkIjpmYWxzZSwiYXNzaWduZWVzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2Fzc2lnbmVlc3svdXNlcn0iLCJibG9ic191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9naXQvYmxvYnN7L3NoYX0iLCJicmFuY2hlc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9icmFuY2hlc3svYnJhbmNofSIsImNsb25lX3VybCI6Imh0dHBzOi8vZ2l0aHViLmNvbS9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24uZ2l0IiwiY29sbGFib3JhdG9yc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9jb2xsYWJvcmF0b3Jzey9jb2xsYWJvcmF0b3J9IiwiY29tbWVudHNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vY29tbWVudHN7L251bWJlcn0iLCJjb21taXRzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2NvbW1pdHN7L3NoYX0iLCJjb21wYXJlX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2NvbXBhcmUve2Jhc2V9Li4ue2hlYWR9IiwiY29udGVudHNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vY29udGVudHMveytwYXRofSIsImNvbnRyaWJ1dG9yc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9jb250cmlidXRvcnMiLCJjcmVhdGVkX2F0IjoiMjAxOS0xMS0xNVQxMjoyNjoxMloiLCJjdXN0b21fcHJvcGVydGllcyI6e30sImRlZmF1bHRfYnJhbmNoIjoiZGV2ZWxvcCIsImRlcGxveW1lbnRzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2RlcGxveW1lbnRzIiwiZGVzY3JpcHRpb24iOiJBIGRldmVsb3BlciB0b29sa2l0IHRvIGltcGxlbWVudCBTZXJ2ZXJsZXNzIGJlc3QgcHJhY3RpY2VzIGFuZCBpbmNyZWFzZSBkZXZlbG9wZXIgdmVsb2NpdHkuIiwiZGlzYWJsZWQiOmZhbHNlLCJkb3dubG9hZHNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vZG93bmxvYWRzIiwiZXZlbnRzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2V2ZW50cyIsImZvcmsiOmZhbHNlLCJmb3JrcyI6Mzc4LCJmb3Jrc19jb3VudCI6Mzc4LCJmb3Jrc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9mb3JrcyIsImZ1bGxfbmFtZSI6ImF3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbiIsImdpdF9jb21taXRzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2dpdC9jb21taXRzey9zaGF9IiwiZ2l0X3JlZnNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vZ2l0L3JlZnN7L3NoYX0iLCJnaXRfdGFnc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9naXQvdGFnc3svc2hhfSIsImdpdF91cmwiOiJnaXQ6Ly9naXRodWIuY29tL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi5naXQiLCJoYXNfZGlzY3Vzc2lvbnMiOnRydWUsImhhc19kb3dubG9hZHMiOnRydWUsImhhc19pc3N1ZXMiOnRydWUsImhhc19wYWdlcyI6ZmFsc2UsImhhc19wcm9qZWN0cyI6dHJ1ZSwiaGFzX3dpa2kiOmZhbHNlLCJob21lcGFnZSI6Imh0dHBzOi8vZG9jcy5wb3dlcnRvb2xzLmF3cy5kZXYvbGFtYmRhL3B5dGhvbi9sYXRlc3QvIiwiaG9va3NfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vaG9va3MiLCJodG1sX3VybCI6Imh0dHBzOi8vZ2l0aHViLmNvbS9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24iLCJpZCI6MjIxOTE5Mzc5LCJpc190ZW1wbGF0ZSI6ZmFsc2UsImlzc3VlX2NvbW1lbnRfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vaXNzdWVzL2NvbW1lbnRzey9udW1iZXJ9IiwiaXNzdWVfZXZlbnRzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2lzc3Vlcy9ldmVudHN7L251bWJlcn0iLCJpc3N1ZXNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vaXNzdWVzey9udW1iZXJ9Iiwia2V5c191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9rZXlzey9rZXlfaWR9IiwibGFiZWxzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2xhYmVsc3svbmFtZX0iLCJsYW5ndWFnZSI6IlB5dGhvbiIsImxhbmd1YWdlc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9sYW5ndWFnZXMiLCJsaWNlbnNlIjp7ImtleSI6Im1pdC0wIiwibmFtZSI6Ik1JVCBObyBBdHRyaWJ1dGlvbiIsIm5vZGVfaWQiOiJNRGM2VEdsalpXNXpaVFF4Iiwic3BkeF9pZCI6Ik1JVC0wIiwidXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9saWNlbnNlcy9taXQtMCJ9LCJtZXJnZXNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vbWVyZ2VzIiwibWlsZXN0b25lc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9taWxlc3RvbmVzey9udW1iZXJ9IiwibWlycm9yX3VybCI6bnVsbCwibmFtZSI6InBvd2VydG9vbHMtbGFtYmRhLXB5dGhvbiIsIm5vZGVfaWQiOiJNREV3T2xKbGNHOXphWFJ2Y25reU1qRTVNVGt6TnprPSIsIm5vdGlmaWNhdGlvbnNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vbm90aWZpY2F0aW9uc3s/c2luY2UsYWxsLHBhcnRpY2lwYXRpbmd9Iiwib3Blbl9pc3N1ZXMiOjk2LCJvcGVuX2lzc3Vlc19jb3VudCI6OTYsIm93bmVyIjp7ImF2YXRhcl91cmwiOiJodHRwczovL2F2YXRhcnMuZ2l0aHVidXNlcmNvbnRlbnQuY29tL3UvMTI5MTI3NjM4P3Y9NCIsImV2ZW50c191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3VzZXJzL2F3cy1wb3dlcnRvb2xzL2V2ZW50c3svcHJpdmFjeX0iLCJmb2xsb3dlcnNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS91c2Vycy9hd3MtcG93ZXJ0b29scy9mb2xsb3dlcnMiLCJmb2xsb3dpbmdfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS91c2Vycy9hd3MtcG93ZXJ0b29scy9mb2xsb3dpbmd7L290aGVyX3VzZXJ9IiwiZ2lzdHNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS91c2Vycy9hd3MtcG93ZXJ0b29scy9naXN0c3svZ2lzdF9pZH0iLCJncmF2YXRhcl9pZCI6IiIsImh0bWxfdXJsIjoiaHR0cHM6Ly9naXRodWIuY29tL2F3cy1wb3dlcnRvb2xzIiwiaWQiOjEyOTEyNzYzOCwibG9naW4iOiJhd3MtcG93ZXJ0b29scyIsIm5vZGVfaWQiOiJPX2tnRE9CN0pVMWciLCJvcmdhbml6YXRpb25zX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vdXNlcnMvYXdzLXBvd2VydG9vbHMvb3JncyIsInJlY2VpdmVkX2V2ZW50c191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3VzZXJzL2F3cy1wb3dlcnRvb2xzL3JlY2VpdmVkX2V2ZW50cyIsInJlcG9zX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vdXNlcnMvYXdzLXBvd2VydG9vbHMvcmVwb3MiLCJzaXRlX2FkbWluIjpmYWxzZSwic3RhcnJlZF91cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3VzZXJzL2F3cy1wb3dlcnRvb2xzL3N0YXJyZWR7L293bmVyfXsvcmVwb30iLCJzdWJzY3JpcHRpb25zX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vdXNlcnMvYXdzLXBvd2VydG9vbHMvc3Vic2NyaXB0aW9ucyIsInR5cGUiOiJPcmdhbml6YXRpb24iLCJ1cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3VzZXJzL2F3cy1wb3dlcnRvb2xzIn0sInByaXZhdGUiOmZhbHNlLCJwdWxsc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9wdWxsc3svbnVtYmVyfSIsInB1c2hlZF9hdCI6IjIwMjQtMDctMTVUMTA6MTQ6NDdaIiwicmVsZWFzZXNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vcmVsZWFzZXN7L2lkfSIsInNpemUiOjQ3NTA0LCJzc2hfdXJsIjoiZ2l0QGdpdGh1Yi5jb206YXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uLmdpdCIsInN0YXJnYXplcnNfY291bnQiOjI3MjksInN0YXJnYXplcnNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vc3RhcmdhemVycyIsInN0YXR1c2VzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL3N0YXR1c2VzL3tzaGF9Iiwic3Vic2NyaWJlcnNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vc3Vic2NyaWJlcnMiLCJzdWJzY3JpcHRpb25fdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vc3Vic2NyaXB0aW9uIiwic3ZuX3VybCI6Imh0dHBzOi8vZ2l0aHViLmNvbS9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24iLCJ0YWdzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL3RhZ3MiLCJ0ZWFtc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi90ZWFtcyIsInRvcGljcyI6WyJhd3MiLCJhd3MtbGFtYmRhIiwiaGFja3RvYmVyZmVzdCIsImxhbWJkYSIsInB5dGhvbiIsInNlcnZlcmxlc3MiXSwidHJlZXNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vZ2l0L3RyZWVzey9zaGF9IiwidXBkYXRlZF9hdCI6IjIwMjQtMDctMTVUMTA6MjM6NDZaIiwidXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24iLCJ2aXNpYmlsaXR5IjoicHVibGljIiwid2F0Y2hlcnMiOjI3MjksIndhdGNoZXJzX2NvdW50IjoyNzI5LCJ3ZWJfY29tbWl0X3NpZ25vZmZfcmVxdWlyZWQiOnRydWV9LCJzZW5kZXIiOnsiYXZhdGFyX3VybCI6Imh0dHBzOi8vYXZhdGFycy5naXRodWJ1c2VyY29udGVudC5jb20vdS80Mjk1MTczP3Y9NCIsImV2ZW50c191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3VzZXJzL2xlYW5kcm9kYW1hc2NlbmEvZXZlbnRzey9wcml2YWN5fSIsImZvbGxvd2Vyc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3VzZXJzL2xlYW5kcm9kYW1hc2NlbmEvZm9sbG93ZXJzIiwiZm9sbG93aW5nX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vdXNlcnMvbGVhbmRyb2RhbWFzY2VuYS9mb2xsb3dpbmd7L290aGVyX3VzZXJ9IiwiZ2lzdHNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS91c2Vycy9sZWFuZHJvZGFtYXNjZW5hL2dpc3Rzey9naXN0X2lkfSIsImdyYXZhdGFyX2lkIjoiIiwiaHRtbF91cmwiOiJodHRwczovL2dpdGh1Yi5jb20vbGVhbmRyb2RhbWFzY2VuYSIsImlkIjo0Mjk1MTczLCJsb2dpbiI6ImxlYW5kcm9kYW1hc2NlbmEiLCJub2RlX2lkIjoiTURRNlZYTmxjalF5T1RVeE56TT0iLCJvcmdhbml6YXRpb25zX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vdXNlcnMvbGVhbmRyb2RhbWFzY2VuYS9vcmdzIiwicmVjZWl2ZWRfZXZlbnRzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vdXNlcnMvbGVhbmRyb2RhbWFzY2VuYS9yZWNlaXZlZF9ldmVudHMiLCJyZXBvc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3VzZXJzL2xlYW5kcm9kYW1hc2NlbmEvcmVwb3MiLCJzaXRlX2FkbWluIjpmYWxzZSwic3RhcnJlZF91cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3VzZXJzL2xlYW5kcm9kYW1hc2NlbmEvc3RhcnJlZHsvb3duZXJ9ey9yZXBvfSIsInN1YnNjcmlwdGlvbnNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS91c2Vycy9sZWFuZHJvZGFtYXNjZW5hL3N1YnNjcmlwdGlvbnMiLCJ0eXBlIjoiVXNlciIsInVybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vdXNlcnMvbGVhbmRyb2RhbWFzY2VuYSJ9LCJ3b3JrZmxvdyI6Ii5naXRodWIvd29ya2Zsb3dzL3ByZS1yZWxlYXNlLnltbCJ9LCJnaXRodWJfaGVhZF9yZWYiOiIiLCJnaXRodWJfcmVmIjoicmVmcy9oZWFkcy9kZXZlbG9wIiwiZ2l0aHViX3JlZl90eXBlIjoiYnJhbmNoIiwiZ2l0aHViX3JlcG9zaXRvcnlfaWQiOiIyMjE5MTkzNzkiLCJnaXRodWJfcmVwb3NpdG9yeV9vd25lciI6ImF3cy1wb3dlcnRvb2xzIiwiZ2l0aHViX3JlcG9zaXRvcnlfb3duZXJfaWQiOiIxMjkxMjc2MzgiLCJnaXRodWJfcnVuX2F0dGVtcHQiOiIxIiwiZ2l0aHViX3J1bl9pZCI6Ijk5Mzc5MDkxMzUiLCJnaXRodWJfcnVuX251bWJlciI6IjIwIiwiZ2l0aHViX3NoYTEiOiJiOGI0NjMyZjdkY2ZmMWI0NWFiZDc2YjYxMTIwMjEzZGMyN2QxY2VhIn19LCJtZXRhZGF0YSI6eyJidWlsZEludm9jYXRpb25JRCI6Ijk5Mzc5MDkxMzUtMSIsImNvbXBsZXRlbmVzcyI6eyJwYXJhbWV0ZXJzIjp0cnVlLCJlbnZpcm9ubWVudCI6ZmFsc2UsIm1hdGVyaWFscyI6ZmFsc2V9LCJyZXByb2R1Y2libGUiOmZhbHNlfSwibWF0ZXJpYWxzIjpbeyJ1cmkiOiJnaXQraHR0cHM6Ly9naXRodWIuY29tL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbkByZWZzL2hlYWRzL2RldmVsb3AiLCJkaWdlc3QiOnsic2hhMSI6ImI4YjQ2MzJmN2RjZmYxYjQ1YWJkNzZiNjExMjAyMTNkYzI3ZDFjZWEifX1dfX0=","signatures":[{"keyid":"","sig":"MEQCIBFVwZdrOE9QFT5dMWMiDgT9ZjgkMjeDFdWT4S2nPhMdAiAweZ+59wXIJeHUoVFmgRrgnGFhOsOtTHsyazRBVfymHg==","cert":"-----BEGIN CERTIFICATE-----\nMIIHdTCCBvygAwIBAgIUXw/KVqUQXYL8oqMlroc7xDTNaU8wCgYIKoZIzj0EAwMw\nNzEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MR4wHAYDVQQDExVzaWdzdG9yZS1pbnRl\ncm1lZGlhdGUwHhcNMjQwNzE1MTA0MjA3WhcNMjQwNzE1MTA1MjA3WjAAMFkwEwYH\nKoZIzj0CAQYIKoZIzj0DAQcDQgAEwt7uyecfIv0SIM1Pdmv5cIBHujndYNZ3U9MW\n0KPKWa7OaAaJVkVusQuS0A4gnD+FtM0TM/zM7VfOl0GIfD/ZD6OCBhswggYXMA4G\nA1UdDwEB/wQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcDAzAdBgNVHQ4EFgQUrvlP\n9Ykg+c6Ow0ult///U/MJ3O8wHwYDVR0jBBgwFoAU39Ppz1YkEZb5qNjpKFWixi4Y\nZD8wgYQGA1UdEQEB/wR6MHiGdmh0dHBzOi8vZ2l0aHViLmNvbS9zbHNhLWZyYW1l\nd29yay9zbHNhLWdpdGh1Yi1nZW5lcmF0b3IvLmdpdGh1Yi93b3JrZmxvd3MvZ2Vu\nZXJhdG9yX2dlbmVyaWNfc2xzYTMueW1sQHJlZnMvdGFncy92Mi4wLjAwOQYKKwYB\nBAGDvzABAQQraHR0cHM6Ly90b2tlbi5hY3Rpb25zLmdpdGh1YnVzZXJjb250ZW50\nLmNvbTAfBgorBgEEAYO/MAECBBF3b3JrZmxvd19kaXNwYXRjaDA2BgorBgEEAYO/\nMAEDBChiOGI0NjMyZjdkY2ZmMWI0NWFiZDc2YjYxMTIwMjEzZGMyN2QxY2VhMBkG\nCisGAQQBg78wAQQEC1ByZS1SZWxlYXNlMDUGCisGAQQBg78wAQUEJ2F3cy1wb3dl\ncnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbjAgBgorBgEEAYO/MAEGBBJy\nZWZzL2hlYWRzL2RldmVsb3AwOwYKKwYBBAGDvzABCAQtDCtodHRwczovL3Rva2Vu\nLmFjdGlvbnMuZ2l0aHVidXNlcmNvbnRlbnQuY29tMIGGBgorBgEEAYO/MAEJBHgM\ndmh0dHBzOi8vZ2l0aHViLmNvbS9zbHNhLWZyYW1ld29yay9zbHNhLWdpdGh1Yi1n\nZW5lcmF0b3IvLmdpdGh1Yi93b3JrZmxvd3MvZ2VuZXJhdG9yX2dlbmVyaWNfc2xz\nYTMueW1sQHJlZnMvdGFncy92Mi4wLjAwOAYKKwYBBAGDvzABCgQqDCg1YTc3NWIz\nNjdhNTZkNWJkMTE4YTIyNGE4MTFiYmEyODgxNTBhNTYzMB0GCisGAQQBg78wAQsE\nDwwNZ2l0aHViLWhvc3RlZDBKBgorBgEEAYO/MAEMBDwMOmh0dHBzOi8vZ2l0aHVi\nLmNvbS9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24wOAYK\nKwYBBAGDvzABDQQqDChiOGI0NjMyZjdkY2ZmMWI0NWFiZDc2YjYxMTIwMjEzZGMy\nN2QxY2VhMCIGCisGAQQBg78wAQ4EFAwScmVmcy9oZWFkcy9kZXZlbG9wMBkGCisG\nAQQBg78wAQ8ECwwJMjIxOTE5Mzc5MDEGCisGAQQBg78wARAEIwwhaHR0cHM6Ly9n\naXRodWIuY29tL2F3cy1wb3dlcnRvb2xzMBkGCisGAQQBg78wAREECwwJMTI5MTI3\nNjM4MH8GCisGAQQBg78wARIEcQxvaHR0cHM6Ly9naXRodWIuY29tL2F3cy1wb3dl\ncnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi8uZ2l0aHViL3dvcmtmbG93\ncy9wcmUtcmVsZWFzZS55bWxAcmVmcy9oZWFkcy9kZXZlbG9wMDgGCisGAQQBg78w\nARMEKgwoYjhiNDYzMmY3ZGNmZjFiNDVhYmQ3NmI2MTEyMDIxM2RjMjdkMWNlYTAh\nBgorBgEEAYO/MAEUBBMMEXdvcmtmbG93X2Rpc3BhdGNoMG0GCisGAQQBg78wARUE\nXwxdaHR0cHM6Ly9naXRodWIuY29tL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMt\nbGFtYmRhLXB5dGhvbi9hY3Rpb25zL3J1bnMvOTkzNzkwOTEzNS9hdHRlbXB0cy8x\nMBYGCisGAQQBg78wARYECAwGcHVibGljMIGJBgorBgEEAdZ5AgQCBHsEeQB3AHUA\n3T0wasbHETJjGR4cmWc3AqJKXrjePK3/h4pygC8p7o4AAAGQtfxFpAAABAMARjBE\nAiAmDG3mXSW4IphIowaW/jGS5FaB7Uh42Qw+ri5H0zR4OAIgbo4NwNkhyjvMYxEw\nxdaHgSaKaQOd1PPGFBRJcKqklHswCgYIKoZIzj0EAwMDZwAwZAIwa/hrsymSMG1Y\niIv/yQq7Vjw+aQC7BrF3blq2byzriHdSNohJS2SiOWrnc7OcJfw+AjBWyhqOj3sD\neyLy0T2TK8t7kJSg/EO74ExPx6cSZXnwBfei753/qAaPiYE7Vs7API4=\n-----END CERTIFICATE-----\n"}]}
\ No newline at end of file
diff --git a/provenance/2.41.1a3/multiple.intoto.jsonl b/provenance/2.41.1a3/multiple.intoto.jsonl
new file mode 100644
index 00000000000..33c2bbe12b9
--- /dev/null
+++ b/provenance/2.41.1a3/multiple.intoto.jsonl
@@ -0,0 +1 @@
+{"payloadType":"application/vnd.in-toto+json","payload":"eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjAuMSIsInByZWRpY2F0ZVR5cGUiOiJodHRwczovL3Nsc2EuZGV2L3Byb3ZlbmFuY2UvdjAuMiIsInN1YmplY3QiOlt7Im5hbWUiOiIuL2F3c19sYW1iZGFfcG93ZXJ0b29scy0yLjQxLjFhMy1weTMtbm9uZS1hbnkud2hsIiwiZGlnZXN0Ijp7InNoYTI1NiI6ImNlZTY4MDUxOGJkZDMwYmFiMjI3NjdjOTFlYWMzNmNmNmU5NDU1ZmVhZGZkMzIzYzRmNTI0YmU0YzFhODVkMTMifX0seyJuYW1lIjoiLi9hd3NfbGFtYmRhX3Bvd2VydG9vbHMtMi40MS4xYTMudGFyLmd6IiwiZGlnZXN0Ijp7InNoYTI1NiI6ImUxMDk4ZTczMmFkYTk3Y2UyZjcyNmU3ZDQ3NjVlMDc5ZjMzYjU2NmMyM2M2N2ViMzFkZjYzNDA3NjkxNGViZDQifX1dLCJwcmVkaWNhdGUiOnsiYnVpbGRlciI6eyJpZCI6Imh0dHBzOi8vZ2l0aHViLmNvbS9zbHNhLWZyYW1ld29yay9zbHNhLWdpdGh1Yi1nZW5lcmF0b3IvLmdpdGh1Yi93b3JrZmxvd3MvZ2VuZXJhdG9yX2dlbmVyaWNfc2xzYTMueW1sQHJlZnMvdGFncy92Mi4wLjAifSwiYnVpbGRUeXBlIjoiaHR0cHM6Ly9naXRodWIuY29tL3Nsc2EtZnJhbWV3b3JrL3Nsc2EtZ2l0aHViLWdlbmVyYXRvci9nZW5lcmljQHYxIiwiaW52b2NhdGlvbiI6eyJjb25maWdTb3VyY2UiOnsidXJpIjoiZ2l0K2h0dHBzOi8vZ2l0aHViLmNvbS9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob25AcmVmcy9oZWFkcy9kZXZlbG9wIiwiZGlnZXN0Ijp7InNoYTEiOiJmZTZhZDdlYzU3NDljMmIxMjc1YjI4NjI0NmQwNmY5MzQ0M2JhNGFhIn0sImVudHJ5UG9pbnQiOiIuZ2l0aHViL3dvcmtmbG93cy9wcmUtcmVsZWFzZS55bWwifSwicGFyYW1ldGVycyI6e30sImVudmlyb25tZW50Ijp7ImdpdGh1Yl9hY3RvciI6ImxlYW5kcm9kYW1hc2NlbmEiLCJnaXRodWJfYWN0b3JfaWQiOiI0Mjk1MTczIiwiZ2l0aHViX2Jhc2VfcmVmIjoiIiwiZ2l0aHViX2V2ZW50X25hbWUiOiJzY2hlZHVsZSIsImdpdGh1Yl9ldmVudF9wYXlsb2FkIjp7ImVudGVycHJpc2UiOnsiYXZhdGFyX3VybCI6Imh0dHBzOi8vYXZhdGFycy5naXRodWJ1c2VyY29udGVudC5jb20vYi8xMjkwP3Y9NCIsImNyZWF0ZWRfYXQiOiIyMDE5LTExLTEzVDE4OjA1OjQxWiIsImRlc2NyaXB0aW9uIjoiIiwiaHRtbF91cmwiOiJodHRwczovL2dpdGh1Yi5jb20vZW50ZXJwcmlzZXMvYW1hem9uIiwiaWQiOjEyOTAsIm5hbWUiOiJBbWF6b24iLCJub2RlX2lkIjoiTURFd09rVnVkR1Z5Y0hKcGMyVXhNamt3Iiwic2x1ZyI6ImFtYXpvbiIsInVwZGF0ZWRfYXQiOiIyMDIzLTAxLTI3VDE0OjU2OjEwWiIsIndlYnNpdGVfdXJsIjoiaHR0cHM6Ly93d3cuYW1hem9uLmNvbS8ifSwib3JnYW5pemF0aW9uIjp7ImF2YXRhcl91cmwiOiJodHRwczovL2F2YXRhcnMuZ2l0aHVidXNlcmNvbnRlbnQuY29tL3UvMTI5MTI3NjM4P3Y9NCIsImRlc2NyaXB0aW9uIjoiIiwiZXZlbnRzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vb3Jncy9hd3MtcG93ZXJ0b29scy9ldmVudHMiLCJob29rc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL29yZ3MvYXdzLXBvd2VydG9vbHMvaG9va3MiLCJpZCI6MTI5MTI3NjM4LCJpc3N1ZXNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9vcmdzL2F3cy1wb3dlcnRvb2xzL2lzc3VlcyIsImxvZ2luIjoiYXdzLXBvd2VydG9vbHMiLCJtZW1iZXJzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vb3Jncy9hd3MtcG93ZXJ0b29scy9tZW1iZXJzey9tZW1iZXJ9Iiwibm9kZV9pZCI6Ik9fa2dET0I3SlUxZyIsInB1YmxpY19tZW1iZXJzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vb3Jncy9hd3MtcG93ZXJ0b29scy9wdWJsaWNfbWVtYmVyc3svbWVtYmVyfSIsInJlcG9zX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vb3Jncy9hd3MtcG93ZXJ0b29scy9yZXBvcyIsInVybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vb3Jncy9hd3MtcG93ZXJ0b29scyJ9LCJyZXBvc2l0b3J5Ijp7ImFsbG93X2ZvcmtpbmciOnRydWUsImFyY2hpdmVfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24ve2FyY2hpdmVfZm9ybWF0fXsvcmVmfSIsImFyY2hpdmVkIjpmYWxzZSwiYXNzaWduZWVzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2Fzc2lnbmVlc3svdXNlcn0iLCJibG9ic191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9naXQvYmxvYnN7L3NoYX0iLCJicmFuY2hlc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9icmFuY2hlc3svYnJhbmNofSIsImNsb25lX3VybCI6Imh0dHBzOi8vZ2l0aHViLmNvbS9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24uZ2l0IiwiY29sbGFib3JhdG9yc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9jb2xsYWJvcmF0b3Jzey9jb2xsYWJvcmF0b3J9IiwiY29tbWVudHNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vY29tbWVudHN7L251bWJlcn0iLCJjb21taXRzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2NvbW1pdHN7L3NoYX0iLCJjb21wYXJlX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2NvbXBhcmUve2Jhc2V9Li4ue2hlYWR9IiwiY29udGVudHNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vY29udGVudHMveytwYXRofSIsImNvbnRyaWJ1dG9yc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9jb250cmlidXRvcnMiLCJjcmVhdGVkX2F0IjoiMjAxOS0xMS0xNVQxMjoyNjoxMloiLCJkZWZhdWx0X2JyYW5jaCI6ImRldmVsb3AiLCJkZXBsb3ltZW50c191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9kZXBsb3ltZW50cyIsImRlc2NyaXB0aW9uIjoiQSBkZXZlbG9wZXIgdG9vbGtpdCB0byBpbXBsZW1lbnQgU2VydmVybGVzcyBiZXN0IHByYWN0aWNlcyBhbmQgaW5jcmVhc2UgZGV2ZWxvcGVyIHZlbG9jaXR5LiIsImRpc2FibGVkIjpmYWxzZSwiZG93bmxvYWRzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2Rvd25sb2FkcyIsImV2ZW50c191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9ldmVudHMiLCJmb3JrIjpmYWxzZSwiZm9ya3MiOjM3OCwiZm9ya3NfY291bnQiOjM3OCwiZm9ya3NfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vZm9ya3MiLCJmdWxsX25hbWUiOiJhd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24iLCJnaXRfY29tbWl0c191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9naXQvY29tbWl0c3svc2hhfSIsImdpdF9yZWZzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2dpdC9yZWZzey9zaGF9IiwiZ2l0X3RhZ3NfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vZ2l0L3RhZ3N7L3NoYX0iLCJnaXRfdXJsIjoiZ2l0Oi8vZ2l0aHViLmNvbS9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24uZ2l0IiwiaGFzX2Rpc2N1c3Npb25zIjp0cnVlLCJoYXNfZG93bmxvYWRzIjp0cnVlLCJoYXNfaXNzdWVzIjp0cnVlLCJoYXNfcGFnZXMiOmZhbHNlLCJoYXNfcHJvamVjdHMiOnRydWUsImhhc193aWtpIjpmYWxzZSwiaG9tZXBhZ2UiOiJodHRwczovL2RvY3MucG93ZXJ0b29scy5hd3MuZGV2L2xhbWJkYS9weXRob24vbGF0ZXN0LyIsImhvb2tzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2hvb2tzIiwiaHRtbF91cmwiOiJodHRwczovL2dpdGh1Yi5jb20vYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uIiwiaWQiOjIyMTkxOTM3OSwiaXNfdGVtcGxhdGUiOmZhbHNlLCJpc3N1ZV9jb21tZW50X3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2lzc3Vlcy9jb21tZW50c3svbnVtYmVyfSIsImlzc3VlX2V2ZW50c191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9pc3N1ZXMvZXZlbnRzey9udW1iZXJ9IiwiaXNzdWVzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2lzc3Vlc3svbnVtYmVyfSIsImtleXNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24va2V5c3sva2V5X2lkfSIsImxhYmVsc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9sYWJlbHN7L25hbWV9IiwibGFuZ3VhZ2UiOiJQeXRob24iLCJsYW5ndWFnZXNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vbGFuZ3VhZ2VzIiwibGljZW5zZSI6eyJrZXkiOiJtaXQtMCIsIm5hbWUiOiJNSVQgTm8gQXR0cmlidXRpb24iLCJub2RlX2lkIjoiTURjNlRHbGpaVzV6WlRReCIsInNwZHhfaWQiOiJNSVQtMCIsInVybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vbGljZW5zZXMvbWl0LTAifSwibWVyZ2VzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL21lcmdlcyIsIm1pbGVzdG9uZXNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vbWlsZXN0b25lc3svbnVtYmVyfSIsIm1pcnJvcl91cmwiOm51bGwsIm5hbWUiOiJwb3dlcnRvb2xzLWxhbWJkYS1weXRob24iLCJub2RlX2lkIjoiTURFd09sSmxjRzl6YVhSdmNua3lNakU1TVRrek56az0iLCJub3RpZmljYXRpb25zX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL25vdGlmaWNhdGlvbnN7P3NpbmNlLGFsbCxwYXJ0aWNpcGF0aW5nfSIsIm9wZW5faXNzdWVzIjoxMDIsIm9wZW5faXNzdWVzX2NvdW50IjoxMDIsIm93bmVyIjp7ImF2YXRhcl91cmwiOiJodHRwczovL2F2YXRhcnMuZ2l0aHVidXNlcmNvbnRlbnQuY29tL3UvMTI5MTI3NjM4P3Y9NCIsImV2ZW50c191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3VzZXJzL2F3cy1wb3dlcnRvb2xzL2V2ZW50c3svcHJpdmFjeX0iLCJmb2xsb3dlcnNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS91c2Vycy9hd3MtcG93ZXJ0b29scy9mb2xsb3dlcnMiLCJmb2xsb3dpbmdfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS91c2Vycy9hd3MtcG93ZXJ0b29scy9mb2xsb3dpbmd7L290aGVyX3VzZXJ9IiwiZ2lzdHNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS91c2Vycy9hd3MtcG93ZXJ0b29scy9naXN0c3svZ2lzdF9pZH0iLCJncmF2YXRhcl9pZCI6IiIsImh0bWxfdXJsIjoiaHR0cHM6Ly9naXRodWIuY29tL2F3cy1wb3dlcnRvb2xzIiwiaWQiOjEyOTEyNzYzOCwibG9naW4iOiJhd3MtcG93ZXJ0b29scyIsIm5vZGVfaWQiOiJPX2tnRE9CN0pVMWciLCJvcmdhbml6YXRpb25zX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vdXNlcnMvYXdzLXBvd2VydG9vbHMvb3JncyIsInJlY2VpdmVkX2V2ZW50c191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3VzZXJzL2F3cy1wb3dlcnRvb2xzL3JlY2VpdmVkX2V2ZW50cyIsInJlcG9zX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vdXNlcnMvYXdzLXBvd2VydG9vbHMvcmVwb3MiLCJzaXRlX2FkbWluIjpmYWxzZSwic3RhcnJlZF91cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3VzZXJzL2F3cy1wb3dlcnRvb2xzL3N0YXJyZWR7L293bmVyfXsvcmVwb30iLCJzdWJzY3JpcHRpb25zX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vdXNlcnMvYXdzLXBvd2VydG9vbHMvc3Vic2NyaXB0aW9ucyIsInR5cGUiOiJPcmdhbml6YXRpb24iLCJ1cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3VzZXJzL2F3cy1wb3dlcnRvb2xzIn0sInByaXZhdGUiOmZhbHNlLCJwdWxsc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9wdWxsc3svbnVtYmVyfSIsInB1c2hlZF9hdCI6IjIwMjQtMDctMTVUMjE6MDQ6MzdaIiwicmVsZWFzZXNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vcmVsZWFzZXN7L2lkfSIsInNpemUiOjQ4MzM4LCJzc2hfdXJsIjoiZ2l0QGdpdGh1Yi5jb206YXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uLmdpdCIsInN0YXJnYXplcnNfY291bnQiOjI3MjksInN0YXJnYXplcnNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vc3RhcmdhemVycyIsInN0YXR1c2VzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL3N0YXR1c2VzL3tzaGF9Iiwic3Vic2NyaWJlcnNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vc3Vic2NyaWJlcnMiLCJzdWJzY3JpcHRpb25fdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vc3Vic2NyaXB0aW9uIiwic3ZuX3VybCI6Imh0dHBzOi8vZ2l0aHViLmNvbS9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24iLCJ0YWdzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL3RhZ3MiLCJ0ZWFtc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi90ZWFtcyIsInRvcGljcyI6WyJhd3MiLCJhd3MtbGFtYmRhIiwiaGFja3RvYmVyZmVzdCIsImxhbWJkYSIsInB5dGhvbiIsInNlcnZlcmxlc3MiXSwidHJlZXNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vZ2l0L3RyZWVzey9zaGF9IiwidXBkYXRlZF9hdCI6IjIwMjQtMDctMTVUMTA6NTA6MzZaIiwidXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24iLCJ2aXNpYmlsaXR5IjoicHVibGljIiwid2F0Y2hlcnMiOjI3MjksIndhdGNoZXJzX2NvdW50IjoyNzI5LCJ3ZWJfY29tbWl0X3NpZ25vZmZfcmVxdWlyZWQiOnRydWV9LCJzY2hlZHVsZSI6IjAgOCAqICogMS01Iiwid29ya2Zsb3ciOiIuZ2l0aHViL3dvcmtmbG93cy9wcmUtcmVsZWFzZS55bWwifSwiZ2l0aHViX2hlYWRfcmVmIjoiIiwiZ2l0aHViX3JlZiI6InJlZnMvaGVhZHMvZGV2ZWxvcCIsImdpdGh1Yl9yZWZfdHlwZSI6ImJyYW5jaCIsImdpdGh1Yl9yZXBvc2l0b3J5X2lkIjoiMjIxOTE5Mzc5IiwiZ2l0aHViX3JlcG9zaXRvcnlfb3duZXIiOiJhd3MtcG93ZXJ0b29scyIsImdpdGh1Yl9yZXBvc2l0b3J5X293bmVyX2lkIjoiMTI5MTI3NjM4IiwiZ2l0aHViX3J1bl9hdHRlbXB0IjoiMSIsImdpdGh1Yl9ydW5faWQiOiI5OTUzMDA2NDkwIiwiZ2l0aHViX3J1bl9udW1iZXIiOiIyMSIsImdpdGh1Yl9zaGExIjoiZmU2YWQ3ZWM1NzQ5YzJiMTI3NWIyODYyNDZkMDZmOTM0NDNiYTRhYSJ9fSwibWV0YWRhdGEiOnsiYnVpbGRJbnZvY2F0aW9uSUQiOiI5OTUzMDA2NDkwLTEiLCJjb21wbGV0ZW5lc3MiOnsicGFyYW1ldGVycyI6dHJ1ZSwiZW52aXJvbm1lbnQiOmZhbHNlLCJtYXRlcmlhbHMiOmZhbHNlfSwicmVwcm9kdWNpYmxlIjpmYWxzZX0sIm1hdGVyaWFscyI6W3sidXJpIjoiZ2l0K2h0dHBzOi8vZ2l0aHViLmNvbS9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob25AcmVmcy9oZWFkcy9kZXZlbG9wIiwiZGlnZXN0Ijp7InNoYTEiOiJmZTZhZDdlYzU3NDljMmIxMjc1YjI4NjI0NmQwNmY5MzQ0M2JhNGFhIn19XX19","signatures":[{"keyid":"","sig":"MEUCIQDzHN06yW8bfdmPwhVEGxXOGcOmhfwvRayySk9ZZ19vhwIgZt10HSqjMp0Fyre2jnUyLZsYouxG98ryzA9E4vL0ZK8=","cert":"-----BEGIN CERTIFICATE-----\nMIIHZzCCBuygAwIBAgIUBaVcVlQaoCaQkIV1MOuKAKNBzqYwCgYIKoZIzj0EAwMw\nNzEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MR4wHAYDVQQDExVzaWdzdG9yZS1pbnRl\ncm1lZGlhdGUwHhcNMjQwNzE2MDgwODUxWhcNMjQwNzE2MDgxODUxWjAAMFkwEwYH\nKoZIzj0CAQYIKoZIzj0DAQcDQgAEcNXZVxcD2zh0H60WlZ3QEJo4cE893qVeqgOA\n7tEYQuF46e8fs/bLhc3/+hFCA+asRGnXT4K/uOFA5P899mnCHKOCBgswggYHMA4G\nA1UdDwEB/wQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcDAzAdBgNVHQ4EFgQUhnlc\nAFCw6u8BOM7u9SFuRQHndy0wHwYDVR0jBBgwFoAU39Ppz1YkEZb5qNjpKFWixi4Y\nZD8wgYQGA1UdEQEB/wR6MHiGdmh0dHBzOi8vZ2l0aHViLmNvbS9zbHNhLWZyYW1l\nd29yay9zbHNhLWdpdGh1Yi1nZW5lcmF0b3IvLmdpdGh1Yi93b3JrZmxvd3MvZ2Vu\nZXJhdG9yX2dlbmVyaWNfc2xzYTMueW1sQHJlZnMvdGFncy92Mi4wLjAwOQYKKwYB\nBAGDvzABAQQraHR0cHM6Ly90b2tlbi5hY3Rpb25zLmdpdGh1YnVzZXJjb250ZW50\nLmNvbTAWBgorBgEEAYO/MAECBAhzY2hlZHVsZTA2BgorBgEEAYO/MAEDBChmZTZh\nZDdlYzU3NDljMmIxMjc1YjI4NjI0NmQwNmY5MzQ0M2JhNGFhMBkGCisGAQQBg78w\nAQQEC1ByZS1SZWxlYXNlMDUGCisGAQQBg78wAQUEJ2F3cy1wb3dlcnRvb2xzL3Bv\nd2VydG9vbHMtbGFtYmRhLXB5dGhvbjAgBgorBgEEAYO/MAEGBBJyZWZzL2hlYWRz\nL2RldmVsb3AwOwYKKwYBBAGDvzABCAQtDCtodHRwczovL3Rva2VuLmFjdGlvbnMu\nZ2l0aHVidXNlcmNvbnRlbnQuY29tMIGGBgorBgEEAYO/MAEJBHgMdmh0dHBzOi8v\nZ2l0aHViLmNvbS9zbHNhLWZyYW1ld29yay9zbHNhLWdpdGh1Yi1nZW5lcmF0b3Iv\nLmdpdGh1Yi93b3JrZmxvd3MvZ2VuZXJhdG9yX2dlbmVyaWNfc2xzYTMueW1sQHJl\nZnMvdGFncy92Mi4wLjAwOAYKKwYBBAGDvzABCgQqDCg1YTc3NWIzNjdhNTZkNWJk\nMTE4YTIyNGE4MTFiYmEyODgxNTBhNTYzMB0GCisGAQQBg78wAQsEDwwNZ2l0aHVi\nLWhvc3RlZDBKBgorBgEEAYO/MAEMBDwMOmh0dHBzOi8vZ2l0aHViLmNvbS9hd3Mt\ncG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24wOAYKKwYBBAGDvzAB\nDQQqDChmZTZhZDdlYzU3NDljMmIxMjc1YjI4NjI0NmQwNmY5MzQ0M2JhNGFhMCIG\nCisGAQQBg78wAQ4EFAwScmVmcy9oZWFkcy9kZXZlbG9wMBkGCisGAQQBg78wAQ8E\nCwwJMjIxOTE5Mzc5MDEGCisGAQQBg78wARAEIwwhaHR0cHM6Ly9naXRodWIuY29t\nL2F3cy1wb3dlcnRvb2xzMBkGCisGAQQBg78wAREECwwJMTI5MTI3NjM4MH8GCisG\nAQQBg78wARIEcQxvaHR0cHM6Ly9naXRodWIuY29tL2F3cy1wb3dlcnRvb2xzL3Bv\nd2VydG9vbHMtbGFtYmRhLXB5dGhvbi8uZ2l0aHViL3dvcmtmbG93cy9wcmUtcmVs\nZWFzZS55bWxAcmVmcy9oZWFkcy9kZXZlbG9wMDgGCisGAQQBg78wARMEKgwoZmU2\nYWQ3ZWM1NzQ5YzJiMTI3NWIyODYyNDZkMDZmOTM0NDNiYTRhYTAYBgorBgEEAYO/\nMAEUBAoMCHNjaGVkdWxlMG0GCisGAQQBg78wARUEXwxdaHR0cHM6Ly9naXRodWIu\nY29tL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9hY3Rp\nb25zL3J1bnMvOTk1MzAwNjQ5MC9hdHRlbXB0cy8xMBYGCisGAQQBg78wARYECAwG\ncHVibGljMIGLBgorBgEEAdZ5AgQCBH0EewB5AHcA3T0wasbHETJjGR4cmWc3AqJK\nXrjePK3/h4pygC8p7o4AAAGQupZSCQAABAMASDBGAiEAxYIjUXmhx6ixz+FyH19c\njWkoyRiXlmJbfQ3n0Yidh5gCIQCoDjVvvUM1MclkuQyvuZOWGcyoAWFcg8O7gkv0\nYIwfOjAKBggqhkjOPQQDAwNpADBmAjEAq8WuRIHdBMQ40T1V6H2vnuPVtspqJ0hF\nh++qvZJzZZHAJr8TjhO5SkIHwc6SRl82AjEAm036Ea067UOfJNVErmD4oxKe4RKO\nILdivivfIn4ulF6iAnT8w652Zlv95W/FqX9M\n-----END CERTIFICATE-----\n"}]}
\ No newline at end of file
diff --git a/provenance/2.41.1a4/multiple.intoto.jsonl b/provenance/2.41.1a4/multiple.intoto.jsonl
new file mode 100644
index 00000000000..6dda521e86c
--- /dev/null
+++ b/provenance/2.41.1a4/multiple.intoto.jsonl
@@ -0,0 +1 @@
+{"payloadType":"application/vnd.in-toto+json","payload":"eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjAuMSIsInByZWRpY2F0ZVR5cGUiOiJodHRwczovL3Nsc2EuZGV2L3Byb3ZlbmFuY2UvdjAuMiIsInN1YmplY3QiOlt7Im5hbWUiOiIuL2F3c19sYW1iZGFfcG93ZXJ0b29scy0yLjQxLjFhNC1weTMtbm9uZS1hbnkud2hsIiwiZGlnZXN0Ijp7InNoYTI1NiI6ImY3OTE0NDE2MmNlMzMxNmFiMTg2MzU5MTM5NzNlOGVmODE4ODQ0NDNkOGMyY2RiYzZhZWI2MThhMGI1OTk4ZjAifX0seyJuYW1lIjoiLi9hd3NfbGFtYmRhX3Bvd2VydG9vbHMtMi40MS4xYTQudGFyLmd6IiwiZGlnZXN0Ijp7InNoYTI1NiI6ImE1ZjBmOTJlNjlhMzgzNTQzZGM1YjE2ZmMxODZlZDJmNDA5MGU2MzBkYThiM2I0Y2FmMzEwYjNiYWQxOWZlYmIifX1dLCJwcmVkaWNhdGUiOnsiYnVpbGRlciI6eyJpZCI6Imh0dHBzOi8vZ2l0aHViLmNvbS9zbHNhLWZyYW1ld29yay9zbHNhLWdpdGh1Yi1nZW5lcmF0b3IvLmdpdGh1Yi93b3JrZmxvd3MvZ2VuZXJhdG9yX2dlbmVyaWNfc2xzYTMueW1sQHJlZnMvdGFncy92Mi4wLjAifSwiYnVpbGRUeXBlIjoiaHR0cHM6Ly9naXRodWIuY29tL3Nsc2EtZnJhbWV3b3JrL3Nsc2EtZ2l0aHViLWdlbmVyYXRvci9nZW5lcmljQHYxIiwiaW52b2NhdGlvbiI6eyJjb25maWdTb3VyY2UiOnsidXJpIjoiZ2l0K2h0dHBzOi8vZ2l0aHViLmNvbS9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob25AcmVmcy9oZWFkcy9kZXZlbG9wIiwiZGlnZXN0Ijp7InNoYTEiOiI1ZjQxZTQ0MGQ2NjBlNDc1NWRhZGJlYTdmYTU5NjU3ZGEzYWI5ZjYxIn0sImVudHJ5UG9pbnQiOiIuZ2l0aHViL3dvcmtmbG93cy9wcmUtcmVsZWFzZS55bWwifSwicGFyYW1ldGVycyI6e30sImVudmlyb25tZW50Ijp7ImdpdGh1Yl9hY3RvciI6ImxlYW5kcm9kYW1hc2NlbmEiLCJnaXRodWJfYWN0b3JfaWQiOiI0Mjk1MTczIiwiZ2l0aHViX2Jhc2VfcmVmIjoiIiwiZ2l0aHViX2V2ZW50X25hbWUiOiJzY2hlZHVsZSIsImdpdGh1Yl9ldmVudF9wYXlsb2FkIjp7ImVudGVycHJpc2UiOnsiYXZhdGFyX3VybCI6Imh0dHBzOi8vYXZhdGFycy5naXRodWJ1c2VyY29udGVudC5jb20vYi8xMjkwP3Y9NCIsImNyZWF0ZWRfYXQiOiIyMDE5LTExLTEzVDE4OjA1OjQxWiIsImRlc2NyaXB0aW9uIjoiIiwiaHRtbF91cmwiOiJodHRwczovL2dpdGh1Yi5jb20vZW50ZXJwcmlzZXMvYW1hem9uIiwiaWQiOjEyOTAsIm5hbWUiOiJBbWF6b24iLCJub2RlX2lkIjoiTURFd09rVnVkR1Z5Y0hKcGMyVXhNamt3Iiwic2x1ZyI6ImFtYXpvbiIsInVwZGF0ZWRfYXQiOiIyMDIzLTAxLTI3VDE0OjU2OjEwWiIsIndlYnNpdGVfdXJsIjoiaHR0cHM6Ly93d3cuYW1hem9uLmNvbS8ifSwib3JnYW5pemF0aW9uIjp7ImF2YXRhcl91cmwiOiJodHRwczovL2F2YXRhcnMuZ2l0aHVidXNlcmNvbnRlbnQuY29tL3UvMTI5MTI3NjM4P3Y9NCIsImRlc2NyaXB0aW9uIjoiIiwiZXZlbnRzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vb3Jncy9hd3MtcG93ZXJ0b29scy9ldmVudHMiLCJob29rc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL29yZ3MvYXdzLXBvd2VydG9vbHMvaG9va3MiLCJpZCI6MTI5MTI3NjM4LCJpc3N1ZXNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9vcmdzL2F3cy1wb3dlcnRvb2xzL2lzc3VlcyIsImxvZ2luIjoiYXdzLXBvd2VydG9vbHMiLCJtZW1iZXJzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vb3Jncy9hd3MtcG93ZXJ0b29scy9tZW1iZXJzey9tZW1iZXJ9Iiwibm9kZV9pZCI6Ik9fa2dET0I3SlUxZyIsInB1YmxpY19tZW1iZXJzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vb3Jncy9hd3MtcG93ZXJ0b29scy9wdWJsaWNfbWVtYmVyc3svbWVtYmVyfSIsInJlcG9zX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vb3Jncy9hd3MtcG93ZXJ0b29scy9yZXBvcyIsInVybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vb3Jncy9hd3MtcG93ZXJ0b29scyJ9LCJyZXBvc2l0b3J5Ijp7ImFsbG93X2ZvcmtpbmciOnRydWUsImFyY2hpdmVfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24ve2FyY2hpdmVfZm9ybWF0fXsvcmVmfSIsImFyY2hpdmVkIjpmYWxzZSwiYXNzaWduZWVzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2Fzc2lnbmVlc3svdXNlcn0iLCJibG9ic191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9naXQvYmxvYnN7L3NoYX0iLCJicmFuY2hlc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9icmFuY2hlc3svYnJhbmNofSIsImNsb25lX3VybCI6Imh0dHBzOi8vZ2l0aHViLmNvbS9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24uZ2l0IiwiY29sbGFib3JhdG9yc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9jb2xsYWJvcmF0b3Jzey9jb2xsYWJvcmF0b3J9IiwiY29tbWVudHNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vY29tbWVudHN7L251bWJlcn0iLCJjb21taXRzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2NvbW1pdHN7L3NoYX0iLCJjb21wYXJlX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2NvbXBhcmUve2Jhc2V9Li4ue2hlYWR9IiwiY29udGVudHNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vY29udGVudHMveytwYXRofSIsImNvbnRyaWJ1dG9yc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9jb250cmlidXRvcnMiLCJjcmVhdGVkX2F0IjoiMjAxOS0xMS0xNVQxMjoyNjoxMloiLCJkZWZhdWx0X2JyYW5jaCI6ImRldmVsb3AiLCJkZXBsb3ltZW50c191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9kZXBsb3ltZW50cyIsImRlc2NyaXB0aW9uIjoiQSBkZXZlbG9wZXIgdG9vbGtpdCB0byBpbXBsZW1lbnQgU2VydmVybGVzcyBiZXN0IHByYWN0aWNlcyBhbmQgaW5jcmVhc2UgZGV2ZWxvcGVyIHZlbG9jaXR5LiIsImRpc2FibGVkIjpmYWxzZSwiZG93bmxvYWRzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2Rvd25sb2FkcyIsImV2ZW50c191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9ldmVudHMiLCJmb3JrIjpmYWxzZSwiZm9ya3MiOjM3OCwiZm9ya3NfY291bnQiOjM3OCwiZm9ya3NfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vZm9ya3MiLCJmdWxsX25hbWUiOiJhd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24iLCJnaXRfY29tbWl0c191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9naXQvY29tbWl0c3svc2hhfSIsImdpdF9yZWZzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2dpdC9yZWZzey9zaGF9IiwiZ2l0X3RhZ3NfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vZ2l0L3RhZ3N7L3NoYX0iLCJnaXRfdXJsIjoiZ2l0Oi8vZ2l0aHViLmNvbS9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24uZ2l0IiwiaGFzX2Rpc2N1c3Npb25zIjp0cnVlLCJoYXNfZG93bmxvYWRzIjp0cnVlLCJoYXNfaXNzdWVzIjp0cnVlLCJoYXNfcGFnZXMiOmZhbHNlLCJoYXNfcHJvamVjdHMiOnRydWUsImhhc193aWtpIjpmYWxzZSwiaG9tZXBhZ2UiOiJodHRwczovL2RvY3MucG93ZXJ0b29scy5hd3MuZGV2L2xhbWJkYS9weXRob24vbGF0ZXN0LyIsImhvb2tzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2hvb2tzIiwiaHRtbF91cmwiOiJodHRwczovL2dpdGh1Yi5jb20vYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uIiwiaWQiOjIyMTkxOTM3OSwiaXNfdGVtcGxhdGUiOmZhbHNlLCJpc3N1ZV9jb21tZW50X3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2lzc3Vlcy9jb21tZW50c3svbnVtYmVyfSIsImlzc3VlX2V2ZW50c191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9pc3N1ZXMvZXZlbnRzey9udW1iZXJ9IiwiaXNzdWVzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2lzc3Vlc3svbnVtYmVyfSIsImtleXNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24va2V5c3sva2V5X2lkfSIsImxhYmVsc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9sYWJlbHN7L25hbWV9IiwibGFuZ3VhZ2UiOiJQeXRob24iLCJsYW5ndWFnZXNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vbGFuZ3VhZ2VzIiwibGljZW5zZSI6eyJrZXkiOiJtaXQtMCIsIm5hbWUiOiJNSVQgTm8gQXR0cmlidXRpb24iLCJub2RlX2lkIjoiTURjNlRHbGpaVzV6WlRReCIsInNwZHhfaWQiOiJNSVQtMCIsInVybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vbGljZW5zZXMvbWl0LTAifSwibWVyZ2VzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL21lcmdlcyIsIm1pbGVzdG9uZXNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vbWlsZXN0b25lc3svbnVtYmVyfSIsIm1pcnJvcl91cmwiOm51bGwsIm5hbWUiOiJwb3dlcnRvb2xzLWxhbWJkYS1weXRob24iLCJub2RlX2lkIjoiTURFd09sSmxjRzl6YVhSdmNua3lNakU1TVRrek56az0iLCJub3RpZmljYXRpb25zX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL25vdGlmaWNhdGlvbnN7P3NpbmNlLGFsbCxwYXJ0aWNpcGF0aW5nfSIsIm9wZW5faXNzdWVzIjo5OCwib3Blbl9pc3N1ZXNfY291bnQiOjk4LCJvd25lciI6eyJhdmF0YXJfdXJsIjoiaHR0cHM6Ly9hdmF0YXJzLmdpdGh1YnVzZXJjb250ZW50LmNvbS91LzEyOTEyNzYzOD92PTQiLCJldmVudHNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS91c2Vycy9hd3MtcG93ZXJ0b29scy9ldmVudHN7L3ByaXZhY3l9IiwiZm9sbG93ZXJzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vdXNlcnMvYXdzLXBvd2VydG9vbHMvZm9sbG93ZXJzIiwiZm9sbG93aW5nX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vdXNlcnMvYXdzLXBvd2VydG9vbHMvZm9sbG93aW5ney9vdGhlcl91c2VyfSIsImdpc3RzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vdXNlcnMvYXdzLXBvd2VydG9vbHMvZ2lzdHN7L2dpc3RfaWR9IiwiZ3JhdmF0YXJfaWQiOiIiLCJodG1sX3VybCI6Imh0dHBzOi8vZ2l0aHViLmNvbS9hd3MtcG93ZXJ0b29scyIsImlkIjoxMjkxMjc2MzgsImxvZ2luIjoiYXdzLXBvd2VydG9vbHMiLCJub2RlX2lkIjoiT19rZ0RPQjdKVTFnIiwib3JnYW5pemF0aW9uc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3VzZXJzL2F3cy1wb3dlcnRvb2xzL29yZ3MiLCJyZWNlaXZlZF9ldmVudHNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS91c2Vycy9hd3MtcG93ZXJ0b29scy9yZWNlaXZlZF9ldmVudHMiLCJyZXBvc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3VzZXJzL2F3cy1wb3dlcnRvb2xzL3JlcG9zIiwic2l0ZV9hZG1pbiI6ZmFsc2UsInN0YXJyZWRfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS91c2Vycy9hd3MtcG93ZXJ0b29scy9zdGFycmVkey9vd25lcn17L3JlcG99Iiwic3Vic2NyaXB0aW9uc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3VzZXJzL2F3cy1wb3dlcnRvb2xzL3N1YnNjcmlwdGlvbnMiLCJ0eXBlIjoiT3JnYW5pemF0aW9uIiwidXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS91c2Vycy9hd3MtcG93ZXJ0b29scyJ9LCJwcml2YXRlIjpmYWxzZSwicHVsbHNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vcHVsbHN7L251bWJlcn0iLCJwdXNoZWRfYXQiOiIyMDI0LTA3LTE2VDIwOjE5OjA1WiIsInJlbGVhc2VzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL3JlbGVhc2Vzey9pZH0iLCJzaXplIjo0ODYzNSwic3NoX3VybCI6ImdpdEBnaXRodWIuY29tOmF3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi5naXQiLCJzdGFyZ2F6ZXJzX2NvdW50IjoyNzMxLCJzdGFyZ2F6ZXJzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL3N0YXJnYXplcnMiLCJzdGF0dXNlc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9zdGF0dXNlcy97c2hhfSIsInN1YnNjcmliZXJzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL3N1YnNjcmliZXJzIiwic3Vic2NyaXB0aW9uX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL3N1YnNjcmlwdGlvbiIsInN2bl91cmwiOiJodHRwczovL2dpdGh1Yi5jb20vYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uIiwidGFnc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi90YWdzIiwidGVhbXNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vdGVhbXMiLCJ0b3BpY3MiOlsiYXdzIiwiYXdzLWxhbWJkYSIsImhhY2t0b2JlcmZlc3QiLCJsYW1iZGEiLCJweXRob24iLCJzZXJ2ZXJsZXNzIl0sInRyZWVzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2dpdC90cmVlc3svc2hhfSIsInVwZGF0ZWRfYXQiOiIyMDI0LTA3LTE2VDEzOjAzOjI4WiIsInVybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uIiwidmlzaWJpbGl0eSI6InB1YmxpYyIsIndhdGNoZXJzIjoyNzMxLCJ3YXRjaGVyc19jb3VudCI6MjczMSwid2ViX2NvbW1pdF9zaWdub2ZmX3JlcXVpcmVkIjp0cnVlfSwic2NoZWR1bGUiOiIwIDggKiAqIDEtNSIsIndvcmtmbG93IjoiLmdpdGh1Yi93b3JrZmxvd3MvcHJlLXJlbGVhc2UueW1sIn0sImdpdGh1Yl9oZWFkX3JlZiI6IiIsImdpdGh1Yl9yZWYiOiJyZWZzL2hlYWRzL2RldmVsb3AiLCJnaXRodWJfcmVmX3R5cGUiOiJicmFuY2giLCJnaXRodWJfcmVwb3NpdG9yeV9pZCI6IjIyMTkxOTM3OSIsImdpdGh1Yl9yZXBvc2l0b3J5X293bmVyIjoiYXdzLXBvd2VydG9vbHMiLCJnaXRodWJfcmVwb3NpdG9yeV9vd25lcl9pZCI6IjEyOTEyNzYzOCIsImdpdGh1Yl9ydW5fYXR0ZW1wdCI6IjEiLCJnaXRodWJfcnVuX2lkIjoiOTk3MDQyNTQ4OCIsImdpdGh1Yl9ydW5fbnVtYmVyIjoiMjIiLCJnaXRodWJfc2hhMSI6IjVmNDFlNDQwZDY2MGU0NzU1ZGFkYmVhN2ZhNTk2NTdkYTNhYjlmNjEifX0sIm1ldGFkYXRhIjp7ImJ1aWxkSW52b2NhdGlvbklEIjoiOTk3MDQyNTQ4OC0xIiwiY29tcGxldGVuZXNzIjp7InBhcmFtZXRlcnMiOnRydWUsImVudmlyb25tZW50IjpmYWxzZSwibWF0ZXJpYWxzIjpmYWxzZX0sInJlcHJvZHVjaWJsZSI6ZmFsc2V9LCJtYXRlcmlhbHMiOlt7InVyaSI6ImdpdCtodHRwczovL2dpdGh1Yi5jb20vYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uQHJlZnMvaGVhZHMvZGV2ZWxvcCIsImRpZ2VzdCI6eyJzaGExIjoiNWY0MWU0NDBkNjYwZTQ3NTVkYWRiZWE3ZmE1OTY1N2RhM2FiOWY2MSJ9fV19fQ==","signatures":[{"keyid":"","sig":"MEQCIAFIDcdh4GhzMbLV6cgDRv1wXaUdEMcI5ETfjlEk9SYnAiBQnB11RGaXyBqIcvDBG+cGb+Wmtwx5DgVU84ra0NvJ9w==","cert":"-----BEGIN CERTIFICATE-----\nMIIHZDCCBuqgAwIBAgIUTeihGfMXMp58IPH3McegYAZmch0wCgYIKoZIzj0EAwMw\nNzEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MR4wHAYDVQQDExVzaWdzdG9yZS1pbnRl\ncm1lZGlhdGUwHhcNMjQwNzE3MDgwNzExWhcNMjQwNzE3MDgxNzExWjAAMFkwEwYH\nKoZIzj0CAQYIKoZIzj0DAQcDQgAEpP8sKDlNc12Tz5U+U8LdW6glp3ARy/pHtDFT\n6zE3dMhTtrB0ci8EziAK5/qvmwZ+2NCAElZwYABkFKxWJ3t4VqOCBgkwggYFMA4G\nA1UdDwEB/wQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcDAzAdBgNVHQ4EFgQUavsk\nl6cHdOHMOFDHMAc+2fy/hWIwHwYDVR0jBBgwFoAU39Ppz1YkEZb5qNjpKFWixi4Y\nZD8wgYQGA1UdEQEB/wR6MHiGdmh0dHBzOi8vZ2l0aHViLmNvbS9zbHNhLWZyYW1l\nd29yay9zbHNhLWdpdGh1Yi1nZW5lcmF0b3IvLmdpdGh1Yi93b3JrZmxvd3MvZ2Vu\nZXJhdG9yX2dlbmVyaWNfc2xzYTMueW1sQHJlZnMvdGFncy92Mi4wLjAwOQYKKwYB\nBAGDvzABAQQraHR0cHM6Ly90b2tlbi5hY3Rpb25zLmdpdGh1YnVzZXJjb250ZW50\nLmNvbTAWBgorBgEEAYO/MAECBAhzY2hlZHVsZTA2BgorBgEEAYO/MAEDBCg1ZjQx\nZTQ0MGQ2NjBlNDc1NWRhZGJlYTdmYTU5NjU3ZGEzYWI5ZjYxMBkGCisGAQQBg78w\nAQQEC1ByZS1SZWxlYXNlMDUGCisGAQQBg78wAQUEJ2F3cy1wb3dlcnRvb2xzL3Bv\nd2VydG9vbHMtbGFtYmRhLXB5dGhvbjAgBgorBgEEAYO/MAEGBBJyZWZzL2hlYWRz\nL2RldmVsb3AwOwYKKwYBBAGDvzABCAQtDCtodHRwczovL3Rva2VuLmFjdGlvbnMu\nZ2l0aHVidXNlcmNvbnRlbnQuY29tMIGGBgorBgEEAYO/MAEJBHgMdmh0dHBzOi8v\nZ2l0aHViLmNvbS9zbHNhLWZyYW1ld29yay9zbHNhLWdpdGh1Yi1nZW5lcmF0b3Iv\nLmdpdGh1Yi93b3JrZmxvd3MvZ2VuZXJhdG9yX2dlbmVyaWNfc2xzYTMueW1sQHJl\nZnMvdGFncy92Mi4wLjAwOAYKKwYBBAGDvzABCgQqDCg1YTc3NWIzNjdhNTZkNWJk\nMTE4YTIyNGE4MTFiYmEyODgxNTBhNTYzMB0GCisGAQQBg78wAQsEDwwNZ2l0aHVi\nLWhvc3RlZDBKBgorBgEEAYO/MAEMBDwMOmh0dHBzOi8vZ2l0aHViLmNvbS9hd3Mt\ncG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24wOAYKKwYBBAGDvzAB\nDQQqDCg1ZjQxZTQ0MGQ2NjBlNDc1NWRhZGJlYTdmYTU5NjU3ZGEzYWI5ZjYxMCIG\nCisGAQQBg78wAQ4EFAwScmVmcy9oZWFkcy9kZXZlbG9wMBkGCisGAQQBg78wAQ8E\nCwwJMjIxOTE5Mzc5MDEGCisGAQQBg78wARAEIwwhaHR0cHM6Ly9naXRodWIuY29t\nL2F3cy1wb3dlcnRvb2xzMBkGCisGAQQBg78wAREECwwJMTI5MTI3NjM4MH8GCisG\nAQQBg78wARIEcQxvaHR0cHM6Ly9naXRodWIuY29tL2F3cy1wb3dlcnRvb2xzL3Bv\nd2VydG9vbHMtbGFtYmRhLXB5dGhvbi8uZ2l0aHViL3dvcmtmbG93cy9wcmUtcmVs\nZWFzZS55bWxAcmVmcy9oZWFkcy9kZXZlbG9wMDgGCisGAQQBg78wARMEKgwoNWY0\nMWU0NDBkNjYwZTQ3NTVkYWRiZWE3ZmE1OTY1N2RhM2FiOWY2MTAYBgorBgEEAYO/\nMAEUBAoMCHNjaGVkdWxlMG0GCisGAQQBg78wARUEXwxdaHR0cHM6Ly9naXRodWIu\nY29tL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9hY3Rp\nb25zL3J1bnMvOTk3MDQyNTQ4OC9hdHRlbXB0cy8xMBYGCisGAQQBg78wARYECAwG\ncHVibGljMIGJBgorBgEEAdZ5AgQCBHsEeQB3AHUA3T0wasbHETJjGR4cmWc3AqJK\nXrjePK3/h4pygC8p7o4AAAGQv7sj9wAABAMARjBEAiBsyv5ZTzofBdvmHXs3IGpI\nGh2IVDGShp0wD4slmasBhQIgHlZ0qj9B3+QqkUl9BTcPN1ykZYNefdegU7LrMt8G\nyL8wCgYIKoZIzj0EAwMDaAAwZQIxAPL1WYH/g2e+JDaOwYP4DLCHaxesbqX8KiCs\nf6/bh4WscsuPyZ/GkjCwBUmv+4r5DAIwTvC0SeyD2E70y0y6qIl6xZA7i362OH9d\nqzw4ZYc5vcV2H5pXbZxZNjw2GQ/5rmeM\n-----END CERTIFICATE-----\n"}]}
\ No newline at end of file
diff --git a/provenance/2.41.1a5/multiple.intoto.jsonl b/provenance/2.41.1a5/multiple.intoto.jsonl
new file mode 100644
index 00000000000..d0e101a2753
--- /dev/null
+++ b/provenance/2.41.1a5/multiple.intoto.jsonl
@@ -0,0 +1 @@
+{"payloadType":"application/vnd.in-toto+json","payload":"eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjAuMSIsInByZWRpY2F0ZVR5cGUiOiJodHRwczovL3Nsc2EuZGV2L3Byb3ZlbmFuY2UvdjAuMiIsInN1YmplY3QiOlt7Im5hbWUiOiIuL2F3c19sYW1iZGFfcG93ZXJ0b29scy0yLjQxLjFhNS1weTMtbm9uZS1hbnkud2hsIiwiZGlnZXN0Ijp7InNoYTI1NiI6Ijk2YTAwZmRhMGEzNjNmZDRkY2FmZDBmZTFmOTdhNTViN2RjNzgwYmQ1YWIyYjZkZjU4MjRkMGE2YWY2YzkxM2IifX0seyJuYW1lIjoiLi9hd3NfbGFtYmRhX3Bvd2VydG9vbHMtMi40MS4xYTUudGFyLmd6IiwiZGlnZXN0Ijp7InNoYTI1NiI6IjFlYjA2MWMxODIxZTI5MzQ4NzY5YzhhMTVjN2ZkNGI3NzIyMTk5ZDg0OWY3YTRiMGY4Yzg4ZjY4NTgyYjRkMjEifX1dLCJwcmVkaWNhdGUiOnsiYnVpbGRlciI6eyJpZCI6Imh0dHBzOi8vZ2l0aHViLmNvbS9zbHNhLWZyYW1ld29yay9zbHNhLWdpdGh1Yi1nZW5lcmF0b3IvLmdpdGh1Yi93b3JrZmxvd3MvZ2VuZXJhdG9yX2dlbmVyaWNfc2xzYTMueW1sQHJlZnMvdGFncy92Mi4wLjAifSwiYnVpbGRUeXBlIjoiaHR0cHM6Ly9naXRodWIuY29tL3Nsc2EtZnJhbWV3b3JrL3Nsc2EtZ2l0aHViLWdlbmVyYXRvci9nZW5lcmljQHYxIiwiaW52b2NhdGlvbiI6eyJjb25maWdTb3VyY2UiOnsidXJpIjoiZ2l0K2h0dHBzOi8vZ2l0aHViLmNvbS9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob25AcmVmcy9oZWFkcy9kZXZlbG9wIiwiZGlnZXN0Ijp7InNoYTEiOiJiMWMwNTMyOTE4M2NlYjM5M2U0YmZjNjA1YTNiMzNmN2NhOTAyZmQ2In0sImVudHJ5UG9pbnQiOiIuZ2l0aHViL3dvcmtmbG93cy9wcmUtcmVsZWFzZS55bWwifSwicGFyYW1ldGVycyI6e30sImVudmlyb25tZW50Ijp7ImdpdGh1Yl9hY3RvciI6ImxlYW5kcm9kYW1hc2NlbmEiLCJnaXRodWJfYWN0b3JfaWQiOiI0Mjk1MTczIiwiZ2l0aHViX2Jhc2VfcmVmIjoiIiwiZ2l0aHViX2V2ZW50X25hbWUiOiJzY2hlZHVsZSIsImdpdGh1Yl9ldmVudF9wYXlsb2FkIjp7ImVudGVycHJpc2UiOnsiYXZhdGFyX3VybCI6Imh0dHBzOi8vYXZhdGFycy5naXRodWJ1c2VyY29udGVudC5jb20vYi8xMjkwP3Y9NCIsImNyZWF0ZWRfYXQiOiIyMDE5LTExLTEzVDE4OjA1OjQxWiIsImRlc2NyaXB0aW9uIjoiIiwiaHRtbF91cmwiOiJodHRwczovL2dpdGh1Yi5jb20vZW50ZXJwcmlzZXMvYW1hem9uIiwiaWQiOjEyOTAsIm5hbWUiOiJBbWF6b24iLCJub2RlX2lkIjoiTURFd09rVnVkR1Z5Y0hKcGMyVXhNamt3Iiwic2x1ZyI6ImFtYXpvbiIsInVwZGF0ZWRfYXQiOiIyMDIzLTAxLTI3VDE0OjU2OjEwWiIsIndlYnNpdGVfdXJsIjoiaHR0cHM6Ly93d3cuYW1hem9uLmNvbS8ifSwib3JnYW5pemF0aW9uIjp7ImF2YXRhcl91cmwiOiJodHRwczovL2F2YXRhcnMuZ2l0aHVidXNlcmNvbnRlbnQuY29tL3UvMTI5MTI3NjM4P3Y9NCIsImRlc2NyaXB0aW9uIjoiIiwiZXZlbnRzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vb3Jncy9hd3MtcG93ZXJ0b29scy9ldmVudHMiLCJob29rc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL29yZ3MvYXdzLXBvd2VydG9vbHMvaG9va3MiLCJpZCI6MTI5MTI3NjM4LCJpc3N1ZXNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9vcmdzL2F3cy1wb3dlcnRvb2xzL2lzc3VlcyIsImxvZ2luIjoiYXdzLXBvd2VydG9vbHMiLCJtZW1iZXJzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vb3Jncy9hd3MtcG93ZXJ0b29scy9tZW1iZXJzey9tZW1iZXJ9Iiwibm9kZV9pZCI6Ik9fa2dET0I3SlUxZyIsInB1YmxpY19tZW1iZXJzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vb3Jncy9hd3MtcG93ZXJ0b29scy9wdWJsaWNfbWVtYmVyc3svbWVtYmVyfSIsInJlcG9zX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vb3Jncy9hd3MtcG93ZXJ0b29scy9yZXBvcyIsInVybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vb3Jncy9hd3MtcG93ZXJ0b29scyJ9LCJyZXBvc2l0b3J5Ijp7ImFsbG93X2ZvcmtpbmciOnRydWUsImFyY2hpdmVfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24ve2FyY2hpdmVfZm9ybWF0fXsvcmVmfSIsImFyY2hpdmVkIjpmYWxzZSwiYXNzaWduZWVzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2Fzc2lnbmVlc3svdXNlcn0iLCJibG9ic191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9naXQvYmxvYnN7L3NoYX0iLCJicmFuY2hlc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9icmFuY2hlc3svYnJhbmNofSIsImNsb25lX3VybCI6Imh0dHBzOi8vZ2l0aHViLmNvbS9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24uZ2l0IiwiY29sbGFib3JhdG9yc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9jb2xsYWJvcmF0b3Jzey9jb2xsYWJvcmF0b3J9IiwiY29tbWVudHNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vY29tbWVudHN7L251bWJlcn0iLCJjb21taXRzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2NvbW1pdHN7L3NoYX0iLCJjb21wYXJlX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2NvbXBhcmUve2Jhc2V9Li4ue2hlYWR9IiwiY29udGVudHNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vY29udGVudHMveytwYXRofSIsImNvbnRyaWJ1dG9yc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9jb250cmlidXRvcnMiLCJjcmVhdGVkX2F0IjoiMjAxOS0xMS0xNVQxMjoyNjoxMloiLCJkZWZhdWx0X2JyYW5jaCI6ImRldmVsb3AiLCJkZXBsb3ltZW50c191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9kZXBsb3ltZW50cyIsImRlc2NyaXB0aW9uIjoiQSBkZXZlbG9wZXIgdG9vbGtpdCB0byBpbXBsZW1lbnQgU2VydmVybGVzcyBiZXN0IHByYWN0aWNlcyBhbmQgaW5jcmVhc2UgZGV2ZWxvcGVyIHZlbG9jaXR5LiIsImRpc2FibGVkIjpmYWxzZSwiZG93bmxvYWRzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2Rvd25sb2FkcyIsImV2ZW50c191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9ldmVudHMiLCJmb3JrIjpmYWxzZSwiZm9ya3MiOjM3OCwiZm9ya3NfY291bnQiOjM3OCwiZm9ya3NfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vZm9ya3MiLCJmdWxsX25hbWUiOiJhd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24iLCJnaXRfY29tbWl0c191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9naXQvY29tbWl0c3svc2hhfSIsImdpdF9yZWZzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2dpdC9yZWZzey9zaGF9IiwiZ2l0X3RhZ3NfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vZ2l0L3RhZ3N7L3NoYX0iLCJnaXRfdXJsIjoiZ2l0Oi8vZ2l0aHViLmNvbS9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24uZ2l0IiwiaGFzX2Rpc2N1c3Npb25zIjp0cnVlLCJoYXNfZG93bmxvYWRzIjp0cnVlLCJoYXNfaXNzdWVzIjp0cnVlLCJoYXNfcGFnZXMiOmZhbHNlLCJoYXNfcHJvamVjdHMiOnRydWUsImhhc193aWtpIjpmYWxzZSwiaG9tZXBhZ2UiOiJodHRwczovL2RvY3MucG93ZXJ0b29scy5hd3MuZGV2L2xhbWJkYS9weXRob24vbGF0ZXN0LyIsImhvb2tzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2hvb2tzIiwiaHRtbF91cmwiOiJodHRwczovL2dpdGh1Yi5jb20vYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uIiwiaWQiOjIyMTkxOTM3OSwiaXNfdGVtcGxhdGUiOmZhbHNlLCJpc3N1ZV9jb21tZW50X3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2lzc3Vlcy9jb21tZW50c3svbnVtYmVyfSIsImlzc3VlX2V2ZW50c191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9pc3N1ZXMvZXZlbnRzey9udW1iZXJ9IiwiaXNzdWVzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2lzc3Vlc3svbnVtYmVyfSIsImtleXNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24va2V5c3sva2V5X2lkfSIsImxhYmVsc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9sYWJlbHN7L25hbWV9IiwibGFuZ3VhZ2UiOiJQeXRob24iLCJsYW5ndWFnZXNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vbGFuZ3VhZ2VzIiwibGljZW5zZSI6eyJrZXkiOiJtaXQtMCIsIm5hbWUiOiJNSVQgTm8gQXR0cmlidXRpb24iLCJub2RlX2lkIjoiTURjNlRHbGpaVzV6WlRReCIsInNwZHhfaWQiOiJNSVQtMCIsInVybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vbGljZW5zZXMvbWl0LTAifSwibWVyZ2VzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL21lcmdlcyIsIm1pbGVzdG9uZXNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vbWlsZXN0b25lc3svbnVtYmVyfSIsIm1pcnJvcl91cmwiOm51bGwsIm5hbWUiOiJwb3dlcnRvb2xzLWxhbWJkYS1weXRob24iLCJub2RlX2lkIjoiTURFd09sSmxjRzl6YVhSdmNua3lNakU1TVRrek56az0iLCJub3RpZmljYXRpb25zX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL25vdGlmaWNhdGlvbnN7P3NpbmNlLGFsbCxwYXJ0aWNpcGF0aW5nfSIsIm9wZW5faXNzdWVzIjo5OSwib3Blbl9pc3N1ZXNfY291bnQiOjk5LCJvd25lciI6eyJhdmF0YXJfdXJsIjoiaHR0cHM6Ly9hdmF0YXJzLmdpdGh1YnVzZXJjb250ZW50LmNvbS91LzEyOTEyNzYzOD92PTQiLCJldmVudHNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS91c2Vycy9hd3MtcG93ZXJ0b29scy9ldmVudHN7L3ByaXZhY3l9IiwiZm9sbG93ZXJzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vdXNlcnMvYXdzLXBvd2VydG9vbHMvZm9sbG93ZXJzIiwiZm9sbG93aW5nX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vdXNlcnMvYXdzLXBvd2VydG9vbHMvZm9sbG93aW5ney9vdGhlcl91c2VyfSIsImdpc3RzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vdXNlcnMvYXdzLXBvd2VydG9vbHMvZ2lzdHN7L2dpc3RfaWR9IiwiZ3JhdmF0YXJfaWQiOiIiLCJodG1sX3VybCI6Imh0dHBzOi8vZ2l0aHViLmNvbS9hd3MtcG93ZXJ0b29scyIsImlkIjoxMjkxMjc2MzgsImxvZ2luIjoiYXdzLXBvd2VydG9vbHMiLCJub2RlX2lkIjoiT19rZ0RPQjdKVTFnIiwib3JnYW5pemF0aW9uc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3VzZXJzL2F3cy1wb3dlcnRvb2xzL29yZ3MiLCJyZWNlaXZlZF9ldmVudHNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS91c2Vycy9hd3MtcG93ZXJ0b29scy9yZWNlaXZlZF9ldmVudHMiLCJyZXBvc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3VzZXJzL2F3cy1wb3dlcnRvb2xzL3JlcG9zIiwic2l0ZV9hZG1pbiI6ZmFsc2UsInN0YXJyZWRfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS91c2Vycy9hd3MtcG93ZXJ0b29scy9zdGFycmVkey9vd25lcn17L3JlcG99Iiwic3Vic2NyaXB0aW9uc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3VzZXJzL2F3cy1wb3dlcnRvb2xzL3N1YnNjcmlwdGlvbnMiLCJ0eXBlIjoiT3JnYW5pemF0aW9uIiwidXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS91c2Vycy9hd3MtcG93ZXJ0b29scyJ9LCJwcml2YXRlIjpmYWxzZSwicHVsbHNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vcHVsbHN7L251bWJlcn0iLCJwdXNoZWRfYXQiOiIyMDI0LTA3LTE3VDIwOjExOjQ4WiIsInJlbGVhc2VzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL3JlbGVhc2Vzey9pZH0iLCJzaXplIjo0ODczNiwic3NoX3VybCI6ImdpdEBnaXRodWIuY29tOmF3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi5naXQiLCJzdGFyZ2F6ZXJzX2NvdW50IjoyNzMyLCJzdGFyZ2F6ZXJzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL3N0YXJnYXplcnMiLCJzdGF0dXNlc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9zdGF0dXNlcy97c2hhfSIsInN1YnNjcmliZXJzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL3N1YnNjcmliZXJzIiwic3Vic2NyaXB0aW9uX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL3N1YnNjcmlwdGlvbiIsInN2bl91cmwiOiJodHRwczovL2dpdGh1Yi5jb20vYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uIiwidGFnc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi90YWdzIiwidGVhbXNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vdGVhbXMiLCJ0b3BpY3MiOlsiYXdzIiwiYXdzLWxhbWJkYSIsImhhY2t0b2JlcmZlc3QiLCJsYW1iZGEiLCJweXRob24iLCJzZXJ2ZXJsZXNzIl0sInRyZWVzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2dpdC90cmVlc3svc2hhfSIsInVwZGF0ZWRfYXQiOiIyMDI0LTA3LTE3VDE5OjEwOjQ2WiIsInVybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uIiwidmlzaWJpbGl0eSI6InB1YmxpYyIsIndhdGNoZXJzIjoyNzMyLCJ3YXRjaGVyc19jb3VudCI6MjczMiwid2ViX2NvbW1pdF9zaWdub2ZmX3JlcXVpcmVkIjp0cnVlfSwic2NoZWR1bGUiOiIwIDggKiAqIDEtNSIsIndvcmtmbG93IjoiLmdpdGh1Yi93b3JrZmxvd3MvcHJlLXJlbGVhc2UueW1sIn0sImdpdGh1Yl9oZWFkX3JlZiI6IiIsImdpdGh1Yl9yZWYiOiJyZWZzL2hlYWRzL2RldmVsb3AiLCJnaXRodWJfcmVmX3R5cGUiOiJicmFuY2giLCJnaXRodWJfcmVwb3NpdG9yeV9pZCI6IjIyMTkxOTM3OSIsImdpdGh1Yl9yZXBvc2l0b3J5X293bmVyIjoiYXdzLXBvd2VydG9vbHMiLCJnaXRodWJfcmVwb3NpdG9yeV9vd25lcl9pZCI6IjEyOTEyNzYzOCIsImdpdGh1Yl9ydW5fYXR0ZW1wdCI6IjEiLCJnaXRodWJfcnVuX2lkIjoiOTk4Nzc2NDk4NyIsImdpdGh1Yl9ydW5fbnVtYmVyIjoiMjMiLCJnaXRodWJfc2hhMSI6ImIxYzA1MzI5MTgzY2ViMzkzZTRiZmM2MDVhM2IzM2Y3Y2E5MDJmZDYifX0sIm1ldGFkYXRhIjp7ImJ1aWxkSW52b2NhdGlvbklEIjoiOTk4Nzc2NDk4Ny0xIiwiY29tcGxldGVuZXNzIjp7InBhcmFtZXRlcnMiOnRydWUsImVudmlyb25tZW50IjpmYWxzZSwibWF0ZXJpYWxzIjpmYWxzZX0sInJlcHJvZHVjaWJsZSI6ZmFsc2V9LCJtYXRlcmlhbHMiOlt7InVyaSI6ImdpdCtodHRwczovL2dpdGh1Yi5jb20vYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uQHJlZnMvaGVhZHMvZGV2ZWxvcCIsImRpZ2VzdCI6eyJzaGExIjoiYjFjMDUzMjkxODNjZWIzOTNlNGJmYzYwNWEzYjMzZjdjYTkwMmZkNiJ9fV19fQ==","signatures":[{"keyid":"","sig":"MEUCIDosHjjV87HKRWLTo51oyLCf4I2buLvbujMm7k6dkcIsAiEAvT6ofcaC0BGBH0S0fTW16wQODz50ZD/PVSf2wWcPRPM=","cert":"-----BEGIN CERTIFICATE-----\nMIIHZTCCBuqgAwIBAgIUdrSKMbnz5ibnS1jpWhXmaNpSuHcwCgYIKoZIzj0EAwMw\nNzEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MR4wHAYDVQQDExVzaWdzdG9yZS1pbnRl\ncm1lZGlhdGUwHhcNMjQwNzE4MDgwNzU2WhcNMjQwNzE4MDgxNzU2WjAAMFkwEwYH\nKoZIzj0CAQYIKoZIzj0DAQcDQgAE5AxTs644cqM1ybFTyMupOjZaw2oPbDOcEVwm\nw2vBlFEkMGwdsyXTqFtZexmXBXlLHqaxwoOGHHMP2od0xcZRnqOCBgkwggYFMA4G\nA1UdDwEB/wQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcDAzAdBgNVHQ4EFgQUF+mZ\ntUoacBuAkBuaIzyGhHp0gk8wHwYDVR0jBBgwFoAU39Ppz1YkEZb5qNjpKFWixi4Y\nZD8wgYQGA1UdEQEB/wR6MHiGdmh0dHBzOi8vZ2l0aHViLmNvbS9zbHNhLWZyYW1l\nd29yay9zbHNhLWdpdGh1Yi1nZW5lcmF0b3IvLmdpdGh1Yi93b3JrZmxvd3MvZ2Vu\nZXJhdG9yX2dlbmVyaWNfc2xzYTMueW1sQHJlZnMvdGFncy92Mi4wLjAwOQYKKwYB\nBAGDvzABAQQraHR0cHM6Ly90b2tlbi5hY3Rpb25zLmdpdGh1YnVzZXJjb250ZW50\nLmNvbTAWBgorBgEEAYO/MAECBAhzY2hlZHVsZTA2BgorBgEEAYO/MAEDBChiMWMw\nNTMyOTE4M2NlYjM5M2U0YmZjNjA1YTNiMzNmN2NhOTAyZmQ2MBkGCisGAQQBg78w\nAQQEC1ByZS1SZWxlYXNlMDUGCisGAQQBg78wAQUEJ2F3cy1wb3dlcnRvb2xzL3Bv\nd2VydG9vbHMtbGFtYmRhLXB5dGhvbjAgBgorBgEEAYO/MAEGBBJyZWZzL2hlYWRz\nL2RldmVsb3AwOwYKKwYBBAGDvzABCAQtDCtodHRwczovL3Rva2VuLmFjdGlvbnMu\nZ2l0aHVidXNlcmNvbnRlbnQuY29tMIGGBgorBgEEAYO/MAEJBHgMdmh0dHBzOi8v\nZ2l0aHViLmNvbS9zbHNhLWZyYW1ld29yay9zbHNhLWdpdGh1Yi1nZW5lcmF0b3Iv\nLmdpdGh1Yi93b3JrZmxvd3MvZ2VuZXJhdG9yX2dlbmVyaWNfc2xzYTMueW1sQHJl\nZnMvdGFncy92Mi4wLjAwOAYKKwYBBAGDvzABCgQqDCg1YTc3NWIzNjdhNTZkNWJk\nMTE4YTIyNGE4MTFiYmEyODgxNTBhNTYzMB0GCisGAQQBg78wAQsEDwwNZ2l0aHVi\nLWhvc3RlZDBKBgorBgEEAYO/MAEMBDwMOmh0dHBzOi8vZ2l0aHViLmNvbS9hd3Mt\ncG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24wOAYKKwYBBAGDvzAB\nDQQqDChiMWMwNTMyOTE4M2NlYjM5M2U0YmZjNjA1YTNiMzNmN2NhOTAyZmQ2MCIG\nCisGAQQBg78wAQ4EFAwScmVmcy9oZWFkcy9kZXZlbG9wMBkGCisGAQQBg78wAQ8E\nCwwJMjIxOTE5Mzc5MDEGCisGAQQBg78wARAEIwwhaHR0cHM6Ly9naXRodWIuY29t\nL2F3cy1wb3dlcnRvb2xzMBkGCisGAQQBg78wAREECwwJMTI5MTI3NjM4MH8GCisG\nAQQBg78wARIEcQxvaHR0cHM6Ly9naXRodWIuY29tL2F3cy1wb3dlcnRvb2xzL3Bv\nd2VydG9vbHMtbGFtYmRhLXB5dGhvbi8uZ2l0aHViL3dvcmtmbG93cy9wcmUtcmVs\nZWFzZS55bWxAcmVmcy9oZWFkcy9kZXZlbG9wMDgGCisGAQQBg78wARMEKgwoYjFj\nMDUzMjkxODNjZWIzOTNlNGJmYzYwNWEzYjMzZjdjYTkwMmZkNjAYBgorBgEEAYO/\nMAEUBAoMCHNjaGVkdWxlMG0GCisGAQQBg78wARUEXwxdaHR0cHM6Ly9naXRodWIu\nY29tL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9hY3Rp\nb25zL3J1bnMvOTk4Nzc2NDk4Ny9hdHRlbXB0cy8xMBYGCisGAQQBg78wARYECAwG\ncHVibGljMIGJBgorBgEEAdZ5AgQCBHsEeQB3AHUA3T0wasbHETJjGR4cmWc3AqJK\nXrjePK3/h4pygC8p7o4AAAGQxOIvwAAABAMARjBEAiB0aUgTCVOdUUUJU8zGH2RE\nu8jpip25jziqEFxw31+r/AIgDcmnCaKTYR8jSGxWnijVjx6MKNz8lpGV2R3ap+EW\nUqswCgYIKoZIzj0EAwMDaQAwZgIxAPy6fzYzDVdo48d6t4XQH58ylyiwCfQfsv0l\ndcfCVRA5n9vwMhFis/cj+W0l04Y24QIxAMGnAgrtbL+3cSDASU8PdHfaCFos/+IY\nRSJ6hPSe3csXLVkeK0Vf6PkDFVXwTC8bkw==\n-----END CERTIFICATE-----\n"}]}
\ No newline at end of file
diff --git a/provenance/2.41.1a6/multiple.intoto.jsonl b/provenance/2.41.1a6/multiple.intoto.jsonl
new file mode 100644
index 00000000000..1137c4f869c
--- /dev/null
+++ b/provenance/2.41.1a6/multiple.intoto.jsonl
@@ -0,0 +1 @@
+{"payloadType":"application/vnd.in-toto+json","payload":"eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjAuMSIsInByZWRpY2F0ZVR5cGUiOiJodHRwczovL3Nsc2EuZGV2L3Byb3ZlbmFuY2UvdjAuMiIsInN1YmplY3QiOlt7Im5hbWUiOiIuL2F3c19sYW1iZGFfcG93ZXJ0b29scy0yLjQxLjFhNi1weTMtbm9uZS1hbnkud2hsIiwiZGlnZXN0Ijp7InNoYTI1NiI6IjdiOWQ3NTEyNmUxYTMzNTU3OGExOTFlMTlkYmNlODRhM2FlOTE2NWFmODA0M2EwMGU0ZWM1YmZjNTM3NjBkY2IifX0seyJuYW1lIjoiLi9hd3NfbGFtYmRhX3Bvd2VydG9vbHMtMi40MS4xYTYudGFyLmd6IiwiZGlnZXN0Ijp7InNoYTI1NiI6ImI5M2FlMjBjM2UzYmM2ZmNiNjM2ODI4ODIxMzcxOWRmZDE5ZGVkN2NkNzcwNmJiZWQzMjA3MjIxMmFiZDZmOWQifX1dLCJwcmVkaWNhdGUiOnsiYnVpbGRlciI6eyJpZCI6Imh0dHBzOi8vZ2l0aHViLmNvbS9zbHNhLWZyYW1ld29yay9zbHNhLWdpdGh1Yi1nZW5lcmF0b3IvLmdpdGh1Yi93b3JrZmxvd3MvZ2VuZXJhdG9yX2dlbmVyaWNfc2xzYTMueW1sQHJlZnMvdGFncy92Mi4wLjAifSwiYnVpbGRUeXBlIjoiaHR0cHM6Ly9naXRodWIuY29tL3Nsc2EtZnJhbWV3b3JrL3Nsc2EtZ2l0aHViLWdlbmVyYXRvci9nZW5lcmljQHYxIiwiaW52b2NhdGlvbiI6eyJjb25maWdTb3VyY2UiOnsidXJpIjoiZ2l0K2h0dHBzOi8vZ2l0aHViLmNvbS9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob25AcmVmcy9oZWFkcy9kZXZlbG9wIiwiZGlnZXN0Ijp7InNoYTEiOiJlNTNiNTIxNGRkZTI0ODc2MTZhMjAzOTRjZWFlMGNkZTlmMzg5YTJlIn0sImVudHJ5UG9pbnQiOiIuZ2l0aHViL3dvcmtmbG93cy9wcmUtcmVsZWFzZS55bWwifSwicGFyYW1ldGVycyI6e30sImVudmlyb25tZW50Ijp7ImdpdGh1Yl9hY3RvciI6ImxlYW5kcm9kYW1hc2NlbmEiLCJnaXRodWJfYWN0b3JfaWQiOiI0Mjk1MTczIiwiZ2l0aHViX2Jhc2VfcmVmIjoiIiwiZ2l0aHViX2V2ZW50X25hbWUiOiJzY2hlZHVsZSIsImdpdGh1Yl9ldmVudF9wYXlsb2FkIjp7ImVudGVycHJpc2UiOnsiYXZhdGFyX3VybCI6Imh0dHBzOi8vYXZhdGFycy5naXRodWJ1c2VyY29udGVudC5jb20vYi8xMjkwP3Y9NCIsImNyZWF0ZWRfYXQiOiIyMDE5LTExLTEzVDE4OjA1OjQxWiIsImRlc2NyaXB0aW9uIjoiIiwiaHRtbF91cmwiOiJodHRwczovL2dpdGh1Yi5jb20vZW50ZXJwcmlzZXMvYW1hem9uIiwiaWQiOjEyOTAsIm5hbWUiOiJBbWF6b24iLCJub2RlX2lkIjoiTURFd09rVnVkR1Z5Y0hKcGMyVXhNamt3Iiwic2x1ZyI6ImFtYXpvbiIsInVwZGF0ZWRfYXQiOiIyMDIzLTAxLTI3VDE0OjU2OjEwWiIsIndlYnNpdGVfdXJsIjoiaHR0cHM6Ly93d3cuYW1hem9uLmNvbS8ifSwib3JnYW5pemF0aW9uIjp7ImF2YXRhcl91cmwiOiJodHRwczovL2F2YXRhcnMuZ2l0aHVidXNlcmNvbnRlbnQuY29tL3UvMTI5MTI3NjM4P3Y9NCIsImRlc2NyaXB0aW9uIjoiIiwiZXZlbnRzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vb3Jncy9hd3MtcG93ZXJ0b29scy9ldmVudHMiLCJob29rc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL29yZ3MvYXdzLXBvd2VydG9vbHMvaG9va3MiLCJpZCI6MTI5MTI3NjM4LCJpc3N1ZXNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9vcmdzL2F3cy1wb3dlcnRvb2xzL2lzc3VlcyIsImxvZ2luIjoiYXdzLXBvd2VydG9vbHMiLCJtZW1iZXJzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vb3Jncy9hd3MtcG93ZXJ0b29scy9tZW1iZXJzey9tZW1iZXJ9Iiwibm9kZV9pZCI6Ik9fa2dET0I3SlUxZyIsInB1YmxpY19tZW1iZXJzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vb3Jncy9hd3MtcG93ZXJ0b29scy9wdWJsaWNfbWVtYmVyc3svbWVtYmVyfSIsInJlcG9zX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vb3Jncy9hd3MtcG93ZXJ0b29scy9yZXBvcyIsInVybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vb3Jncy9hd3MtcG93ZXJ0b29scyJ9LCJyZXBvc2l0b3J5Ijp7ImFsbG93X2ZvcmtpbmciOnRydWUsImFyY2hpdmVfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24ve2FyY2hpdmVfZm9ybWF0fXsvcmVmfSIsImFyY2hpdmVkIjpmYWxzZSwiYXNzaWduZWVzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2Fzc2lnbmVlc3svdXNlcn0iLCJibG9ic191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9naXQvYmxvYnN7L3NoYX0iLCJicmFuY2hlc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9icmFuY2hlc3svYnJhbmNofSIsImNsb25lX3VybCI6Imh0dHBzOi8vZ2l0aHViLmNvbS9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24uZ2l0IiwiY29sbGFib3JhdG9yc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9jb2xsYWJvcmF0b3Jzey9jb2xsYWJvcmF0b3J9IiwiY29tbWVudHNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vY29tbWVudHN7L251bWJlcn0iLCJjb21taXRzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2NvbW1pdHN7L3NoYX0iLCJjb21wYXJlX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2NvbXBhcmUve2Jhc2V9Li4ue2hlYWR9IiwiY29udGVudHNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vY29udGVudHMveytwYXRofSIsImNvbnRyaWJ1dG9yc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9jb250cmlidXRvcnMiLCJjcmVhdGVkX2F0IjoiMjAxOS0xMS0xNVQxMjoyNjoxMloiLCJkZWZhdWx0X2JyYW5jaCI6ImRldmVsb3AiLCJkZXBsb3ltZW50c191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9kZXBsb3ltZW50cyIsImRlc2NyaXB0aW9uIjoiQSBkZXZlbG9wZXIgdG9vbGtpdCB0byBpbXBsZW1lbnQgU2VydmVybGVzcyBiZXN0IHByYWN0aWNlcyBhbmQgaW5jcmVhc2UgZGV2ZWxvcGVyIHZlbG9jaXR5LiIsImRpc2FibGVkIjpmYWxzZSwiZG93bmxvYWRzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2Rvd25sb2FkcyIsImV2ZW50c191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9ldmVudHMiLCJmb3JrIjpmYWxzZSwiZm9ya3MiOjM3OCwiZm9ya3NfY291bnQiOjM3OCwiZm9ya3NfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vZm9ya3MiLCJmdWxsX25hbWUiOiJhd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24iLCJnaXRfY29tbWl0c191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9naXQvY29tbWl0c3svc2hhfSIsImdpdF9yZWZzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2dpdC9yZWZzey9zaGF9IiwiZ2l0X3RhZ3NfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vZ2l0L3RhZ3N7L3NoYX0iLCJnaXRfdXJsIjoiZ2l0Oi8vZ2l0aHViLmNvbS9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24uZ2l0IiwiaGFzX2Rpc2N1c3Npb25zIjp0cnVlLCJoYXNfZG93bmxvYWRzIjp0cnVlLCJoYXNfaXNzdWVzIjp0cnVlLCJoYXNfcGFnZXMiOmZhbHNlLCJoYXNfcHJvamVjdHMiOnRydWUsImhhc193aWtpIjpmYWxzZSwiaG9tZXBhZ2UiOiJodHRwczovL2RvY3MucG93ZXJ0b29scy5hd3MuZGV2L2xhbWJkYS9weXRob24vbGF0ZXN0LyIsImhvb2tzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2hvb2tzIiwiaHRtbF91cmwiOiJodHRwczovL2dpdGh1Yi5jb20vYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uIiwiaWQiOjIyMTkxOTM3OSwiaXNfdGVtcGxhdGUiOmZhbHNlLCJpc3N1ZV9jb21tZW50X3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2lzc3Vlcy9jb21tZW50c3svbnVtYmVyfSIsImlzc3VlX2V2ZW50c191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9pc3N1ZXMvZXZlbnRzey9udW1iZXJ9IiwiaXNzdWVzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2lzc3Vlc3svbnVtYmVyfSIsImtleXNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24va2V5c3sva2V5X2lkfSIsImxhYmVsc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9sYWJlbHN7L25hbWV9IiwibGFuZ3VhZ2UiOiJQeXRob24iLCJsYW5ndWFnZXNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vbGFuZ3VhZ2VzIiwibGljZW5zZSI6eyJrZXkiOiJtaXQtMCIsIm5hbWUiOiJNSVQgTm8gQXR0cmlidXRpb24iLCJub2RlX2lkIjoiTURjNlRHbGpaVzV6WlRReCIsInNwZHhfaWQiOiJNSVQtMCIsInVybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vbGljZW5zZXMvbWl0LTAifSwibWVyZ2VzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL21lcmdlcyIsIm1pbGVzdG9uZXNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vbWlsZXN0b25lc3svbnVtYmVyfSIsIm1pcnJvcl91cmwiOm51bGwsIm5hbWUiOiJwb3dlcnRvb2xzLWxhbWJkYS1weXRob24iLCJub2RlX2lkIjoiTURFd09sSmxjRzl6YVhSdmNua3lNakU1TVRrek56az0iLCJub3RpZmljYXRpb25zX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL25vdGlmaWNhdGlvbnN7P3NpbmNlLGFsbCxwYXJ0aWNpcGF0aW5nfSIsIm9wZW5faXNzdWVzIjoxMDAsIm9wZW5faXNzdWVzX2NvdW50IjoxMDAsIm93bmVyIjp7ImF2YXRhcl91cmwiOiJodHRwczovL2F2YXRhcnMuZ2l0aHVidXNlcmNvbnRlbnQuY29tL3UvMTI5MTI3NjM4P3Y9NCIsImV2ZW50c191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3VzZXJzL2F3cy1wb3dlcnRvb2xzL2V2ZW50c3svcHJpdmFjeX0iLCJmb2xsb3dlcnNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS91c2Vycy9hd3MtcG93ZXJ0b29scy9mb2xsb3dlcnMiLCJmb2xsb3dpbmdfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS91c2Vycy9hd3MtcG93ZXJ0b29scy9mb2xsb3dpbmd7L290aGVyX3VzZXJ9IiwiZ2lzdHNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS91c2Vycy9hd3MtcG93ZXJ0b29scy9naXN0c3svZ2lzdF9pZH0iLCJncmF2YXRhcl9pZCI6IiIsImh0bWxfdXJsIjoiaHR0cHM6Ly9naXRodWIuY29tL2F3cy1wb3dlcnRvb2xzIiwiaWQiOjEyOTEyNzYzOCwibG9naW4iOiJhd3MtcG93ZXJ0b29scyIsIm5vZGVfaWQiOiJPX2tnRE9CN0pVMWciLCJvcmdhbml6YXRpb25zX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vdXNlcnMvYXdzLXBvd2VydG9vbHMvb3JncyIsInJlY2VpdmVkX2V2ZW50c191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3VzZXJzL2F3cy1wb3dlcnRvb2xzL3JlY2VpdmVkX2V2ZW50cyIsInJlcG9zX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vdXNlcnMvYXdzLXBvd2VydG9vbHMvcmVwb3MiLCJzaXRlX2FkbWluIjpmYWxzZSwic3RhcnJlZF91cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3VzZXJzL2F3cy1wb3dlcnRvb2xzL3N0YXJyZWR7L293bmVyfXsvcmVwb30iLCJzdWJzY3JpcHRpb25zX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vdXNlcnMvYXdzLXBvd2VydG9vbHMvc3Vic2NyaXB0aW9ucyIsInR5cGUiOiJPcmdhbml6YXRpb24iLCJ1cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3VzZXJzL2F3cy1wb3dlcnRvb2xzIn0sInByaXZhdGUiOmZhbHNlLCJwdWxsc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9wdWxsc3svbnVtYmVyfSIsInB1c2hlZF9hdCI6IjIwMjQtMDctMThUMjM6NDY6NTdaIiwicmVsZWFzZXNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vcmVsZWFzZXN7L2lkfSIsInNpemUiOjQ4ODM4LCJzc2hfdXJsIjoiZ2l0QGdpdGh1Yi5jb206YXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uLmdpdCIsInN0YXJnYXplcnNfY291bnQiOjI3MzIsInN0YXJnYXplcnNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vc3RhcmdhemVycyIsInN0YXR1c2VzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL3N0YXR1c2VzL3tzaGF9Iiwic3Vic2NyaWJlcnNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vc3Vic2NyaWJlcnMiLCJzdWJzY3JpcHRpb25fdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vc3Vic2NyaXB0aW9uIiwic3ZuX3VybCI6Imh0dHBzOi8vZ2l0aHViLmNvbS9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24iLCJ0YWdzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL3RhZ3MiLCJ0ZWFtc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi90ZWFtcyIsInRvcGljcyI6WyJhd3MiLCJhd3MtbGFtYmRhIiwiaGFja3RvYmVyZmVzdCIsImxhbWJkYSIsInB5dGhvbiIsInNlcnZlcmxlc3MiXSwidHJlZXNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vZ2l0L3RyZWVzey9zaGF9IiwidXBkYXRlZF9hdCI6IjIwMjQtMDctMThUMjM6NDQ6MThaIiwidXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24iLCJ2aXNpYmlsaXR5IjoicHVibGljIiwid2F0Y2hlcnMiOjI3MzIsIndhdGNoZXJzX2NvdW50IjoyNzMyLCJ3ZWJfY29tbWl0X3NpZ25vZmZfcmVxdWlyZWQiOnRydWV9LCJzY2hlZHVsZSI6IjAgOCAqICogMS01Iiwid29ya2Zsb3ciOiIuZ2l0aHViL3dvcmtmbG93cy9wcmUtcmVsZWFzZS55bWwifSwiZ2l0aHViX2hlYWRfcmVmIjoiIiwiZ2l0aHViX3JlZiI6InJlZnMvaGVhZHMvZGV2ZWxvcCIsImdpdGh1Yl9yZWZfdHlwZSI6ImJyYW5jaCIsImdpdGh1Yl9yZXBvc2l0b3J5X2lkIjoiMjIxOTE5Mzc5IiwiZ2l0aHViX3JlcG9zaXRvcnlfb3duZXIiOiJhd3MtcG93ZXJ0b29scyIsImdpdGh1Yl9yZXBvc2l0b3J5X293bmVyX2lkIjoiMTI5MTI3NjM4IiwiZ2l0aHViX3J1bl9hdHRlbXB0IjoiMSIsImdpdGh1Yl9ydW5faWQiOiIxMDAwNDY3MjMyMCIsImdpdGh1Yl9ydW5fbnVtYmVyIjoiMjQiLCJnaXRodWJfc2hhMSI6ImU1M2I1MjE0ZGRlMjQ4NzYxNmEyMDM5NGNlYWUwY2RlOWYzODlhMmUifX0sIm1ldGFkYXRhIjp7ImJ1aWxkSW52b2NhdGlvbklEIjoiMTAwMDQ2NzIzMjAtMSIsImNvbXBsZXRlbmVzcyI6eyJwYXJhbWV0ZXJzIjp0cnVlLCJlbnZpcm9ubWVudCI6ZmFsc2UsIm1hdGVyaWFscyI6ZmFsc2V9LCJyZXByb2R1Y2libGUiOmZhbHNlfSwibWF0ZXJpYWxzIjpbeyJ1cmkiOiJnaXQraHR0cHM6Ly9naXRodWIuY29tL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbkByZWZzL2hlYWRzL2RldmVsb3AiLCJkaWdlc3QiOnsic2hhMSI6ImU1M2I1MjE0ZGRlMjQ4NzYxNmEyMDM5NGNlYWUwY2RlOWYzODlhMmUifX1dfX0=","signatures":[{"keyid":"","sig":"MEYCIQD7L87da4qT7zGnAIe+HZZudVD7VPcOKwPggsgTi7C1CAIhALchyUV51O46Bu5mZG8spiqgzDQvOtz0awXs4V+7B5KH","cert":"-----BEGIN CERTIFICATE-----\nMIIHZjCCBuygAwIBAgIUWzIvKMaH8DyI1R7LeATDcFqJ5xwwCgYIKoZIzj0EAwMw\nNzEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MR4wHAYDVQQDExVzaWdzdG9yZS1pbnRl\ncm1lZGlhdGUwHhcNMjQwNzE5MDgwNzM1WhcNMjQwNzE5MDgxNzM1WjAAMFkwEwYH\nKoZIzj0CAQYIKoZIzj0DAQcDQgAEZW2bby/x7EEkioufITTfLcpyw8SQPKRTrWyf\nQayxuP5YwpPdqkYkSo0FHagGzoJgLYmX5uip6jsGprMczX9kQaOCBgswggYHMA4G\nA1UdDwEB/wQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcDAzAdBgNVHQ4EFgQUE80X\niTLI1BcC8hnD6+3PpvmgBVIwHwYDVR0jBBgwFoAU39Ppz1YkEZb5qNjpKFWixi4Y\nZD8wgYQGA1UdEQEB/wR6MHiGdmh0dHBzOi8vZ2l0aHViLmNvbS9zbHNhLWZyYW1l\nd29yay9zbHNhLWdpdGh1Yi1nZW5lcmF0b3IvLmdpdGh1Yi93b3JrZmxvd3MvZ2Vu\nZXJhdG9yX2dlbmVyaWNfc2xzYTMueW1sQHJlZnMvdGFncy92Mi4wLjAwOQYKKwYB\nBAGDvzABAQQraHR0cHM6Ly90b2tlbi5hY3Rpb25zLmdpdGh1YnVzZXJjb250ZW50\nLmNvbTAWBgorBgEEAYO/MAECBAhzY2hlZHVsZTA2BgorBgEEAYO/MAEDBChlNTNi\nNTIxNGRkZTI0ODc2MTZhMjAzOTRjZWFlMGNkZTlmMzg5YTJlMBkGCisGAQQBg78w\nAQQEC1ByZS1SZWxlYXNlMDUGCisGAQQBg78wAQUEJ2F3cy1wb3dlcnRvb2xzL3Bv\nd2VydG9vbHMtbGFtYmRhLXB5dGhvbjAgBgorBgEEAYO/MAEGBBJyZWZzL2hlYWRz\nL2RldmVsb3AwOwYKKwYBBAGDvzABCAQtDCtodHRwczovL3Rva2VuLmFjdGlvbnMu\nZ2l0aHVidXNlcmNvbnRlbnQuY29tMIGGBgorBgEEAYO/MAEJBHgMdmh0dHBzOi8v\nZ2l0aHViLmNvbS9zbHNhLWZyYW1ld29yay9zbHNhLWdpdGh1Yi1nZW5lcmF0b3Iv\nLmdpdGh1Yi93b3JrZmxvd3MvZ2VuZXJhdG9yX2dlbmVyaWNfc2xzYTMueW1sQHJl\nZnMvdGFncy92Mi4wLjAwOAYKKwYBBAGDvzABCgQqDCg1YTc3NWIzNjdhNTZkNWJk\nMTE4YTIyNGE4MTFiYmEyODgxNTBhNTYzMB0GCisGAQQBg78wAQsEDwwNZ2l0aHVi\nLWhvc3RlZDBKBgorBgEEAYO/MAEMBDwMOmh0dHBzOi8vZ2l0aHViLmNvbS9hd3Mt\ncG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24wOAYKKwYBBAGDvzAB\nDQQqDChlNTNiNTIxNGRkZTI0ODc2MTZhMjAzOTRjZWFlMGNkZTlmMzg5YTJlMCIG\nCisGAQQBg78wAQ4EFAwScmVmcy9oZWFkcy9kZXZlbG9wMBkGCisGAQQBg78wAQ8E\nCwwJMjIxOTE5Mzc5MDEGCisGAQQBg78wARAEIwwhaHR0cHM6Ly9naXRodWIuY29t\nL2F3cy1wb3dlcnRvb2xzMBkGCisGAQQBg78wAREECwwJMTI5MTI3NjM4MH8GCisG\nAQQBg78wARIEcQxvaHR0cHM6Ly9naXRodWIuY29tL2F3cy1wb3dlcnRvb2xzL3Bv\nd2VydG9vbHMtbGFtYmRhLXB5dGhvbi8uZ2l0aHViL3dvcmtmbG93cy9wcmUtcmVs\nZWFzZS55bWxAcmVmcy9oZWFkcy9kZXZlbG9wMDgGCisGAQQBg78wARMEKgwoZTUz\nYjUyMTRkZGUyNDg3NjE2YTIwMzk0Y2VhZTBjZGU5ZjM4OWEyZTAYBgorBgEEAYO/\nMAEUBAoMCHNjaGVkdWxlMG4GCisGAQQBg78wARUEYAxeaHR0cHM6Ly9naXRodWIu\nY29tL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9hY3Rp\nb25zL3J1bnMvMTAwMDQ2NzIzMjAvYXR0ZW1wdHMvMTAWBgorBgEEAYO/MAEWBAgM\nBnB1YmxpYzCBigYKKwYBBAHWeQIEAgR8BHoAeAB2AN09MGrGxxEyYxkeHJlnNwKi\nSl643jyt/4eKcoAvKe6OAAABkMoIOmsAAAQDAEcwRQIhAJ1oa/W483Tpau7ZBrsm\n7AN/p0uyup7foIqb9lBmZelrAiBRrd2ikZUqWnJaPVyb2aEAxzFIOLmDk/JZfSh4\ntxb+ozAKBggqhkjOPQQDAwNoADBlAjBncCvvjQx7UGW2inWgMZLFpNBhIXIn5dW2\nN1o2f13vvO06HvJ9l8yWz7uwJKOd81gCMQCT61q/AZ2GWWNnmt91QFwjGGxRE3fG\nIvjAdLrRVzcbLnXUYA1N18eq9tWUJfd5ATI=\n-----END CERTIFICATE-----\n"}]}
\ No newline at end of file
diff --git a/provenance/2.41.1a7/multiple.intoto.jsonl b/provenance/2.41.1a7/multiple.intoto.jsonl
new file mode 100644
index 00000000000..44f83fad0b1
--- /dev/null
+++ b/provenance/2.41.1a7/multiple.intoto.jsonl
@@ -0,0 +1 @@
+{"payloadType":"application/vnd.in-toto+json","payload":"eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjAuMSIsInByZWRpY2F0ZVR5cGUiOiJodHRwczovL3Nsc2EuZGV2L3Byb3ZlbmFuY2UvdjAuMiIsInN1YmplY3QiOlt7Im5hbWUiOiIuL2F3c19sYW1iZGFfcG93ZXJ0b29scy0yLjQxLjFhNy1weTMtbm9uZS1hbnkud2hsIiwiZGlnZXN0Ijp7InNoYTI1NiI6IjI4NWYzNmFkOWE0NDRjMjc5ZTk1ODRhZjFmNTIxNjNiNjM5YWViNjBhMjAzOGVlY2Q0ZDljNDAzNTZjZTEwMTMifX0seyJuYW1lIjoiLi9hd3NfbGFtYmRhX3Bvd2VydG9vbHMtMi40MS4xYTcudGFyLmd6IiwiZGlnZXN0Ijp7InNoYTI1NiI6IjgzMjI2NGQ2NmZkZmRmYWYwNzJiYWE0NmRiMWE5OTkxMTg5NmY2YWRmMWRjYzJlYzdkZTljOTk4NGI3YWNlNjcifX1dLCJwcmVkaWNhdGUiOnsiYnVpbGRlciI6eyJpZCI6Imh0dHBzOi8vZ2l0aHViLmNvbS9zbHNhLWZyYW1ld29yay9zbHNhLWdpdGh1Yi1nZW5lcmF0b3IvLmdpdGh1Yi93b3JrZmxvd3MvZ2VuZXJhdG9yX2dlbmVyaWNfc2xzYTMueW1sQHJlZnMvdGFncy92Mi4wLjAifSwiYnVpbGRUeXBlIjoiaHR0cHM6Ly9naXRodWIuY29tL3Nsc2EtZnJhbWV3b3JrL3Nsc2EtZ2l0aHViLWdlbmVyYXRvci9nZW5lcmljQHYxIiwiaW52b2NhdGlvbiI6eyJjb25maWdTb3VyY2UiOnsidXJpIjoiZ2l0K2h0dHBzOi8vZ2l0aHViLmNvbS9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob25AcmVmcy9oZWFkcy9kZXZlbG9wIiwiZGlnZXN0Ijp7InNoYTEiOiJhOGFlNDZlYzdiODUxYzIxODIwOWU0MjNjODIyZmY5YjVjODBiMzhlIn0sImVudHJ5UG9pbnQiOiIuZ2l0aHViL3dvcmtmbG93cy9wcmUtcmVsZWFzZS55bWwifSwicGFyYW1ldGVycyI6e30sImVudmlyb25tZW50Ijp7ImdpdGh1Yl9hY3RvciI6ImxlYW5kcm9kYW1hc2NlbmEiLCJnaXRodWJfYWN0b3JfaWQiOiI0Mjk1MTczIiwiZ2l0aHViX2Jhc2VfcmVmIjoiIiwiZ2l0aHViX2V2ZW50X25hbWUiOiJzY2hlZHVsZSIsImdpdGh1Yl9ldmVudF9wYXlsb2FkIjp7ImVudGVycHJpc2UiOnsiYXZhdGFyX3VybCI6Imh0dHBzOi8vYXZhdGFycy5naXRodWJ1c2VyY29udGVudC5jb20vYi8xMjkwP3Y9NCIsImNyZWF0ZWRfYXQiOiIyMDE5LTExLTEzVDE4OjA1OjQxWiIsImRlc2NyaXB0aW9uIjoiIiwiaHRtbF91cmwiOiJodHRwczovL2dpdGh1Yi5jb20vZW50ZXJwcmlzZXMvYW1hem9uIiwiaWQiOjEyOTAsIm5hbWUiOiJBbWF6b24iLCJub2RlX2lkIjoiTURFd09rVnVkR1Z5Y0hKcGMyVXhNamt3Iiwic2x1ZyI6ImFtYXpvbiIsInVwZGF0ZWRfYXQiOiIyMDIzLTAxLTI3VDE0OjU2OjEwWiIsIndlYnNpdGVfdXJsIjoiaHR0cHM6Ly93d3cuYW1hem9uLmNvbS8ifSwib3JnYW5pemF0aW9uIjp7ImF2YXRhcl91cmwiOiJodHRwczovL2F2YXRhcnMuZ2l0aHVidXNlcmNvbnRlbnQuY29tL3UvMTI5MTI3NjM4P3Y9NCIsImRlc2NyaXB0aW9uIjoiIiwiZXZlbnRzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vb3Jncy9hd3MtcG93ZXJ0b29scy9ldmVudHMiLCJob29rc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL29yZ3MvYXdzLXBvd2VydG9vbHMvaG9va3MiLCJpZCI6MTI5MTI3NjM4LCJpc3N1ZXNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9vcmdzL2F3cy1wb3dlcnRvb2xzL2lzc3VlcyIsImxvZ2luIjoiYXdzLXBvd2VydG9vbHMiLCJtZW1iZXJzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vb3Jncy9hd3MtcG93ZXJ0b29scy9tZW1iZXJzey9tZW1iZXJ9Iiwibm9kZV9pZCI6Ik9fa2dET0I3SlUxZyIsInB1YmxpY19tZW1iZXJzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vb3Jncy9hd3MtcG93ZXJ0b29scy9wdWJsaWNfbWVtYmVyc3svbWVtYmVyfSIsInJlcG9zX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vb3Jncy9hd3MtcG93ZXJ0b29scy9yZXBvcyIsInVybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vb3Jncy9hd3MtcG93ZXJ0b29scyJ9LCJyZXBvc2l0b3J5Ijp7ImFsbG93X2ZvcmtpbmciOnRydWUsImFyY2hpdmVfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24ve2FyY2hpdmVfZm9ybWF0fXsvcmVmfSIsImFyY2hpdmVkIjpmYWxzZSwiYXNzaWduZWVzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2Fzc2lnbmVlc3svdXNlcn0iLCJibG9ic191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9naXQvYmxvYnN7L3NoYX0iLCJicmFuY2hlc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9icmFuY2hlc3svYnJhbmNofSIsImNsb25lX3VybCI6Imh0dHBzOi8vZ2l0aHViLmNvbS9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24uZ2l0IiwiY29sbGFib3JhdG9yc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9jb2xsYWJvcmF0b3Jzey9jb2xsYWJvcmF0b3J9IiwiY29tbWVudHNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vY29tbWVudHN7L251bWJlcn0iLCJjb21taXRzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2NvbW1pdHN7L3NoYX0iLCJjb21wYXJlX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2NvbXBhcmUve2Jhc2V9Li4ue2hlYWR9IiwiY29udGVudHNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vY29udGVudHMveytwYXRofSIsImNvbnRyaWJ1dG9yc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9jb250cmlidXRvcnMiLCJjcmVhdGVkX2F0IjoiMjAxOS0xMS0xNVQxMjoyNjoxMloiLCJkZWZhdWx0X2JyYW5jaCI6ImRldmVsb3AiLCJkZXBsb3ltZW50c191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9kZXBsb3ltZW50cyIsImRlc2NyaXB0aW9uIjoiQSBkZXZlbG9wZXIgdG9vbGtpdCB0byBpbXBsZW1lbnQgU2VydmVybGVzcyBiZXN0IHByYWN0aWNlcyBhbmQgaW5jcmVhc2UgZGV2ZWxvcGVyIHZlbG9jaXR5LiIsImRpc2FibGVkIjpmYWxzZSwiZG93bmxvYWRzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2Rvd25sb2FkcyIsImV2ZW50c191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9ldmVudHMiLCJmb3JrIjpmYWxzZSwiZm9ya3MiOjM3OCwiZm9ya3NfY291bnQiOjM3OCwiZm9ya3NfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vZm9ya3MiLCJmdWxsX25hbWUiOiJhd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24iLCJnaXRfY29tbWl0c191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9naXQvY29tbWl0c3svc2hhfSIsImdpdF9yZWZzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2dpdC9yZWZzey9zaGF9IiwiZ2l0X3RhZ3NfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vZ2l0L3RhZ3N7L3NoYX0iLCJnaXRfdXJsIjoiZ2l0Oi8vZ2l0aHViLmNvbS9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24uZ2l0IiwiaGFzX2Rpc2N1c3Npb25zIjp0cnVlLCJoYXNfZG93bmxvYWRzIjp0cnVlLCJoYXNfaXNzdWVzIjp0cnVlLCJoYXNfcGFnZXMiOmZhbHNlLCJoYXNfcHJvamVjdHMiOnRydWUsImhhc193aWtpIjpmYWxzZSwiaG9tZXBhZ2UiOiJodHRwczovL2RvY3MucG93ZXJ0b29scy5hd3MuZGV2L2xhbWJkYS9weXRob24vbGF0ZXN0LyIsImhvb2tzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2hvb2tzIiwiaHRtbF91cmwiOiJodHRwczovL2dpdGh1Yi5jb20vYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uIiwiaWQiOjIyMTkxOTM3OSwiaXNfdGVtcGxhdGUiOmZhbHNlLCJpc3N1ZV9jb21tZW50X3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2lzc3Vlcy9jb21tZW50c3svbnVtYmVyfSIsImlzc3VlX2V2ZW50c191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9pc3N1ZXMvZXZlbnRzey9udW1iZXJ9IiwiaXNzdWVzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2lzc3Vlc3svbnVtYmVyfSIsImtleXNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24va2V5c3sva2V5X2lkfSIsImxhYmVsc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9sYWJlbHN7L25hbWV9IiwibGFuZ3VhZ2UiOiJQeXRob24iLCJsYW5ndWFnZXNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vbGFuZ3VhZ2VzIiwibGljZW5zZSI6eyJrZXkiOiJtaXQtMCIsIm5hbWUiOiJNSVQgTm8gQXR0cmlidXRpb24iLCJub2RlX2lkIjoiTURjNlRHbGpaVzV6WlRReCIsInNwZHhfaWQiOiJNSVQtMCIsInVybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vbGljZW5zZXMvbWl0LTAifSwibWVyZ2VzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL21lcmdlcyIsIm1pbGVzdG9uZXNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vbWlsZXN0b25lc3svbnVtYmVyfSIsIm1pcnJvcl91cmwiOm51bGwsIm5hbWUiOiJwb3dlcnRvb2xzLWxhbWJkYS1weXRob24iLCJub2RlX2lkIjoiTURFd09sSmxjRzl6YVhSdmNua3lNakU1TVRrek56az0iLCJub3RpZmljYXRpb25zX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL25vdGlmaWNhdGlvbnN7P3NpbmNlLGFsbCxwYXJ0aWNpcGF0aW5nfSIsIm9wZW5faXNzdWVzIjo5Niwib3Blbl9pc3N1ZXNfY291bnQiOjk2LCJvd25lciI6eyJhdmF0YXJfdXJsIjoiaHR0cHM6Ly9hdmF0YXJzLmdpdGh1YnVzZXJjb250ZW50LmNvbS91LzEyOTEyNzYzOD92PTQiLCJldmVudHNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS91c2Vycy9hd3MtcG93ZXJ0b29scy9ldmVudHN7L3ByaXZhY3l9IiwiZm9sbG93ZXJzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vdXNlcnMvYXdzLXBvd2VydG9vbHMvZm9sbG93ZXJzIiwiZm9sbG93aW5nX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vdXNlcnMvYXdzLXBvd2VydG9vbHMvZm9sbG93aW5ney9vdGhlcl91c2VyfSIsImdpc3RzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vdXNlcnMvYXdzLXBvd2VydG9vbHMvZ2lzdHN7L2dpc3RfaWR9IiwiZ3JhdmF0YXJfaWQiOiIiLCJodG1sX3VybCI6Imh0dHBzOi8vZ2l0aHViLmNvbS9hd3MtcG93ZXJ0b29scyIsImlkIjoxMjkxMjc2MzgsImxvZ2luIjoiYXdzLXBvd2VydG9vbHMiLCJub2RlX2lkIjoiT19rZ0RPQjdKVTFnIiwib3JnYW5pemF0aW9uc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3VzZXJzL2F3cy1wb3dlcnRvb2xzL29yZ3MiLCJyZWNlaXZlZF9ldmVudHNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS91c2Vycy9hd3MtcG93ZXJ0b29scy9yZWNlaXZlZF9ldmVudHMiLCJyZXBvc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3VzZXJzL2F3cy1wb3dlcnRvb2xzL3JlcG9zIiwic2l0ZV9hZG1pbiI6ZmFsc2UsInN0YXJyZWRfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS91c2Vycy9hd3MtcG93ZXJ0b29scy9zdGFycmVkey9vd25lcn17L3JlcG99Iiwic3Vic2NyaXB0aW9uc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3VzZXJzL2F3cy1wb3dlcnRvb2xzL3N1YnNjcmlwdGlvbnMiLCJ0eXBlIjoiT3JnYW5pemF0aW9uIiwidXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS91c2Vycy9hd3MtcG93ZXJ0b29scyJ9LCJwcml2YXRlIjpmYWxzZSwicHVsbHNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vcHVsbHN7L251bWJlcn0iLCJwdXNoZWRfYXQiOiIyMDI0LTA3LTIyVDA1OjUyOjE3WiIsInJlbGVhc2VzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL3JlbGVhc2Vzey9pZH0iLCJzaXplIjo0ODcxNCwic3NoX3VybCI6ImdpdEBnaXRodWIuY29tOmF3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi5naXQiLCJzdGFyZ2F6ZXJzX2NvdW50IjoyNzM0LCJzdGFyZ2F6ZXJzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL3N0YXJnYXplcnMiLCJzdGF0dXNlc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9zdGF0dXNlcy97c2hhfSIsInN1YnNjcmliZXJzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL3N1YnNjcmliZXJzIiwic3Vic2NyaXB0aW9uX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL3N1YnNjcmlwdGlvbiIsInN2bl91cmwiOiJodHRwczovL2dpdGh1Yi5jb20vYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uIiwidGFnc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi90YWdzIiwidGVhbXNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vdGVhbXMiLCJ0b3BpY3MiOlsiYXdzIiwiYXdzLWxhbWJkYSIsImhhY2t0b2JlcmZlc3QiLCJsYW1iZGEiLCJweXRob24iLCJzZXJ2ZXJsZXNzIl0sInRyZWVzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2dpdC90cmVlc3svc2hhfSIsInVwZGF0ZWRfYXQiOiIyMDI0LTA3LTIyVDAwOjIyOjU0WiIsInVybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uIiwidmlzaWJpbGl0eSI6InB1YmxpYyIsIndhdGNoZXJzIjoyNzM0LCJ3YXRjaGVyc19jb3VudCI6MjczNCwid2ViX2NvbW1pdF9zaWdub2ZmX3JlcXVpcmVkIjp0cnVlfSwic2NoZWR1bGUiOiIwIDggKiAqIDEtNSIsIndvcmtmbG93IjoiLmdpdGh1Yi93b3JrZmxvd3MvcHJlLXJlbGVhc2UueW1sIn0sImdpdGh1Yl9oZWFkX3JlZiI6IiIsImdpdGh1Yl9yZWYiOiJyZWZzL2hlYWRzL2RldmVsb3AiLCJnaXRodWJfcmVmX3R5cGUiOiJicmFuY2giLCJnaXRodWJfcmVwb3NpdG9yeV9pZCI6IjIyMTkxOTM3OSIsImdpdGh1Yl9yZXBvc2l0b3J5X293bmVyIjoiYXdzLXBvd2VydG9vbHMiLCJnaXRodWJfcmVwb3NpdG9yeV9vd25lcl9pZCI6IjEyOTEyNzYzOCIsImdpdGh1Yl9ydW5fYXR0ZW1wdCI6IjEiLCJnaXRodWJfcnVuX2lkIjoiMTAwMzY5NjI5NDIiLCJnaXRodWJfcnVuX251bWJlciI6IjI1IiwiZ2l0aHViX3NoYTEiOiJhOGFlNDZlYzdiODUxYzIxODIwOWU0MjNjODIyZmY5YjVjODBiMzhlIn19LCJtZXRhZGF0YSI6eyJidWlsZEludm9jYXRpb25JRCI6IjEwMDM2OTYyOTQyLTEiLCJjb21wbGV0ZW5lc3MiOnsicGFyYW1ldGVycyI6dHJ1ZSwiZW52aXJvbm1lbnQiOmZhbHNlLCJtYXRlcmlhbHMiOmZhbHNlfSwicmVwcm9kdWNpYmxlIjpmYWxzZX0sIm1hdGVyaWFscyI6W3sidXJpIjoiZ2l0K2h0dHBzOi8vZ2l0aHViLmNvbS9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob25AcmVmcy9oZWFkcy9kZXZlbG9wIiwiZGlnZXN0Ijp7InNoYTEiOiJhOGFlNDZlYzdiODUxYzIxODIwOWU0MjNjODIyZmY5YjVjODBiMzhlIn19XX19","signatures":[{"keyid":"","sig":"MEQCIQC1xEJ9jp+bEUK6mP4Lr25rPOqeHOdOfVfa4lBc/8ck/QIfbOyIE/lisE9LPUdhGeYftvZ5MmNdH2QvRXvXkTbUdg==","cert":"-----BEGIN CERTIFICATE-----\nMIIHZjCCBuygAwIBAgIUaKkqcRRIRscDiJS8lc+CcDc7X+4wCgYIKoZIzj0EAwMw\nNzEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MR4wHAYDVQQDExVzaWdzdG9yZS1pbnRl\ncm1lZGlhdGUwHhcNMjQwNzIyMDgwNzQ0WhcNMjQwNzIyMDgxNzQ0WjAAMFkwEwYH\nKoZIzj0CAQYIKoZIzj0DAQcDQgAENmwMqwMZe3a0nFmK56xY3mIN2EseQUJVky8U\nj9sNGqbtOJhm9kC8bcWUWxVq+dSmVu2Ec4xqJb0Uy4iF1HgfSqOCBgswggYHMA4G\nA1UdDwEB/wQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcDAzAdBgNVHQ4EFgQUv3+e\nbqegiOZaR0PJmlv/kD7xfRYwHwYDVR0jBBgwFoAU39Ppz1YkEZb5qNjpKFWixi4Y\nZD8wgYQGA1UdEQEB/wR6MHiGdmh0dHBzOi8vZ2l0aHViLmNvbS9zbHNhLWZyYW1l\nd29yay9zbHNhLWdpdGh1Yi1nZW5lcmF0b3IvLmdpdGh1Yi93b3JrZmxvd3MvZ2Vu\nZXJhdG9yX2dlbmVyaWNfc2xzYTMueW1sQHJlZnMvdGFncy92Mi4wLjAwOQYKKwYB\nBAGDvzABAQQraHR0cHM6Ly90b2tlbi5hY3Rpb25zLmdpdGh1YnVzZXJjb250ZW50\nLmNvbTAWBgorBgEEAYO/MAECBAhzY2hlZHVsZTA2BgorBgEEAYO/MAEDBChhOGFl\nNDZlYzdiODUxYzIxODIwOWU0MjNjODIyZmY5YjVjODBiMzhlMBkGCisGAQQBg78w\nAQQEC1ByZS1SZWxlYXNlMDUGCisGAQQBg78wAQUEJ2F3cy1wb3dlcnRvb2xzL3Bv\nd2VydG9vbHMtbGFtYmRhLXB5dGhvbjAgBgorBgEEAYO/MAEGBBJyZWZzL2hlYWRz\nL2RldmVsb3AwOwYKKwYBBAGDvzABCAQtDCtodHRwczovL3Rva2VuLmFjdGlvbnMu\nZ2l0aHVidXNlcmNvbnRlbnQuY29tMIGGBgorBgEEAYO/MAEJBHgMdmh0dHBzOi8v\nZ2l0aHViLmNvbS9zbHNhLWZyYW1ld29yay9zbHNhLWdpdGh1Yi1nZW5lcmF0b3Iv\nLmdpdGh1Yi93b3JrZmxvd3MvZ2VuZXJhdG9yX2dlbmVyaWNfc2xzYTMueW1sQHJl\nZnMvdGFncy92Mi4wLjAwOAYKKwYBBAGDvzABCgQqDCg1YTc3NWIzNjdhNTZkNWJk\nMTE4YTIyNGE4MTFiYmEyODgxNTBhNTYzMB0GCisGAQQBg78wAQsEDwwNZ2l0aHVi\nLWhvc3RlZDBKBgorBgEEAYO/MAEMBDwMOmh0dHBzOi8vZ2l0aHViLmNvbS9hd3Mt\ncG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24wOAYKKwYBBAGDvzAB\nDQQqDChhOGFlNDZlYzdiODUxYzIxODIwOWU0MjNjODIyZmY5YjVjODBiMzhlMCIG\nCisGAQQBg78wAQ4EFAwScmVmcy9oZWFkcy9kZXZlbG9wMBkGCisGAQQBg78wAQ8E\nCwwJMjIxOTE5Mzc5MDEGCisGAQQBg78wARAEIwwhaHR0cHM6Ly9naXRodWIuY29t\nL2F3cy1wb3dlcnRvb2xzMBkGCisGAQQBg78wAREECwwJMTI5MTI3NjM4MH8GCisG\nAQQBg78wARIEcQxvaHR0cHM6Ly9naXRodWIuY29tL2F3cy1wb3dlcnRvb2xzL3Bv\nd2VydG9vbHMtbGFtYmRhLXB5dGhvbi8uZ2l0aHViL3dvcmtmbG93cy9wcmUtcmVs\nZWFzZS55bWxAcmVmcy9oZWFkcy9kZXZlbG9wMDgGCisGAQQBg78wARMEKgwoYThh\nZTQ2ZWM3Yjg1MWMyMTgyMDllNDIzYzgyMmZmOWI1YzgwYjM4ZTAYBgorBgEEAYO/\nMAEUBAoMCHNjaGVkdWxlMG4GCisGAQQBg78wARUEYAxeaHR0cHM6Ly9naXRodWIu\nY29tL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9hY3Rp\nb25zL3J1bnMvMTAwMzY5NjI5NDIvYXR0ZW1wdHMvMTAWBgorBgEEAYO/MAEWBAgM\nBnB1YmxpYzCBigYKKwYBBAHWeQIEAgR8BHoAeAB2AN09MGrGxxEyYxkeHJlnNwKi\nSl643jyt/4eKcoAvKe6OAAABkNl7cN0AAAQDAEcwRQIhAP3gu7fpA5mkh3hwWSCd\niMOAJQYf/Y2EbmS32xlb4sVqAiBdu6k8VYQdAgDutttCEqBgg/3+wGCfnczCZJP5\nxfyGJDAKBggqhkjOPQQDAwNoADBlAjEAi7fuYKiV6ENyCRJv8rKO8nnKTNQETxa4\nRdV3OaJ2AlX6CzBepImLK6r+uCoXSSR4AjBXK+20z+qiZ3KSGv5r8VScgVxMJ4nF\nNaFHlvjVfdbHA5l1j4jQ49WBfxYEnaNj5JU=\n-----END CERTIFICATE-----\n"}]}
\ No newline at end of file
diff --git a/provenance/2.41.1a8/multiple.intoto.jsonl b/provenance/2.41.1a8/multiple.intoto.jsonl
new file mode 100644
index 00000000000..a52d67d4d20
--- /dev/null
+++ b/provenance/2.41.1a8/multiple.intoto.jsonl
@@ -0,0 +1 @@
+{"payloadType":"application/vnd.in-toto+json","payload":"eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjAuMSIsInByZWRpY2F0ZVR5cGUiOiJodHRwczovL3Nsc2EuZGV2L3Byb3ZlbmFuY2UvdjAuMiIsInN1YmplY3QiOlt7Im5hbWUiOiIuL2F3c19sYW1iZGFfcG93ZXJ0b29scy0yLjQxLjFhOC1weTMtbm9uZS1hbnkud2hsIiwiZGlnZXN0Ijp7InNoYTI1NiI6ImYwNGY0NGNiNjkwNTVkOTZhZWZjNTM1MjNiNzA2MTNjMjQzOTg2MzRiZmY1YmE4MzY2MjY2NWNlNGY1MDIzNGEifX0seyJuYW1lIjoiLi9hd3NfbGFtYmRhX3Bvd2VydG9vbHMtMi40MS4xYTgudGFyLmd6IiwiZGlnZXN0Ijp7InNoYTI1NiI6IjJlMTNkMzRiMTM2YjJjZTk0MWM1ZDg3MTkyNjgyODg2ZmY5NTUxZGEyMjQ0ZGZhOGRhMDJiYjUzODYwODM1NDkifX1dLCJwcmVkaWNhdGUiOnsiYnVpbGRlciI6eyJpZCI6Imh0dHBzOi8vZ2l0aHViLmNvbS9zbHNhLWZyYW1ld29yay9zbHNhLWdpdGh1Yi1nZW5lcmF0b3IvLmdpdGh1Yi93b3JrZmxvd3MvZ2VuZXJhdG9yX2dlbmVyaWNfc2xzYTMueW1sQHJlZnMvdGFncy92Mi4wLjAifSwiYnVpbGRUeXBlIjoiaHR0cHM6Ly9naXRodWIuY29tL3Nsc2EtZnJhbWV3b3JrL3Nsc2EtZ2l0aHViLWdlbmVyYXRvci9nZW5lcmljQHYxIiwiaW52b2NhdGlvbiI6eyJjb25maWdTb3VyY2UiOnsidXJpIjoiZ2l0K2h0dHBzOi8vZ2l0aHViLmNvbS9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob25AcmVmcy9oZWFkcy9kZXZlbG9wIiwiZGlnZXN0Ijp7InNoYTEiOiJjNzc4NjlhODljMWViYjg3NzE4Yjc4YjIwMjdhMmYwOTc4NTEyMTQyIn0sImVudHJ5UG9pbnQiOiIuZ2l0aHViL3dvcmtmbG93cy9wcmUtcmVsZWFzZS55bWwifSwicGFyYW1ldGVycyI6e30sImVudmlyb25tZW50Ijp7ImdpdGh1Yl9hY3RvciI6ImxlYW5kcm9kYW1hc2NlbmEiLCJnaXRodWJfYWN0b3JfaWQiOiI0Mjk1MTczIiwiZ2l0aHViX2Jhc2VfcmVmIjoiIiwiZ2l0aHViX2V2ZW50X25hbWUiOiJzY2hlZHVsZSIsImdpdGh1Yl9ldmVudF9wYXlsb2FkIjp7ImVudGVycHJpc2UiOnsiYXZhdGFyX3VybCI6Imh0dHBzOi8vYXZhdGFycy5naXRodWJ1c2VyY29udGVudC5jb20vYi8xMjkwP3Y9NCIsImNyZWF0ZWRfYXQiOiIyMDE5LTExLTEzVDE4OjA1OjQxWiIsImRlc2NyaXB0aW9uIjoiIiwiaHRtbF91cmwiOiJodHRwczovL2dpdGh1Yi5jb20vZW50ZXJwcmlzZXMvYW1hem9uIiwiaWQiOjEyOTAsIm5hbWUiOiJBbWF6b24iLCJub2RlX2lkIjoiTURFd09rVnVkR1Z5Y0hKcGMyVXhNamt3Iiwic2x1ZyI6ImFtYXpvbiIsInVwZGF0ZWRfYXQiOiIyMDIzLTAxLTI3VDE0OjU2OjEwWiIsIndlYnNpdGVfdXJsIjoiaHR0cHM6Ly93d3cuYW1hem9uLmNvbS8ifSwib3JnYW5pemF0aW9uIjp7ImF2YXRhcl91cmwiOiJodHRwczovL2F2YXRhcnMuZ2l0aHVidXNlcmNvbnRlbnQuY29tL3UvMTI5MTI3NjM4P3Y9NCIsImRlc2NyaXB0aW9uIjoiIiwiZXZlbnRzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vb3Jncy9hd3MtcG93ZXJ0b29scy9ldmVudHMiLCJob29rc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL29yZ3MvYXdzLXBvd2VydG9vbHMvaG9va3MiLCJpZCI6MTI5MTI3NjM4LCJpc3N1ZXNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9vcmdzL2F3cy1wb3dlcnRvb2xzL2lzc3VlcyIsImxvZ2luIjoiYXdzLXBvd2VydG9vbHMiLCJtZW1iZXJzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vb3Jncy9hd3MtcG93ZXJ0b29scy9tZW1iZXJzey9tZW1iZXJ9Iiwibm9kZV9pZCI6Ik9fa2dET0I3SlUxZyIsInB1YmxpY19tZW1iZXJzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vb3Jncy9hd3MtcG93ZXJ0b29scy9wdWJsaWNfbWVtYmVyc3svbWVtYmVyfSIsInJlcG9zX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vb3Jncy9hd3MtcG93ZXJ0b29scy9yZXBvcyIsInVybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vb3Jncy9hd3MtcG93ZXJ0b29scyJ9LCJyZXBvc2l0b3J5Ijp7ImFsbG93X2ZvcmtpbmciOnRydWUsImFyY2hpdmVfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24ve2FyY2hpdmVfZm9ybWF0fXsvcmVmfSIsImFyY2hpdmVkIjpmYWxzZSwiYXNzaWduZWVzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2Fzc2lnbmVlc3svdXNlcn0iLCJibG9ic191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9naXQvYmxvYnN7L3NoYX0iLCJicmFuY2hlc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9icmFuY2hlc3svYnJhbmNofSIsImNsb25lX3VybCI6Imh0dHBzOi8vZ2l0aHViLmNvbS9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24uZ2l0IiwiY29sbGFib3JhdG9yc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9jb2xsYWJvcmF0b3Jzey9jb2xsYWJvcmF0b3J9IiwiY29tbWVudHNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vY29tbWVudHN7L251bWJlcn0iLCJjb21taXRzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2NvbW1pdHN7L3NoYX0iLCJjb21wYXJlX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2NvbXBhcmUve2Jhc2V9Li4ue2hlYWR9IiwiY29udGVudHNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vY29udGVudHMveytwYXRofSIsImNvbnRyaWJ1dG9yc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9jb250cmlidXRvcnMiLCJjcmVhdGVkX2F0IjoiMjAxOS0xMS0xNVQxMjoyNjoxMloiLCJkZWZhdWx0X2JyYW5jaCI6ImRldmVsb3AiLCJkZXBsb3ltZW50c191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9kZXBsb3ltZW50cyIsImRlc2NyaXB0aW9uIjoiQSBkZXZlbG9wZXIgdG9vbGtpdCB0byBpbXBsZW1lbnQgU2VydmVybGVzcyBiZXN0IHByYWN0aWNlcyBhbmQgaW5jcmVhc2UgZGV2ZWxvcGVyIHZlbG9jaXR5LiIsImRpc2FibGVkIjpmYWxzZSwiZG93bmxvYWRzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2Rvd25sb2FkcyIsImV2ZW50c191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9ldmVudHMiLCJmb3JrIjpmYWxzZSwiZm9ya3MiOjM3OCwiZm9ya3NfY291bnQiOjM3OCwiZm9ya3NfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vZm9ya3MiLCJmdWxsX25hbWUiOiJhd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24iLCJnaXRfY29tbWl0c191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9naXQvY29tbWl0c3svc2hhfSIsImdpdF9yZWZzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2dpdC9yZWZzey9zaGF9IiwiZ2l0X3RhZ3NfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vZ2l0L3RhZ3N7L3NoYX0iLCJnaXRfdXJsIjoiZ2l0Oi8vZ2l0aHViLmNvbS9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24uZ2l0IiwiaGFzX2Rpc2N1c3Npb25zIjp0cnVlLCJoYXNfZG93bmxvYWRzIjp0cnVlLCJoYXNfaXNzdWVzIjp0cnVlLCJoYXNfcGFnZXMiOmZhbHNlLCJoYXNfcHJvamVjdHMiOnRydWUsImhhc193aWtpIjpmYWxzZSwiaG9tZXBhZ2UiOiJodHRwczovL2RvY3MucG93ZXJ0b29scy5hd3MuZGV2L2xhbWJkYS9weXRob24vbGF0ZXN0LyIsImhvb2tzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2hvb2tzIiwiaHRtbF91cmwiOiJodHRwczovL2dpdGh1Yi5jb20vYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uIiwiaWQiOjIyMTkxOTM3OSwiaXNfdGVtcGxhdGUiOmZhbHNlLCJpc3N1ZV9jb21tZW50X3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2lzc3Vlcy9jb21tZW50c3svbnVtYmVyfSIsImlzc3VlX2V2ZW50c191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9pc3N1ZXMvZXZlbnRzey9udW1iZXJ9IiwiaXNzdWVzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2lzc3Vlc3svbnVtYmVyfSIsImtleXNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24va2V5c3sva2V5X2lkfSIsImxhYmVsc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9sYWJlbHN7L25hbWV9IiwibGFuZ3VhZ2UiOiJQeXRob24iLCJsYW5ndWFnZXNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vbGFuZ3VhZ2VzIiwibGljZW5zZSI6eyJrZXkiOiJtaXQtMCIsIm5hbWUiOiJNSVQgTm8gQXR0cmlidXRpb24iLCJub2RlX2lkIjoiTURjNlRHbGpaVzV6WlRReCIsInNwZHhfaWQiOiJNSVQtMCIsInVybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vbGljZW5zZXMvbWl0LTAifSwibWVyZ2VzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL21lcmdlcyIsIm1pbGVzdG9uZXNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vbWlsZXN0b25lc3svbnVtYmVyfSIsIm1pcnJvcl91cmwiOm51bGwsIm5hbWUiOiJwb3dlcnRvb2xzLWxhbWJkYS1weXRob24iLCJub2RlX2lkIjoiTURFd09sSmxjRzl6YVhSdmNua3lNakU1TVRrek56az0iLCJub3RpZmljYXRpb25zX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL25vdGlmaWNhdGlvbnN7P3NpbmNlLGFsbCxwYXJ0aWNpcGF0aW5nfSIsIm9wZW5faXNzdWVzIjo5Niwib3Blbl9pc3N1ZXNfY291bnQiOjk2LCJvd25lciI6eyJhdmF0YXJfdXJsIjoiaHR0cHM6Ly9hdmF0YXJzLmdpdGh1YnVzZXJjb250ZW50LmNvbS91LzEyOTEyNzYzOD92PTQiLCJldmVudHNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS91c2Vycy9hd3MtcG93ZXJ0b29scy9ldmVudHN7L3ByaXZhY3l9IiwiZm9sbG93ZXJzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vdXNlcnMvYXdzLXBvd2VydG9vbHMvZm9sbG93ZXJzIiwiZm9sbG93aW5nX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vdXNlcnMvYXdzLXBvd2VydG9vbHMvZm9sbG93aW5ney9vdGhlcl91c2VyfSIsImdpc3RzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vdXNlcnMvYXdzLXBvd2VydG9vbHMvZ2lzdHN7L2dpc3RfaWR9IiwiZ3JhdmF0YXJfaWQiOiIiLCJodG1sX3VybCI6Imh0dHBzOi8vZ2l0aHViLmNvbS9hd3MtcG93ZXJ0b29scyIsImlkIjoxMjkxMjc2MzgsImxvZ2luIjoiYXdzLXBvd2VydG9vbHMiLCJub2RlX2lkIjoiT19rZ0RPQjdKVTFnIiwib3JnYW5pemF0aW9uc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3VzZXJzL2F3cy1wb3dlcnRvb2xzL29yZ3MiLCJyZWNlaXZlZF9ldmVudHNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS91c2Vycy9hd3MtcG93ZXJ0b29scy9yZWNlaXZlZF9ldmVudHMiLCJyZXBvc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3VzZXJzL2F3cy1wb3dlcnRvb2xzL3JlcG9zIiwic2l0ZV9hZG1pbiI6ZmFsc2UsInN0YXJyZWRfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS91c2Vycy9hd3MtcG93ZXJ0b29scy9zdGFycmVkey9vd25lcn17L3JlcG99Iiwic3Vic2NyaXB0aW9uc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3VzZXJzL2F3cy1wb3dlcnRvb2xzL3N1YnNjcmlwdGlvbnMiLCJ0eXBlIjoiT3JnYW5pemF0aW9uIiwidXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS91c2Vycy9hd3MtcG93ZXJ0b29scyJ9LCJwcml2YXRlIjpmYWxzZSwicHVsbHNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vcHVsbHN7L251bWJlcn0iLCJwdXNoZWRfYXQiOiIyMDI0LTA3LTIzVDA3OjQ1OjQzWiIsInJlbGVhc2VzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL3JlbGVhc2Vzey9pZH0iLCJzaXplIjo0OTI1NCwic3NoX3VybCI6ImdpdEBnaXRodWIuY29tOmF3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi5naXQiLCJzdGFyZ2F6ZXJzX2NvdW50IjoyNzM1LCJzdGFyZ2F6ZXJzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL3N0YXJnYXplcnMiLCJzdGF0dXNlc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9zdGF0dXNlcy97c2hhfSIsInN1YnNjcmliZXJzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL3N1YnNjcmliZXJzIiwic3Vic2NyaXB0aW9uX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL3N1YnNjcmlwdGlvbiIsInN2bl91cmwiOiJodHRwczovL2dpdGh1Yi5jb20vYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uIiwidGFnc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi90YWdzIiwidGVhbXNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vdGVhbXMiLCJ0b3BpY3MiOlsiYXdzIiwiYXdzLWxhbWJkYSIsImhhY2t0b2JlcmZlc3QiLCJsYW1iZGEiLCJweXRob24iLCJzZXJ2ZXJsZXNzIl0sInRyZWVzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2dpdC90cmVlc3svc2hhfSIsInVwZGF0ZWRfYXQiOiIyMDI0LTA3LTIyVDIwOjU5OjUyWiIsInVybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uIiwidmlzaWJpbGl0eSI6InB1YmxpYyIsIndhdGNoZXJzIjoyNzM1LCJ3YXRjaGVyc19jb3VudCI6MjczNSwid2ViX2NvbW1pdF9zaWdub2ZmX3JlcXVpcmVkIjp0cnVlfSwic2NoZWR1bGUiOiIwIDggKiAqIDEtNSIsIndvcmtmbG93IjoiLmdpdGh1Yi93b3JrZmxvd3MvcHJlLXJlbGVhc2UueW1sIn0sImdpdGh1Yl9oZWFkX3JlZiI6IiIsImdpdGh1Yl9yZWYiOiJyZWZzL2hlYWRzL2RldmVsb3AiLCJnaXRodWJfcmVmX3R5cGUiOiJicmFuY2giLCJnaXRodWJfcmVwb3NpdG9yeV9pZCI6IjIyMTkxOTM3OSIsImdpdGh1Yl9yZXBvc2l0b3J5X293bmVyIjoiYXdzLXBvd2VydG9vbHMiLCJnaXRodWJfcmVwb3NpdG9yeV9vd25lcl9pZCI6IjEyOTEyNzYzOCIsImdpdGh1Yl9ydW5fYXR0ZW1wdCI6IjEiLCJnaXRodWJfcnVuX2lkIjoiMTAwNTUwNjI2MDUiLCJnaXRodWJfcnVuX251bWJlciI6IjI2IiwiZ2l0aHViX3NoYTEiOiJjNzc4NjlhODljMWViYjg3NzE4Yjc4YjIwMjdhMmYwOTc4NTEyMTQyIn19LCJtZXRhZGF0YSI6eyJidWlsZEludm9jYXRpb25JRCI6IjEwMDU1MDYyNjA1LTEiLCJjb21wbGV0ZW5lc3MiOnsicGFyYW1ldGVycyI6dHJ1ZSwiZW52aXJvbm1lbnQiOmZhbHNlLCJtYXRlcmlhbHMiOmZhbHNlfSwicmVwcm9kdWNpYmxlIjpmYWxzZX0sIm1hdGVyaWFscyI6W3sidXJpIjoiZ2l0K2h0dHBzOi8vZ2l0aHViLmNvbS9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob25AcmVmcy9oZWFkcy9kZXZlbG9wIiwiZGlnZXN0Ijp7InNoYTEiOiJjNzc4NjlhODljMWViYjg3NzE4Yjc4YjIwMjdhMmYwOTc4NTEyMTQyIn19XX19","signatures":[{"keyid":"","sig":"MEYCIQD5Me632vX0g1kYiy4F1Xu4mghZxwYVbnR3e8mOvRfAQQIhANp4UgjdqWYFW4nXMsSn0aM3z4hw6pyTGmwUYzSVHSeu","cert":"-----BEGIN CERTIFICATE-----\nMIIHZjCCBuugAwIBAgIURHXU5rqeA2MC/4QkArKNvkXaDiEwCgYIKoZIzj0EAwMw\nNzEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MR4wHAYDVQQDExVzaWdzdG9yZS1pbnRl\ncm1lZGlhdGUwHhcNMjQwNzIzMDgwNzE4WhcNMjQwNzIzMDgxNzE4WjAAMFkwEwYH\nKoZIzj0CAQYIKoZIzj0DAQcDQgAExCJZoPc4jzHDgTcV2/OfRHocp0UWWHLMcr6W\n0OdJj+ov7UFGE+Ki92JhW/yH2nDFGCjVI5wkTIJamP75TpkPRaOCBgowggYGMA4G\nA1UdDwEB/wQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcDAzAdBgNVHQ4EFgQUGH9Y\nG1HOpaAKVeYr4wC8dov3Aq8wHwYDVR0jBBgwFoAU39Ppz1YkEZb5qNjpKFWixi4Y\nZD8wgYQGA1UdEQEB/wR6MHiGdmh0dHBzOi8vZ2l0aHViLmNvbS9zbHNhLWZyYW1l\nd29yay9zbHNhLWdpdGh1Yi1nZW5lcmF0b3IvLmdpdGh1Yi93b3JrZmxvd3MvZ2Vu\nZXJhdG9yX2dlbmVyaWNfc2xzYTMueW1sQHJlZnMvdGFncy92Mi4wLjAwOQYKKwYB\nBAGDvzABAQQraHR0cHM6Ly90b2tlbi5hY3Rpb25zLmdpdGh1YnVzZXJjb250ZW50\nLmNvbTAWBgorBgEEAYO/MAECBAhzY2hlZHVsZTA2BgorBgEEAYO/MAEDBChjNzc4\nNjlhODljMWViYjg3NzE4Yjc4YjIwMjdhMmYwOTc4NTEyMTQyMBkGCisGAQQBg78w\nAQQEC1ByZS1SZWxlYXNlMDUGCisGAQQBg78wAQUEJ2F3cy1wb3dlcnRvb2xzL3Bv\nd2VydG9vbHMtbGFtYmRhLXB5dGhvbjAgBgorBgEEAYO/MAEGBBJyZWZzL2hlYWRz\nL2RldmVsb3AwOwYKKwYBBAGDvzABCAQtDCtodHRwczovL3Rva2VuLmFjdGlvbnMu\nZ2l0aHVidXNlcmNvbnRlbnQuY29tMIGGBgorBgEEAYO/MAEJBHgMdmh0dHBzOi8v\nZ2l0aHViLmNvbS9zbHNhLWZyYW1ld29yay9zbHNhLWdpdGh1Yi1nZW5lcmF0b3Iv\nLmdpdGh1Yi93b3JrZmxvd3MvZ2VuZXJhdG9yX2dlbmVyaWNfc2xzYTMueW1sQHJl\nZnMvdGFncy92Mi4wLjAwOAYKKwYBBAGDvzABCgQqDCg1YTc3NWIzNjdhNTZkNWJk\nMTE4YTIyNGE4MTFiYmEyODgxNTBhNTYzMB0GCisGAQQBg78wAQsEDwwNZ2l0aHVi\nLWhvc3RlZDBKBgorBgEEAYO/MAEMBDwMOmh0dHBzOi8vZ2l0aHViLmNvbS9hd3Mt\ncG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24wOAYKKwYBBAGDvzAB\nDQQqDChjNzc4NjlhODljMWViYjg3NzE4Yjc4YjIwMjdhMmYwOTc4NTEyMTQyMCIG\nCisGAQQBg78wAQ4EFAwScmVmcy9oZWFkcy9kZXZlbG9wMBkGCisGAQQBg78wAQ8E\nCwwJMjIxOTE5Mzc5MDEGCisGAQQBg78wARAEIwwhaHR0cHM6Ly9naXRodWIuY29t\nL2F3cy1wb3dlcnRvb2xzMBkGCisGAQQBg78wAREECwwJMTI5MTI3NjM4MH8GCisG\nAQQBg78wARIEcQxvaHR0cHM6Ly9naXRodWIuY29tL2F3cy1wb3dlcnRvb2xzL3Bv\nd2VydG9vbHMtbGFtYmRhLXB5dGhvbi8uZ2l0aHViL3dvcmtmbG93cy9wcmUtcmVs\nZWFzZS55bWxAcmVmcy9oZWFkcy9kZXZlbG9wMDgGCisGAQQBg78wARMEKgwoYzc3\nODY5YTg5YzFlYmI4NzcxOGI3OGIyMDI3YTJmMDk3ODUxMjE0MjAYBgorBgEEAYO/\nMAEUBAoMCHNjaGVkdWxlMG4GCisGAQQBg78wARUEYAxeaHR0cHM6Ly9naXRodWIu\nY29tL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9hY3Rp\nb25zL3J1bnMvMTAwNTUwNjI2MDUvYXR0ZW1wdHMvMTAWBgorBgEEAYO/MAEWBAgM\nBnB1YmxpYzCBiQYKKwYBBAHWeQIEAgR7BHkAdwB1AN09MGrGxxEyYxkeHJlnNwKi\nSl643jyt/4eKcoAvKe6OAAABkN6haqsAAAQDAEYwRAIgQU6JM8nzH8fdtYImIYJu\nPAmNLgF+jyMYRf30Bosm264CIFSkomXRf0CnVTMtnQb0ySYZ6qB9TXf9XevTJ9iZ\nKvBiMAoGCCqGSM49BAMDA2kAMGYCMQCEL0K3gGVlMjH8u4xKXkeoPe3aub0j7Qm0\nLejEdXnpkyjF2V6W+pkReBjqYhg3vH0CMQDqtur4iSZT9u03n48T4JMmrZLDqiQ9\nKdUByNr/AauFsxVvEQQ03fjCtDQZldLJwHI=\n-----END CERTIFICATE-----\n"}]}
\ No newline at end of file
diff --git a/provenance/2.41.1a9/multiple.intoto.jsonl b/provenance/2.41.1a9/multiple.intoto.jsonl
new file mode 100644
index 00000000000..c2290f60aef
--- /dev/null
+++ b/provenance/2.41.1a9/multiple.intoto.jsonl
@@ -0,0 +1 @@
+{"payloadType":"application/vnd.in-toto+json","payload":"eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjAuMSIsInByZWRpY2F0ZVR5cGUiOiJodHRwczovL3Nsc2EuZGV2L3Byb3ZlbmFuY2UvdjAuMiIsInN1YmplY3QiOlt7Im5hbWUiOiIuL2F3c19sYW1iZGFfcG93ZXJ0b29scy0yLjQxLjFhOS1weTMtbm9uZS1hbnkud2hsIiwiZGlnZXN0Ijp7InNoYTI1NiI6Ijk1ZjkyNjU1OTlkZTYyZDFlMmMxNTI2ZmZmN2ZkYTc2OTY5OTI5OWI4ZDkzM2YxMzZkZGQwMWJkN2IwZWY1OGUifX0seyJuYW1lIjoiLi9hd3NfbGFtYmRhX3Bvd2VydG9vbHMtMi40MS4xYTkudGFyLmd6IiwiZGlnZXN0Ijp7InNoYTI1NiI6ImQzMTFhNTFmZGQzNjI1MWUzOTkyZDIxYTc3NjU5ODViYjJiMjJmYTliZGU3NTdlNWUxYWIzZjg4NWI2MTA1OTAifX1dLCJwcmVkaWNhdGUiOnsiYnVpbGRlciI6eyJpZCI6Imh0dHBzOi8vZ2l0aHViLmNvbS9zbHNhLWZyYW1ld29yay9zbHNhLWdpdGh1Yi1nZW5lcmF0b3IvLmdpdGh1Yi93b3JrZmxvd3MvZ2VuZXJhdG9yX2dlbmVyaWNfc2xzYTMueW1sQHJlZnMvdGFncy92Mi4wLjAifSwiYnVpbGRUeXBlIjoiaHR0cHM6Ly9naXRodWIuY29tL3Nsc2EtZnJhbWV3b3JrL3Nsc2EtZ2l0aHViLWdlbmVyYXRvci9nZW5lcmljQHYxIiwiaW52b2NhdGlvbiI6eyJjb25maWdTb3VyY2UiOnsidXJpIjoiZ2l0K2h0dHBzOi8vZ2l0aHViLmNvbS9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob25AcmVmcy9oZWFkcy9kZXZlbG9wIiwiZGlnZXN0Ijp7InNoYTEiOiI5Yjc2ZmVkZTM0MzUyODg3ZjUzNGVmY2M2OGJkMWQ2NWFlMTU0YzA2In0sImVudHJ5UG9pbnQiOiIuZ2l0aHViL3dvcmtmbG93cy9wcmUtcmVsZWFzZS55bWwifSwicGFyYW1ldGVycyI6e30sImVudmlyb25tZW50Ijp7ImdpdGh1Yl9hY3RvciI6ImxlYW5kcm9kYW1hc2NlbmEiLCJnaXRodWJfYWN0b3JfaWQiOiI0Mjk1MTczIiwiZ2l0aHViX2Jhc2VfcmVmIjoiIiwiZ2l0aHViX2V2ZW50X25hbWUiOiJzY2hlZHVsZSIsImdpdGh1Yl9ldmVudF9wYXlsb2FkIjp7ImVudGVycHJpc2UiOnsiYXZhdGFyX3VybCI6Imh0dHBzOi8vYXZhdGFycy5naXRodWJ1c2VyY29udGVudC5jb20vYi8xMjkwP3Y9NCIsImNyZWF0ZWRfYXQiOiIyMDE5LTExLTEzVDE4OjA1OjQxWiIsImRlc2NyaXB0aW9uIjoiIiwiaHRtbF91cmwiOiJodHRwczovL2dpdGh1Yi5jb20vZW50ZXJwcmlzZXMvYW1hem9uIiwiaWQiOjEyOTAsIm5hbWUiOiJBbWF6b24iLCJub2RlX2lkIjoiTURFd09rVnVkR1Z5Y0hKcGMyVXhNamt3Iiwic2x1ZyI6ImFtYXpvbiIsInVwZGF0ZWRfYXQiOiIyMDIzLTAxLTI3VDE0OjU2OjEwWiIsIndlYnNpdGVfdXJsIjoiaHR0cHM6Ly93d3cuYW1hem9uLmNvbS8ifSwib3JnYW5pemF0aW9uIjp7ImF2YXRhcl91cmwiOiJodHRwczovL2F2YXRhcnMuZ2l0aHVidXNlcmNvbnRlbnQuY29tL3UvMTI5MTI3NjM4P3Y9NCIsImRlc2NyaXB0aW9uIjoiIiwiZXZlbnRzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vb3Jncy9hd3MtcG93ZXJ0b29scy9ldmVudHMiLCJob29rc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL29yZ3MvYXdzLXBvd2VydG9vbHMvaG9va3MiLCJpZCI6MTI5MTI3NjM4LCJpc3N1ZXNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9vcmdzL2F3cy1wb3dlcnRvb2xzL2lzc3VlcyIsImxvZ2luIjoiYXdzLXBvd2VydG9vbHMiLCJtZW1iZXJzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vb3Jncy9hd3MtcG93ZXJ0b29scy9tZW1iZXJzey9tZW1iZXJ9Iiwibm9kZV9pZCI6Ik9fa2dET0I3SlUxZyIsInB1YmxpY19tZW1iZXJzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vb3Jncy9hd3MtcG93ZXJ0b29scy9wdWJsaWNfbWVtYmVyc3svbWVtYmVyfSIsInJlcG9zX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vb3Jncy9hd3MtcG93ZXJ0b29scy9yZXBvcyIsInVybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vb3Jncy9hd3MtcG93ZXJ0b29scyJ9LCJyZXBvc2l0b3J5Ijp7ImFsbG93X2ZvcmtpbmciOnRydWUsImFyY2hpdmVfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24ve2FyY2hpdmVfZm9ybWF0fXsvcmVmfSIsImFyY2hpdmVkIjpmYWxzZSwiYXNzaWduZWVzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2Fzc2lnbmVlc3svdXNlcn0iLCJibG9ic191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9naXQvYmxvYnN7L3NoYX0iLCJicmFuY2hlc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9icmFuY2hlc3svYnJhbmNofSIsImNsb25lX3VybCI6Imh0dHBzOi8vZ2l0aHViLmNvbS9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24uZ2l0IiwiY29sbGFib3JhdG9yc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9jb2xsYWJvcmF0b3Jzey9jb2xsYWJvcmF0b3J9IiwiY29tbWVudHNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vY29tbWVudHN7L251bWJlcn0iLCJjb21taXRzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2NvbW1pdHN7L3NoYX0iLCJjb21wYXJlX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2NvbXBhcmUve2Jhc2V9Li4ue2hlYWR9IiwiY29udGVudHNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vY29udGVudHMveytwYXRofSIsImNvbnRyaWJ1dG9yc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9jb250cmlidXRvcnMiLCJjcmVhdGVkX2F0IjoiMjAxOS0xMS0xNVQxMjoyNjoxMloiLCJkZWZhdWx0X2JyYW5jaCI6ImRldmVsb3AiLCJkZXBsb3ltZW50c191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9kZXBsb3ltZW50cyIsImRlc2NyaXB0aW9uIjoiQSBkZXZlbG9wZXIgdG9vbGtpdCB0byBpbXBsZW1lbnQgU2VydmVybGVzcyBiZXN0IHByYWN0aWNlcyBhbmQgaW5jcmVhc2UgZGV2ZWxvcGVyIHZlbG9jaXR5LiIsImRpc2FibGVkIjpmYWxzZSwiZG93bmxvYWRzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2Rvd25sb2FkcyIsImV2ZW50c191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9ldmVudHMiLCJmb3JrIjpmYWxzZSwiZm9ya3MiOjM4MCwiZm9ya3NfY291bnQiOjM4MCwiZm9ya3NfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vZm9ya3MiLCJmdWxsX25hbWUiOiJhd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24iLCJnaXRfY29tbWl0c191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9naXQvY29tbWl0c3svc2hhfSIsImdpdF9yZWZzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2dpdC9yZWZzey9zaGF9IiwiZ2l0X3RhZ3NfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vZ2l0L3RhZ3N7L3NoYX0iLCJnaXRfdXJsIjoiZ2l0Oi8vZ2l0aHViLmNvbS9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24uZ2l0IiwiaGFzX2Rpc2N1c3Npb25zIjp0cnVlLCJoYXNfZG93bmxvYWRzIjp0cnVlLCJoYXNfaXNzdWVzIjp0cnVlLCJoYXNfcGFnZXMiOmZhbHNlLCJoYXNfcHJvamVjdHMiOnRydWUsImhhc193aWtpIjpmYWxzZSwiaG9tZXBhZ2UiOiJodHRwczovL2RvY3MucG93ZXJ0b29scy5hd3MuZGV2L2xhbWJkYS9weXRob24vbGF0ZXN0LyIsImhvb2tzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2hvb2tzIiwiaHRtbF91cmwiOiJodHRwczovL2dpdGh1Yi5jb20vYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uIiwiaWQiOjIyMTkxOTM3OSwiaXNfdGVtcGxhdGUiOmZhbHNlLCJpc3N1ZV9jb21tZW50X3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2lzc3Vlcy9jb21tZW50c3svbnVtYmVyfSIsImlzc3VlX2V2ZW50c191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9pc3N1ZXMvZXZlbnRzey9udW1iZXJ9IiwiaXNzdWVzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL2lzc3Vlc3svbnVtYmVyfSIsImtleXNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24va2V5c3sva2V5X2lkfSIsImxhYmVsc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9sYWJlbHN7L25hbWV9IiwibGFuZ3VhZ2UiOiJQeXRob24iLCJsYW5ndWFnZXNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vbGFuZ3VhZ2VzIiwibGljZW5zZSI6eyJrZXkiOiJtaXQtMCIsIm5hbWUiOiJNSVQgTm8gQXR0cmlidXRpb24iLCJub2RlX2lkIjoiTURjNlRHbGpaVzV6WlRReCIsInNwZHhfaWQiOiJNSVQtMCIsInVybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vbGljZW5zZXMvbWl0LTAifSwibWVyZ2VzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL21lcmdlcyIsIm1pbGVzdG9uZXNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vbWlsZXN0b25lc3svbnVtYmVyfSIsIm1pcnJvcl91cmwiOm51bGwsIm5hbWUiOiJwb3dlcnRvb2xzLWxhbWJkYS1weXRob24iLCJub2RlX2lkIjoiTURFd09sSmxjRzl6YVhSdmNua3lNakU1TVRrek56az0iLCJub3RpZmljYXRpb25zX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL25vdGlmaWNhdGlvbnN7P3NpbmNlLGFsbCxwYXJ0aWNpcGF0aW5nfSIsIm9wZW5faXNzdWVzIjoxMDEsIm9wZW5faXNzdWVzX2NvdW50IjoxMDEsIm93bmVyIjp7ImF2YXRhcl91cmwiOiJodHRwczovL2F2YXRhcnMuZ2l0aHVidXNlcmNvbnRlbnQuY29tL3UvMTI5MTI3NjM4P3Y9NCIsImV2ZW50c191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3VzZXJzL2F3cy1wb3dlcnRvb2xzL2V2ZW50c3svcHJpdmFjeX0iLCJmb2xsb3dlcnNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS91c2Vycy9hd3MtcG93ZXJ0b29scy9mb2xsb3dlcnMiLCJmb2xsb3dpbmdfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS91c2Vycy9hd3MtcG93ZXJ0b29scy9mb2xsb3dpbmd7L290aGVyX3VzZXJ9IiwiZ2lzdHNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS91c2Vycy9hd3MtcG93ZXJ0b29scy9naXN0c3svZ2lzdF9pZH0iLCJncmF2YXRhcl9pZCI6IiIsImh0bWxfdXJsIjoiaHR0cHM6Ly9naXRodWIuY29tL2F3cy1wb3dlcnRvb2xzIiwiaWQiOjEyOTEyNzYzOCwibG9naW4iOiJhd3MtcG93ZXJ0b29scyIsIm5vZGVfaWQiOiJPX2tnRE9CN0pVMWciLCJvcmdhbml6YXRpb25zX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vdXNlcnMvYXdzLXBvd2VydG9vbHMvb3JncyIsInJlY2VpdmVkX2V2ZW50c191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3VzZXJzL2F3cy1wb3dlcnRvb2xzL3JlY2VpdmVkX2V2ZW50cyIsInJlcG9zX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vdXNlcnMvYXdzLXBvd2VydG9vbHMvcmVwb3MiLCJzaXRlX2FkbWluIjpmYWxzZSwic3RhcnJlZF91cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3VzZXJzL2F3cy1wb3dlcnRvb2xzL3N0YXJyZWR7L293bmVyfXsvcmVwb30iLCJzdWJzY3JpcHRpb25zX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vdXNlcnMvYXdzLXBvd2VydG9vbHMvc3Vic2NyaXB0aW9ucyIsInR5cGUiOiJPcmdhbml6YXRpb24iLCJ1cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3VzZXJzL2F3cy1wb3dlcnRvb2xzIn0sInByaXZhdGUiOmZhbHNlLCJwdWxsc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9wdWxsc3svbnVtYmVyfSIsInB1c2hlZF9hdCI6IjIwMjQtMDctMjNUMjE6MDY6MTZaIiwicmVsZWFzZXNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vcmVsZWFzZXN7L2lkfSIsInNpemUiOjQ5NDQzLCJzc2hfdXJsIjoiZ2l0QGdpdGh1Yi5jb206YXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uLmdpdCIsInN0YXJnYXplcnNfY291bnQiOjI3MzYsInN0YXJnYXplcnNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vc3RhcmdhemVycyIsInN0YXR1c2VzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL3N0YXR1c2VzL3tzaGF9Iiwic3Vic2NyaWJlcnNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vc3Vic2NyaWJlcnMiLCJzdWJzY3JpcHRpb25fdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vc3Vic2NyaXB0aW9uIiwic3ZuX3VybCI6Imh0dHBzOi8vZ2l0aHViLmNvbS9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24iLCJ0YWdzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXdzLXBvd2VydG9vbHMvcG93ZXJ0b29scy1sYW1iZGEtcHl0aG9uL3RhZ3MiLCJ0ZWFtc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi90ZWFtcyIsInRvcGljcyI6WyJhd3MiLCJhd3MtbGFtYmRhIiwiaGFja3RvYmVyZmVzdCIsImxhbWJkYSIsInB5dGhvbiIsInNlcnZlcmxlc3MiXSwidHJlZXNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24vZ2l0L3RyZWVzey9zaGF9IiwidXBkYXRlZF9hdCI6IjIwMjQtMDctMjRUMDA6NDI6MzNaIiwidXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hd3MtcG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24iLCJ2aXNpYmlsaXR5IjoicHVibGljIiwid2F0Y2hlcnMiOjI3MzYsIndhdGNoZXJzX2NvdW50IjoyNzM2LCJ3ZWJfY29tbWl0X3NpZ25vZmZfcmVxdWlyZWQiOnRydWV9LCJzY2hlZHVsZSI6IjAgOCAqICogMS01Iiwid29ya2Zsb3ciOiIuZ2l0aHViL3dvcmtmbG93cy9wcmUtcmVsZWFzZS55bWwifSwiZ2l0aHViX2hlYWRfcmVmIjoiIiwiZ2l0aHViX3JlZiI6InJlZnMvaGVhZHMvZGV2ZWxvcCIsImdpdGh1Yl9yZWZfdHlwZSI6ImJyYW5jaCIsImdpdGh1Yl9yZXBvc2l0b3J5X2lkIjoiMjIxOTE5Mzc5IiwiZ2l0aHViX3JlcG9zaXRvcnlfb3duZXIiOiJhd3MtcG93ZXJ0b29scyIsImdpdGh1Yl9yZXBvc2l0b3J5X293bmVyX2lkIjoiMTI5MTI3NjM4IiwiZ2l0aHViX3J1bl9hdHRlbXB0IjoiMSIsImdpdGh1Yl9ydW5faWQiOiIxMDA3MjgxMTM0OCIsImdpdGh1Yl9ydW5fbnVtYmVyIjoiMjciLCJnaXRodWJfc2hhMSI6IjliNzZmZWRlMzQzNTI4ODdmNTM0ZWZjYzY4YmQxZDY1YWUxNTRjMDYifX0sIm1ldGFkYXRhIjp7ImJ1aWxkSW52b2NhdGlvbklEIjoiMTAwNzI4MTEzNDgtMSIsImNvbXBsZXRlbmVzcyI6eyJwYXJhbWV0ZXJzIjp0cnVlLCJlbnZpcm9ubWVudCI6ZmFsc2UsIm1hdGVyaWFscyI6ZmFsc2V9LCJyZXByb2R1Y2libGUiOmZhbHNlfSwibWF0ZXJpYWxzIjpbeyJ1cmkiOiJnaXQraHR0cHM6Ly9naXRodWIuY29tL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbkByZWZzL2hlYWRzL2RldmVsb3AiLCJkaWdlc3QiOnsic2hhMSI6IjliNzZmZWRlMzQzNTI4ODdmNTM0ZWZjYzY4YmQxZDY1YWUxNTRjMDYifX1dfX0=","signatures":[{"keyid":"","sig":"MEUCIQC3pUm6z/ADjXpcnBIS07QDDo5NhKbIxAKOm3QwsQHE1QIgJx8H3r/JBTM9wUP9jeHSFw5ADZyBQggeQbuaiSkcpsU=","cert":"-----BEGIN CERTIFICATE-----\nMIIHZjCCBuygAwIBAgIUcER3iK+NFbMCk8m9QXTj/Ccz6yAwCgYIKoZIzj0EAwMw\nNzEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MR4wHAYDVQQDExVzaWdzdG9yZS1pbnRl\ncm1lZGlhdGUwHhcNMjQwNzI0MDgwNzMzWhcNMjQwNzI0MDgxNzMzWjAAMFkwEwYH\nKoZIzj0CAQYIKoZIzj0DAQcDQgAEc1sU9M7MvZAH9NMRTpfUieDSKTQV+gmD339/\ncWhP9x2wmJCAZ/vdA/QARxTl9uIe77WEFOwg8z6UYtSgMZzwCaOCBgswggYHMA4G\nA1UdDwEB/wQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcDAzAdBgNVHQ4EFgQUKgIE\n+t9V6V0ZzT6EBNk8+ChjKlswHwYDVR0jBBgwFoAU39Ppz1YkEZb5qNjpKFWixi4Y\nZD8wgYQGA1UdEQEB/wR6MHiGdmh0dHBzOi8vZ2l0aHViLmNvbS9zbHNhLWZyYW1l\nd29yay9zbHNhLWdpdGh1Yi1nZW5lcmF0b3IvLmdpdGh1Yi93b3JrZmxvd3MvZ2Vu\nZXJhdG9yX2dlbmVyaWNfc2xzYTMueW1sQHJlZnMvdGFncy92Mi4wLjAwOQYKKwYB\nBAGDvzABAQQraHR0cHM6Ly90b2tlbi5hY3Rpb25zLmdpdGh1YnVzZXJjb250ZW50\nLmNvbTAWBgorBgEEAYO/MAECBAhzY2hlZHVsZTA2BgorBgEEAYO/MAEDBCg5Yjc2\nZmVkZTM0MzUyODg3ZjUzNGVmY2M2OGJkMWQ2NWFlMTU0YzA2MBkGCisGAQQBg78w\nAQQEC1ByZS1SZWxlYXNlMDUGCisGAQQBg78wAQUEJ2F3cy1wb3dlcnRvb2xzL3Bv\nd2VydG9vbHMtbGFtYmRhLXB5dGhvbjAgBgorBgEEAYO/MAEGBBJyZWZzL2hlYWRz\nL2RldmVsb3AwOwYKKwYBBAGDvzABCAQtDCtodHRwczovL3Rva2VuLmFjdGlvbnMu\nZ2l0aHVidXNlcmNvbnRlbnQuY29tMIGGBgorBgEEAYO/MAEJBHgMdmh0dHBzOi8v\nZ2l0aHViLmNvbS9zbHNhLWZyYW1ld29yay9zbHNhLWdpdGh1Yi1nZW5lcmF0b3Iv\nLmdpdGh1Yi93b3JrZmxvd3MvZ2VuZXJhdG9yX2dlbmVyaWNfc2xzYTMueW1sQHJl\nZnMvdGFncy92Mi4wLjAwOAYKKwYBBAGDvzABCgQqDCg1YTc3NWIzNjdhNTZkNWJk\nMTE4YTIyNGE4MTFiYmEyODgxNTBhNTYzMB0GCisGAQQBg78wAQsEDwwNZ2l0aHVi\nLWhvc3RlZDBKBgorBgEEAYO/MAEMBDwMOmh0dHBzOi8vZ2l0aHViLmNvbS9hd3Mt\ncG93ZXJ0b29scy9wb3dlcnRvb2xzLWxhbWJkYS1weXRob24wOAYKKwYBBAGDvzAB\nDQQqDCg5Yjc2ZmVkZTM0MzUyODg3ZjUzNGVmY2M2OGJkMWQ2NWFlMTU0YzA2MCIG\nCisGAQQBg78wAQ4EFAwScmVmcy9oZWFkcy9kZXZlbG9wMBkGCisGAQQBg78wAQ8E\nCwwJMjIxOTE5Mzc5MDEGCisGAQQBg78wARAEIwwhaHR0cHM6Ly9naXRodWIuY29t\nL2F3cy1wb3dlcnRvb2xzMBkGCisGAQQBg78wAREECwwJMTI5MTI3NjM4MH8GCisG\nAQQBg78wARIEcQxvaHR0cHM6Ly9naXRodWIuY29tL2F3cy1wb3dlcnRvb2xzL3Bv\nd2VydG9vbHMtbGFtYmRhLXB5dGhvbi8uZ2l0aHViL3dvcmtmbG93cy9wcmUtcmVs\nZWFzZS55bWxAcmVmcy9oZWFkcy9kZXZlbG9wMDgGCisGAQQBg78wARMEKgwoOWI3\nNmZlZGUzNDM1Mjg4N2Y1MzRlZmNjNjhiZDFkNjVhZTE1NGMwNjAYBgorBgEEAYO/\nMAEUBAoMCHNjaGVkdWxlMG4GCisGAQQBg78wARUEYAxeaHR0cHM6Ly9naXRodWIu\nY29tL2F3cy1wb3dlcnRvb2xzL3Bvd2VydG9vbHMtbGFtYmRhLXB5dGhvbi9hY3Rp\nb25zL3J1bnMvMTAwNzI4MTEzNDgvYXR0ZW1wdHMvMTAWBgorBgEEAYO/MAEWBAgM\nBnB1YmxpYzCBigYKKwYBBAHWeQIEAgR8BHoAeAB2AN09MGrGxxEyYxkeHJlnNwKi\nSl643jyt/4eKcoAvKe6OAAABkOPIAGAAAAQDAEcwRQIgBJ8V4yFKhSdCenbtkqV5\nVxaMuEknwpRKfgVsIyYjcQcCIQCxSJrQA/PFOPN6KgOuPnBf9SJ9yFlKAMAu6OWb\neZo+SjAKBggqhkjOPQQDAwNoADBlAjEA8QOikpttAVw7F8CDDpmisbdnmLTR1Gi8\nq3T9fzXDcFn1tTVVEuAutIVSrIHJarhqAjBlFRT1z4gVyrsnVegy2cxVXcvz+vkX\nEvxAsq7REKj7Jzgw1MphfHbEP0oPiGKX9cw=\n-----END CERTIFICATE-----\n"}]}
\ No newline at end of file
diff --git a/pyproject.toml b/pyproject.toml
index d1a3ec78ef7..aec633a774e 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
[tool.poetry]
name = "aws_lambda_powertools"
-version = "2.40.2a8"
+version = "2.42.0"
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"]
@@ -55,41 +55,41 @@ aws-encryption-sdk = { version = "^3.1.1", optional = true }
jsonpath-ng = { version = "^1.6.0", optional = true }
[tool.poetry.dev-dependencies]
-coverage = { extras = ["toml"], version = "^7.5" }
-pytest = "^8.2.2"
+coverage = { extras = ["toml"], version = "^7.6" }
+pytest = "^8.3.1"
black = "^24.4"
boto3 = "^1.26.164"
isort = "^5.13.2"
pytest-cov = "^5.0.0"
pytest-mock = "^3.14.0"
pdoc3 = "^0.11.0"
-pytest-asyncio = "^0.23.7"
+pytest-asyncio = "^0.23.8"
bandit = "^1.7.9"
radon = "^6.0.1"
xenon = "^0.9.1"
mkdocs-git-revision-date-plugin = "^0.3.2"
mike = "^2.1.2"
pytest-xdist = "^3.6.1"
-aws-cdk-lib = "^2.145.0"
+aws-cdk-lib = "^2.150.0"
"aws-cdk.aws-apigatewayv2-alpha" = "^2.38.1-alpha.0"
"aws-cdk.aws-apigatewayv2-integrations-alpha" = "^2.38.1-alpha.0"
"aws-cdk.aws-apigatewayv2-authorizers-alpha" = "^2.38.1-alpha.0"
-"aws-cdk.aws-lambda-python-alpha" = "^2.145.0a0"
-"cdklabs.generative-ai-cdk-constructs" = "^0.1.205"
+"aws-cdk.aws-lambda-python-alpha" = "^2.150.0a0"
+"cdklabs.generative-ai-cdk-constructs" = "^0.1.219"
pytest-benchmark = "^4.0.0"
mypy-boto3-appconfig = "^1.34.58"
mypy-boto3-cloudformation = "^1.34.111"
mypy-boto3-cloudwatch = "^1.34.83"
-mypy-boto3-dynamodb = "^1.34.131"
+mypy-boto3-dynamodb = "^1.34.148"
mypy-boto3-lambda = "^1.34.77"
mypy-boto3-logs = "^1.34.66"
-mypy-boto3-secretsmanager = "^1.34.128"
+mypy-boto3-secretsmanager = "^1.34.145"
mypy-boto3-ssm = "^1.34.132"
mypy-boto3-s3 = "^1.34.138"
mypy-boto3-xray = "^1.34.0"
types-requests = "^2.31.0"
typing-extensions = "^4.12.2"
-mkdocs-material = "^9.5.28"
+mkdocs-material = "^9.5.30"
filelock = "^3.15.4"
checksumdir = "^1.2.0"
mypy-boto3-appconfigdata = "^1.34.24"
@@ -97,7 +97,7 @@ ijson = "^3.3.0"
typed-ast = { version = "^1.5.5", python = "< 3.8" }
hvac = "^2.3.0"
aws-requests-auth = "^0.4.3"
-datadog-lambda = "^6.96.0"
+datadog-lambda = "^6.97.0"
[tool.poetry.extras]
parser = ["pydantic"]
@@ -115,13 +115,13 @@ datadog = ["datadog-lambda"]
datamasking = ["aws-encryption-sdk", "jsonpath-ng"]
[tool.poetry.group.dev.dependencies]
-cfn-lint = "1.5.3"
+cfn-lint = "1.8.2"
mypy = "^1.1.1"
types-python-dateutil = "^2.8.19.6"
aws-cdk-aws-appsync-alpha = "^2.59.0a0"
httpx = ">=0.23.3,<0.28.0"
sentry-sdk = ">=1.22.2,<3.0.0"
-ruff = ">=0.5.1,<0.5.2"
+ruff = ">=0.5.1,<0.5.5"
retry2 = "^0.9.5"
pytest-socket = ">=0.6,<0.8"
types-redis = "^4.6.0.7"
diff --git a/tests/e2e/event_handler/handlers/openapi_handler.py b/tests/e2e/event_handler/handlers/openapi_handler.py
new file mode 100644
index 00000000000..13cfb69f016
--- /dev/null
+++ b/tests/e2e/event_handler/handlers/openapi_handler.py
@@ -0,0 +1,19 @@
+from aws_lambda_powertools.event_handler import (
+ APIGatewayRestResolver,
+)
+
+app = APIGatewayRestResolver(enable_validation=True)
+
+
+@app.get("/openapi_schema")
+def openapi_schema():
+ return app.get_openapi_json_schema(
+ title="Powertools e2e API",
+ version="1.0.0",
+ description="This is a sample Powertools e2e API",
+ openapi_extensions={"x-amazon-apigateway-gateway-responses": {"DEFAULT_4XX"}},
+ )
+
+
+def lambda_handler(event, context):
+ return app.resolve(event, context)
diff --git a/tests/e2e/event_handler/infrastructure.py b/tests/e2e/event_handler/infrastructure.py
index 5fd78896a34..b607e32caf8 100644
--- a/tests/e2e/event_handler/infrastructure.py
+++ b/tests/e2e/event_handler/infrastructure.py
@@ -18,7 +18,7 @@ def create_resources(self):
functions = self.create_lambda_functions()
self._create_alb(function=[functions["AlbHandler"], functions["AlbHandlerWithBodyNone"]])
- self._create_api_gateway_rest(function=functions["ApiGatewayRestHandler"])
+ self._create_api_gateway_rest(function=[functions["ApiGatewayRestHandler"], functions["OpenapiHandler"]])
self._create_api_gateway_http(function=functions["ApiGatewayHttpHandler"])
self._create_lambda_function_url(function=functions["LambdaFunctionUrlHandler"])
@@ -76,7 +76,7 @@ def _create_api_gateway_http(self, function: Function):
CfnOutput(self.stack, "APIGatewayHTTPUrl", value=(apigw.url or ""))
- def _create_api_gateway_rest(self, function: Function):
+ def _create_api_gateway_rest(self, function: List[Function]):
apigw = apigwv1.RestApi(
self.stack,
"APIGatewayRest",
@@ -87,7 +87,10 @@ def _create_api_gateway_rest(self, function: Function):
)
todos = apigw.root.add_resource("todos")
- todos.add_method("POST", apigwv1.LambdaIntegration(function, proxy=True))
+ todos.add_method("POST", apigwv1.LambdaIntegration(function[0], proxy=True))
+
+ openapi_schema = apigw.root.add_resource("openapi_schema")
+ openapi_schema.add_method("GET", apigwv1.LambdaIntegration(function[1], proxy=True))
CfnOutput(self.stack, "APIGatewayRestUrl", value=apigw.url)
diff --git a/tests/e2e/event_handler/test_openapi.py b/tests/e2e/event_handler/test_openapi.py
new file mode 100644
index 00000000000..d69c3b142b2
--- /dev/null
+++ b/tests/e2e/event_handler/test_openapi.py
@@ -0,0 +1,27 @@
+import pytest
+from requests import Request
+
+from tests.e2e.utils import data_fetcher
+
+
+@pytest.fixture
+def apigw_rest_endpoint(infrastructure: dict) -> str:
+ return infrastructure.get("APIGatewayRestUrl", "")
+
+
+@pytest.mark.xdist_group(name="event_handler")
+def test_get_openapi_schema(apigw_rest_endpoint):
+ # GIVEN
+ url = f"{apigw_rest_endpoint}openapi_schema"
+
+ # WHEN
+ response = data_fetcher.get_http_response(
+ Request(
+ method="GET",
+ url=url,
+ ),
+ )
+
+ assert "Powertools e2e API" in response.text
+ assert "x-amazon-apigateway-gateway-responses" in response.text
+ assert response.status_code == 200
diff --git a/tests/functional/event_handler/_pydantic/conftest.py b/tests/functional/event_handler/_pydantic/conftest.py
index a099ae4cea5..1d38e2e26b1 100644
--- a/tests/functional/event_handler/_pydantic/conftest.py
+++ b/tests/functional/event_handler/_pydantic/conftest.py
@@ -120,3 +120,20 @@ def openapi31_schema():
@pytest.fixture
def security_scheme():
return {"apiKey": APIKey(name="X-API-KEY", description="API Key", in_=APIKeyIn.header)}
+
+
+@pytest.fixture
+def openapi_extension_integration_detail():
+ return {
+ "type": "aws",
+ "httpMethod": "POST",
+ "uri": "arn:aws:apigateway:us-east-1:lambda:path/2015-03-31/functions/..integration/invocations",
+ "responses": {"default": {"statusCode": "200"}},
+ "passthroughBehavior": "when_no_match",
+ "contentHandling": "CONVERT_TO_TEXT",
+ }
+
+
+@pytest.fixture
+def openapi_extension_validator_detail():
+ return "Validate body, query string parameters, and headers"
diff --git a/tests/functional/event_handler/_pydantic/test_openapi_extensions.py b/tests/functional/event_handler/_pydantic/test_openapi_extensions.py
new file mode 100644
index 00000000000..2f0552ffc4c
--- /dev/null
+++ b/tests/functional/event_handler/_pydantic/test_openapi_extensions.py
@@ -0,0 +1,266 @@
+import json
+
+from aws_lambda_powertools.event_handler.api_gateway import APIGatewayRestResolver, Router
+from aws_lambda_powertools.event_handler.openapi.models import (
+ APIKey,
+ APIKeyIn,
+ OAuth2,
+ OAuthFlowImplicit,
+ OAuthFlows,
+ Server,
+)
+
+
+def test_openapi_extension_root_level():
+ # GIVEN an APIGatewayRestResolver instance
+ app = APIGatewayRestResolver()
+
+ cors_config = {
+ "maxAge": 0,
+ "allowCredentials": False,
+ }
+
+ # WHEN we get the OpenAPI JSON schema with CORS extension in the Root Level
+ schema = json.loads(
+ app.get_openapi_json_schema(
+ openapi_extensions={"x-amazon-apigateway-cors": cors_config},
+ ),
+ )
+
+ # THEN the OpenAPI schema must contain the "x-amazon-apigateway-cors" extension
+ assert "x-amazon-apigateway-cors" in schema
+ assert schema["x-amazon-apigateway-cors"] == cors_config
+
+
+def test_openapi_extension_server_level():
+ # GIVEN an APIGatewayRestResolver instance
+ app = APIGatewayRestResolver()
+
+ endpoint_config = {
+ "disableExecuteApiEndpoint": True,
+ "vpcEndpointIds": ["vpce-0df8e77555fca0000"],
+ }
+
+ server_config = {
+ "url": "https://example.org/",
+ "description": "Example website",
+ }
+
+ # WHEN we get the OpenAPI JSON schema with a server-level openapi extension
+ schema = json.loads(
+ app.get_openapi_json_schema(
+ title="Hello API",
+ version="1.0.0",
+ servers=[
+ Server(
+ **server_config,
+ openapi_extensions={
+ "x-amazon-apigateway-endpoint-configuration": endpoint_config,
+ },
+ ),
+ ],
+ ),
+ )
+
+ # THEN the OpenAPI schema must contain the "x-amazon-apigateway-endpoint-configuration" at the server level
+ assert "x-amazon-apigateway-endpoint-configuration" in schema["servers"][0]
+ assert schema["servers"][0]["x-amazon-apigateway-endpoint-configuration"] == endpoint_config
+ assert schema["servers"][0]["url"] == server_config["url"]
+ assert schema["servers"][0]["description"] == server_config["description"]
+
+
+def test_openapi_extension_security_scheme_level_with_api_key():
+ # GIVEN an APIGatewayRestResolver instance
+ app = APIGatewayRestResolver()
+
+ authorizer_config = {
+ "authorizerUri": "arn:aws:apigateway:us-east-1:...:function:authorizer/invocations",
+ "authorizerResultTtlInSeconds": 300,
+ "type": "token",
+ }
+
+ api_key_config = {
+ "name": "X-API-KEY",
+ "description": "API Key",
+ "in_": APIKeyIn.header,
+ }
+
+ # WHEN we get the OpenAPI JSON schema with a security scheme-level extension for a custom auth
+ schema = json.loads(
+ app.get_openapi_json_schema(
+ security_schemes={
+ "apiKey": APIKey(
+ **api_key_config,
+ openapi_extensions={
+ "x-amazon-apigateway-authtype": "custom",
+ "x-amazon-apigateway-authorizer": authorizer_config,
+ },
+ ),
+ },
+ ),
+ )
+
+ # THEN the OpenAPI schema must contain the "x-amazon-apigateway-authtype" extension at the security scheme level
+ assert "x-amazon-apigateway-authtype" in schema["components"]["securitySchemes"]["apiKey"]
+ assert schema["components"]["securitySchemes"]["apiKey"]["x-amazon-apigateway-authtype"] == "custom"
+ assert schema["components"]["securitySchemes"]["apiKey"]["x-amazon-apigateway-authorizer"] == authorizer_config
+ assert schema["components"]["securitySchemes"]["apiKey"]["name"] == api_key_config["name"]
+ assert schema["components"]["securitySchemes"]["apiKey"]["description"] == api_key_config["description"]
+ assert schema["components"]["securitySchemes"]["apiKey"]["in"] == "header"
+
+
+def test_openapi_extension_security_scheme_level_with_oauth2():
+ # GIVEN an APIGatewayRestResolver instance
+ app = APIGatewayRestResolver()
+
+ authorizer_config = {
+ "identitySource": "$request.header.Authorization",
+ "jwtConfiguration": {
+ "audience": ["test"],
+ "issuer": "https://cognito-idp.us-east-1.amazonaws.com/us-east-1_xxxxx/",
+ },
+ "type": "jwt",
+ }
+
+ oauth2_config = {
+ "flows": OAuthFlows(
+ implicit=OAuthFlowImplicit(
+ authorizationUrl="https://example.com/oauth2/authorize",
+ ),
+ ),
+ }
+
+ # WHEN we get the OpenAPI JSON schema with a security scheme-level extension for a custom auth
+ schema = json.loads(
+ app.get_openapi_json_schema(
+ security_schemes={
+ "oauth2": OAuth2(
+ **oauth2_config,
+ openapi_extensions={
+ "x-amazon-apigateway-authorizer": authorizer_config,
+ },
+ ),
+ },
+ ),
+ )
+
+ # THEN the OpenAPI schema must contain the "x-amazon-apigateway-authorizer" extension at the security scheme level
+ assert "x-amazon-apigateway-authorizer" in schema["components"]["securitySchemes"]["oauth2"]
+ assert schema["components"]["securitySchemes"]["oauth2"]["x-amazon-apigateway-authorizer"] == authorizer_config
+ assert (
+ schema["components"]["securitySchemes"]["oauth2"]["x-amazon-apigateway-authorizer"]["identitySource"]
+ == "$request.header.Authorization"
+ )
+ assert schema["components"]["securitySchemes"]["oauth2"]["x-amazon-apigateway-authorizer"]["jwtConfiguration"][
+ "audience"
+ ] == ["test"]
+ assert (
+ schema["components"]["securitySchemes"]["oauth2"]["x-amazon-apigateway-authorizer"]["jwtConfiguration"][
+ "issuer"
+ ]
+ == "https://cognito-idp.us-east-1.amazonaws.com/us-east-1_xxxxx/"
+ )
+
+
+def test_openapi_extension_operation_level(openapi_extension_integration_detail):
+ # GIVEN an APIGatewayRestResolver instance
+ app = APIGatewayRestResolver()
+
+ # WHEN we define an integration extension at operation level
+ # AND get the schema
+ @app.get("/test", openapi_extensions={"x-amazon-apigateway-integration": openapi_extension_integration_detail})
+ def lambda_handler():
+ pass
+
+ schema = json.loads(app.get_openapi_json_schema())
+
+ # THEN the OpenAPI schema must contain the "x-amazon-apigateway-integration" extension at the operation level
+ assert "x-amazon-apigateway-integration" in schema["paths"]["/test"]["get"]
+ assert schema["paths"]["/test"]["get"]["x-amazon-apigateway-integration"] == openapi_extension_integration_detail
+ assert schema["paths"]["/test"]["get"]["operationId"] == "lambda_handler_test_get"
+
+
+def test_openapi_extension_operation_level_multiple_paths(
+ openapi_extension_integration_detail,
+ openapi_extension_validator_detail,
+):
+ # GIVEN an APIGatewayRestResolver instance
+ app = APIGatewayRestResolver()
+
+ # WHEN we define multiple routes with integration extension at operation level
+ # AND get the schema
+ @app.get("/test", openapi_extensions={"x-amazon-apigateway-integration": openapi_extension_integration_detail})
+ def lambda_handler_get():
+ pass
+
+ @app.post("/test", openapi_extensions={"x-amazon-apigateway-request-validator": openapi_extension_validator_detail})
+ def lambda_handler_post():
+ pass
+
+ schema = json.loads(app.get_openapi_json_schema())
+
+ # THEN each route must contain only your extension
+ assert "x-amazon-apigateway-integration" in schema["paths"]["/test"]["get"]
+ assert schema["paths"]["/test"]["get"]["x-amazon-apigateway-integration"] == openapi_extension_integration_detail
+
+ assert "x-amazon-apigateway-integration" not in schema["paths"]["/test"]["post"]
+ assert "x-amazon-apigateway-request-validator" in schema["paths"]["/test"]["post"]
+ assert (
+ schema["paths"]["/test"]["post"]["x-amazon-apigateway-request-validator"] == openapi_extension_validator_detail
+ )
+
+
+def test_openapi_extension_operation_level_with_router(openapi_extension_integration_detail):
+ # GIVEN an APIGatewayRestResolver and Router instance
+ app = APIGatewayRestResolver()
+ router = Router()
+
+ # WHEN we define an integration extension at operation level using Router
+ # AND get the schema
+ @router.get("/test", openapi_extensions={"x-amazon-apigateway-integration": openapi_extension_integration_detail})
+ def lambda_handler():
+ pass
+
+ app.include_router(router)
+
+ schema = json.loads(app.get_openapi_json_schema())
+
+ # THEN the OpenAPI schema must contain the "x-amazon-apigateway-integration" extension at the operation level
+ assert "x-amazon-apigateway-integration" in schema["paths"]["/test"]["get"]
+ assert schema["paths"]["/test"]["get"]["x-amazon-apigateway-integration"] == openapi_extension_integration_detail
+
+
+def test_openapi_extension_operation_level_multiple_paths_with_router(
+ openapi_extension_integration_detail,
+ openapi_extension_validator_detail,
+):
+ # GIVEN an APIGatewayRestResolver and Router instance
+ app = APIGatewayRestResolver()
+ router = Router()
+
+ # WHEN we define multiple routes using extensions at operation level using Router
+ # AND get the schema
+ @router.get("/test", openapi_extensions={"x-amazon-apigateway-integration": openapi_extension_integration_detail})
+ def lambda_handler_get():
+ pass
+
+ @router.post(
+ "/test",
+ openapi_extensions={"x-amazon-apigateway-request-validator": openapi_extension_validator_detail},
+ )
+ def lambda_handler_post():
+ pass
+
+ app.include_router(router)
+
+ schema = json.loads(app.get_openapi_json_schema())
+
+ # THEN each route must contain only your extension
+ assert "x-amazon-apigateway-integration" in schema["paths"]["/test"]["get"]
+ assert schema["paths"]["/test"]["get"]["x-amazon-apigateway-integration"] == openapi_extension_integration_detail
+
+ assert "x-amazon-apigateway-integration" not in schema["paths"]["/test"]["post"]
+ assert "x-amazon-apigateway-request-validator" in schema["paths"]["/test"]["post"]
+ assert (
+ schema["paths"]["/test"]["post"]["x-amazon-apigateway-request-validator"] == openapi_extension_validator_detail
+ )
diff --git a/tests/unit/event_handler/__init__.py b/tests/unit/event_handler/__init__.py
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/tests/unit/event_handler/_pydantic/__init__.py b/tests/unit/event_handler/_pydantic/__init__.py
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/tests/unit/event_handler/_pydantic/conftest.py b/tests/unit/event_handler/_pydantic/conftest.py
new file mode 100644
index 00000000000..d50d4e483ef
--- /dev/null
+++ b/tests/unit/event_handler/_pydantic/conftest.py
@@ -0,0 +1,18 @@
+import pytest
+from pydantic import __version__
+
+
+@pytest.fixture(scope="session")
+def pydanticv1_only():
+
+ version = __version__.split(".")
+ if version[0] != "1":
+ pytest.skip("pydanticv1 test 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_v1.py b/tests/unit/event_handler/_pydantic/test_openapi_models_pydantic_v1.py
new file mode 100644
index 00000000000..49ee4920f3d
--- /dev/null
+++ b/tests/unit/event_handler/_pydantic/test_openapi_models_pydantic_v1.py
@@ -0,0 +1,44 @@
+import pytest
+
+from aws_lambda_powertools.event_handler.openapi.exceptions import SchemaValidationError
+from aws_lambda_powertools.event_handler.openapi.models import OpenAPIExtensions
+
+
+@pytest.mark.usefixtures("pydanticv1_only")
+def test_openapi_extensions_with_dict():
+ # GIVEN we create an OpenAPIExtensions object with a dict
+ extensions = OpenAPIExtensions(openapi_extensions={"x-amazon-apigateway": {"foo": "bar"}})
+
+ # THEN we get a dict with the extension
+ assert extensions.dict(exclude_none=True) == {"x-amazon-apigateway": {"foo": "bar"}}
+
+
+@pytest.mark.usefixtures("pydanticv1_only")
+def test_openapi_extensions_with_invalid_key():
+ # GIVEN we create an OpenAPIExtensions object with an invalid value
+ with pytest.raises(SchemaValidationError):
+ # THEN must raise an exception
+ OpenAPIExtensions(openapi_extensions={"amazon-apigateway-invalid": {"foo": "bar"}})
+
+
+@pytest.mark.usefixtures("pydanticv1_only")
+def test_openapi_extensions_with_proxy_models():
+
+ # GIVEN we create an models using OpenAPIExtensions as a "Proxy" Model
+ class MyModelFoo(OpenAPIExtensions):
+ foo: str
+
+ class MyModelBar(OpenAPIExtensions):
+ bar: str
+ foo: MyModelFoo
+
+ value_to_serialize = MyModelBar(
+ bar="bar",
+ foo=MyModelFoo(foo="foo"),
+ openapi_extensions={"x-amazon-apigateway": {"foo": "bar"}},
+ )
+
+ value_to_return = value_to_serialize.dict(exclude_none=True)
+
+ # THEN we get a dict with the value serialized
+ assert value_to_return == {"bar": "bar", "foo": {"foo": "foo"}, "x-amazon-apigateway": {"foo": "bar"}}
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
new file mode 100644
index 00000000000..5191b4f7520
--- /dev/null
+++ b/tests/unit/event_handler/_pydantic/test_openapi_models_pydantic_v2.py
@@ -0,0 +1,44 @@
+import pytest
+
+from aws_lambda_powertools.event_handler.openapi.exceptions import SchemaValidationError
+from aws_lambda_powertools.event_handler.openapi.models import OpenAPIExtensions
+
+
+@pytest.mark.usefixtures("pydanticv2_only")
+def test_openapi_extensions_with_dict():
+ # GIVEN we create an OpenAPIExtensions object with a dict
+ extensions = OpenAPIExtensions(openapi_extensions={"x-amazon-apigateway": {"foo": "bar"}})
+
+ # THEN we get a dict with the extension
+ assert extensions.model_dump(exclude_none=True) == {"x-amazon-apigateway": {"foo": "bar"}}
+
+
+@pytest.mark.usefixtures("pydanticv2_only")
+def test_openapi_extensions_with_invalid_key():
+ # GIVEN we create an OpenAPIExtensions object with an invalid value
+ with pytest.raises(SchemaValidationError):
+ # THEN must raise an exception
+ OpenAPIExtensions(openapi_extensions={"amazon-apigateway-invalid": {"foo": "bar"}})
+
+
+@pytest.mark.usefixtures("pydanticv2_only")
+def test_openapi_extensions_with_proxy_models():
+
+ # GIVEN we create an models using OpenAPIExtensions as a "Proxy" Model
+ class MyModelFoo(OpenAPIExtensions):
+ foo: str
+
+ class MyModelBar(OpenAPIExtensions):
+ bar: str
+ foo: MyModelFoo
+
+ value_to_serialize = MyModelBar(
+ bar="bar",
+ foo=MyModelFoo(foo="foo"),
+ openapi_extensions={"x-amazon-apigateway": {"foo": "bar"}},
+ )
+
+ value_to_return = value_to_serialize.model_dump(exclude_none=True)
+
+ # THEN we get a dict with the value serialized
+ assert value_to_return == {"bar": "bar", "foo": {"foo": "foo"}, "x-amazon-apigateway": {"foo": "bar"}}