repro-lambda 0.8.1__tar.gz → 0.8.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. repro_lambda-0.8.2/.release-please-manifest.json +3 -0
  2. {repro_lambda-0.8.1 → repro_lambda-0.8.2}/CHANGELOG.md +7 -0
  3. {repro_lambda-0.8.1 → repro_lambda-0.8.2}/PKG-INFO +1 -1
  4. {repro_lambda-0.8.1 → repro_lambda-0.8.2}/SETUP.md +17 -0
  5. {repro_lambda-0.8.1 → repro_lambda-0.8.2}/USAGE.md +3 -3
  6. {repro_lambda-0.8.1 → repro_lambda-0.8.2}/pyproject.toml +1 -1
  7. {repro_lambda-0.8.1 → repro_lambda-0.8.2}/src/repro_lambda/__init__.py +1 -1
  8. {repro_lambda-0.8.1 → repro_lambda-0.8.2}/src/repro_lambda/build.py +10 -6
  9. {repro_lambda-0.8.1 → repro_lambda-0.8.2}/src/repro_lambda/hasher.py +10 -0
  10. {repro_lambda-0.8.1 → repro_lambda-0.8.2}/tests/test_build_integration.py +3 -0
  11. {repro_lambda-0.8.1 → repro_lambda-0.8.2}/tests/test_extra_files.py +28 -0
  12. repro_lambda-0.8.2/tests/test_hash_contract.py +82 -0
  13. {repro_lambda-0.8.1 → repro_lambda-0.8.2}/uv.lock +1 -1
  14. repro_lambda-0.8.1/.release-please-manifest.json +0 -3
  15. {repro_lambda-0.8.1 → repro_lambda-0.8.2}/.github/workflows/build.yml +0 -0
  16. {repro_lambda-0.8.1 → repro_lambda-0.8.2}/.github/workflows/ci.yml +0 -0
  17. {repro_lambda-0.8.1 → repro_lambda-0.8.2}/.github/workflows/move-major-tag.yml +0 -0
  18. {repro_lambda-0.8.1 → repro_lambda-0.8.2}/.github/workflows/promote.yml +0 -0
  19. {repro_lambda-0.8.1 → repro_lambda-0.8.2}/.github/workflows/publish.yml +0 -0
  20. {repro_lambda-0.8.1 → repro_lambda-0.8.2}/.github/workflows/release.yml +0 -0
  21. {repro_lambda-0.8.1 → repro_lambda-0.8.2}/.gitignore +0 -0
  22. {repro_lambda-0.8.1 → repro_lambda-0.8.2}/.pre-commit-config.yaml +0 -0
  23. {repro_lambda-0.8.1 → repro_lambda-0.8.2}/LICENSE +0 -0
  24. {repro_lambda-0.8.1 → repro_lambda-0.8.2}/README.md +0 -0
  25. {repro_lambda-0.8.1 → repro_lambda-0.8.2}/assets/social-preview.html +0 -0
  26. {repro_lambda-0.8.1 → repro_lambda-0.8.2}/assets/social-preview.png +0 -0
  27. {repro_lambda-0.8.1 → repro_lambda-0.8.2}/examples/complete/README.md +0 -0
  28. {repro_lambda-0.8.1 → repro_lambda-0.8.2}/examples/complete/builds/catalog.json +0 -0
  29. {repro_lambda-0.8.1 → repro_lambda-0.8.2}/examples/complete/lambdas.toml +0 -0
  30. {repro_lambda-0.8.1 → repro_lambda-0.8.2}/examples/complete/main.tf +0 -0
  31. {repro_lambda-0.8.1 → repro_lambda-0.8.2}/examples/complete/outputs.tf +0 -0
  32. {repro_lambda-0.8.1 → repro_lambda-0.8.2}/examples/complete/src/app/app.py +0 -0
  33. {repro_lambda-0.8.1 → repro_lambda-0.8.2}/examples/complete/src/app/requirements.arm64.lock +0 -0
  34. {repro_lambda-0.8.1 → repro_lambda-0.8.2}/examples/complete/variables.tf +0 -0
  35. {repro_lambda-0.8.1 → repro_lambda-0.8.2}/examples/complete/versions.tf +0 -0
  36. {repro_lambda-0.8.1 → repro_lambda-0.8.2}/release-please-config.json +0 -0
  37. {repro_lambda-0.8.1 → repro_lambda-0.8.2}/src/repro_lambda/__main__.py +0 -0
  38. {repro_lambda-0.8.1 → repro_lambda-0.8.2}/src/repro_lambda/catalog.py +0 -0
  39. {repro_lambda-0.8.1 → repro_lambda-0.8.2}/src/repro_lambda/cli.py +0 -0
  40. {repro_lambda-0.8.1 → repro_lambda-0.8.2}/src/repro_lambda/docker_runner.py +0 -0
  41. {repro_lambda-0.8.1 → repro_lambda-0.8.2}/src/repro_lambda/git_guard.py +0 -0
  42. {repro_lambda-0.8.1 → repro_lambda-0.8.2}/src/repro_lambda/manifest.py +0 -0
  43. {repro_lambda-0.8.1 → repro_lambda-0.8.2}/src/repro_lambda/promote.py +0 -0
  44. {repro_lambda-0.8.1 → repro_lambda-0.8.2}/src/repro_lambda/s3_uploader.py +0 -0
  45. {repro_lambda-0.8.1 → repro_lambda-0.8.2}/src/repro_lambda/source_locker.py +0 -0
  46. {repro_lambda-0.8.1 → repro_lambda-0.8.2}/src/repro_lambda/source_stager.py +0 -0
  47. {repro_lambda-0.8.1 → repro_lambda-0.8.2}/src/repro_lambda/sources.py +0 -0
  48. {repro_lambda-0.8.1 → repro_lambda-0.8.2}/src/repro_lambda/verify.py +0 -0
  49. {repro_lambda-0.8.1 → repro_lambda-0.8.2}/src/repro_lambda/zip_packager.py +0 -0
  50. {repro_lambda-0.8.1 → repro_lambda-0.8.2}/tests/__init__.py +0 -0
  51. {repro_lambda-0.8.1 → repro_lambda-0.8.2}/tests/conftest.py +0 -0
  52. {repro_lambda-0.8.1 → repro_lambda-0.8.2}/tests/fixtures/sample_nodejs_lambda/handler/index.js +0 -0
  53. {repro_lambda-0.8.1 → repro_lambda-0.8.2}/tests/fixtures/sample_nodejs_lambda/handler/package-lock.json +0 -0
  54. {repro_lambda-0.8.1 → repro_lambda-0.8.2}/tests/fixtures/sample_nodejs_lambda/handler/package.json +0 -0
  55. {repro_lambda-0.8.1 → repro_lambda-0.8.2}/tests/fixtures/sample_nodejs_lambda/lambdas.toml +0 -0
  56. {repro_lambda-0.8.1 → repro_lambda-0.8.2}/tests/fixtures/sample_python_lambda/handler/app.py +0 -0
  57. {repro_lambda-0.8.1 → repro_lambda-0.8.2}/tests/fixtures/sample_python_lambda/handler/requirements.arm64.lock +0 -0
  58. {repro_lambda-0.8.1 → repro_lambda-0.8.2}/tests/fixtures/sample_python_lambda/handler/requirements.in +0 -0
  59. {repro_lambda-0.8.1 → repro_lambda-0.8.2}/tests/fixtures/sample_python_lambda/lambdas.toml +0 -0
  60. {repro_lambda-0.8.1 → repro_lambda-0.8.2}/tests/test_build_nodejs.py +0 -0
  61. {repro_lambda-0.8.1 → repro_lambda-0.8.2}/tests/test_catalog.py +0 -0
  62. {repro_lambda-0.8.1 → repro_lambda-0.8.2}/tests/test_cli_build.py +0 -0
  63. {repro_lambda-0.8.1 → repro_lambda-0.8.2}/tests/test_cli_lock.py +0 -0
  64. {repro_lambda-0.8.1 → repro_lambda-0.8.2}/tests/test_cli_smoke.py +0 -0
  65. {repro_lambda-0.8.1 → repro_lambda-0.8.2}/tests/test_docker_runner.py +0 -0
  66. {repro_lambda-0.8.1 → repro_lambda-0.8.2}/tests/test_docker_runner_nodejs.py +0 -0
  67. {repro_lambda-0.8.1 → repro_lambda-0.8.2}/tests/test_e2e_nodejs_lambda.py +0 -0
  68. {repro_lambda-0.8.1 → repro_lambda-0.8.2}/tests/test_e2e_python_lambda.py +0 -0
  69. {repro_lambda-0.8.1 → repro_lambda-0.8.2}/tests/test_git_guard.py +0 -0
  70. {repro_lambda-0.8.1 → repro_lambda-0.8.2}/tests/test_hasher.py +0 -0
  71. {repro_lambda-0.8.1 → repro_lambda-0.8.2}/tests/test_manifest.py +0 -0
  72. {repro_lambda-0.8.1 → repro_lambda-0.8.2}/tests/test_per_lambda_builder.py +0 -0
  73. {repro_lambda-0.8.1 → repro_lambda-0.8.2}/tests/test_promote.py +0 -0
  74. {repro_lambda-0.8.1 → repro_lambda-0.8.2}/tests/test_python_byte_compat_regression.py +0 -0
  75. {repro_lambda-0.8.1 → repro_lambda-0.8.2}/tests/test_s3_uploader.py +0 -0
  76. {repro_lambda-0.8.1 → repro_lambda-0.8.2}/tests/test_source_locker.py +0 -0
  77. {repro_lambda-0.8.1 → repro_lambda-0.8.2}/tests/test_source_stager.py +0 -0
  78. {repro_lambda-0.8.1 → repro_lambda-0.8.2}/tests/test_sources.py +0 -0
  79. {repro_lambda-0.8.1 → repro_lambda-0.8.2}/tests/test_sources_hash.py +0 -0
  80. {repro_lambda-0.8.1 → repro_lambda-0.8.2}/tests/test_sources_schema.py +0 -0
  81. {repro_lambda-0.8.1 → repro_lambda-0.8.2}/tests/test_verify.py +0 -0
  82. {repro_lambda-0.8.1 → repro_lambda-0.8.2}/tests/test_zip_excludes.py +0 -0
  83. {repro_lambda-0.8.1 → repro_lambda-0.8.2}/tests/test_zip_packager.py +0 -0
