headroom/scripts/tests/test_pr_governance.py

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

314 lines
9.2 KiB
Python
Raw Permalink Normal View History

"""Tests for pr-governance.py."""
from __future__ import annotations
import importlib.util
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 23:34:34 -05:00
import json
import sys
from pathlib import Path
def _load_module():
script = Path(__file__).parent.parent / "pr-governance.py"
spec = importlib.util.spec_from_file_location("pr_governance", script)
assert spec is not None
assert spec.loader is not None
module = importlib.util.module_from_spec(spec)
sys.modules[spec.name] = module
spec.loader.exec_module(module)
return module
ci(governance): require a Conventional Commit PR title (#3063) ## Description The repo squash-merges, so the PR title — not the commits inside the PR — becomes the commit subject on `main`. Nothing validated it. `commitlint` (`ci.yml:429`) lints a PR's *commits* and therefore cannot catch this by construction: a PR with clean conventional commits and a prose title passes CI and then lands a prose subject on `main`. That is how `31452426` landed: ``` Unify savings attribution across stats, perf, metrics, and dashboard (#2976) ``` release-please cannot parse it — `unexpected token ' ' at 1:6`, because `Unify` is five characters and position six is a space where the parser needs `(`, `!` or `:`. The change is silently dropped from the changelog. ## Type of Change - [x] Bug fix (non-breaking change that fixes an issue) - [ ] New feature (non-breaking change that adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to change) - [ ] Documentation update ## Changes Made - Added `COMMIT_TYPES` and `TITLE_RE` to `scripts/pr-governance.py`, matching `.commitlintrc.json`'s `type-enum`. - Added a title check to `validate_pull_request`, reported through the existing governance comment. - Added a test asserting `COMMIT_TYPES` equals `.commitlintrc.json`'s `type-enum`, so the two gates cannot drift apart. - Gave the `_event` test helper the `title` field a real `pull_request` payload always carries. ## Testing - [x] Unit tests pass - [x] Linting passes (ruff check + format) - [ ] Type checking passes — N/A (script + test only) - [x] New tests added for new functionality ### Test Output ```text $ .venv/bin/python -m pytest scripts/tests/test_pr_governance.py -q 12 passed in 0.02s ``` Against the parent commit: ```text FAILED test_validate_pull_request_rejects_non_conventional_title FAILED test_validate_pull_request_rejects_empty_and_typeless_titles FAILED test_commit_types_match_commitlint_config 3 failed, 9 passed ``` ## Real Behavior Proof - Environment: macOS 15 (darwin 25.4.0), Python 3.12.13, `scripts/pr-governance.py` loaded directly. - Exact command / steps: ran `TITLE_RE` against the titles of **all 117 pull requests opened in this repository between 2026-08-10 and 2026-08-16**, pulled with `gh pr list --json title`. - Observed result: exactly one title is flagged — `#2976`, `Unify savings attribution across stats, perf, metrics, and dashboard`, the one that jammed the release. Zero false positives across the other 116, including every Dependabot `deps: bump ...` title, `chore: release main`, and scoped forms like `fix(proxy/anthropic): ...`. - Not tested: the check running inside a live `pull_request_target` event on a GitHub runner. ## Runtime Rollout Safety - Rollout-managed feature(s): none. - Minimum rollout channel: N/A. - Stable/default behavior changed: yes — a PR with a non-conventional title now gets the `status: needs author action` label and a governance comment. - Kill switch / disable path: revert; the check is not independently configurable. - Unsafe override required: none. - Qualification impact: none. - Rollback path: revert this commit. ## Review Readiness - [x] I have performed a self-review - [x] This PR is ready for human review ## Checklist - [x] My code follows the project's style guidelines - [x] I have performed a self-review of my code - [x] I have commented my code, particularly in hard-to-understand areas - [x] My changes generate no new warnings - [x] I have added tests that prove my fix is effective - [x] New and existing unit tests pass locally with my changes ## Additional Notes The check lives in `pr-governance.py` rather than `ci.yml` for two reasons: 1. `ci.yml`'s `pull_request` trigger has no `edited` type, so a corrected title would never be re-checked. 2. Its `paths-ignore` skips docs-only PRs, which still squash-merge a subject onto `main`. `pr-health.yml` already triggers on `edited` and reports through the same governance comment the author is reading anyway. Bot PRs keep their existing exemption — Dependabot and release-please titles are already conventional, and the early return for `is_bot_pr` is untouched. Co-authored-by: Tejas Chopra <tejas@Tejass-MacBook-Pro.local>
2026-08-16 19:05:36 -07:00
def _event(
body: str,
*,
draft: bool = False,
login: str = "octocat",
title: str = "feat(governance): add a required PR-governance gate",
) -> dict[str, object]:
# A real `pull_request` payload always carries a title, and squash-merge
# turns it into the commit subject on main, so the default here is a valid
# Conventional Commit. Tests that exercise the title rule pass their own.
return {
"pull_request": {
"number": 42,
"draft": draft,
ci(governance): require a Conventional Commit PR title (#3063) ## Description The repo squash-merges, so the PR title — not the commits inside the PR — becomes the commit subject on `main`. Nothing validated it. `commitlint` (`ci.yml:429`) lints a PR's *commits* and therefore cannot catch this by construction: a PR with clean conventional commits and a prose title passes CI and then lands a prose subject on `main`. That is how `31452426` landed: ``` Unify savings attribution across stats, perf, metrics, and dashboard (#2976) ``` release-please cannot parse it — `unexpected token ' ' at 1:6`, because `Unify` is five characters and position six is a space where the parser needs `(`, `!` or `:`. The change is silently dropped from the changelog. ## Type of Change - [x] Bug fix (non-breaking change that fixes an issue) - [ ] New feature (non-breaking change that adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to change) - [ ] Documentation update ## Changes Made - Added `COMMIT_TYPES` and `TITLE_RE` to `scripts/pr-governance.py`, matching `.commitlintrc.json`'s `type-enum`. - Added a title check to `validate_pull_request`, reported through the existing governance comment. - Added a test asserting `COMMIT_TYPES` equals `.commitlintrc.json`'s `type-enum`, so the two gates cannot drift apart. - Gave the `_event` test helper the `title` field a real `pull_request` payload always carries. ## Testing - [x] Unit tests pass - [x] Linting passes (ruff check + format) - [ ] Type checking passes — N/A (script + test only) - [x] New tests added for new functionality ### Test Output ```text $ .venv/bin/python -m pytest scripts/tests/test_pr_governance.py -q 12 passed in 0.02s ``` Against the parent commit: ```text FAILED test_validate_pull_request_rejects_non_conventional_title FAILED test_validate_pull_request_rejects_empty_and_typeless_titles FAILED test_commit_types_match_commitlint_config 3 failed, 9 passed ``` ## Real Behavior Proof - Environment: macOS 15 (darwin 25.4.0), Python 3.12.13, `scripts/pr-governance.py` loaded directly. - Exact command / steps: ran `TITLE_RE` against the titles of **all 117 pull requests opened in this repository between 2026-08-10 and 2026-08-16**, pulled with `gh pr list --json title`. - Observed result: exactly one title is flagged — `#2976`, `Unify savings attribution across stats, perf, metrics, and dashboard`, the one that jammed the release. Zero false positives across the other 116, including every Dependabot `deps: bump ...` title, `chore: release main`, and scoped forms like `fix(proxy/anthropic): ...`. - Not tested: the check running inside a live `pull_request_target` event on a GitHub runner. ## Runtime Rollout Safety - Rollout-managed feature(s): none. - Minimum rollout channel: N/A. - Stable/default behavior changed: yes — a PR with a non-conventional title now gets the `status: needs author action` label and a governance comment. - Kill switch / disable path: revert; the check is not independently configurable. - Unsafe override required: none. - Qualification impact: none. - Rollback path: revert this commit. ## Review Readiness - [x] I have performed a self-review - [x] This PR is ready for human review ## Checklist - [x] My code follows the project's style guidelines - [x] I have performed a self-review of my code - [x] I have commented my code, particularly in hard-to-understand areas - [x] My changes generate no new warnings - [x] I have added tests that prove my fix is effective - [x] New and existing unit tests pass locally with my changes ## Additional Notes The check lives in `pr-governance.py` rather than `ci.yml` for two reasons: 1. `ci.yml`'s `pull_request` trigger has no `edited` type, so a corrected title would never be re-checked. 2. Its `paths-ignore` skips docs-only PRs, which still squash-merge a subject onto `main`. `pr-health.yml` already triggers on `edited` and reports through the same governance comment the author is reading anyway. Bot PRs keep their existing exemption — Dependabot and release-please titles are already conventional, and the early return for `is_bot_pr` is untouched. Co-authored-by: Tejas Chopra <tejas@Tejass-MacBook-Pro.local>
2026-08-16 19:05:36 -07:00
"title": title,
"body": body,
"user": {"login": login},
}
}
VALID_BODY = """## Description
Add a required PR-governance gate for template validation and review readiness.
Closes #123
## Type of Change
- [x] New feature (non-breaking change that adds functionality)
- [ ] Documentation update
## Changes Made
- Added a workflow-backed PR template validator.
- Added local commit message linting in the commit-msg hook.
## Testing
- [x] Unit tests pass (`pytest`)
- [x] Manual testing performed
### Test Output
```text
pytest scripts/tests/test_pr_governance.py -q
```
## Real Behavior Proof
- Environment: Ubuntu runner, Python 3.12
- Exact command / steps: Open a PR, remove the ready checkbox, re-run the workflow.
- Observed result: The governance check fails and the PR gets a needs-author-action label.
- Not tested: Automatic Copilot review rulesets in repository settings.
feat: add deterministic runtime rollout controls (#1490) ## Description Establish one centrally resolved, observable, deterministic, versioned runtime rollout-control mechanism for Headroom. Runtime rollout controls which behaviors an already-built artifact may expose; it does not select or qualify a Headroom release/version. ## Type of Change - [x] New feature (non-breaking change that adds functionality) - [x] Bug fix (non-breaking change that fixes rollout enforcement regressions) - [x] Documentation update - [x] Code refactoring (no functional changes) ## Changes Made - Added `RolloutChannel`, `HEADROOM_ROLLOUT_CHANNEL`, `--rollout-channel`, and a versioned immutable `RolloutSnapshot` shared by Python configuration boundaries. - Added schema/policy versions, canonical registry and snapshot SHA-256 identities, per-feature decision reasons, disable precedence, unsafe qualification poisoning, strict CLI validation, and fail-closed environment handling. - Added `headroom rollout status --json`, Python `/stats.rollout`, and Rust `/rollout/status` runtime provenance. - Added equivalent Rust snapshot semantics and shared Python/Rust policy vectors while retaining language-specific feature registries. - Enforced rollout policy at alternate Python server composition roots so `HEADROOM_READ_MATURATION=1` cannot bypass its beta gate. - Preserved typed rollout snapshots across multi-worker serialization with schema, policy, registry, snapshot-digest, type, and feature-name validation. - Made loopback runtime output-shaper updates replace the immutable snapshot atomically for request readers, retain explicit request/disable provenance, preserve channel and kill-switch precedence, invalidate cached stats, and return the effective rollout decision. - Made `headroom learn --verbosity --apply` report a channel-blocked update instead of claiming the shaper is live. - Made explicit CLI feature flags fail loudly when their current channel blocks them. - Made persistent interceptor installation select canary automatically, or reject an explicitly insufficient channel unless the break-glass override is set. - Updated architecture, proxy, rollout, learn, and output-shaper documentation with required channels and hot-reload semantics. ## Testing - [x] Unit tests pass - [x] Linting passes (`ruff check .` and `ruff format --check .`) - [x] Type checking passes (`mypy headroom --ignore-missing-imports`) - [x] New regression tests added for every corrected behavior - [x] Rust tests and production-target Clippy pass - [x] Documentation build passes ### Test Output ```text Focused rollout coverage suite 57 passed; headroom.rollout + rollout CLI: 98% coverage Affected proxy/rollout/transform/governance suites 222 passed; 0 failed Final changed regression suites 100 passed; 0 failed Cross-module hot-reload isolation regression 6 passed; 0 failed cargo test -p headroom-core -p headroom-proxy --quiet headroom-core: 924 passed; 1 ignored headroom-proxy and integration suites: all passed cargo clippy -p headroom-core -p headroom-proxy --lib --bins -- -D warnings cargo fmt --all -- --check ruff check . ruff format --check . mypy headroom --ignore-missing-imports git diff --check All passed cd docs && npm run build Compiled successfully; 164 static pages generated ``` The unsharded Windows-only CI selection exposed unrelated baseline failures, principally the existing `sqlite:///C:\\...` URL parser producing an invalid `\\C:\\...` path. At commit `8e793a80`, all 52 completed GitHub checks passed; the only other conclusions are expected skips and superseded governance jobs. ## Real Behavior Proof - **Environment:** Windows checkout on Python 3.13.3 and the current Rust workspace, based on upstream `main` at `93f2d7a2`. - **Exact command / steps:** Exercised canary and beta feature requests through CLI status, Python `/stats.rollout`, Rust `/rollout/status`, multi-worker payload round trips, loopback `/admin/runtime-env`, real proxy request shaping before/after hot reload, installer manifest generation, and shared Python/Rust policy vectors. - **Observed result:** Stable blocks unstable requests; disable wins over explicit/default/legacy/unsafe paths; unsafe state reports `qualification_eligible=false`; worker handoff rejects tampering; running output shaping changes only when the effective beta policy permits it; explicit blocked flags fail with actionable diagnostics. - **Not tested:** Live production traffic requiring provider credentials, or future artifact qualification/promotion automation (intentionally out of scope). ## Runtime Rollout Safety - **Rollout-managed features:** Python `tool_result_interceptors`, `proxy_output_shaper`, `read_maturation`; Rust `native_bedrock`, `openai_responses_streaming`, `canary_probe`. - **Minimum rollout channel:** Registry-defined per feature; process default is `stable`. - **Stable/default behavior changed:** No unstable feature becomes enabled by default. Explicit blocked CLI flags now fail instead of silently doing nothing. - **Kill switch / disable path:** `HEADROOM_DISABLE_FEATURES=<comma-separated feature names>`; explicit disable has highest precedence, including over the unsafe override. - **Unsafe override required:** No. `HEADROOM_UNSAFE_ALLOW_UNSTABLE_FEATURES=1` is break-glass only and makes qualification evidence ineligible. - **Qualification impact:** Adds machine-readable policy/snapshot identities and eligibility; does not implement qualification itself. - **Rollback path:** Set the named disable list for operational rollback, lower the channel, or revert this PR. ## Review Readiness - [x] I have performed a full diff review - [x] This PR is ready for human review ## Checklist - [x] My code follows the project's style guidelines - [x] I have performed a self-review of my code - [x] I have commented hard-to-understand areas - [x] I have made corresponding documentation changes - [x] My changes generate no new warnings - [x] I added tests that reproduce and prevent every regression fixed during review - [x] New and existing affected tests pass locally - [x] I did **not** edit `CHANGELOG.md`; release-please generates it from the Conventional Commit PR title ## Additional Notes Out of scope: artifact candidates, benchmark orchestration, qualification manifests/gates, promotion automation, release branches, publication guards, and release-risk classification. Those workflows can consume the rollout registry digest, runtime snapshot digest, decision reasons, and qualification eligibility through supported black-box interfaces. --------- Co-authored-by: JD Davis <jd@jds-macbook-air.tail2a279.ts.net> Co-authored-by: JD Davis <jd@JDH-AIR-00.local>
2026-08-12 23:16:54 -05:00
## Runtime Rollout Safety
- Rollout-managed feature(s): None.
- Minimum rollout channel: Stable.
- Stable/default behavior changed: No.
- Kill switch / disable path: Not applicable.
- Unsafe override required: No.
- Qualification impact: None.
- Rollback path: Revert the workflow and script changes.
## Review Readiness
- [x] I have performed a self-review
- [x] This PR is ready for human review
## Additional Notes
- Maintainers can optionally enable Copilot code review from repository rulesets.
"""
def test_validate_pull_request_marks_ready_pr_valid() -> None:
module = _load_module()
report = module.validate_pull_request(_event(VALID_BODY))
assert report.valid is True
assert report.ready_for_review is True
assert report.needs_author_action is False
assert report.problems == []
assert report.labels_to_add == [module.READY_LABEL]
assert module.AUTHOR_ACTION_LABEL in report.labels_to_remove
fix(ci): normalize Windows CRLF line endings in PR governance script (#1012) ## Description The `CODE_BLOCK_RE` regex in `scripts/pr-governance.py` expects LF after the opening fenced code block. PR bodies authored on Windows can arrive with CRLF line endings, which leaves a `\r` before the `\n` and prevents `has_test_output()` from detecting a valid Test Output block. This normalizes CRLF to LF once when loading the pull request body, before section extraction and code-block matching. A regression test now verifies that a valid PR body with CRLF line endings still passes governance. ## Type of Change - [x] Bug fix (non-breaking change that fixes an issue) - [x] Tests only ## Changes Made - Normalize Windows CRLF line endings in `scripts/pr-governance.py` before regex-based validation runs. - Added `test_validate_pull_request_accepts_crlf_test_output_code_block` to prevent regressions. ## Testing - [x] Unit tests pass (`pytest`) - [x] Linting passes (`ruff check`) - [x] Formatting passes (`ruff format --check`) - [x] New tests added for new functionality ### Test Output ```text python -m pytest scripts/tests/test_pr_governance.py scripts/tests/test_pr_health_labels.py scripts/tests/test_pr_health_workflow.py -q 8 passed in 0.06s ruff check scripts/pr-governance.py scripts/tests/test_pr_governance.py scripts/tests/test_pr_health_labels.py scripts/tests/test_pr_health_workflow.py All checks passed! ruff format --check scripts/pr-governance.py scripts/tests/test_pr_governance.py scripts/tests/test_pr_health_labels.py scripts/tests/test_pr_health_workflow.py 4 files already formatted ``` ## Real Behavior Proof - Environment: local Windows 11 checkout, Python 3.13.13. - Exact command / steps: Converted the known-valid governance test body to CRLF line endings and passed it through `validate_pull_request` in the new regression test. - Observed result: The report is valid with no problems, proving the fenced Test Output block is recognized after normalization. - Not tested: GitHub-hosted Windows PR authoring path end to end; the unit test covers the exact CRLF body shape consumed by the validator. ## Review Readiness - [x] I have performed a self-review - [x] This PR is ready for human review --------- Co-authored-by: JerrettDavis <mxjerrett@gmail.com>
2026-06-23 01:45:12 +02:00
def test_validate_pull_request_accepts_crlf_test_output_code_block() -> None:
module = _load_module()
body = VALID_BODY.replace("\n", "\r\n")
report = module.validate_pull_request(_event(body))
assert report.valid is True
assert report.problems == []
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 23:34:34 -05:00
def test_validate_pull_request_body_override_uses_live_body() -> None:
module = _load_module()
stale_event_body = ""
report = module.validate_pull_request_body(_event(stale_event_body), VALID_BODY)
assert report.valid is True
assert report.ready_for_review is True
assert report.problems == []
def test_cli_body_file_override_uses_live_body(tmp_path: Path, monkeypatch) -> None:
module = _load_module()
event_path = tmp_path / "event.json"
body_path = tmp_path / "body.md"
report_path = tmp_path / "report.json"
event_path.write_text(
json.dumps(_event("")),
encoding="utf-8",
)
body_path.write_text(VALID_BODY, encoding="utf-8")
monkeypatch.delenv("GITHUB_OUTPUT", raising=False)
exit_code = module.main(
[
"--event",
str(event_path),
"--body-file",
str(body_path),
"--report",
str(report_path),
]
)
assert exit_code == 0
report = json.loads(report_path.read_text(encoding="utf-8"))
assert report["valid"] is True
assert report["ready_for_review"] is True
def test_validate_pull_request_allows_draft_without_ready_checkboxes() -> None:
module = _load_module()
body = VALID_BODY.replace(
"- [x] I have performed a self-review", "- [ ] I have performed a self-review"
)
body = body.replace(
"- [x] This PR is ready for human review",
"- [ ] This PR is ready for human review",
)
report = module.validate_pull_request(_event(body, draft=True))
assert report.valid is True
assert report.ready_for_review is False
assert report.needs_author_action is False
assert report.labels_to_add == []
assert module.READY_LABEL in report.labels_to_remove
def test_validate_pull_request_fails_on_missing_required_content() -> None:
module = _load_module()
body = """## Description
Fixes #123
## Type of Change
- [ ] New feature (non-breaking change that adds functionality)
## Changes Made
- Change 1
## Testing
### Test Output
```text
# Paste relevant command output or artifact links here
```
## Real Behavior Proof
- Environment:
- Exact command / steps:
- Observed result:
- Not tested:
feat: add deterministic runtime rollout controls (#1490) ## Description Establish one centrally resolved, observable, deterministic, versioned runtime rollout-control mechanism for Headroom. Runtime rollout controls which behaviors an already-built artifact may expose; it does not select or qualify a Headroom release/version. ## Type of Change - [x] New feature (non-breaking change that adds functionality) - [x] Bug fix (non-breaking change that fixes rollout enforcement regressions) - [x] Documentation update - [x] Code refactoring (no functional changes) ## Changes Made - Added `RolloutChannel`, `HEADROOM_ROLLOUT_CHANNEL`, `--rollout-channel`, and a versioned immutable `RolloutSnapshot` shared by Python configuration boundaries. - Added schema/policy versions, canonical registry and snapshot SHA-256 identities, per-feature decision reasons, disable precedence, unsafe qualification poisoning, strict CLI validation, and fail-closed environment handling. - Added `headroom rollout status --json`, Python `/stats.rollout`, and Rust `/rollout/status` runtime provenance. - Added equivalent Rust snapshot semantics and shared Python/Rust policy vectors while retaining language-specific feature registries. - Enforced rollout policy at alternate Python server composition roots so `HEADROOM_READ_MATURATION=1` cannot bypass its beta gate. - Preserved typed rollout snapshots across multi-worker serialization with schema, policy, registry, snapshot-digest, type, and feature-name validation. - Made loopback runtime output-shaper updates replace the immutable snapshot atomically for request readers, retain explicit request/disable provenance, preserve channel and kill-switch precedence, invalidate cached stats, and return the effective rollout decision. - Made `headroom learn --verbosity --apply` report a channel-blocked update instead of claiming the shaper is live. - Made explicit CLI feature flags fail loudly when their current channel blocks them. - Made persistent interceptor installation select canary automatically, or reject an explicitly insufficient channel unless the break-glass override is set. - Updated architecture, proxy, rollout, learn, and output-shaper documentation with required channels and hot-reload semantics. ## Testing - [x] Unit tests pass - [x] Linting passes (`ruff check .` and `ruff format --check .`) - [x] Type checking passes (`mypy headroom --ignore-missing-imports`) - [x] New regression tests added for every corrected behavior - [x] Rust tests and production-target Clippy pass - [x] Documentation build passes ### Test Output ```text Focused rollout coverage suite 57 passed; headroom.rollout + rollout CLI: 98% coverage Affected proxy/rollout/transform/governance suites 222 passed; 0 failed Final changed regression suites 100 passed; 0 failed Cross-module hot-reload isolation regression 6 passed; 0 failed cargo test -p headroom-core -p headroom-proxy --quiet headroom-core: 924 passed; 1 ignored headroom-proxy and integration suites: all passed cargo clippy -p headroom-core -p headroom-proxy --lib --bins -- -D warnings cargo fmt --all -- --check ruff check . ruff format --check . mypy headroom --ignore-missing-imports git diff --check All passed cd docs && npm run build Compiled successfully; 164 static pages generated ``` The unsharded Windows-only CI selection exposed unrelated baseline failures, principally the existing `sqlite:///C:\\...` URL parser producing an invalid `\\C:\\...` path. At commit `8e793a80`, all 52 completed GitHub checks passed; the only other conclusions are expected skips and superseded governance jobs. ## Real Behavior Proof - **Environment:** Windows checkout on Python 3.13.3 and the current Rust workspace, based on upstream `main` at `93f2d7a2`. - **Exact command / steps:** Exercised canary and beta feature requests through CLI status, Python `/stats.rollout`, Rust `/rollout/status`, multi-worker payload round trips, loopback `/admin/runtime-env`, real proxy request shaping before/after hot reload, installer manifest generation, and shared Python/Rust policy vectors. - **Observed result:** Stable blocks unstable requests; disable wins over explicit/default/legacy/unsafe paths; unsafe state reports `qualification_eligible=false`; worker handoff rejects tampering; running output shaping changes only when the effective beta policy permits it; explicit blocked flags fail with actionable diagnostics. - **Not tested:** Live production traffic requiring provider credentials, or future artifact qualification/promotion automation (intentionally out of scope). ## Runtime Rollout Safety - **Rollout-managed features:** Python `tool_result_interceptors`, `proxy_output_shaper`, `read_maturation`; Rust `native_bedrock`, `openai_responses_streaming`, `canary_probe`. - **Minimum rollout channel:** Registry-defined per feature; process default is `stable`. - **Stable/default behavior changed:** No unstable feature becomes enabled by default. Explicit blocked CLI flags now fail instead of silently doing nothing. - **Kill switch / disable path:** `HEADROOM_DISABLE_FEATURES=<comma-separated feature names>`; explicit disable has highest precedence, including over the unsafe override. - **Unsafe override required:** No. `HEADROOM_UNSAFE_ALLOW_UNSTABLE_FEATURES=1` is break-glass only and makes qualification evidence ineligible. - **Qualification impact:** Adds machine-readable policy/snapshot identities and eligibility; does not implement qualification itself. - **Rollback path:** Set the named disable list for operational rollback, lower the channel, or revert this PR. ## Review Readiness - [x] I have performed a full diff review - [x] This PR is ready for human review ## Checklist - [x] My code follows the project's style guidelines - [x] I have performed a self-review of my code - [x] I have commented hard-to-understand areas - [x] I have made corresponding documentation changes - [x] My changes generate no new warnings - [x] I added tests that reproduce and prevent every regression fixed during review - [x] New and existing affected tests pass locally - [x] I did **not** edit `CHANGELOG.md`; release-please generates it from the Conventional Commit PR title ## Additional Notes Out of scope: artifact candidates, benchmark orchestration, qualification manifests/gates, promotion automation, release branches, publication guards, and release-risk classification. Those workflows can consume the rollout registry digest, runtime snapshot digest, decision reasons, and qualification eligibility through supported black-box interfaces. --------- Co-authored-by: JD Davis <jd@jds-macbook-air.tail2a279.ts.net> Co-authored-by: JD Davis <jd@JDH-AIR-00.local>
2026-08-12 23:16:54 -05:00
## Runtime Rollout Safety
- Rollout-managed feature(s):
- Minimum rollout channel:
- Stable/default behavior changed:
- Kill switch / disable path:
- Unsafe override required:
- Qualification impact:
- Rollback path:
## Review Readiness
- [ ] I have performed a self-review
- [ ] This PR is ready for human review
"""
report = module.validate_pull_request(_event(body))
assert report.valid is False
assert report.needs_author_action is True
assert module.AUTHOR_ACTION_LABEL in report.labels_to_add
assert any("Description" in problem for problem in report.problems)
assert any("Type of Change" in problem for problem in report.problems)
assert any("Test Output" in problem for problem in report.problems)
assert any("Real Behavior Proof" in problem for problem in report.problems)
feat: add deterministic runtime rollout controls (#1490) ## Description Establish one centrally resolved, observable, deterministic, versioned runtime rollout-control mechanism for Headroom. Runtime rollout controls which behaviors an already-built artifact may expose; it does not select or qualify a Headroom release/version. ## Type of Change - [x] New feature (non-breaking change that adds functionality) - [x] Bug fix (non-breaking change that fixes rollout enforcement regressions) - [x] Documentation update - [x] Code refactoring (no functional changes) ## Changes Made - Added `RolloutChannel`, `HEADROOM_ROLLOUT_CHANNEL`, `--rollout-channel`, and a versioned immutable `RolloutSnapshot` shared by Python configuration boundaries. - Added schema/policy versions, canonical registry and snapshot SHA-256 identities, per-feature decision reasons, disable precedence, unsafe qualification poisoning, strict CLI validation, and fail-closed environment handling. - Added `headroom rollout status --json`, Python `/stats.rollout`, and Rust `/rollout/status` runtime provenance. - Added equivalent Rust snapshot semantics and shared Python/Rust policy vectors while retaining language-specific feature registries. - Enforced rollout policy at alternate Python server composition roots so `HEADROOM_READ_MATURATION=1` cannot bypass its beta gate. - Preserved typed rollout snapshots across multi-worker serialization with schema, policy, registry, snapshot-digest, type, and feature-name validation. - Made loopback runtime output-shaper updates replace the immutable snapshot atomically for request readers, retain explicit request/disable provenance, preserve channel and kill-switch precedence, invalidate cached stats, and return the effective rollout decision. - Made `headroom learn --verbosity --apply` report a channel-blocked update instead of claiming the shaper is live. - Made explicit CLI feature flags fail loudly when their current channel blocks them. - Made persistent interceptor installation select canary automatically, or reject an explicitly insufficient channel unless the break-glass override is set. - Updated architecture, proxy, rollout, learn, and output-shaper documentation with required channels and hot-reload semantics. ## Testing - [x] Unit tests pass - [x] Linting passes (`ruff check .` and `ruff format --check .`) - [x] Type checking passes (`mypy headroom --ignore-missing-imports`) - [x] New regression tests added for every corrected behavior - [x] Rust tests and production-target Clippy pass - [x] Documentation build passes ### Test Output ```text Focused rollout coverage suite 57 passed; headroom.rollout + rollout CLI: 98% coverage Affected proxy/rollout/transform/governance suites 222 passed; 0 failed Final changed regression suites 100 passed; 0 failed Cross-module hot-reload isolation regression 6 passed; 0 failed cargo test -p headroom-core -p headroom-proxy --quiet headroom-core: 924 passed; 1 ignored headroom-proxy and integration suites: all passed cargo clippy -p headroom-core -p headroom-proxy --lib --bins -- -D warnings cargo fmt --all -- --check ruff check . ruff format --check . mypy headroom --ignore-missing-imports git diff --check All passed cd docs && npm run build Compiled successfully; 164 static pages generated ``` The unsharded Windows-only CI selection exposed unrelated baseline failures, principally the existing `sqlite:///C:\\...` URL parser producing an invalid `\\C:\\...` path. At commit `8e793a80`, all 52 completed GitHub checks passed; the only other conclusions are expected skips and superseded governance jobs. ## Real Behavior Proof - **Environment:** Windows checkout on Python 3.13.3 and the current Rust workspace, based on upstream `main` at `93f2d7a2`. - **Exact command / steps:** Exercised canary and beta feature requests through CLI status, Python `/stats.rollout`, Rust `/rollout/status`, multi-worker payload round trips, loopback `/admin/runtime-env`, real proxy request shaping before/after hot reload, installer manifest generation, and shared Python/Rust policy vectors. - **Observed result:** Stable blocks unstable requests; disable wins over explicit/default/legacy/unsafe paths; unsafe state reports `qualification_eligible=false`; worker handoff rejects tampering; running output shaping changes only when the effective beta policy permits it; explicit blocked flags fail with actionable diagnostics. - **Not tested:** Live production traffic requiring provider credentials, or future artifact qualification/promotion automation (intentionally out of scope). ## Runtime Rollout Safety - **Rollout-managed features:** Python `tool_result_interceptors`, `proxy_output_shaper`, `read_maturation`; Rust `native_bedrock`, `openai_responses_streaming`, `canary_probe`. - **Minimum rollout channel:** Registry-defined per feature; process default is `stable`. - **Stable/default behavior changed:** No unstable feature becomes enabled by default. Explicit blocked CLI flags now fail instead of silently doing nothing. - **Kill switch / disable path:** `HEADROOM_DISABLE_FEATURES=<comma-separated feature names>`; explicit disable has highest precedence, including over the unsafe override. - **Unsafe override required:** No. `HEADROOM_UNSAFE_ALLOW_UNSTABLE_FEATURES=1` is break-glass only and makes qualification evidence ineligible. - **Qualification impact:** Adds machine-readable policy/snapshot identities and eligibility; does not implement qualification itself. - **Rollback path:** Set the named disable list for operational rollback, lower the channel, or revert this PR. ## Review Readiness - [x] I have performed a full diff review - [x] This PR is ready for human review ## Checklist - [x] My code follows the project's style guidelines - [x] I have performed a self-review of my code - [x] I have commented hard-to-understand areas - [x] I have made corresponding documentation changes - [x] My changes generate no new warnings - [x] I added tests that reproduce and prevent every regression fixed during review - [x] New and existing affected tests pass locally - [x] I did **not** edit `CHANGELOG.md`; release-please generates it from the Conventional Commit PR title ## Additional Notes Out of scope: artifact candidates, benchmark orchestration, qualification manifests/gates, promotion automation, release branches, publication guards, and release-risk classification. Those workflows can consume the rollout registry digest, runtime snapshot digest, decision reasons, and qualification eligibility through supported black-box interfaces. --------- Co-authored-by: JD Davis <jd@jds-macbook-air.tail2a279.ts.net> Co-authored-by: JD Davis <jd@JDH-AIR-00.local>
2026-08-12 23:16:54 -05:00
assert any("Runtime Rollout Safety" in problem for problem in report.problems)
def test_validate_pull_request_skips_bot_authored_prs() -> None:
module = _load_module()
report = module.validate_pull_request(_event("", login="dependabot[bot]"))
assert report.valid is True
assert report.is_bot_pr is True
assert report.needs_author_action is False
assert report.labels_to_add == []
ci(governance): require a Conventional Commit PR title (#3063) ## Description The repo squash-merges, so the PR title — not the commits inside the PR — becomes the commit subject on `main`. Nothing validated it. `commitlint` (`ci.yml:429`) lints a PR's *commits* and therefore cannot catch this by construction: a PR with clean conventional commits and a prose title passes CI and then lands a prose subject on `main`. That is how `31452426` landed: ``` Unify savings attribution across stats, perf, metrics, and dashboard (#2976) ``` release-please cannot parse it — `unexpected token ' ' at 1:6`, because `Unify` is five characters and position six is a space where the parser needs `(`, `!` or `:`. The change is silently dropped from the changelog. ## Type of Change - [x] Bug fix (non-breaking change that fixes an issue) - [ ] New feature (non-breaking change that adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to change) - [ ] Documentation update ## Changes Made - Added `COMMIT_TYPES` and `TITLE_RE` to `scripts/pr-governance.py`, matching `.commitlintrc.json`'s `type-enum`. - Added a title check to `validate_pull_request`, reported through the existing governance comment. - Added a test asserting `COMMIT_TYPES` equals `.commitlintrc.json`'s `type-enum`, so the two gates cannot drift apart. - Gave the `_event` test helper the `title` field a real `pull_request` payload always carries. ## Testing - [x] Unit tests pass - [x] Linting passes (ruff check + format) - [ ] Type checking passes — N/A (script + test only) - [x] New tests added for new functionality ### Test Output ```text $ .venv/bin/python -m pytest scripts/tests/test_pr_governance.py -q 12 passed in 0.02s ``` Against the parent commit: ```text FAILED test_validate_pull_request_rejects_non_conventional_title FAILED test_validate_pull_request_rejects_empty_and_typeless_titles FAILED test_commit_types_match_commitlint_config 3 failed, 9 passed ``` ## Real Behavior Proof - Environment: macOS 15 (darwin 25.4.0), Python 3.12.13, `scripts/pr-governance.py` loaded directly. - Exact command / steps: ran `TITLE_RE` against the titles of **all 117 pull requests opened in this repository between 2026-08-10 and 2026-08-16**, pulled with `gh pr list --json title`. - Observed result: exactly one title is flagged — `#2976`, `Unify savings attribution across stats, perf, metrics, and dashboard`, the one that jammed the release. Zero false positives across the other 116, including every Dependabot `deps: bump ...` title, `chore: release main`, and scoped forms like `fix(proxy/anthropic): ...`. - Not tested: the check running inside a live `pull_request_target` event on a GitHub runner. ## Runtime Rollout Safety - Rollout-managed feature(s): none. - Minimum rollout channel: N/A. - Stable/default behavior changed: yes — a PR with a non-conventional title now gets the `status: needs author action` label and a governance comment. - Kill switch / disable path: revert; the check is not independently configurable. - Unsafe override required: none. - Qualification impact: none. - Rollback path: revert this commit. ## Review Readiness - [x] I have performed a self-review - [x] This PR is ready for human review ## Checklist - [x] My code follows the project's style guidelines - [x] I have performed a self-review of my code - [x] I have commented my code, particularly in hard-to-understand areas - [x] My changes generate no new warnings - [x] I have added tests that prove my fix is effective - [x] New and existing unit tests pass locally with my changes ## Additional Notes The check lives in `pr-governance.py` rather than `ci.yml` for two reasons: 1. `ci.yml`'s `pull_request` trigger has no `edited` type, so a corrected title would never be re-checked. 2. Its `paths-ignore` skips docs-only PRs, which still squash-merge a subject onto `main`. `pr-health.yml` already triggers on `edited` and reports through the same governance comment the author is reading anyway. Bot PRs keep their existing exemption — Dependabot and release-please titles are already conventional, and the early return for `is_bot_pr` is untouched. Co-authored-by: Tejas Chopra <tejas@Tejass-MacBook-Pro.local>
2026-08-16 19:05:36 -07:00
def test_validate_pull_request_rejects_non_conventional_title() -> None:
"""The exact title that stalled release-please on v0.35.0 must be caught.
`Unify savings attribution ...` squash-merged to main as commit 31452426.
release-please could not parse it (`unexpected token ' ' at 1:6`), so the
change never reached a changelog. commitlint passed the PR because it lints
the commits inside it, not the title that replaces them on squash-merge.
"""
module = _load_module()
report = module.validate_pull_request(
_event(
VALID_BODY, title="Unify savings attribution across stats, perf, metrics, and dashboard"
)
)
assert report.valid is False
assert any("Conventional Commit" in problem for problem in report.problems)
def test_validate_pull_request_accepts_conventional_titles() -> None:
module = _load_module()
for title in (
"fix(proxy): keep prefixed core tools resident",
"deps: bump sha2 from 0.10.9 to 0.11.0",
"chore: release main",
"feat!: drop python 3.10",
"fix(proxy/anthropic): stop replaying the recorded prefix",
):
report = module.validate_pull_request(_event(VALID_BODY, title=title))
assert report.valid is True, (title, report.problems)
def test_validate_pull_request_rejects_empty_and_typeless_titles() -> None:
module = _load_module()
for title in ("", " ", "WIP", "fix:", "Fix(proxy): capitalised type", "update stuff"):
report = module.validate_pull_request(_event(VALID_BODY, title=title))
assert report.valid is False, title
assert any("Conventional Commit" in problem for problem in report.problems), title
def test_bot_authored_prs_skip_title_enforcement() -> None:
"""Dependabot/release-please titles are already conventional; don't gate them."""
module = _load_module()
report = module.validate_pull_request(
_event("", login="dependabot[bot]", title="Bump sha2 from 0.10.9 to 0.11.0")
)
assert report.is_bot_pr is True
assert report.valid is True
def test_commit_types_match_commitlint_config() -> None:
"""The gate and commitlint must accept the same type vocabulary.
They enforce the same rule at two points in the lifecycle -- commitlint on
the PR's commits, this on the title that squash-merge substitutes for them.
Divergence would let a title through that the commit hook rejects, or vice
versa.
"""
module = _load_module()
config = json.loads(
(Path(__file__).parent.parent.parent / ".commitlintrc.json").read_text(encoding="utf-8")
)
_level, _applicable, allowed = config["rules"]["type-enum"]
assert sorted(module.COMMIT_TYPES) == sorted(allowed)