diff --git a/.aiwg/bt6-maintainer.yaml b/.aiwg/bt6-maintainer.yaml index 9cedc3f..455bd19 100644 --- a/.aiwg/bt6-maintainer.yaml +++ b/.aiwg/bt6-maintainer.yaml @@ -19,14 +19,15 @@ delivery: allowedMergeMethods: ["rebase"] validation: quick: - - "python -m ruff check --select F obliteratus tests scripts/check_coverage_thresholds.py scripts/gemma4_12b_recursive_loop.py" + - "python -m ruff check --select F app.py obliteratus tests scripts/check_coverage_thresholds.py scripts/check_supply_chain_policy.py scripts/gemma4_12b_recursive_loop.py" + - "uv lock --check" full: - "python -m pytest" - "python -m build --sdist --wheel" - "python -c 'import obliteratus; print(obliteratus.__version__)'" - "python -m obliteratus --help" documentation: - - "python -m ruff check --select F obliteratus tests scripts/check_coverage_thresholds.py scripts/gemma4_12b_recursive_loop.py" + - "python -m ruff check --select F app.py obliteratus tests scripts/check_coverage_thresholds.py scripts/check_supply_chain_policy.py scripts/gemma4_12b_recursive_loop.py" researchIntegrity: - "python -m pytest tests/test_advanced_metrics.py tests/test_breakthrough_modules.py tests/test_community.py" riskSurfaces: @@ -47,9 +48,9 @@ riskSurfaces: concerns: ["cli-contract", "ui-contract", "platform-compatibility", "notebook-reproducibility"] requiredChecks: ["python -m pytest tests/test_cli.py tests/test_module_imports.py", "python -m obliteratus --help"] - id: "ci-supply-chain" - paths: [".github/workflows/**", "pyproject.toml"] + paths: [".github/workflows/**", "ci/**", "docs/SUPPLY_CHAIN_POLICY.md", "pyproject.toml", "scripts/check_supply_chain_policy.py", "uv.lock"] concerns: ["workflow-permissions", "dependency-pinning", "untrusted-pull-request-code"] - requiredChecks: ["python -m ruff check --select F obliteratus tests scripts/check_coverage_thresholds.py scripts/gemma4_12b_recursive_loop.py", "python -m build --sdist --wheel"] + requiredChecks: ["uv lock --check", "python -m pytest tests/test_ci_policy.py tests/test_supply_chain_policy.py", "python -m build --sdist --wheel"] research: corpusPaths: ["obliteratus/prompts.py", "community_results/**"] evidencePaths: ["paper/**", "docs/**", "community_results/**"] diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 10ee74c..f4d638c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,12 +16,15 @@ concurrency: env: PIP_DISABLE_PIP_VERSION_CHECK: "1" PIP_NO_INPUT: "1" + UV_VERSION: "0.12.4" jobs: package: name: Package runs-on: ubuntu-latest timeout-minutes: 15 + env: + BUILD_TOOLS: /tmp/obliteratus-ci-tools steps: - name: Check out repository @@ -34,13 +37,16 @@ jobs: cache: pip cache-dependency-path: | pyproject.toml - requirements*.txt + uv.lock - name: Install build tooling - run: python -m pip install "build==1.2.2.post1" "twine==7.0.0" + run: | + python -m pip install "uv==${UV_VERSION}" + UV_PROJECT_ENVIRONMENT="$BUILD_TOOLS" uv sync --locked --only-group ci - name: Build source and wheel distributions - run: python -m build --sdist --wheel + run: >- + "$BUILD_TOOLS/bin/python" -m build --sdist --wheel - name: Verify wheel contents and entry point run: | @@ -78,7 +84,7 @@ jobs: - name: Validate distribution metadata run: | mkdir -p package-evidence - python -m twine check dist/* | tee package-evidence/twine-check.txt + "$BUILD_TOOLS/bin/python" -m twine check dist/* | tee package-evidence/twine-check.txt sha256sum dist/* | tee package-evidence/SHA256SUMS - name: Verify installed wheel contract @@ -168,10 +174,12 @@ jobs: with: python-version: "3.12" cache: pip - cache-dependency-path: pyproject.toml + cache-dependency-path: | + pyproject.toml + uv.lock - name: Install Ruff - run: python -m pip install "ruff==0.8.6" + run: python -m pip install "ruff==0.16.2" - name: Install actionlint with checksum verification env: @@ -190,15 +198,17 @@ jobs: - name: Enforce Ruff F gate run: >- - python -m ruff check --select F obliteratus tests + python -m ruff check --select F app.py obliteratus tests scripts/check_coverage_thresholds.py + scripts/check_supply_chain_policy.py scripts/gemma4_12b_recursive_loop.py - name: Report E501 legacy baseline if: always() run: >- - python -m ruff check --select E501 --statistics obliteratus tests + python -m ruff check --select E501 --statistics app.py obliteratus tests scripts/check_coverage_thresholds.py + scripts/check_supply_chain_policy.py scripts/gemma4_12b_recursive_loop.py || true test: @@ -219,6 +229,7 @@ jobs: HF_HUB_OFFLINE: "1" TOKENIZERS_PARALLELISM: "false" TRANSFORMERS_OFFLINE: "1" + TEST_ENV: /tmp/obliteratus-test-env steps: - name: Check out repository @@ -231,22 +242,17 @@ jobs: cache: pip cache-dependency-path: | pyproject.toml - requirements*.txt + uv.lock - - name: Install CPU PyTorch - run: python -m pip install --index-url https://download.pytorch.org/whl/cpu "torch>=2.0" - - - name: Install package and test tools + - name: Install locked package and test tools run: | - python - <<'PY' > /tmp/torch-cpu-constraint.txt - import torch - print(f"torch=={torch.__version__}") - PY - python -m pip install -e ".[dev]" -c /tmp/torch-cpu-constraint.txt + python -m pip install "uv==${UV_VERSION}" + UV_PROJECT_ENVIRONMENT="$TEST_ENV" \ + uv sync --locked --no-default-groups --extra dev --no-editable - name: Smoke import and CLI run: | - python - <<'PY' + "$TEST_ENV/bin/python" - <<'PY' import obliteratus version = getattr(obliteratus, "__version__", None) @@ -255,12 +261,12 @@ jobs: else: print("obliteratus import: ok") PY - python -m obliteratus --help + "$TEST_ENV/bin/python" -m obliteratus --help - name: Run tests with coverage run: | mkdir -p test-results - python -m pytest \ + "$TEST_ENV/bin/python" -m pytest \ -m "not slow and not gpu and not mps and not mlx and not network and not download and not remote" \ --cov-branch \ --cov-fail-under=0 \ @@ -270,7 +276,7 @@ jobs: - name: Enforce line and branch coverage floors run: >- - python scripts/check_coverage_thresholds.py + "$TEST_ENV/bin/python" scripts/check_coverage_thresholds.py "test-results/coverage-py${{ matrix.python-version }}.json" --min-line 49 --min-branch 36 @@ -283,3 +289,140 @@ jobs: path: test-results/ if-no-files-found: error retention-days: 14 + + supply-chain: + name: Supply chain + runs-on: ubuntu-latest + timeout-minutes: 30 + env: + EVIDENCE: /tmp/supply-chain-evidence + GITLEAKS_SHA256: "551f6fc83ea457d62a0d98237cbad105af8d557003051f41f3e7ca7b3f2470eb" + GITLEAKS_VERSION: "8.30.1" + RUNTIME_ENV: /tmp/obliteratus-runtime + SUPPLY_TOOLS: /tmp/obliteratus-supply-tools + + steps: + - name: Check out repository + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + + - name: Set up Python + uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 + with: + python-version: "3.12" + cache: pip + cache-dependency-path: | + pyproject.toml + uv.lock + + - name: Install locked supply-chain tooling + run: | + python -m pip install "uv==${UV_VERSION}" + uv lock --check + UV_PROJECT_ENVIRONMENT="$RUNTIME_ENV" \ + uv sync --locked --all-extras --no-default-groups --no-editable + UV_PROJECT_ENVIRONMENT="$SUPPLY_TOOLS" \ + uv sync --locked --only-group ci + mkdir -p "$EVIDENCE" + cd "$RUNNER_TEMP" + "$RUNTIME_ENV/bin/python" -I - <<'PY' | tee "$EVIDENCE/spaces-import.txt" + import gradio + import app + + assert type(app.demo).__name__ == "Blocks" + print(f"installed all-extras app import: {app.__file__}") + print(f"gradio: {gradio.__version__}") + PY + + - name: Validate exception policy + run: >- + python scripts/check_supply_chain_policy.py policy + --policy ci/supply-chain-policy.json + + - name: Scan checkout for secrets with redacted evidence + run: | + archive="$RUNNER_TEMP/gitleaks.tar.gz" + curl -fsSLo "$archive" \ + "https://github.com/gitleaks/gitleaks/releases/download/v${GITLEAKS_VERSION}/gitleaks_${GITLEAKS_VERSION}_linux_x64.tar.gz" + echo "${GITLEAKS_SHA256} ${archive}" | sha256sum -c - + tar -xzf "$archive" -C "$RUNNER_TEMP" gitleaks + set +e + "$RUNNER_TEMP/gitleaks" dir "$GITHUB_WORKSPACE" \ + --redact=100 \ + --report-format json \ + --report-path "$EVIDENCE/gitleaks.json" \ + --no-banner \ + --no-color + status=$? + set -e + echo "$status" > "$EVIDENCE/gitleaks.status" + + - name: Collect vulnerability evidence for supported Python versions + run: | + for version in 3.10 3.11 3.12; do + set +e + uv --preview-features audit-command,json-output audit \ + --locked \ + --python-version "$version" \ + --python-platform linux \ + --output-format json > "$EVIDENCE/uv-audit-py${version}.json" + status=$? + set -e + echo "$status" > "$EVIDENCE/uv-audit-py${version}.status" + done + + - name: Build wheel and bind CycloneDX SBOM + run: | + mkdir -p "$EVIDENCE/dist" + "$SUPPLY_TOOLS/bin/python" -m build --wheel --outdir "$EVIDENCE/dist" + uv --preview-features sbom-export export \ + --locked \ + --format cyclonedx1.5 \ + --all-extras \ + --no-dev \ + --no-editable \ + --output-file "$EVIDENCE/obliteratus.cdx.unbound.json" + mapfile -t wheels < <(find "$EVIDENCE/dist" -maxdepth 1 -type f -name '*.whl' -print) + if [ "${#wheels[@]}" -ne 1 ]; then + echo "expected exactly one wheel, found ${#wheels[@]}" + exit 1 + fi + python scripts/check_supply_chain_policy.py sbom \ + --input "$EVIDENCE/obliteratus.cdx.unbound.json" \ + --wheel "${wheels[0]}" \ + --output "$EVIDENCE/obliteratus.cdx.json" + sha256sum "${wheels[0]}" > "$EVIDENCE/distribution.SHA256SUM" + + - name: Collect packaged-dependency license inventory + run: >- + "$SUPPLY_TOOLS/bin/pip-licenses" + --python "$RUNTIME_ENV/bin/python" + --format json + --output-file "$EVIDENCE/licenses.json" + + - name: Enforce vulnerability, secret, and license policies + run: | + for version in 3.10 3.11 3.12; do + python scripts/check_supply_chain_policy.py audit \ + --policy ci/supply-chain-policy.json \ + --evidence "$EVIDENCE/uv-audit-py${version}.json" \ + --scanner-status "$EVIDENCE/uv-audit-py${version}.status" \ + --decision "$EVIDENCE/uv-audit-py${version}.decision.json" + done + python scripts/check_supply_chain_policy.py secrets \ + --policy ci/supply-chain-policy.json \ + --evidence "$EVIDENCE/gitleaks.json" \ + --scanner-status "$EVIDENCE/gitleaks.status" \ + --decision "$EVIDENCE/gitleaks.decision.json" + python scripts/check_supply_chain_policy.py licenses \ + --policy ci/supply-chain-policy.json \ + --evidence "$EVIDENCE/licenses.json" \ + --decision "$EVIDENCE/licenses.decision.json" + + - name: Upload supply-chain evidence + if: always() + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: supply-chain-evidence-py3.12 + path: ${{ runner.temp }}/supply-chain-evidence/ + if-no-files-found: error + retention-days: 14 diff --git a/WORKSPACE.md b/WORKSPACE.md index 8e6f6eb..1858888 100644 --- a/WORKSPACE.md +++ b/WORKSPACE.md @@ -42,7 +42,7 @@ accelerator, or remote-execution credentials. Canonical required checks: -- `python -m ruff check --select F obliteratus tests scripts/check_coverage_thresholds.py scripts/gemma4_12b_recursive_loop.py` +- `python -m ruff check --select F app.py obliteratus tests scripts/check_coverage_thresholds.py scripts/check_supply_chain_policy.py scripts/gemma4_12b_recursive_loop.py` - `python -m pytest` (includes the measured 49% coverage floor) - CI additionally enforces the measured 36% branch-coverage floor from its retained coverage JSON report. @@ -55,6 +55,13 @@ in an independent environment outside the checkout, exercises both CLI entry paths, and retains the distributions plus evidence. Immutable CI action/tool pins are recorded in [ci/digests.txt](ci/digests.txt). +Python CI resolution is locked by `uv.lock`, including the official CPU-only +PyTorch source for Linux and Windows. The required Supply chain job scans all +supported Python versions for known vulnerabilities, scans the checkout for +secrets with fully redacted evidence, enforces the packaged-dependency license +allow list, and binds a CycloneDX SBOM to the built wheel. Exception and update +rules are documented in [docs/SUPPLY_CHAIN_POLICY.md](docs/SUPPLY_CHAIN_POLICY.md). + GPU, MPS, model-download, network, and remote-execution checks are conditional release or risk-surface gates, not part of the default CPU job. diff --git a/app.py b/app.py index 0829d4e..722ae06 100644 --- a/app.py +++ b/app.py @@ -892,7 +892,7 @@ def _get_vram_html() -> str: reserved_html = ( f'reserved: {mem.reserved_gb:.1f} GB' if mem.reserved_gb > 0 - else f'unified memory' + else 'unified memory' ) return ( f'