@@ -0,0 +1,3 @@
1
+ {
2
+ ".": "0.8.2"
3
+ }
@@ -1,5 +1,12 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.8.2](https://github.com/antonbabenko/repro-lambda/compare/v0.8.1...v0.8.2) (2026-09-23)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * **hasher:** stop re-keying artifacts on every repro-lambda release ([#19](https://github.com/antonbabenko/repro-lambda/issues/19)) ([44a263a](https://github.com/antonbabenko/repro-lambda/commit/44a263a560d6d7d3504a1365c4d2dac9c4dbad4f))
9
+
3
10
  ## [0.8.1](https://github.com/antonbabenko/repro-lambda/compare/v0.8.0...v0.8.1) (2026-09-23)
4
11
 
5
12
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: repro-lambda
3
- Version: 0.8.1
3
+ Version: 0.8.2
4
4
  Summary: Build reproducible AWS Lambda packages outside Terraform, optimized for terraform-aws-lambda by serverless.tf.
5
5
  Project-URL: Homepage, https://github.com/antonbabenko/repro-lambda
6
6
  Project-URL: Repository, https://github.com/antonbabenko/repro-lambda
@@ -32,6 +32,23 @@ Once an artifact with a content-hash key (`lambdas/<name>/<sha256>.zip`) is
32
32
  uploaded, the key is permanently bound to those bytes. `repro-lambda` treats
33
33
  HTTP 412 PreconditionFailed on a duplicate upload as success.
34
34
 
35
+ The key hashes the build inputs (staged source, requirements lock, spec, base
36
+ image, filters, sources) plus a builder hash contract. It does not hash the
37
+ repro-lambda package version, so upgrading repro-lambda keeps every existing key
38
+ unless the release changes the packaged bytes, in which case it bumps
39
+ `HASH_CONTRACT` in `hasher.py` and says so in its changelog entry.
40
+ `tests/test_hash_contract.py` fingerprints the packaging modules, so a change to
41
+ them fails CI until the contract is reviewed.
42
+
43
+ `--verify` builds twice and compares the two results; it does not compare against
44
+ the object already in S3. On a cache hit the build catalog records
45
+ `hash-contract:<value>` as the builder, because the running version did not
46
+ build that object.
47
+
48
+ Releases up to 0.8.1 folded the package version into the key, so 0.8.1 would mint
49
+ keys of its own. 0.8.2 returns to the keys 0.8.0 minted. If a pin was taken from a
50
+ 0.8.1 build, it moves back to the 0.8.0 key on the next build, with identical bytes.
51
+
35
52
  ## Terraform - per-account bootstrap
36
53
 
37
54
  The Terraform below assumes you already have a configured AWS provider in the
@@ -86,9 +86,9 @@ include_patterns = ["worker/**/*.py", "worker/**/*.json"]
86
86
  exclude_patterns = ["**/tests/**"]
87
87
  ```
88
88
 
89
- The resolved per-lambda builder (base-image digest + include/exclude lists +
90
- builder version) folds into the content hash, so changing an override re-keys
91
- that lambda's artifact while leaving the others untouched.
89
+ The resolved per-lambda builder (base-image digest + include/exclude lists)
90
+ folds into the content hash, so changing an override re-keys that lambda's
91
+ artifact while leaving the others untouched.
92
92
 
93
93
  ## Declarative sources - `[[lambda.source]]`
94
94
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "repro-lambda"
3
- version = "0.8.1"
3
+ version = "0.8.2"
4
4
  description = "Build reproducible AWS Lambda packages outside Terraform, optimized for terraform-aws-lambda by serverless.tf."
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11"
@@ -1,3 +1,3 @@
1
1
  """repro-lambda — reproducible AWS Lambda packaging outside Terraform."""
2
2
 
3
- __version__ = "0.8.1" # x-release-please-version
3
+ __version__ = "0.8.2" # x-release-please-version
@@ -11,7 +11,7 @@ from pathlib import Path
11
11
  from repro_lambda import __version__
12
12
  from repro_lambda.catalog import Catalog, CatalogEntry
13
13
  from repro_lambda.docker_runner import build_nodejs_lambda, build_python_lambda
14
- from repro_lambda.hasher import compute_content_hash
14
+ from repro_lambda.hasher import HASH_CONTRACT, compute_content_hash
15
15
  from repro_lambda.manifest import BuilderConfig, LambdaSpec, resolve_builder
16
16
  from repro_lambda.s3_uploader import S3Uploader, UploadResult
17
17
  from repro_lambda.source_stager import stage_source
@@ -83,7 +83,7 @@ def compute_sha_for(
83
83
  requirements_lock=lock_path,
84
84
  spec=spec,
85
85
  base_image=primary_base_image,
86
- builder_version=__version__,
86
+ builder_version=HASH_CONTRACT,
87
87
  extra_files=extras,
88
88
  payload_exec=[(ef.dest, ef.executable) for ef in spec.extra_files],
89
89
  include_patterns=builder.include_patterns,
@@ -130,7 +130,7 @@ def build_one(
130
130
  requirements_lock=lock_path,
131
131
  spec=spec,
132
132
  base_image=primary_base_image,
133
- builder_version=__version__,
133
+ builder_version=HASH_CONTRACT,
134
134
  extra_files=extras,
135
135
  payload_exec=[(ef.dest, ef.executable) for ef in spec.extra_files],
136
136
  include_patterns=builder.include_patterns,
@@ -144,7 +144,7 @@ def build_one(
144
144
 
145
145
  uploader = S3Uploader(region=spec.region)
146
146
  if uploader.exists(bucket=target_bucket, key=bucket_key):
147
- _record(catalog, spec, sha, source_commit, builder)
147
+ _record(catalog, spec, sha, source_commit, builder, built=False)
148
148
  return BuildOutcome(BuildResult.CACHE_HIT, sha, bucket_key)
149
149
 
150
150
  # Cache miss only: fetch + verify + extract declarative sources into the staged
@@ -182,7 +182,7 @@ def build_one(
182
182
 
183
183
  result = uploader.upload(bucket=target_bucket, key=bucket_key, body_path=out_zip)
184
184
  assert result in {UploadResult.UPLOADED, UploadResult.ALREADY_PRESENT}
185
- _record(catalog, spec, sha, source_commit, builder)
185
+ _record(catalog, spec, sha, source_commit, builder, built=True)
186
186
  return BuildOutcome(BuildResult.BUILT_AND_UPLOADED, sha, bucket_key)
187
187
 
188
188
 
@@ -192,6 +192,8 @@ def _record(
192
192
  sha: str,
193
193
  source_commit: str,
194
194
  builder: BuilderConfig,
195
+ *,
196
+ built: bool,
195
197
  ) -> None:
196
198
  if spec.package_manager == "npm":
197
199
  primary_image = builder.base_image_nodejs
@@ -205,7 +207,9 @@ def _record(
205
207
  runtime=spec.runtime,
206
208
  arch=spec.arch,
207
209
  region=spec.region,
208
- builder_version=__version__,
210
+ # A cache hit did not build the object, and the key no longer carries the
211
+ # package version, so only the hash contract is known to match it.
212
+ builder_version=__version__ if built else f"hash-contract:{HASH_CONTRACT}",
209
213
  base_image_digest=primary_image.split("@", 1)[-1],
210
214
  built_at=datetime.now(UTC).strftime("%Y-%m-%dT%H:%M:%SZ"),
211
215
  ),
@@ -7,6 +7,15 @@ from pathlib import Path
7
7
 
8
8
  from repro_lambda.manifest import LambdaSpec, Source
9
9
 
10
+ # The builder's contribution to every artifact key. NOT the package version: a
11
+ # release that leaves the packaged bytes alone must keep every key, or promote-by-key
12
+ # sees two keys for one zip. Bump it only when the same inputs would produce different
13
+ # zip bytes (packaging, normalisation, install flags, base-image handling), and update
14
+ # the fingerprints in tests/test_hash_contract.py with it. Frozen at "0.8.0", the last
15
+ # package version folded here, so keys minted by 0.8.0 stay valid; the packaging
16
+ # modules did not change between 0.7.2 and 0.8.1.
17
+ HASH_CONTRACT = "0.8.0"
18
+
10
19
 
11
20
  def _sha256_file(path: Path) -> str:
12
21
  h = hashlib.sha256()
@@ -32,6 +41,7 @@ def compute_content_hash(
32
41
  """
33
42
  sha256 over: sorted (relative-path, sha256(content)) tuples for the staged tree
34
43
  + sha256(requirements_lock) + spec scalars + base_image + builder_version
44
+ (the HASH_CONTRACT, not the package version)
35
45
  + optional resolved include/exclude filter lists + optional extra_files keyed by
36
46
  destination relname (e.g. "package.json").
37
47
 
@@ -7,6 +7,7 @@ from moto import mock_aws
7
7
 
8
8
  from repro_lambda.build import BuildResult, build_one, compute_sha_for
9
9
  from repro_lambda.catalog import Catalog
10
+ from repro_lambda.hasher import HASH_CONTRACT
10
11
  from repro_lambda.manifest import BuilderConfig, LambdaSpec
11
12
 
12
13
 
@@ -84,6 +85,8 @@ def test_build_one_cache_hit_skips_docker_and_returns_existing_sha(
84
85
  assert outcome.sha256 == sha
85
86
  mock_docker.assert_not_called()
86
87
  assert catalog.lambdas["app"].current == sha
88
+ # This process did not build the object; claim only the hash contract.
89
+ assert catalog.lambdas["app"].history[0].builder_version == f"hash-contract:{HASH_CONTRACT}"
87
90
 
88
91
 
89
92
  def test_build_one_cache_miss_runs_docker_uploads_and_records(git_repo_with_sample: Path, mocker):
@@ -220,3 +220,31 @@ def test_compute_sha_for_reflects_payload_binary(tmp_path: Path):
220
220
  sha_b = compute_sha_for(repo_root=repo, spec=spec, builder=builder)
221
221
 
222
222
  assert sha_a != sha_b, "content hash must track the bundled binary's bytes"
223
+
224
+
225
+ def test_compute_sha_for_ignores_the_package_version(tmp_path: Path, monkeypatch):
226
+ # A release that does not change the packaged bytes must not re-key artifacts:
227
+ # identical zips under two keys break promote-by-key between accounts.
228
+ repo = _git_repo_with_source(tmp_path)
229
+ (repo / "handler" / "requirements.arm64.lock").write_text("")
230
+ builder = BuilderConfig(base_image_python=PINNED_IMAGE)
231
+ spec = _spec()
232
+
233
+ sha_a = compute_sha_for(repo_root=repo, spec=spec, builder=builder)
234
+ monkeypatch.setattr("repro_lambda.build.__version__", "99.0.0")
235
+ sha_b = compute_sha_for(repo_root=repo, spec=spec, builder=builder)
236
+
237
+ assert sha_a == sha_b
238
+
239
+
240
+ def test_compute_sha_for_rekeys_on_hash_contract_bump(tmp_path: Path, monkeypatch):
241
+ repo = _git_repo_with_source(tmp_path)
242
+ (repo / "handler" / "requirements.arm64.lock").write_text("")
243
+ builder = BuilderConfig(base_image_python=PINNED_IMAGE)
244
+ spec = _spec()
245
+
246
+ sha_a = compute_sha_for(repo_root=repo, spec=spec, builder=builder)
247
+ monkeypatch.setattr("repro_lambda.build.HASH_CONTRACT", "bumped", raising=False)
248
+ sha_b = compute_sha_for(repo_root=repo, spec=spec, builder=builder)
249
+
250
+ assert sha_a != sha_b
@@ -0,0 +1,82 @@
1
+ """Guards for HASH_CONTRACT: artifact keys must change exactly when packaged bytes can."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import hashlib
6
+ from pathlib import Path
7
+
8
+ import repro_lambda
9
+ from repro_lambda.hasher import HASH_CONTRACT, compute_content_hash
10
+ from repro_lambda.manifest import LambdaSpec
11
+
12
+ _PKG = Path(repro_lambda.__file__).parent
13
+
14
+ # Modules that shape the zip bytes or the key beyond the hashed inputs. Editing one
15
+ # means deciding whether the same inputs can now produce different bytes: if yes, bump
16
+ # HASH_CONTRACT; either way, record the new fingerprints here under the (possibly new)
17
+ # contract. A fingerprint update is cheap; a key change re-keys every artifact.
18
+ # Known gap: npm packaging uses base_image_python, which is not in the key (issue #20).
19
+ PACKAGING_FINGERPRINTS = {
20
+ "0.8.0": {
21
+ "build.py": "2dc3140e8af9e7e68d70199f85f261db04eef3e847d60ac21c9276133e9ec041",
22
+ "hasher.py": "c5eb56e00737efdca6c4bb47b694390b3dbd8941569f6c5cb299ba4cd5b18704",
23
+ "zip_packager.py": "56810a5e337a2fd5f72c269ba81bab06ca2f019e14e0edb728d6c37881d7cf0a",
24
+ "docker_runner.py": "0092a103b8dd11e6c0a00979e4c38272ccc6d707f2ee74035d8d84e61fed4988",
25
+ "source_stager.py": "38186277fb94ab6feac8a07cad2ae35f5adfaa52bde3d51ac71daccb003d3c68",
26
+ "sources.py": "10415ccc89b85f521fb64ef199f084f0bea1842032d414cb3d3537334f6caa0b",
27
+ },
28
+ }
29
+
30
+
31
+ def test_packaging_modules_match_the_hash_contract():
32
+ expected = PACKAGING_FINGERPRINTS.get(HASH_CONTRACT)
33
+ assert expected, f"no packaging fingerprints recorded for HASH_CONTRACT={HASH_CONTRACT!r}"
34
+ actual = {name: hashlib.sha256((_PKG / name).read_bytes()).hexdigest() for name in expected}
35
+ changed = sorted(n for n in expected if actual[n] != expected[n])
36
+ assert not changed, (
37
+ f"packaging module(s) changed: {changed}. If the same inputs can now produce "
38
+ "different zip bytes, bump HASH_CONTRACT in hasher.py. Then record these "
39
+ f"fingerprints under the contract: {actual}"
40
+ )
41
+
42
+
43
+ def test_content_hash_golden_key(tmp_path: Path):
44
+ # Pins the hash layout: any change to what is folded, or how, re-keys every artifact.
45
+ src = tmp_path / "source"
46
+ src.mkdir()
47
+ (src / "app.py").write_text("ok\n")
48
+ lock = tmp_path / "lock.txt"
49
+ lock.write_text("\n")
50
+ spec = LambdaSpec(
51
+ logical_name="fn",
52
+ source_dir="src/fn",
53
+ requirements_lock="src/fn/requirements.${arch}.lock",
54
+ runtime="python3.13",
55
+ arch="arm64",
56
+ handler="app.handler",
57
+ region="eu-west-1",
58
+ )
59
+ got = compute_content_hash(src, lock, spec, "img@sha256:0", HASH_CONTRACT)
60
+ assert got == "de8da0c8801288758e8dc86edc15b085ffadc6eb915dcc45d875bfb235854dea"
61
+
62
+
63
+ # The zip command's exclusion list lives in cli.py; changing it changes the zip bytes.
64
+ ZIP_EXCLUDES = {
65
+ "0.8.0": [
66
+ "*__pycache__*",
67
+ "*.pyc",
68
+ "*.dist-info/RECORD",
69
+ "*.dist-info/INSTALLER",
70
+ "*.dist-info/direct_url.json",
71
+ "*.dist-info/REQUESTED",
72
+ ],
73
+ }
74
+
75
+
76
+ def test_zip_excludes_match_the_hash_contract():
77
+ from repro_lambda.cli import _LAMBDA_ZIP_EXCLUDES
78
+
79
+ assert ZIP_EXCLUDES.get(HASH_CONTRACT) == _LAMBDA_ZIP_EXCLUDES, (
80
+ "the lambda zip exclusion list changed: bump HASH_CONTRACT in hasher.py "
81
+ "and record the new list under the contract"
82
+ )
@@ -646,7 +646,7 @@ wheels = [
646
646
 
647
647
  [[package]]
648
648
  name = "repro-lambda"
649
- version = "0.8.1"
649
+ version = "0.8.2"
650
650
  source = { editable = "." }
651
651
  dependencies = [
652
652
  { name = "boto3" },
@@ -1,3 +0,0 @@
1
- {
2
- ".": "0.8.1"
3
- }
File without changes
File without changes
File without changes