From 0cac905538aa51831157626d026146bd97236db0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 16 Aug 2026 20:04:18 +0000 Subject: [PATCH 1/3] chore(deps): bump ruff from 0.16.1 to 0.16.3 Bumps [ruff](https://github.com/astral-sh/ruff) from 0.16.1 to 0.16.3. - [Release notes](https://github.com/astral-sh/ruff/releases) - [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md) - [Commits](https://github.com/astral-sh/ruff/compare/0.16.1...0.16.3) --- updated-dependencies: - dependency-name: ruff dependency-version: 0.16.3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements-ci.txt | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/requirements-ci.txt b/requirements-ci.txt index 267acdae..fffc84b7 100644 --- a/requirements-ci.txt +++ b/requirements-ci.txt @@ -34,8 +34,25 @@ pytest==9.1.1 \ --hash=sha256:37a86b45efb9a47a61a36449063e8e18d0cab3161329fc099eb21783169c4f0c pytest-asyncio==1.4.0 \ --hash=sha256:933ca923a23075a87fb7070c0ec272a6848489824d887c85c812670932835aa1 -ruff==0.16.1 \ - --hash=sha256:39897739f112253ee4fdd2e8aa9a4f9ded99fb2be367d5f31dfa4ded6025584c +ruff==0.16.3 \ + --hash=sha256:09571e6d1288ed9be475207a3ac04ada404f1cd898104be0f6ab8d7df438575b \ + --hash=sha256:0c5710e247a58a4521e66e124ba9a74655b414f61ba3a2e9e3811e11098f48f7 \ + --hash=sha256:294b95c4ae0cda9388525c2047778aa758d6b8d4bb876fd4e9eaa3ebc92343eb \ + --hash=sha256:2c18c5a101eb540010638cc1ff3c84944d3adb3df62b8d98ca8f22ba484d3413 \ + --hash=sha256:388cdf2166642bd9b13d52b5932d3170f34f8abed7e8d9a855f1d84b83645a0a \ + --hash=sha256:3d0c7c40c87c2a820509c31ba007968da6e1306468c067b2d82fbfdbcd0e8474 \ + --hash=sha256:8457c44f15033c85ddbb77b15d451df9e24e4bd03b628396dd3610cedc3b8f82 \ + --hash=sha256:9e0b1da805eb043654645d74d5de1e5ce2edc686e40790d2b86f56d71cc06a84 \ + --hash=sha256:9f738c0fdfa8eed0b2ce7fb27ee7258208a92a68d7949e62aa15164bc7b389da \ + --hash=sha256:a2d85c02f9b8e165d85e6779184d38c4132de12603dab59c51c28e22584f9e4d \ + --hash=sha256:a37bdea0bbe21780f590bf437d6412c8c4e1b6cd010f91a65c2c40c5e5f5f870 \ + --hash=sha256:b8ca152da82c1acc1fa8d5874b15951935f0eef46f10e6954c83859011b6178a \ + --hash=sha256:c5536e3acfbf9563085aa2be7b13c629c3077e902afc5b941ac44024dbb9f506 \ + --hash=sha256:e2ed719e14aa64d895c2ee922594a90a43c861a93f0575a95ff8c47cdbd13eb9 \ + --hash=sha256:e76d33a347661a84b5be6d043d0347fdc745dfdcf825a8f4fed64b5e26eebdf2 \ + --hash=sha256:e80a7d69ca2a6d1c4d352ec91458cdca6e56c83cdbcabd93e4abe1e53591d948 \ + --hash=sha256:fb785f0be25abe69d320415cd4f833b59e17ba7613d9ba6a958023b6bceb0a50 \ + --hash=sha256:fe155130631a2471fd2e14a7a664a4dfbd7194b8229c3d7b2a40b21178639081 tomli==2.4.1 ; python_version < "3.11" \ --hash=sha256:0d85819802132122da43cb86656f8d1f8c6587d54ae7dcaf30e90533028b49fe typing-extensions==4.16.0 ; python_version < "3.13" \ From 006161e9530ae081623f81b6cf4d32bce6a55bcc Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 18 Aug 2026 06:22:38 -0700 Subject: [PATCH 2/3] ci: repair multi-hash lock evidence for Ruff update --- .github/workflows/repair-pr-224.yml | 313 ++++++++++++++++++++++++++++ 1 file changed, 313 insertions(+) create mode 100644 .github/workflows/repair-pr-224.yml diff --git a/.github/workflows/repair-pr-224.yml b/.github/workflows/repair-pr-224.yml new file mode 100644 index 00000000..7b39bfd4 --- /dev/null +++ b/.github/workflows/repair-pr-224.yml @@ -0,0 +1,313 @@ +name: Repair PR 224 multi-hash lock evidence + +on: + push: + branches: + - dependabot/pip/ruff-0.16.3 + paths: + - .github/workflows/repair-pr-224.yml + +permissions: + contents: write + +concurrency: + group: repair-pr-224-${{ github.ref }} + cancel-in-progress: false + +jobs: + repair: + if: >- + github.repository == 'ContextualWisdomLab/EgressWeave' && + github.ref == 'refs/heads/dependabot/pip/ruff-0.16.3' + runs-on: ubuntu-24.04 + timeout-minutes: 35 + env: + EXPECTED_PARENT_SHA: 0cac905538aa51831157626d026146bd97236db0 + TARGET_BRANCH: dependabot/pip/ruff-0.16.3 + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + ref: ${{ github.sha }} + fetch-depth: 2 + persist-credentials: false + - name: Verify exact repair boundary + shell: bash + run: | + set -euo pipefail + test "$(git rev-parse HEAD^)" = "$EXPECTED_PARENT_SHA" + test "$(git rev-parse HEAD)" = "$GITHUB_SHA" + test "$(git branch --show-current)" = "$TARGET_BRANCH" + - name: Apply minimal multi-hash parser correction and RED regressions + shell: bash + run: | + set -euo pipefail + python - <<'PY' + from pathlib import Path + + script_path = Path("scripts/ci/generate_release_sbom.py") + script = script_path.read_text(encoding="utf-8") + old_loader = '''def _load_runtime_lock(path: Path) -> dict[str, dict[str, str | None]]: + """Load exact package versions, markers, and hashes from the CI lock.""" + try: + if path.stat().st_size > MAX_MANIFEST_BYTES: + raise SystemExit("runtime lock exceeds the safety bound") + content = path.read_text(encoding="utf-8") + except (OSError, UnicodeError) as error: + raise SystemExit("runtime lock is unreadable") from error + entries: dict[str, dict[str, str | None]] = {} + for raw_line in content.replace("\\\\\\n", " ").splitlines(): + line = raw_line.strip() + if not line or line.startswith("#"): + continue + if line.count("--hash=sha256:") != 1: + raise SystemExit("runtime lock entries require exactly one SHA-256 hash") + requirement_text, digest = line.split("--hash=sha256:", 1) + digest = digest.strip() + if SHA256.fullmatch(digest) is None: + raise SystemExit("runtime lock contains a noncanonical SHA-256 hash") + try: + requirement = Requirement(requirement_text.strip()) + except InvalidRequirement as error: + raise SystemExit("runtime lock contains an invalid requirement") from error + if requirement.extras: + raise SystemExit("runtime lock requirements must not use extras") + name = _name(requirement.name) + if name in entries: + raise SystemExit(f"runtime lock duplicates package {name!r}") + specifiers = list(requirement.specifier) + version = ( + specifiers[0].version + if requirement.url is None + and len(specifiers) == 1 + and specifiers[0].operator == "==" + else None + ) + entries[name] = { + "version": version, + "sha256": digest, + "marker": str(requirement.marker) if requirement.marker is not None else None, + } + return entries + ''' + new_loader = '''def _load_runtime_lock( + path: Path, + ) -> dict[str, dict[str, str | tuple[str, ...] | None]]: + """Load exact package versions, markers, and allowed hashes from the CI lock.""" + try: + if path.stat().st_size > MAX_MANIFEST_BYTES: + raise SystemExit("runtime lock exceeds the safety bound") + content = path.read_text(encoding="utf-8") + except (OSError, UnicodeError) as error: + raise SystemExit("runtime lock is unreadable") from error + entries: dict[str, dict[str, str | tuple[str, ...] | None]] = {} + for raw_line in content.replace("\\\\\\n", " ").splitlines(): + line = raw_line.strip() + if not line or line.startswith("#"): + continue + segments = line.split("--hash=sha256:") + if len(segments) < 2: + raise SystemExit("runtime lock entries require at least one SHA-256 hash") + requirement_text = segments[0] + digests = tuple(segment.strip() for segment in segments[1:]) + if any(SHA256.fullmatch(digest) is None for digest in digests): + raise SystemExit("runtime lock contains a noncanonical SHA-256 hash") + if len(set(digests)) != len(digests): + raise SystemExit("runtime lock contains a duplicate SHA-256 hash") + try: + requirement = Requirement(requirement_text.strip()) + except InvalidRequirement as error: + raise SystemExit("runtime lock contains an invalid requirement") from error + if requirement.extras: + raise SystemExit("runtime lock requirements must not use extras") + name = _name(requirement.name) + if name in entries: + raise SystemExit(f"runtime lock duplicates package {name!r}") + specifiers = list(requirement.specifier) + version = ( + specifiers[0].version + if requirement.url is None + and len(specifiers) == 1 + and specifiers[0].operator == "==" + else None + ) + entries[name] = { + "version": version, + "sha256": digests, + "marker": str(requirement.marker) if requirement.marker is not None else None, + } + return entries + ''' + if script.count(old_loader) != 1: + raise SystemExit("runtime-lock loader boundary drifted") + script = script.replace(old_loader, new_loader, 1) + + old_validator = ''' for name, component in components.items(): + locked = lock_entries.get(name) + expected = { + "version": component["version"], + "sha256": component["sha256"], + "marker": _canonical_marker(component["marker"], f"component {name}"), + } + if locked != expected: + raise SystemExit( + f"component {name!r} does not match the hash-locked runtime subset" + ) + ''' + new_validator = ''' for name, component in components.items(): + locked = lock_entries.get(name) + expected_marker = _canonical_marker( + component["marker"], f"component {name}" + ) + if ( + locked is None + or locked["version"] != component["version"] + or locked["marker"] != expected_marker + or component["sha256"] not in locked["sha256"] + ): + raise SystemExit( + f"component {name!r} does not match the hash-locked runtime subset" + ) + ''' + if script.count(old_validator) != 1: + raise SystemExit("runtime-lock validation boundary drifted") + script_path.write_text( + script.replace(old_validator, new_validator, 1), + encoding="utf-8", + ) + + Path("tests/test_release_lock_hash_sets.py").write_text( + '''"""Regression contracts for portable multi-artifact hash locks.""" + + from __future__ import annotations + + import importlib.util + from pathlib import Path + + import pytest + + REPOSITORY_ROOT = Path(__file__).resolve().parents[1] + GENERATOR_PATH = REPOSITORY_ROOT / "scripts" / "ci" / "generate_release_sbom.py" + MANIFEST_PATH = REPOSITORY_ROOT / "scripts" / "ci" / "release_runtime_dependencies.json" + LOCK_PATH = REPOSITORY_ROOT / "requirements-ci.txt" + + + def _load_generator(): + """Load the repository SBOM generator without packaging it.""" + specification = importlib.util.spec_from_file_location( + "egressweave_release_lock_hash_sets", + GENERATOR_PATH, + ) + assert specification is not None and specification.loader is not None + module = importlib.util.module_from_spec(specification) + specification.loader.exec_module(module) + return module + + + def test_runtime_lock_accepts_distinct_platform_hashes(tmp_path: Path) -> None: + """Preserve every exact artifact hash attached to one pinned version.""" + generator = _load_generator() + first_digest = "a" * 64 + second_digest = "b" * 64 + lock_path = tmp_path / "requirements-ci.txt" + lock_path.write_text( + "tool==1.2.3 \\\\n" + f" --hash=sha256:{first_digest} \\\\n" + f" --hash=sha256:{second_digest}\\n", + encoding="utf-8", + ) + + assert generator._load_runtime_lock(lock_path) == { + "tool": { + "version": "1.2.3", + "sha256": (first_digest, second_digest), + "marker": None, + } + } + + + def test_runtime_lock_rejects_duplicate_artifact_hashes(tmp_path: Path) -> None: + """Reject ambiguous duplicate evidence within one package entry.""" + generator = _load_generator() + digest = "a" * 64 + lock_path = tmp_path / "requirements-ci.txt" + lock_path.write_text( + f"tool==1.2.3 --hash=sha256:{digest} --hash=sha256:{digest}\\n", + encoding="utf-8", + ) + + with pytest.raises(SystemExit, match="duplicate SHA-256"): + generator._load_runtime_lock(lock_path) + + + def test_runtime_manifest_accepts_its_digest_among_platform_hashes( + tmp_path: Path, + ) -> None: + """Bind runtime evidence to one reviewed digest in a portable hash set.""" + generator = _load_generator() + lock_text = LOCK_PATH.read_text(encoding="utf-8") + marker = "anyio==4.14.2 \\\\n --hash=sha256:" + assert lock_text.count(marker) == 1 + prefix, suffix = lock_text.split(marker, 1) + digest, remainder = suffix.split("\\n", 1) + portable_lock = tmp_path / "requirements-ci.txt" + portable_lock.write_text( + prefix + + marker + + digest + + " \\\\n --hash=sha256:" + + ("f" * 64) + + "\\n" + + remainder, + encoding="utf-8", + ) + + generator.validate_runtime_lock(MANIFEST_PATH, portable_lock) + ''', + encoding="utf-8", + ) + + changelog_path = Path("CHANGELOG.md") + changelog = changelog_path.read_text(encoding="utf-8") + anchor = "### Fixed\n" + addition = ( + "- Accept distinct exact SHA-256 artifact hashes for one pinned CI package while " + "requiring every reviewed runtime SBOM digest to be present in that immutable set. " + "This preserves portable hash-locked tooling without weakening release evidence.\n" + ) + if changelog.count(anchor) != 1 or addition in changelog: + raise SystemExit("changelog insertion boundary drifted") + changelog_path.write_text( + changelog.replace(anchor, anchor + addition, 1), + encoding="utf-8", + ) + PY + - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 + with: + python-version: '3.14' + cache: pip + cache-dependency-path: requirements-ci.txt + - name: Install immutable CI toolchain + run: python -m pip install --require-hashes -r requirements-ci.txt + - name: Verify focused and complete GREEN + shell: bash + run: | + set -euo pipefail + ruff check . + coverage erase + coverage run -m pytest -q + coverage report -m + python -m compileall -q src tests scripts + git diff --check + - name: Publish verified repair and remove temporary authority + env: + GH_TOKEN: ${{ github.token }} + shell: bash + run: | + set -euo pipefail + rm .github/workflows/repair-pr-224.yml + git add -A + git config user.name "CWL One-shot Repair" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git commit -m "fix(sbom): accept portable multi-hash lock entries" + git remote set-url origin "https://x-access-token:${GH_TOKEN}@github.com/${GITHUB_REPOSITORY}.git" + git push origin "HEAD:refs/heads/${TARGET_BRANCH}" From 6ebfe145363229f06ee34898c75bd9ede2c6c6ac Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 18 Aug 2026 06:24:14 -0700 Subject: [PATCH 3/3] ci: correct the multi-hash repair boundary --- .github/workflows/repair-pr-224.yml | 282 ++++++++++++---------------- 1 file changed, 115 insertions(+), 167 deletions(-) diff --git a/.github/workflows/repair-pr-224.yml b/.github/workflows/repair-pr-224.yml index 7b39bfd4..3e606270 100644 --- a/.github/workflows/repair-pr-224.yml +++ b/.github/workflows/repair-pr-224.yml @@ -22,7 +22,7 @@ jobs: runs-on: ubuntu-24.04 timeout-minutes: 35 env: - EXPECTED_PARENT_SHA: 0cac905538aa51831157626d026146bd97236db0 + EXPECTED_PARENT_SHA: 006161e9530ae081623f81b6cf4d32bce6a55bcc TARGET_BRANCH: dependabot/pip/ruff-0.16.3 steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 @@ -37,111 +37,58 @@ jobs: test "$(git rev-parse HEAD^)" = "$EXPECTED_PARENT_SHA" test "$(git rev-parse HEAD)" = "$GITHUB_SHA" test "$(git branch --show-current)" = "$TARGET_BRANCH" - - name: Apply minimal multi-hash parser correction and RED regressions + - name: Apply minimal multi-hash parser correction and regressions shell: bash run: | set -euo pipefail python - <<'PY' from pathlib import Path + from textwrap import dedent script_path = Path("scripts/ci/generate_release_sbom.py") script = script_path.read_text(encoding="utf-8") - old_loader = '''def _load_runtime_lock(path: Path) -> dict[str, dict[str, str | None]]: - """Load exact package versions, markers, and hashes from the CI lock.""" - try: - if path.stat().st_size > MAX_MANIFEST_BYTES: - raise SystemExit("runtime lock exceeds the safety bound") - content = path.read_text(encoding="utf-8") - except (OSError, UnicodeError) as error: - raise SystemExit("runtime lock is unreadable") from error - entries: dict[str, dict[str, str | None]] = {} - for raw_line in content.replace("\\\\\\n", " ").splitlines(): - line = raw_line.strip() - if not line or line.startswith("#"): - continue - if line.count("--hash=sha256:") != 1: + + replacements = [ + ( + "def _load_runtime_lock(path: Path) -> dict[str, dict[str, str | None]]:\n", + "def _load_runtime_lock(\n" + " path: Path,\n" + ") -> dict[str, dict[str, str | tuple[str, ...] | None]]:\n", + ), + ( + ' """Load exact package versions, markers, and hashes from the CI lock."""\n', + ' """Load exact package versions, markers, and allowed hashes from the CI lock."""\n', + ), + ( + " entries: dict[str, dict[str, str | None]] = {}\n", + " entries: dict[\n" + " str, dict[str, str | tuple[str, ...] | None]\n" + " ] = {}\n", + ), + ( + ''' if line.count("--hash=sha256:") != 1: raise SystemExit("runtime lock entries require exactly one SHA-256 hash") requirement_text, digest = line.split("--hash=sha256:", 1) digest = digest.strip() if SHA256.fullmatch(digest) is None: raise SystemExit("runtime lock contains a noncanonical SHA-256 hash") - try: - requirement = Requirement(requirement_text.strip()) - except InvalidRequirement as error: - raise SystemExit("runtime lock contains an invalid requirement") from error - if requirement.extras: - raise SystemExit("runtime lock requirements must not use extras") - name = _name(requirement.name) - if name in entries: - raise SystemExit(f"runtime lock duplicates package {name!r}") - specifiers = list(requirement.specifier) - version = ( - specifiers[0].version - if requirement.url is None - and len(specifiers) == 1 - and specifiers[0].operator == "==" - else None - ) - entries[name] = { - "version": version, - "sha256": digest, - "marker": str(requirement.marker) if requirement.marker is not None else None, - } - return entries - ''' - new_loader = '''def _load_runtime_lock( - path: Path, - ) -> dict[str, dict[str, str | tuple[str, ...] | None]]: - """Load exact package versions, markers, and allowed hashes from the CI lock.""" - try: - if path.stat().st_size > MAX_MANIFEST_BYTES: - raise SystemExit("runtime lock exceeds the safety bound") - content = path.read_text(encoding="utf-8") - except (OSError, UnicodeError) as error: - raise SystemExit("runtime lock is unreadable") from error - entries: dict[str, dict[str, str | tuple[str, ...] | None]] = {} - for raw_line in content.replace("\\\\\\n", " ").splitlines(): - line = raw_line.strip() - if not line or line.startswith("#"): - continue - segments = line.split("--hash=sha256:") + ''', + ''' segments = line.split("--hash=sha256:") if len(segments) < 2: - raise SystemExit("runtime lock entries require at least one SHA-256 hash") + raise SystemExit( + "runtime lock entries require at least one SHA-256 hash" + ) requirement_text = segments[0] digests = tuple(segment.strip() for segment in segments[1:]) if any(SHA256.fullmatch(digest) is None for digest in digests): raise SystemExit("runtime lock contains a noncanonical SHA-256 hash") if len(set(digests)) != len(digests): raise SystemExit("runtime lock contains a duplicate SHA-256 hash") - try: - requirement = Requirement(requirement_text.strip()) - except InvalidRequirement as error: - raise SystemExit("runtime lock contains an invalid requirement") from error - if requirement.extras: - raise SystemExit("runtime lock requirements must not use extras") - name = _name(requirement.name) - if name in entries: - raise SystemExit(f"runtime lock duplicates package {name!r}") - specifiers = list(requirement.specifier) - version = ( - specifiers[0].version - if requirement.url is None - and len(specifiers) == 1 - and specifiers[0].operator == "==" - else None - ) - entries[name] = { - "version": version, - "sha256": digests, - "marker": str(requirement.marker) if requirement.marker is not None else None, - } - return entries - ''' - if script.count(old_loader) != 1: - raise SystemExit("runtime-lock loader boundary drifted") - script = script.replace(old_loader, new_loader, 1) - - old_validator = ''' for name, component in components.items(): + ''', + ), + (' "sha256": digest,\n', ' "sha256": digests,\n'), + ( + ''' for name, component in components.items(): locked = lock_entries.get(name) expected = { "version": component["version"], @@ -152,8 +99,8 @@ jobs: raise SystemExit( f"component {name!r} does not match the hash-locked runtime subset" ) - ''' - new_validator = ''' for name, component in components.items(): + ''', + ''' for name, component in components.items(): locked = lock_entries.get(name) expected_marker = _canonical_marker( component["marker"], f"component {name}" @@ -167,102 +114,103 @@ jobs: raise SystemExit( f"component {name!r} does not match the hash-locked runtime subset" ) - ''' - if script.count(old_validator) != 1: - raise SystemExit("runtime-lock validation boundary drifted") - script_path.write_text( - script.replace(old_validator, new_validator, 1), - encoding="utf-8", - ) + ''', + ), + ] + for old, new in replacements: + if script.count(old) != 1: + raise SystemExit(f"runtime-lock repair boundary drifted: {old[:60]!r}") + script = script.replace(old, new, 1) + script_path.write_text(script, encoding="utf-8") Path("tests/test_release_lock_hash_sets.py").write_text( - '''"""Regression contracts for portable multi-artifact hash locks.""" + dedent( + '''\ + """Regression contracts for portable multi-artifact hash locks.""" - from __future__ import annotations + from __future__ import annotations - import importlib.util - from pathlib import Path + import importlib.util + from pathlib import Path - import pytest + import pytest - REPOSITORY_ROOT = Path(__file__).resolve().parents[1] - GENERATOR_PATH = REPOSITORY_ROOT / "scripts" / "ci" / "generate_release_sbom.py" - MANIFEST_PATH = REPOSITORY_ROOT / "scripts" / "ci" / "release_runtime_dependencies.json" - LOCK_PATH = REPOSITORY_ROOT / "requirements-ci.txt" + REPOSITORY_ROOT = Path(__file__).resolve().parents[1] + GENERATOR_PATH = REPOSITORY_ROOT / "scripts" / "ci" / "generate_release_sbom.py" + MANIFEST_PATH = REPOSITORY_ROOT / "scripts" / "ci" / "release_runtime_dependencies.json" + LOCK_PATH = REPOSITORY_ROOT / "requirements-ci.txt" - def _load_generator(): - """Load the repository SBOM generator without packaging it.""" - specification = importlib.util.spec_from_file_location( - "egressweave_release_lock_hash_sets", - GENERATOR_PATH, - ) - assert specification is not None and specification.loader is not None - module = importlib.util.module_from_spec(specification) - specification.loader.exec_module(module) - return module + def _load_generator(): + """Load the repository SBOM generator without packaging it.""" + specification = importlib.util.spec_from_file_location( + "egressweave_release_lock_hash_sets", + GENERATOR_PATH, + ) + assert specification is not None and specification.loader is not None + module = importlib.util.module_from_spec(specification) + specification.loader.exec_module(module) + return module - def test_runtime_lock_accepts_distinct_platform_hashes(tmp_path: Path) -> None: - """Preserve every exact artifact hash attached to one pinned version.""" - generator = _load_generator() - first_digest = "a" * 64 - second_digest = "b" * 64 - lock_path = tmp_path / "requirements-ci.txt" - lock_path.write_text( - "tool==1.2.3 \\\\n" - f" --hash=sha256:{first_digest} \\\\n" - f" --hash=sha256:{second_digest}\\n", - encoding="utf-8", - ) + def test_runtime_lock_accepts_distinct_platform_hashes(tmp_path: Path) -> None: + """Preserve every exact artifact hash attached to one pinned version.""" + generator = _load_generator() + first_digest = "a" * 64 + second_digest = "b" * 64 + lock_path = tmp_path / "requirements-ci.txt" + lock_path.write_text( + "tool==1.2.3 " + f"--hash=sha256:{first_digest} " + f"--hash=sha256:{second_digest}\\n", + encoding="utf-8", + ) - assert generator._load_runtime_lock(lock_path) == { - "tool": { - "version": "1.2.3", - "sha256": (first_digest, second_digest), - "marker": None, - } - } + assert generator._load_runtime_lock(lock_path) == { + "tool": { + "version": "1.2.3", + "sha256": (first_digest, second_digest), + "marker": None, + } + } - def test_runtime_lock_rejects_duplicate_artifact_hashes(tmp_path: Path) -> None: - """Reject ambiguous duplicate evidence within one package entry.""" - generator = _load_generator() - digest = "a" * 64 - lock_path = tmp_path / "requirements-ci.txt" - lock_path.write_text( - f"tool==1.2.3 --hash=sha256:{digest} --hash=sha256:{digest}\\n", - encoding="utf-8", - ) + def test_runtime_lock_rejects_duplicate_artifact_hashes(tmp_path: Path) -> None: + """Reject ambiguous duplicate evidence within one package entry.""" + generator = _load_generator() + digest = "a" * 64 + lock_path = tmp_path / "requirements-ci.txt" + lock_path.write_text( + f"tool==1.2.3 --hash=sha256:{digest} --hash=sha256:{digest}\\n", + encoding="utf-8", + ) - with pytest.raises(SystemExit, match="duplicate SHA-256"): - generator._load_runtime_lock(lock_path) + with pytest.raises(SystemExit, match="duplicate SHA-256"): + generator._load_runtime_lock(lock_path) - def test_runtime_manifest_accepts_its_digest_among_platform_hashes( - tmp_path: Path, - ) -> None: - """Bind runtime evidence to one reviewed digest in a portable hash set.""" - generator = _load_generator() - lock_text = LOCK_PATH.read_text(encoding="utf-8") - marker = "anyio==4.14.2 \\\\n --hash=sha256:" - assert lock_text.count(marker) == 1 - prefix, suffix = lock_text.split(marker, 1) - digest, remainder = suffix.split("\\n", 1) - portable_lock = tmp_path / "requirements-ci.txt" - portable_lock.write_text( - prefix - + marker - + digest - + " \\\\n --hash=sha256:" - + ("f" * 64) - + "\\n" - + remainder, - encoding="utf-8", - ) + def test_runtime_manifest_accepts_its_digest_among_platform_hashes( + tmp_path: Path, + ) -> None: + """Bind runtime evidence to one reviewed digest in a portable hash set.""" + generator = _load_generator() + lock_text = LOCK_PATH.read_text(encoding="utf-8") + needle = ( + "anyio==4.14.2 \\\\n" + " --hash=sha256:" + "9f505dda5ac9f0c8309b5e8bd445a8c2bf7246f3ce950121e45ea15bc41d1494" + ) + replacement = needle + " \\\\n --hash=sha256:" + ("f" * 64) + assert lock_text.count(needle) == 1 + portable_lock = tmp_path / "requirements-ci.txt" + portable_lock.write_text( + lock_text.replace(needle, replacement, 1), + encoding="utf-8", + ) - generator.validate_runtime_lock(MANIFEST_PATH, portable_lock) - ''', + generator.validate_runtime_lock(MANIFEST_PATH, portable_lock) + ''' + ), encoding="utf-8", )