fix: harden v2 provenance and release gates

This commit is contained in:
Agrici Daniel
2026-07-12 02:00:32 +03:00
parent d7c57b00df
commit 34bd1141ea
41 changed files with 13224 additions and 201 deletions
+92 -17
View File
@@ -57,7 +57,9 @@ jobs:
cache: pip
- name: Install core test dependencies
run: python -m pip install -e . -r requirements-dev.txt
run: |
python -m pip install --no-deps -e .
python -m pip install --require-hashes --only-binary=:all: -r requirements-dev.lock
- name: Run core tests
run: python -m pytest tests/core tests/release -v --tb=short
@@ -77,7 +79,11 @@ jobs:
cache: pip
- name: Install runtime and test dependencies
run: python -m pip install -e . -r requirements.txt -r requirements-dev.txt
run: |
python -m pip install --no-deps -e .
python -m pip install --require-hashes --only-binary=:all: -r requirements.lock
python -m pip install --require-hashes --only-binary=:all: -r requirements-dev.lock
python -m pip check
- name: Run full test suite
run: python -m pytest tests -v --tb=short
@@ -85,16 +91,18 @@ jobs:
- name: Audit dependencies for known vulnerabilities
run: |
python -m pip install pip-audit==2.10.0
python -m pip_audit -r requirements.txt --strict
python -m pip_audit -r requirements.lock --strict
python -m pip_audit -r requirements-dev.lock --strict
installer-matrix:
name: Installer tests (${{ matrix.os }})
name: Installer tests (${{ matrix.os }}, Python ${{ matrix.python-version }})
runs-on: ${{ matrix.os }}
timeout-minutes: 15
timeout-minutes: 25
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
os: [ubuntu-latest, macos-15, macos-15-intel, windows-latest]
python-version: ["3.11", "3.12"]
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
@@ -102,15 +110,81 @@ jobs:
- name: Set up Python
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6
with:
python-version: "3.12"
python-version: ${{ matrix.python-version }}
cache: pip
- name: Install installer-test dependencies
run: python -m pip install -e . -r requirements-dev.txt
run: |
python -m pip install --no-deps -e .
python -m pip install --require-hashes --only-binary=:all: -r requirements-dev.lock
- name: Produce and verify native target dependency evidence
run: |
python -c "from pathlib import Path; Path('target-evidence/runtime-wheels').mkdir(parents=True); Path('target-evidence/development-wheels').mkdir(parents=True)"
python -m pip install --dry-run --ignore-installed --report target-evidence/runtime-pip-report.json --require-hashes --only-binary=:all: -r requirements.lock
python -m pip download --dest target-evidence/runtime-wheels --require-hashes --only-binary=:all: -r requirements.lock
python scripts/verify_target_lock.py --profile runtime --pip-report target-evidence/runtime-pip-report.json --wheel-dir target-evidence/runtime-wheels --output target-evidence/runtime-attestation.json
python -m pip install --dry-run --ignore-installed --report target-evidence/development-pip-report.json --require-hashes --only-binary=:all: -r requirements-dev.lock
python -m pip download --dest target-evidence/development-wheels --require-hashes --only-binary=:all: -r requirements-dev.lock
python scripts/verify_target_lock.py --profile development --pip-report target-evidence/development-pip-report.json --wheel-dir target-evidence/development-wheels --output target-evidence/development-attestation.json
- name: Upload exact native target evidence
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: dependency-evidence-${{ matrix.os }}-py${{ matrix.python-version }}
path: |
target-evidence/*-pip-report.json
target-evidence/*-attestation.json
if-no-files-found: error
retention-days: 14
- name: Run installer security and ownership tests
run: python -m pytest tests/scripts/test_installer_security.py -v --tb=short
- name: Exercise real managed install, repeat install, and uninstall (Unix)
if: runner.os != 'Windows'
shell: bash
run: |
probe="$RUNNER_TEMP/claude-ads-managed-install"
for pass in 1 2; do
bash install.sh --target=claude --source=local --skill-dir="$probe/skills" --agent-dir="$probe/agents"
python scripts/verify_managed_install.py --skill-dir "$probe/skills/ads"
done
"$probe/skills/ads/.venv/bin/python" -c "import pathlib,sysconfig; p=pathlib.Path(sysconfig.get_paths()['purelib'])/'unexpected_probe-1.0.dist-info'; p.mkdir(exist_ok=True); (p/'METADATA').write_text('Metadata-Version: 2.1\nName: unexpected-probe\nVersion: 1.0\n')"
if bash install.sh --target=claude --source=local --skill-dir="$probe/skills" --agent-dir="$probe/agents"; then
echo "poisoned managed environment unexpectedly passed repeat install" >&2
exit 1
fi
test ! -e "$probe/skills/ads/managed-runtime-receipt.json"
test ! -e "$probe/skills/ads/.venv"
bash uninstall.sh --target=claude --skill-dir="$probe/skills" --agent-dir="$probe/agents"
test ! -e "$probe/skills/ads/managed-runtime-receipt.json"
test ! -e "$probe/skills/ads/.venv"
- name: Exercise real managed install, repeat install, and uninstall (Windows)
if: runner.os == 'Windows'
shell: pwsh
run: |
$probe = Join-Path $env:RUNNER_TEMP "claude-ads-managed-install"
foreach ($pass in 1, 2) {
./install.ps1 -Target claude -Source local -SkillDir "$probe/skills" -AgentDir "$probe/agents"
python scripts/verify_managed_install.py --skill-dir "$probe/skills/ads"
if ($LASTEXITCODE -ne 0) { throw "managed install verification failed" }
}
& "$probe/skills/ads/.venv/Scripts/python.exe" -c 'import pathlib,sysconfig; p=pathlib.Path(sysconfig.get_paths()["purelib"])/"unexpected_probe-1.0.dist-info"; p.mkdir(exist_ok=True); (p/"METADATA").write_text("Metadata-Version: 2.1\nName: unexpected-probe\nVersion: 1.0\n")'
$failedClosed = $false
try {
./install.ps1 -Target claude -Source local -SkillDir "$probe/skills" -AgentDir "$probe/agents"
} catch {
$failedClosed = $true
}
if (-not $failedClosed) { throw "poisoned managed environment unexpectedly passed repeat install" }
if (Test-Path "$probe/skills/ads/managed-runtime-receipt.json") { throw "stale receipt survived failed repeat install" }
if (Test-Path "$probe/skills/ads/.venv") { throw "failed managed environment survived repeat install" }
./uninstall.ps1 -Target claude -SkillDir "$probe/skills" -AgentDir "$probe/agents"
if (Test-Path "$probe/skills/ads/managed-runtime-receipt.json") { throw "receipt survived uninstall" }
if (Test-Path "$probe/skills/ads/.venv") { throw "managed environment survived uninstall" }
package-smoke:
name: Reproducible package smoke test
runs-on: ubuntu-latest
@@ -126,15 +200,16 @@ jobs:
python-version: "3.12"
- name: Install packaging dependency
run: python -m pip install "pyyaml>=6.0,<7.0"
run: |
python -m pip install --require-hashes --only-binary=:all: -r requirements-dev.lock
- name: Build and verify twice
run: |
python scripts/release.py package --output-dir dist/first
python scripts/release.py package --output-dir dist/second
python scripts/release.py verify --output-dir dist/first
python scripts/release.py verify --output-dir dist/second
cmp dist/first/*.zip dist/second/*.zip
cmp dist/first/release-manifest.json dist/second/release-manifest.json
cmp dist/first/sbom.cdx.json dist/second/sbom.cdx.json
cmp dist/first/SHA256SUMS dist/second/SHA256SUMS
python scripts/release.py package --output-dir /tmp/claude-ads-first
python scripts/release.py package --output-dir /tmp/claude-ads-second
python scripts/release.py verify --output-dir /tmp/claude-ads-first --expected-commit "${{ github.sha }}"
python scripts/release.py verify --output-dir /tmp/claude-ads-second --expected-commit "${{ github.sha }}"
cmp /tmp/claude-ads-first/*.zip /tmp/claude-ads-second/*.zip
cmp /tmp/claude-ads-first/release-manifest.json /tmp/claude-ads-second/release-manifest.json
cmp /tmp/claude-ads-first/sbom.cdx.json /tmp/claude-ads-second/sbom.cdx.json
cmp /tmp/claude-ads-first/SHA256SUMS /tmp/claude-ads-second/SHA256SUMS
+24 -4
View File
@@ -9,8 +9,9 @@ consultants, and in-house performance teams.
Claude Ads turns authorized account exports or reads into source-grounded audits,
plans, experiments, creative workflows, monitoring, and client reports. Versioned
JSON is the system of record; Markdown and HTML are deterministic renderings, and
PDF is rendered from the same HTML through the declared WeasyPrint dependency.
JSON is the system of record; Markdown and HTML are deterministic renderings.
PDF can be rendered from the same HTML when the execution host also satisfies
the separately managed WeasyPrint native-library requirements.
Live account changes are disabled by default and released per platform only after
approval, idempotency, verification, audit, and rollback pass.
@@ -94,6 +95,22 @@ cd claude-ads
bash install.sh --source=local
```
Managed Python wheels install only on the CPython 3.11/3.12 resolution matrix:
glibc 2.17+/manylinux-compatible x86_64, macOS 11+ x86_64/arm64, and Windows
amd64. This matrix attests exact Python wheel selection and installer behavior;
it does not attest browser or PDF feature execution on every tuple. Other
interpreters fail before destination mutation; use `--no-deps`/`-NoDeps` for a
skill-only install. The installer uses the exact union-hash lock and never falls
back to moving requirement ranges.
Browser capture additionally requires an operator-installed Playwright browser
payload on a Playwright-supported host (Windows 11/Server 2019+, macOS 14+,
Debian 12/13, or Ubuntu 22.04/24.04/26.04). PDF rendering additionally requires
the host's WeasyPrint/Pango system libraries. These external payloads are not
Python packages, are not installed by the base lock, and are intentionally not
represented as Python components in the CycloneDX SBOM. Their machine-readable
boundary is `control-plane/manifests/external-runtime-dependencies.json`.
Select another host explicitly:
```bash
@@ -194,8 +211,11 @@ remote CI demote the current state.
Create a virtual environment and run the complete suite:
```bash
python -m venv .venv
.venv/bin/python -m pip install -e . -r requirements.txt -r requirements-dev.txt
python3.12 -m venv .venv
.venv/bin/python -m pip install --no-deps -e .
.venv/bin/python -m pip install --require-hashes --only-binary=:all: -r requirements.lock
.venv/bin/python -m pip install --require-hashes --only-binary=:all: -r requirements-dev.lock
.venv/bin/python -m pip check
.venv/bin/python -m pytest -q
```
+22 -8
View File
@@ -23,18 +23,32 @@ owners and are not relicensed by this repository.
Dependencies are installed from their publishers and retain their own licenses:
- Requests and urllib3: Apache-2.0.
- Requests: Apache-2.0.
- urllib3: MIT.
- Playwright for Python: Apache-2.0.
- Cryptography: Apache-2.0 or BSD-3-Clause.
- Pillow: HPND-style license.
- ReportLab: BSD-style license.
- WeasyPrint: BSD-style license.
- Matplotlib: PSF-based license.
- Pillow: MIT-CMU.
- ReportLab and WeasyPrint code: BSD-3-Clause. ReportLab wheels also include
DarkGarden under GPL-2.0-or-later with a document-embedding exception and
Bitstream Vera font terms.
- Matplotlib: the Matplotlib 1.3+ license (`LicenseRef-Matplotlib-1.3`), plus
bundled font/style/colormap notices including OFL-1.1, Apache-2.0, MIT, and
BSD-style terms.
- PyYAML and pytest: MIT.
The release package must generate an SBOM and license inventory from the versions
actually resolved for that release. This notice is not a substitute for that
machine-generated inventory.
The transitive closure is part of the release inventory, not exhausted by this
summary. In particular, Pyphen code offers GPL-2.0-or-later OR
LGPL-2.1-or-later OR MPL-1.1 alternatives and its dictionaries have
language-specific terms. The dependency inventory preserves the exact path,
SHA-256, text, and artifact assignment for every license/notice-like file found
across all 119 selected wheels. The webencodings 0.5.1 wheel contains no matching
embedded path and is explicitly recorded as documentless rather than assigned
invented notice text.
The release SBOM is generated from the checked-in, publisher-metadata-backed
dependency inventory and validates every declared direct requirement and locked
transitive edge. This notice is not a substitute for that machine-generated
inventory.
## Platform interfaces
+18 -3
View File
@@ -100,6 +100,21 @@ def _number(value: Any, path: str, *, minimum: float | None = None) -> float:
return result
def _integer(value: Any, path: str, *, minimum: int | None = None) -> int:
"""Validate a schema ``integer`` without Python's bool/int coercion.
JSON contract inputs must carry an actual integer token. Floats, including
integral-looking values such as ``100.0``, are rejected deliberately so the
dependency-free validator cannot silently widen the public contract.
"""
if isinstance(value, bool) or not isinstance(value, int):
raise WorkflowContractError(f"{path} must be an integer")
if minimum is not None and value < minimum:
raise WorkflowContractError(f"{path} must be >= {minimum}")
return value
def _list(value: Any, path: str, *, minimum: int = 0) -> list[Any]:
if not isinstance(value, list):
raise WorkflowContractError(f"{path} must be an array")
@@ -175,7 +190,7 @@ def _validate_data_lifecycle_at(value: Any, path: str) -> Mapping[str, Any]:
doc["retention"], f"{path}.retention",
("minimum_seconds", "mode", "delete_after", "purpose", "exception_reason"),
)
_number(retention["minimum_seconds"], f"{path}.retention.minimum_seconds", minimum=0)
_integer(retention["minimum_seconds"], f"{path}.retention.minimum_seconds", minimum=0)
mode = _enum(retention["mode"], f"{path}.retention.mode", {"ephemeral", "operator-defined", "policy-defined", "exception"})
delete_after = retention["delete_after"]
if delete_after is not None:
@@ -405,8 +420,8 @@ def _validate_generation(payload: Mapping[str, Any]) -> None:
_relative_path(output["path"], f"$.outputs[{index}].path")
_sha256(output["sha256"], f"$.outputs[{index}].sha256")
_string(output["media_type"], f"$.outputs[{index}].media_type")
_number(output["width"], f"$.outputs[{index}].width", minimum=1)
_number(output["height"], f"$.outputs[{index}].height", minimum=1)
_integer(output["width"], f"$.outputs[{index}].width", minimum=1)
_integer(output["height"], f"$.outputs[{index}].height", minimum=1)
_string(output["prompt_version"], f"$.outputs[{index}].prompt_version")
_sha256(output["prompt_sha256"], f"$.outputs[{index}].prompt_sha256")
if output["prompt_summary"] != "[redacted: raw prompt is ephemeral and is not persisted]":
+15 -2
View File
@@ -76,13 +76,26 @@ capability loses its test evidence, or required remote CI does not pass.
## Installation and packaging gates
- Install, upgrade, and uninstall are tested on Linux, macOS, and Windows for
every advertised runtime.
- Python-wheel resolution, install, upgrade, and uninstall are tested on the
declared Linux, macOS, and Windows interpreter tuples. This is not a claim of
cross-platform Playwright browser or WeasyPrint PDF feature execution.
- Installation does not silently mutate global Python or execute unverified
network content.
- Uninstall removes only ownership-manifest entries and leaves no unowned files.
- A clean checkout reproducibly builds the release archive, release manifest,
SHA-256 checksums, SBOM, and license notices.
- Runtime and development locks use exact versions plus the union of publisher
wheel hashes for every declared CPython 3.11/3.12 Linux, macOS, and Windows
target. Installers use `--require-hashes --only-binary=:all:`, run `pip check`,
and fail before mutation when the interpreter target is not in that matrix.
- The CycloneDX release SBOM contains the application and runtime closure only;
the separate development lock/inventory remains audit evidence, not an
application dependency.
- Playwright browser payloads and WeasyPrint native/system libraries are modeled
in `control-plane/manifests/external-runtime-dependencies.json`; they remain
outside the Python lock/SBOM. Browser execution requires separate host/payload
attestation. Real PDF rendering is smoke-tested on the Ubuntu full-test job,
not across the full wheel matrix.
- Archive paths are portable and contain no invalid, absolute, or traversal
names.
@@ -0,0 +1,134 @@
{
"components": [
{
"artifact": {
"filename": "cffi-2.1.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl",
"sha256": "aa7a1b53a2a4452ada2d1b5dade9960b2522f1e61293a811a077439e39029565",
"url": "https://files.pythonhosted.org/packages/fb/d2/4398416cd699b35167947c6e22aca52c47e69ad5695073c9f1f2c52e04aa/cffi-2.1.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl"
},
"direct": false,
"name": "cffi",
"purl": "pkg:pypi/cffi@2.1.0",
"version": "2.1.0"
},
{
"artifact": {
"filename": "cryptography-48.0.1-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl",
"sha256": "f0d27a5696721ef7a672b8c810f6aded391058e0b9486e63e6d93baf765da691",
"url": "https://files.pythonhosted.org/packages/9c/87/c85d147b53323c7eb4d850920c8901377323c2a0ff8d79c262d4fee89aa2/cryptography-48.0.1-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl"
},
"direct": true,
"name": "cryptography",
"purl": "pkg:pypi/cryptography@48.0.1",
"version": "48.0.1"
},
{
"artifact": {
"filename": "iniconfig-2.3.0-py3-none-any.whl",
"sha256": "f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12",
"url": "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl"
},
"direct": false,
"name": "iniconfig",
"purl": "pkg:pypi/iniconfig@2.3.0",
"version": "2.3.0"
},
{
"artifact": {
"filename": "packaging-26.2-py3-none-any.whl",
"sha256": "5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e",
"url": "https://files.pythonhosted.org/packages/df/b2/87e62e8c3e2f4b32e5fe99e0b86d576da1312593b39f47d8ceef365e95ed/packaging-26.2-py3-none-any.whl"
},
"direct": false,
"name": "packaging",
"purl": "pkg:pypi/packaging@26.2",
"version": "26.2"
},
{
"artifact": {
"filename": "pluggy-1.6.0-py3-none-any.whl",
"sha256": "e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746",
"url": "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl"
},
"direct": false,
"name": "pluggy",
"purl": "pkg:pypi/pluggy@1.6.0",
"version": "1.6.0"
},
{
"artifact": {
"filename": "pycparser-3.0-py3-none-any.whl",
"sha256": "b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992",
"url": "https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl"
},
"direct": false,
"name": "pycparser",
"purl": "pkg:pypi/pycparser@3.0",
"version": "3.0"
},
{
"artifact": {
"filename": "pygments-2.20.0-py3-none-any.whl",
"sha256": "81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176",
"url": "https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl"
},
"direct": false,
"name": "pygments",
"purl": "pkg:pypi/pygments@2.20.0",
"version": "2.20.0"
},
{
"artifact": {
"filename": "pytest-9.0.3-py3-none-any.whl",
"sha256": "2c5efc453d45394fdd706ade797c0a81091eccd1d6e4bccfcd476e2b8e0ab5d9",
"url": "https://files.pythonhosted.org/packages/d4/24/a372aaf5c9b7208e7112038812994107bc65a84cd00e0354a88c2c77a617/pytest-9.0.3-py3-none-any.whl"
},
"direct": true,
"name": "pytest",
"purl": "pkg:pypi/pytest@9.0.3",
"version": "9.0.3"
},
{
"artifact": {
"filename": "pyyaml-6.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",
"sha256": "b8bb0864c5a28024fac8a632c443c87c5aa6f215c0b126c449ae1a150412f31d",
"url": "https://files.pythonhosted.org/packages/71/60/917329f640924b18ff085ab889a11c763e0b573da888e8404ff486657602/pyyaml-6.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl"
},
"direct": true,
"name": "pyyaml",
"purl": "pkg:pypi/pyyaml@6.0.3",
"version": "6.0.3"
}
],
"environment": {
"abi": "cp311",
"arch": "x86_64",
"implementation": "CPython",
"os": "linux",
"profile": "development",
"python_version": "3.11"
},
"evidence_class": "cross-target-pip-resolution-requiring-native-ci-confirmation",
"evidence_id": "development-linux-cp311",
"normalization_notes": [],
"platform_policy": "Exact lowest-common lock with target-selected PyPI wheels",
"resolved_at": "2026-07-11T00:00:00Z",
"resolver": "pip / PyPI",
"schema_version": "1.0.0",
"source_date_epoch": 1783728000,
"source_environment": {
"implementation_name": "cpython",
"implementation_version": "3.14.4",
"os_name": "posix",
"platform_machine": "x86_64",
"platform_python_implementation": "CPython",
"platform_release": "6.19.14-101.fc44.x86_64",
"platform_system": "Linux",
"platform_version": "#1 SMP PREEMPT_DYNAMIC Fri May 8 18:24:24 UTC 2026",
"python_full_version": "3.14.4",
"python_version": "3.14",
"sys_platform": "linux"
},
"source_pip_version": "26.0.1",
"source_report_sha256": "8710f1c774b006fce5a4c76e85498fa8871c36b578d99304c795b7fc678829d3"
}
@@ -0,0 +1,134 @@
{
"components": [
{
"artifact": {
"filename": "cffi-2.1.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl",
"sha256": "1e9f50d192a3e525b15a75ab5114e442d83d657b7ec29182a991bc9a88fd3a66",
"url": "https://files.pythonhosted.org/packages/62/f2/c9522a81c32132799a1972c39f5c5f8b4c8b9f00488a23feaa6c06f07741/cffi-2.1.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl"
},
"direct": false,
"name": "cffi",
"purl": "pkg:pypi/cffi@2.1.0",
"version": "2.1.0"
},
{
"artifact": {
"filename": "cryptography-48.0.1-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl",
"sha256": "f0d27a5696721ef7a672b8c810f6aded391058e0b9486e63e6d93baf765da691",
"url": "https://files.pythonhosted.org/packages/9c/87/c85d147b53323c7eb4d850920c8901377323c2a0ff8d79c262d4fee89aa2/cryptography-48.0.1-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl"
},
"direct": true,
"name": "cryptography",
"purl": "pkg:pypi/cryptography@48.0.1",
"version": "48.0.1"
},
{
"artifact": {
"filename": "iniconfig-2.3.0-py3-none-any.whl",
"sha256": "f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12",
"url": "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl"
},
"direct": false,
"name": "iniconfig",
"purl": "pkg:pypi/iniconfig@2.3.0",
"version": "2.3.0"
},
{
"artifact": {
"filename": "packaging-26.2-py3-none-any.whl",
"sha256": "5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e",
"url": "https://files.pythonhosted.org/packages/df/b2/87e62e8c3e2f4b32e5fe99e0b86d576da1312593b39f47d8ceef365e95ed/packaging-26.2-py3-none-any.whl"
},
"direct": false,
"name": "packaging",
"purl": "pkg:pypi/packaging@26.2",
"version": "26.2"
},
{
"artifact": {
"filename": "pluggy-1.6.0-py3-none-any.whl",
"sha256": "e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746",
"url": "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl"
},
"direct": false,
"name": "pluggy",
"purl": "pkg:pypi/pluggy@1.6.0",
"version": "1.6.0"
},
{
"artifact": {
"filename": "pycparser-3.0-py3-none-any.whl",
"sha256": "b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992",
"url": "https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl"
},
"direct": false,
"name": "pycparser",
"purl": "pkg:pypi/pycparser@3.0",
"version": "3.0"
},
{
"artifact": {
"filename": "pygments-2.20.0-py3-none-any.whl",
"sha256": "81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176",
"url": "https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl"
},
"direct": false,
"name": "pygments",
"purl": "pkg:pypi/pygments@2.20.0",
"version": "2.20.0"
},
{
"artifact": {
"filename": "pytest-9.0.3-py3-none-any.whl",
"sha256": "2c5efc453d45394fdd706ade797c0a81091eccd1d6e4bccfcd476e2b8e0ab5d9",
"url": "https://files.pythonhosted.org/packages/d4/24/a372aaf5c9b7208e7112038812994107bc65a84cd00e0354a88c2c77a617/pytest-9.0.3-py3-none-any.whl"
},
"direct": true,
"name": "pytest",
"purl": "pkg:pypi/pytest@9.0.3",
"version": "9.0.3"
},
{
"artifact": {
"filename": "pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",
"sha256": "ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc",
"url": "https://files.pythonhosted.org/packages/8b/9d/b3589d3877982d4f2329302ef98a8026e7f4443c765c46cfecc8858c6b4b/pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl"
},
"direct": true,
"name": "pyyaml",
"purl": "pkg:pypi/pyyaml@6.0.3",
"version": "6.0.3"
}
],
"environment": {
"abi": "cp312",
"arch": "x86_64",
"implementation": "CPython",
"os": "linux",
"profile": "development",
"python_version": "3.12"
},
"evidence_class": "cross-target-pip-resolution-requiring-native-ci-confirmation",
"evidence_id": "development-linux-cp312",
"normalization_notes": [],
"platform_policy": "Exact lowest-common lock with target-selected PyPI wheels",
"resolved_at": "2026-07-11T00:00:00Z",
"resolver": "pip / PyPI",
"schema_version": "1.0.0",
"source_date_epoch": 1783728000,
"source_environment": {
"implementation_name": "cpython",
"implementation_version": "3.14.4",
"os_name": "posix",
"platform_machine": "x86_64",
"platform_python_implementation": "CPython",
"platform_release": "6.19.14-101.fc44.x86_64",
"platform_system": "Linux",
"platform_version": "#1 SMP PREEMPT_DYNAMIC Fri May 8 18:24:24 UTC 2026",
"python_full_version": "3.14.4",
"python_version": "3.14",
"sys_platform": "linux"
},
"source_pip_version": "26.0.1",
"source_report_sha256": "26ac8c24db4ff4e100371f33e129018e66452d193bb870bac438cc233d609ebf"
}
@@ -0,0 +1,134 @@
{
"components": [
{
"artifact": {
"filename": "cffi-2.1.0-cp311-cp311-macosx_11_0_arm64.whl",
"sha256": "f5bce581e6b8c235e566a14768a943b172ada3ed73537bb0c0be1edee312d4e7",
"url": "https://files.pythonhosted.org/packages/ea/dd/e3b0baa2d3d6a857ac72b7efbf18e32e487c9cdafcc13049ad765495b15e/cffi-2.1.0-cp311-cp311-macosx_11_0_arm64.whl"
},
"direct": false,
"name": "cffi",
"purl": "pkg:pypi/cffi@2.1.0",
"version": "2.1.0"
},
{
"artifact": {
"filename": "cryptography-48.0.1-cp311-abi3-macosx_10_9_universal2.whl",
"sha256": "3e4a1a3232eef2e6c732827d5722db29a0cc8b27af2a4d865b094cf954be9ca1",
"url": "https://files.pythonhosted.org/packages/1b/bc/ee4137cbbe105652c0ee4252792b78fc8e7afa4b8e61d9d5dc05a7f45731/cryptography-48.0.1-cp311-abi3-macosx_10_9_universal2.whl"
},
"direct": true,
"name": "cryptography",
"purl": "pkg:pypi/cryptography@48.0.1",
"version": "48.0.1"
},
{
"artifact": {
"filename": "iniconfig-2.3.0-py3-none-any.whl",
"sha256": "f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12",
"url": "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl"
},
"direct": false,
"name": "iniconfig",
"purl": "pkg:pypi/iniconfig@2.3.0",
"version": "2.3.0"
},
{
"artifact": {
"filename": "packaging-26.2-py3-none-any.whl",
"sha256": "5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e",
"url": "https://files.pythonhosted.org/packages/df/b2/87e62e8c3e2f4b32e5fe99e0b86d576da1312593b39f47d8ceef365e95ed/packaging-26.2-py3-none-any.whl"
},
"direct": false,
"name": "packaging",
"purl": "pkg:pypi/packaging@26.2",
"version": "26.2"
},
{
"artifact": {
"filename": "pluggy-1.6.0-py3-none-any.whl",
"sha256": "e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746",
"url": "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl"
},
"direct": false,
"name": "pluggy",
"purl": "pkg:pypi/pluggy@1.6.0",
"version": "1.6.0"
},
{
"artifact": {
"filename": "pycparser-3.0-py3-none-any.whl",
"sha256": "b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992",
"url": "https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl"
},
"direct": false,
"name": "pycparser",
"purl": "pkg:pypi/pycparser@3.0",
"version": "3.0"
},
{
"artifact": {
"filename": "pygments-2.20.0-py3-none-any.whl",
"sha256": "81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176",
"url": "https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl"
},
"direct": false,
"name": "pygments",
"purl": "pkg:pypi/pygments@2.20.0",
"version": "2.20.0"
},
{
"artifact": {
"filename": "pytest-9.0.3-py3-none-any.whl",
"sha256": "2c5efc453d45394fdd706ade797c0a81091eccd1d6e4bccfcd476e2b8e0ab5d9",
"url": "https://files.pythonhosted.org/packages/d4/24/a372aaf5c9b7208e7112038812994107bc65a84cd00e0354a88c2c77a617/pytest-9.0.3-py3-none-any.whl"
},
"direct": true,
"name": "pytest",
"purl": "pkg:pypi/pytest@9.0.3",
"version": "9.0.3"
},
{
"artifact": {
"filename": "pyyaml-6.0.3-cp311-cp311-macosx_11_0_arm64.whl",
"sha256": "652cb6edd41e718550aad172851962662ff2681490a8a711af6a4d288dd96824",
"url": "https://files.pythonhosted.org/packages/16/19/13de8e4377ed53079ee996e1ab0a9c33ec2faf808a4647b7b4c0d46dd239/pyyaml-6.0.3-cp311-cp311-macosx_11_0_arm64.whl"
},
"direct": true,
"name": "pyyaml",
"purl": "pkg:pypi/pyyaml@6.0.3",
"version": "6.0.3"
}
],
"environment": {
"abi": "cp311",
"arch": "arm64",
"implementation": "CPython",
"os": "macos",
"profile": "development",
"python_version": "3.11"
},
"evidence_class": "cross-target-pip-resolution-requiring-native-ci-confirmation",
"evidence_id": "development-macos-arm-cp311",
"normalization_notes": [],
"platform_policy": "Exact lowest-common lock with target-selected PyPI wheels",
"resolved_at": "2026-07-11T00:00:00Z",
"resolver": "pip / PyPI",
"schema_version": "1.0.0",
"source_date_epoch": 1783728000,
"source_environment": {
"implementation_name": "cpython",
"implementation_version": "3.14.4",
"os_name": "posix",
"platform_machine": "x86_64",
"platform_python_implementation": "CPython",
"platform_release": "6.19.14-101.fc44.x86_64",
"platform_system": "Linux",
"platform_version": "#1 SMP PREEMPT_DYNAMIC Fri May 8 18:24:24 UTC 2026",
"python_full_version": "3.14.4",
"python_version": "3.14",
"sys_platform": "linux"
},
"source_pip_version": "26.0.1",
"source_report_sha256": "6181b9169a4618021b85df8f2f2c48b3dde2fc224d787fa18a5652ad6108d03d"
}
@@ -0,0 +1,134 @@
{
"components": [
{
"artifact": {
"filename": "cffi-2.1.0-cp312-cp312-macosx_11_0_arm64.whl",
"sha256": "78474632761faa0fb96f30b1c928c84ebcf68713cbb80d15bab09dfe61640fde",
"url": "https://files.pythonhosted.org/packages/4b/92/e7bb136ad6b5352603732cf907ef862ca103f20f2031c1735a46300c20c9/cffi-2.1.0-cp312-cp312-macosx_11_0_arm64.whl"
},
"direct": false,
"name": "cffi",
"purl": "pkg:pypi/cffi@2.1.0",
"version": "2.1.0"
},
{
"artifact": {
"filename": "cryptography-48.0.1-cp311-abi3-macosx_10_9_universal2.whl",
"sha256": "3e4a1a3232eef2e6c732827d5722db29a0cc8b27af2a4d865b094cf954be9ca1",
"url": "https://files.pythonhosted.org/packages/1b/bc/ee4137cbbe105652c0ee4252792b78fc8e7afa4b8e61d9d5dc05a7f45731/cryptography-48.0.1-cp311-abi3-macosx_10_9_universal2.whl"
},
"direct": true,
"name": "cryptography",
"purl": "pkg:pypi/cryptography@48.0.1",
"version": "48.0.1"
},
{
"artifact": {
"filename": "iniconfig-2.3.0-py3-none-any.whl",
"sha256": "f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12",
"url": "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl"
},
"direct": false,
"name": "iniconfig",
"purl": "pkg:pypi/iniconfig@2.3.0",
"version": "2.3.0"
},
{
"artifact": {
"filename": "packaging-26.2-py3-none-any.whl",
"sha256": "5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e",
"url": "https://files.pythonhosted.org/packages/df/b2/87e62e8c3e2f4b32e5fe99e0b86d576da1312593b39f47d8ceef365e95ed/packaging-26.2-py3-none-any.whl"
},
"direct": false,
"name": "packaging",
"purl": "pkg:pypi/packaging@26.2",
"version": "26.2"
},
{
"artifact": {
"filename": "pluggy-1.6.0-py3-none-any.whl",
"sha256": "e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746",
"url": "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl"
},
"direct": false,
"name": "pluggy",
"purl": "pkg:pypi/pluggy@1.6.0",
"version": "1.6.0"
},
{
"artifact": {
"filename": "pycparser-3.0-py3-none-any.whl",
"sha256": "b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992",
"url": "https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl"
},
"direct": false,
"name": "pycparser",
"purl": "pkg:pypi/pycparser@3.0",
"version": "3.0"
},
{
"artifact": {
"filename": "pygments-2.20.0-py3-none-any.whl",
"sha256": "81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176",
"url": "https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl"
},
"direct": false,
"name": "pygments",
"purl": "pkg:pypi/pygments@2.20.0",
"version": "2.20.0"
},
{
"artifact": {
"filename": "pytest-9.0.3-py3-none-any.whl",
"sha256": "2c5efc453d45394fdd706ade797c0a81091eccd1d6e4bccfcd476e2b8e0ab5d9",
"url": "https://files.pythonhosted.org/packages/d4/24/a372aaf5c9b7208e7112038812994107bc65a84cd00e0354a88c2c77a617/pytest-9.0.3-py3-none-any.whl"
},
"direct": true,
"name": "pytest",
"purl": "pkg:pypi/pytest@9.0.3",
"version": "9.0.3"
},
{
"artifact": {
"filename": "pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl",
"sha256": "fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0",
"url": "https://files.pythonhosted.org/packages/89/a0/6cf41a19a1f2f3feab0e9c0b74134aa2ce6849093d5517a0c550fe37a648/pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl"
},
"direct": true,
"name": "pyyaml",
"purl": "pkg:pypi/pyyaml@6.0.3",
"version": "6.0.3"
}
],
"environment": {
"abi": "cp312",
"arch": "arm64",
"implementation": "CPython",
"os": "macos",
"profile": "development",
"python_version": "3.12"
},
"evidence_class": "cross-target-pip-resolution-requiring-native-ci-confirmation",
"evidence_id": "development-macos-arm-cp312",
"normalization_notes": [],
"platform_policy": "Exact lowest-common lock with target-selected PyPI wheels",
"resolved_at": "2026-07-11T00:00:00Z",
"resolver": "pip / PyPI",
"schema_version": "1.0.0",
"source_date_epoch": 1783728000,
"source_environment": {
"implementation_name": "cpython",
"implementation_version": "3.14.4",
"os_name": "posix",
"platform_machine": "x86_64",
"platform_python_implementation": "CPython",
"platform_release": "6.19.14-101.fc44.x86_64",
"platform_system": "Linux",
"platform_version": "#1 SMP PREEMPT_DYNAMIC Fri May 8 18:24:24 UTC 2026",
"python_full_version": "3.14.4",
"python_version": "3.14",
"sys_platform": "linux"
},
"source_pip_version": "26.0.1",
"source_report_sha256": "aa522645b30a423c27027d0a7c6a3b9f4aaf4558f1c50700a5c7bc94dc6cf00d"
}
@@ -0,0 +1,134 @@
{
"components": [
{
"artifact": {
"filename": "cffi-2.1.0-cp311-cp311-macosx_10_15_x86_64.whl",
"sha256": "02cb7ff33ded4f1532476731f89ede53e2e488a8e6205515a82144246ffa7dcc",
"url": "https://files.pythonhosted.org/packages/d3/67/85c89a59ba36a671e79638f44d466749f08179266a57e4f2ffdf92174072/cffi-2.1.0-cp311-cp311-macosx_10_15_x86_64.whl"
},
"direct": false,
"name": "cffi",
"purl": "pkg:pypi/cffi@2.1.0",
"version": "2.1.0"
},
{
"artifact": {
"filename": "cryptography-48.0.1-cp311-abi3-macosx_10_9_universal2.whl",
"sha256": "3e4a1a3232eef2e6c732827d5722db29a0cc8b27af2a4d865b094cf954be9ca1",
"url": "https://files.pythonhosted.org/packages/1b/bc/ee4137cbbe105652c0ee4252792b78fc8e7afa4b8e61d9d5dc05a7f45731/cryptography-48.0.1-cp311-abi3-macosx_10_9_universal2.whl"
},
"direct": true,
"name": "cryptography",
"purl": "pkg:pypi/cryptography@48.0.1",
"version": "48.0.1"
},
{
"artifact": {
"filename": "iniconfig-2.3.0-py3-none-any.whl",
"sha256": "f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12",
"url": "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl"
},
"direct": false,
"name": "iniconfig",
"purl": "pkg:pypi/iniconfig@2.3.0",
"version": "2.3.0"
},
{
"artifact": {
"filename": "packaging-26.2-py3-none-any.whl",
"sha256": "5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e",
"url": "https://files.pythonhosted.org/packages/df/b2/87e62e8c3e2f4b32e5fe99e0b86d576da1312593b39f47d8ceef365e95ed/packaging-26.2-py3-none-any.whl"
},
"direct": false,
"name": "packaging",
"purl": "pkg:pypi/packaging@26.2",
"version": "26.2"
},
{
"artifact": {
"filename": "pluggy-1.6.0-py3-none-any.whl",
"sha256": "e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746",
"url": "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl"
},
"direct": false,
"name": "pluggy",
"purl": "pkg:pypi/pluggy@1.6.0",
"version": "1.6.0"
},
{
"artifact": {
"filename": "pycparser-3.0-py3-none-any.whl",
"sha256": "b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992",
"url": "https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl"
},
"direct": false,
"name": "pycparser",
"purl": "pkg:pypi/pycparser@3.0",
"version": "3.0"
},
{
"artifact": {
"filename": "pygments-2.20.0-py3-none-any.whl",
"sha256": "81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176",
"url": "https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl"
},
"direct": false,
"name": "pygments",
"purl": "pkg:pypi/pygments@2.20.0",
"version": "2.20.0"
},
{
"artifact": {
"filename": "pytest-9.0.3-py3-none-any.whl",
"sha256": "2c5efc453d45394fdd706ade797c0a81091eccd1d6e4bccfcd476e2b8e0ab5d9",
"url": "https://files.pythonhosted.org/packages/d4/24/a372aaf5c9b7208e7112038812994107bc65a84cd00e0354a88c2c77a617/pytest-9.0.3-py3-none-any.whl"
},
"direct": true,
"name": "pytest",
"purl": "pkg:pypi/pytest@9.0.3",
"version": "9.0.3"
},
{
"artifact": {
"filename": "pyyaml-6.0.3-cp311-cp311-macosx_10_13_x86_64.whl",
"sha256": "44edc647873928551a01e7a563d7452ccdebee747728c1080d881d68af7b997e",
"url": "https://files.pythonhosted.org/packages/6d/16/a95b6757765b7b031c9374925bb718d55e0a9ba8a1b6a12d25962ea44347/pyyaml-6.0.3-cp311-cp311-macosx_10_13_x86_64.whl"
},
"direct": true,
"name": "pyyaml",
"purl": "pkg:pypi/pyyaml@6.0.3",
"version": "6.0.3"
}
],
"environment": {
"abi": "cp311",
"arch": "x86_64",
"implementation": "CPython",
"os": "macos",
"profile": "development",
"python_version": "3.11"
},
"evidence_class": "cross-target-pip-resolution-requiring-native-ci-confirmation",
"evidence_id": "development-macos-x86-cp311",
"normalization_notes": [],
"platform_policy": "Exact lowest-common lock with target-selected PyPI wheels",
"resolved_at": "2026-07-11T00:00:00Z",
"resolver": "pip / PyPI",
"schema_version": "1.0.0",
"source_date_epoch": 1783728000,
"source_environment": {
"implementation_name": "cpython",
"implementation_version": "3.14.4",
"os_name": "posix",
"platform_machine": "x86_64",
"platform_python_implementation": "CPython",
"platform_release": "6.19.14-101.fc44.x86_64",
"platform_system": "Linux",
"platform_version": "#1 SMP PREEMPT_DYNAMIC Fri May 8 18:24:24 UTC 2026",
"python_full_version": "3.14.4",
"python_version": "3.14",
"sys_platform": "linux"
},
"source_pip_version": "26.0.1",
"source_report_sha256": "41d99080b681d00aff4895baa509c6688100ecbc00f26b3274e7500bb336e17e"
}
@@ -0,0 +1,134 @@
{
"components": [
{
"artifact": {
"filename": "cffi-2.1.0-cp312-cp312-macosx_10_15_x86_64.whl",
"sha256": "df2b82571a1b30f58a87bf4e5a9e78d2b1eff6c6ce8fd3aa3757221f93f0863f",
"url": "https://files.pythonhosted.org/packages/1e/85/990925db5df586ec90beb97529c853497e7f85ba0234830447faf41c3057/cffi-2.1.0-cp312-cp312-macosx_10_15_x86_64.whl"
},
"direct": false,
"name": "cffi",
"purl": "pkg:pypi/cffi@2.1.0",
"version": "2.1.0"
},
{
"artifact": {
"filename": "cryptography-48.0.1-cp311-abi3-macosx_10_9_universal2.whl",
"sha256": "3e4a1a3232eef2e6c732827d5722db29a0cc8b27af2a4d865b094cf954be9ca1",
"url": "https://files.pythonhosted.org/packages/1b/bc/ee4137cbbe105652c0ee4252792b78fc8e7afa4b8e61d9d5dc05a7f45731/cryptography-48.0.1-cp311-abi3-macosx_10_9_universal2.whl"
},
"direct": true,
"name": "cryptography",
"purl": "pkg:pypi/cryptography@48.0.1",
"version": "48.0.1"
},
{
"artifact": {
"filename": "iniconfig-2.3.0-py3-none-any.whl",
"sha256": "f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12",
"url": "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl"
},
"direct": false,
"name": "iniconfig",
"purl": "pkg:pypi/iniconfig@2.3.0",
"version": "2.3.0"
},
{
"artifact": {
"filename": "packaging-26.2-py3-none-any.whl",
"sha256": "5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e",
"url": "https://files.pythonhosted.org/packages/df/b2/87e62e8c3e2f4b32e5fe99e0b86d576da1312593b39f47d8ceef365e95ed/packaging-26.2-py3-none-any.whl"
},
"direct": false,
"name": "packaging",
"purl": "pkg:pypi/packaging@26.2",
"version": "26.2"
},
{
"artifact": {
"filename": "pluggy-1.6.0-py3-none-any.whl",
"sha256": "e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746",
"url": "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl"
},
"direct": false,
"name": "pluggy",
"purl": "pkg:pypi/pluggy@1.6.0",
"version": "1.6.0"
},
{
"artifact": {
"filename": "pycparser-3.0-py3-none-any.whl",
"sha256": "b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992",
"url": "https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl"
},
"direct": false,
"name": "pycparser",
"purl": "pkg:pypi/pycparser@3.0",
"version": "3.0"
},
{
"artifact": {
"filename": "pygments-2.20.0-py3-none-any.whl",
"sha256": "81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176",
"url": "https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl"
},
"direct": false,
"name": "pygments",
"purl": "pkg:pypi/pygments@2.20.0",
"version": "2.20.0"
},
{
"artifact": {
"filename": "pytest-9.0.3-py3-none-any.whl",
"sha256": "2c5efc453d45394fdd706ade797c0a81091eccd1d6e4bccfcd476e2b8e0ab5d9",
"url": "https://files.pythonhosted.org/packages/d4/24/a372aaf5c9b7208e7112038812994107bc65a84cd00e0354a88c2c77a617/pytest-9.0.3-py3-none-any.whl"
},
"direct": true,
"name": "pytest",
"purl": "pkg:pypi/pytest@9.0.3",
"version": "9.0.3"
},
{
"artifact": {
"filename": "pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl",
"sha256": "7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196",
"url": "https://files.pythonhosted.org/packages/d1/33/422b98d2195232ca1826284a76852ad5a86fe23e31b009c9886b2d0fb8b2/pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl"
},
"direct": true,
"name": "pyyaml",
"purl": "pkg:pypi/pyyaml@6.0.3",
"version": "6.0.3"
}
],
"environment": {
"abi": "cp312",
"arch": "x86_64",
"implementation": "CPython",
"os": "macos",
"profile": "development",
"python_version": "3.12"
},
"evidence_class": "cross-target-pip-resolution-requiring-native-ci-confirmation",
"evidence_id": "development-macos-x86-cp312",
"normalization_notes": [],
"platform_policy": "Exact lowest-common lock with target-selected PyPI wheels",
"resolved_at": "2026-07-11T00:00:00Z",
"resolver": "pip / PyPI",
"schema_version": "1.0.0",
"source_date_epoch": 1783728000,
"source_environment": {
"implementation_name": "cpython",
"implementation_version": "3.14.4",
"os_name": "posix",
"platform_machine": "x86_64",
"platform_python_implementation": "CPython",
"platform_release": "6.19.14-101.fc44.x86_64",
"platform_system": "Linux",
"platform_version": "#1 SMP PREEMPT_DYNAMIC Fri May 8 18:24:24 UTC 2026",
"python_full_version": "3.14.4",
"python_version": "3.14",
"sys_platform": "linux"
},
"source_pip_version": "26.0.1",
"source_report_sha256": "d90304611d6da50ff27c6eb2339d67cfe5fcf58b3adb11789ec8ce4e57450de4"
}
@@ -0,0 +1,147 @@
{
"components": [
{
"artifact": {
"filename": "cffi-2.1.0-cp311-cp311-win_amd64.whl",
"sha256": "4f26194e3d95e06501b942642855aed4f953d55e95d7d01b7c4483db3ecff458",
"url": "https://files.pythonhosted.org/packages/f9/c8/6c2de1d55cf35ef8b92885d5ef280790f0fb9634d87ea1cc315176aecd61/cffi-2.1.0-cp311-cp311-win_amd64.whl"
},
"direct": false,
"name": "cffi",
"purl": "pkg:pypi/cffi@2.1.0",
"version": "2.1.0"
},
{
"artifact": {
"filename": "colorama-0.4.6-py2.py3-none-any.whl",
"sha256": "4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6",
"url": "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl"
},
"direct": false,
"name": "colorama",
"purl": "pkg:pypi/colorama@0.4.6",
"version": "0.4.6"
},
{
"artifact": {
"filename": "cryptography-48.0.1-cp311-abi3-win_amd64.whl",
"sha256": "39489bfca54c7a1f6b297efcd8bc608ab92d16c4ca631b0cad4da46724588b24",
"url": "https://files.pythonhosted.org/packages/c5/69/0572c77dbace6fef72f33755bd52ea399c71367250d366237f8691826b9e/cryptography-48.0.1-cp311-abi3-win_amd64.whl"
},
"direct": true,
"name": "cryptography",
"purl": "pkg:pypi/cryptography@48.0.1",
"version": "48.0.1"
},
{
"artifact": {
"filename": "iniconfig-2.3.0-py3-none-any.whl",
"sha256": "f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12",
"url": "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl"
},
"direct": false,
"name": "iniconfig",
"purl": "pkg:pypi/iniconfig@2.3.0",
"version": "2.3.0"
},
{
"artifact": {
"filename": "packaging-26.2-py3-none-any.whl",
"sha256": "5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e",
"url": "https://files.pythonhosted.org/packages/df/b2/87e62e8c3e2f4b32e5fe99e0b86d576da1312593b39f47d8ceef365e95ed/packaging-26.2-py3-none-any.whl"
},
"direct": false,
"name": "packaging",
"purl": "pkg:pypi/packaging@26.2",
"version": "26.2"
},
{
"artifact": {
"filename": "pluggy-1.6.0-py3-none-any.whl",
"sha256": "e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746",
"url": "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl"
},
"direct": false,
"name": "pluggy",
"purl": "pkg:pypi/pluggy@1.6.0",
"version": "1.6.0"
},
{
"artifact": {
"filename": "pycparser-3.0-py3-none-any.whl",
"sha256": "b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992",
"url": "https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl"
},
"direct": false,
"name": "pycparser",
"purl": "pkg:pypi/pycparser@3.0",
"version": "3.0"
},
{
"artifact": {
"filename": "pygments-2.20.0-py3-none-any.whl",
"sha256": "81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176",
"url": "https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl"
},
"direct": false,
"name": "pygments",
"purl": "pkg:pypi/pygments@2.20.0",
"version": "2.20.0"
},
{
"artifact": {
"filename": "pytest-9.0.3-py3-none-any.whl",
"sha256": "2c5efc453d45394fdd706ade797c0a81091eccd1d6e4bccfcd476e2b8e0ab5d9",
"url": "https://files.pythonhosted.org/packages/d4/24/a372aaf5c9b7208e7112038812994107bc65a84cd00e0354a88c2c77a617/pytest-9.0.3-py3-none-any.whl"
},
"direct": true,
"name": "pytest",
"purl": "pkg:pypi/pytest@9.0.3",
"version": "9.0.3"
},
{
"artifact": {
"filename": "pyyaml-6.0.3-cp311-cp311-win_amd64.whl",
"sha256": "9f3bfb4965eb874431221a3ff3fdcddc7e74e3b07799e0e84ca4a0f867d449bf",
"url": "https://files.pythonhosted.org/packages/da/e3/ea007450a105ae919a72393cb06f122f288ef60bba2dc64b26e2646fa315/pyyaml-6.0.3-cp311-cp311-win_amd64.whl"
},
"direct": true,
"name": "pyyaml",
"purl": "pkg:pypi/pyyaml@6.0.3",
"version": "6.0.3"
}
],
"environment": {
"abi": "cp311",
"arch": "amd64",
"implementation": "CPython",
"os": "windows",
"profile": "development",
"python_version": "3.11"
},
"evidence_class": "cross-target-pip-resolution-requiring-native-ci-confirmation",
"evidence_id": "development-windows-cp311",
"normalization_notes": [
"The foreign-platform source report did not evaluate the win32 colorama marker; the exact lock marker supplies that occurrence and native CI must confirm it."
],
"platform_policy": "Exact lowest-common lock with target-selected PyPI wheels",
"resolved_at": "2026-07-11T00:00:00Z",
"resolver": "pip / PyPI",
"schema_version": "1.0.0",
"source_date_epoch": 1783728000,
"source_environment": {
"implementation_name": "cpython",
"implementation_version": "3.14.4",
"os_name": "posix",
"platform_machine": "x86_64",
"platform_python_implementation": "CPython",
"platform_release": "6.19.14-101.fc44.x86_64",
"platform_system": "Linux",
"platform_version": "#1 SMP PREEMPT_DYNAMIC Fri May 8 18:24:24 UTC 2026",
"python_full_version": "3.14.4",
"python_version": "3.14",
"sys_platform": "linux"
},
"source_pip_version": "26.0.1",
"source_report_sha256": "6ecb2325ac69fb8c4753b676c9dfaecf0c085badbba3296c41c492aed1abbd5a"
}
@@ -0,0 +1,147 @@
{
"components": [
{
"artifact": {
"filename": "cffi-2.1.0-cp312-cp312-win_amd64.whl",
"sha256": "c97f080ea627e2863524c5af3836e2270b5f5dfff1f104392b959f8df0c5d384",
"url": "https://files.pythonhosted.org/packages/a8/eb/f636456ff21a83fc13c032b58cc5dde061691546ac79efa284b2989b7982/cffi-2.1.0-cp312-cp312-win_amd64.whl"
},
"direct": false,
"name": "cffi",
"purl": "pkg:pypi/cffi@2.1.0",
"version": "2.1.0"
},
{
"artifact": {
"filename": "colorama-0.4.6-py2.py3-none-any.whl",
"sha256": "4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6",
"url": "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl"
},
"direct": false,
"name": "colorama",
"purl": "pkg:pypi/colorama@0.4.6",
"version": "0.4.6"
},
{
"artifact": {
"filename": "cryptography-48.0.1-cp311-abi3-win_amd64.whl",
"sha256": "39489bfca54c7a1f6b297efcd8bc608ab92d16c4ca631b0cad4da46724588b24",
"url": "https://files.pythonhosted.org/packages/c5/69/0572c77dbace6fef72f33755bd52ea399c71367250d366237f8691826b9e/cryptography-48.0.1-cp311-abi3-win_amd64.whl"
},
"direct": true,
"name": "cryptography",
"purl": "pkg:pypi/cryptography@48.0.1",
"version": "48.0.1"
},
{
"artifact": {
"filename": "iniconfig-2.3.0-py3-none-any.whl",
"sha256": "f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12",
"url": "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl"
},
"direct": false,
"name": "iniconfig",
"purl": "pkg:pypi/iniconfig@2.3.0",
"version": "2.3.0"
},
{
"artifact": {
"filename": "packaging-26.2-py3-none-any.whl",
"sha256": "5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e",
"url": "https://files.pythonhosted.org/packages/df/b2/87e62e8c3e2f4b32e5fe99e0b86d576da1312593b39f47d8ceef365e95ed/packaging-26.2-py3-none-any.whl"
},
"direct": false,
"name": "packaging",
"purl": "pkg:pypi/packaging@26.2",
"version": "26.2"
},
{
"artifact": {
"filename": "pluggy-1.6.0-py3-none-any.whl",
"sha256": "e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746",
"url": "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl"
},
"direct": false,
"name": "pluggy",
"purl": "pkg:pypi/pluggy@1.6.0",
"version": "1.6.0"
},
{
"artifact": {
"filename": "pycparser-3.0-py3-none-any.whl",
"sha256": "b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992",
"url": "https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl"
},
"direct": false,
"name": "pycparser",
"purl": "pkg:pypi/pycparser@3.0",
"version": "3.0"
},
{
"artifact": {
"filename": "pygments-2.20.0-py3-none-any.whl",
"sha256": "81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176",
"url": "https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl"
},
"direct": false,
"name": "pygments",
"purl": "pkg:pypi/pygments@2.20.0",
"version": "2.20.0"
},
{
"artifact": {
"filename": "pytest-9.0.3-py3-none-any.whl",
"sha256": "2c5efc453d45394fdd706ade797c0a81091eccd1d6e4bccfcd476e2b8e0ab5d9",
"url": "https://files.pythonhosted.org/packages/d4/24/a372aaf5c9b7208e7112038812994107bc65a84cd00e0354a88c2c77a617/pytest-9.0.3-py3-none-any.whl"
},
"direct": true,
"name": "pytest",
"purl": "pkg:pypi/pytest@9.0.3",
"version": "9.0.3"
},
{
"artifact": {
"filename": "pyyaml-6.0.3-cp312-cp312-win_amd64.whl",
"sha256": "5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b",
"url": "https://files.pythonhosted.org/packages/86/bf/899e81e4cce32febab4fb42bb97dcdf66bc135272882d1987881a4b519e9/pyyaml-6.0.3-cp312-cp312-win_amd64.whl"
},
"direct": true,
"name": "pyyaml",
"purl": "pkg:pypi/pyyaml@6.0.3",
"version": "6.0.3"
}
],
"environment": {
"abi": "cp312",
"arch": "amd64",
"implementation": "CPython",
"os": "windows",
"profile": "development",
"python_version": "3.12"
},
"evidence_class": "cross-target-pip-resolution-requiring-native-ci-confirmation",
"evidence_id": "development-windows-cp312",
"normalization_notes": [
"The foreign-platform source report did not evaluate the win32 colorama marker; the exact lock marker supplies that occurrence and native CI must confirm it."
],
"platform_policy": "Exact lowest-common lock with target-selected PyPI wheels",
"resolved_at": "2026-07-11T00:00:00Z",
"resolver": "pip / PyPI",
"schema_version": "1.0.0",
"source_date_epoch": 1783728000,
"source_environment": {
"implementation_name": "cpython",
"implementation_version": "3.14.4",
"os_name": "posix",
"platform_machine": "x86_64",
"platform_python_implementation": "CPython",
"platform_release": "6.19.14-101.fc44.x86_64",
"platform_system": "Linux",
"platform_version": "#1 SMP PREEMPT_DYNAMIC Fri May 8 18:24:24 UTC 2026",
"python_full_version": "3.14.4",
"python_version": "3.14",
"sys_platform": "linux"
},
"source_pip_version": "26.0.1",
"source_report_sha256": "887a2658aabee1933e970cbfc639f054ef787fc5a4b52c00381433ba686f104f"
}
@@ -0,0 +1,398 @@
{
"components": [
{
"artifact": {
"filename": "brotli-1.2.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl",
"sha256": "40d918bce2b427a0c4ba189df7a006ac0c7277c180aee4617d99e9ccaaf59e6a",
"url": "https://files.pythonhosted.org/packages/89/5a/7071a621eb2d052d64efd5da2ef55ecdac7c3b0c6e4f9d519e9c66d987ef/brotli-1.2.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl"
},
"direct": false,
"name": "brotli",
"purl": "pkg:pypi/brotli@1.2.0",
"version": "1.2.0"
},
{
"artifact": {
"filename": "certifi-2026.6.17-py3-none-any.whl",
"sha256": "2227dcbaafe0d2f59279d1762ddddc37783ed4354594f194ffc31d20f41fc3db",
"url": "https://files.pythonhosted.org/packages/ef/2f/c5464532e965badff2f4c4c1a3a83f5697f0d7c407ed0cda44aaa99bb451/certifi-2026.6.17-py3-none-any.whl"
},
"direct": false,
"name": "certifi",
"purl": "pkg:pypi/certifi@2026.6.17",
"version": "2026.6.17"
},
{
"artifact": {
"filename": "cffi-2.1.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl",
"sha256": "aa7a1b53a2a4452ada2d1b5dade9960b2522f1e61293a811a077439e39029565",
"url": "https://files.pythonhosted.org/packages/fb/d2/4398416cd699b35167947c6e22aca52c47e69ad5695073c9f1f2c52e04aa/cffi-2.1.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl"
},
"direct": false,
"name": "cffi",
"purl": "pkg:pypi/cffi@2.1.0",
"version": "2.1.0"
},
{
"artifact": {
"filename": "charset_normalizer-3.4.9-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",
"sha256": "04ce310cb89c15df659582aee80a0603788732a5e017d5bd5c81158106ce249c",
"url": "https://files.pythonhosted.org/packages/a5/34/49b9060e8418b14fb5cba9cf6bfb383111e2538a03a1fb18e66a95aeb3d5/charset_normalizer-3.4.9-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl"
},
"direct": false,
"name": "charset-normalizer",
"purl": "pkg:pypi/charset-normalizer@3.4.9",
"version": "3.4.9"
},
{
"artifact": {
"filename": "contourpy-1.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",
"sha256": "3859783aefa2b8355697f16642695a5b9792e7a46ab86da1118a4a23a51a33d7",
"url": "https://files.pythonhosted.org/packages/c2/f3/20c5d1ef4f4748e52d60771b8560cf00b69d5c6368b5c2e9311bcfa2a08b/contourpy-1.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl"
},
"direct": false,
"name": "contourpy",
"purl": "pkg:pypi/contourpy@1.3.2",
"version": "1.3.2"
},
{
"artifact": {
"filename": "cryptography-48.0.1-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl",
"sha256": "f0d27a5696721ef7a672b8c810f6aded391058e0b9486e63e6d93baf765da691",
"url": "https://files.pythonhosted.org/packages/9c/87/c85d147b53323c7eb4d850920c8901377323c2a0ff8d79c262d4fee89aa2/cryptography-48.0.1-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl"
},
"direct": true,
"name": "cryptography",
"purl": "pkg:pypi/cryptography@48.0.1",
"version": "48.0.1"
},
{
"artifact": {
"filename": "cssselect2-0.9.0-py3-none-any.whl",
"sha256": "6a99e5f91f9a016a304dd929b0966ca464bcfda15177b6fb4a118fc0fb5d9563",
"url": "https://files.pythonhosted.org/packages/21/0e/8459ca4413e1a21a06c97d134bfaf18adfd27cea068813dc0faae06cbf00/cssselect2-0.9.0-py3-none-any.whl"
},
"direct": false,
"name": "cssselect2",
"purl": "pkg:pypi/cssselect2@0.9.0",
"version": "0.9.0"
},
{
"artifact": {
"filename": "cycler-0.12.1-py3-none-any.whl",
"sha256": "85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30",
"url": "https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl"
},
"direct": false,
"name": "cycler",
"purl": "pkg:pypi/cycler@0.12.1",
"version": "0.12.1"
},
{
"artifact": {
"filename": "fonttools-4.63.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl",
"sha256": "d76ac49f929aecaf82d83250b8347e099d7aecba0f4726c1d9b6df3b8bb5fe18",
"url": "https://files.pythonhosted.org/packages/0b/43/a81f20050a3115b57d62c8e781446949512eac36690dc384ccea65ff4cc1/fonttools-4.63.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl"
},
"direct": false,
"name": "fonttools",
"purl": "pkg:pypi/fonttools@4.63.0",
"version": "4.63.0"
},
{
"artifact": {
"filename": "greenlet-3.2.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl",
"sha256": "0db5594dce18db94f7d1650d7489909b57afde4c580806b8d9203b6e79cdc079",
"url": "https://files.pythonhosted.org/packages/25/5d/382753b52006ce0218297ec1b628e048c4e64b155379331f25a7316eb749/greenlet-3.2.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl"
},
"direct": false,
"name": "greenlet",
"purl": "pkg:pypi/greenlet@3.2.4",
"version": "3.2.4"
},
{
"artifact": {
"filename": "idna-3.18-py3-none-any.whl",
"sha256": "7f952cbe720b688055e3f87de14f5c3e5fdaa8bc3928985c4077ca689de849a2",
"url": "https://files.pythonhosted.org/packages/1e/5e/d4e9f1a599fb8e573b7b87160658329fbf28d19eac2718f51fc3def3aa5a/idna-3.18-py3-none-any.whl"
},
"direct": false,
"name": "idna",
"purl": "pkg:pypi/idna@3.18",
"version": "3.18"
},
{
"artifact": {
"filename": "kiwisolver-1.5.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl",
"sha256": "2517e24d7315eb51c10664cdb865195df38ab74456c677df67bb47f12d088a27",
"url": "https://files.pythonhosted.org/packages/80/46/bddc13df6c2a40741e0cc7865bb1c9ed4796b6760bd04ce5fae3928ef917/kiwisolver-1.5.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl"
},
"direct": false,
"name": "kiwisolver",
"purl": "pkg:pypi/kiwisolver@1.5.0",
"version": "1.5.0"
},
{
"artifact": {
"filename": "matplotlib-3.11.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl",
"sha256": "630eee0e67d35cce2019a0e670719f4816e3b86aff0fa72729f6c69786fceb45",
"url": "https://files.pythonhosted.org/packages/53/f4/f0b4f9ba7ec14a7af8151f3ad71ecfe3561e6ba38cfab1db3681ba4ca112/matplotlib-3.11.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl"
},
"direct": true,
"name": "matplotlib",
"purl": "pkg:pypi/matplotlib@3.11.0",
"version": "3.11.0"
},
{
"artifact": {
"filename": "numpy-2.2.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",
"sha256": "ba10f8411898fc418a521833e014a77d3ca01c15b0c6cdcce6a0d2897e6dbbdf",
"url": "https://files.pythonhosted.org/packages/b3/dd/2238b898e51bd6d389b7389ffb20d7f4c10066d80351187ec8e303a5a475/numpy-2.2.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl"
},
"direct": false,
"name": "numpy",
"purl": "pkg:pypi/numpy@2.2.6",
"version": "2.2.6"
},
{
"artifact": {
"filename": "packaging-26.2-py3-none-any.whl",
"sha256": "5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e",
"url": "https://files.pythonhosted.org/packages/df/b2/87e62e8c3e2f4b32e5fe99e0b86d576da1312593b39f47d8ceef365e95ed/packaging-26.2-py3-none-any.whl"
},
"direct": false,
"name": "packaging",
"purl": "pkg:pypi/packaging@26.2",
"version": "26.2"
},
{
"artifact": {
"filename": "pillow-12.2.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl",
"sha256": "8bd7903a5f2a4545f6fd5935c90058b89d30045568985a71c79f5fd6edf9b91e",
"url": "https://files.pythonhosted.org/packages/d3/f1/00b7278c7dd52b17ad4329153748f87b6756ec195ff786c2bdf12518337d/pillow-12.2.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl"
},
"direct": true,
"name": "pillow",
"purl": "pkg:pypi/pillow@12.2.0",
"version": "12.2.0"
},
{
"artifact": {
"filename": "playwright-1.61.0-py3-none-manylinux1_x86_64.whl",
"sha256": "54f3b39f6eab832e33458c1dd7da0b5682aedab3b09ae731b5c59fa12fd2024e",
"url": "https://files.pythonhosted.org/packages/ab/f8/a35bf179e4ba2522c1893635094a64e407572547bd61528820fc0abc87fe/playwright-1.61.0-py3-none-manylinux1_x86_64.whl"
},
"direct": true,
"name": "playwright",
"purl": "pkg:pypi/playwright@1.61.0",
"version": "1.61.0"
},
{
"artifact": {
"filename": "pycparser-3.0-py3-none-any.whl",
"sha256": "b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992",
"url": "https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl"
},
"direct": false,
"name": "pycparser",
"purl": "pkg:pypi/pycparser@3.0",
"version": "3.0"
},
{
"artifact": {
"filename": "pydyf-0.12.1-py3-none-any.whl",
"sha256": "ea25b4e1fe7911195cb57067560daaa266639184e8335365cc3ee5214e7eaadc",
"url": "https://files.pythonhosted.org/packages/22/11/47efe2f66ba848a107adfd490b508f5c0cedc82127950553dca44d29e6c4/pydyf-0.12.1-py3-none-any.whl"
},
"direct": false,
"name": "pydyf",
"purl": "pkg:pypi/pydyf@0.12.1",
"version": "0.12.1"
},
{
"artifact": {
"filename": "pyee-13.0.1-py3-none-any.whl",
"sha256": "af2f8fede4171ef667dfded53f96e2ed0d6e6bd7ee3bb46437f77e3b57689228",
"url": "https://files.pythonhosted.org/packages/a0/c4/b4d4827c93ef43c01f599ef31453ccc1c132b353284fc6c87d535c233129/pyee-13.0.1-py3-none-any.whl"
},
"direct": false,
"name": "pyee",
"purl": "pkg:pypi/pyee@13.0.1",
"version": "13.0.1"
},
{
"artifact": {
"filename": "pyparsing-3.3.2-py3-none-any.whl",
"sha256": "850ba148bd908d7e2411587e247a1e4f0327839c40e2e5e6d05a007ecc69911d",
"url": "https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl"
},
"direct": false,
"name": "pyparsing",
"purl": "pkg:pypi/pyparsing@3.3.2",
"version": "3.3.2"
},
{
"artifact": {
"filename": "pyphen-0.17.2-py3-none-any.whl",
"sha256": "3a07fb017cb2341e1d9ff31b8634efb1ae4dc4b130468c7c39dd3d32e7c3affd",
"url": "https://files.pythonhosted.org/packages/7b/1f/c2142d2edf833a90728e5cdeb10bdbdc094dde8dbac078cee0cf33f5e11b/pyphen-0.17.2-py3-none-any.whl"
},
"direct": false,
"name": "pyphen",
"purl": "pkg:pypi/pyphen@0.17.2",
"version": "0.17.2"
},
{
"artifact": {
"filename": "python_dateutil-2.9.0.post0-py2.py3-none-any.whl",
"sha256": "a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427",
"url": "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl"
},
"direct": false,
"name": "python-dateutil",
"purl": "pkg:pypi/python-dateutil@2.9.0.post0",
"version": "2.9.0.post0"
},
{
"artifact": {
"filename": "reportlab-4.5.1-py3-none-any.whl",
"sha256": "06fce8cb56c83307cfa4909cdf4e6a2ddbb44e5d6ef4d2edca896d7e9769f091",
"url": "https://files.pythonhosted.org/packages/a7/45/ea7fad10122440de6e845568d106bffdc456ca0e8a1d8ae10b46016087e4/reportlab-4.5.1-py3-none-any.whl"
},
"direct": true,
"name": "reportlab",
"purl": "pkg:pypi/reportlab@4.5.1",
"version": "4.5.1"
},
{
"artifact": {
"filename": "requests-2.34.2-py3-none-any.whl",
"sha256": "2a0d60c172f83ac6ab31e4554906c0f3b3588d37b5cb939b1c061f4907e278e0",
"url": "https://files.pythonhosted.org/packages/a0/f4/c67b0b3f1b9245e8d266f0f112c500d50e5b4e83cb6f3b71b6528104182a/requests-2.34.2-py3-none-any.whl"
},
"direct": true,
"name": "requests",
"purl": "pkg:pypi/requests@2.34.2",
"version": "2.34.2"
},
{
"artifact": {
"filename": "six-1.17.0-py2.py3-none-any.whl",
"sha256": "4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274",
"url": "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl"
},
"direct": false,
"name": "six",
"purl": "pkg:pypi/six@1.17.0",
"version": "1.17.0"
},
{
"artifact": {
"filename": "tinycss2-1.5.1-py3-none-any.whl",
"sha256": "3415ba0f5839c062696996998176c4a3751d18b7edaaeeb658c9ce21ec150661",
"url": "https://files.pythonhosted.org/packages/60/45/c7b5c3168458db837e8ceab06dc77824e18202679d0463f0e8f002143a97/tinycss2-1.5.1-py3-none-any.whl"
},
"direct": false,
"name": "tinycss2",
"purl": "pkg:pypi/tinycss2@1.5.1",
"version": "1.5.1"
},
{
"artifact": {
"filename": "tinyhtml5-2.1.0-py3-none-any.whl",
"sha256": "6e11cfff38515834268daf89d5f85bbde0b6dd02e8d9e212d1385c2289b89f0a",
"url": "https://files.pythonhosted.org/packages/52/48/01695a036b695f83fea7aef6955d735db0f517b1c8e25ddb399ac0bdbcbf/tinyhtml5-2.1.0-py3-none-any.whl"
},
"direct": false,
"name": "tinyhtml5",
"purl": "pkg:pypi/tinyhtml5@2.1.0",
"version": "2.1.0"
},
{
"artifact": {
"filename": "typing_extensions-4.16.0-py3-none-any.whl",
"sha256": "481caa481374e813c1b176ada14e97f1f67a4539ce9cfeb3f350d78d6370c2e8",
"url": "https://files.pythonhosted.org/packages/49/d3/b8441a820a491ddfc024b0b0cf0393375b75ea13866d9c66727e54c2fc80/typing_extensions-4.16.0-py3-none-any.whl"
},
"direct": false,
"name": "typing-extensions",
"purl": "pkg:pypi/typing-extensions@4.16.0",
"version": "4.16.0"
},
{
"artifact": {
"filename": "urllib3-2.7.0-py3-none-any.whl",
"sha256": "9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897",
"url": "https://files.pythonhosted.org/packages/7f/3e/5db95bcf282c52709639744ca2a8b149baccf648e39c8cc87553df9eae0c/urllib3-2.7.0-py3-none-any.whl"
},
"direct": true,
"name": "urllib3",
"purl": "pkg:pypi/urllib3@2.7.0",
"version": "2.7.0"
},
{
"artifact": {
"filename": "weasyprint-69.0-py3-none-any.whl",
"sha256": "475951cfd917014de6d4d005caff48c6aa867e7e42b80cd5b16a0484a1609ee6",
"url": "https://files.pythonhosted.org/packages/93/cb/208525c6bd5033d7b2589b55e07bec23d9c61bb00703cbaf20ef52c3811f/weasyprint-69.0-py3-none-any.whl"
},
"direct": true,
"name": "weasyprint",
"purl": "pkg:pypi/weasyprint@69.0",
"version": "69.0"
},
{
"artifact": {
"filename": "webencodings-0.5.1-py2.py3-none-any.whl",
"sha256": "a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78",
"url": "https://files.pythonhosted.org/packages/f4/24/2a3e3df732393fed8b3ebf2ec078f05546de641fe1b667ee316ec1dcf3b7/webencodings-0.5.1-py2.py3-none-any.whl"
},
"direct": false,
"name": "webencodings",
"purl": "pkg:pypi/webencodings@0.5.1",
"version": "0.5.1"
},
{
"artifact": {
"filename": "zopfli-0.4.3-cp310-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl",
"sha256": "769875152d0625c46707bcca57d4b2233fe653482067acd55fbf6ec525cb9bdc",
"url": "https://files.pythonhosted.org/packages/55/c8/d8d8d731e0b192024567b7198fb77b748821d355f3c8bf0109de27191f43/zopfli-0.4.3-cp310-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl"
},
"direct": false,
"name": "zopfli",
"purl": "pkg:pypi/zopfli@0.4.3",
"version": "0.4.3"
}
],
"environment": {
"abi": "cp311",
"arch": "x86_64",
"implementation": "CPython",
"os": "linux",
"profile": "runtime",
"python_version": "3.11"
},
"evidence_class": "cross-target-pip-resolution-requiring-native-ci-confirmation",
"evidence_id": "runtime-linux-cp311",
"normalization_notes": [],
"platform_policy": "Exact lowest-common lock with target-selected PyPI wheels",
"resolved_at": "2026-07-11T00:00:00Z",
"resolver": "pip / PyPI",
"schema_version": "1.0.0",
"source_date_epoch": 1783728000,
"source_environment": {
"implementation_name": "cpython",
"implementation_version": "3.14.4",
"os_name": "posix",
"platform_machine": "x86_64",
"platform_python_implementation": "CPython",
"platform_release": "6.19.14-101.fc44.x86_64",
"platform_system": "Linux",
"platform_version": "#1 SMP PREEMPT_DYNAMIC Fri May 8 18:24:24 UTC 2026",
"python_full_version": "3.14.4",
"python_version": "3.14",
"sys_platform": "linux"
},
"source_pip_version": "26.0.1",
"source_report_sha256": "c4b4b56f72755d3ee01bb1f94a5c4d15afac9daba42a693e3d04b2b1429968d6"
}
@@ -0,0 +1,398 @@
{
"components": [
{
"artifact": {
"filename": "brotli-1.2.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl",
"sha256": "072e7624b1fc4d601036ab3f4f27942ef772887e876beff0301d261210bca97f",
"url": "https://files.pythonhosted.org/packages/03/a7/03aa61fbc3c5cbf99b44d158665f9b0dd3d8059be16c460208d9e385c837/brotli-1.2.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl"
},
"direct": false,
"name": "brotli",
"purl": "pkg:pypi/brotli@1.2.0",
"version": "1.2.0"
},
{
"artifact": {
"filename": "certifi-2026.6.17-py3-none-any.whl",
"sha256": "2227dcbaafe0d2f59279d1762ddddc37783ed4354594f194ffc31d20f41fc3db",
"url": "https://files.pythonhosted.org/packages/ef/2f/c5464532e965badff2f4c4c1a3a83f5697f0d7c407ed0cda44aaa99bb451/certifi-2026.6.17-py3-none-any.whl"
},
"direct": false,
"name": "certifi",
"purl": "pkg:pypi/certifi@2026.6.17",
"version": "2026.6.17"
},
{
"artifact": {
"filename": "cffi-2.1.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl",
"sha256": "1e9f50d192a3e525b15a75ab5114e442d83d657b7ec29182a991bc9a88fd3a66",
"url": "https://files.pythonhosted.org/packages/62/f2/c9522a81c32132799a1972c39f5c5f8b4c8b9f00488a23feaa6c06f07741/cffi-2.1.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl"
},
"direct": false,
"name": "cffi",
"purl": "pkg:pypi/cffi@2.1.0",
"version": "2.1.0"
},
{
"artifact": {
"filename": "charset_normalizer-3.4.9-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",
"sha256": "5e226f6218febc71f6c1fc2fafb91c226f75bdc1d8fb12d66823716e891608fd",
"url": "https://files.pythonhosted.org/packages/01/c4/4fa4c8b3097a11f3c5f09a35b72ed6855fb1d332469504962ab7bafcc702/charset_normalizer-3.4.9-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl"
},
"direct": false,
"name": "charset-normalizer",
"purl": "pkg:pypi/charset-normalizer@3.4.9",
"version": "3.4.9"
},
{
"artifact": {
"filename": "contourpy-1.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",
"sha256": "f26b383144cf2d2c29f01a1e8170f50dacf0eac02d64139dcd709a8ac4eb3cfe",
"url": "https://files.pythonhosted.org/packages/a8/32/b8a1c8965e4f72482ff2d1ac2cd670ce0b542f203c8e1d34e7c3e6925da7/contourpy-1.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl"
},
"direct": false,
"name": "contourpy",
"purl": "pkg:pypi/contourpy@1.3.2",
"version": "1.3.2"
},
{
"artifact": {
"filename": "cryptography-48.0.1-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl",
"sha256": "f0d27a5696721ef7a672b8c810f6aded391058e0b9486e63e6d93baf765da691",
"url": "https://files.pythonhosted.org/packages/9c/87/c85d147b53323c7eb4d850920c8901377323c2a0ff8d79c262d4fee89aa2/cryptography-48.0.1-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl"
},
"direct": true,
"name": "cryptography",
"purl": "pkg:pypi/cryptography@48.0.1",
"version": "48.0.1"
},
{
"artifact": {
"filename": "cssselect2-0.9.0-py3-none-any.whl",
"sha256": "6a99e5f91f9a016a304dd929b0966ca464bcfda15177b6fb4a118fc0fb5d9563",
"url": "https://files.pythonhosted.org/packages/21/0e/8459ca4413e1a21a06c97d134bfaf18adfd27cea068813dc0faae06cbf00/cssselect2-0.9.0-py3-none-any.whl"
},
"direct": false,
"name": "cssselect2",
"purl": "pkg:pypi/cssselect2@0.9.0",
"version": "0.9.0"
},
{
"artifact": {
"filename": "cycler-0.12.1-py3-none-any.whl",
"sha256": "85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30",
"url": "https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl"
},
"direct": false,
"name": "cycler",
"purl": "pkg:pypi/cycler@0.12.1",
"version": "0.12.1"
},
{
"artifact": {
"filename": "fonttools-4.63.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl",
"sha256": "58dc6bb86a78d782f00f9190ca02c119cf5bbe2807536e361e18d42019f877d8",
"url": "https://files.pythonhosted.org/packages/77/c7/2342da9830e3e9d4870305ca5d2091d2a83284f2953079b7bdd3b5e029d8/fonttools-4.63.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl"
},
"direct": false,
"name": "fonttools",
"purl": "pkg:pypi/fonttools@4.63.0",
"version": "4.63.0"
},
{
"artifact": {
"filename": "greenlet-3.2.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl",
"sha256": "c60a6d84229b271d44b70fb6e5fa23781abb5d742af7b808ae3f6efd7c9c60f6",
"url": "https://files.pythonhosted.org/packages/68/88/69bf19fd4dc19981928ceacbc5fd4bb6bc2215d53199e367832e98d1d8fe/greenlet-3.2.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl"
},
"direct": false,
"name": "greenlet",
"purl": "pkg:pypi/greenlet@3.2.4",
"version": "3.2.4"
},
{
"artifact": {
"filename": "idna-3.18-py3-none-any.whl",
"sha256": "7f952cbe720b688055e3f87de14f5c3e5fdaa8bc3928985c4077ca689de849a2",
"url": "https://files.pythonhosted.org/packages/1e/5e/d4e9f1a599fb8e573b7b87160658329fbf28d19eac2718f51fc3def3aa5a/idna-3.18-py3-none-any.whl"
},
"direct": false,
"name": "idna",
"purl": "pkg:pypi/idna@3.18",
"version": "3.18"
},
{
"artifact": {
"filename": "kiwisolver-1.5.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl",
"sha256": "bb5136fb5352d3f422df33f0c879a1b0c204004324150cc3b5e3c4f310c9049f",
"url": "https://files.pythonhosted.org/packages/c4/13/680c54afe3e65767bed7ec1a15571e1a2f1257128733851ade24abcefbcc/kiwisolver-1.5.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl"
},
"direct": false,
"name": "kiwisolver",
"purl": "pkg:pypi/kiwisolver@1.5.0",
"version": "1.5.0"
},
{
"artifact": {
"filename": "matplotlib-3.11.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl",
"sha256": "be5f93a1d21981bfb802ded0d77a0caa92d4342a47d45754fac77e314a506344",
"url": "https://files.pythonhosted.org/packages/94/95/7f522393c88313336b20d70fc849555757b2e5febc22b83b3a3f0fd4bce9/matplotlib-3.11.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl"
},
"direct": true,
"name": "matplotlib",
"purl": "pkg:pypi/matplotlib@3.11.0",
"version": "3.11.0"
},
{
"artifact": {
"filename": "numpy-2.2.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",
"sha256": "fd83c01228a688733f1ded5201c678f0c53ecc1006ffbc404db9f7a899ac6249",
"url": "https://files.pythonhosted.org/packages/8c/3d/1e1db36cfd41f895d266b103df00ca5b3cbe965184df824dec5c08c6b803/numpy-2.2.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl"
},
"direct": false,
"name": "numpy",
"purl": "pkg:pypi/numpy@2.2.6",
"version": "2.2.6"
},
{
"artifact": {
"filename": "packaging-26.2-py3-none-any.whl",
"sha256": "5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e",
"url": "https://files.pythonhosted.org/packages/df/b2/87e62e8c3e2f4b32e5fe99e0b86d576da1312593b39f47d8ceef365e95ed/packaging-26.2-py3-none-any.whl"
},
"direct": false,
"name": "packaging",
"purl": "pkg:pypi/packaging@26.2",
"version": "26.2"
},
{
"artifact": {
"filename": "pillow-12.2.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl",
"sha256": "b86024e52a1b269467a802258c25521e6d742349d760728092e1bc2d135b4d76",
"url": "https://files.pythonhosted.org/packages/e9/9e/c05e19657fd57841e476be1ab46c4d501bffbadbafdc31a6d665f8b737b6/pillow-12.2.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl"
},
"direct": true,
"name": "pillow",
"purl": "pkg:pypi/pillow@12.2.0",
"version": "12.2.0"
},
{
"artifact": {
"filename": "playwright-1.61.0-py3-none-manylinux1_x86_64.whl",
"sha256": "54f3b39f6eab832e33458c1dd7da0b5682aedab3b09ae731b5c59fa12fd2024e",
"url": "https://files.pythonhosted.org/packages/ab/f8/a35bf179e4ba2522c1893635094a64e407572547bd61528820fc0abc87fe/playwright-1.61.0-py3-none-manylinux1_x86_64.whl"
},
"direct": true,
"name": "playwright",
"purl": "pkg:pypi/playwright@1.61.0",
"version": "1.61.0"
},
{
"artifact": {
"filename": "pycparser-3.0-py3-none-any.whl",
"sha256": "b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992",
"url": "https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl"
},
"direct": false,
"name": "pycparser",
"purl": "pkg:pypi/pycparser@3.0",
"version": "3.0"
},
{
"artifact": {
"filename": "pydyf-0.12.1-py3-none-any.whl",
"sha256": "ea25b4e1fe7911195cb57067560daaa266639184e8335365cc3ee5214e7eaadc",
"url": "https://files.pythonhosted.org/packages/22/11/47efe2f66ba848a107adfd490b508f5c0cedc82127950553dca44d29e6c4/pydyf-0.12.1-py3-none-any.whl"
},
"direct": false,
"name": "pydyf",
"purl": "pkg:pypi/pydyf@0.12.1",
"version": "0.12.1"
},
{
"artifact": {
"filename": "pyee-13.0.1-py3-none-any.whl",
"sha256": "af2f8fede4171ef667dfded53f96e2ed0d6e6bd7ee3bb46437f77e3b57689228",
"url": "https://files.pythonhosted.org/packages/a0/c4/b4d4827c93ef43c01f599ef31453ccc1c132b353284fc6c87d535c233129/pyee-13.0.1-py3-none-any.whl"
},
"direct": false,
"name": "pyee",
"purl": "pkg:pypi/pyee@13.0.1",
"version": "13.0.1"
},
{
"artifact": {
"filename": "pyparsing-3.3.2-py3-none-any.whl",
"sha256": "850ba148bd908d7e2411587e247a1e4f0327839c40e2e5e6d05a007ecc69911d",
"url": "https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl"
},
"direct": false,
"name": "pyparsing",
"purl": "pkg:pypi/pyparsing@3.3.2",
"version": "3.3.2"
},
{
"artifact": {
"filename": "pyphen-0.17.2-py3-none-any.whl",
"sha256": "3a07fb017cb2341e1d9ff31b8634efb1ae4dc4b130468c7c39dd3d32e7c3affd",
"url": "https://files.pythonhosted.org/packages/7b/1f/c2142d2edf833a90728e5cdeb10bdbdc094dde8dbac078cee0cf33f5e11b/pyphen-0.17.2-py3-none-any.whl"
},
"direct": false,
"name": "pyphen",
"purl": "pkg:pypi/pyphen@0.17.2",
"version": "0.17.2"
},
{
"artifact": {
"filename": "python_dateutil-2.9.0.post0-py2.py3-none-any.whl",
"sha256": "a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427",
"url": "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl"
},
"direct": false,
"name": "python-dateutil",
"purl": "pkg:pypi/python-dateutil@2.9.0.post0",
"version": "2.9.0.post0"
},
{
"artifact": {
"filename": "reportlab-4.5.1-py3-none-any.whl",
"sha256": "06fce8cb56c83307cfa4909cdf4e6a2ddbb44e5d6ef4d2edca896d7e9769f091",
"url": "https://files.pythonhosted.org/packages/a7/45/ea7fad10122440de6e845568d106bffdc456ca0e8a1d8ae10b46016087e4/reportlab-4.5.1-py3-none-any.whl"
},
"direct": true,
"name": "reportlab",
"purl": "pkg:pypi/reportlab@4.5.1",
"version": "4.5.1"
},
{
"artifact": {
"filename": "requests-2.34.2-py3-none-any.whl",
"sha256": "2a0d60c172f83ac6ab31e4554906c0f3b3588d37b5cb939b1c061f4907e278e0",
"url": "https://files.pythonhosted.org/packages/a0/f4/c67b0b3f1b9245e8d266f0f112c500d50e5b4e83cb6f3b71b6528104182a/requests-2.34.2-py3-none-any.whl"
},
"direct": true,
"name": "requests",
"purl": "pkg:pypi/requests@2.34.2",
"version": "2.34.2"
},
{
"artifact": {
"filename": "six-1.17.0-py2.py3-none-any.whl",
"sha256": "4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274",
"url": "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl"
},
"direct": false,
"name": "six",
"purl": "pkg:pypi/six@1.17.0",
"version": "1.17.0"
},
{
"artifact": {
"filename": "tinycss2-1.5.1-py3-none-any.whl",
"sha256": "3415ba0f5839c062696996998176c4a3751d18b7edaaeeb658c9ce21ec150661",
"url": "https://files.pythonhosted.org/packages/60/45/c7b5c3168458db837e8ceab06dc77824e18202679d0463f0e8f002143a97/tinycss2-1.5.1-py3-none-any.whl"
},
"direct": false,
"name": "tinycss2",
"purl": "pkg:pypi/tinycss2@1.5.1",
"version": "1.5.1"
},
{
"artifact": {
"filename": "tinyhtml5-2.1.0-py3-none-any.whl",
"sha256": "6e11cfff38515834268daf89d5f85bbde0b6dd02e8d9e212d1385c2289b89f0a",
"url": "https://files.pythonhosted.org/packages/52/48/01695a036b695f83fea7aef6955d735db0f517b1c8e25ddb399ac0bdbcbf/tinyhtml5-2.1.0-py3-none-any.whl"
},
"direct": false,
"name": "tinyhtml5",
"purl": "pkg:pypi/tinyhtml5@2.1.0",
"version": "2.1.0"
},
{
"artifact": {
"filename": "typing_extensions-4.16.0-py3-none-any.whl",
"sha256": "481caa481374e813c1b176ada14e97f1f67a4539ce9cfeb3f350d78d6370c2e8",
"url": "https://files.pythonhosted.org/packages/49/d3/b8441a820a491ddfc024b0b0cf0393375b75ea13866d9c66727e54c2fc80/typing_extensions-4.16.0-py3-none-any.whl"
},
"direct": false,
"name": "typing-extensions",
"purl": "pkg:pypi/typing-extensions@4.16.0",
"version": "4.16.0"
},
{
"artifact": {
"filename": "urllib3-2.7.0-py3-none-any.whl",
"sha256": "9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897",
"url": "https://files.pythonhosted.org/packages/7f/3e/5db95bcf282c52709639744ca2a8b149baccf648e39c8cc87553df9eae0c/urllib3-2.7.0-py3-none-any.whl"
},
"direct": true,
"name": "urllib3",
"purl": "pkg:pypi/urllib3@2.7.0",
"version": "2.7.0"
},
{
"artifact": {
"filename": "weasyprint-69.0-py3-none-any.whl",
"sha256": "475951cfd917014de6d4d005caff48c6aa867e7e42b80cd5b16a0484a1609ee6",
"url": "https://files.pythonhosted.org/packages/93/cb/208525c6bd5033d7b2589b55e07bec23d9c61bb00703cbaf20ef52c3811f/weasyprint-69.0-py3-none-any.whl"
},
"direct": true,
"name": "weasyprint",
"purl": "pkg:pypi/weasyprint@69.0",
"version": "69.0"
},
{
"artifact": {
"filename": "webencodings-0.5.1-py2.py3-none-any.whl",
"sha256": "a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78",
"url": "https://files.pythonhosted.org/packages/f4/24/2a3e3df732393fed8b3ebf2ec078f05546de641fe1b667ee316ec1dcf3b7/webencodings-0.5.1-py2.py3-none-any.whl"
},
"direct": false,
"name": "webencodings",
"purl": "pkg:pypi/webencodings@0.5.1",
"version": "0.5.1"
},
{
"artifact": {
"filename": "zopfli-0.4.3-cp310-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl",
"sha256": "769875152d0625c46707bcca57d4b2233fe653482067acd55fbf6ec525cb9bdc",
"url": "https://files.pythonhosted.org/packages/55/c8/d8d8d731e0b192024567b7198fb77b748821d355f3c8bf0109de27191f43/zopfli-0.4.3-cp310-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl"
},
"direct": false,
"name": "zopfli",
"purl": "pkg:pypi/zopfli@0.4.3",
"version": "0.4.3"
}
],
"environment": {
"abi": "cp312",
"arch": "x86_64",
"implementation": "CPython",
"os": "linux",
"profile": "runtime",
"python_version": "3.12"
},
"evidence_class": "cross-target-pip-resolution-requiring-native-ci-confirmation",
"evidence_id": "runtime-linux-cp312",
"normalization_notes": [],
"platform_policy": "Exact lowest-common lock with target-selected PyPI wheels",
"resolved_at": "2026-07-11T00:00:00Z",
"resolver": "pip / PyPI",
"schema_version": "1.0.0",
"source_date_epoch": 1783728000,
"source_environment": {
"implementation_name": "cpython",
"implementation_version": "3.14.4",
"os_name": "posix",
"platform_machine": "x86_64",
"platform_python_implementation": "CPython",
"platform_release": "6.19.14-101.fc44.x86_64",
"platform_system": "Linux",
"platform_version": "#1 SMP PREEMPT_DYNAMIC Fri May 8 18:24:24 UTC 2026",
"python_full_version": "3.14.4",
"python_version": "3.14",
"sys_platform": "linux"
},
"source_pip_version": "26.0.1",
"source_report_sha256": "fbaebbde3da8fb7285572d364e0a3f72e93e5aba9bcbe4a187c27dd3acc68cae"
}
@@ -0,0 +1,398 @@
{
"components": [
{
"artifact": {
"filename": "brotli-1.2.0-cp311-cp311-macosx_10_9_universal2.whl",
"sha256": "15b33fe93cedc4caaff8a0bd1eb7e3dab1c61bb22a0bf5bdfdfd97cd7da79744",
"url": "https://files.pythonhosted.org/packages/7a/ef/f285668811a9e1ddb47a18cb0b437d5fc2760d537a2fe8a57875ad6f8448/brotli-1.2.0-cp311-cp311-macosx_10_9_universal2.whl"
},
"direct": false,
"name": "brotli",
"purl": "pkg:pypi/brotli@1.2.0",
"version": "1.2.0"
},
{
"artifact": {
"filename": "certifi-2026.6.17-py3-none-any.whl",
"sha256": "2227dcbaafe0d2f59279d1762ddddc37783ed4354594f194ffc31d20f41fc3db",
"url": "https://files.pythonhosted.org/packages/ef/2f/c5464532e965badff2f4c4c1a3a83f5697f0d7c407ed0cda44aaa99bb451/certifi-2026.6.17-py3-none-any.whl"
},
"direct": false,
"name": "certifi",
"purl": "pkg:pypi/certifi@2026.6.17",
"version": "2026.6.17"
},
{
"artifact": {
"filename": "cffi-2.1.0-cp311-cp311-macosx_11_0_arm64.whl",
"sha256": "f5bce581e6b8c235e566a14768a943b172ada3ed73537bb0c0be1edee312d4e7",
"url": "https://files.pythonhosted.org/packages/ea/dd/e3b0baa2d3d6a857ac72b7efbf18e32e487c9cdafcc13049ad765495b15e/cffi-2.1.0-cp311-cp311-macosx_11_0_arm64.whl"
},
"direct": false,
"name": "cffi",
"purl": "pkg:pypi/cffi@2.1.0",
"version": "2.1.0"
},
{
"artifact": {
"filename": "charset_normalizer-3.4.9-cp311-cp311-macosx_10_9_universal2.whl",
"sha256": "0e94703ec9684807f20cfb5eed95c70f67f2a8f21ad620146d7b5a13677b93e5",
"url": "https://files.pythonhosted.org/packages/0b/e3/85ec501f206fb049259288c1f3506e53876937fb00edb47009348e66756b/charset_normalizer-3.4.9-cp311-cp311-macosx_10_9_universal2.whl"
},
"direct": false,
"name": "charset-normalizer",
"purl": "pkg:pypi/charset-normalizer@3.4.9",
"version": "3.4.9"
},
{
"artifact": {
"filename": "contourpy-1.3.2-cp311-cp311-macosx_11_0_arm64.whl",
"sha256": "b7cd50c38f500bbcc9b6a46643a40e0913673f869315d8e70de0438817cb7773",
"url": "https://files.pythonhosted.org/packages/e6/75/3469f011d64b8bbfa04f709bfc23e1dd71be54d05b1b083be9f5b22750d1/contourpy-1.3.2-cp311-cp311-macosx_11_0_arm64.whl"
},
"direct": false,
"name": "contourpy",
"purl": "pkg:pypi/contourpy@1.3.2",
"version": "1.3.2"
},
{
"artifact": {
"filename": "cryptography-48.0.1-cp311-abi3-macosx_10_9_universal2.whl",
"sha256": "3e4a1a3232eef2e6c732827d5722db29a0cc8b27af2a4d865b094cf954be9ca1",
"url": "https://files.pythonhosted.org/packages/1b/bc/ee4137cbbe105652c0ee4252792b78fc8e7afa4b8e61d9d5dc05a7f45731/cryptography-48.0.1-cp311-abi3-macosx_10_9_universal2.whl"
},
"direct": true,
"name": "cryptography",
"purl": "pkg:pypi/cryptography@48.0.1",
"version": "48.0.1"
},
{
"artifact": {
"filename": "cssselect2-0.9.0-py3-none-any.whl",
"sha256": "6a99e5f91f9a016a304dd929b0966ca464bcfda15177b6fb4a118fc0fb5d9563",
"url": "https://files.pythonhosted.org/packages/21/0e/8459ca4413e1a21a06c97d134bfaf18adfd27cea068813dc0faae06cbf00/cssselect2-0.9.0-py3-none-any.whl"
},
"direct": false,
"name": "cssselect2",
"purl": "pkg:pypi/cssselect2@0.9.0",
"version": "0.9.0"
},
{
"artifact": {
"filename": "cycler-0.12.1-py3-none-any.whl",
"sha256": "85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30",
"url": "https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl"
},
"direct": false,
"name": "cycler",
"purl": "pkg:pypi/cycler@0.12.1",
"version": "0.12.1"
},
{
"artifact": {
"filename": "fonttools-4.63.0-cp311-cp311-macosx_10_9_universal2.whl",
"sha256": "2b8ae05d9eacf6081414d759c0a352769ac28ce31280d6bb8e77b03f9e3c449f",
"url": "https://files.pythonhosted.org/packages/75/2b/a7f1545bdf5da69c4bda0cea2a5781f0ad2a6623e0277267672db43c5fe6/fonttools-4.63.0-cp311-cp311-macosx_10_9_universal2.whl"
},
"direct": false,
"name": "fonttools",
"purl": "pkg:pypi/fonttools@4.63.0",
"version": "4.63.0"
},
{
"artifact": {
"filename": "greenlet-3.2.4-cp311-cp311-macosx_11_0_universal2.whl",
"sha256": "96378df1de302bc38e99c3a9aa311967b7dc80ced1dcc6f171e99842987882a2",
"url": "https://files.pythonhosted.org/packages/a4/de/f28ced0a67749cac23fecb02b694f6473f47686dff6afaa211d186e2ef9c/greenlet-3.2.4-cp311-cp311-macosx_11_0_universal2.whl"
},
"direct": false,
"name": "greenlet",
"purl": "pkg:pypi/greenlet@3.2.4",
"version": "3.2.4"
},
{
"artifact": {
"filename": "idna-3.18-py3-none-any.whl",
"sha256": "7f952cbe720b688055e3f87de14f5c3e5fdaa8bc3928985c4077ca689de849a2",
"url": "https://files.pythonhosted.org/packages/1e/5e/d4e9f1a599fb8e573b7b87160658329fbf28d19eac2718f51fc3def3aa5a/idna-3.18-py3-none-any.whl"
},
"direct": false,
"name": "idna",
"purl": "pkg:pypi/idna@3.18",
"version": "3.18"
},
{
"artifact": {
"filename": "kiwisolver-1.5.0-cp311-cp311-macosx_11_0_arm64.whl",
"sha256": "0df54df7e686afa55e6f21fb86195224a6d9beb71d637e8d7920c95cf0f89aac",
"url": "https://files.pythonhosted.org/packages/0a/aa/510dc933d87767584abfe03efa445889996c70c2990f6f87c3ebaa0a18c5/kiwisolver-1.5.0-cp311-cp311-macosx_11_0_arm64.whl"
},
"direct": false,
"name": "kiwisolver",
"purl": "pkg:pypi/kiwisolver@1.5.0",
"version": "1.5.0"
},
{
"artifact": {
"filename": "matplotlib-3.11.0-cp311-cp311-macosx_11_0_arm64.whl",
"sha256": "57baa92fdc82948ed716eae6d2579d4d6f40965cd8d2f416755b4a72580a3233",
"url": "https://files.pythonhosted.org/packages/5e/92/044f1de43901310202f4c79acf4f141be53b2ca8d8380e2fcefb3d523a75/matplotlib-3.11.0-cp311-cp311-macosx_11_0_arm64.whl"
},
"direct": true,
"name": "matplotlib",
"purl": "pkg:pypi/matplotlib@3.11.0",
"version": "3.11.0"
},
{
"artifact": {
"filename": "numpy-2.2.6-cp311-cp311-macosx_11_0_arm64.whl",
"sha256": "c820a93b0255bc360f53eca31a0e676fd1101f673dda8da93454a12e23fc5f7a",
"url": "https://files.pythonhosted.org/packages/b3/2b/64e1affc7972decb74c9e29e5649fac940514910960ba25cd9af4488b66c/numpy-2.2.6-cp311-cp311-macosx_11_0_arm64.whl"
},
"direct": false,
"name": "numpy",
"purl": "pkg:pypi/numpy@2.2.6",
"version": "2.2.6"
},
{
"artifact": {
"filename": "packaging-26.2-py3-none-any.whl",
"sha256": "5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e",
"url": "https://files.pythonhosted.org/packages/df/b2/87e62e8c3e2f4b32e5fe99e0b86d576da1312593b39f47d8ceef365e95ed/packaging-26.2-py3-none-any.whl"
},
"direct": false,
"name": "packaging",
"purl": "pkg:pypi/packaging@26.2",
"version": "26.2"
},
{
"artifact": {
"filename": "pillow-12.2.0-cp311-cp311-macosx_11_0_arm64.whl",
"sha256": "71cde9a1e1551df7d34a25462fc60325e8a11a82cc2e2f54578e5e9a1e153d65",
"url": "https://files.pythonhosted.org/packages/47/a1/d5ff69e747374c33a3b53b9f98cca7889fce1fd03d79cdc4e1bccc6c5a87/pillow-12.2.0-cp311-cp311-macosx_11_0_arm64.whl"
},
"direct": true,
"name": "pillow",
"purl": "pkg:pypi/pillow@12.2.0",
"version": "12.2.0"
},
{
"artifact": {
"filename": "playwright-1.61.0-py3-none-macosx_11_0_arm64.whl",
"sha256": "009588c2a7e499bc5a8b425b61fa65490968bbda9cd69e0cf2cff10f8304659a",
"url": "https://files.pythonhosted.org/packages/42/35/71395dd3ecc798965be4a3ef8c443217d4abca168e7cb34536304f9489e6/playwright-1.61.0-py3-none-macosx_11_0_arm64.whl"
},
"direct": true,
"name": "playwright",
"purl": "pkg:pypi/playwright@1.61.0",
"version": "1.61.0"
},
{
"artifact": {
"filename": "pycparser-3.0-py3-none-any.whl",
"sha256": "b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992",
"url": "https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl"
},
"direct": false,
"name": "pycparser",
"purl": "pkg:pypi/pycparser@3.0",
"version": "3.0"
},
{
"artifact": {
"filename": "pydyf-0.12.1-py3-none-any.whl",
"sha256": "ea25b4e1fe7911195cb57067560daaa266639184e8335365cc3ee5214e7eaadc",
"url": "https://files.pythonhosted.org/packages/22/11/47efe2f66ba848a107adfd490b508f5c0cedc82127950553dca44d29e6c4/pydyf-0.12.1-py3-none-any.whl"
},
"direct": false,
"name": "pydyf",
"purl": "pkg:pypi/pydyf@0.12.1",
"version": "0.12.1"
},
{
"artifact": {
"filename": "pyee-13.0.1-py3-none-any.whl",
"sha256": "af2f8fede4171ef667dfded53f96e2ed0d6e6bd7ee3bb46437f77e3b57689228",
"url": "https://files.pythonhosted.org/packages/a0/c4/b4d4827c93ef43c01f599ef31453ccc1c132b353284fc6c87d535c233129/pyee-13.0.1-py3-none-any.whl"
},
"direct": false,
"name": "pyee",
"purl": "pkg:pypi/pyee@13.0.1",
"version": "13.0.1"
},
{
"artifact": {
"filename": "pyparsing-3.3.2-py3-none-any.whl",
"sha256": "850ba148bd908d7e2411587e247a1e4f0327839c40e2e5e6d05a007ecc69911d",
"url": "https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl"
},
"direct": false,
"name": "pyparsing",
"purl": "pkg:pypi/pyparsing@3.3.2",
"version": "3.3.2"
},
{
"artifact": {
"filename": "pyphen-0.17.2-py3-none-any.whl",
"sha256": "3a07fb017cb2341e1d9ff31b8634efb1ae4dc4b130468c7c39dd3d32e7c3affd",
"url": "https://files.pythonhosted.org/packages/7b/1f/c2142d2edf833a90728e5cdeb10bdbdc094dde8dbac078cee0cf33f5e11b/pyphen-0.17.2-py3-none-any.whl"
},
"direct": false,
"name": "pyphen",
"purl": "pkg:pypi/pyphen@0.17.2",
"version": "0.17.2"
},
{
"artifact": {
"filename": "python_dateutil-2.9.0.post0-py2.py3-none-any.whl",
"sha256": "a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427",
"url": "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl"
},
"direct": false,
"name": "python-dateutil",
"purl": "pkg:pypi/python-dateutil@2.9.0.post0",
"version": "2.9.0.post0"
},
{
"artifact": {
"filename": "reportlab-4.5.1-py3-none-any.whl",
"sha256": "06fce8cb56c83307cfa4909cdf4e6a2ddbb44e5d6ef4d2edca896d7e9769f091",
"url": "https://files.pythonhosted.org/packages/a7/45/ea7fad10122440de6e845568d106bffdc456ca0e8a1d8ae10b46016087e4/reportlab-4.5.1-py3-none-any.whl"
},
"direct": true,
"name": "reportlab",
"purl": "pkg:pypi/reportlab@4.5.1",
"version": "4.5.1"
},
{
"artifact": {
"filename": "requests-2.34.2-py3-none-any.whl",
"sha256": "2a0d60c172f83ac6ab31e4554906c0f3b3588d37b5cb939b1c061f4907e278e0",
"url": "https://files.pythonhosted.org/packages/a0/f4/c67b0b3f1b9245e8d266f0f112c500d50e5b4e83cb6f3b71b6528104182a/requests-2.34.2-py3-none-any.whl"
},
"direct": true,
"name": "requests",
"purl": "pkg:pypi/requests@2.34.2",
"version": "2.34.2"
},
{
"artifact": {
"filename": "six-1.17.0-py2.py3-none-any.whl",
"sha256": "4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274",
"url": "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl"
},
"direct": false,
"name": "six",
"purl": "pkg:pypi/six@1.17.0",
"version": "1.17.0"
},
{
"artifact": {
"filename": "tinycss2-1.5.1-py3-none-any.whl",
"sha256": "3415ba0f5839c062696996998176c4a3751d18b7edaaeeb658c9ce21ec150661",
"url": "https://files.pythonhosted.org/packages/60/45/c7b5c3168458db837e8ceab06dc77824e18202679d0463f0e8f002143a97/tinycss2-1.5.1-py3-none-any.whl"
},
"direct": false,
"name": "tinycss2",
"purl": "pkg:pypi/tinycss2@1.5.1",
"version": "1.5.1"
},
{
"artifact": {
"filename": "tinyhtml5-2.1.0-py3-none-any.whl",
"sha256": "6e11cfff38515834268daf89d5f85bbde0b6dd02e8d9e212d1385c2289b89f0a",
"url": "https://files.pythonhosted.org/packages/52/48/01695a036b695f83fea7aef6955d735db0f517b1c8e25ddb399ac0bdbcbf/tinyhtml5-2.1.0-py3-none-any.whl"
},
"direct": false,
"name": "tinyhtml5",
"purl": "pkg:pypi/tinyhtml5@2.1.0",
"version": "2.1.0"
},
{
"artifact": {
"filename": "typing_extensions-4.16.0-py3-none-any.whl",
"sha256": "481caa481374e813c1b176ada14e97f1f67a4539ce9cfeb3f350d78d6370c2e8",
"url": "https://files.pythonhosted.org/packages/49/d3/b8441a820a491ddfc024b0b0cf0393375b75ea13866d9c66727e54c2fc80/typing_extensions-4.16.0-py3-none-any.whl"
},
"direct": false,
"name": "typing-extensions",
"purl": "pkg:pypi/typing-extensions@4.16.0",
"version": "4.16.0"
},
{
"artifact": {
"filename": "urllib3-2.7.0-py3-none-any.whl",
"sha256": "9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897",
"url": "https://files.pythonhosted.org/packages/7f/3e/5db95bcf282c52709639744ca2a8b149baccf648e39c8cc87553df9eae0c/urllib3-2.7.0-py3-none-any.whl"
},
"direct": true,
"name": "urllib3",
"purl": "pkg:pypi/urllib3@2.7.0",
"version": "2.7.0"
},
{
"artifact": {
"filename": "weasyprint-69.0-py3-none-any.whl",
"sha256": "475951cfd917014de6d4d005caff48c6aa867e7e42b80cd5b16a0484a1609ee6",
"url": "https://files.pythonhosted.org/packages/93/cb/208525c6bd5033d7b2589b55e07bec23d9c61bb00703cbaf20ef52c3811f/weasyprint-69.0-py3-none-any.whl"
},
"direct": true,
"name": "weasyprint",
"purl": "pkg:pypi/weasyprint@69.0",
"version": "69.0"
},
{
"artifact": {
"filename": "webencodings-0.5.1-py2.py3-none-any.whl",
"sha256": "a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78",
"url": "https://files.pythonhosted.org/packages/f4/24/2a3e3df732393fed8b3ebf2ec078f05546de641fe1b667ee316ec1dcf3b7/webencodings-0.5.1-py2.py3-none-any.whl"
},
"direct": false,
"name": "webencodings",
"purl": "pkg:pypi/webencodings@0.5.1",
"version": "0.5.1"
},
{
"artifact": {
"filename": "zopfli-0.4.3-cp310-abi3-macosx_10_9_universal2.whl",
"sha256": "88f4fbe429aad72bc206275d81fab11a097e0f951a5848d1f51083c37ea73073",
"url": "https://files.pythonhosted.org/packages/a5/5f/b7d81b670daf990e15a0f7551da96c3c0700f69ae6d96b0245d6a19f51f3/zopfli-0.4.3-cp310-abi3-macosx_10_9_universal2.whl"
},
"direct": false,
"name": "zopfli",
"purl": "pkg:pypi/zopfli@0.4.3",
"version": "0.4.3"
}
],
"environment": {
"abi": "cp311",
"arch": "arm64",
"implementation": "CPython",
"os": "macos",
"profile": "runtime",
"python_version": "3.11"
},
"evidence_class": "cross-target-pip-resolution-requiring-native-ci-confirmation",
"evidence_id": "runtime-macos-arm-cp311",
"normalization_notes": [],
"platform_policy": "Exact lowest-common lock with target-selected PyPI wheels",
"resolved_at": "2026-07-11T00:00:00Z",
"resolver": "pip / PyPI",
"schema_version": "1.0.0",
"source_date_epoch": 1783728000,
"source_environment": {
"implementation_name": "cpython",
"implementation_version": "3.14.4",
"os_name": "posix",
"platform_machine": "x86_64",
"platform_python_implementation": "CPython",
"platform_release": "6.19.14-101.fc44.x86_64",
"platform_system": "Linux",
"platform_version": "#1 SMP PREEMPT_DYNAMIC Fri May 8 18:24:24 UTC 2026",
"python_full_version": "3.14.4",
"python_version": "3.14",
"sys_platform": "linux"
},
"source_pip_version": "26.0.1",
"source_report_sha256": "e5ca5803aafc907e8b99b94597d3050a7944bbdd9d4e662dee36f767af166e94"
}
@@ -0,0 +1,398 @@
{
"components": [
{
"artifact": {
"filename": "brotli-1.2.0-cp312-cp312-macosx_10_13_universal2.whl",
"sha256": "35d382625778834a7f3061b15423919aa03e4f5da34ac8e02c074e4b75ab4f84",
"url": "https://files.pythonhosted.org/packages/11/ee/b0a11ab2315c69bb9b45a2aaed022499c9c24a205c3a49c3513b541a7967/brotli-1.2.0-cp312-cp312-macosx_10_13_universal2.whl"
},
"direct": false,
"name": "brotli",
"purl": "pkg:pypi/brotli@1.2.0",
"version": "1.2.0"
},
{
"artifact": {
"filename": "certifi-2026.6.17-py3-none-any.whl",
"sha256": "2227dcbaafe0d2f59279d1762ddddc37783ed4354594f194ffc31d20f41fc3db",
"url": "https://files.pythonhosted.org/packages/ef/2f/c5464532e965badff2f4c4c1a3a83f5697f0d7c407ed0cda44aaa99bb451/certifi-2026.6.17-py3-none-any.whl"
},
"direct": false,
"name": "certifi",
"purl": "pkg:pypi/certifi@2026.6.17",
"version": "2026.6.17"
},
{
"artifact": {
"filename": "cffi-2.1.0-cp312-cp312-macosx_11_0_arm64.whl",
"sha256": "78474632761faa0fb96f30b1c928c84ebcf68713cbb80d15bab09dfe61640fde",
"url": "https://files.pythonhosted.org/packages/4b/92/e7bb136ad6b5352603732cf907ef862ca103f20f2031c1735a46300c20c9/cffi-2.1.0-cp312-cp312-macosx_11_0_arm64.whl"
},
"direct": false,
"name": "cffi",
"purl": "pkg:pypi/cffi@2.1.0",
"version": "2.1.0"
},
{
"artifact": {
"filename": "charset_normalizer-3.4.9-cp312-cp312-macosx_10_13_universal2.whl",
"sha256": "45b0cc4e3556cd875e09102988d1ab8356c998b596c9fced84547c8138b487a0",
"url": "https://files.pythonhosted.org/packages/70/4a/ecbd131485c07fcdfad54e28946d513e3da22ef3b4bd854dcafae54ec739/charset_normalizer-3.4.9-cp312-cp312-macosx_10_13_universal2.whl"
},
"direct": false,
"name": "charset-normalizer",
"purl": "pkg:pypi/charset-normalizer@3.4.9",
"version": "3.4.9"
},
{
"artifact": {
"filename": "contourpy-1.3.2-cp312-cp312-macosx_11_0_arm64.whl",
"sha256": "82199cb78276249796419fe36b7386bd8d2cc3f28b3bc19fe2454fe2e26c4c15",
"url": "https://files.pythonhosted.org/packages/93/3b/0004767622a9826ea3d95f0e9d98cd8729015768075d61f9fea8eeca42a8/contourpy-1.3.2-cp312-cp312-macosx_11_0_arm64.whl"
},
"direct": false,
"name": "contourpy",
"purl": "pkg:pypi/contourpy@1.3.2",
"version": "1.3.2"
},
{
"artifact": {
"filename": "cryptography-48.0.1-cp311-abi3-macosx_10_9_universal2.whl",
"sha256": "3e4a1a3232eef2e6c732827d5722db29a0cc8b27af2a4d865b094cf954be9ca1",
"url": "https://files.pythonhosted.org/packages/1b/bc/ee4137cbbe105652c0ee4252792b78fc8e7afa4b8e61d9d5dc05a7f45731/cryptography-48.0.1-cp311-abi3-macosx_10_9_universal2.whl"
},
"direct": true,
"name": "cryptography",
"purl": "pkg:pypi/cryptography@48.0.1",
"version": "48.0.1"
},
{
"artifact": {
"filename": "cssselect2-0.9.0-py3-none-any.whl",
"sha256": "6a99e5f91f9a016a304dd929b0966ca464bcfda15177b6fb4a118fc0fb5d9563",
"url": "https://files.pythonhosted.org/packages/21/0e/8459ca4413e1a21a06c97d134bfaf18adfd27cea068813dc0faae06cbf00/cssselect2-0.9.0-py3-none-any.whl"
},
"direct": false,
"name": "cssselect2",
"purl": "pkg:pypi/cssselect2@0.9.0",
"version": "0.9.0"
},
{
"artifact": {
"filename": "cycler-0.12.1-py3-none-any.whl",
"sha256": "85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30",
"url": "https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl"
},
"direct": false,
"name": "cycler",
"purl": "pkg:pypi/cycler@0.12.1",
"version": "0.12.1"
},
{
"artifact": {
"filename": "fonttools-4.63.0-cp312-cp312-macosx_10_13_universal2.whl",
"sha256": "37dd23e621e3b0aef1baa70a303b80aaf38449632cfc8fd2a55fb285bbccfc02",
"url": "https://files.pythonhosted.org/packages/08/ef/b3c6b9b5be2f82416d73fe2ed2e96e2793cd80e7510bd6a17ca79cdd88ec/fonttools-4.63.0-cp312-cp312-macosx_10_13_universal2.whl"
},
"direct": false,
"name": "fonttools",
"purl": "pkg:pypi/fonttools@4.63.0",
"version": "4.63.0"
},
{
"artifact": {
"filename": "greenlet-3.2.4-cp312-cp312-macosx_11_0_universal2.whl",
"sha256": "3b67ca49f54cede0186854a008109d6ee71f66bd57bb36abd6d0a0267b540cdd",
"url": "https://files.pythonhosted.org/packages/44/69/9b804adb5fd0671f367781560eb5eb586c4d495277c93bde4307b9e28068/greenlet-3.2.4-cp312-cp312-macosx_11_0_universal2.whl"
},
"direct": false,
"name": "greenlet",
"purl": "pkg:pypi/greenlet@3.2.4",
"version": "3.2.4"
},
{
"artifact": {
"filename": "idna-3.18-py3-none-any.whl",
"sha256": "7f952cbe720b688055e3f87de14f5c3e5fdaa8bc3928985c4077ca689de849a2",
"url": "https://files.pythonhosted.org/packages/1e/5e/d4e9f1a599fb8e573b7b87160658329fbf28d19eac2718f51fc3def3aa5a/idna-3.18-py3-none-any.whl"
},
"direct": false,
"name": "idna",
"purl": "pkg:pypi/idna@3.18",
"version": "3.18"
},
{
"artifact": {
"filename": "kiwisolver-1.5.0-cp312-cp312-macosx_11_0_arm64.whl",
"sha256": "ed3a984b31da7481b103f68776f7128a89ef26ed40f4dc41a2223cda7fb24819",
"url": "https://files.pythonhosted.org/packages/99/9f/795fedf35634f746151ca8839d05681ceb6287fbed6cc1c9bf235f7887c2/kiwisolver-1.5.0-cp312-cp312-macosx_11_0_arm64.whl"
},
"direct": false,
"name": "kiwisolver",
"purl": "pkg:pypi/kiwisolver@1.5.0",
"version": "1.5.0"
},
{
"artifact": {
"filename": "matplotlib-3.11.0-cp312-cp312-macosx_11_0_arm64.whl",
"sha256": "0515d495124be3124340e59f164d901ed4484e2246a5b74cfa483cac3b80bd97",
"url": "https://files.pythonhosted.org/packages/82/34/bdd77418adb2178a1d59f044bd67bfebb115896e91b840b8a197eb3f4f4e/matplotlib-3.11.0-cp312-cp312-macosx_11_0_arm64.whl"
},
"direct": true,
"name": "matplotlib",
"purl": "pkg:pypi/matplotlib@3.11.0",
"version": "3.11.0"
},
{
"artifact": {
"filename": "numpy-2.2.6-cp312-cp312-macosx_11_0_arm64.whl",
"sha256": "de749064336d37e340f640b05f24e9e3dd678c57318c7289d222a8a2f543e90c",
"url": "https://files.pythonhosted.org/packages/66/ee/560deadcdde6c2f90200450d5938f63a34b37e27ebff162810f716f6a230/numpy-2.2.6-cp312-cp312-macosx_11_0_arm64.whl"
},
"direct": false,
"name": "numpy",
"purl": "pkg:pypi/numpy@2.2.6",
"version": "2.2.6"
},
{
"artifact": {
"filename": "packaging-26.2-py3-none-any.whl",
"sha256": "5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e",
"url": "https://files.pythonhosted.org/packages/df/b2/87e62e8c3e2f4b32e5fe99e0b86d576da1312593b39f47d8ceef365e95ed/packaging-26.2-py3-none-any.whl"
},
"direct": false,
"name": "packaging",
"purl": "pkg:pypi/packaging@26.2",
"version": "26.2"
},
{
"artifact": {
"filename": "pillow-12.2.0-cp312-cp312-macosx_11_0_arm64.whl",
"sha256": "f3f40b3c5a968281fd507d519e444c35f0ff171237f4fdde090dd60699458421",
"url": "https://files.pythonhosted.org/packages/d8/95/0a351b9289c2b5cbde0bacd4a83ebc44023e835490a727b2a3bd60ddc0f4/pillow-12.2.0-cp312-cp312-macosx_11_0_arm64.whl"
},
"direct": true,
"name": "pillow",
"purl": "pkg:pypi/pillow@12.2.0",
"version": "12.2.0"
},
{
"artifact": {
"filename": "playwright-1.61.0-py3-none-macosx_11_0_arm64.whl",
"sha256": "009588c2a7e499bc5a8b425b61fa65490968bbda9cd69e0cf2cff10f8304659a",
"url": "https://files.pythonhosted.org/packages/42/35/71395dd3ecc798965be4a3ef8c443217d4abca168e7cb34536304f9489e6/playwright-1.61.0-py3-none-macosx_11_0_arm64.whl"
},
"direct": true,
"name": "playwright",
"purl": "pkg:pypi/playwright@1.61.0",
"version": "1.61.0"
},
{
"artifact": {
"filename": "pycparser-3.0-py3-none-any.whl",
"sha256": "b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992",
"url": "https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl"
},
"direct": false,
"name": "pycparser",
"purl": "pkg:pypi/pycparser@3.0",
"version": "3.0"
},
{
"artifact": {
"filename": "pydyf-0.12.1-py3-none-any.whl",
"sha256": "ea25b4e1fe7911195cb57067560daaa266639184e8335365cc3ee5214e7eaadc",
"url": "https://files.pythonhosted.org/packages/22/11/47efe2f66ba848a107adfd490b508f5c0cedc82127950553dca44d29e6c4/pydyf-0.12.1-py3-none-any.whl"
},
"direct": false,
"name": "pydyf",
"purl": "pkg:pypi/pydyf@0.12.1",
"version": "0.12.1"
},
{
"artifact": {
"filename": "pyee-13.0.1-py3-none-any.whl",
"sha256": "af2f8fede4171ef667dfded53f96e2ed0d6e6bd7ee3bb46437f77e3b57689228",
"url": "https://files.pythonhosted.org/packages/a0/c4/b4d4827c93ef43c01f599ef31453ccc1c132b353284fc6c87d535c233129/pyee-13.0.1-py3-none-any.whl"
},
"direct": false,
"name": "pyee",
"purl": "pkg:pypi/pyee@13.0.1",
"version": "13.0.1"
},
{
"artifact": {
"filename": "pyparsing-3.3.2-py3-none-any.whl",
"sha256": "850ba148bd908d7e2411587e247a1e4f0327839c40e2e5e6d05a007ecc69911d",
"url": "https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl"
},
"direct": false,
"name": "pyparsing",
"purl": "pkg:pypi/pyparsing@3.3.2",
"version": "3.3.2"
},
{
"artifact": {
"filename": "pyphen-0.17.2-py3-none-any.whl",
"sha256": "3a07fb017cb2341e1d9ff31b8634efb1ae4dc4b130468c7c39dd3d32e7c3affd",
"url": "https://files.pythonhosted.org/packages/7b/1f/c2142d2edf833a90728e5cdeb10bdbdc094dde8dbac078cee0cf33f5e11b/pyphen-0.17.2-py3-none-any.whl"
},
"direct": false,
"name": "pyphen",
"purl": "pkg:pypi/pyphen@0.17.2",
"version": "0.17.2"
},
{
"artifact": {
"filename": "python_dateutil-2.9.0.post0-py2.py3-none-any.whl",
"sha256": "a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427",
"url": "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl"
},
"direct": false,
"name": "python-dateutil",
"purl": "pkg:pypi/python-dateutil@2.9.0.post0",
"version": "2.9.0.post0"
},
{
"artifact": {
"filename": "reportlab-4.5.1-py3-none-any.whl",
"sha256": "06fce8cb56c83307cfa4909cdf4e6a2ddbb44e5d6ef4d2edca896d7e9769f091",
"url": "https://files.pythonhosted.org/packages/a7/45/ea7fad10122440de6e845568d106bffdc456ca0e8a1d8ae10b46016087e4/reportlab-4.5.1-py3-none-any.whl"
},
"direct": true,
"name": "reportlab",
"purl": "pkg:pypi/reportlab@4.5.1",
"version": "4.5.1"
},
{
"artifact": {
"filename": "requests-2.34.2-py3-none-any.whl",
"sha256": "2a0d60c172f83ac6ab31e4554906c0f3b3588d37b5cb939b1c061f4907e278e0",
"url": "https://files.pythonhosted.org/packages/a0/f4/c67b0b3f1b9245e8d266f0f112c500d50e5b4e83cb6f3b71b6528104182a/requests-2.34.2-py3-none-any.whl"
},
"direct": true,
"name": "requests",
"purl": "pkg:pypi/requests@2.34.2",
"version": "2.34.2"
},
{
"artifact": {
"filename": "six-1.17.0-py2.py3-none-any.whl",
"sha256": "4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274",
"url": "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl"
},
"direct": false,
"name": "six",
"purl": "pkg:pypi/six@1.17.0",
"version": "1.17.0"
},
{
"artifact": {
"filename": "tinycss2-1.5.1-py3-none-any.whl",
"sha256": "3415ba0f5839c062696996998176c4a3751d18b7edaaeeb658c9ce21ec150661",
"url": "https://files.pythonhosted.org/packages/60/45/c7b5c3168458db837e8ceab06dc77824e18202679d0463f0e8f002143a97/tinycss2-1.5.1-py3-none-any.whl"
},
"direct": false,
"name": "tinycss2",
"purl": "pkg:pypi/tinycss2@1.5.1",
"version": "1.5.1"
},
{
"artifact": {
"filename": "tinyhtml5-2.1.0-py3-none-any.whl",
"sha256": "6e11cfff38515834268daf89d5f85bbde0b6dd02e8d9e212d1385c2289b89f0a",
"url": "https://files.pythonhosted.org/packages/52/48/01695a036b695f83fea7aef6955d735db0f517b1c8e25ddb399ac0bdbcbf/tinyhtml5-2.1.0-py3-none-any.whl"
},
"direct": false,
"name": "tinyhtml5",
"purl": "pkg:pypi/tinyhtml5@2.1.0",
"version": "2.1.0"
},
{
"artifact": {
"filename": "typing_extensions-4.16.0-py3-none-any.whl",
"sha256": "481caa481374e813c1b176ada14e97f1f67a4539ce9cfeb3f350d78d6370c2e8",
"url": "https://files.pythonhosted.org/packages/49/d3/b8441a820a491ddfc024b0b0cf0393375b75ea13866d9c66727e54c2fc80/typing_extensions-4.16.0-py3-none-any.whl"
},
"direct": false,
"name": "typing-extensions",
"purl": "pkg:pypi/typing-extensions@4.16.0",
"version": "4.16.0"
},
{
"artifact": {
"filename": "urllib3-2.7.0-py3-none-any.whl",
"sha256": "9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897",
"url": "https://files.pythonhosted.org/packages/7f/3e/5db95bcf282c52709639744ca2a8b149baccf648e39c8cc87553df9eae0c/urllib3-2.7.0-py3-none-any.whl"
},
"direct": true,
"name": "urllib3",
"purl": "pkg:pypi/urllib3@2.7.0",
"version": "2.7.0"
},
{
"artifact": {
"filename": "weasyprint-69.0-py3-none-any.whl",
"sha256": "475951cfd917014de6d4d005caff48c6aa867e7e42b80cd5b16a0484a1609ee6",
"url": "https://files.pythonhosted.org/packages/93/cb/208525c6bd5033d7b2589b55e07bec23d9c61bb00703cbaf20ef52c3811f/weasyprint-69.0-py3-none-any.whl"
},
"direct": true,
"name": "weasyprint",
"purl": "pkg:pypi/weasyprint@69.0",
"version": "69.0"
},
{
"artifact": {
"filename": "webencodings-0.5.1-py2.py3-none-any.whl",
"sha256": "a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78",
"url": "https://files.pythonhosted.org/packages/f4/24/2a3e3df732393fed8b3ebf2ec078f05546de641fe1b667ee316ec1dcf3b7/webencodings-0.5.1-py2.py3-none-any.whl"
},
"direct": false,
"name": "webencodings",
"purl": "pkg:pypi/webencodings@0.5.1",
"version": "0.5.1"
},
{
"artifact": {
"filename": "zopfli-0.4.3-cp310-abi3-macosx_10_9_universal2.whl",
"sha256": "88f4fbe429aad72bc206275d81fab11a097e0f951a5848d1f51083c37ea73073",
"url": "https://files.pythonhosted.org/packages/a5/5f/b7d81b670daf990e15a0f7551da96c3c0700f69ae6d96b0245d6a19f51f3/zopfli-0.4.3-cp310-abi3-macosx_10_9_universal2.whl"
},
"direct": false,
"name": "zopfli",
"purl": "pkg:pypi/zopfli@0.4.3",
"version": "0.4.3"
}
],
"environment": {
"abi": "cp312",
"arch": "arm64",
"implementation": "CPython",
"os": "macos",
"profile": "runtime",
"python_version": "3.12"
},
"evidence_class": "cross-target-pip-resolution-requiring-native-ci-confirmation",
"evidence_id": "runtime-macos-arm-cp312",
"normalization_notes": [],
"platform_policy": "Exact lowest-common lock with target-selected PyPI wheels",
"resolved_at": "2026-07-11T00:00:00Z",
"resolver": "pip / PyPI",
"schema_version": "1.0.0",
"source_date_epoch": 1783728000,
"source_environment": {
"implementation_name": "cpython",
"implementation_version": "3.14.4",
"os_name": "posix",
"platform_machine": "x86_64",
"platform_python_implementation": "CPython",
"platform_release": "6.19.14-101.fc44.x86_64",
"platform_system": "Linux",
"platform_version": "#1 SMP PREEMPT_DYNAMIC Fri May 8 18:24:24 UTC 2026",
"python_full_version": "3.14.4",
"python_version": "3.14",
"sys_platform": "linux"
},
"source_pip_version": "26.0.1",
"source_report_sha256": "412fc5510223c9d55955a1ef8e369d04bfac523208d24764761c1200b14c3f59"
}
@@ -0,0 +1,398 @@
{
"components": [
{
"artifact": {
"filename": "brotli-1.2.0-cp311-cp311-macosx_10_9_x86_64.whl",
"sha256": "898be2be399c221d2671d29eed26b6b2713a02c2119168ed914e7d00ceadb56f",
"url": "https://files.pythonhosted.org/packages/50/62/a3b77593587010c789a9d6eaa527c79e0848b7b860402cc64bc0bc28a86c/brotli-1.2.0-cp311-cp311-macosx_10_9_x86_64.whl"
},
"direct": false,
"name": "brotli",
"purl": "pkg:pypi/brotli@1.2.0",
"version": "1.2.0"
},
{
"artifact": {
"filename": "certifi-2026.6.17-py3-none-any.whl",
"sha256": "2227dcbaafe0d2f59279d1762ddddc37783ed4354594f194ffc31d20f41fc3db",
"url": "https://files.pythonhosted.org/packages/ef/2f/c5464532e965badff2f4c4c1a3a83f5697f0d7c407ed0cda44aaa99bb451/certifi-2026.6.17-py3-none-any.whl"
},
"direct": false,
"name": "certifi",
"purl": "pkg:pypi/certifi@2026.6.17",
"version": "2026.6.17"
},
{
"artifact": {
"filename": "cffi-2.1.0-cp311-cp311-macosx_10_15_x86_64.whl",
"sha256": "02cb7ff33ded4f1532476731f89ede53e2e488a8e6205515a82144246ffa7dcc",
"url": "https://files.pythonhosted.org/packages/d3/67/85c89a59ba36a671e79638f44d466749f08179266a57e4f2ffdf92174072/cffi-2.1.0-cp311-cp311-macosx_10_15_x86_64.whl"
},
"direct": false,
"name": "cffi",
"purl": "pkg:pypi/cffi@2.1.0",
"version": "2.1.0"
},
{
"artifact": {
"filename": "charset_normalizer-3.4.9-cp311-cp311-macosx_10_9_universal2.whl",
"sha256": "0e94703ec9684807f20cfb5eed95c70f67f2a8f21ad620146d7b5a13677b93e5",
"url": "https://files.pythonhosted.org/packages/0b/e3/85ec501f206fb049259288c1f3506e53876937fb00edb47009348e66756b/charset_normalizer-3.4.9-cp311-cp311-macosx_10_9_universal2.whl"
},
"direct": false,
"name": "charset-normalizer",
"purl": "pkg:pypi/charset-normalizer@3.4.9",
"version": "3.4.9"
},
{
"artifact": {
"filename": "contourpy-1.3.2-cp311-cp311-macosx_10_9_x86_64.whl",
"sha256": "6a37a2fb93d4df3fc4c0e363ea4d16f83195fc09c891bc8ce072b9d084853445",
"url": "https://files.pythonhosted.org/packages/b3/b9/ede788a0b56fc5b071639d06c33cb893f68b1178938f3425debebe2dab78/contourpy-1.3.2-cp311-cp311-macosx_10_9_x86_64.whl"
},
"direct": false,
"name": "contourpy",
"purl": "pkg:pypi/contourpy@1.3.2",
"version": "1.3.2"
},
{
"artifact": {
"filename": "cryptography-48.0.1-cp311-abi3-macosx_10_9_universal2.whl",
"sha256": "3e4a1a3232eef2e6c732827d5722db29a0cc8b27af2a4d865b094cf954be9ca1",
"url": "https://files.pythonhosted.org/packages/1b/bc/ee4137cbbe105652c0ee4252792b78fc8e7afa4b8e61d9d5dc05a7f45731/cryptography-48.0.1-cp311-abi3-macosx_10_9_universal2.whl"
},
"direct": true,
"name": "cryptography",
"purl": "pkg:pypi/cryptography@48.0.1",
"version": "48.0.1"
},
{
"artifact": {
"filename": "cssselect2-0.9.0-py3-none-any.whl",
"sha256": "6a99e5f91f9a016a304dd929b0966ca464bcfda15177b6fb4a118fc0fb5d9563",
"url": "https://files.pythonhosted.org/packages/21/0e/8459ca4413e1a21a06c97d134bfaf18adfd27cea068813dc0faae06cbf00/cssselect2-0.9.0-py3-none-any.whl"
},
"direct": false,
"name": "cssselect2",
"purl": "pkg:pypi/cssselect2@0.9.0",
"version": "0.9.0"
},
{
"artifact": {
"filename": "cycler-0.12.1-py3-none-any.whl",
"sha256": "85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30",
"url": "https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl"
},
"direct": false,
"name": "cycler",
"purl": "pkg:pypi/cycler@0.12.1",
"version": "0.12.1"
},
{
"artifact": {
"filename": "fonttools-4.63.0-cp311-cp311-macosx_10_9_x86_64.whl",
"sha256": "79cdc9f567aec74a72918fd060283911406750cbc9fd28c1316023deb6ce31a9",
"url": "https://files.pythonhosted.org/packages/49/50/965308c703f085f225db2886813b27e015b8b3438c350b22dd65b52c2a2c/fonttools-4.63.0-cp311-cp311-macosx_10_9_x86_64.whl"
},
"direct": false,
"name": "fonttools",
"purl": "pkg:pypi/fonttools@4.63.0",
"version": "4.63.0"
},
{
"artifact": {
"filename": "greenlet-3.2.4-cp311-cp311-macosx_11_0_universal2.whl",
"sha256": "96378df1de302bc38e99c3a9aa311967b7dc80ced1dcc6f171e99842987882a2",
"url": "https://files.pythonhosted.org/packages/a4/de/f28ced0a67749cac23fecb02b694f6473f47686dff6afaa211d186e2ef9c/greenlet-3.2.4-cp311-cp311-macosx_11_0_universal2.whl"
},
"direct": false,
"name": "greenlet",
"purl": "pkg:pypi/greenlet@3.2.4",
"version": "3.2.4"
},
{
"artifact": {
"filename": "idna-3.18-py3-none-any.whl",
"sha256": "7f952cbe720b688055e3f87de14f5c3e5fdaa8bc3928985c4077ca689de849a2",
"url": "https://files.pythonhosted.org/packages/1e/5e/d4e9f1a599fb8e573b7b87160658329fbf28d19eac2718f51fc3def3aa5a/idna-3.18-py3-none-any.whl"
},
"direct": false,
"name": "idna",
"purl": "pkg:pypi/idna@3.18",
"version": "3.18"
},
{
"artifact": {
"filename": "kiwisolver-1.5.0-cp311-cp311-macosx_10_9_x86_64.whl",
"sha256": "3c4923e404d6bcd91b6779c009542e5647fef32e4a5d75e115e3bbac6f2335eb",
"url": "https://files.pythonhosted.org/packages/11/60/37b4047a2af0cf5ef6d8b4b26e91829ae6fc6a2d1f74524bcb0e7cd28a32/kiwisolver-1.5.0-cp311-cp311-macosx_10_9_x86_64.whl"
},
"direct": false,
"name": "kiwisolver",
"purl": "pkg:pypi/kiwisolver@1.5.0",
"version": "1.5.0"
},
{
"artifact": {
"filename": "matplotlib-3.11.0-cp311-cp311-macosx_10_12_x86_64.whl",
"sha256": "f857524b442f0f36e641868ce2171aafa88cb0bc0644f4e1d8a5df9b32649fef",
"url": "https://files.pythonhosted.org/packages/ce/a2/78f662f1b18968531f67d3fcde1b7ea8496920bacd4f16ddb5b79d112e46/matplotlib-3.11.0-cp311-cp311-macosx_10_12_x86_64.whl"
},
"direct": true,
"name": "matplotlib",
"purl": "pkg:pypi/matplotlib@3.11.0",
"version": "3.11.0"
},
{
"artifact": {
"filename": "numpy-2.2.6-cp311-cp311-macosx_10_9_x86_64.whl",
"sha256": "f9f1adb22318e121c5c69a09142811a201ef17ab257a1e66ca3025065b7f53ae",
"url": "https://files.pythonhosted.org/packages/da/a8/4f83e2aa666a9fbf56d6118faaaf5f1974d456b1823fda0a176eff722839/numpy-2.2.6-cp311-cp311-macosx_10_9_x86_64.whl"
},
"direct": false,
"name": "numpy",
"purl": "pkg:pypi/numpy@2.2.6",
"version": "2.2.6"
},
{
"artifact": {
"filename": "packaging-26.2-py3-none-any.whl",
"sha256": "5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e",
"url": "https://files.pythonhosted.org/packages/df/b2/87e62e8c3e2f4b32e5fe99e0b86d576da1312593b39f47d8ceef365e95ed/packaging-26.2-py3-none-any.whl"
},
"direct": false,
"name": "packaging",
"purl": "pkg:pypi/packaging@26.2",
"version": "26.2"
},
{
"artifact": {
"filename": "pillow-12.2.0-cp311-cp311-macosx_10_10_x86_64.whl",
"sha256": "8be29e59487a79f173507c30ddf57e733a357f67881430449bb32614075a40ab",
"url": "https://files.pythonhosted.org/packages/68/e1/748f5663efe6edcfc4e74b2b93edfb9b8b99b67f21a854c3ae416500a2d9/pillow-12.2.0-cp311-cp311-macosx_10_10_x86_64.whl"
},
"direct": true,
"name": "pillow",
"purl": "pkg:pypi/pillow@12.2.0",
"version": "12.2.0"
},
{
"artifact": {
"filename": "playwright-1.61.0-py3-none-macosx_11_0_universal2.whl",
"sha256": "9f7de4536088d12037c13a52b7ea34b59270b78926bb56935070597ffac6b1af",
"url": "https://files.pythonhosted.org/packages/f4/44/323164cf5cd1647bdefce76ffce27651aadb959d089b48f53ea40918276e/playwright-1.61.0-py3-none-macosx_11_0_universal2.whl"
},
"direct": true,
"name": "playwright",
"purl": "pkg:pypi/playwright@1.61.0",
"version": "1.61.0"
},
{
"artifact": {
"filename": "pycparser-3.0-py3-none-any.whl",
"sha256": "b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992",
"url": "https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl"
},
"direct": false,
"name": "pycparser",
"purl": "pkg:pypi/pycparser@3.0",
"version": "3.0"
},
{
"artifact": {
"filename": "pydyf-0.12.1-py3-none-any.whl",
"sha256": "ea25b4e1fe7911195cb57067560daaa266639184e8335365cc3ee5214e7eaadc",
"url": "https://files.pythonhosted.org/packages/22/11/47efe2f66ba848a107adfd490b508f5c0cedc82127950553dca44d29e6c4/pydyf-0.12.1-py3-none-any.whl"
},
"direct": false,
"name": "pydyf",
"purl": "pkg:pypi/pydyf@0.12.1",
"version": "0.12.1"
},
{
"artifact": {
"filename": "pyee-13.0.1-py3-none-any.whl",
"sha256": "af2f8fede4171ef667dfded53f96e2ed0d6e6bd7ee3bb46437f77e3b57689228",
"url": "https://files.pythonhosted.org/packages/a0/c4/b4d4827c93ef43c01f599ef31453ccc1c132b353284fc6c87d535c233129/pyee-13.0.1-py3-none-any.whl"
},
"direct": false,
"name": "pyee",
"purl": "pkg:pypi/pyee@13.0.1",
"version": "13.0.1"
},
{
"artifact": {
"filename": "pyparsing-3.3.2-py3-none-any.whl",
"sha256": "850ba148bd908d7e2411587e247a1e4f0327839c40e2e5e6d05a007ecc69911d",
"url": "https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl"
},
"direct": false,
"name": "pyparsing",
"purl": "pkg:pypi/pyparsing@3.3.2",
"version": "3.3.2"
},
{
"artifact": {
"filename": "pyphen-0.17.2-py3-none-any.whl",
"sha256": "3a07fb017cb2341e1d9ff31b8634efb1ae4dc4b130468c7c39dd3d32e7c3affd",
"url": "https://files.pythonhosted.org/packages/7b/1f/c2142d2edf833a90728e5cdeb10bdbdc094dde8dbac078cee0cf33f5e11b/pyphen-0.17.2-py3-none-any.whl"
},
"direct": false,
"name": "pyphen",
"purl": "pkg:pypi/pyphen@0.17.2",
"version": "0.17.2"
},
{
"artifact": {
"filename": "python_dateutil-2.9.0.post0-py2.py3-none-any.whl",
"sha256": "a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427",
"url": "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl"
},
"direct": false,
"name": "python-dateutil",
"purl": "pkg:pypi/python-dateutil@2.9.0.post0",
"version": "2.9.0.post0"
},
{
"artifact": {
"filename": "reportlab-4.5.1-py3-none-any.whl",
"sha256": "06fce8cb56c83307cfa4909cdf4e6a2ddbb44e5d6ef4d2edca896d7e9769f091",
"url": "https://files.pythonhosted.org/packages/a7/45/ea7fad10122440de6e845568d106bffdc456ca0e8a1d8ae10b46016087e4/reportlab-4.5.1-py3-none-any.whl"
},
"direct": true,
"name": "reportlab",
"purl": "pkg:pypi/reportlab@4.5.1",
"version": "4.5.1"
},
{
"artifact": {
"filename": "requests-2.34.2-py3-none-any.whl",
"sha256": "2a0d60c172f83ac6ab31e4554906c0f3b3588d37b5cb939b1c061f4907e278e0",
"url": "https://files.pythonhosted.org/packages/a0/f4/c67b0b3f1b9245e8d266f0f112c500d50e5b4e83cb6f3b71b6528104182a/requests-2.34.2-py3-none-any.whl"
},
"direct": true,
"name": "requests",
"purl": "pkg:pypi/requests@2.34.2",
"version": "2.34.2"
},
{
"artifact": {
"filename": "six-1.17.0-py2.py3-none-any.whl",
"sha256": "4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274",
"url": "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl"
},
"direct": false,
"name": "six",
"purl": "pkg:pypi/six@1.17.0",
"version": "1.17.0"
},
{
"artifact": {
"filename": "tinycss2-1.5.1-py3-none-any.whl",
"sha256": "3415ba0f5839c062696996998176c4a3751d18b7edaaeeb658c9ce21ec150661",
"url": "https://files.pythonhosted.org/packages/60/45/c7b5c3168458db837e8ceab06dc77824e18202679d0463f0e8f002143a97/tinycss2-1.5.1-py3-none-any.whl"
},
"direct": false,
"name": "tinycss2",
"purl": "pkg:pypi/tinycss2@1.5.1",
"version": "1.5.1"
},
{
"artifact": {
"filename": "tinyhtml5-2.1.0-py3-none-any.whl",
"sha256": "6e11cfff38515834268daf89d5f85bbde0b6dd02e8d9e212d1385c2289b89f0a",
"url": "https://files.pythonhosted.org/packages/52/48/01695a036b695f83fea7aef6955d735db0f517b1c8e25ddb399ac0bdbcbf/tinyhtml5-2.1.0-py3-none-any.whl"
},
"direct": false,
"name": "tinyhtml5",
"purl": "pkg:pypi/tinyhtml5@2.1.0",
"version": "2.1.0"
},
{
"artifact": {
"filename": "typing_extensions-4.16.0-py3-none-any.whl",
"sha256": "481caa481374e813c1b176ada14e97f1f67a4539ce9cfeb3f350d78d6370c2e8",
"url": "https://files.pythonhosted.org/packages/49/d3/b8441a820a491ddfc024b0b0cf0393375b75ea13866d9c66727e54c2fc80/typing_extensions-4.16.0-py3-none-any.whl"
},
"direct": false,
"name": "typing-extensions",
"purl": "pkg:pypi/typing-extensions@4.16.0",
"version": "4.16.0"
},
{
"artifact": {
"filename": "urllib3-2.7.0-py3-none-any.whl",
"sha256": "9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897",
"url": "https://files.pythonhosted.org/packages/7f/3e/5db95bcf282c52709639744ca2a8b149baccf648e39c8cc87553df9eae0c/urllib3-2.7.0-py3-none-any.whl"
},
"direct": true,
"name": "urllib3",
"purl": "pkg:pypi/urllib3@2.7.0",
"version": "2.7.0"
},
{
"artifact": {
"filename": "weasyprint-69.0-py3-none-any.whl",
"sha256": "475951cfd917014de6d4d005caff48c6aa867e7e42b80cd5b16a0484a1609ee6",
"url": "https://files.pythonhosted.org/packages/93/cb/208525c6bd5033d7b2589b55e07bec23d9c61bb00703cbaf20ef52c3811f/weasyprint-69.0-py3-none-any.whl"
},
"direct": true,
"name": "weasyprint",
"purl": "pkg:pypi/weasyprint@69.0",
"version": "69.0"
},
{
"artifact": {
"filename": "webencodings-0.5.1-py2.py3-none-any.whl",
"sha256": "a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78",
"url": "https://files.pythonhosted.org/packages/f4/24/2a3e3df732393fed8b3ebf2ec078f05546de641fe1b667ee316ec1dcf3b7/webencodings-0.5.1-py2.py3-none-any.whl"
},
"direct": false,
"name": "webencodings",
"purl": "pkg:pypi/webencodings@0.5.1",
"version": "0.5.1"
},
{
"artifact": {
"filename": "zopfli-0.4.3-cp310-abi3-macosx_10_9_universal2.whl",
"sha256": "88f4fbe429aad72bc206275d81fab11a097e0f951a5848d1f51083c37ea73073",
"url": "https://files.pythonhosted.org/packages/a5/5f/b7d81b670daf990e15a0f7551da96c3c0700f69ae6d96b0245d6a19f51f3/zopfli-0.4.3-cp310-abi3-macosx_10_9_universal2.whl"
},
"direct": false,
"name": "zopfli",
"purl": "pkg:pypi/zopfli@0.4.3",
"version": "0.4.3"
}
],
"environment": {
"abi": "cp311",
"arch": "x86_64",
"implementation": "CPython",
"os": "macos",
"profile": "runtime",
"python_version": "3.11"
},
"evidence_class": "cross-target-pip-resolution-requiring-native-ci-confirmation",
"evidence_id": "runtime-macos-x86-cp311",
"normalization_notes": [],
"platform_policy": "Exact lowest-common lock with target-selected PyPI wheels",
"resolved_at": "2026-07-11T00:00:00Z",
"resolver": "pip / PyPI",
"schema_version": "1.0.0",
"source_date_epoch": 1783728000,
"source_environment": {
"implementation_name": "cpython",
"implementation_version": "3.14.4",
"os_name": "posix",
"platform_machine": "x86_64",
"platform_python_implementation": "CPython",
"platform_release": "6.19.14-101.fc44.x86_64",
"platform_system": "Linux",
"platform_version": "#1 SMP PREEMPT_DYNAMIC Fri May 8 18:24:24 UTC 2026",
"python_full_version": "3.14.4",
"python_version": "3.14",
"sys_platform": "linux"
},
"source_pip_version": "26.0.1",
"source_report_sha256": "30d88d4b7546fde1ed1a9a0b57a4b2cf688a4398bd146a301267ff7774fcf168"
}
@@ -0,0 +1,398 @@
{
"components": [
{
"artifact": {
"filename": "brotli-1.2.0-cp312-cp312-macosx_10_13_x86_64.whl",
"sha256": "7a61c06b334bd99bc5ae84f1eeb36bfe01400264b3c352f968c6e30a10f9d08b",
"url": "https://files.pythonhosted.org/packages/e1/2f/29c1459513cd35828e25531ebfcbf3e92a5e49f560b1777a9af7203eb46e/brotli-1.2.0-cp312-cp312-macosx_10_13_x86_64.whl"
},
"direct": false,
"name": "brotli",
"purl": "pkg:pypi/brotli@1.2.0",
"version": "1.2.0"
},
{
"artifact": {
"filename": "certifi-2026.6.17-py3-none-any.whl",
"sha256": "2227dcbaafe0d2f59279d1762ddddc37783ed4354594f194ffc31d20f41fc3db",
"url": "https://files.pythonhosted.org/packages/ef/2f/c5464532e965badff2f4c4c1a3a83f5697f0d7c407ed0cda44aaa99bb451/certifi-2026.6.17-py3-none-any.whl"
},
"direct": false,
"name": "certifi",
"purl": "pkg:pypi/certifi@2026.6.17",
"version": "2026.6.17"
},
{
"artifact": {
"filename": "cffi-2.1.0-cp312-cp312-macosx_10_15_x86_64.whl",
"sha256": "df2b82571a1b30f58a87bf4e5a9e78d2b1eff6c6ce8fd3aa3757221f93f0863f",
"url": "https://files.pythonhosted.org/packages/1e/85/990925db5df586ec90beb97529c853497e7f85ba0234830447faf41c3057/cffi-2.1.0-cp312-cp312-macosx_10_15_x86_64.whl"
},
"direct": false,
"name": "cffi",
"purl": "pkg:pypi/cffi@2.1.0",
"version": "2.1.0"
},
{
"artifact": {
"filename": "charset_normalizer-3.4.9-cp312-cp312-macosx_10_13_universal2.whl",
"sha256": "45b0cc4e3556cd875e09102988d1ab8356c998b596c9fced84547c8138b487a0",
"url": "https://files.pythonhosted.org/packages/70/4a/ecbd131485c07fcdfad54e28946d513e3da22ef3b4bd854dcafae54ec739/charset_normalizer-3.4.9-cp312-cp312-macosx_10_13_universal2.whl"
},
"direct": false,
"name": "charset-normalizer",
"purl": "pkg:pypi/charset-normalizer@3.4.9",
"version": "3.4.9"
},
{
"artifact": {
"filename": "contourpy-1.3.2-cp312-cp312-macosx_10_13_x86_64.whl",
"sha256": "4caf2bcd2969402bf77edc4cb6034c7dd7c0803213b3523f111eb7460a51b8d2",
"url": "https://files.pythonhosted.org/packages/34/f7/44785876384eff370c251d58fd65f6ad7f39adce4a093c934d4a67a7c6b6/contourpy-1.3.2-cp312-cp312-macosx_10_13_x86_64.whl"
},
"direct": false,
"name": "contourpy",
"purl": "pkg:pypi/contourpy@1.3.2",
"version": "1.3.2"
},
{
"artifact": {
"filename": "cryptography-48.0.1-cp311-abi3-macosx_10_9_universal2.whl",
"sha256": "3e4a1a3232eef2e6c732827d5722db29a0cc8b27af2a4d865b094cf954be9ca1",
"url": "https://files.pythonhosted.org/packages/1b/bc/ee4137cbbe105652c0ee4252792b78fc8e7afa4b8e61d9d5dc05a7f45731/cryptography-48.0.1-cp311-abi3-macosx_10_9_universal2.whl"
},
"direct": true,
"name": "cryptography",
"purl": "pkg:pypi/cryptography@48.0.1",
"version": "48.0.1"
},
{
"artifact": {
"filename": "cssselect2-0.9.0-py3-none-any.whl",
"sha256": "6a99e5f91f9a016a304dd929b0966ca464bcfda15177b6fb4a118fc0fb5d9563",
"url": "https://files.pythonhosted.org/packages/21/0e/8459ca4413e1a21a06c97d134bfaf18adfd27cea068813dc0faae06cbf00/cssselect2-0.9.0-py3-none-any.whl"
},
"direct": false,
"name": "cssselect2",
"purl": "pkg:pypi/cssselect2@0.9.0",
"version": "0.9.0"
},
{
"artifact": {
"filename": "cycler-0.12.1-py3-none-any.whl",
"sha256": "85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30",
"url": "https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl"
},
"direct": false,
"name": "cycler",
"purl": "pkg:pypi/cycler@0.12.1",
"version": "0.12.1"
},
{
"artifact": {
"filename": "fonttools-4.63.0-cp312-cp312-macosx_10_13_x86_64.whl",
"sha256": "a9faff9e0c1f76f9fd55899d2ce785832efebab37eb8ae13995853aef178bef0",
"url": "https://files.pythonhosted.org/packages/44/a0/c815bea63117fa63e4e1c01f8a1110d2112fa003f838e6467094ec2432ce/fonttools-4.63.0-cp312-cp312-macosx_10_13_x86_64.whl"
},
"direct": false,
"name": "fonttools",
"purl": "pkg:pypi/fonttools@4.63.0",
"version": "4.63.0"
},
{
"artifact": {
"filename": "greenlet-3.2.4-cp312-cp312-macosx_11_0_universal2.whl",
"sha256": "3b67ca49f54cede0186854a008109d6ee71f66bd57bb36abd6d0a0267b540cdd",
"url": "https://files.pythonhosted.org/packages/44/69/9b804adb5fd0671f367781560eb5eb586c4d495277c93bde4307b9e28068/greenlet-3.2.4-cp312-cp312-macosx_11_0_universal2.whl"
},
"direct": false,
"name": "greenlet",
"purl": "pkg:pypi/greenlet@3.2.4",
"version": "3.2.4"
},
{
"artifact": {
"filename": "idna-3.18-py3-none-any.whl",
"sha256": "7f952cbe720b688055e3f87de14f5c3e5fdaa8bc3928985c4077ca689de849a2",
"url": "https://files.pythonhosted.org/packages/1e/5e/d4e9f1a599fb8e573b7b87160658329fbf28d19eac2718f51fc3def3aa5a/idna-3.18-py3-none-any.whl"
},
"direct": false,
"name": "idna",
"purl": "pkg:pypi/idna@3.18",
"version": "3.18"
},
{
"artifact": {
"filename": "kiwisolver-1.5.0-cp312-cp312-macosx_10_13_x86_64.whl",
"sha256": "72ec46b7eba5b395e0a7b63025490d3214c11013f4aacb4f5e8d6c3041829588",
"url": "https://files.pythonhosted.org/packages/bf/d9/405320f8077e8e1c5c4bd6adc45e1e6edf6d727b6da7f2e2533cf58bff71/kiwisolver-1.5.0-cp312-cp312-macosx_10_13_x86_64.whl"
},
"direct": false,
"name": "kiwisolver",
"purl": "pkg:pypi/kiwisolver@1.5.0",
"version": "1.5.0"
},
{
"artifact": {
"filename": "matplotlib-3.11.0-cp312-cp312-macosx_10_13_x86_64.whl",
"sha256": "06b5872e9cf11adc8f589ded3ce11bc3e1061ad498259664fabc1f6615beb918",
"url": "https://files.pythonhosted.org/packages/da/17/f5276b496c61477a6c4fc5e7401f4bfe1c2e5ef7c6cd67896f2ade3809cb/matplotlib-3.11.0-cp312-cp312-macosx_10_13_x86_64.whl"
},
"direct": true,
"name": "matplotlib",
"purl": "pkg:pypi/matplotlib@3.11.0",
"version": "3.11.0"
},
{
"artifact": {
"filename": "numpy-2.2.6-cp312-cp312-macosx_10_13_x86_64.whl",
"sha256": "41c5a21f4a04fa86436124d388f6ed60a9343a6f767fced1a8a71c3fbca038ff",
"url": "https://files.pythonhosted.org/packages/82/5d/c00588b6cf18e1da539b45d3598d3557084990dcc4331960c15ee776ee41/numpy-2.2.6-cp312-cp312-macosx_10_13_x86_64.whl"
},
"direct": false,
"name": "numpy",
"purl": "pkg:pypi/numpy@2.2.6",
"version": "2.2.6"
},
{
"artifact": {
"filename": "packaging-26.2-py3-none-any.whl",
"sha256": "5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e",
"url": "https://files.pythonhosted.org/packages/df/b2/87e62e8c3e2f4b32e5fe99e0b86d576da1312593b39f47d8ceef365e95ed/packaging-26.2-py3-none-any.whl"
},
"direct": false,
"name": "packaging",
"purl": "pkg:pypi/packaging@26.2",
"version": "26.2"
},
{
"artifact": {
"filename": "pillow-12.2.0-cp312-cp312-macosx_10_13_x86_64.whl",
"sha256": "2d192a155bbcec180f8564f693e6fd9bccff5a7af9b32e2e4bf8c9c69dbad6b5",
"url": "https://files.pythonhosted.org/packages/58/be/7482c8a5ebebbc6470b3eb791812fff7d5e0216c2be3827b30b8bb6603ed/pillow-12.2.0-cp312-cp312-macosx_10_13_x86_64.whl"
},
"direct": true,
"name": "pillow",
"purl": "pkg:pypi/pillow@12.2.0",
"version": "12.2.0"
},
{
"artifact": {
"filename": "playwright-1.61.0-py3-none-macosx_11_0_universal2.whl",
"sha256": "9f7de4536088d12037c13a52b7ea34b59270b78926bb56935070597ffac6b1af",
"url": "https://files.pythonhosted.org/packages/f4/44/323164cf5cd1647bdefce76ffce27651aadb959d089b48f53ea40918276e/playwright-1.61.0-py3-none-macosx_11_0_universal2.whl"
},
"direct": true,
"name": "playwright",
"purl": "pkg:pypi/playwright@1.61.0",
"version": "1.61.0"
},
{
"artifact": {
"filename": "pycparser-3.0-py3-none-any.whl",
"sha256": "b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992",
"url": "https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl"
},
"direct": false,
"name": "pycparser",
"purl": "pkg:pypi/pycparser@3.0",
"version": "3.0"
},
{
"artifact": {
"filename": "pydyf-0.12.1-py3-none-any.whl",
"sha256": "ea25b4e1fe7911195cb57067560daaa266639184e8335365cc3ee5214e7eaadc",
"url": "https://files.pythonhosted.org/packages/22/11/47efe2f66ba848a107adfd490b508f5c0cedc82127950553dca44d29e6c4/pydyf-0.12.1-py3-none-any.whl"
},
"direct": false,
"name": "pydyf",
"purl": "pkg:pypi/pydyf@0.12.1",
"version": "0.12.1"
},
{
"artifact": {
"filename": "pyee-13.0.1-py3-none-any.whl",
"sha256": "af2f8fede4171ef667dfded53f96e2ed0d6e6bd7ee3bb46437f77e3b57689228",
"url": "https://files.pythonhosted.org/packages/a0/c4/b4d4827c93ef43c01f599ef31453ccc1c132b353284fc6c87d535c233129/pyee-13.0.1-py3-none-any.whl"
},
"direct": false,
"name": "pyee",
"purl": "pkg:pypi/pyee@13.0.1",
"version": "13.0.1"
},
{
"artifact": {
"filename": "pyparsing-3.3.2-py3-none-any.whl",
"sha256": "850ba148bd908d7e2411587e247a1e4f0327839c40e2e5e6d05a007ecc69911d",
"url": "https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl"
},
"direct": false,
"name": "pyparsing",
"purl": "pkg:pypi/pyparsing@3.3.2",
"version": "3.3.2"
},
{
"artifact": {
"filename": "pyphen-0.17.2-py3-none-any.whl",
"sha256": "3a07fb017cb2341e1d9ff31b8634efb1ae4dc4b130468c7c39dd3d32e7c3affd",
"url": "https://files.pythonhosted.org/packages/7b/1f/c2142d2edf833a90728e5cdeb10bdbdc094dde8dbac078cee0cf33f5e11b/pyphen-0.17.2-py3-none-any.whl"
},
"direct": false,
"name": "pyphen",
"purl": "pkg:pypi/pyphen@0.17.2",
"version": "0.17.2"
},
{
"artifact": {
"filename": "python_dateutil-2.9.0.post0-py2.py3-none-any.whl",
"sha256": "a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427",
"url": "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl"
},
"direct": false,
"name": "python-dateutil",
"purl": "pkg:pypi/python-dateutil@2.9.0.post0",
"version": "2.9.0.post0"
},
{
"artifact": {
"filename": "reportlab-4.5.1-py3-none-any.whl",
"sha256": "06fce8cb56c83307cfa4909cdf4e6a2ddbb44e5d6ef4d2edca896d7e9769f091",
"url": "https://files.pythonhosted.org/packages/a7/45/ea7fad10122440de6e845568d106bffdc456ca0e8a1d8ae10b46016087e4/reportlab-4.5.1-py3-none-any.whl"
},
"direct": true,
"name": "reportlab",
"purl": "pkg:pypi/reportlab@4.5.1",
"version": "4.5.1"
},
{
"artifact": {
"filename": "requests-2.34.2-py3-none-any.whl",
"sha256": "2a0d60c172f83ac6ab31e4554906c0f3b3588d37b5cb939b1c061f4907e278e0",
"url": "https://files.pythonhosted.org/packages/a0/f4/c67b0b3f1b9245e8d266f0f112c500d50e5b4e83cb6f3b71b6528104182a/requests-2.34.2-py3-none-any.whl"
},
"direct": true,
"name": "requests",
"purl": "pkg:pypi/requests@2.34.2",
"version": "2.34.2"
},
{
"artifact": {
"filename": "six-1.17.0-py2.py3-none-any.whl",
"sha256": "4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274",
"url": "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl"
},
"direct": false,
"name": "six",
"purl": "pkg:pypi/six@1.17.0",
"version": "1.17.0"
},
{
"artifact": {
"filename": "tinycss2-1.5.1-py3-none-any.whl",
"sha256": "3415ba0f5839c062696996998176c4a3751d18b7edaaeeb658c9ce21ec150661",
"url": "https://files.pythonhosted.org/packages/60/45/c7b5c3168458db837e8ceab06dc77824e18202679d0463f0e8f002143a97/tinycss2-1.5.1-py3-none-any.whl"
},
"direct": false,
"name": "tinycss2",
"purl": "pkg:pypi/tinycss2@1.5.1",
"version": "1.5.1"
},
{
"artifact": {
"filename": "tinyhtml5-2.1.0-py3-none-any.whl",
"sha256": "6e11cfff38515834268daf89d5f85bbde0b6dd02e8d9e212d1385c2289b89f0a",
"url": "https://files.pythonhosted.org/packages/52/48/01695a036b695f83fea7aef6955d735db0f517b1c8e25ddb399ac0bdbcbf/tinyhtml5-2.1.0-py3-none-any.whl"
},
"direct": false,
"name": "tinyhtml5",
"purl": "pkg:pypi/tinyhtml5@2.1.0",
"version": "2.1.0"
},
{
"artifact": {
"filename": "typing_extensions-4.16.0-py3-none-any.whl",
"sha256": "481caa481374e813c1b176ada14e97f1f67a4539ce9cfeb3f350d78d6370c2e8",
"url": "https://files.pythonhosted.org/packages/49/d3/b8441a820a491ddfc024b0b0cf0393375b75ea13866d9c66727e54c2fc80/typing_extensions-4.16.0-py3-none-any.whl"
},
"direct": false,
"name": "typing-extensions",
"purl": "pkg:pypi/typing-extensions@4.16.0",
"version": "4.16.0"
},
{
"artifact": {
"filename": "urllib3-2.7.0-py3-none-any.whl",
"sha256": "9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897",
"url": "https://files.pythonhosted.org/packages/7f/3e/5db95bcf282c52709639744ca2a8b149baccf648e39c8cc87553df9eae0c/urllib3-2.7.0-py3-none-any.whl"
},
"direct": true,
"name": "urllib3",
"purl": "pkg:pypi/urllib3@2.7.0",
"version": "2.7.0"
},
{
"artifact": {
"filename": "weasyprint-69.0-py3-none-any.whl",
"sha256": "475951cfd917014de6d4d005caff48c6aa867e7e42b80cd5b16a0484a1609ee6",
"url": "https://files.pythonhosted.org/packages/93/cb/208525c6bd5033d7b2589b55e07bec23d9c61bb00703cbaf20ef52c3811f/weasyprint-69.0-py3-none-any.whl"
},
"direct": true,
"name": "weasyprint",
"purl": "pkg:pypi/weasyprint@69.0",
"version": "69.0"
},
{
"artifact": {
"filename": "webencodings-0.5.1-py2.py3-none-any.whl",
"sha256": "a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78",
"url": "https://files.pythonhosted.org/packages/f4/24/2a3e3df732393fed8b3ebf2ec078f05546de641fe1b667ee316ec1dcf3b7/webencodings-0.5.1-py2.py3-none-any.whl"
},
"direct": false,
"name": "webencodings",
"purl": "pkg:pypi/webencodings@0.5.1",
"version": "0.5.1"
},
{
"artifact": {
"filename": "zopfli-0.4.3-cp310-abi3-macosx_10_9_universal2.whl",
"sha256": "88f4fbe429aad72bc206275d81fab11a097e0f951a5848d1f51083c37ea73073",
"url": "https://files.pythonhosted.org/packages/a5/5f/b7d81b670daf990e15a0f7551da96c3c0700f69ae6d96b0245d6a19f51f3/zopfli-0.4.3-cp310-abi3-macosx_10_9_universal2.whl"
},
"direct": false,
"name": "zopfli",
"purl": "pkg:pypi/zopfli@0.4.3",
"version": "0.4.3"
}
],
"environment": {
"abi": "cp312",
"arch": "x86_64",
"implementation": "CPython",
"os": "macos",
"profile": "runtime",
"python_version": "3.12"
},
"evidence_class": "cross-target-pip-resolution-requiring-native-ci-confirmation",
"evidence_id": "runtime-macos-x86-cp312",
"normalization_notes": [],
"platform_policy": "Exact lowest-common lock with target-selected PyPI wheels",
"resolved_at": "2026-07-11T00:00:00Z",
"resolver": "pip / PyPI",
"schema_version": "1.0.0",
"source_date_epoch": 1783728000,
"source_environment": {
"implementation_name": "cpython",
"implementation_version": "3.14.4",
"os_name": "posix",
"platform_machine": "x86_64",
"platform_python_implementation": "CPython",
"platform_release": "6.19.14-101.fc44.x86_64",
"platform_system": "Linux",
"platform_version": "#1 SMP PREEMPT_DYNAMIC Fri May 8 18:24:24 UTC 2026",
"python_full_version": "3.14.4",
"python_version": "3.14",
"sys_platform": "linux"
},
"source_pip_version": "26.0.1",
"source_report_sha256": "bfa997627364323c8ba3fc35aeb013d9979147bfa4b15b579e45349cdd30384f"
}
@@ -0,0 +1,398 @@
{
"components": [
{
"artifact": {
"filename": "brotli-1.2.0-cp311-cp311-win_amd64.whl",
"sha256": "022426c9e99fd65d9475dce5c195526f04bb8be8907607e27e747893f6ee3e24",
"url": "https://files.pythonhosted.org/packages/02/8b/ecb5761b989629a4758c394b9301607a5880de61ee2ee5fe104b87149ebc/brotli-1.2.0-cp311-cp311-win_amd64.whl"
},
"direct": false,
"name": "brotli",
"purl": "pkg:pypi/brotli@1.2.0",
"version": "1.2.0"
},
{
"artifact": {
"filename": "certifi-2026.6.17-py3-none-any.whl",
"sha256": "2227dcbaafe0d2f59279d1762ddddc37783ed4354594f194ffc31d20f41fc3db",
"url": "https://files.pythonhosted.org/packages/ef/2f/c5464532e965badff2f4c4c1a3a83f5697f0d7c407ed0cda44aaa99bb451/certifi-2026.6.17-py3-none-any.whl"
},
"direct": false,
"name": "certifi",
"purl": "pkg:pypi/certifi@2026.6.17",
"version": "2026.6.17"
},
{
"artifact": {
"filename": "cffi-2.1.0-cp311-cp311-win_amd64.whl",
"sha256": "4f26194e3d95e06501b942642855aed4f953d55e95d7d01b7c4483db3ecff458",
"url": "https://files.pythonhosted.org/packages/f9/c8/6c2de1d55cf35ef8b92885d5ef280790f0fb9634d87ea1cc315176aecd61/cffi-2.1.0-cp311-cp311-win_amd64.whl"
},
"direct": false,
"name": "cffi",
"purl": "pkg:pypi/cffi@2.1.0",
"version": "2.1.0"
},
{
"artifact": {
"filename": "charset_normalizer-3.4.9-cp311-cp311-win_amd64.whl",
"sha256": "6366a16e1a25018694d6a5d784d09b046edc9eac40ea2b54065c3052672516a1",
"url": "https://files.pythonhosted.org/packages/ed/a1/e29995109e455dc8eff8d0fac6ae509be39561318a7cfeac5d33ad029213/charset_normalizer-3.4.9-cp311-cp311-win_amd64.whl"
},
"direct": false,
"name": "charset-normalizer",
"purl": "pkg:pypi/charset-normalizer@3.4.9",
"version": "3.4.9"
},
{
"artifact": {
"filename": "contourpy-1.3.2-cp311-cp311-win_amd64.whl",
"sha256": "5ebac872ba09cb8f2131c46b8739a7ff71de28a24c869bcad554477eb089a878",
"url": "https://files.pythonhosted.org/packages/5e/fe/4029038b4e1c4485cef18e480b0e2cd2d755448bb071eb9977caac80b77b/contourpy-1.3.2-cp311-cp311-win_amd64.whl"
},
"direct": false,
"name": "contourpy",
"purl": "pkg:pypi/contourpy@1.3.2",
"version": "1.3.2"
},
{
"artifact": {
"filename": "cryptography-48.0.1-cp311-abi3-win_amd64.whl",
"sha256": "39489bfca54c7a1f6b297efcd8bc608ab92d16c4ca631b0cad4da46724588b24",
"url": "https://files.pythonhosted.org/packages/c5/69/0572c77dbace6fef72f33755bd52ea399c71367250d366237f8691826b9e/cryptography-48.0.1-cp311-abi3-win_amd64.whl"
},
"direct": true,
"name": "cryptography",
"purl": "pkg:pypi/cryptography@48.0.1",
"version": "48.0.1"
},
{
"artifact": {
"filename": "cssselect2-0.9.0-py3-none-any.whl",
"sha256": "6a99e5f91f9a016a304dd929b0966ca464bcfda15177b6fb4a118fc0fb5d9563",
"url": "https://files.pythonhosted.org/packages/21/0e/8459ca4413e1a21a06c97d134bfaf18adfd27cea068813dc0faae06cbf00/cssselect2-0.9.0-py3-none-any.whl"
},
"direct": false,
"name": "cssselect2",
"purl": "pkg:pypi/cssselect2@0.9.0",
"version": "0.9.0"
},
{
"artifact": {
"filename": "cycler-0.12.1-py3-none-any.whl",
"sha256": "85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30",
"url": "https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl"
},
"direct": false,
"name": "cycler",
"purl": "pkg:pypi/cycler@0.12.1",
"version": "0.12.1"
},
{
"artifact": {
"filename": "fonttools-4.63.0-cp311-cp311-win_amd64.whl",
"sha256": "063e08bd17bd5a90127a14123de0d6a952dbc847695fd98b63c043d58057f90c",
"url": "https://files.pythonhosted.org/packages/08/60/defa5e69641db890a63be281f41345f4c33b157824eaf0b9fad3e08b0dcb/fonttools-4.63.0-cp311-cp311-win_amd64.whl"
},
"direct": false,
"name": "fonttools",
"purl": "pkg:pypi/fonttools@4.63.0",
"version": "4.63.0"
},
{
"artifact": {
"filename": "greenlet-3.2.4-cp311-cp311-win_amd64.whl",
"sha256": "9c40adce87eaa9ddb593ccb0fa6a07caf34015a29bf8d344811665b573138db9",
"url": "https://files.pythonhosted.org/packages/d8/0f/30aef242fcab550b0b3520b8e3561156857c94288f0332a79928c31a52cf/greenlet-3.2.4-cp311-cp311-win_amd64.whl"
},
"direct": false,
"name": "greenlet",
"purl": "pkg:pypi/greenlet@3.2.4",
"version": "3.2.4"
},
{
"artifact": {
"filename": "idna-3.18-py3-none-any.whl",
"sha256": "7f952cbe720b688055e3f87de14f5c3e5fdaa8bc3928985c4077ca689de849a2",
"url": "https://files.pythonhosted.org/packages/1e/5e/d4e9f1a599fb8e573b7b87160658329fbf28d19eac2718f51fc3def3aa5a/idna-3.18-py3-none-any.whl"
},
"direct": false,
"name": "idna",
"purl": "pkg:pypi/idna@3.18",
"version": "3.18"
},
{
"artifact": {
"filename": "kiwisolver-1.5.0-cp311-cp311-win_amd64.whl",
"sha256": "beb7f344487cdcb9e1efe4b7a29681b74d34c08f0043a327a74da852a6749e7b",
"url": "https://files.pythonhosted.org/packages/be/6c/28f17390b62b8f2f520e2915095b3c94d88681ecf0041e75389d9667f202/kiwisolver-1.5.0-cp311-cp311-win_amd64.whl"
},
"direct": false,
"name": "kiwisolver",
"purl": "pkg:pypi/kiwisolver@1.5.0",
"version": "1.5.0"
},
{
"artifact": {
"filename": "matplotlib-3.11.0-cp311-cp311-win_amd64.whl",
"sha256": "70a5b3e9a5dab708c0f039709ae7c68d5b4d254e291ef76492cdba230c8bb5e4",
"url": "https://files.pythonhosted.org/packages/d0/9f/970fcbf381e82ec66fdf5da8ea76e2e9240f61a24011ce9fd1d42c37ac2d/matplotlib-3.11.0-cp311-cp311-win_amd64.whl"
},
"direct": true,
"name": "matplotlib",
"purl": "pkg:pypi/matplotlib@3.11.0",
"version": "3.11.0"
},
{
"artifact": {
"filename": "numpy-2.2.6-cp311-cp311-win_amd64.whl",
"sha256": "e8213002e427c69c45a52bbd94163084025f533a55a59d6f9c5b820774ef3303",
"url": "https://files.pythonhosted.org/packages/31/0a/f354fb7176b81747d870f7991dc763e157a934c717b67b58456bc63da3df/numpy-2.2.6-cp311-cp311-win_amd64.whl"
},
"direct": false,
"name": "numpy",
"purl": "pkg:pypi/numpy@2.2.6",
"version": "2.2.6"
},
{
"artifact": {
"filename": "packaging-26.2-py3-none-any.whl",
"sha256": "5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e",
"url": "https://files.pythonhosted.org/packages/df/b2/87e62e8c3e2f4b32e5fe99e0b86d576da1312593b39f47d8ceef365e95ed/packaging-26.2-py3-none-any.whl"
},
"direct": false,
"name": "packaging",
"purl": "pkg:pypi/packaging@26.2",
"version": "26.2"
},
{
"artifact": {
"filename": "pillow-12.2.0-cp311-cp311-win_amd64.whl",
"sha256": "6e6b2a0c538fc200b38ff9eb6628228b77908c319a005815f2dde585a0664b60",
"url": "https://files.pythonhosted.org/packages/69/42/836b6f3cd7f3e5fa10a1f1a5420447c17966044c8fbf589cc0452d5502db/pillow-12.2.0-cp311-cp311-win_amd64.whl"
},
"direct": true,
"name": "pillow",
"purl": "pkg:pypi/pillow@12.2.0",
"version": "12.2.0"
},
{
"artifact": {
"filename": "playwright-1.61.0-py3-none-win_amd64.whl",
"sha256": "35c6cc4589a5d00964a59d7b3e59641e0aac0c02f15479a7af77d20f6bc79597",
"url": "https://files.pythonhosted.org/packages/6c/fd/2b78036e5fbe9d5f5645bbe08a1eac7160c51243c0093963edbcf67c35d9/playwright-1.61.0-py3-none-win_amd64.whl"
},
"direct": true,
"name": "playwright",
"purl": "pkg:pypi/playwright@1.61.0",
"version": "1.61.0"
},
{
"artifact": {
"filename": "pycparser-3.0-py3-none-any.whl",
"sha256": "b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992",
"url": "https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl"
},
"direct": false,
"name": "pycparser",
"purl": "pkg:pypi/pycparser@3.0",
"version": "3.0"
},
{
"artifact": {
"filename": "pydyf-0.12.1-py3-none-any.whl",
"sha256": "ea25b4e1fe7911195cb57067560daaa266639184e8335365cc3ee5214e7eaadc",
"url": "https://files.pythonhosted.org/packages/22/11/47efe2f66ba848a107adfd490b508f5c0cedc82127950553dca44d29e6c4/pydyf-0.12.1-py3-none-any.whl"
},
"direct": false,
"name": "pydyf",
"purl": "pkg:pypi/pydyf@0.12.1",
"version": "0.12.1"
},
{
"artifact": {
"filename": "pyee-13.0.1-py3-none-any.whl",
"sha256": "af2f8fede4171ef667dfded53f96e2ed0d6e6bd7ee3bb46437f77e3b57689228",
"url": "https://files.pythonhosted.org/packages/a0/c4/b4d4827c93ef43c01f599ef31453ccc1c132b353284fc6c87d535c233129/pyee-13.0.1-py3-none-any.whl"
},
"direct": false,
"name": "pyee",
"purl": "pkg:pypi/pyee@13.0.1",
"version": "13.0.1"
},
{
"artifact": {
"filename": "pyparsing-3.3.2-py3-none-any.whl",
"sha256": "850ba148bd908d7e2411587e247a1e4f0327839c40e2e5e6d05a007ecc69911d",
"url": "https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl"
},
"direct": false,
"name": "pyparsing",
"purl": "pkg:pypi/pyparsing@3.3.2",
"version": "3.3.2"
},
{
"artifact": {
"filename": "pyphen-0.17.2-py3-none-any.whl",
"sha256": "3a07fb017cb2341e1d9ff31b8634efb1ae4dc4b130468c7c39dd3d32e7c3affd",
"url": "https://files.pythonhosted.org/packages/7b/1f/c2142d2edf833a90728e5cdeb10bdbdc094dde8dbac078cee0cf33f5e11b/pyphen-0.17.2-py3-none-any.whl"
},
"direct": false,
"name": "pyphen",
"purl": "pkg:pypi/pyphen@0.17.2",
"version": "0.17.2"
},
{
"artifact": {
"filename": "python_dateutil-2.9.0.post0-py2.py3-none-any.whl",
"sha256": "a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427",
"url": "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl"
},
"direct": false,
"name": "python-dateutil",
"purl": "pkg:pypi/python-dateutil@2.9.0.post0",
"version": "2.9.0.post0"
},
{
"artifact": {
"filename": "reportlab-4.5.1-py3-none-any.whl",
"sha256": "06fce8cb56c83307cfa4909cdf4e6a2ddbb44e5d6ef4d2edca896d7e9769f091",
"url": "https://files.pythonhosted.org/packages/a7/45/ea7fad10122440de6e845568d106bffdc456ca0e8a1d8ae10b46016087e4/reportlab-4.5.1-py3-none-any.whl"
},
"direct": true,
"name": "reportlab",
"purl": "pkg:pypi/reportlab@4.5.1",
"version": "4.5.1"
},
{
"artifact": {
"filename": "requests-2.34.2-py3-none-any.whl",
"sha256": "2a0d60c172f83ac6ab31e4554906c0f3b3588d37b5cb939b1c061f4907e278e0",
"url": "https://files.pythonhosted.org/packages/a0/f4/c67b0b3f1b9245e8d266f0f112c500d50e5b4e83cb6f3b71b6528104182a/requests-2.34.2-py3-none-any.whl"
},
"direct": true,
"name": "requests",
"purl": "pkg:pypi/requests@2.34.2",
"version": "2.34.2"
},
{
"artifact": {
"filename": "six-1.17.0-py2.py3-none-any.whl",
"sha256": "4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274",
"url": "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl"
},
"direct": false,
"name": "six",
"purl": "pkg:pypi/six@1.17.0",
"version": "1.17.0"
},
{
"artifact": {
"filename": "tinycss2-1.5.1-py3-none-any.whl",
"sha256": "3415ba0f5839c062696996998176c4a3751d18b7edaaeeb658c9ce21ec150661",
"url": "https://files.pythonhosted.org/packages/60/45/c7b5c3168458db837e8ceab06dc77824e18202679d0463f0e8f002143a97/tinycss2-1.5.1-py3-none-any.whl"
},
"direct": false,
"name": "tinycss2",
"purl": "pkg:pypi/tinycss2@1.5.1",
"version": "1.5.1"
},
{
"artifact": {
"filename": "tinyhtml5-2.1.0-py3-none-any.whl",
"sha256": "6e11cfff38515834268daf89d5f85bbde0b6dd02e8d9e212d1385c2289b89f0a",
"url": "https://files.pythonhosted.org/packages/52/48/01695a036b695f83fea7aef6955d735db0f517b1c8e25ddb399ac0bdbcbf/tinyhtml5-2.1.0-py3-none-any.whl"
},
"direct": false,
"name": "tinyhtml5",
"purl": "pkg:pypi/tinyhtml5@2.1.0",
"version": "2.1.0"
},
{
"artifact": {
"filename": "typing_extensions-4.16.0-py3-none-any.whl",
"sha256": "481caa481374e813c1b176ada14e97f1f67a4539ce9cfeb3f350d78d6370c2e8",
"url": "https://files.pythonhosted.org/packages/49/d3/b8441a820a491ddfc024b0b0cf0393375b75ea13866d9c66727e54c2fc80/typing_extensions-4.16.0-py3-none-any.whl"
},
"direct": false,
"name": "typing-extensions",
"purl": "pkg:pypi/typing-extensions@4.16.0",
"version": "4.16.0"
},
{
"artifact": {
"filename": "urllib3-2.7.0-py3-none-any.whl",
"sha256": "9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897",
"url": "https://files.pythonhosted.org/packages/7f/3e/5db95bcf282c52709639744ca2a8b149baccf648e39c8cc87553df9eae0c/urllib3-2.7.0-py3-none-any.whl"
},
"direct": true,
"name": "urllib3",
"purl": "pkg:pypi/urllib3@2.7.0",
"version": "2.7.0"
},
{
"artifact": {
"filename": "weasyprint-69.0-py3-none-any.whl",
"sha256": "475951cfd917014de6d4d005caff48c6aa867e7e42b80cd5b16a0484a1609ee6",
"url": "https://files.pythonhosted.org/packages/93/cb/208525c6bd5033d7b2589b55e07bec23d9c61bb00703cbaf20ef52c3811f/weasyprint-69.0-py3-none-any.whl"
},
"direct": true,
"name": "weasyprint",
"purl": "pkg:pypi/weasyprint@69.0",
"version": "69.0"
},
{
"artifact": {
"filename": "webencodings-0.5.1-py2.py3-none-any.whl",
"sha256": "a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78",
"url": "https://files.pythonhosted.org/packages/f4/24/2a3e3df732393fed8b3ebf2ec078f05546de641fe1b667ee316ec1dcf3b7/webencodings-0.5.1-py2.py3-none-any.whl"
},
"direct": false,
"name": "webencodings",
"purl": "pkg:pypi/webencodings@0.5.1",
"version": "0.5.1"
},
{
"artifact": {
"filename": "zopfli-0.4.3-cp310-abi3-win_amd64.whl",
"sha256": "921c2c9907f4364963848da5ad194b46d68865e07fdb975d04fd09bc42d47357",
"url": "https://files.pythonhosted.org/packages/9d/3a/4ff4fdead77ef30f5832b38a47eb7a1283e98b3c678576b83f8fdfff53eb/zopfli-0.4.3-cp310-abi3-win_amd64.whl"
},
"direct": false,
"name": "zopfli",
"purl": "pkg:pypi/zopfli@0.4.3",
"version": "0.4.3"
}
],
"environment": {
"abi": "cp311",
"arch": "amd64",
"implementation": "CPython",
"os": "windows",
"profile": "runtime",
"python_version": "3.11"
},
"evidence_class": "cross-target-pip-resolution-requiring-native-ci-confirmation",
"evidence_id": "runtime-windows-cp311",
"normalization_notes": [],
"platform_policy": "Exact lowest-common lock with target-selected PyPI wheels",
"resolved_at": "2026-07-11T00:00:00Z",
"resolver": "pip / PyPI",
"schema_version": "1.0.0",
"source_date_epoch": 1783728000,
"source_environment": {
"implementation_name": "cpython",
"implementation_version": "3.14.4",
"os_name": "posix",
"platform_machine": "x86_64",
"platform_python_implementation": "CPython",
"platform_release": "6.19.14-101.fc44.x86_64",
"platform_system": "Linux",
"platform_version": "#1 SMP PREEMPT_DYNAMIC Fri May 8 18:24:24 UTC 2026",
"python_full_version": "3.14.4",
"python_version": "3.14",
"sys_platform": "linux"
},
"source_pip_version": "26.0.1",
"source_report_sha256": "7bfaab3191fdb340609218ae93d23b1daa49c22ae4eebc7bd38859ae234fe80e"
}
@@ -0,0 +1,398 @@
{
"components": [
{
"artifact": {
"filename": "brotli-1.2.0-cp312-cp312-win_amd64.whl",
"sha256": "b35c13ce241abdd44cb8ca70683f20c0c079728a36a996297adb5334adfc1c44",
"url": "https://files.pythonhosted.org/packages/1c/4e/bc1dcac9498859d5e353c9b153627a3752868a9d5f05ce8dedd81a2354ab/brotli-1.2.0-cp312-cp312-win_amd64.whl"
},
"direct": false,
"name": "brotli",
"purl": "pkg:pypi/brotli@1.2.0",
"version": "1.2.0"
},
{
"artifact": {
"filename": "certifi-2026.6.17-py3-none-any.whl",
"sha256": "2227dcbaafe0d2f59279d1762ddddc37783ed4354594f194ffc31d20f41fc3db",
"url": "https://files.pythonhosted.org/packages/ef/2f/c5464532e965badff2f4c4c1a3a83f5697f0d7c407ed0cda44aaa99bb451/certifi-2026.6.17-py3-none-any.whl"
},
"direct": false,
"name": "certifi",
"purl": "pkg:pypi/certifi@2026.6.17",
"version": "2026.6.17"
},
{
"artifact": {
"filename": "cffi-2.1.0-cp312-cp312-win_amd64.whl",
"sha256": "c97f080ea627e2863524c5af3836e2270b5f5dfff1f104392b959f8df0c5d384",
"url": "https://files.pythonhosted.org/packages/a8/eb/f636456ff21a83fc13c032b58cc5dde061691546ac79efa284b2989b7982/cffi-2.1.0-cp312-cp312-win_amd64.whl"
},
"direct": false,
"name": "cffi",
"purl": "pkg:pypi/cffi@2.1.0",
"version": "2.1.0"
},
{
"artifact": {
"filename": "charset_normalizer-3.4.9-cp312-cp312-win_amd64.whl",
"sha256": "4b3dac63058cc36820b0dd072f89898604e2d39686fe05321729d00d8ac185a0",
"url": "https://files.pythonhosted.org/packages/80/cc/f920afd1a23c58ccd53c1d36085a71893a4737ff5e66e0371efab6809850/charset_normalizer-3.4.9-cp312-cp312-win_amd64.whl"
},
"direct": false,
"name": "charset-normalizer",
"purl": "pkg:pypi/charset-normalizer@3.4.9",
"version": "3.4.9"
},
{
"artifact": {
"filename": "contourpy-1.3.2-cp312-cp312-win_amd64.whl",
"sha256": "8c942a01d9163e2e5cfb05cb66110121b8d07ad438a17f9e766317bcb62abf73",
"url": "https://files.pythonhosted.org/packages/01/c8/fadd0b92ffa7b5eb5949bf340a63a4a496a6930a6c37a7ba0f12acb076d6/contourpy-1.3.2-cp312-cp312-win_amd64.whl"
},
"direct": false,
"name": "contourpy",
"purl": "pkg:pypi/contourpy@1.3.2",
"version": "1.3.2"
},
{
"artifact": {
"filename": "cryptography-48.0.1-cp311-abi3-win_amd64.whl",
"sha256": "39489bfca54c7a1f6b297efcd8bc608ab92d16c4ca631b0cad4da46724588b24",
"url": "https://files.pythonhosted.org/packages/c5/69/0572c77dbace6fef72f33755bd52ea399c71367250d366237f8691826b9e/cryptography-48.0.1-cp311-abi3-win_amd64.whl"
},
"direct": true,
"name": "cryptography",
"purl": "pkg:pypi/cryptography@48.0.1",
"version": "48.0.1"
},
{
"artifact": {
"filename": "cssselect2-0.9.0-py3-none-any.whl",
"sha256": "6a99e5f91f9a016a304dd929b0966ca464bcfda15177b6fb4a118fc0fb5d9563",
"url": "https://files.pythonhosted.org/packages/21/0e/8459ca4413e1a21a06c97d134bfaf18adfd27cea068813dc0faae06cbf00/cssselect2-0.9.0-py3-none-any.whl"
},
"direct": false,
"name": "cssselect2",
"purl": "pkg:pypi/cssselect2@0.9.0",
"version": "0.9.0"
},
{
"artifact": {
"filename": "cycler-0.12.1-py3-none-any.whl",
"sha256": "85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30",
"url": "https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl"
},
"direct": false,
"name": "cycler",
"purl": "pkg:pypi/cycler@0.12.1",
"version": "0.12.1"
},
{
"artifact": {
"filename": "fonttools-4.63.0-cp312-cp312-win_amd64.whl",
"sha256": "59ac449f8cca9b4ffa08d2e7bbadad87ce710d69d1eda5c3c1ce579baa987272",
"url": "https://files.pythonhosted.org/packages/87/36/cccb9bc2a6ab63d1b2980374f0dca72ce95ae267c9b4cfe77455bb70d0d4/fonttools-4.63.0-cp312-cp312-win_amd64.whl"
},
"direct": false,
"name": "fonttools",
"purl": "pkg:pypi/fonttools@4.63.0",
"version": "4.63.0"
},
{
"artifact": {
"filename": "greenlet-3.2.4-cp312-cp312-win_amd64.whl",
"sha256": "a7d4e128405eea3814a12cc2605e0e6aedb4035bf32697f72deca74de4105e02",
"url": "https://files.pythonhosted.org/packages/e9/08/b0814846b79399e585f974bbeebf5580fbe59e258ea7be64d9dfb253c84f/greenlet-3.2.4-cp312-cp312-win_amd64.whl"
},
"direct": false,
"name": "greenlet",
"purl": "pkg:pypi/greenlet@3.2.4",
"version": "3.2.4"
},
{
"artifact": {
"filename": "idna-3.18-py3-none-any.whl",
"sha256": "7f952cbe720b688055e3f87de14f5c3e5fdaa8bc3928985c4077ca689de849a2",
"url": "https://files.pythonhosted.org/packages/1e/5e/d4e9f1a599fb8e573b7b87160658329fbf28d19eac2718f51fc3def3aa5a/idna-3.18-py3-none-any.whl"
},
"direct": false,
"name": "idna",
"purl": "pkg:pypi/idna@3.18",
"version": "3.18"
},
{
"artifact": {
"filename": "kiwisolver-1.5.0-cp312-cp312-win_amd64.whl",
"sha256": "f18c2d9782259a6dc132fdc7a63c168cbc74b35284b6d75c673958982a378384",
"url": "https://files.pythonhosted.org/packages/ad/cf/0348374369ca588f8fe9c338fae49fa4e16eeb10ffb3d012f23a54578a9e/kiwisolver-1.5.0-cp312-cp312-win_amd64.whl"
},
"direct": false,
"name": "kiwisolver",
"purl": "pkg:pypi/kiwisolver@1.5.0",
"version": "1.5.0"
},
{
"artifact": {
"filename": "matplotlib-3.11.0-cp312-cp312-win_amd64.whl",
"sha256": "ac6f1ef39f3d0f9e2463303013094992cdbe0f85f43bc54155bc472b2042768e",
"url": "https://files.pythonhosted.org/packages/e5/2f/a58a4443a4d052a4ea77557478336aefc26c7981f6408d37adba763aa758/matplotlib-3.11.0-cp312-cp312-win_amd64.whl"
},
"direct": true,
"name": "matplotlib",
"purl": "pkg:pypi/matplotlib@3.11.0",
"version": "3.11.0"
},
{
"artifact": {
"filename": "numpy-2.2.6-cp312-cp312-win_amd64.whl",
"sha256": "c1f9540be57940698ed329904db803cf7a402f3fc200bfe599334c9bd84a40b2",
"url": "https://files.pythonhosted.org/packages/36/fa/8c9210162ca1b88529ab76b41ba02d433fd54fecaf6feb70ef9f124683f1/numpy-2.2.6-cp312-cp312-win_amd64.whl"
},
"direct": false,
"name": "numpy",
"purl": "pkg:pypi/numpy@2.2.6",
"version": "2.2.6"
},
{
"artifact": {
"filename": "packaging-26.2-py3-none-any.whl",
"sha256": "5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e",
"url": "https://files.pythonhosted.org/packages/df/b2/87e62e8c3e2f4b32e5fe99e0b86d576da1312593b39f47d8ceef365e95ed/packaging-26.2-py3-none-any.whl"
},
"direct": false,
"name": "packaging",
"purl": "pkg:pypi/packaging@26.2",
"version": "26.2"
},
{
"artifact": {
"filename": "pillow-12.2.0-cp312-cp312-win_amd64.whl",
"sha256": "7f84204dee22a783350679a0333981df803dac21a0190d706a50475e361c93f5",
"url": "https://files.pythonhosted.org/packages/5d/7b/25a221d2c761c6a8ae21bfa3874988ff2583e19cf8a27bf2fee358df7942/pillow-12.2.0-cp312-cp312-win_amd64.whl"
},
"direct": true,
"name": "pillow",
"purl": "pkg:pypi/pillow@12.2.0",
"version": "12.2.0"
},
{
"artifact": {
"filename": "playwright-1.61.0-py3-none-win_amd64.whl",
"sha256": "35c6cc4589a5d00964a59d7b3e59641e0aac0c02f15479a7af77d20f6bc79597",
"url": "https://files.pythonhosted.org/packages/6c/fd/2b78036e5fbe9d5f5645bbe08a1eac7160c51243c0093963edbcf67c35d9/playwright-1.61.0-py3-none-win_amd64.whl"
},
"direct": true,
"name": "playwright",
"purl": "pkg:pypi/playwright@1.61.0",
"version": "1.61.0"
},
{
"artifact": {
"filename": "pycparser-3.0-py3-none-any.whl",
"sha256": "b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992",
"url": "https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl"
},
"direct": false,
"name": "pycparser",
"purl": "pkg:pypi/pycparser@3.0",
"version": "3.0"
},
{
"artifact": {
"filename": "pydyf-0.12.1-py3-none-any.whl",
"sha256": "ea25b4e1fe7911195cb57067560daaa266639184e8335365cc3ee5214e7eaadc",
"url": "https://files.pythonhosted.org/packages/22/11/47efe2f66ba848a107adfd490b508f5c0cedc82127950553dca44d29e6c4/pydyf-0.12.1-py3-none-any.whl"
},
"direct": false,
"name": "pydyf",
"purl": "pkg:pypi/pydyf@0.12.1",
"version": "0.12.1"
},
{
"artifact": {
"filename": "pyee-13.0.1-py3-none-any.whl",
"sha256": "af2f8fede4171ef667dfded53f96e2ed0d6e6bd7ee3bb46437f77e3b57689228",
"url": "https://files.pythonhosted.org/packages/a0/c4/b4d4827c93ef43c01f599ef31453ccc1c132b353284fc6c87d535c233129/pyee-13.0.1-py3-none-any.whl"
},
"direct": false,
"name": "pyee",
"purl": "pkg:pypi/pyee@13.0.1",
"version": "13.0.1"
},
{
"artifact": {
"filename": "pyparsing-3.3.2-py3-none-any.whl",
"sha256": "850ba148bd908d7e2411587e247a1e4f0327839c40e2e5e6d05a007ecc69911d",
"url": "https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl"
},
"direct": false,
"name": "pyparsing",
"purl": "pkg:pypi/pyparsing@3.3.2",
"version": "3.3.2"
},
{
"artifact": {
"filename": "pyphen-0.17.2-py3-none-any.whl",
"sha256": "3a07fb017cb2341e1d9ff31b8634efb1ae4dc4b130468c7c39dd3d32e7c3affd",
"url": "https://files.pythonhosted.org/packages/7b/1f/c2142d2edf833a90728e5cdeb10bdbdc094dde8dbac078cee0cf33f5e11b/pyphen-0.17.2-py3-none-any.whl"
},
"direct": false,
"name": "pyphen",
"purl": "pkg:pypi/pyphen@0.17.2",
"version": "0.17.2"
},
{
"artifact": {
"filename": "python_dateutil-2.9.0.post0-py2.py3-none-any.whl",
"sha256": "a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427",
"url": "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl"
},
"direct": false,
"name": "python-dateutil",
"purl": "pkg:pypi/python-dateutil@2.9.0.post0",
"version": "2.9.0.post0"
},
{
"artifact": {
"filename": "reportlab-4.5.1-py3-none-any.whl",
"sha256": "06fce8cb56c83307cfa4909cdf4e6a2ddbb44e5d6ef4d2edca896d7e9769f091",
"url": "https://files.pythonhosted.org/packages/a7/45/ea7fad10122440de6e845568d106bffdc456ca0e8a1d8ae10b46016087e4/reportlab-4.5.1-py3-none-any.whl"
},
"direct": true,
"name": "reportlab",
"purl": "pkg:pypi/reportlab@4.5.1",
"version": "4.5.1"
},
{
"artifact": {
"filename": "requests-2.34.2-py3-none-any.whl",
"sha256": "2a0d60c172f83ac6ab31e4554906c0f3b3588d37b5cb939b1c061f4907e278e0",
"url": "https://files.pythonhosted.org/packages/a0/f4/c67b0b3f1b9245e8d266f0f112c500d50e5b4e83cb6f3b71b6528104182a/requests-2.34.2-py3-none-any.whl"
},
"direct": true,
"name": "requests",
"purl": "pkg:pypi/requests@2.34.2",
"version": "2.34.2"
},
{
"artifact": {
"filename": "six-1.17.0-py2.py3-none-any.whl",
"sha256": "4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274",
"url": "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl"
},
"direct": false,
"name": "six",
"purl": "pkg:pypi/six@1.17.0",
"version": "1.17.0"
},
{
"artifact": {
"filename": "tinycss2-1.5.1-py3-none-any.whl",
"sha256": "3415ba0f5839c062696996998176c4a3751d18b7edaaeeb658c9ce21ec150661",
"url": "https://files.pythonhosted.org/packages/60/45/c7b5c3168458db837e8ceab06dc77824e18202679d0463f0e8f002143a97/tinycss2-1.5.1-py3-none-any.whl"
},
"direct": false,
"name": "tinycss2",
"purl": "pkg:pypi/tinycss2@1.5.1",
"version": "1.5.1"
},
{
"artifact": {
"filename": "tinyhtml5-2.1.0-py3-none-any.whl",
"sha256": "6e11cfff38515834268daf89d5f85bbde0b6dd02e8d9e212d1385c2289b89f0a",
"url": "https://files.pythonhosted.org/packages/52/48/01695a036b695f83fea7aef6955d735db0f517b1c8e25ddb399ac0bdbcbf/tinyhtml5-2.1.0-py3-none-any.whl"
},
"direct": false,
"name": "tinyhtml5",
"purl": "pkg:pypi/tinyhtml5@2.1.0",
"version": "2.1.0"
},
{
"artifact": {
"filename": "typing_extensions-4.16.0-py3-none-any.whl",
"sha256": "481caa481374e813c1b176ada14e97f1f67a4539ce9cfeb3f350d78d6370c2e8",
"url": "https://files.pythonhosted.org/packages/49/d3/b8441a820a491ddfc024b0b0cf0393375b75ea13866d9c66727e54c2fc80/typing_extensions-4.16.0-py3-none-any.whl"
},
"direct": false,
"name": "typing-extensions",
"purl": "pkg:pypi/typing-extensions@4.16.0",
"version": "4.16.0"
},
{
"artifact": {
"filename": "urllib3-2.7.0-py3-none-any.whl",
"sha256": "9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897",
"url": "https://files.pythonhosted.org/packages/7f/3e/5db95bcf282c52709639744ca2a8b149baccf648e39c8cc87553df9eae0c/urllib3-2.7.0-py3-none-any.whl"
},
"direct": true,
"name": "urllib3",
"purl": "pkg:pypi/urllib3@2.7.0",
"version": "2.7.0"
},
{
"artifact": {
"filename": "weasyprint-69.0-py3-none-any.whl",
"sha256": "475951cfd917014de6d4d005caff48c6aa867e7e42b80cd5b16a0484a1609ee6",
"url": "https://files.pythonhosted.org/packages/93/cb/208525c6bd5033d7b2589b55e07bec23d9c61bb00703cbaf20ef52c3811f/weasyprint-69.0-py3-none-any.whl"
},
"direct": true,
"name": "weasyprint",
"purl": "pkg:pypi/weasyprint@69.0",
"version": "69.0"
},
{
"artifact": {
"filename": "webencodings-0.5.1-py2.py3-none-any.whl",
"sha256": "a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78",
"url": "https://files.pythonhosted.org/packages/f4/24/2a3e3df732393fed8b3ebf2ec078f05546de641fe1b667ee316ec1dcf3b7/webencodings-0.5.1-py2.py3-none-any.whl"
},
"direct": false,
"name": "webencodings",
"purl": "pkg:pypi/webencodings@0.5.1",
"version": "0.5.1"
},
{
"artifact": {
"filename": "zopfli-0.4.3-cp310-abi3-win_amd64.whl",
"sha256": "921c2c9907f4364963848da5ad194b46d68865e07fdb975d04fd09bc42d47357",
"url": "https://files.pythonhosted.org/packages/9d/3a/4ff4fdead77ef30f5832b38a47eb7a1283e98b3c678576b83f8fdfff53eb/zopfli-0.4.3-cp310-abi3-win_amd64.whl"
},
"direct": false,
"name": "zopfli",
"purl": "pkg:pypi/zopfli@0.4.3",
"version": "0.4.3"
}
],
"environment": {
"abi": "cp312",
"arch": "amd64",
"implementation": "CPython",
"os": "windows",
"profile": "runtime",
"python_version": "3.12"
},
"evidence_class": "cross-target-pip-resolution-requiring-native-ci-confirmation",
"evidence_id": "runtime-windows-cp312",
"normalization_notes": [],
"platform_policy": "Exact lowest-common lock with target-selected PyPI wheels",
"resolved_at": "2026-07-11T00:00:00Z",
"resolver": "pip / PyPI",
"schema_version": "1.0.0",
"source_date_epoch": 1783728000,
"source_environment": {
"implementation_name": "cpython",
"implementation_version": "3.14.4",
"os_name": "posix",
"platform_machine": "x86_64",
"platform_python_implementation": "CPython",
"platform_release": "6.19.14-101.fc44.x86_64",
"platform_system": "Linux",
"platform_version": "#1 SMP PREEMPT_DYNAMIC Fri May 8 18:24:24 UTC 2026",
"python_full_version": "3.14.4",
"python_version": "3.14",
"sys_platform": "linux"
},
"source_pip_version": "26.0.1",
"source_report_sha256": "5a2059fd79482fc3d95e0cb2b2d30852d68651aa18268966820a7117b178e388"
}
File diff suppressed because one or more lines are too long
@@ -0,0 +1,41 @@
{
"schema_version": "1.0.0",
"subject": "claude-ads-external-runtime-dependencies",
"scope": "Non-Python payloads and system libraries are outside requirements.lock and the Python CycloneDX component closure.",
"sources": [
{"id": "playwright-python-intro", "url": "https://playwright.dev/python/docs/intro", "publisher": "Microsoft Playwright", "accessed_at": "2026-07-11"},
{"id": "playwright-browsers", "url": "https://playwright.dev/python/docs/browsers", "publisher": "Microsoft Playwright", "accessed_at": "2026-07-11"},
{"id": "playwright-ci", "url": "https://playwright.dev/python/docs/ci", "publisher": "Microsoft Playwright", "accessed_at": "2026-07-11"},
{"id": "weasyprint-69-install", "url": "https://doc.courtbouillon.org/weasyprint/v69.0/first_steps.html#installation", "publisher": "CourtBouillon", "accessed_at": "2026-07-11"},
{"id": "cyclonedx-1.5-json", "url": "https://cyclonedx.org/docs/1.5/json/", "publisher": "OWASP CycloneDX", "accessed_at": "2026-07-11"}
],
"dependencies": [
{
"id": "playwright-browser-payload",
"consumer": "Playwright browser capture helpers",
"acquisition": "Operator-managed `python -m playwright install chromium` or an approved prebuilt browser image.",
"supported_execution_hosts": [
"Windows 11 or Windows Server 2019+",
"macOS 14+",
"Debian 12 or 13",
"Ubuntu 22.04, 24.04, or 26.04"
],
"included_in_python_lock": false,
"included_in_python_sbom": false,
"cross_platform_feature_attestation": "not-claimed",
"source_ids": ["playwright-python-intro", "playwright-browsers", "playwright-ci"],
"release_requirement": "Install and attest the browser payload and OS sandbox/egress controls separately before browser capture."
},
{
"id": "weasyprint-native-libraries",
"consumer": "WeasyPrint PDF rendering",
"acquisition": "Operator-managed OS packages/libraries required by the selected WeasyPrint host.",
"included_in_python_lock": false,
"included_in_python_sbom": false,
"cross_platform_feature_attestation": "not-claimed",
"source_ids": ["weasyprint-69-install", "cyclonedx-1.5-json"],
"tested_surface": "A deterministic real-PDF integration smoke runs on the Ubuntu full-test job only.",
"release_requirement": "Validate Pango and other WeasyPrint native/system dependencies on the actual execution host before enabling PDF output."
}
]
}
@@ -0,0 +1,45 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://github.com/AI-Marketing-Hub/claude-ads/schemas/dependency-inventory.v1.json",
"title": "Claude Ads per-target dependency inventory",
"type": "object",
"additionalProperties": false,
"required": ["schema_version", "inventory_id", "resolved_at", "resolution", "manifests", "direct_requirements", "targets", "component_catalog", "dependency_edges", "bundled_notices"],
"properties": {
"schema_version": {"const": "1.0.0"},
"inventory_id": {"const": "claude-ads-python-dependencies"},
"resolved_at": {"const": "2026-07-11T00:00:00Z", "format": "date-time"},
"resolution": {
"type": "object", "additionalProperties": false,
"required": ["index", "managed_lock_python_range", "supported_python_versions", "implementation", "source_date_epoch", "policy", "reviewed_metadata_sha256", "third_party_notices_sha256", "target_evidence_class", "subject_binding"],
"properties": {
"index": {"const": "https://pypi.org/simple"}, "managed_lock_python_range": {"const": ">=3.11,<3.13"},
"supported_python_versions": {"const": ["3.11", "3.12"]}, "implementation": {"const": "CPython"},
"source_date_epoch": {"const": 1783728000}, "policy": {"type": "string", "minLength": 1},
"reviewed_metadata_sha256": {"$ref": "#/$defs/sha256"}, "third_party_notices_sha256": {"$ref": "#/$defs/sha256"},
"target_evidence_class": {"const": "cross-target-pip-resolution-requiring-native-ci-confirmation"},
"subject_binding": {"type": "string", "minLength": 1}
}
},
"manifests": {"type": "array", "minItems": 5, "maxItems": 5, "uniqueItems": true, "items": {"$ref": "#/$defs/manifest"}},
"direct_requirements": {"type": "array", "minItems": 1, "items": {"$ref": "#/$defs/directRequirement"}},
"targets": {"type": "array", "minItems": 16, "maxItems": 16, "items": {"$ref": "#/$defs/target"}},
"component_catalog": {"type": "array", "minItems": 1, "items": {"$ref": "#/$defs/catalogComponent"}},
"dependency_edges": {"type": "array", "minItems": 1, "items": {"$ref": "#/$defs/edge"}},
"bundled_notices": {"type": "array", "minItems": 39, "maxItems": 39, "items": {"$ref": "#/$defs/bundledNotice"}}
},
"$defs": {
"sha256": {"type": "string", "pattern": "^[0-9a-f]{64}$"},
"name": {"type": "string", "pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$"},
"manifest": {"type": "object", "additionalProperties": false, "required": ["path", "sha256"], "properties": {"path": {"enum": ["requirements.txt", "requirements-dev.txt", "requirements.lock", "requirements-dev.lock", "pyproject.toml"]}, "sha256": {"$ref": "#/$defs/sha256"}}},
"directRequirement": {"type": "object", "additionalProperties": false, "required": ["name", "requirement", "source", "profile"], "properties": {"name": {"$ref": "#/$defs/name"}, "requirement": {"type": "string", "minLength": 1}, "source": {"type": "string", "minLength": 1}, "profile": {"enum": ["runtime", "development"]}}},
"artifact": {"type": "object", "additionalProperties": false, "required": ["filename", "url", "sha256"], "properties": {"filename": {"type": "string", "minLength": 1}, "url": {"type": "string", "pattern": "^https://files.pythonhosted.org/"}, "sha256": {"$ref": "#/$defs/sha256"}}},
"targetComponent": {"type": "object", "additionalProperties": false, "required": ["name", "version", "direct", "purl", "artifact"], "properties": {"name": {"$ref": "#/$defs/name"}, "version": {"type": "string", "minLength": 1}, "direct": {"type": "boolean"}, "purl": {"type": "string", "pattern": "^pkg:pypi/[a-z0-9-]+@[^/?#]+$"}, "artifact": {"$ref": "#/$defs/artifact"}}},
"target": {"type": "object", "additionalProperties": false, "required": ["id", "profile", "python_version", "implementation", "os", "arch", "abi", "platform_policy", "resolver", "resolution_evidence_sha256", "components"], "properties": {"id": {"type": "string", "minLength": 1}, "profile": {"enum": ["runtime", "development"]}, "python_version": {"enum": ["3.11", "3.12"]}, "implementation": {"const": "CPython"}, "os": {"enum": ["linux", "macos", "windows"]}, "arch": {"enum": ["x86_64", "arm64", "amd64"]}, "abi": {"enum": ["cp311", "cp312"]}, "platform_policy": {"const": "Exact lowest-common lock with target-selected PyPI wheels"}, "resolver": {"const": "pip / PyPI"}, "resolution_evidence_sha256": {"$ref": "#/$defs/sha256"}, "components": {"type": "array", "minItems": 1, "items": {"$ref": "#/$defs/targetComponent"}}}},
"licenseProvenance": {"type": "object", "additionalProperties": false, "required": ["kind", "metadata_url", "evidence_sha256"], "properties": {"kind": {"const": "publisher-metadata-review"}, "metadata_url": {"type": "string", "pattern": "^https://pypi.org/pypi/.+/json$"}, "evidence_sha256": {"$ref": "#/$defs/sha256"}}},
"catalogComponent": {"type": "object", "additionalProperties": false, "required": ["name", "version", "purl", "license_expression", "license_provenance", "target_occurrences", "bundled_notice_ids"], "properties": {"name": {"$ref": "#/$defs/name"}, "version": {"type": "string", "minLength": 1}, "purl": {"type": "string", "minLength": 1}, "license_expression": {"type": "string", "minLength": 2}, "license_provenance": {"$ref": "#/$defs/licenseProvenance"}, "target_occurrences": {"type": "array", "minItems": 1, "uniqueItems": true, "items": {"type": "string", "minLength": 1}}, "bundled_notice_ids": {"type": "array", "uniqueItems": true, "items": {"type": "string", "minLength": 1}}}},
"edge": {"type": "object", "additionalProperties": false, "required": ["profile", "from", "to", "requirement", "specifier", "marker", "extras", "publisher_metadata_sha256"], "properties": {"profile": {"enum": ["runtime", "development"]}, "from": {"$ref": "#/$defs/name"}, "to": {"$ref": "#/$defs/name"}, "requirement": {"type": "string", "minLength": 1}, "specifier": {"type": "string"}, "marker": {"type": ["string", "null"]}, "extras": {"type": "array", "uniqueItems": true, "items": {"type": "string"}}, "publisher_metadata_sha256": {"$ref": "#/$defs/sha256"}}},
"bundledNotice": {"type": "object", "additionalProperties": false, "required": ["id", "component", "summary", "documents", "documentless_artifact_sha256s"], "properties": {"id": {"type": "string", "minLength": 1}, "component": {"$ref": "#/$defs/name"}, "summary": {"type": "string", "minLength": 1}, "documents": {"type": "array", "items": {"$ref": "#/$defs/noticeDocument"}}, "documentless_artifact_sha256s": {"type": "array", "uniqueItems": true, "items": {"$ref": "#/$defs/sha256"}}}},
"noticeDocument": {"type": "object", "additionalProperties": false, "required": ["path", "sha256", "text", "artifact_sha256s"], "properties": {"path": {"type": "string", "minLength": 1}, "sha256": {"$ref": "#/$defs/sha256"}, "text": {"type": "string", "minLength": 1}, "artifact_sha256s": {"type": "array", "minItems": 1, "uniqueItems": true, "items": {"$ref": "#/$defs/sha256"}}}}
}
}
@@ -0,0 +1,18 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://github.com/AI-Marketing-Hub/claude-ads/schemas/target-evidence.v1.json",
"title": "Claude Ads normalized target resolver evidence",
"type": "object",
"additionalProperties": false,
"required": ["schema_version", "evidence_id", "evidence_class", "resolved_at", "source_date_epoch", "source_report_sha256", "source_pip_version", "source_environment", "normalization_notes", "environment", "resolver", "platform_policy", "components"],
"properties": {
"schema_version": {"const": "1.0.0"}, "evidence_id": {"type": "string", "minLength": 1},
"evidence_class": {"const": "cross-target-pip-resolution-requiring-native-ci-confirmation"},
"resolved_at": {"const": "2026-07-11T00:00:00Z"}, "source_date_epoch": {"const": 1783728000},
"source_report_sha256": {"type": "string", "pattern": "^[0-9a-f]{64}$"}, "source_pip_version": {"type": "string", "pattern": "^[0-9]+(?:\\.[0-9]+){1,3}$"},
"source_environment": {"type": "object"}, "normalization_notes": {"type": "array", "items": {"type": "string"}},
"environment": {"type": "object"}, "resolver": {"const": "pip / PyPI"},
"platform_policy": {"const": "Exact lowest-common lock with target-selected PyPI wheels"},
"components": {"type": "array", "minItems": 1}
}
}
Vendored
+67 -33
View File
@@ -177,6 +177,19 @@ function Main {
Write-Host "X Git is required for -Source git." -ForegroundColor Red
exit 1
}
$ManagedPython = $null
$PythonTarget = $null
if ($AllowPip -and -not $NoDeps) {
$ManagedPython = Get-Command python -ErrorAction SilentlyContinue
if (-not $ManagedPython) {
throw "python not found. Re-run with -NoDeps to install without Python helpers."
}
$PythonTarget = & $ManagedPython.Source -c "import platform,sys; print('|'.join((sys.implementation.name, f'{sys.version_info.major}.{sys.version_info.minor}', platform.system().lower(), platform.machine().lower())))"
if ($PythonTarget -notin @("cpython|3.11|windows|amd64", "cpython|3.12|windows|amd64")) {
throw "No verified dependency lock target for $PythonTarget. Re-run with -NoDeps; moving-range fallback is disabled."
}
$DependencyTargetId = if ($PythonTarget -eq "cpython|3.11|windows|amd64") { "runtime-windows-cp311" } else { "runtime-windows-cp312" }
}
Write-Host "OK Distribution source: $Source" -ForegroundColor Green
# Create directories
@@ -263,37 +276,30 @@ function Main {
}
Copy-Item "$SourceDir\requirements.txt" -Destination "$SkillDirResolved\requirements.txt" -Force
[void]$OwnedFiles.Add((Join-Path $SkillDirResolved "requirements.txt"))
Copy-Item "$SourceDir\requirements.lock" -Destination "$SkillDirResolved\requirements.lock" -Force
[void]$OwnedFiles.Add((Join-Path $SkillDirResolved "requirements.lock"))
$CoreSource = Join-Path $SourceDir "claude_ads_core"
$CoreDir = Join-Path $ScriptsDir "claude_ads_core"
Get-ChildItem $CoreSource -File -Recurse | Where-Object {
$_.Extension -in @(".py", ".json") -and $_.FullName -notmatch "[\\/]__pycache__[\\/]"
} | ForEach-Object {
$Relative = [System.IO.Path]::GetRelativePath($CoreSource, $_.FullName)
$Destination = Join-Path $CoreDir $Relative
New-Item -ItemType Directory -Path (Split-Path $Destination -Parent) -Force | Out-Null
Copy-Item $_.FullName -Destination $Destination -Force
[void]$OwnedFiles.Add($Destination)
}
[void]$OwnedDirs.Add($CoreDir)
[void]$OwnedDirs.Add($ScriptsDir)
}
Write-Host ""
# Commit ownership before dependency installation so a later pip
# failure is always recoverable with uninstall.
$VenvDir = Join-Path $SkillDirResolved ".venv"
if ($AllowPip -and -not $NoDeps) {
Write-Host "Installing Python dependencies into a managed virtual environment..."
$ErrorActionPreference = "Continue"
$Python = Get-Command python -ErrorAction SilentlyContinue
$VenvDir = Join-Path $SkillDirResolved ".venv"
if ($Python) {
& $Python.Source -m venv $VenvDir
if ($LASTEXITCODE -eq 0) {
& "$VenvDir\Scripts\python.exe" -m pip install -q $SourceDir -r "$SkillDirResolved\requirements.txt"
}
}
if ($Python -and $LASTEXITCODE -eq 0) {
[void]$RecursiveDirs.Add($VenvDir)
Write-Host " OK Python dependencies installed in $VenvDir" -ForegroundColor Green
} else {
Remove-Item $VenvDir -Recurse -Force -ErrorAction SilentlyContinue
Write-Host " Warning: dependency install failed; create a virtual environment manually." -ForegroundColor Yellow
}
$ErrorActionPreference = "Stop"
} elseif ($NoDeps) {
Write-Host "i Skipping Python dependencies (-NoDeps)." -ForegroundColor Yellow
} else {
Write-Host "i Skipping Python dependencies - $HostLabel host runtime may not execute Python skills directly." -ForegroundColor Yellow
Write-Host " If you need PDF reports / landing-page analysis / screenshots, install manually:"
Write-Host " python -m venv <env>; <env>\Scripts\python.exe -m pip install -r $SkillDirResolved\requirements.txt"
[void]$RecursiveDirs.Add($VenvDir)
[void]$OwnedFiles.Add((Join-Path $SkillDirResolved "managed-runtime-receipt.json"))
}
[void]$OwnedDirs.Add($SkillDirResolved)
$Manifest = @{
version = 1
@@ -305,17 +311,45 @@ function Main {
$Manifest | ConvertTo-Json -Depth 4 | Set-Content -Path $ManifestPath -Encoding UTF8
Write-Host "OK Ownership manifest: $ManifestPath" -ForegroundColor Green
# Check for banana-claude (image generation provider)
Write-Host ""
$BananaPath = Join-Path $SkillBase "banana\SKILL.md"
if (Test-Path $BananaPath) {
Write-Host " OK banana-claude detected (image generation ready)" -ForegroundColor Green
if ($AllowPip -and -not $NoDeps) {
$ReceiptPath = Join-Path $SkillDirResolved "managed-runtime-receipt.json"
Remove-Item $ReceiptPath -Force -ErrorAction SilentlyContinue
Write-Host "Installing exact hashed Python dependencies into a managed virtual environment..."
$ErrorActionPreference = "Continue"
if ($ManagedPython) {
& $ManagedPython.Source -m venv $VenvDir
if ($LASTEXITCODE -eq 0) {
& "$VenvDir\Scripts\python.exe" -m pip install -q --ignore-installed --report "$VenvDir\install-report.json" --require-hashes --only-binary=:all: -r "$SkillDirResolved\requirements.lock"
}
if ($LASTEXITCODE -eq 0) {
$SitePackages = & "$VenvDir\Scripts\python.exe" -c "import sysconfig; print(sysconfig.get_paths()['purelib'])"
$ScriptsDir | Set-Content -Path (Join-Path $SitePackages "claude-ads-core.pth") -Encoding UTF8
& "$VenvDir\Scripts\python.exe" -m pip check
}
if ($LASTEXITCODE -eq 0) {
& "$VenvDir\Scripts\python.exe" "$ScriptsDir\write_install_receipt.py" --inventory "$SourceDir\control-plane\manifests\dependency-inventory.json" --lock "$SkillDirResolved\requirements.lock" --evidence "$SourceDir\control-plane\dependency-evidence\$DependencyTargetId.json" --pip-report "$VenvDir\install-report.json" --target-id $DependencyTargetId --output "$SkillDirResolved\managed-runtime-receipt.json"
}
}
if ($ManagedPython -and $LASTEXITCODE -eq 0) {
Write-Host " OK Exact locked Python dependencies installed in $VenvDir" -ForegroundColor Green
} else {
Remove-Item $VenvDir -Recurse -Force -ErrorAction SilentlyContinue
Remove-Item $ReceiptPath -Force -ErrorAction SilentlyContinue
throw "Exact hashed dependency installation failed; no moving-range fallback was attempted."
}
$ErrorActionPreference = "Stop"
} elseif ($NoDeps) {
Write-Host "i Skipping Python dependencies (-NoDeps)." -ForegroundColor Yellow
} else {
Write-Host " Warning: banana-claude not installed. Image generation requires it." -ForegroundColor Yellow
Write-Host " Install: https://github.com/AgriciDaniel/banana-claude"
Write-Host " Then run: /banana setup (to configure API key)"
Write-Host "i Skipping Python dependencies - $HostLabel host runtime may not execute Python skills directly." -ForegroundColor Yellow
Write-Host " Python helpers require the packaged exact-hash lock on a supported CPython wheel target."
}
Write-Host ""
Write-Host "i Image generation requires an explicitly configured eligible provider/model"
Write-Host " with capability evidence; Claude Ads does not probe or recommend a default."
Write-Host ""
Write-Host "Claude Ads installed successfully for $HostLabel!" -ForegroundColor Green
Write-Host ""
+78 -24
View File
@@ -128,7 +128,7 @@ Overrides:
--agent-dir=<path> Override the target's default agent install root
--source=<mode> auto uses this checkout when available, otherwise authenticated Git
--repo-dir=<path> Local checkout to install (implies --source=local)
--no-deps Do not create the managed Python virtual environment
--no-deps Do not create the verified managed Python environment
Examples:
bash install.sh
@@ -278,6 +278,32 @@ main() {
}
fi
# Fail before any destination mutation or dependency network access when
# the requested managed runtime is outside the supported wheel-lock matrix.
if [ "${ALLOW_PIP}" = "1" ] && [ "$INSTALL_DEPS" = "1" ]; then
command -v python3 >/dev/null 2>&1 || {
echo "✗ python3 not found. Re-run with --no-deps to install without Python helpers." >&2
return 1
}
PYTHON_TARGET=$(python3 -c 'import platform,sys
system=platform.system().lower(); machine=platform.machine().lower(); libc_name,libc_version=platform.libc_ver(); mac_version=platform.mac_ver()[0]
def pair(value):
try: return tuple(int(part) for part in value.split(".")[:2])
except ValueError: return (0,0)
boundary_ok=(system=="linux" and libc_name=="glibc" and pair(libc_version)>=(2,17)) or (system=="darwin" and pair(mac_version)>=(11,0))
print("|".join((sys.implementation.name, f"{sys.version_info.major}.{sys.version_info.minor}", system, machine, libc_name or "none", libc_version or mac_version or "none", "supported" if boundary_ok else "unsupported")))')
case "$PYTHON_TARGET" in
cpython\|3.11\|linux\|x86_64\|glibc\|*\|supported) DEPENDENCY_TARGET_ID="runtime-linux-cp311" ;;
cpython\|3.12\|linux\|x86_64\|glibc\|*\|supported) DEPENDENCY_TARGET_ID="runtime-linux-cp312" ;;
cpython\|3.11\|darwin\|x86_64\|*\|*\|supported) DEPENDENCY_TARGET_ID="runtime-macos-x86-cp311" ;;
cpython\|3.12\|darwin\|x86_64\|*\|*\|supported) DEPENDENCY_TARGET_ID="runtime-macos-x86-cp312" ;;
cpython\|3.11\|darwin\|arm64\|*\|*\|supported) DEPENDENCY_TARGET_ID="runtime-macos-arm-cp311" ;;
cpython\|3.12\|darwin\|arm64\|*\|*\|supported) DEPENDENCY_TARGET_ID="runtime-macos-arm-cp312" ;;
*\|linux\|*\|musl\|*\|unsupported) echo "✗ Managed dependencies require glibc >=2.17; musl Linux is unsupported. Re-run with --no-deps." >&2; return 1 ;;
*) echo "✗ No verified dependency lock target for ${PYTHON_TARGET}. Re-run with --no-deps; moving-range fallback is disabled." >&2; return 1 ;;
esac
fi
echo "════════════════════════════════════════"
echo "║ Claude Ads - Installer ║"
echo "║ Target: ${HOST_LABEL}"
@@ -404,50 +430,78 @@ main() {
install_file "$source_file" "${SCRIPTS_DIR}/$(basename -- "$source_file")"
done
install_file "${SOURCE_DIR}/requirements.txt" "${SKILL_DIR}/requirements.txt"
install_file "${SOURCE_DIR}/requirements.lock" "${SKILL_DIR}/requirements.lock"
CORE_DIR=$(ensure_owned_dir "${SCRIPTS_DIR}/claude_ads_core")
for source_file in "${SOURCE_DIR}/claude_ads_core/"*.py; do
[ -f "$source_file" ] || continue
install_file "$source_file" "${CORE_DIR}/$(basename -- "$source_file")"
done
ADAPTERS_DIR=$(ensure_owned_dir "${CORE_DIR}/adapters")
for source_file in "${SOURCE_DIR}/claude_ads_core/adapters/"*.py; do
[ -f "$source_file" ] || continue
install_file "$source_file" "${ADAPTERS_DIR}/$(basename -- "$source_file")"
done
SCHEMAS_DIR=$(ensure_owned_dir "${CORE_DIR}/schemas/v1")
for source_file in "${SOURCE_DIR}/claude_ads_core/schemas/v1/"*.json; do
[ -f "$source_file" ] || continue
install_file "$source_file" "${SCHEMAS_DIR}/$(basename -- "$source_file")"
done
record_dir "$SCHEMAS_DIR"
record_dir "$ADAPTERS_DIR"
record_dir "$CORE_DIR"
record_dir "${SCRIPTS_DIR}"
fi
# Commit ownership before dependency installation. A later pip failure is
# therefore recoverable with uninstall and never leaves unowned files.
if [ "${ALLOW_PIP}" = "1" ] && [ "$INSTALL_DEPS" = "1" ]; then
VENV_DIR="${SKILL_DIR}/.venv"
printf 'R\t%s\n' "$VENV_DIR" >> "$MANIFEST_TMP"
record_file "${SKILL_DIR}/managed-runtime-receipt.json"
fi
record_dir "${SKILL_DIR}"
mv -f "$MANIFEST_TMP" "$MANIFEST_PATH"
MANIFEST_TMP=""
echo "✓ Ownership manifest: ${MANIFEST_PATH}"
# Install Python dependencies — only for hosts that explicitly support
# Python execution (claude, codex). Other targets skip the pip step.
echo ""
if [ "${ALLOW_PIP}" = "1" ] && [ "$INSTALL_DEPS" = "1" ]; then
echo "→ Installing Python dependencies into a managed virtual environment..."
RECEIPT_PATH="${SKILL_DIR}/managed-runtime-receipt.json"
if ! rm -f -- "$RECEIPT_PATH"; then
echo "✗ Could not invalidate the prior managed runtime receipt." >&2
return 1
fi
echo "→ Installing exact hashed Python dependencies into a managed virtual environment..."
if command -v python3 >/dev/null 2>&1; then
VENV_DIR="${SKILL_DIR}/.venv"
if python3 -m venv "${VENV_DIR}" \
&& "${VENV_DIR}/bin/python" -m pip install -q "${SOURCE_DIR}" -r "${SKILL_DIR}/requirements.txt"; then
printf 'R\t%s\n' "$VENV_DIR" >> "$MANIFEST_TMP"
echo " ✓ Python dependencies installed in ${VENV_DIR}"
&& "${VENV_DIR}/bin/python" -m pip install -q --ignore-installed --report "${VENV_DIR}/install-report.json" --require-hashes --only-binary=:all: -r "${SKILL_DIR}/requirements.lock" \
&& SITE_PACKAGES=$("${VENV_DIR}/bin/python" -c 'import sysconfig; print(sysconfig.get_paths()["purelib"])') \
&& printf '%s\n' "${SCRIPTS_DIR}" > "${SITE_PACKAGES}/claude-ads-core.pth" \
&& "${VENV_DIR}/bin/python" -m pip check \
&& "${VENV_DIR}/bin/python" "${SCRIPTS_DIR}/write_install_receipt.py" --inventory "${SOURCE_DIR}/control-plane/manifests/dependency-inventory.json" --lock "${SKILL_DIR}/requirements.lock" --evidence "${SOURCE_DIR}/control-plane/dependency-evidence/${DEPENDENCY_TARGET_ID}.json" --pip-report "${VENV_DIR}/install-report.json" --target-id "${DEPENDENCY_TARGET_ID}" --output "${SKILL_DIR}/managed-runtime-receipt.json"; then
echo " ✓ Exact locked Python dependencies installed in ${VENV_DIR}"
else
rm -rf "$VENV_DIR"
echo " ⚠ Dependency install failed. Create a virtual environment and install ${SKILL_DIR}/requirements.txt manually." >&2
rm -f -- "$RECEIPT_PATH"
echo "✗ Exact hashed dependency installation failed; no moving-range fallback was attempted." >&2
return 1
fi
else
echo " python3 not found. Python helper dependencies were not installed."
echo " python3 not found. Use --no-deps to install without Python helpers." >&2
return 1
fi
elif [ "$INSTALL_DEPS" = "0" ]; then
echo " Skipping Python dependencies (--no-deps)."
else
echo " Skipping Python dependencies — ${HOST_LABEL} host runtime may not execute Python skills directly."
echo " If you need PDF reports / landing-page analysis / screenshots, install manually:"
echo " python -m venv <env> && <env>/bin/python -m pip install -r ${SKILL_DIR}/requirements.txt"
echo " Python helpers require the packaged exact-hash lock on a supported CPython wheel target."
fi
# Check for banana-claude (image generation provider)
echo ""
if [ -d "${SKILL_BASE}/banana" ] || [ -f "${SKILL_BASE}/banana/SKILL.md" ]; then
echo " ✓ banana-claude detected (image generation ready)"
else
echo " ⚠ banana-claude not installed. Image generation (/ads generate, /ads photoshoot) requires it."
echo " Install through your host marketplace, or clone a tagged release and verify its checksum:"
echo " https://github.com/AgriciDaniel/banana-claude"
echo " Then run: /banana setup (to configure API key)"
fi
record_dir "${SKILL_DIR}"
mv -f "$MANIFEST_TMP" "$MANIFEST_PATH"
MANIFEST_TMP=""
echo "✓ Ownership manifest: ${MANIFEST_PATH}"
echo " Image generation requires an explicitly configured eligible provider/model"
echo " with capability evidence; Claude Ads does not probe or recommend a default."
echo ""
echo "✓ Claude Ads installed successfully for ${HOST_LABEL}!"
+38
View File
@@ -0,0 +1,38 @@
# Claude Ads development dependency lock
# Exact versions and union hashes for the supported CPython 3.11/3.12 wheel matrix.
cffi==2.1.0 \
--hash=sha256:02cb7ff33ded4f1532476731f89ede53e2e488a8e6205515a82144246ffa7dcc \
--hash=sha256:1e9f50d192a3e525b15a75ab5114e442d83d657b7ec29182a991bc9a88fd3a66 \
--hash=sha256:4f26194e3d95e06501b942642855aed4f953d55e95d7d01b7c4483db3ecff458 \
--hash=sha256:78474632761faa0fb96f30b1c928c84ebcf68713cbb80d15bab09dfe61640fde \
--hash=sha256:aa7a1b53a2a4452ada2d1b5dade9960b2522f1e61293a811a077439e39029565 \
--hash=sha256:c97f080ea627e2863524c5af3836e2270b5f5dfff1f104392b959f8df0c5d384 \
--hash=sha256:df2b82571a1b30f58a87bf4e5a9e78d2b1eff6c6ce8fd3aa3757221f93f0863f \
--hash=sha256:f5bce581e6b8c235e566a14768a943b172ada3ed73537bb0c0be1edee312d4e7
colorama==0.4.6 ; sys_platform == "win32" \
--hash=sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6
cryptography==48.0.1 \
--hash=sha256:39489bfca54c7a1f6b297efcd8bc608ab92d16c4ca631b0cad4da46724588b24 \
--hash=sha256:3e4a1a3232eef2e6c732827d5722db29a0cc8b27af2a4d865b094cf954be9ca1 \
--hash=sha256:f0d27a5696721ef7a672b8c810f6aded391058e0b9486e63e6d93baf765da691
iniconfig==2.3.0 \
--hash=sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12
packaging==26.2 \
--hash=sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e
pluggy==1.6.0 \
--hash=sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746
pycparser==3.0 \
--hash=sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992
pygments==2.20.0 \
--hash=sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176
pytest==9.0.3 \
--hash=sha256:2c5efc453d45394fdd706ade797c0a81091eccd1d6e4bccfcd476e2b8e0ab5d9
pyyaml==6.0.3 \
--hash=sha256:44edc647873928551a01e7a563d7452ccdebee747728c1080d881d68af7b997e \
--hash=sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b \
--hash=sha256:652cb6edd41e718550aad172851962662ff2681490a8a711af6a4d288dd96824 \
--hash=sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196 \
--hash=sha256:9f3bfb4965eb874431221a3ff3fdcddc7e74e3b07799e0e84ca4a0f867d449bf \
--hash=sha256:b8bb0864c5a28024fac8a632c443c87c5aa6f215c0b126c449ae1a150412f31d \
--hash=sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc \
--hash=sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0
+1 -1
View File
@@ -1,6 +1,6 @@
# Development dependencies for running the claude-ads eval harness.
# Production users do NOT need these — install only when contributing or
# running tests locally.
pytest>=8.0,<9.0
pytest>=9.0.3,<10.0
pyyaml>=6.0,<7.0
cryptography>=48.0.1,<49.0.0 # GHSA-537c-gmf6-5ccf fix; review/egress tests
+144
View File
@@ -0,0 +1,144 @@
# Claude Ads runtime dependency lock
# Exact lowest-common versions and the union of wheel hashes for the verified
# CPython 3.11/3.12 matrix: manylinux2014 x86_64, macOS 11 x86_64/arm64,
# and Windows amd64. Regenerate only from publisher artifacts and update the
# dependency inventory in the same review.
brotli==1.2.0 \
--hash=sha256:022426c9e99fd65d9475dce5c195526f04bb8be8907607e27e747893f6ee3e24 \
--hash=sha256:072e7624b1fc4d601036ab3f4f27942ef772887e876beff0301d261210bca97f \
--hash=sha256:15b33fe93cedc4caaff8a0bd1eb7e3dab1c61bb22a0bf5bdfdfd97cd7da79744 \
--hash=sha256:35d382625778834a7f3061b15423919aa03e4f5da34ac8e02c074e4b75ab4f84 \
--hash=sha256:40d918bce2b427a0c4ba189df7a006ac0c7277c180aee4617d99e9ccaaf59e6a \
--hash=sha256:7a61c06b334bd99bc5ae84f1eeb36bfe01400264b3c352f968c6e30a10f9d08b \
--hash=sha256:898be2be399c221d2671d29eed26b6b2713a02c2119168ed914e7d00ceadb56f \
--hash=sha256:b35c13ce241abdd44cb8ca70683f20c0c079728a36a996297adb5334adfc1c44
certifi==2026.6.17 \
--hash=sha256:2227dcbaafe0d2f59279d1762ddddc37783ed4354594f194ffc31d20f41fc3db
cffi==2.1.0 \
--hash=sha256:02cb7ff33ded4f1532476731f89ede53e2e488a8e6205515a82144246ffa7dcc \
--hash=sha256:1e9f50d192a3e525b15a75ab5114e442d83d657b7ec29182a991bc9a88fd3a66 \
--hash=sha256:4f26194e3d95e06501b942642855aed4f953d55e95d7d01b7c4483db3ecff458 \
--hash=sha256:78474632761faa0fb96f30b1c928c84ebcf68713cbb80d15bab09dfe61640fde \
--hash=sha256:aa7a1b53a2a4452ada2d1b5dade9960b2522f1e61293a811a077439e39029565 \
--hash=sha256:c97f080ea627e2863524c5af3836e2270b5f5dfff1f104392b959f8df0c5d384 \
--hash=sha256:df2b82571a1b30f58a87bf4e5a9e78d2b1eff6c6ce8fd3aa3757221f93f0863f \
--hash=sha256:f5bce581e6b8c235e566a14768a943b172ada3ed73537bb0c0be1edee312d4e7
charset-normalizer==3.4.9 \
--hash=sha256:04ce310cb89c15df659582aee80a0603788732a5e017d5bd5c81158106ce249c \
--hash=sha256:0e94703ec9684807f20cfb5eed95c70f67f2a8f21ad620146d7b5a13677b93e5 \
--hash=sha256:45b0cc4e3556cd875e09102988d1ab8356c998b596c9fced84547c8138b487a0 \
--hash=sha256:4b3dac63058cc36820b0dd072f89898604e2d39686fe05321729d00d8ac185a0 \
--hash=sha256:5e226f6218febc71f6c1fc2fafb91c226f75bdc1d8fb12d66823716e891608fd \
--hash=sha256:6366a16e1a25018694d6a5d784d09b046edc9eac40ea2b54065c3052672516a1
contourpy==1.3.2 \
--hash=sha256:3859783aefa2b8355697f16642695a5b9792e7a46ab86da1118a4a23a51a33d7 \
--hash=sha256:4caf2bcd2969402bf77edc4cb6034c7dd7c0803213b3523f111eb7460a51b8d2 \
--hash=sha256:5ebac872ba09cb8f2131c46b8739a7ff71de28a24c869bcad554477eb089a878 \
--hash=sha256:6a37a2fb93d4df3fc4c0e363ea4d16f83195fc09c891bc8ce072b9d084853445 \
--hash=sha256:82199cb78276249796419fe36b7386bd8d2cc3f28b3bc19fe2454fe2e26c4c15 \
--hash=sha256:8c942a01d9163e2e5cfb05cb66110121b8d07ad438a17f9e766317bcb62abf73 \
--hash=sha256:b7cd50c38f500bbcc9b6a46643a40e0913673f869315d8e70de0438817cb7773 \
--hash=sha256:f26b383144cf2d2c29f01a1e8170f50dacf0eac02d64139dcd709a8ac4eb3cfe
cryptography==48.0.1 \
--hash=sha256:39489bfca54c7a1f6b297efcd8bc608ab92d16c4ca631b0cad4da46724588b24 \
--hash=sha256:3e4a1a3232eef2e6c732827d5722db29a0cc8b27af2a4d865b094cf954be9ca1 \
--hash=sha256:f0d27a5696721ef7a672b8c810f6aded391058e0b9486e63e6d93baf765da691
cssselect2==0.9.0 \
--hash=sha256:6a99e5f91f9a016a304dd929b0966ca464bcfda15177b6fb4a118fc0fb5d9563
cycler==0.12.1 \
--hash=sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30
fonttools==4.63.0 \
--hash=sha256:063e08bd17bd5a90127a14123de0d6a952dbc847695fd98b63c043d58057f90c \
--hash=sha256:2b8ae05d9eacf6081414d759c0a352769ac28ce31280d6bb8e77b03f9e3c449f \
--hash=sha256:37dd23e621e3b0aef1baa70a303b80aaf38449632cfc8fd2a55fb285bbccfc02 \
--hash=sha256:58dc6bb86a78d782f00f9190ca02c119cf5bbe2807536e361e18d42019f877d8 \
--hash=sha256:59ac449f8cca9b4ffa08d2e7bbadad87ce710d69d1eda5c3c1ce579baa987272 \
--hash=sha256:79cdc9f567aec74a72918fd060283911406750cbc9fd28c1316023deb6ce31a9 \
--hash=sha256:a9faff9e0c1f76f9fd55899d2ce785832efebab37eb8ae13995853aef178bef0 \
--hash=sha256:d76ac49f929aecaf82d83250b8347e099d7aecba0f4726c1d9b6df3b8bb5fe18
greenlet==3.2.4 \
--hash=sha256:0db5594dce18db94f7d1650d7489909b57afde4c580806b8d9203b6e79cdc079 \
--hash=sha256:3b67ca49f54cede0186854a008109d6ee71f66bd57bb36abd6d0a0267b540cdd \
--hash=sha256:96378df1de302bc38e99c3a9aa311967b7dc80ced1dcc6f171e99842987882a2 \
--hash=sha256:9c40adce87eaa9ddb593ccb0fa6a07caf34015a29bf8d344811665b573138db9 \
--hash=sha256:a7d4e128405eea3814a12cc2605e0e6aedb4035bf32697f72deca74de4105e02 \
--hash=sha256:c60a6d84229b271d44b70fb6e5fa23781abb5d742af7b808ae3f6efd7c9c60f6
idna==3.18 \
--hash=sha256:7f952cbe720b688055e3f87de14f5c3e5fdaa8bc3928985c4077ca689de849a2
kiwisolver==1.5.0 \
--hash=sha256:0df54df7e686afa55e6f21fb86195224a6d9beb71d637e8d7920c95cf0f89aac \
--hash=sha256:2517e24d7315eb51c10664cdb865195df38ab74456c677df67bb47f12d088a27 \
--hash=sha256:3c4923e404d6bcd91b6779c009542e5647fef32e4a5d75e115e3bbac6f2335eb \
--hash=sha256:72ec46b7eba5b395e0a7b63025490d3214c11013f4aacb4f5e8d6c3041829588 \
--hash=sha256:bb5136fb5352d3f422df33f0c879a1b0c204004324150cc3b5e3c4f310c9049f \
--hash=sha256:beb7f344487cdcb9e1efe4b7a29681b74d34c08f0043a327a74da852a6749e7b \
--hash=sha256:ed3a984b31da7481b103f68776f7128a89ef26ed40f4dc41a2223cda7fb24819 \
--hash=sha256:f18c2d9782259a6dc132fdc7a63c168cbc74b35284b6d75c673958982a378384
matplotlib==3.11.0 \
--hash=sha256:0515d495124be3124340e59f164d901ed4484e2246a5b74cfa483cac3b80bd97 \
--hash=sha256:06b5872e9cf11adc8f589ded3ce11bc3e1061ad498259664fabc1f6615beb918 \
--hash=sha256:57baa92fdc82948ed716eae6d2579d4d6f40965cd8d2f416755b4a72580a3233 \
--hash=sha256:630eee0e67d35cce2019a0e670719f4816e3b86aff0fa72729f6c69786fceb45 \
--hash=sha256:70a5b3e9a5dab708c0f039709ae7c68d5b4d254e291ef76492cdba230c8bb5e4 \
--hash=sha256:ac6f1ef39f3d0f9e2463303013094992cdbe0f85f43bc54155bc472b2042768e \
--hash=sha256:be5f93a1d21981bfb802ded0d77a0caa92d4342a47d45754fac77e314a506344 \
--hash=sha256:f857524b442f0f36e641868ce2171aafa88cb0bc0644f4e1d8a5df9b32649fef
numpy==2.2.6 \
--hash=sha256:41c5a21f4a04fa86436124d388f6ed60a9343a6f767fced1a8a71c3fbca038ff \
--hash=sha256:ba10f8411898fc418a521833e014a77d3ca01c15b0c6cdcce6a0d2897e6dbbdf \
--hash=sha256:c1f9540be57940698ed329904db803cf7a402f3fc200bfe599334c9bd84a40b2 \
--hash=sha256:c820a93b0255bc360f53eca31a0e676fd1101f673dda8da93454a12e23fc5f7a \
--hash=sha256:de749064336d37e340f640b05f24e9e3dd678c57318c7289d222a8a2f543e90c \
--hash=sha256:e8213002e427c69c45a52bbd94163084025f533a55a59d6f9c5b820774ef3303 \
--hash=sha256:f9f1adb22318e121c5c69a09142811a201ef17ab257a1e66ca3025065b7f53ae \
--hash=sha256:fd83c01228a688733f1ded5201c678f0c53ecc1006ffbc404db9f7a899ac6249
packaging==26.2 \
--hash=sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e
pillow==12.2.0 \
--hash=sha256:2d192a155bbcec180f8564f693e6fd9bccff5a7af9b32e2e4bf8c9c69dbad6b5 \
--hash=sha256:6e6b2a0c538fc200b38ff9eb6628228b77908c319a005815f2dde585a0664b60 \
--hash=sha256:71cde9a1e1551df7d34a25462fc60325e8a11a82cc2e2f54578e5e9a1e153d65 \
--hash=sha256:7f84204dee22a783350679a0333981df803dac21a0190d706a50475e361c93f5 \
--hash=sha256:8bd7903a5f2a4545f6fd5935c90058b89d30045568985a71c79f5fd6edf9b91e \
--hash=sha256:8be29e59487a79f173507c30ddf57e733a357f67881430449bb32614075a40ab \
--hash=sha256:b86024e52a1b269467a802258c25521e6d742349d760728092e1bc2d135b4d76 \
--hash=sha256:f3f40b3c5a968281fd507d519e444c35f0ff171237f4fdde090dd60699458421
playwright==1.61.0 \
--hash=sha256:009588c2a7e499bc5a8b425b61fa65490968bbda9cd69e0cf2cff10f8304659a \
--hash=sha256:35c6cc4589a5d00964a59d7b3e59641e0aac0c02f15479a7af77d20f6bc79597 \
--hash=sha256:54f3b39f6eab832e33458c1dd7da0b5682aedab3b09ae731b5c59fa12fd2024e \
--hash=sha256:9f7de4536088d12037c13a52b7ea34b59270b78926bb56935070597ffac6b1af
pycparser==3.0 \
--hash=sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992
pydyf==0.12.1 \
--hash=sha256:ea25b4e1fe7911195cb57067560daaa266639184e8335365cc3ee5214e7eaadc
pyee==13.0.1 \
--hash=sha256:af2f8fede4171ef667dfded53f96e2ed0d6e6bd7ee3bb46437f77e3b57689228
pyparsing==3.3.2 \
--hash=sha256:850ba148bd908d7e2411587e247a1e4f0327839c40e2e5e6d05a007ecc69911d
pyphen==0.17.2 \
--hash=sha256:3a07fb017cb2341e1d9ff31b8634efb1ae4dc4b130468c7c39dd3d32e7c3affd
python-dateutil==2.9.0.post0 \
--hash=sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427
reportlab==4.5.1 \
--hash=sha256:06fce8cb56c83307cfa4909cdf4e6a2ddbb44e5d6ef4d2edca896d7e9769f091
requests==2.34.2 \
--hash=sha256:2a0d60c172f83ac6ab31e4554906c0f3b3588d37b5cb939b1c061f4907e278e0
six==1.17.0 \
--hash=sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274
tinycss2==1.5.1 \
--hash=sha256:3415ba0f5839c062696996998176c4a3751d18b7edaaeeb658c9ce21ec150661
tinyhtml5==2.1.0 \
--hash=sha256:6e11cfff38515834268daf89d5f85bbde0b6dd02e8d9e212d1385c2289b89f0a
typing-extensions==4.16.0 \
--hash=sha256:481caa481374e813c1b176ada14e97f1f67a4539ce9cfeb3f350d78d6370c2e8
urllib3==2.7.0 \
--hash=sha256:9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897
weasyprint==69.0 \
--hash=sha256:475951cfd917014de6d4d005caff48c6aa867e7e42b80cd5b16a0484a1609ee6
webencodings==0.5.1 \
--hash=sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78
zopfli==0.4.3 \
--hash=sha256:769875152d0625c46707bcca57d4b2233fe653482067acd55fbf6ec525cb9bdc \
--hash=sha256:88f4fbe429aad72bc206275d81fab11a097e0f951a5848d1f51083c37ea73073 \
--hash=sha256:921c2c9907f4364963848da5ad194b46d68865e07fdb975d04fd09bc42d47357
+2 -8
View File
@@ -16,11 +16,5 @@ weasyprint>=69.0,<70.0 # Canonical ReportBundle HTML-to-PDF renderer
reportlab>=4.0,<5.0.0 # PDF layout and rendering
matplotlib>=3.8.0,<4.0.0 # Health score gauge and platform bar charts
# Image generation: banana-claude is the default provider (no pip packages needed).
# Install banana-claude separately: https://github.com/AgriciDaniel/claude-banana
# The packages below are only needed if using generate_image.py fallback directly.
# google-genai>=1.16.0,<2.0.0 # Gemini (only for generate_image.py fallback)
# openai>=1.75.0,<2.0.0 # OpenAI gpt-image-1
# stability-sdk>=0.8.4,<1.0.0 # Stability AI SD 3.5
# replicate>=1.0.4,<2.0.0 # Replicate / FLUX.1 Pro
# Image generation has no preferred/default provider or model. Operators must
# explicitly configure an eligible provider capability outside this base lock.
+853 -78
View File
File diff suppressed because it is too large Load Diff
+93
View File
@@ -0,0 +1,93 @@
#!/usr/bin/env python3
"""Verify a completed managed installer result on its native host."""
from __future__ import annotations
import argparse
import hashlib
import json
from pathlib import Path
import platform
import subprocess
import sys
def _sha256(path: Path) -> str:
return hashlib.sha256(path.read_bytes()).hexdigest()
def _target_id() -> str:
if sys.implementation.name != "cpython" or sys.version_info[:2] not in {(3, 11), (3, 12)}:
raise ValueError("managed install verification requires CPython 3.11 or 3.12")
system = platform.system().lower()
machine = platform.machine().lower()
machine = {"amd64": "x86_64", "aarch64": "arm64"}.get(machine, machine)
if system == "linux" and machine == "x86_64":
platform_id = "linux"
elif system == "darwin" and machine in {"x86_64", "arm64"}:
platform_id = "macos-x86" if machine == "x86_64" else "macos-arm"
elif system == "windows" and machine == "x86_64":
platform_id = "windows"
else:
raise ValueError(f"no managed install target for {system}/{machine}")
return f"runtime-{platform_id}-cp{sys.version_info.major}{sys.version_info.minor}"
def verify(repository: Path, skill_dir: Path) -> dict[str, object]:
receipt_path = skill_dir / "managed-runtime-receipt.json"
receipt = json.loads(receipt_path.read_text(encoding="utf-8-sig"))
expected_fields = {
"schema_version", "receipt_class", "target_id", "target", "resolver",
"python_full_version", "pip_version", "requirements_lock_sha256",
"dependency_inventory_sha256", "target_evidence_sha256",
"pip_install_report_sha256", "components",
}
if not isinstance(receipt, dict) or set(receipt) != expected_fields:
raise ValueError("managed runtime receipt fields mismatch")
if receipt.get("schema_version") != "1.0.0" or receipt.get("receipt_class") != "managed-python-runtime-install":
raise ValueError("managed runtime receipt identity mismatch")
target_id = _target_id()
if receipt.get("target_id") != target_id or receipt.get("resolver") != "pip / PyPI":
raise ValueError("managed runtime receipt native target mismatch")
components = receipt.get("components")
if not isinstance(components, list) or len(components) != 33:
raise ValueError("managed runtime receipt closure mismatch")
if receipt.get("requirements_lock_sha256") != _sha256(repository / "requirements.lock"):
raise ValueError("managed runtime receipt lock binding mismatch")
inventory_path = repository / "control-plane/manifests/dependency-inventory.json"
if receipt.get("dependency_inventory_sha256") != _sha256(inventory_path):
raise ValueError("managed runtime receipt inventory binding mismatch")
evidence_path = repository / "control-plane/dependency-evidence" / f"{target_id}.json"
if receipt.get("target_evidence_sha256") != _sha256(evidence_path):
raise ValueError("managed runtime receipt target evidence binding mismatch")
python = skill_dir / ".venv" / ("Scripts/python.exe" if platform.system() == "Windows" else "bin/python")
if not python.is_file():
raise ValueError("managed runtime interpreter is missing")
checks = (
[str(python), "-m", "pip", "check"],
[str(python), "-c", "import claude_ads_core; print(claude_ads_core.__version__)"],
)
for command in checks:
result = subprocess.run(command, check=False, text=True, capture_output=True)
if result.returncode:
raise ValueError(f"managed runtime execution check failed: {result.stderr.strip()}")
return receipt
def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument("--repository", type=Path, default=Path(__file__).resolve().parents[1])
parser.add_argument("--skill-dir", type=Path, required=True)
args = parser.parse_args()
try:
receipt = verify(args.repository.resolve(), args.skill_dir.resolve())
except (OSError, ValueError, json.JSONDecodeError) as exc:
print(f"managed install verification failed: {exc}", file=sys.stderr)
return 1
print(f"verified managed install: {receipt['target_id']}")
return 0
if __name__ == "__main__":
raise SystemExit(main())
+141
View File
@@ -0,0 +1,141 @@
#!/usr/bin/env python3
"""Verify a native pip report and downloaded wheel closure against reviewed evidence."""
from __future__ import annotations
import argparse
import hashlib
import importlib.util
import json
import os
import platform
from pathlib import Path
import subprocess
import sys
_RELEASE_SPEC = importlib.util.spec_from_file_location("claude_ads_target_release", Path(__file__).with_name("release.py"))
if _RELEASE_SPEC is None or _RELEASE_SPEC.loader is None: # pragma: no cover
raise RuntimeError("cannot load the release verifier")
release = importlib.util.module_from_spec(_RELEASE_SPEC)
sys.modules[_RELEASE_SPEC.name] = release
_RELEASE_SPEC.loader.exec_module(release)
def sha256(path: Path) -> str:
return hashlib.sha256(path.read_bytes()).hexdigest()
def native_target_id(profile: str) -> str:
if sys.implementation.name != "cpython" or sys.version_info[:2] not in {(3, 11), (3, 12)}:
raise release.ReleaseError("native lock evidence requires CPython 3.11 or 3.12")
system = platform.system().lower()
machine = platform.machine().lower()
machine = {"amd64": "x86_64", "aarch64": "arm64"}.get(machine, machine)
if system == "linux" and machine == "x86_64":
libc_name, libc_version = platform.libc_ver()
try:
libc_ok = libc_name == "glibc" and tuple(map(int, libc_version.split(".")[:2])) >= (2, 17)
except ValueError:
libc_ok = False
if not libc_ok:
raise release.ReleaseError("native Linux lock evidence requires glibc >=2.17; musl is unsupported")
target = "linux"
elif system == "darwin" and machine in {"arm64", "x86_64"}:
target = "macos-arm" if machine == "arm64" else "macos-x86"
elif system == "windows" and machine == "x86_64":
target = "windows"
else:
raise release.ReleaseError(f"no reviewed native lock target for {system}/{machine}")
return f"{profile}-{target}-cp{sys.version_info.major}{sys.version_info.minor}"
def verify(root: Path, profile: str, pip_report: Path, wheel_dir: Path, output: Path) -> None:
inventory = release._load_dependency_inventory(root)
target_id = native_target_id(profile)
target = next(item for item in inventory["targets"] if item["id"] == target_id)
expected = {item["name"]: item for item in target["components"]}
report_data = pip_report.read_bytes()
report = json.loads(report_data)
try:
from pip import __version__ as executing_pip_version
except ImportError as exc:
raise release.ReleaseError("pip is required for native target verification") from exc
if report.get("pip_version") != executing_pip_version:
raise release.ReleaseError("pip report version does not equal the executing resolver")
observed = {}
for item in report.get("install", []):
metadata = item.get("metadata", {})
name = release._canonical_package_name(str(metadata.get("name", "")))
download = item.get("download_info", {})
hashes = download.get("archive_info", {}).get("hashes", {})
record = expected.get(name)
if record is None or name in observed:
raise release.ReleaseError(f"pip report contains duplicate or unexpected component: {name}")
artifact = record["artifact"]
if (
str(metadata.get("version")) != record["version"]
or download.get("url") != artifact["url"]
or hashes.get("sha256") != artifact["sha256"]
):
raise release.ReleaseError(f"pip report artifact mismatch: {target_id}/{name}")
observed[name] = {
"name": name, "version": record["version"], "filename": artifact["filename"],
"url": artifact["url"], "sha256": artifact["sha256"],
}
if set(observed) != set(expected):
raise release.ReleaseError(f"pip report closure mismatch: missing={sorted(set(expected)-set(observed))}")
wheels = {path.name: sha256(path) for path in wheel_dir.glob("*.whl") if path.is_file()}
expected_wheels = {item["artifact"]["filename"]: item["artifact"]["sha256"] for item in expected.values()}
if wheels != expected_wheels:
raise release.ReleaseError("downloaded wheel filenames/hashes do not equal the target inventory")
evidence_path = root / "control-plane/dependency-evidence" / f"{target_id}.json"
inventory_path = root / "control-plane/manifests/dependency-inventory.json"
lock_path = root / ("requirements.lock" if profile == "runtime" else "requirements-dev.lock")
commit_result = subprocess.run(["git", "rev-parse", "HEAD"], cwd=root, check=False, capture_output=True, text=True)
if commit_result.returncode:
raise release.ReleaseError("cannot bind native target evidence to a Git commit")
attestation = {
"schema_version": "1.0.0",
"evidence_class": "native-pip-report-and-downloaded-wheel-confirmation",
"target_id": target_id,
"environment": {
"implementation": platform.python_implementation(),
"python_version": f"{sys.version_info.major}.{sys.version_info.minor}",
"platform": platform.platform(),
"machine": platform.machine(),
"libc": list(platform.libc_ver()),
"runner": {name: os.environ[name] for name in ("ImageOS", "ImageVersion", "RUNNER_OS", "RUNNER_ARCH") if name in os.environ},
},
"source_commit": commit_result.stdout.strip(),
"dependency_inventory_sha256": sha256(inventory_path),
"requirements_lock_sha256": sha256(lock_path),
"pip_version": executing_pip_version,
"pip_report_sha256": hashlib.sha256(report_data).hexdigest(),
"normalized_evidence_sha256": sha256(evidence_path),
"components": [observed[name] for name in sorted(observed)],
}
output.parent.mkdir(parents=True, exist_ok=True)
output.write_text(json.dumps(attestation, indent=2, sort_keys=True) + "\n", encoding="utf-8", newline="\n")
def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument("--profile", choices=("runtime", "development"), required=True)
parser.add_argument("--pip-report", type=Path, required=True)
parser.add_argument("--wheel-dir", type=Path, required=True)
parser.add_argument("--output", type=Path, required=True)
args = parser.parse_args()
try:
verify(Path(__file__).resolve().parents[1], args.profile, args.pip_report, args.wheel_dir, args.output)
except (OSError, ValueError, json.JSONDecodeError, release.ReleaseError) as exc:
print(f"target lock verification failed: {exc}", file=sys.stderr)
return 1
print(f"verified native target lock: {native_target_id(args.profile)}")
return 0
if __name__ == "__main__":
raise SystemExit(main())
+96
View File
@@ -0,0 +1,96 @@
#!/usr/bin/env python3
"""Validate an actual managed install and write its target-bound receipt."""
from __future__ import annotations
import argparse
import hashlib
from importlib import metadata
import json
from pathlib import Path
import platform
import sys
EXPECTED_INVENTORY_SHA256 = "48ed1cdd3023bdb9781e7ffe9d987fb9425cc23e2dea053e2da5e8d461e588de"
def digest(path: Path) -> str:
return hashlib.sha256(path.read_bytes()).hexdigest()
def canonical(value: str) -> str:
return value.lower().replace("_", "-").replace(".", "-")
def write_receipt(inventory_path: Path, lock_path: Path, evidence_path: Path, report_path: Path, target_id: str, output: Path) -> None:
if digest(inventory_path) != EXPECTED_INVENTORY_SHA256:
raise ValueError("dependency inventory is not the reviewed document")
inventory = json.loads(inventory_path.read_text(encoding="utf-8"))
target = next((item for item in inventory["targets"] if item["id"] == target_id), None)
if target is None or target["profile"] != "runtime":
raise ValueError("unknown runtime target")
if digest(evidence_path) != target["resolution_evidence_sha256"]:
raise ValueError("target evidence binding mismatch")
expected = {item["name"]: item for item in target["components"]}
report_data = report_path.read_bytes()
report = json.loads(report_data)
pip_version = metadata.version("pip")
if report.get("pip_version") != pip_version:
raise ValueError("pip report resolver version mismatch")
selected = {}
for item in report.get("install", []):
name = canonical(item.get("metadata", {}).get("name", ""))
component = expected.get(name)
download = item.get("download_info", {})
if component is None or name in selected:
raise ValueError(f"unexpected or duplicate installed component: {name}")
artifact = component["artifact"]
if item["metadata"].get("version") != component["version"] or download.get("url") != artifact["url"] or download.get("archive_info", {}).get("hashes", {}).get("sha256") != artifact["sha256"]:
raise ValueError(f"installed artifact mismatch: {name}")
if metadata.version(name) != component["version"]:
raise ValueError(f"installed version mismatch: {name}")
selected[name] = {"name": name, "version": component["version"], "artifact": artifact}
if set(selected) != set(expected):
raise ValueError("installed closure does not equal the target inventory")
installed = {
canonical(distribution.metadata.get("Name", "")): distribution.version
for distribution in metadata.distributions()
if distribution.metadata.get("Name")
}
bootstrap = {"pip", "setuptools", "wheel"}
unexpected = sorted(set(installed) - set(expected) - bootstrap)
missing = sorted(set(expected) - set(installed))
if unexpected or missing:
raise ValueError(
f"managed environment closure mismatch: missing={missing}, unexpected={unexpected}"
)
for name, component in expected.items():
if installed[name] != component["version"]:
raise ValueError(f"managed environment version mismatch: {name}")
receipt = {
"schema_version": "1.0.0", "receipt_class": "managed-python-runtime-install",
"target_id": target_id, "target": {key: target[key] for key in ("python_version", "implementation", "os", "arch", "abi")},
"resolver": "pip / PyPI", "python_full_version": platform.python_version(), "pip_version": pip_version,
"requirements_lock_sha256": digest(lock_path), "dependency_inventory_sha256": digest(inventory_path),
"target_evidence_sha256": digest(evidence_path), "pip_install_report_sha256": hashlib.sha256(report_data).hexdigest(),
"components": [selected[name] for name in sorted(selected)],
}
temporary = output.with_suffix(output.suffix + ".tmp")
temporary.write_text(json.dumps(receipt, indent=2, sort_keys=True) + "\n", encoding="utf-8", newline="\n")
temporary.replace(output)
def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument("--inventory", type=Path, required=True); parser.add_argument("--lock", type=Path, required=True)
parser.add_argument("--evidence", type=Path, required=True); parser.add_argument("--pip-report", type=Path, required=True)
parser.add_argument("--target-id", required=True); parser.add_argument("--output", type=Path, required=True)
args = parser.parse_args()
try: write_receipt(args.inventory, args.lock, args.evidence, args.pip_report, args.target_id, args.output)
except Exception as exc:
print(f"install receipt validation failed: {exc}", file=sys.stderr); return 1
return 0
if __name__ == "__main__": raise SystemExit(main())
+10
View File
@@ -166,6 +166,16 @@ def test_manifest_requires_matching_data_lifecycle_classification():
validate_contract("run-manifest", payload)
@pytest.mark.parametrize("invalid", [0.5, 0.0, True, False, "0", None, -1])
def test_run_manifest_embedded_lifecycle_requires_strict_nonnegative_integer_retention(
invalid
):
payload = run_manifest()
payload["data_lifecycle"]["retention"]["minimum_seconds"] = invalid
with pytest.raises(ContractError, match="integer|must be >="):
validate_contract("run-manifest", payload)
def test_pass_or_fail_requires_evidence():
payload = finding()
payload["evidence"] = []
+7
View File
@@ -171,6 +171,13 @@ def test_pdf_bridge_fails_clearly_when_optional_dependency_is_unavailable(monkey
render_pdf(load_bundle())
def test_real_pdf_render_smoke_when_runtime_dependencies_are_installed():
pytest.importorskip("weasyprint")
rendered = render_pdf(load_bundle())
assert rendered.startswith(b"%PDF-")
assert len(rendered) > 1_000
def test_cli_render_writes_validated_report_under_safe_root(tmp_path, capsys):
root = tmp_path / "runs"
assert main(["render", str(BUNDLE_PATH), "--format", "html", "--root", str(root)]) == 0
+129
View File
@@ -24,6 +24,56 @@ def _contract_name(fixture_name: str) -> str:
return fixture_name
INTEGER_SCHEMA_FIELDS = (
("data-lifecycle", ("retention", "minimum_seconds"), 0),
("generation-manifest", ("outputs", 0, "width"), 1),
("generation-manifest", ("outputs", 0, "height"), 1),
)
DATA_LIFECYCLE_REFERENCE_CONTRACTS = {
"run-manifest",
"setup-profile",
"brand-profile",
"media-plan",
"creative-brief",
"generation-manifest",
"monitoring-bundle",
"experiment-artifact",
"mutation-plan",
"orchestration-run",
}
def _set_path(payload: dict, path: tuple[str | int, ...], value) -> None:
target = payload
for part in path[:-1]:
target = target[part]
target[path[-1]] = value
def _declared_integer_fields(repo_root: Path) -> set[tuple[str, tuple[str | int, ...], int]]:
declared: set[tuple[str, tuple[str | int, ...], int]] = set()
for contract in CONTRACT_NAMES:
if contract in {"account-snapshot", "control-definition", "finding", "report-bundle", "run-manifest"}:
continue
schema = json.loads(schema_path(contract).read_text(encoding="utf-8"))
def walk(node, path=()):
if not isinstance(node, dict):
return
if node.get("type") == "integer":
declared.add((contract, path, node.get("minimum")))
properties = node.get("properties", {})
if isinstance(properties, dict):
for name, child in properties.items():
walk(child, (*path, name))
items = node.get("items")
if isinstance(items, dict):
walk(items, (*path, 0))
walk(schema)
return declared
def test_all_workflow_fixtures_validate_and_have_strict_schemas(workflow_fixtures):
for fixture_name, payload in workflow_fixtures.items():
contract = _contract_name(fixture_name)
@@ -35,6 +85,85 @@ def test_all_workflow_fixtures_validate_and_have_strict_schemas(workflow_fixture
assert set(schema["required"]) == set(schema["properties"])
def test_integer_parity_inventory_covers_every_workflow_orchestration_schema_field(repo_root):
assert _declared_integer_fields(repo_root) == set(INTEGER_SCHEMA_FIELDS)
lifecycle_refs = set()
for contract in CONTRACT_NAMES:
schema = json.loads(schema_path(contract).read_text(encoding="utf-8"))
lifecycle = schema.get("properties", {}).get("data_lifecycle", {})
if lifecycle.get("$ref") == "data-lifecycle.schema.json":
lifecycle_refs.add(contract)
assert lifecycle_refs == DATA_LIFECYCLE_REFERENCE_CONTRACTS
@pytest.mark.parametrize(("fixture_name", "path", "minimum"), INTEGER_SCHEMA_FIELDS)
@pytest.mark.parametrize("invalid_kind", ("bool-true", "bool-false", "fraction", "float", "string", "null", "below-minimum"))
def test_every_schema_integer_field_rejects_non_integer_or_below_minimum(
workflow_fixtures, fixture_name, path, minimum, invalid_kind
):
payload = copy.deepcopy(workflow_fixtures[fixture_name])
values = {
"bool-true": True,
"bool-false": False,
"fraction": minimum + 0.5,
"float": float(minimum),
"string": str(minimum),
"null": None,
"below-minimum": minimum - 1,
}
_set_path(payload, path, values[invalid_kind])
with pytest.raises(ContractError, match="integer|must be >="):
validate_contract(fixture_name, payload)
@pytest.mark.parametrize(("fixture_name", "path", "minimum"), INTEGER_SCHEMA_FIELDS)
@pytest.mark.parametrize("offset", (0, 1, 10_000))
def test_every_schema_integer_field_accepts_integer_values_at_or_above_minimum(
workflow_fixtures, fixture_name, path, minimum, offset
):
payload = copy.deepcopy(workflow_fixtures[fixture_name])
_set_path(payload, path, minimum + offset)
validate_contract(fixture_name, payload)
@pytest.mark.parametrize(
"fixture_name",
[
"setup-profile",
"brand-profile",
"media-plan",
"creative-brief",
"generation-manifest",
"monitoring-bundle",
"experiment-setup",
"experiment-readout",
"mutation-plan",
"orchestration-run",
],
)
def test_embedded_data_lifecycle_integer_validation_is_identical_across_contracts(
workflow_fixtures, fixture_name
):
payload = copy.deepcopy(workflow_fixtures[fixture_name])
payload["data_lifecycle"]["retention"]["minimum_seconds"] = 0.5
with pytest.raises(ContractError, match="minimum_seconds must be an integer"):
validate_contract(_contract_name(fixture_name), payload)
def test_schema_number_fields_still_accept_fractional_values(workflow_fixtures):
media_plan = copy.deepcopy(workflow_fixtures["media-plan"])
media_plan["channels"][0]["budget_amount"] = 0.5
validate_contract("media-plan", media_plan)
generation = copy.deepcopy(workflow_fixtures["generation-manifest"])
generation["outputs"][0]["cost"] = {"currency": "USD", "amount": 0.5}
validate_contract("generation-manifest", generation)
mutation = copy.deepcopy(workflow_fixtures["mutation-plan"])
mutation["ceilings"][0]["value"] = 0.5
validate_contract("mutation-plan", mutation)
@pytest.mark.parametrize(
"fixture_name",
[
+309 -23
View File
@@ -1,5 +1,6 @@
from __future__ import annotations
import copy
import hashlib
import importlib.util
import json
@@ -32,6 +33,10 @@ def _git(root: Path, *args: str) -> None:
subprocess.run(["git", *args], cwd=root, check=True, capture_output=True)
def _commit(root: Path) -> str:
return subprocess.check_output(["git", "rev-parse", "HEAD"], cwd=root, text=True).strip()
def _write(root: Path, relative: str, content: str) -> None:
path = root / relative
path.parent.mkdir(parents=True, exist_ok=True)
@@ -82,13 +87,17 @@ def _repository(tmp_path: Path) -> Path:
(root / "skills").mkdir(exist_ok=True)
_write(root, "README.md", "# Claude Ads\n")
_write(root, "LICENSE", "MIT\n")
_write(
root,
"pyproject.toml",
'[project]\nname = "claude-ads-core"\nversion = "2.0.0"\ndependencies = []\n',
)
_write(root, "requirements.txt", "requests>=2.32,<3\n")
_write(root, "requirements-dev.txt", "pytest>=8,<9\nrequests>=2.32,<3\n")
source_root = RELEASE_SCRIPT.parents[1]
for relative in (
"pyproject.toml", "requirements.txt", "requirements-dev.txt",
"requirements.lock", "requirements-dev.lock",
"THIRD_PARTY_NOTICES.md", "control-plane/manifests/dependency-inventory.json",
"control-plane/manifests/external-runtime-dependencies.json",
):
_write(root, relative, (source_root / relative).read_text(encoding="utf-8"))
for evidence in sorted((source_root / "control-plane/dependency-evidence").glob("*.json")):
relative = evidence.relative_to(source_root).as_posix()
_write(root, relative, evidence.read_text(encoding="utf-8"))
_write(root, "ads/research-sources/raw.md", "Research output does not ship.\n")
_write(root, "branding/internal.html", "Internal branding does not ship.\n")
_write(root, "research/private.md", "This tracked research does not ship.\n")
@@ -129,15 +138,15 @@ def test_audit_checks_frontmatter_and_sensitive_content(tmp_path: Path) -> None:
def test_package_is_deterministic_public_safe_and_verifiable(tmp_path: Path) -> None:
root = _repository(tmp_path)
first = build_release(root, root / "dist-a")
second = build_release(root, root / "dist-b")
first = build_release(root, tmp_path / "dist-a")
second = build_release(root, tmp_path / "dist-b")
assert hashlib.sha256(first["archive"].read_bytes()).digest() == hashlib.sha256(
second["archive"].read_bytes()
).digest()
assert first["manifest"].read_bytes() == second["manifest"].read_bytes()
assert first["sbom"].read_bytes() == second["sbom"].read_bytes()
verify_release(root / "dist-a")
verify_release(tmp_path / "dist-a", _commit(root), root)
with zipfile.ZipFile(first["archive"]) as archive:
names = archive.namelist()
@@ -151,28 +160,300 @@ def test_package_is_deterministic_public_safe_and_verifiable(tmp_path: Path) ->
sbom = json.loads(first["sbom"].read_text(encoding="utf-8"))
assert sbom["bomFormat"] == "CycloneDX"
assert [component["name"] for component in sbom["components"]] == ["pytest", "requests"]
assert len(sbom["components"]) == 33
assert "pytest" not in {component["name"] for component in sbom["components"]}
assert all(component["scope"] == "required" for component in sbom["components"])
def test_verify_detects_tampering(tmp_path: Path) -> None:
root = _repository(tmp_path)
artifacts = build_release(root, root / "dist")
artifacts = build_release(root, tmp_path / "dist")
artifacts["archive"].write_bytes(artifacts["archive"].read_bytes() + b"tampered")
with pytest.raises(ReleaseError, match="checksum mismatch"):
verify_release(root / "dist")
verify_release(tmp_path / "dist", _commit(root), root)
@pytest.mark.parametrize("case", ["top-field", "bool-size", "float-size", "archive-field", "product"])
def test_release_manifest_schema_and_types_fail_closed(tmp_path: Path, case: str) -> None:
root = _repository(tmp_path)
artifacts = build_release(root, tmp_path / "dist")
manifest = json.loads(artifacts["manifest"].read_text(encoding="utf-8"))
if case == "top-field": manifest["unexpected"] = True
elif case == "bool-size": manifest["files"][0]["size"] = True
elif case == "float-size": manifest["archive"]["size"] = float(manifest["archive"]["size"])
elif case == "archive-field": manifest["archive"]["unexpected"] = "x"
else: manifest["product"]["name"] = "forged"
artifacts["manifest"].write_text(json.dumps(manifest, indent=2, sort_keys=True) + "\n", encoding="utf-8")
lines = artifacts["checksums"].read_text(encoding="utf-8").splitlines()
artifacts["checksums"].write_text("\n".join(f"{hashlib.sha256(artifacts['manifest'].read_bytes()).hexdigest()} release-manifest.json" if line.endswith(" release-manifest.json") else line for line in lines) + "\n", encoding="utf-8")
with pytest.raises(ReleaseError):
verify_release(tmp_path / "dist", _commit(root), root)
def test_release_verifier_requires_trusted_commit_and_exact_checksum_set(tmp_path: Path) -> None:
root = _repository(tmp_path)
artifacts = build_release(root, tmp_path / "dist")
with pytest.raises(ReleaseError, match="trusted expected commit"):
verify_release(tmp_path / "dist", "A" * 40, root)
with artifacts["checksums"].open("a", encoding="utf-8") as handle:
handle.write(f"{'0' * 64} extra.json\n")
with pytest.raises(ReleaseError, match="checksum mismatch|file set"):
verify_release(tmp_path / "dist", _commit(root), root)
def test_external_runtime_manifest_is_exact_and_archived(tmp_path: Path) -> None:
root = _repository(tmp_path)
document = release._load_external_runtime_dependencies(root)
assert {item["id"] for item in document["dependencies"]} == {"playwright-browser-payload", "weasyprint-native-libraries"}
artifacts = build_release(root, tmp_path / "dist")
verify_release(tmp_path / "dist", _commit(root), root)
path = root / "control-plane/manifests/external-runtime-dependencies.json"
value = json.loads(path.read_text(encoding="utf-8")); value["dependencies"][0]["included_in_python_sbom"] = True
path.write_text(json.dumps(value, indent=2, sort_keys=True) + "\n", encoding="utf-8")
with pytest.raises(ReleaseError, match="reviewed document|boundary"):
release._load_external_runtime_dependencies(root)
def test_sbom_uses_actual_manifests(tmp_path: Path) -> None:
root = _repository(tmp_path)
sbom = build_sbom(root, "claude-ads", "2.0.0")
components = {component["name"]: component for component in sbom["components"]}
assert set(components) == {"pytest", "requests"}
request_sources = {
property_["value"]
for property_ in components["requests"]["properties"]
if property_["name"] == "claude-ads:manifest"
}
assert request_sources == {"requirements.txt", "requirements-dev.txt"}
assert len(components) == 33
assert components["requests"]["version"] == "2.34.2"
assert components["requests"]["licenses"] == [{"expression": "Apache-2.0"}]
assert components["urllib3"]["licenses"] == [{"expression": "MIT"}]
assert "pytest" not in components
app = sbom["dependencies"][0]
assert "pkg:pypi/pytest@9.0.3" not in app["dependsOn"]
def _rewrite_inventory(root: Path, mutate) -> None:
path = root / "control-plane/manifests/dependency-inventory.json"
inventory = json.loads(path.read_text(encoding="utf-8"))
mutate(inventory)
_write(root, "control-plane/manifests/dependency-inventory.json", json.dumps(inventory, indent=2, sort_keys=True) + "\n")
@pytest.mark.parametrize("field", ["version", "license_expression"])
def test_sbom_fails_closed_on_missing_version_or_license(tmp_path: Path, field: str) -> None:
root = _repository(tmp_path)
_rewrite_inventory(root, lambda inventory: inventory["component_catalog"][0].__setitem__(field, ""))
with pytest.raises(ReleaseError, match="invalid name/version|lacks a reviewed license"):
build_sbom(root, "claude-ads", "2.0.0")
def test_sbom_rejects_duplicate_components(tmp_path: Path) -> None:
root = _repository(tmp_path)
_rewrite_inventory(root, lambda inventory: inventory["component_catalog"].append(copy.deepcopy(inventory["component_catalog"][0])))
with pytest.raises(ReleaseError, match="duplicate or multi-version"):
build_sbom(root, "claude-ads", "2.0.0")
def test_sbom_rejects_direct_requirement_coverage_or_constraint_mismatch(tmp_path: Path) -> None:
root = _repository(tmp_path)
_rewrite_inventory(root, lambda inventory: inventory["direct_requirements"].pop())
with pytest.raises(ReleaseError, match="direct requirement coverage mismatch"):
build_sbom(root, "claude-ads", "2.0.0")
second = tmp_path / "second"
second.mkdir()
root = _repository(second)
def mismatch(inventory):
for item in inventory["direct_requirements"]:
if item["name"] == "requests":
item["requirement"] = "requests>=3,<4"
break
_rewrite_inventory(root, mismatch)
with pytest.raises(ReleaseError, match="direct requirement coverage mismatch"):
build_sbom(root, "claude-ads", "2.0.0")
def test_lock_target_hash_and_marker_parity_fail_closed(tmp_path: Path) -> None:
root = _repository(tmp_path)
_rewrite_inventory(root, lambda inventory: inventory["targets"][0]["components"][0]["artifact"].__setitem__("sha256", "0" * 64))
with pytest.raises(ReleaseError, match="target component evidence mismatch|lock target artifact mismatch"):
build_sbom(root, "claude-ads", "2.0.0")
parsed = release._parse_hash_lock(RELEASE_SCRIPT.parents[1] / "requirements-dev.lock")
assert parsed["colorama"]["marker"] == 'sys_platform == "win32"'
def test_notice_inventory_has_no_dangling_references_and_records_bundled_terms() -> None:
root = RELEASE_SCRIPT.parents[1]
inventory = release._load_dependency_inventory(root)
notices = {item["id"] for item in inventory["bundled_notices"]}
assert len(notices) == len(inventory["component_catalog"]) == 39
assert {"pyphen-selected-wheel-documents", "reportlab-selected-wheel-documents", "matplotlib-selected-wheel-documents"} <= notices
artifacts = {component["artifact"]["sha256"] for target in inventory["targets"] for component in target["components"]}
covered = {digest for notice in inventory["bundled_notices"] for document in notice["documents"] for digest in document["artifact_sha256s"]} | {digest for notice in inventory["bundled_notices"] for digest in notice["documentless_artifact_sha256s"]}
assert covered == artifacts and len(artifacts) == 119
webencodings = next(item for item in inventory["bundled_notices"] if item["component"] == "webencodings")
assert not webencodings["documents"] and len(webencodings["documentless_artifact_sha256s"]) == 1
urllib3 = next(item for item in inventory["component_catalog"] if item["name"] == "urllib3")
assert urllib3["license_expression"] == "MIT"
notices_text = (root / "THIRD_PARTY_NOTICES.md").read_text(encoding="utf-8")
assert "urllib3: MIT" in notices_text
assert "LicenseRef-Matplotlib-1.3" in notices_text
@pytest.mark.parametrize("case", ["header", "target-evidence", "artifact-filename", "dangling-edge", "dangling-notice"])
def test_inventory_provenance_and_graph_tampering_fail_closed(tmp_path: Path, case: str) -> None:
root = _repository(tmp_path)
def mutate(inventory):
if case == "header":
inventory["schema_version"] = "9.0.0"
elif case == "target-evidence":
inventory["targets"][0]["resolution_evidence_sha256"] = "missing"
elif case == "artifact-filename":
inventory["targets"][0]["components"][0]["artifact"]["filename"] = "wrong.whl"
elif case == "dangling-edge":
inventory["dependency_edges"].append({"profile": "runtime", "from": "requests", "to": "missing", "specifier": "", "marker": None, "extras": []})
else:
inventory["component_catalog"][0]["bundled_notice_ids"] = ["missing-notice"]
_rewrite_inventory(root, mutate)
with pytest.raises(ReleaseError):
build_sbom(root, "claude-ads", "2.0.0")
def test_standalone_verify_rejects_self_consistent_sbom_semantic_tamper(tmp_path: Path) -> None:
root = _repository(tmp_path)
artifacts = build_release(root, tmp_path / "dist")
sbom = json.loads(artifacts["sbom"].read_text(encoding="utf-8"))
sbom["components"][0]["scope"] = "optional"
artifacts["sbom"].write_text(json.dumps(sbom, indent=2, sort_keys=True) + "\n", encoding="utf-8")
checksum_lines = artifacts["checksums"].read_text(encoding="utf-8").splitlines()
checksum_lines = [
f"{hashlib.sha256(artifacts['sbom'].read_bytes()).hexdigest()} sbom.cdx.json"
if line.endswith(" sbom.cdx.json") else line
for line in checksum_lines
]
artifacts["checksums"].write_text("\n".join(checksum_lines) + "\n", encoding="utf-8")
with pytest.raises(ReleaseError, match="canonical archived-inventory projection"):
verify_release(tmp_path / "dist", _commit(root), root)
@pytest.mark.parametrize(
("filename", "target_id"),
[
("example-1.0-cp312-cp312-win_amd64.whl", "runtime-linux-cp312"),
("example-1.0-cp312-cp312-musllinux_1_2_x86_64.whl", "runtime-linux-cp312"),
("example-1.0-cp312-cp312-manylinux_2_28_x86_64.whl", "runtime-linux-cp312"),
("example-1.0-cp312-cp312-macosx_12_0_arm64.whl", "runtime-macos-arm-cp312"),
("example-1.0-cp312-cp312-macosx_11_0_x86_64.whl", "runtime-macos-arm-cp312"),
],
)
def test_wheel_tag_policy_rejects_cross_target_and_boundary_swaps(filename: str, target_id: str) -> None:
inventory = release._load_dependency_inventory(RELEASE_SCRIPT.parents[1])
target = next(item for item in inventory["targets"] if item["id"] == target_id)
with pytest.raises(ReleaseError, match="wheel platform|newer"):
release._wheel_is_compatible(filename, target, "example", "1.0")
def test_inventory_rejects_direct_flag_and_empty_graph_forgery(tmp_path: Path) -> None:
root = _repository(tmp_path)
def forge(inventory):
inventory["dependency_edges"] = []
for target in inventory["targets"]:
for component in target["components"]:
component["direct"] = True
_rewrite_inventory(root, forge)
with pytest.raises(ReleaseError, match="direct component flags mismatch"):
build_sbom(root, "claude-ads", "2.0.0")
@pytest.mark.parametrize("case", ["remove", "rename", "assignment", "text", "omit-document", "extra-artifact", "documentless"])
def test_inventory_rejects_bundled_notice_forgery(tmp_path: Path, case: str) -> None:
root = _repository(tmp_path)
def forge(inventory):
if case == "remove":
inventory["bundled_notices"].pop()
elif case == "rename":
inventory["bundled_notices"][0]["id"] = "renamed"
elif case == "assignment":
next(item for item in inventory["component_catalog"] if item["name"] == "fonttools")["bundled_notice_ids"] = []
elif case == "text":
inventory["bundled_notices"][0]["documents"][0]["text"] += "tampered"
elif case == "omit-document":
inventory["bundled_notices"][0]["documents"].pop()
elif case == "extra-artifact":
inventory["bundled_notices"][0]["documents"][0]["artifact_sha256s"].append("0" * 64)
else:
next(item for item in inventory["bundled_notices"] if item["component"] == "webencodings")["documentless_artifact_sha256s"] = []
_rewrite_inventory(root, forge)
with pytest.raises(ReleaseError, match="notice"):
build_sbom(root, "claude-ads", "2.0.0")
def test_inventory_rejects_arbitrary_license_and_header_policy(tmp_path: Path) -> None:
root = _repository(tmp_path)
_rewrite_inventory(root, lambda inventory: inventory["component_catalog"][0].__setitem__("license_expression", "Definitely-A-License"))
with pytest.raises(ReleaseError, match="reviewed license"):
build_sbom(root, "claude-ads", "2.0.0")
for field, value in (("managed_lock_python_range", ">=3.11"), ("source_date_epoch", 0), ("policy", "looks fine")):
nested = tmp_path / field
nested.mkdir()
candidate = _repository(nested)
_rewrite_inventory(candidate, lambda inventory, field=field, value=value: inventory["resolution"].__setitem__(field, value))
with pytest.raises(ReleaseError, match="policy mismatch"):
build_sbom(candidate, "claude-ads", "2.0.0")
def test_normalized_target_evidence_is_honest_about_foreign_source_and_native_ci_requirement() -> None:
root = RELEASE_SCRIPT.parents[1]
linux = json.loads((root / "control-plane/dependency-evidence/runtime-linux-cp311.json").read_text(encoding="utf-8"))
windows = json.loads((root / "control-plane/dependency-evidence/development-windows-cp311.json").read_text(encoding="utf-8"))
assert linux["evidence_class"] == "cross-target-pip-resolution-requiring-native-ci-confirmation"
assert linux["source_environment"]["python_version"] == "3.14"
assert linux["source_environment"]["sys_platform"] == "linux"
assert windows["normalization_notes"] and "colorama" in windows["normalization_notes"][0]
def test_standalone_verify_rejects_self_consistent_archive_inventory_tamper(tmp_path: Path) -> None:
root = _repository(tmp_path)
artifacts = build_release(root, tmp_path / "dist")
archive_path = artifacts["archive"]
manifest = json.loads(artifacts["manifest"].read_text(encoding="utf-8"))
archive_root = manifest["archive"]["root"]
inventory_member = f"{archive_root}/control-plane/manifests/dependency-inventory.json"
with zipfile.ZipFile(archive_path) as archive:
infos = archive.infolist()
contents = {info.filename: archive.read(info.filename) for info in infos}
inventory = json.loads(contents[inventory_member])
inventory["component_catalog"].reverse() # Semantically equivalent, but not the reviewed bytes.
inventory_bytes = (json.dumps(inventory, indent=2, sort_keys=True) + "\n").encode()
contents[inventory_member] = inventory_bytes
replacement = archive_path.with_suffix(".replacement")
with zipfile.ZipFile(replacement, "w", compression=zipfile.ZIP_DEFLATED, compresslevel=9) as archive:
for old in infos:
info = zipfile.ZipInfo(old.filename, old.date_time)
info.create_system = old.create_system
info.external_attr = old.external_attr
info.compress_type = zipfile.ZIP_DEFLATED
archive.writestr(info, contents[old.filename], compress_type=zipfile.ZIP_DEFLATED, compresslevel=9)
replacement.replace(archive_path)
record = next(item for item in manifest["files"] if item["path"] == "control-plane/manifests/dependency-inventory.json")
record.update(size=len(inventory_bytes), sha256=hashlib.sha256(inventory_bytes).hexdigest())
manifest["archive"].update(size=archive_path.stat().st_size, sha256=hashlib.sha256(archive_path.read_bytes()).hexdigest())
artifacts["manifest"].write_text(json.dumps(manifest, indent=2, sort_keys=True) + "\n", encoding="utf-8")
sbom = release._build_sbom_from_inventory(
inventory, manifest["product"]["name"], manifest["product"]["version"],
manifest["source"]["commit"], hashlib.sha256(inventory_bytes).hexdigest(),
)
artifacts["sbom"].write_text(json.dumps(sbom, indent=2, sort_keys=True) + "\n", encoding="utf-8")
artifacts["checksums"].write_text(
"".join(f"{hashlib.sha256(path.read_bytes()).hexdigest()} {path.name}\n" for path in (archive_path, artifacts["manifest"], artifacts["sbom"])),
encoding="utf-8",
)
with pytest.raises(ReleaseError, match="trusted Git commit|independently reviewed document"):
verify_release(tmp_path / "dist", _commit(root), root)
def test_package_requires_clean_head_subject(tmp_path: Path) -> None:
root = _repository(tmp_path)
(root / "README.md").write_text("dirty\n", encoding="utf-8")
with pytest.raises(ReleaseError, match="clean index and worktree"):
build_release(root, tmp_path / "dist")
def test_claude_command_contract_distinguishes_plugin_namespace() -> None:
@@ -263,9 +544,14 @@ def test_remote_ci_verifier_requires_exact_private_subject_and_all_jobs(
"Core tests (Python 3.11)",
"Core tests (Python 3.12)",
"Full test suite",
"Installer tests (ubuntu-latest)",
"Installer tests (macos-latest)",
"Installer tests (windows-latest)",
"Installer tests (ubuntu-latest, Python 3.11)",
"Installer tests (ubuntu-latest, Python 3.12)",
"Installer tests (macos-15, Python 3.11)",
"Installer tests (macos-15, Python 3.12)",
"Installer tests (macos-15-intel, Python 3.11)",
"Installer tests (macos-15-intel, Python 3.12)",
"Installer tests (windows-latest, Python 3.11)",
"Installer tests (windows-latest, Python 3.12)",
"Reproducible package smoke test",
]
@@ -0,0 +1,70 @@
from __future__ import annotations
import copy
import importlib.util
import json
from pathlib import Path
from types import SimpleNamespace
import pytest
ROOT = Path(__file__).resolve().parents[2]
SPEC = importlib.util.spec_from_file_location("target_lock_verifier", ROOT / "scripts/verify_target_lock.py")
assert SPEC and SPEC.loader
verifier = importlib.util.module_from_spec(SPEC)
SPEC.loader.exec_module(verifier)
@pytest.fixture
def target_case(tmp_path, monkeypatch):
inventory = verifier.release._load_dependency_inventory(ROOT)
target = next(item for item in inventory["targets"] if item["id"] == "runtime-linux-cp311")
monkeypatch.setattr(verifier.release, "_load_dependency_inventory", lambda root: inventory)
monkeypatch.setattr(verifier, "native_target_id", lambda profile: "runtime-linux-cp311")
monkeypatch.setattr(verifier.subprocess, "run", lambda *a, **k: SimpleNamespace(returncode=0, stdout="a" * 40 + "\n"))
report = {"pip_version": __import__("pip").__version__, "install": []}
wheels = tmp_path / "wheels"; wheels.mkdir()
expected_hashes = {}
for component in target["components"]:
artifact = component["artifact"]
report["install"].append({"metadata": {"name": component["name"], "version": component["version"]}, "download_info": {"url": artifact["url"], "archive_info": {"hashes": {"sha256": artifact["sha256"]}}}})
(wheels / artifact["filename"]).write_bytes(b"fixture")
expected_hashes[artifact["filename"]] = artifact["sha256"]
original_sha = verifier.sha256
monkeypatch.setattr(verifier, "sha256", lambda path: expected_hashes[path.name] if path.suffix == ".whl" else original_sha(path))
report_path = tmp_path / "report.json"; report_path.write_text(json.dumps(report), encoding="utf-8")
return report, report_path, wheels, tmp_path / "attestation.json"
def test_valid_native_report_writes_commit_lock_inventory_resolver_and_runner_bindings(target_case, monkeypatch):
report, report_path, wheels, output = target_case
monkeypatch.setenv("RUNNER_OS", "Linux"); monkeypatch.setenv("RUNNER_ARCH", "X64")
verifier.verify(ROOT, "runtime", report_path, wheels, output)
result = json.loads(output.read_text())
assert result["source_commit"] == "a" * 40
assert result["pip_version"] == report["pip_version"]
assert result["dependency_inventory_sha256"] and result["requirements_lock_sha256"]
assert result["environment"]["runner"] == {"RUNNER_ARCH": "X64", "RUNNER_OS": "Linux"}
@pytest.mark.parametrize("mutation", ["url", "hash", "missing", "extra"])
def test_native_report_rejects_wrong_or_incomplete_closure(target_case, mutation):
report, report_path, wheels, output = target_case
if mutation == "url": report["install"][0]["download_info"]["url"] = "https://files.pythonhosted.org/wrong.whl"
elif mutation == "hash": report["install"][0]["download_info"]["archive_info"]["hashes"]["sha256"] = "0" * 64
elif mutation == "missing": report["install"].pop()
else: report["install"].append(copy.deepcopy(report["install"][0]))
report_path.write_text(json.dumps(report), encoding="utf-8")
with pytest.raises(verifier.release.ReleaseError): verifier.verify(ROOT, "runtime", report_path, wheels, output)
def test_native_report_rejects_wrong_wheel_set(target_case):
_report, report_path, wheels, output = target_case
next(wheels.glob("*.whl")).unlink()
with pytest.raises(verifier.release.ReleaseError, match="wheel"): verifier.verify(ROOT, "runtime", report_path, wheels, output)
def test_native_target_rejects_unsupported_interpreter(monkeypatch):
monkeypatch.setattr(verifier.sys, "version_info", (3, 14, 0))
with pytest.raises(verifier.release.ReleaseError, match="3.11 or 3.12"): verifier.native_target_id("runtime")
+82
View File
@@ -28,6 +28,22 @@ def _run(script: str, *args: str) -> subprocess.CompletedProcess[str]:
)
def _fake_python(tmp_path: Path, target: str, fail_venv: bool = False) -> Path:
directory = tmp_path / "fake-bin"
directory.mkdir()
script = directory / "python3"
if target.count("|") == 3:
target += "|glibc|2.17|supported" if "|linux|" in target else "|none|11.0|supported"
script.write_text(
"#!/bin/sh\n"
f"if [ \"$1\" = \"-c\" ]; then printf '%s\\n' '{target}'; exit 0; fi\n"
+ ("exit 42\n" if fail_venv else "exec /usr/bin/python3 \"$@\"\n"),
encoding="utf-8",
)
script.chmod(0o755)
return directory
def _install(tmp_path: Path) -> tuple[Path, Path]:
skills = tmp_path / "skills"
agents = tmp_path / "agents"
@@ -55,11 +71,28 @@ def test_bash_installer_syntax_and_no_global_pip_escape_hatch():
assert "--break-system-packages" not in installer
assert "curl -fsSL" not in installer
assert "python3 -m venv" in installer
assert "--require-hashes --only-binary=:all:" in installer
assert "-m pip check" in installer
assert "requirements.lock" in installer
assert "--report" in installer
assert "write_install_receipt.py" in installer
assert "managed-runtime-receipt.json" in installer
assert "banana-claude" not in installer
powershell = (ROOT / "install.ps1").read_text(encoding="utf-8")
assert "--require-hashes --only-binary=:all:" in powershell
assert "-m pip check" in powershell
assert "requirements.lock" in powershell
assert "--report" in powershell
assert "write_install_receipt.py" in powershell
assert "managed-runtime-receipt.json" in powershell
assert "banana-claude" not in powershell
@BASH_INSTALLER_ONLY
def test_manifest_owned_uninstall_preserves_unrelated_ads_skill(tmp_path):
skills, agents = _install(tmp_path)
assert (skills / "ads" / "requirements.lock").is_file()
unrelated = skills / "ads-user-owned"
unrelated.mkdir()
(unrelated / "SKILL.md").write_text("user data", encoding="utf-8")
@@ -72,6 +105,7 @@ def test_manifest_owned_uninstall_preserves_unrelated_ads_skill(tmp_path):
)
assert result.returncode == 0, result.stdout + result.stderr
assert not (skills / "ads" / "SKILL.md").exists()
assert not (skills / "ads" / "requirements.lock").exists()
assert unrelated.joinpath("SKILL.md").read_text(encoding="utf-8") == "user data"
assert not (skills / ".claude-ads-claude.manifest").exists()
@@ -128,6 +162,54 @@ def test_installer_refuses_symlink_escape(tmp_path):
assert not (outside / "SKILL.md").exists()
@BASH_INSTALLER_ONLY
def test_unsupported_python_fails_before_any_destination_mutation(tmp_path):
skills, agents = tmp_path / "skills", tmp_path / "agents"
fake_bin = _fake_python(tmp_path, "cpython|3.14|linux|x86_64")
result = subprocess.run(
["bash", str(ROOT / "install.sh"), "--target=claude", "--source=local", f"--skill-dir={skills}", f"--agent-dir={agents}"],
cwd=ROOT, env={**os.environ, "PATH": f"{fake_bin}:{os.environ['PATH']}"}, capture_output=True, text=True, check=False,
)
assert result.returncode != 0
assert "No verified dependency lock target" in result.stderr
assert not skills.exists()
assert not agents.exists()
@BASH_INSTALLER_ONLY
def test_musl_linux_fails_before_any_destination_mutation(tmp_path):
skills, agents = tmp_path / "skills", tmp_path / "agents"
fake_bin = _fake_python(tmp_path, "cpython|3.12|linux|x86_64|musl|1.2.5|unsupported")
result = subprocess.run(
["bash", str(ROOT / "install.sh"), "--target=claude", "--source=local", f"--skill-dir={skills}", f"--agent-dir={agents}"],
cwd=ROOT, env={**os.environ, "PATH": f"{fake_bin}:{os.environ['PATH']}"}, capture_output=True, text=True, check=False,
)
assert result.returncode != 0
assert "musl Linux is unsupported" in result.stderr
assert not skills.exists()
assert not agents.exists()
@BASH_INSTALLER_ONLY
def test_dependency_failure_leaves_complete_ownership_manifest_for_uninstall(tmp_path):
skills, agents = tmp_path / "skills", tmp_path / "agents"
stale_receipt = skills / "ads" / "managed-runtime-receipt.json"
stale_receipt.parent.mkdir(parents=True)
stale_receipt.write_text('{"stale": true}\n', encoding="utf-8")
fake_bin = _fake_python(tmp_path, "cpython|3.12|linux|x86_64", fail_venv=True)
result = subprocess.run(
["bash", str(ROOT / "install.sh"), "--target=claude", "--source=local", f"--skill-dir={skills}", f"--agent-dir={agents}"],
cwd=ROOT, env={**os.environ, "PATH": f"{fake_bin}:{os.environ['PATH']}"}, capture_output=True, text=True, check=False,
)
assert result.returncode != 0
assert (skills / ".claude-ads-claude.manifest").is_file()
assert (skills / "ads" / "requirements.lock").is_file()
assert not stale_receipt.exists()
uninstall = _run("uninstall.sh", "--target=claude", f"--skill-dir={skills}", f"--agent-dir={agents}")
assert uninstall.returncode == 0, uninstall.stdout + uninstall.stderr
assert not (skills / "ads" / "requirements.lock").exists()
@BASH_INSTALLER_ONLY
def test_manifest_traversal_is_rejected_before_removal(tmp_path):
skills, agents = _install(tmp_path)