headroom/scripts/ci/verify_hf_model_cache.py
JD Davis adb793bee1
ci: harden PR governance and model cache checks (#1401)
## Description

Hardens two routine PR-review pain points from the recent open-PR sweep:

- PR Governance reruns could keep validating the stale
`pull_request_target` event body even after the live PR description had
been fixed.
- Main CI model-cache misses could surface as dozens of unrelated
memory-test failures instead of one clear cache-preflight failure.

This intentionally avoids PyPI/package-bloat and release/nightly
workflow changes so the PR stays scoped to review and CI stabilization.

## Type of Change

- [x] Bug fix
- [ ] New feature
- [ ] Documentation
- [x] Refactor
- [x] Tests only

## Changes Made

- Added `--body-file` support to `scripts/pr-governance.py` so workflows
can validate the current PR body rather than stale rerun payloads.
- Updated PR Governance to fetch the live PR body via the GitHub API
before validating template fields.
- Added a CI preflight script that loads the default
sentence-transformer model in offline mode and verifies the expected
embedding dimension.
- Wired that preflight into the sharded CI job before pytest starts,
turning missing/corrupt Hugging Face caches into one early, actionable
failure.
- Added workflow/script regression tests for the live-body override and
model-cache preflight placement.

## Testing

- [x] Unit tests
- [x] Lint/static checks
- [ ] Integration tests
- [ ] Manual testing

### Test Output

```text
uv run --with pytest --with pytest-asyncio python -m pytest scripts/tests/test_pr_governance.py scripts/tests/test_pr_health_workflow.py scripts/tests/test_ci_workflow.py -q
9 passed in 0.04s

uv run ruff check scripts/pr-governance.py scripts/ci/verify_hf_model_cache.py scripts/tests/test_pr_governance.py scripts/tests/test_pr_health_workflow.py scripts/tests/test_ci_workflow.py
All checks passed!

python -m py_compile scripts\ci\verify_hf_model_cache.py scripts\pr-governance.py
# passed
```

## Real Behavior Proof

- Environment: Windows 11, Python 3.13.3, isolated worktree
`C:\git\headroom\.worktrees\stabilization-hardening`.
- Exact command / steps: Ran the focused governance/workflow tests, ruff
on touched Python files, and `py_compile` for the executable scripts.
- Observed result: Governance tests prove a stale event body can be
overridden by the live PR body; workflow tests prove CI validates live
PR body and runs the Hugging Face offline-cache preflight before pytest
shards.
- Not tested: Full GitHub CI before PR creation; that will run on this
PR. The new Hugging Face preflight itself is intentionally not run
locally because it depends on the CI-warmed offline model cache.

## Review Readiness

- [x] I have performed a self-review
- [x] This PR is ready for human review
2026-06-26 21:34:34 -07:00

56 lines
1.8 KiB
Python

#!/usr/bin/env python3
"""Verify that CI can load the default embedding model offline.
The main test shards run with TRANSFORMERS_OFFLINE=1. If the Hugging Face cache
misses or is partially restored, many unrelated memory tests fail later with
network/cache errors. This preflight keeps that failure mode early and specific.
"""
from __future__ import annotations
import os
import sys
def main() -> int:
os.environ.setdefault("HF_HUB_OFFLINE", "1")
os.environ.setdefault("TRANSFORMERS_OFFLINE", "1")
os.environ.setdefault("HF_HUB_DISABLE_TELEMETRY", "1")
from headroom.models.config import ML_MODEL_DEFAULTS
model_name = ML_MODEL_DEFAULTS.sentence_transformer
expected_dim = ML_MODEL_DEFAULTS.sentence_transformer_dim
try:
from sentence_transformers import SentenceTransformer
model = SentenceTransformer(model_name, local_files_only=True)
embedding = model.encode(["headroom cache preflight"], convert_to_numpy=True)
except Exception as exc:
print(
f"::error::Hugging Face offline model cache is not usable for {model_name!r}: {exc}",
file=sys.stderr,
)
print(
"The prefetch-model job or fallback download must populate "
"~/.cache/huggingface before offline test shards run.",
file=sys.stderr,
)
return 1
actual_dim = int(embedding.shape[-1])
if actual_dim != expected_dim:
print(
"::error::Loaded embedding model has unexpected dimension: "
f"{actual_dim} != {expected_dim}",
file=sys.stderr,
)
return 1
print(f"offline Hugging Face model cache OK: {model_name} ({actual_dim} dims)")
return 0
if __name__ == "__main__":
raise SystemExit(main())