mirror of
https://github.com/AgriciDaniel/claude-ads.git
synced 2026-09-14 20:36:30 +08:00
fix: harden v2 provenance and release gates
This commit is contained in:
+92
-17
@@ -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
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
|
||||
@@ -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]":
|
||||
|
||||
@@ -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
@@ -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
@@ -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}!"
|
||||
|
||||
@@ -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,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
|
||||
|
||||
@@ -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
@@ -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
File diff suppressed because it is too large
Load Diff
@@ -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())
|
||||
@@ -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())
|
||||
@@ -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())
|
||||
@@ -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"] = []
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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")
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user