headroom/tests/test_openai_responses_output_shaper.py
JD Davis 3077ac81e8
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

214 lines
7 KiB
Python

from __future__ import annotations
import copy
from typing import Any
import httpx
import pytest
pytest.importorskip("fastapi")
from fastapi.testclient import TestClient # noqa: E402
from headroom.proxy import runtime_env # noqa: E402
from headroom.proxy.loopback_guard import require_loopback # noqa: E402
from headroom.proxy.server import ProxyConfig, create_app # noqa: E402
@pytest.fixture(autouse=True)
def _isolate_runtime_env_overrides():
"""Keep loopback hot-reload state from leaking into later test modules."""
runtime_env.clear_overrides()
yield
runtime_env.clear_overrides()
def _make_client() -> TestClient:
app = create_app(
ProxyConfig(
optimize=False,
cache_enabled=False,
rate_limit_enabled=False,
cost_tracking_enabled=False,
log_requests=False,
http2=False,
)
)
app.dependency_overrides[require_loopback] = lambda: None
return TestClient(app)
async def _ok_response(
method: str,
url: str,
headers: dict[str, str],
body: dict[str, Any],
stream: bool = False,
**kwargs: Any,
) -> httpx.Response:
return httpx.Response(
200,
json={
"id": "resp_1",
"output": [],
"usage": {"input_tokens": 10, "output_tokens": 1},
},
)
def test_http_responses_output_shaper_rewrites_and_labels(monkeypatch):
monkeypatch.setenv("HEADROOM_OUTPUT_SHAPER", "1")
monkeypatch.setenv("HEADROOM_ROLLOUT_CHANNEL", "beta")
monkeypatch.setenv("HEADROOM_VERBOSITY_LEVEL", "2")
monkeypatch.delenv("HEADROOM_OUTPUT_HOLDOUT", raising=False)
captured: dict[str, Any] = {}
outcomes: list[Any] = []
payload = {
"model": "gpt-5",
"input": [
{
"type": "function_call_output",
"call_id": "call_1",
"output": "ok",
}
],
"reasoning": {"effort": "xhigh"},
"text": {"verbosity": "medium"},
}
with _make_client() as client:
proxy = client.app.state.proxy
async def _fake_retry(*args: Any, **kwargs: Any) -> httpx.Response:
body = args[3]
captured["body"] = copy.deepcopy(body)
captured["retry_kwargs"] = dict(kwargs)
return await _ok_response(*args, **kwargs)
async def _record_request_outcome(outcome: Any) -> None:
outcomes.append(outcome)
proxy._retry_request = _fake_retry
proxy._record_request_outcome = _record_request_outcome
response = client.post(
"/v1/responses",
headers={"authorization": "Bearer test-key"},
json=payload,
)
assert response.status_code == 200
sent = captured["body"]
assert "<headroom_output_shaping>" in sent["instructions"]
assert sent["reasoning"]["effort"] == "low"
assert sent["text"]["verbosity"] == "low"
assert captured["retry_kwargs"]["body_mutated"] is True
assert captured["retry_kwargs"]["original_body_bytes"] is not None
transforms = outcomes[-1].transforms_applied
assert any(t.startswith("output_shaper:stratum:") for t in transforms)
assert "output_shaper:verbosity:L2" in transforms
assert "output_shaper:reasoning_effort:xhigh->low" in transforms
assert "output_shaper:text_verbosity:medium->low" in transforms
def test_http_responses_output_shaper_respects_bypass(monkeypatch):
monkeypatch.setenv("HEADROOM_OUTPUT_SHAPER", "1")
monkeypatch.setenv("HEADROOM_ROLLOUT_CHANNEL", "beta")
captured: dict[str, Any] = {}
payload = {"model": "gpt-5", "input": "hi"}
with _make_client() as client:
proxy = client.app.state.proxy
async def _fake_retry(*args: Any, **kwargs: Any) -> httpx.Response:
captured["body"] = copy.deepcopy(args[3])
return await _ok_response(*args, **kwargs)
proxy._retry_request = _fake_retry
response = client.post(
"/v1/responses",
headers={
"authorization": "Bearer test-key",
"x-headroom-bypass": "true",
},
json=payload,
)
assert response.status_code == 200
assert captured["body"] == payload
def test_http_responses_output_shaper_holdout_labels_without_rewrite(monkeypatch):
monkeypatch.setenv("HEADROOM_OUTPUT_SHAPER", "1")
monkeypatch.setenv("HEADROOM_ROLLOUT_CHANNEL", "beta")
monkeypatch.setenv("HEADROOM_OUTPUT_HOLDOUT", "1")
captured: dict[str, Any] = {}
outcomes: list[Any] = []
payload = {"model": "gpt-5", "input": "hi"}
with _make_client() as client:
proxy = client.app.state.proxy
async def _fake_retry(*args: Any, **kwargs: Any) -> httpx.Response:
captured["body"] = copy.deepcopy(args[3])
return await _ok_response(*args, **kwargs)
async def _record_request_outcome(outcome: Any) -> None:
outcomes.append(outcome)
proxy._retry_request = _fake_retry
proxy._record_request_outcome = _record_request_outcome
response = client.post(
"/v1/responses",
headers={"authorization": "Bearer test-key"},
json=payload,
)
assert response.status_code == 200
assert captured["body"] == payload
transforms = outcomes[-1].transforms_applied
assert any(t.startswith("output_shaper:control:") for t in transforms)
assert "output_shaper:verbosity:L2" not in transforms
def test_http_output_shaper_hot_reload_changes_the_running_request_path(monkeypatch):
"""The admin endpoint must not report success while traffic stays unchanged."""
monkeypatch.setenv("HEADROOM_ROLLOUT_CHANNEL", "beta")
monkeypatch.delenv("HEADROOM_OUTPUT_SHAPER", raising=False)
payload = {
"model": "gpt-5",
"input": [{"type": "function_call_output", "call_id": "call_1", "output": "ok"}],
"reasoning": {"effort": "high"},
"text": {"verbosity": "medium"},
}
sent: list[dict[str, Any]] = []
with _make_client() as client:
proxy = client.app.state.proxy
async def _fake_retry(*args: Any, **kwargs: Any) -> httpx.Response:
sent.append(copy.deepcopy(args[3]))
return await _ok_response(*args, **kwargs)
proxy._retry_request = _fake_retry
first = client.post(
"/v1/responses", headers={"authorization": "Bearer test-key"}, json=payload
)
update = client.post("/admin/runtime-env", json={"HEADROOM_OUTPUT_SHAPER": "1"})
second = client.post(
"/v1/responses", headers={"authorization": "Bearer test-key"}, json=payload
)
assert first.status_code == second.status_code == update.status_code == 200
assert sent[0] == payload
assert "<headroom_output_shaping>" in sent[1]["instructions"]
decision = next(
item
for item in update.json()["rollout"]["features"]
if item["name"] == "proxy_output_shaper"
)
assert decision["enabled"] is True
assert decision["decision"] == "legacy_alias"