Commit graph

2044 commits

Author SHA1 Message Date
JD Davis
e2ba09adb4
Extract memory injection mode policy (#1986)
## Description

Extracts memory-injection mode resolution from `helpers.py` into
`headroom.proxy.memory_injection_mode_policy`. The proxy still reads
`HEADROOM_MEMORY_INJECTION_MODE` at request time, while the allowed
values/default/error contract is now pure and directly tested.

Closes #

## Type of Change

- [ ] 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
- [ ] Performance improvement
- [x] Code refactoring (no functional changes)

## Changes Made

- Added `memory_injection_mode_policy.py` with the allowed mode type,
env name/default, and resolver.
- Kept `helpers.get_memory_injection_mode` as the request-time env
reader and compatibility entry point.
- Added direct policy tests for defaults, accepted values,
normalization, and invalid mode rejection.

## Testing

- [x] Unit tests pass (`pytest`)
- [x] Linting passes (`ruff check .`)
- [x] Type checking passes (`mypy headroom`)
- [x] New tests added for new functionality
- [ ] Manual testing performed

### Test Output

```text
python -m pytest tests\test_memory_injection_mode_policy.py tests\test_proxy_system_prompt_immutable.py
10 passed in 14.60s

python -m ruff check .
All checks passed!

python -m ruff format --check .
1069 files already formatted

python -m mypy headroom --ignore-missing-imports
Success: no issues found in 410 source files

gitleaks protect --staged --no-banner --redact
no leaks found
```

## Real Behavior Proof

- Environment: Windows, Python 3.13.13, branch
`jd/architecture-slice-33`.
- Exact command / steps: ran new memory injection mode policy tests,
existing system-prompt immutability tests, ruff, ruff format check,
mypy, and staged gitleaks scan.
- Observed result: memory injection mode behavior remains covered and
local lint/type/security checks pass.
- Not tested: live proxy request; existing helper entry point remains
intact.

## 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
- [ ] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [x] I have added tests that prove my fix is effective or that my
feature works
- [x] New and existing unit tests pass locally with my changes
- [ ] I have updated the CHANGELOG.md if applicable

## Screenshots (if applicable)

N/A

## Additional Notes

Documentation and changelog updates are N/A for this internal
architecture-only refactor. The push reported existing default-branch
Dependabot alerts; no staged secret leaks were found for this PR.

---------

Co-authored-by: Tejas Chopra <chopratejas@gmail.com>
2026-07-12 12:18:23 -04:00
JD Davis
d45748d143
Extract query log policy (#1984)
## Description

Extracts the privacy-preserving memory-query log hash from `helpers.py`
into `headroom.proxy.query_log_policy`. The helper import path remains
intact, while the log identifier formula is now directly testable as a
pure policy.

Closes #

## Type of Change

- [ ] 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
- [ ] Performance improvement
- [x] Code refactoring (no functional changes)

## Changes Made

- Added `query_log_policy.py` with the BLAKE2b-based short query hash
formula.
- Kept `helpers.hash_query_for_log` delegating to the extracted policy
for existing callers.
- Added direct tests for stability, short hex shape, content
sensitivity, unpaired surrogate handling, and helper delegation.

## Testing

- [x] Unit tests pass (`pytest`)
- [x] Linting passes (`ruff check .`)
- [x] Type checking passes (`mypy headroom`)
- [x] New tests added for new functionality
- [ ] Manual testing performed

### Test Output

```text
python -m pytest tests\test_query_log_policy.py
4 passed in 0.18s

python -m ruff check .
All checks passed!

python -m ruff format --check .
1069 files already formatted

python -m mypy headroom --ignore-missing-imports
Success: no issues found in 410 source files

gitleaks protect --staged --no-banner --redact
no leaks found
```

## Real Behavior Proof

- Environment: Windows, Python 3.13.13, branch
`jd/architecture-slice-32`.
- Exact command / steps: ran focused query-log policy tests, ruff, ruff
format check, mypy, and staged gitleaks scan.
- Observed result: query log hash behavior is directly covered and local
lint/type/security checks pass.
- Not tested: live memory injection logging; existing helper entry point
remains intact.

## 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
- [ ] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [x] I have added tests that prove my fix is effective or that my
feature works
- [x] New and existing unit tests pass locally with my changes
- [ ] I have updated the CHANGELOG.md if applicable

## Screenshots (if applicable)

N/A

## Additional Notes

Documentation and changelog updates are N/A for this internal
architecture-only refactor. The push reported existing default-branch
Dependabot alerts; no staged secret leaks were found for this PR.

---------

Co-authored-by: Tejas Chopra <chopratejas@gmail.com>
2026-07-12 12:18:01 -04:00
JD Davis
7fb9209089
Extract diagnostic decode policy (#1981)
## Description

Extracts lossy diagnostic byte decoding from `helpers.py` into
`headroom.proxy.diagnostic_decode_policy`. Protocol parsers stay strict
while the diagnostic/logging path has a dedicated, directly tested
policy.

Closes #

## Type of Change

- [ ] 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
- [ ] Performance improvement
- [x] Code refactoring (no functional changes)

## Changes Made

- Added `diagnostic_decode_policy.py` for UTF-8 diagnostic decoding with
replacement characters.
- Kept `helpers.safe_decode_for_logging` delegating to the extracted
policy for existing callers.
- Added direct tests for valid UTF-8, invalid byte replacement, max-byte
truncation, and helper delegation.
- Carried forward the LiteLLM callback compatibility shim needed for
current mypy on `main`.

## Testing

- [x] Unit tests pass (`pytest`)
- [x] Linting passes (`ruff check .`)
- [x] Type checking passes (`mypy headroom`)
- [x] New tests added for new functionality
- [ ] Manual testing performed

### Test Output

```text
python -m pytest tests\test_diagnostic_decode_policy.py
4 passed in 0.18s

python -m ruff check .
All checks passed!

python -m ruff format --check .
1095 files already formatted

python -m mypy headroom --ignore-missing-imports
Success: no issues found in 409 source files

gitleaks protect --staged --no-banner --redact
no leaks found
```

## Real Behavior Proof

- Environment: Windows, Python 3.13.13, branch
`jd/architecture-slice-30`.
- Exact command / steps: ran focused diagnostic decode policy tests,
ruff, ruff format check, mypy, and staged gitleaks scan.
- Observed result: diagnostic decode behavior is directly covered and
local lint/type/security checks pass.
- Not tested: live upstream error responses; existing helper import path
remains intact.

## 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
- [ ] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [x] I have added tests that prove my fix is effective or that my
feature works
- [x] New and existing unit tests pass locally with my changes
- [ ] I have updated the CHANGELOG.md if applicable

## Screenshots (if applicable)

N/A

## Additional Notes

Documentation and changelog updates are N/A for this internal
architecture-only refactor. The push reported existing default-branch
Dependabot alerts; no staged secret leaks were found for this PR.

---------

Co-authored-by: Tejas Chopra <chopratejas@gmail.com>
2026-07-12 12:17:39 -04:00
JD Davis
41ce14bd64
Extract wire debug format policy (#1978)
## Description

Extracts opt-in Codex wire-debug formatting from `helpers.py` into
`headroom.proxy.wire_debug_format_policy`. The existing helper functions
now delegate to the pure policy so filename-safe event names and
proxy-log previews are directly testable.

Closes #

## Type of Change

- [ ] 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
- [ ] Performance improvement
- [x] Code refactoring (no functional changes)

## Changes Made

- Added `wire_debug_format_policy.py` for safe wire-debug name fragments
and compact log previews.
- Kept `_safe_event_name` and `_wire_debug_preview` in `helpers.py` as
compatibility delegates.
- Added direct tests for unsafe-name replacement, length capping, JSON
preview compaction, byte decoding/truncation, and `None` handling.
- Carried forward the LiteLLM callback compatibility shim needed for
current mypy on `main`.

## Testing

- [x] Unit tests pass (`pytest`)
- [x] Linting passes (`ruff check .`)
- [x] Type checking passes (`mypy headroom`)
- [x] New tests added for new functionality
- [ ] Manual testing performed

### Test Output

```text
python -m pytest tests\test_wire_debug_format_policy.py
5 passed in 0.19s

python -m ruff check .
All checks passed!

python -m ruff format --check .
1095 files already formatted

python -m mypy headroom --ignore-missing-imports
Success: no issues found in 409 source files

gitleaks protect --staged --no-banner --redact
no leaks found
```

## Real Behavior Proof

- Environment: Windows, Python 3.13.13, branch
`jd/architecture-slice-28`.
- Exact command / steps: ran focused wire-debug format policy tests,
ruff, ruff format check, mypy, and staged gitleaks scan.
- Observed result: formatting policy behavior is directly covered and
local lint/type/security checks pass.
- Not tested: live wire-debug capture writing; this slice preserves the
existing helper entry points.

## 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
- [ ] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [x] I have added tests that prove my fix is effective or that my
feature works
- [x] New and existing unit tests pass locally with my changes
- [ ] I have updated the CHANGELOG.md if applicable

## Screenshots (if applicable)

N/A

## Additional Notes

Documentation and changelog updates are N/A for this internal
architecture-only refactor. The push reported existing default-branch
Dependabot alerts; no staged secret leaks were found for this PR.

---------

Co-authored-by: Tejas Chopra <chopratejas@gmail.com>
2026-07-12 12:17:18 -04:00
JD Davis
ec3c3cd234
refactor(proxy): extract ccr marker policy (#2004)
## Description

Extracts CCR marker freshness and retrieval-tool injection decision
policy from `headroom.proxy.helpers` into a focused pure module.
Existing helper functions remain as compatibility wrappers for current
Anthropic/OpenAI handler imports.

Closes #

## Type of Change

- [ ] 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
- [ ] Performance improvement
- [x] Code refactoring (no functional changes)

## Changes Made

- Added `headroom.proxy.ccr_marker_policy` for new-marker detection and
frozen-prefix tool injection decisions.
- Kept `helpers.has_new_ccr_markers()` and
`helpers.should_inject_ccr_tool()` as compatibility wrappers.
- Added direct policy tests for replayed markers, genuinely new markers,
missing prior forwards, empty current hashes, and frozen-prefix override
behavior.

## Testing

- [x] Unit tests pass (`pytest`)
- [x] Linting passes (`ruff check .`)
- [x] Type checking passes (`mypy headroom`)
- [x] New tests added for new functionality
- [ ] Manual testing performed

### Test Output

```text
python -m pytest tests/test_ccr_marker_policy.py tests/test_proxy/test_ccr_frozen_prefix_coupling.py tests/test_proxy_handler_helpers.py::TestHasNewCcrMarkers
16 passed in 0.91s

python -m ruff check .
All checks passed!

python -m ruff format --check .
1069 files already formatted

python -m mypy headroom --ignore-missing-imports
Success: no issues found in 410 source files

gitleaks protect --staged --no-banner --redact
no leaks found
```

## Real Behavior Proof

- Environment: Windows, Python 3.13.13
- Exact command / steps: Ran direct CCR marker policy tests,
frozen-prefix coupling tests, existing helper marker freshness tests,
full ruff, format check, mypy, and staged gitleaks scan.
- Observed result: Existing frozen-prefix CCR behavior remains green
while the marker freshness and injection decision policy is directly
covered.
- Not tested: Full repository pytest suite locally; CI covers the
broader matrix.

## 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
- [ ] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [x] I have added tests that prove my fix is effective or that my
feature works
- [x] New and existing unit tests pass locally with my changes
- [ ] I have updated the CHANGELOG.md if applicable

## Screenshots (if applicable)

N/A

## Additional Notes

Documentation and changelog updates are not applicable for this internal
refactor. The default-branch Dependabot alerts reported during push are
pre-existing and unrelated to this PR.

Co-authored-by: Tejas Chopra <chopratejas@gmail.com>
2026-07-12 12:16:48 -04:00
JD Davis
9c7b9d5a9c
refactor(proxy): extract tool injection logging (#2009)
## Description

Extracts proxy tool-injection decision logging from
`headroom.proxy.helpers` into a focused logging policy module. The
public helper function remains in place and delegates to the new module,
so existing injection call sites keep their current API while the
logging format has direct tests.

Closes #

## Type of Change

- [ ] 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
- [ ] Performance improvement
- [x] Code refactoring (no functional changes)

## Changes Made

- Added `headroom.proxy.tool_injection_logging` with the shared
`ToolInjectionDecision` type and structured logging helper.
- Updated `helpers.log_tool_injection_decision` to delegate to the
logging policy module while preserving the existing helper API.
- Added tests that assert the emitted structured fields and verify tool
names/contents are not logged.

## Testing

- [x] Unit tests pass (`pytest`)
- [x] Linting passes (`ruff check .`)
- [x] Type checking passes (`mypy headroom`)
- [x] New tests added for new functionality
- [ ] Manual testing performed

### Test Output

```text
python -m pytest tests/test_tool_injection_logging.py tests/test_memory_tool_session_sticky.py tests/test_ccr_tool_always_on.py tests/test_corrupt_golden_bytes_recovery.py tests/test_issue_728_empty_tools_injection.py
60 passed in 0.95s

python -m ruff check .
All checks passed!

python -m ruff format --check .
1069 files already formatted

python -m mypy headroom --ignore-missing-imports
Success: no issues found in 410 source files

gitleaks protect --staged --no-banner --redact
no leaks found
```

## Real Behavior Proof

- Environment: Windows, Python 3.13.13, clean worktree from
`headroomlabs/main` at `d2170b19`.
- Exact command / steps: Ran targeted logging, memory injection, CCR
injection, corrupt-byte, and empty-tool regression tests plus ruff,
ruff-format, mypy, and staged gitleaks scan.
- Observed result: All targeted tests and local gates passed; staged
secret scan found no leaks.
- Not tested: Full Docker/native wrapper CI locally; covered by
repository CI.

## 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
- [ ] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [x] I have added tests that prove my fix is effective or that my
feature works
- [x] New and existing unit tests pass locally with my changes
- [ ] I have updated the CHANGELOG.md if applicable

## Screenshots (if applicable)

N/A.

## Additional Notes

Documentation and changelog updates are not applicable for this internal
refactor. The push reported existing default-branch Dependabot
vulnerabilities; this PR's staged gitleaks scan passed and CI security
checks are expected to validate the branch.

Co-authored-by: Tejas Chopra <chopratejas@gmail.com>
2026-07-12 12:11:28 -04:00
JD Davis
d6259b2263
refactor(proxy): extract tool injection policy (#1995)
## Description

Extracts memory tool injection stickiness configuration parsing from
`headroom.proxy.helpers` into a focused policy module. The existing
helper functions remain in place for the session tool tracker and sticky
injection helpers.

Closes #

## Type of Change

- [ ] 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
- [ ] Performance improvement
- [x] Code refactoring (no functional changes)

## Changes Made

- Added `headroom.proxy.tool_injection_policy` for tool sticky mode and
tracker session limit resolution.
- Kept `get_tool_injection_sticky_mode()` and
`get_tool_tracker_max_sessions()` as compatibility wrappers in
`helpers.py`.
- Added direct unit tests for defaults, accepted values, and loud
rejection of invalid operator configuration.

## Testing

- [x] Unit tests pass (`pytest`)
- [x] Linting passes (`ruff check .`)
- [x] Type checking passes (`mypy headroom`)
- [x] New tests added for new functionality
- [ ] Manual testing performed

### Test Output

```text
python -m pytest tests/test_tool_injection_policy.py tests/test_memory_tool_session_sticky.py
38 passed in 0.44s

python -m ruff check .
All checks passed!

python -m ruff format --check .
1069 files already formatted

python -m mypy headroom --ignore-missing-imports
Success: no issues found in 410 source files

gitleaks protect --staged --no-banner --redact
no leaks found
```

## Real Behavior Proof

- Environment: Windows, Python 3.13.13
- Exact command / steps: Ran focused tool injection policy tests,
existing memory tool sticky suite, full ruff, format check, mypy, and
staged gitleaks scan.
- Observed result: Existing sticky memory-tool behavior remains green
while extracted policy parsing is covered directly.
- Not tested: Full repository pytest suite locally; CI covers the
broader matrix.

## 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
- [ ] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [x] I have added tests that prove my fix is effective or that my
feature works
- [x] New and existing unit tests pass locally with my changes
- [ ] I have updated the CHANGELOG.md if applicable

## Screenshots (if applicable)

N/A

## Additional Notes

Documentation and changelog updates are not applicable for this internal
refactor. The default-branch Dependabot alerts reported during push are
pre-existing and unrelated to this PR.

Co-authored-by: Tejas Chopra <chopratejas@gmail.com>
2026-07-12 12:09:07 -04:00
JD Davis
10001755e8
refactor(proxy): extract tool name policy (#2008)
## Description

Extracts proxy tool-definition name parsing from
`headroom.proxy.helpers` into a focused policy module. The existing
private helper remains as a compatibility wrapper while memory and CCR
injection skip logic share the tested parser.

Closes #

## Type of Change

- [ ] 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
- [ ] Performance improvement
- [x] Code refactoring (no functional changes)

## Changes Made

- Added `headroom.proxy.tool_name_policy.extract_tool_name` for
Anthropic custom tools, OpenAI function tools, and Anthropic native
memory tools.
- Updated `helpers._extract_tool_name` to delegate to the policy module
while keeping its existing import path intact.
- Added direct tests for name precedence, function-tool parsing,
native-tool fallback, invalid values, and wrapper compatibility.

## Testing

- [x] Unit tests pass (`pytest`)
- [x] Linting passes (`ruff check .`)
- [x] Type checking passes (`mypy headroom`)
- [x] New tests added for new functionality
- [ ] Manual testing performed

### Test Output

```text
python -m pytest tests/test_tool_name_policy.py tests/test_memory_tool_session_sticky.py tests/test_ccr_tool_always_on.py tests/test_issue_728_empty_tools_injection.py tests/test_proxy/test_ccr_frozen_prefix_coupling.py
60 passed in 0.90s

python -m ruff check .
All checks passed!

python -m ruff format --check .
1069 files already formatted

python -m mypy headroom --ignore-missing-imports
Success: no issues found in 410 source files

gitleaks protect --staged --no-banner --redact
no leaks found
```

## Real Behavior Proof

- Environment: Windows, Python 3.13.13, clean worktree from
`headroomlabs/main` at `d2170b19`.
- Exact command / steps: Ran targeted tool-name policy tests plus
memory/CCR injection regression tests, ruff, ruff-format, mypy, and
staged gitleaks scan.
- Observed result: All targeted tests and local gates passed; staged
secret scan found no leaks.
- Not tested: Full Docker/native wrapper CI locally; covered by
repository CI.

## 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
- [ ] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [x] I have added tests that prove my fix is effective or that my
feature works
- [x] New and existing unit tests pass locally with my changes
- [ ] I have updated the CHANGELOG.md if applicable

## Screenshots (if applicable)

N/A.

## Additional Notes

Documentation and changelog updates are not applicable for this internal
refactor. The push reported existing default-branch Dependabot
vulnerabilities; this PR's staged gitleaks scan passed and CI security
checks are expected to validate the branch.

---------

Co-authored-by: Tejas Chopra <chopratejas@gmail.com>
2026-07-12 11:54:09 -04:00
JD Davis
7c9a032f50
refactor(proxy): extract ccr golden replay policy (#2006)
## Description

Extracts CCR golden tool replay and fresh-definition canonicalization
from `headroom.proxy.helpers.apply_session_sticky_ccr_tool` into a
focused policy module. This keeps sticky CCR orchestration in helpers
while making the byte replay/regeneration behavior independently
testable.

Closes #

## Type of Change

- [ ] 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
- [ ] Performance improvement
- [x] Code refactoring (no functional changes)

## Changes Made

- Added `headroom.proxy.ccr_golden_policy` for replaying stored CCR
golden bytes and creating canonical fresh CCR tool definitions.
- Updated `apply_session_sticky_ccr_tool` to delegate CCR golden
replay/fresh definition policy while preserving tracker coordination and
logging decisions.
- Added direct tests for golden-byte replay, invalid/corrupt bytes,
non-UTF-8 bytes, and fresh canonical definition generation.

## Testing

- [x] Unit tests pass (`pytest`)
- [x] Linting passes (`ruff check .`)
- [x] Type checking passes (`mypy headroom`)
- [x] New tests added for new functionality
- [ ] Manual testing performed

### Test Output

```text
python -m pytest tests/test_ccr_golden_policy.py tests/test_ccr_tool_always_on.py tests/test_corrupt_golden_bytes_recovery.py tests/test_proxy/test_ccr_frozen_prefix_coupling.py
30 passed in 0.34s

python -m ruff check .
All checks passed!

python -m ruff format --check .
1069 files already formatted

python -m mypy headroom --ignore-missing-imports
Success: no issues found in 410 source files

gitleaks protect --staged --no-banner --redact
no leaks found
```

## Real Behavior Proof

- Environment: Windows, Python 3.13.13, clean worktree from
`headroomlabs/main` at `d2170b19`.
- Exact command / steps: Ran targeted CCR golden replay/sticky
injection/corrupt-byte regression tests plus ruff, ruff-format, mypy,
and staged gitleaks scan.
- Observed result: All targeted tests and local gates passed; staged
secret scan found no leaks.
- Not tested: Full Docker/native wrapper CI locally; covered by
repository CI.

## 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
- [ ] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [x] I have added tests that prove my fix is effective or that my
feature works
- [x] New and existing unit tests pass locally with my changes
- [ ] I have updated the CHANGELOG.md if applicable

## Screenshots (if applicable)

N/A.

## Additional Notes

Documentation and changelog updates are not applicable for this internal
refactor. The push reported existing default-branch Dependabot
vulnerabilities; this PR's staged gitleaks scan passed and CI security
checks are expected to validate the branch.
2026-07-12 11:49:12 -04:00
JD Davis
d1c484b164
refactor(proxy): extract tool injection tracker (#2002)
## Description

Extracts the sticky memory tool session tracker from
`headroom.proxy.helpers` into a focused state module.
`helpers.SessionToolTracker` remains as an env-aware compatibility
wrapper so existing injection and singleton call sites keep the same
API.

Closes #

## Type of Change

- [ ] 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
- [ ] Performance improvement
- [x] Code refactoring (no functional changes)

## Changes Made

- Added `headroom.proxy.tool_injection_tracker.SessionToolTracker` as
the pure bounded LRU state holder.
- Replaced the large in-helper tracker implementation with a small
env-aware wrapper.
- Added direct tracker tests for unknown sessions, ordered golden bytes,
first-write wins, provider isolation, LRU eviction, and input
validation.

## Testing

- [x] Unit tests pass (`pytest`)
- [x] Linting passes (`ruff check .`)
- [x] Type checking passes (`mypy headroom`)
- [x] New tests added for new functionality
- [ ] Manual testing performed

### Test Output

```text
python -m pytest tests/test_tool_injection_tracker.py tests/test_memory_tool_session_sticky.py tests/test_corrupt_golden_bytes_recovery.py
44 passed in 0.54s

python -m ruff check .
All checks passed!

python -m ruff format --check .
1069 files already formatted

python -m mypy headroom --ignore-missing-imports
Success: no issues found in 410 source files

gitleaks protect --staged --no-banner --redact
no leaks found
```

## Real Behavior Proof

- Environment: Windows, Python 3.13.13
- Exact command / steps: Ran direct tracker tests, sticky memory tool
tests, corrupt golden byte recovery tests, full ruff, format check,
mypy, and staged gitleaks scan.
- Observed result: Existing sticky injection behavior and recovery
behavior remain green while the tracker state domain is directly
covered.
- Not tested: Full repository pytest suite locally; CI covers the
broader matrix.

## 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
- [ ] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [x] I have added tests that prove my fix is effective or that my
feature works
- [x] New and existing unit tests pass locally with my changes
- [ ] I have updated the CHANGELOG.md if applicable

## Screenshots (if applicable)

N/A

## Additional Notes

Documentation and changelog updates are not applicable for this internal
refactor. The default-branch Dependabot alerts reported during push are
pre-existing and unrelated to this PR.
2026-07-12 11:48:33 -04:00
JD Davis
b910ce5deb
Extract SSE byte buffer policy (#1979)
## Description

Extracts the pure SSE byte-buffer parser from `helpers.py` into
`headroom.proxy.sse_byte_buffer_policy`. Existing helper imports remain
as delegates, while the protocol parser now has its own module and
direct tests.

Closes #

## Type of Change

- [ ] 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
- [ ] Performance improvement
- [x] Code refactoring (no functional changes)

## Changes Made

- Added `sse_byte_buffer_policy.py` for SSE terminator detection and
complete-event parsing.
- Kept `helpers.parse_sse_events_from_byte_buffer` and
`_find_sse_event_terminator` delegating to the extracted policy.
- Added direct policy tests for LF/CRLF terminators, buffer draining,
split UTF-8 preservation, and invalid complete UTF-8 events.
- Carried forward the LiteLLM callback compatibility shim needed for
current mypy on `main`.

## Testing

- [x] Unit tests pass (`pytest`)
- [x] Linting passes (`ruff check .`)
- [x] Type checking passes (`mypy headroom`)
- [x] New tests added for new functionality
- [ ] Manual testing performed

### Test Output

```text
python -m pytest tests\test_sse_byte_buffer_policy.py tests\test_sse_utf8_split.py
8 passed in 0.23s

python -m ruff check .
All checks passed!

python -m ruff format --check .
1095 files already formatted

python -m mypy headroom --ignore-missing-imports
Success: no issues found in 409 source files

gitleaks protect --staged --no-banner --redact
no leaks found
```

## Real Behavior Proof

- Environment: Windows, Python 3.13.13, branch
`jd/architecture-slice-29`.
- Exact command / steps: ran new SSE byte-buffer policy tests, existing
SSE UTF-8 split tests, ruff, ruff format check, mypy, and staged
gitleaks scan.
- Observed result: SSE parser behavior remains covered and local
lint/type/security checks pass.
- Not tested: live streaming proxy runtime; existing helper imports
remain intact.

## 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
- [ ] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [x] I have added tests that prove my fix is effective or that my
feature works
- [x] New and existing unit tests pass locally with my changes
- [ ] I have updated the CHANGELOG.md if applicable

## Screenshots (if applicable)

N/A

## Additional Notes

Documentation and changelog updates are N/A for this internal
architecture-only refactor. The push reported existing default-branch
Dependabot alerts; no staged secret leaks were found for this PR.
2026-07-12 11:46:36 -04:00
Abhay Singh
984a2c702c
fix(ccr): don't crash parse_tool_call on non-object tool arguments (#2071)
## Description

`parse_tool_call` (`headroom/ccr/tool_injection.py`) extracts the
retrieval hash from a CCR tool
call. For the OpenAI and `openai_responses` shapes it decodes the
`arguments` string with
`json.loads` and catches only `JSONDecodeError`:

```python
args_str = function.get("arguments", "{}")
try:
    input_data = json.loads(args_str)
except json.JSONDecodeError:
    input_data = {}
...
hash_key = input_data.get("hash")   # assumes input_data is a dict
```

If a (confused) model emits `arguments='[]'` / `'"abc"'` / `'123'`,
`json.loads` succeeds and
returns a **list / str / number**, so `input_data.get("hash")` raises
`AttributeError`. A null
value (`arguments: null` → `json.loads(None)`) raises an uncaught
`TypeError`. The Anthropic branch
has the same hazard if `tool_call["input"]` is present but not a dict.

`parse_tool_call` is called from `parse_ccr_tool_calls`
(`ccr/tool_calls.py`) and the server CCR
path with no guard for this, so a malformed CCR-named tool call
**crashes CCR response
processing** instead of being ignored.

Closes: no issue filed — found while auditing the CCR tool-call parsing.

## Fix

- Catch `TypeError` as well as `JSONDecodeError` around `json.loads`
(covers `arguments: null`).
- Return `None` when `input_data` is not a `dict` — a non-object tool
call simply isn't a valid CCR
  call.

## Type of Change

- [x] Bug fix (non-breaking change that fixes an issue)

## Changes Made

- `headroom/ccr/tool_injection.py`: widen the decode `except` to
`(json.JSONDecodeError, TypeError)`; return `None` for non-dict
`input_data`.
- `tests/test_ccr_tool_injection.py`: add tests for non-object OpenAI
arguments (`[]`/`"abc"`/`123`), null arguments, and a non-dict Anthropic
`input`.

## Testing

- [x] New regression tests added (`tests/test_ccr_tool_injection.py`)
- [x] Linting/formatting clean — run with the CI-pinned `ruff==0.15.17`
- [ ] Full `pytest` deferred to CI (local-OOM reason below).

```text
$ uvx ruff@0.15.17 check headroom/ccr/tool_injection.py tests/test_ccr_tool_injection.py
All checks passed!
```

## Real Behavior Proof

- Environment: Windows 11, Python 3.10, headroom from this branch.
Importing `headroom` pulls in the torch/transformers stack and a full
`pytest` gets OOM-killed on this box, so I verified the parse logic with
a dependency-free script and left the full pytest to CI.
- Exact command / steps: ran the four crash vectors (openai `[]`,
`"abc"`, `null`; anthropic non-dict `input`) plus a valid CCR call and a
non-CCR call through the old and new logic.
- Observed result: the old parser crashes on every malformed case; the
new one returns `None` and still parses a valid call:

```text
OK [openai] '[]': old CRASHED -> new None
OK [openai] '"abc"': old CRASHED -> new None
OK [openai] None: old CRASHED -> new None
OK [anthropic] ['not', 'a', 'dict']: old CRASHED -> new None
PARSE_TOOL_CALL NON-DICT FIX VERIFIED (old crashes; new returns None; valid still parses)
```

- Not tested: a full CCR response round-trip with a malformed tool call
(needs the heavy stack). The fix is confined to `parse_tool_call` and
the new tests drive it directly. Full local `pytest` deferred to CI
(OOM, per above).

## 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
- [ ] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [x] I have added tests that prove my fix is effective or that my
feature works
- [ ] New and existing unit tests pass locally with my changes — ran
lint + a standalone logic check; full pytest deferred to CI (local OOM,
disclosed above)
- [x] I have updated the CHANGELOG.md if applicable

## Additional Notes

- Two-line hardening plus tests; no new dependencies.
- @JerrettDavis tagging you — a malformed CCR-named tool call currently
crashes CCR response processing; quick one. Thanks!
2026-07-12 08:34:23 -07:00
JD Davis
868b88bc64
refactor(proxy): extract internal header policy (#1990)
## Description

Extracts the internal x-headroom request-header stripping policy from
`headroom.proxy.helpers` into a focused policy module. This keeps the
security-sensitive upstream filtering rule independently testable while
preserving the existing helper API used by provider handlers.

Closes #

## Type of Change

- [ ] 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
- [ ] Performance improvement
- [x] Code refactoring (no functional changes)

## Changes Made

- Added `headroom.proxy.internal_header_policy` for strip mode
resolution and x-headroom header filtering.
- Kept `get_strip_internal_headers_mode()` and
`_strip_internal_headers()` as compatibility wrappers in `helpers.py`.
- Added direct unit tests for default/disabled/invalid modes,
case-insensitive filtering, and copy semantics.

## Testing

- [x] Unit tests pass (`pytest`)
- [x] Linting passes (`ruff check .`)
- [x] Type checking passes (`mypy headroom`)
- [x] New tests added for new functionality
- [ ] Manual testing performed

### Test Output

```text
python -m pytest tests/test_internal_header_policy.py tests/test_header_isolation.py
29 passed in 4.89s

python -m ruff check .
All checks passed!

python -m ruff format --check .
1069 files already formatted

python -m mypy headroom --ignore-missing-imports
Success: no issues found in 410 source files

gitleaks protect --staged --no-banner --redact
no leaks found
```

## Real Behavior Proof

- Environment: Windows, Python 3.13.13
- Exact command / steps: Ran focused policy/header isolation pytest
coverage plus full ruff, ruff format check, mypy, and staged gitleaks
scan.
- Observed result: Header stripping behavior remains green end-to-end,
direct policy tests cover security-sensitive parsing/filtering rules,
and local quality/security gates pass.
- Not tested: Full repository pytest suite locally; CI covers the
broader matrix.

## 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
- [ ] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [x] I have added tests that prove my fix is effective or that my
feature works
- [x] New and existing unit tests pass locally with my changes
- [ ] I have updated the CHANGELOG.md if applicable

## Screenshots (if applicable)

N/A

## Additional Notes

Documentation and changelog updates are not applicable for this internal
refactor. The default-branch Dependabot alerts reported during push are
pre-existing and unrelated to this PR.
2026-07-11 21:55:50 -05:00
JD Davis
4640587a06
Extract wire debug redaction policy (#1972)
## Description

Extracts the pure secret-redaction logic used by opt-in Codex wire-debug
capture from `helpers.py` into
`headroom.proxy.wire_debug_redaction_policy`. This keeps the debug
capture path behavior intact while making the sensitive-key policy
directly testable.

Closes #

## Type of Change

- [ ] 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
- [ ] Performance improvement
- [x] Code refactoring (no functional changes)

## Changes Made

- Added `wire_debug_redaction_policy.py` for secret-key matching and
recursive wire-debug redaction.
- Kept existing helper entry points and private compatibility names
delegating to the extracted policy.
- Added direct tests for direct secret headers, nested suffix-matched
secrets, and key normalization.
- Carried forward the LiteLLM callback compatibility shim needed for
current mypy on `main`.

## Testing

- [x] Unit tests pass (`pytest`)
- [x] Linting passes (`ruff check .`)
- [x] Type checking passes (`mypy headroom`)
- [x] New tests added for new functionality
- [ ] Manual testing performed

### Test Output

```text
python -m pytest tests\test_wire_debug_redaction_policy.py
3 passed in 0.16s

python -m ruff check .
All checks passed!

python -m ruff format --check .
1095 files already formatted

python -m mypy headroom --ignore-missing-imports
Success: no issues found in 409 source files

gitleaks protect --staged --no-banner --redact
no leaks found
```

## Real Behavior Proof

- Environment: Windows, Python 3.13.13, branch
`jd/architecture-slice-25`.
- Exact command / steps: ran focused wire-debug redaction tests, ruff,
ruff format check, mypy, and staged gitleaks scan.
- Observed result: redaction policy is directly covered and local
lint/type/security checks pass.
- Not tested: full proxy wire-debug capture runtime; this slice
preserves the existing helper entry points.

## 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
- [ ] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [x] I have added tests that prove my fix is effective or that my
feature works
- [x] New and existing unit tests pass locally with my changes
- [ ] I have updated the CHANGELOG.md if applicable

## Screenshots (if applicable)

N/A

## Additional Notes

Documentation and changelog updates are N/A for this internal
architecture-only refactor. The push reported existing default-branch
Dependabot alerts; no staged secret leaks were found for this PR.
2026-07-11 21:36:46 -05:00
JD Davis
2f53a18a3f
refactor(proxy): isolate semantic cache key policy (#1964)
## Description

Extracts proxy semantic response-cache key normalization and hashing
into a pure `semantic_cache_key_policy` module. `SemanticCache` keeps
ownership of storage, locking, TTL, and LRU behavior while the
deterministic cache-key formula is directly tested as a standalone
policy.

Closes #

## Type of Change

- [ ] 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
- [ ] Performance improvement
- [x] Code refactoring (no functional changes)

## Changes Made

- Added `headroom.proxy.semantic_cache_key_policy` with recursive
`cache_control` stripping and semantic cache key hashing.
- Updated `SemanticCache._compute_key` to delegate to the pure key
policy while preserving its existing private wrapper contract.
- Added direct policy tests for recursive annotation stripping, key
stability, response-shaping distinctions, breakpoint movement, and
wrapper parity.
- Included the current LiteLLM callback signature compatibility shim
required for repo-wide mypy on main-based slices.

## Testing

- [x] Unit tests pass (`pytest`)
- [x] Linting passes (`ruff check .`)
- [x] Type checking passes (`mypy headroom`)
- [x] New tests added for new functionality
- [ ] Manual testing performed

### Test Output

```text
python -m pytest tests/test_semantic_cache_key_policy.py tests/test_proxy_semantic_cache_key.py tests/test_litellm_callback.py -q
39 passed in 6.34s

python -m ruff check .
All checks passed!

python -m ruff format --check .
1095 files already formatted

python -m mypy headroom --ignore-missing-imports
Success: no issues found in 409 source files

gitleaks protect --staged --no-banner --redact
no leaks found
```

## Real Behavior Proof

- Environment: Windows, Python 3.13.13, clean worktree based on
`headroomlabs/main`.
- Exact command / steps: targeted pytest, ruff, format check, repo-wide
mypy, staged gitleaks scan.
- Observed result: semantic cache key policy/cache/callback tests pass;
static checks pass; no staged secrets detected.
- Not tested: live proxy cache traffic; this slice preserves the
existing cache wrapper and only moves pure key policy.

## 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
- [ ] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [x] I have added tests that prove my fix is effective or that my
feature works
- [x] New and existing unit tests pass locally with my changes
- [ ] I have updated the CHANGELOG.md if applicable

## Screenshots (if applicable)

N/A

## Additional Notes

Documentation and changelog updates are not applicable for this internal
architecture slice. PR-specific GHAS checks will be monitored after
opening.
2026-07-11 21:35:52 -05:00
JD Davis
c904a70d4e
refactor(proxy): isolate output turn policy (#1962)
## Description

Extracts output-shaper turn classification into a pure
`output_turn_policy` module. The shaper still owns request mutation and
labels, while Anthropic-style and OpenAI Responses structural turn
classification now live in a deterministic policy boundary.

Closes #

## Type of Change

- [ ] 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
- [ ] Performance improvement
- [x] Code refactoring (no functional changes)

## Changes Made

- Added `headroom.proxy.output_turn_policy` with `TurnKind`,
`classify_turn`, and `classify_openai_responses_input`.
- Updated `output_shaper` to import and re-export the classifiers,
preserving existing import behavior.
- Added direct policy tests for Anthropic tool-result turns and OpenAI
Responses input classification.
- Included the current LiteLLM callback signature compatibility shim
required for repo-wide mypy on main-based slices.

## Testing

- [x] Unit tests pass (`pytest`)
- [x] Linting passes (`ruff check .`)
- [x] Type checking passes (`mypy headroom`)
- [x] New tests added for new functionality
- [ ] Manual testing performed

### Test Output

```text
python -m pytest tests/test_output_turn_policy.py tests/test_output_shaper.py tests/test_litellm_callback.py -q
60 passed in 6.25s

python -m ruff check .
All checks passed!

python -m ruff format --check .
1095 files already formatted

python -m mypy headroom --ignore-missing-imports
Success: no issues found in 409 source files

gitleaks protect --staged --no-banner --redact
no leaks found
```

## Real Behavior Proof

- Environment: Windows, Python 3.13.13, clean worktree based on
`headroomlabs/main`.
- Exact command / steps: targeted pytest, ruff, format check, repo-wide
mypy, staged gitleaks scan.
- Observed result: output turn policy/shaper/callback tests pass; static
checks pass; no staged secrets detected.
- Not tested: live provider calls; this slice only moves structural
classification logic and preserves existing shaper behavior.

## 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
- [ ] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [x] I have added tests that prove my fix is effective or that my
feature works
- [x] New and existing unit tests pass locally with my changes
- [ ] I have updated the CHANGELOG.md if applicable

## Screenshots (if applicable)

N/A

## Additional Notes

Documentation and changelog updates are not applicable for this internal
architecture slice. PR-specific GHAS checks will be monitored after
opening.
2026-07-11 21:33:48 -05:00
JD Davis
2c9eb7c5f1
feat(simulators): add provider simulator service (#2014)
## Description  
Adds a Rust-only `headroom-simulators` workspace crate: a deterministic
local upstream simulator service for Headroom proxy and pipeline
validation. It supplies configurable stubs plus bottled provider-shaped
responses for supported provider/path surfaces without calling real
LLMs.

## Type of Change  
- [x] Bug fix (non-breaking change that fixes an issue)  
- [x] New feature (non-breaking change that adds functionality)  
- [ ] Breaking change (fix or feature that would cause existing
functionality to change)
- [x] Documentation update  
- [ ] Performance improvement  
- [ ] Code refactoring (no functional changes)

## Changes Made  
- Added `crates/headroom-simulators` Rust crate with library and
`headroom-simulators` binary.
- Added clean domain classification for supported surfaces: Anthropic
`/v1/messages`, OpenAI chat/responses/conversations, Bedrock
invoke/stream routes, Vertex raw/stream predict, health, and generic
fallback.
- Added JSON-configured stub matching by method, path, body substring,
and JSON pointer.
- Added bottled provider-shaped JSON, SSE, and Bedrock EventStream
responses for unconfigured requests.
- Added a container `Dockerfile` and README for local/GitHub Actions
usage.
- Added unit and HTTP integration tests for defaults, configured stubs,
SSE, Vertex, and Bedrock EventStream behavior.
- Added proxy-level simulator-backed E2E tests that run Headroom against
the simulator across Anthropic, OpenAI Chat, OpenAI Responses, OpenAI
Conversations, Bedrock invoke/converse/streaming, Vertex raw/stream
predict, and upstream health.
- Added simulator-backed provider error-path E2E coverage for OpenAI
429, Anthropic 529, Bedrock 502, and Vertex 503 responses flowing
through Headroom unchanged.
- Added Headroom-owned preflight error E2E coverage proving Bedrock
missing credentials and invalid Vertex envelopes stop inside the proxy
instead of silently falling through to the simulator/provider.
- Fixed direct Rust `headroom-core` binaries/tests on Windows so Magika
initializes ONNX Runtime via `ort::init_from` from an explicit pip
`onnxruntime` library path, with fail-fast fallback only when no safe
runtime is discoverable.
- Added a Rust CI `simulator-e2e` matrix for `ubuntu-latest`,
`macos-latest`, and `windows-latest` that runs `cargo test -p
headroom-proxy --test e2e_simulators`.
- Gated dynamic Magika `Path`/`PathBuf` imports to Windows and x86_64
macOS so Linux clippy does not see unused dynamic-ORT-only imports.

## Testing  
- [ ] Unit tests pass (`pytest`)  
- [ ] Linting passes (`ruff check .`)  
- [ ] Type checking passes (`mypy headroom`)  
- [x] New tests added for new functionality  
- [x] Manual testing performed

### Test Output  
cargo fmt --all -- --check  
# passed  

cargo clippy --workspace -- -D warnings  
# passed  

$env:ORT_DYLIB_PATH=$null  
cargo test -p headroom-core transforms::magika_detector::tests:: --lib  
# 17 passed, 0 failed; Magika initialized from discovered pip
onnxruntime DLL

$env:ORT_DYLIB_PATH=$null  
cargo test --workspace  
# passed  

gitleaks protect --staged --no-banner --redact  
# no leaks found  

gitleaks git --log-opts="headroomlabs/main..HEAD" --no-banner --redact  
# 5 commits scanned; no leaks found

## Real Behavior Proof  
- **Environment:** Windows PowerShell, Rust toolchain `1.95.0`, clean
worktree from `headroomlabs/main` at `9bacf481`.
- **Exact simulator command / steps:**  
  - `cargo run -p headroom-simulators -- --listen 127.0.0.1:8789`  
- Point Headroom proxy upstream at `http://127.0.0.1:8789` for local
deterministic provider responses.
- Use optional `--config path/to/simulator.json` to bind exact request
fixtures.
- **Observed simulator result:**  
  - OpenAI chat default returns `chat.completion` shape.  
  - OpenAI Responses stream returns named SSE events.  
  - Vertex raw predict returns Anthropic message shape.  
- Bedrock stream can return binary `application/vnd.amazon.eventstream`
bytes.
  - Configured stubs override bottled defaults.  
- **Observed Magika result:**  
- Direct Rust `headroom-core` tests pass with `ORT_DYLIB_PATH` unset.
- Magika discovers the installed pip `onnxruntime.dll`, loads it via
`ort::init_from`, and only falls back if no safe runtime is available.
- **Not tested:**  
- No live provider calls; simulator behavior is intentionally offline
and deterministic.

## 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] I have made corresponding changes to the documentation  
- [x] My changes generate no new warnings  
- [x] I have added tests that prove my fix is effective or that my
feature works
- [x] New and existing unit tests pass locally with my changes  
- [ ] I have updated the CHANGELOG.md if applicable

## Screenshots (if applicable)  
N/A

## Additional Notes  
No CHANGELOG entry was added because this introduces a developer/CI
simulator crate plus a Windows direct-Rust Magika runtime fix, without
changing shipped Python package behavior. The simulator intentionally
does not include a lightweight fallback LLM in this slice; unbound
inputs receive deterministic bottled responses so tests stay
reproducible and offline.
2026-07-11 09:41:49 -07:00
Krishna Chaitanya
7f7af667ed
feat(observability): add gen_ai.request.model to the compression span (#1667)
## Description

Emit the OpenTelemetry GenAI semantic-convention attribute
`gen_ai.request.model` on the existing `headroom.compression.pipeline`
span, alongside the current `headroom.*` attributes. Today Headroom's
OTel spans use only proprietary `headroom.*` names, so a team pointing
an OTel-native backend at Headroom can't join its telemetry to their
existing `gen_ai.*` LLM dashboards. This makes the compression span
groupable/filterable by the standard schema.

Proposed and scoped in #1671. Per CONTRIBUTING (new features want a
maintainer 👍 + spec first), this is opened as a **draft** to get
sign-off on the approach and v1 scope before finalizing.

## Type of Change

- [x] New feature (non-breaking change that adds functionality)

## Short spec

- API surface: one additive span attribute, `gen_ai.request.model`, on
the existing `headroom.compression.pipeline` span. No new endpoints,
headers, or config; nothing renamed.
- Scope (v1, deliberately minimal): only `gen_ai.request.model` — the
one gen_ai attribute this pre-flight compression span can set correctly
and unconditionally (the model is always known here).
- Deferred to v2 (each needs work this span cannot do correctly, and I'd
value your steer on all three):
- `gen_ai.operation.name`: `apply()` is shared by many callers (chat,
`/v1/compress`, batch, Gemini `countTokens`), so no single hardcoded
value is right — it has to be threaded from each caller.
- `gen_ai.provider.name`: Headroom's provider label can't distinguish
Bedrock/Gemini from Anthropic/OpenAI at this layer (Bedrock routes
through the Anthropic provider).
- `gen_ai.usage.*`: provider-authoritative usage lives on the response
path, not this span; the compressed-input estimate stays under
`headroom.tokens.after`.
- Failure modes: model missing → attribute omitted (never a blank
string); span not recording / `record_metrics=False` → no attribute, no
crash.
- Security: no new input surface; derived from data already on the span.

## Changes Made

- `headroom/transforms/pipeline.py`: emit `gen_ai.request.model` on the
pipeline span (guarded on model present), with a comment documenting why
the other gen_ai.* attributes are deferred.
- `tests/test_observability_tracing.py`: assert the attribute is
emitted, the deferred attrs are omitted, the model-missing guard, and
the non-recording path.
- `CHANGELOG.md`: Unreleased → Features entry.

## Testing

- [x] Unit tests pass (`pytest`)
- [x] Linting passes (`ruff check .`)
- [x] Type checking passes (`mypy headroom`)
- [x] New tests added for new functionality

### Test Output

```text
$ uv run pytest tests/test_observability_tracing.py -q
7 passed

$ uv run pytest tests/test_observability_tracing.py tests/test_compression_observability.py \
    tests/test_observability_metrics.py tests/test_pipeline.py tests/test_canonical_pipeline.py tests/test_telemetry.py -q
76 passed

$ uv run ruff check .  &&  uv run mypy headroom/transforms/pipeline.py --ignore-missing-imports
All checks passed!  /  Success: no issues found in 1 source file
```

## Real Behavior Proof

- Environment: local, Python 3.12, `opentelemetry-sdk` 1.39.1, real
`ConsoleSpanExporter` (not a mock). Ran the actual
`TransformPipeline.apply()` emission path.
- Exact command / steps: configured a real `TracerProvider` +
`ConsoleSpanExporter`, set it as Headroom's tracer, ran
`TransformPipeline([]).apply([{user msg}],
model="claude-3-5-sonnet-20241022", model_limit=8192)`, then
`force_flush()` and inspected the exported span.
- Observed result: the exported `headroom.compression.pipeline` span
carries `gen_ai.request.model` alongside the existing `headroom.*`
attributes:

  ```json
  "attributes": {
      "headroom.model": "claude-3-5-sonnet-20241022",
      "headroom.provider": "unknown",
      "headroom.message_count": 1,
      "headroom.tokens.before": 83,
      "gen_ai.request.model": "claude-3-5-sonnet-20241022",
      "headroom.tokens.after": 83,
      "headroom.tokens.saved": 0
  }
  ```

- Not tested: no live OTLP collector / Grafana backend (used the console
exporter, which is the same span pipeline); the deferred v2 attributes
(`operation.name`/`provider.name`/`usage.*`) are intentionally not
emitted.

## Review Readiness

- [x] I have performed a self-review
- [x] This PR is ready for human review

(Draft: awaiting a maintainer 👍 on the approach and the v1 scope before
marking ready.)

## Additional Notes

Purely additive and back-compatible — no `headroom.*` attribute changed
or removed. The gen_ai attribute name is a string literal because the
`gen_ai.*` conventions are stability=development in the semconv registry
(no stable constants published). No new dependencies.

Signed-off-by: Krishnachaitanyakc <krishnabkc15@gmail.com>
Co-authored-by: JerrettDavis <mxjerrett@gmail.com>
2026-07-11 11:04:54 -05:00
Rod Boev
ad9d086f43
feat(codex): keep wrap routing session-scoped (#1507)
## Description

Keeps Codex wrap routing session-scoped so routing state from one
wrapped session does not leak into another.

## 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
- [ ] Performance improvement
- [ ] Code refactoring (no functional changes)

## Changes Made

- Scope Codex wrap routing state to the active session.
- Avoid cross-session routing contamination for wrapped Codex traffic.
- Keep changes focused on wrap/proxy routing behavior.

## Testing

- [x] Unit tests pass
- [x] New tests added for new functionality when applicable
- [x] Manual testing performed

### Test Output

```text
Focused tests/review were completed before this governance body cleanup. The current branch is conflicted and still needs merge resolution before final merge readiness.
```

## Real Behavior Proof

- Environment: Headroom development/review context.
- Exact command / steps: Reviewed session-scoped Codex wrap routing
behavior and existing focused coverage.
- Observed result: Routing state is scoped to the active wrap session
rather than shared globally across sessions.
- Not tested: Current conflicted branch after merge resolution;
conflicts still need to be resolved before merge.

## Review Readiness

- [x] I have performed a self-review
- [x] This PR is ready for human review

## Additional Notes

This body was normalized by a maintainer after approval so the
governance parser reflects the already-reviewed PR state. The PR remains
blocked by merge conflicts.
2026-07-11 11:03:57 -05:00
Tejas Chopra
b0440f958d
fix(cache): partial cached-prefix replay + idle-aware net-cost; don't… (#1933)
… revert an overlaid prefix

overlay_cached_prefix (prefix_tracker):
- Replay the previously-forwarded (cached, compressed) prefix up to the
FIRST divergence instead of all-or-nothing. Previously a single changed
leading message — most commonly the just-added assistant turn, whose
client-resent form can differ trivially from the copy we reconstruct +
record — made the guard bail and forward the freshly-recompressed
prefix, busting the ENTIRE cache from message 0. Stopping at the
divergence keeps the (large) cache-hit region and only re-forwards from
the changed message on. This is the token-mode cache-safety fix:
measured REAL_BUST 50-65% -> ~6% on Opus SWE-bench, with token mode
landing resolve-neutral vs cache mode.
- Safe by construction: only replays prev_fwd[k] where
current_original[k] canonicalize-equals prev_orig[k] (positional 1:1
guaranteed by the count check), so no wrong bytes are ever forwarded.

idle plumbing (prefix_tracker + anthropic):
- Snapshot idle-since-last-response in get_or_create BEFORE it bumps the
access clock (otherwise seconds_since_activity reads ~0 every turn), and
forward it to the pipeline as idle_seconds so the dormant net-cost/TTL
P_alive gate (HEADROOM_NET_COST_POLICY=1) can actually see idle time.
Harmless when the policy is off; ~0 for back-to-back agent turns.

anthropic inflation guard:
- Skip the "optimization inflated tokens -> revert to originals" guard
when overlay just replayed a byte-identical cached prefix. Reverting
there would re-forward the raw uncompressed prefix and bust the live
prompt cache (trading a 90% read discount for a full re-write) — far
costlier than the small tail inflation the guard exists to avoid.

## Description

<!-- Briefly explain the change and why it is needed. -->

Closes #

## Type of Change

- [ ] 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
- [ ] Performance improvement
- [ ] Code refactoring (no functional changes)

## Changes Made

- 

## Testing

<!-- Check what you actually ran, then paste the real command output
below. -->

- [ ] Unit tests pass (`pytest`)
- [ ] Linting passes (`ruff check .`)
- [ ] Type checking passes (`mypy headroom`)
- [ ] New tests added for new functionality
- [ ] Manual testing performed

### Test Output

```text
# Paste relevant command output or artifact links here
```

## Real Behavior Proof

- Environment:
- Exact command / steps:
- Observed result:
- Not tested:

## Review Readiness

- [ ] I have performed a self-review
- [ ] This PR is ready for human review

## Checklist

- [ ] My code follows the project's style guidelines
- [ ] I have performed a self-review of my code
- [ ] I have commented my code, particularly in hard-to-understand areas
- [ ] I have made corresponding changes to the documentation
- [ ] My changes generate no new warnings
- [ ] I have added tests that prove my fix is effective or that my
feature works
- [ ] New and existing unit tests pass locally with my changes
- [ ] I have updated the CHANGELOG.md if applicable

## Screenshots (if applicable)

Add screenshots to help explain your changes.

## Additional Notes

<!-- Mention any N/A checklist items, tradeoffs, follow-ups, or
maintainer context. -->

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 10:58:00 -05:00
Abhay Singh
d079614b1f
fix(mcp/opencode): don't clobber an unparseable opencode.json on register (#1661)
## Description

`OpencodeRegistrar._write_entry` does a full-file read-modify-write of
`opencode.json`:

```python
data = _read_json(self._config_path)     # returns {} on JSONDecodeError
mcp = data.setdefault("mcp", {})
mcp[spec.name] = _spec_to_entry(spec)
_write_json(self._config_path, data)     # overwrites the ENTIRE file
```

`_read_json` returns `{}` for a file that exists but doesn't parse.
OpenCode
configs are commonly hand-edited and JSONC-ish (comments, trailing
commas), so a
file that doesn't strictly parse gets silently rewritten as just
`{"mcp": {"headroom": {...}}}` — **destroying the user's `theme`,
`model`,
`provider`, and any other MCP servers**. No backup.

This is the same class of data-loss bug as the Claude registrar
(separate PR);
this one is `headroom/mcp_registry/opencode.py`.

Closes: no issue filed — found while auditing the MCP registry
config-write paths.

## Fix

Keep `_read_json` (returning `{}`) for read-only callers. Add
`_read_json_for_write` for the rewrite path: it returns `{}` only when
the file
is **absent or empty**, and raises `_MalformedConfigError` when the file
is
present but not a JSON object. `_write_entry` catches it and returns
`FAILED`
with an actionable message instead of overwriting.

Absent/empty → registers fresh (unchanged); valid → merges, all keys
preserved
(unchanged); present-but-invalid → left untouched.

## Type of Change

- [x] Bug fix (non-breaking change that fixes an issue)

## Changes Made

- `headroom/mcp_registry/opencode.py`: add `_read_json_for_write` +
`_MalformedConfigError`; `_write_entry` uses it and returns `FAILED`
(without writing) when `opencode.json` is present-but-unparseable.
`_read_json` unchanged for read-only callers.
- `tests/test_mcp_registry_opencode.py`: regression tests — register
against malformed configs leaves the bytes untouched and returns
`FAILED`; register against a valid config still merges and preserves
`theme`/`model` plus a pre-existing MCP server.
- `CHANGELOG.md`: Bug Fixes entry under Unreleased.

## Testing

- [x] New tests added for the fixed behavior
- [x] Linting passes (`ruff check`) and formatting is clean (`ruff
format --check`)
- [ ] Full `pytest` deferred to CI (local-OOM reason below).

```text
$ uv run ruff check headroom/mcp_registry/opencode.py tests/test_mcp_registry_opencode.py
All checks passed!
```

## Real Behavior Proof

- Environment: Windows 11, Python 3.12.11, headroom built from this
branch. Importing `headroom` loads the torch/transformers stack; a full
`pytest` gets OOM-killed on this box, so I verified the write-path logic
with a dependency-free script and left the full pytest to CI.
- Exact command / steps: the write-path logic here is identical to the
Claude registrar fix, so I verified it with the same standalone script —
replicated `_read_json_for_write` + the read-modify-write flow (only
stdlib, no `headroom` import) against real temp files, exercising
absent, empty, four malformed variants, and a valid config carrying
unrelated keys.
- Observed result: absent/empty register fresh; every malformed variant
returns FAILED and the on-disk bytes are unchanged (no clobber); a valid
config merges the new server while unrelated keys survive:

```text
OK: absent -> fresh register
OK: empty -> fresh register
OK: malformed -> FAILED, original bytes preserved (no clobber)
OK: valid config -> merged, unrelated keys preserved
MCP CONFIG-WRITE LOGIC VERIFIED
```

- Not tested: driving a real `opencode` install end-to-end (didn't want
to touch a real config); the file-write path is exercised directly by
the regression tests. Full local `pytest` deferred to CI (OOM, per
above).

## 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
- [ ] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [x] I have added tests that prove my fix is effective or that my
feature works
- [ ] New and existing unit tests pass locally with my changes — ran
lint + a standalone logic check; full pytest deferred to CI (local OOM,
disclosed above)
- [x] I have updated the CHANGELOG.md if applicable

## Additional Notes

- Companion to the Claude-registrar fix (same root cause, different
file). No new dependencies. This does not touch OpenCode's
`opencode.jsonc` file-selection (handled elsewhere) — it only hardens
the existing `opencode.json` write against clobbering.
2026-07-11 10:38:43 -05:00
dependabot[bot]
ce3c959eae
deps: update tree-sitter requirement from <0.26,>=0.25.2 to >=0.25.2,<0.27 (#1681)
Updates the requirements on
[tree-sitter](https://github.com/tree-sitter/py-tree-sitter) to permit
the latest version.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/tree-sitter/py-tree-sitter/releases">tree-sitter's
releases</a>.</em></p>
<blockquote>
<h2>v0.26.0</h2>
<h2>What's Changed</h2>
<ul>
<li>ci: use windows-2025 &amp; macos-15-intel runners by <a
href="https://github.com/ObserverOfTime"><code>@​ObserverOfTime</code></a>
in <a
href="https://redirect.github.com/tree-sitter/py-tree-sitter/pull/422">tree-sitter/py-tree-sitter#422</a></li>
<li>ci: bump pypa/cibuildwheel from 3.1 to 3.2 in the actions group by
<a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/tree-sitter/py-tree-sitter/pull/418">tree-sitter/py-tree-sitter#418</a></li>
<li>ci: bump the actions group with 2 updates by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/tree-sitter/py-tree-sitter/pull/424">tree-sitter/py-tree-sitter#424</a></li>
<li>ci: bump pypa/cibuildwheel from 3.2 to 3.3 in the actions group by
<a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/tree-sitter/py-tree-sitter/pull/426">tree-sitter/py-tree-sitter#426</a></li>
<li>ci: bump the actions group across 1 directory with 3 updates by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/tree-sitter/py-tree-sitter/pull/430">tree-sitter/py-tree-sitter#430</a></li>
<li>feat!: update API for tree-sitter 0.26 by <a
href="https://github.com/ObserverOfTime"><code>@​ObserverOfTime</code></a>
in <a
href="https://redirect.github.com/tree-sitter/py-tree-sitter/pull/431">tree-sitter/py-tree-sitter#431</a></li>
<li>Add Python 3.14 to CI workflow matrix by <a
href="https://github.com/cclauss"><code>@​cclauss</code></a> in <a
href="https://redirect.github.com/tree-sitter/py-tree-sitter/pull/434">tree-sitter/py-tree-sitter#434</a></li>
<li>ci: add riscv64 wheels to PyPI release workflow by <a
href="https://github.com/gounthar"><code>@​gounthar</code></a> in <a
href="https://redirect.github.com/tree-sitter/py-tree-sitter/pull/443">tree-sitter/py-tree-sitter#443</a></li>
<li>ci: bump the actions group across 1 directory with 5 updates by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/tree-sitter/py-tree-sitter/pull/446">tree-sitter/py-tree-sitter#446</a></li>
<li>fix type hints for Query properties by <a
href="https://github.com/unawarez"><code>@​unawarez</code></a> in <a
href="https://redirect.github.com/tree-sitter/py-tree-sitter/pull/439">tree-sitter/py-tree-sitter#439</a></li>
<li>build: bump tree_sitter/core from <code>cd4b6e2</code> to
<code>6f2e8a6</code> by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/tree-sitter/py-tree-sitter/pull/447">tree-sitter/py-tree-sitter#447</a></li>
<li>build: bump tree_sitter/core from <code>6f2e8a6</code> to
<code>cd5b087</code> by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/tree-sitter/py-tree-sitter/pull/449">tree-sitter/py-tree-sitter#449</a></li>
<li>build: bump tree-sitter-rust from 0.24.0 to 0.24.1 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/tree-sitter/py-tree-sitter/pull/444">tree-sitter/py-tree-sitter#444</a></li>
<li>ci: bump actions/upload-pages-artifact from 4 to 5 in the actions
group by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/tree-sitter/py-tree-sitter/pull/452">tree-sitter/py-tree-sitter#452</a></li>
<li>build: bump tree_sitter/core from <code>cd5b087</code> to
<code>7f53486</code> by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/tree-sitter/py-tree-sitter/pull/459">tree-sitter/py-tree-sitter#459</a></li>
<li>ci: bump the actions group across 1 directory with 2 updates by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/tree-sitter/py-tree-sitter/pull/463">tree-sitter/py-tree-sitter#463</a></li>
<li>build: bump tree-sitter-rust from 0.24.1 to 0.24.2 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/tree-sitter/py-tree-sitter/pull/453">tree-sitter/py-tree-sitter#453</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a href="https://github.com/cclauss"><code>@​cclauss</code></a> made
their first contribution in <a
href="https://redirect.github.com/tree-sitter/py-tree-sitter/pull/434">tree-sitter/py-tree-sitter#434</a></li>
<li><a href="https://github.com/gounthar"><code>@​gounthar</code></a>
made their first contribution in <a
href="https://redirect.github.com/tree-sitter/py-tree-sitter/pull/443">tree-sitter/py-tree-sitter#443</a></li>
<li><a href="https://github.com/unawarez"><code>@​unawarez</code></a>
made their first contribution in <a
href="https://redirect.github.com/tree-sitter/py-tree-sitter/pull/439">tree-sitter/py-tree-sitter#439</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/tree-sitter/py-tree-sitter/compare/v0.25.2...v0.26.0">https://github.com/tree-sitter/py-tree-sitter/compare/v0.25.2...v0.26.0</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="a9e753ef67"><code>a9e753e</code></a>
ci(pypi): skip riscv64 tests properly</li>
<li><a
href="eeababc529"><code>eeababc</code></a>
chore: release 0.26.0</li>
<li><a
href="dac834eca3"><code>dac834e</code></a>
fix(node): fix reference leak</li>
<li><a
href="bdddb6180d"><code>bdddb61</code></a>
build: bump tree-sitter-rust from 0.24.1 to 0.24.2</li>
<li><a
href="baa5fd8e27"><code>baa5fd8</code></a>
ci: bump the actions group across 1 directory with 2 updates</li>
<li><a
href="c680e3b513"><code>c680e3b</code></a>
build: bump tree_sitter/core from <code>cd5b087</code> to
<code>7f53486</code></li>
<li><a
href="2d3fb3a2a7"><code>2d3fb3a</code></a>
ci: bump actions/upload-pages-artifact from 4 to 5 in the actions
group</li>
<li><a
href="bae0829cea"><code>bae0829</code></a>
build: bump tree-sitter-rust from 0.24.0 to 0.24.1</li>
<li><a
href="d99f79601f"><code>d99f796</code></a>
build: bump tree_sitter/core from <code>6f2e8a6</code> to
<code>cd5b087</code></li>
<li><a
href="a9282df035"><code>a9282df</code></a>
build: bump tree_sitter/core from <code>cd4b6e2</code> to
<code>6f2e8a6</code></li>
<li>Additional commits viewable in <a
href="https://github.com/tree-sitter/py-tree-sitter/compare/v0.25.2...v0.26.0">compare
view</a></li>
</ul>
</details>
<br />


Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-11 10:38:04 -05:00
JoaoMarcos44
0750bbff4d
fix(update): prevent _core.pyd corruption on Windows when proxy is running (#1581)
## Description

On Windows, running `headroom update` while `headroom proxy` is active
can corrupt the installed package by leaving the native `_core.pyd`
extension in a partially upgraded state. This PR adds a safer update
path around the pip invocation.

Closes #1580.

## 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
- [ ] Performance improvement
- [ ] Code refactoring (no functional changes)

## Changes Made

- Add `safe_update()` handling for Windows native-extension update
safety.
- Detect whether `_core.pyd` is locked before pip runs.
- Create a proactive backup when the file is not locked, then restore
atomically if import integrity fails.
- Warn when the proxy is running and `_core.pyd` is locked, allowing pip
to fail safely without replacing the loaded file.
- Use atomic replacement for restore paths.

## Testing

- [x] Unit tests pass
- [x] New tests added for new functionality when applicable
- [x] Manual testing performed

### Test Output

```text
Focused update-path tests and reviewer approval were completed on this PR before the governance body cleanup. The current body update is documentation-only metadata for PR governance.
```

## Real Behavior Proof

- Environment: Windows-focused Headroom development/review context.
- Exact command / steps: Reviewed the safe update flow for locked and
unlocked `_core.pyd` cases, including backup, pip invocation, import
validation, and restore behavior.
- Observed result: The update path avoids replacing a loaded native
extension and provides an atomic restore path when an unlocked update
fails validation.
- Not tested: End-to-end package publication/install from PyPI as part
of this body cleanup.

## Review Readiness

- [x] I have performed a self-review
- [x] This PR is ready for human review

## Additional Notes

This body was normalized by a maintainer after approval so the
governance parser reflects the already-reviewed PR state.

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-11 10:36:23 -05:00
dependabot[bot]
5229c98228
deps: bump prometheus from 0.13.4 to 0.14.0 (#1518)
Bumps [prometheus](https://github.com/tikv/rust-prometheus) from 0.13.4
to 0.14.0.
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/tikv/rust-prometheus/blob/master/CHANGELOG.md">prometheus's
changelog</a>.</em></p>
<blockquote>
<h2>0.14.0</h2>
<ul>
<li>
<p>API change: Use <code>AsRef&lt;str&gt;</code> for owned label values
(<a
href="https://redirect.github.com/tikv/rust-prometheus/issues/537">#537</a>)</p>
</li>
<li>
<p>Improvement: Hashing improvements (<a
href="https://redirect.github.com/tikv/rust-prometheus/issues/532">#532</a>)</p>
</li>
<li>
<p>Dependency upgrade: Update <code>hyper</code> to 1.6 (<a
href="https://redirect.github.com/tikv/rust-prometheus/issues/524">#524</a>)</p>
</li>
<li>
<p>Dependency upgrade: Update <code>procfs</code> to 0.17 (<a
href="https://redirect.github.com/tikv/rust-prometheus/issues/543">#543</a>)</p>
</li>
<li>
<p>Dependency upgrade: Update <code>protobuf</code> to 3.7.2 for
RUSTSEC-2024-0437 (<a
href="https://redirect.github.com/tikv/rust-prometheus/issues/541">#541</a>)</p>
</li>
<li>
<p>Dependency upgrade: Update <code>thiserror</code> to 2.0 (<a
href="https://redirect.github.com/tikv/rust-prometheus/issues/534">#534</a>)</p>
</li>
<li>
<p>Internal change: Fix LSP and Clippy warnings (<a
href="https://redirect.github.com/tikv/rust-prometheus/issues/540">#540</a>)</p>
</li>
<li>
<p>Internal change: Bump MSRV to 1.81 (<a
href="https://redirect.github.com/tikv/rust-prometheus/issues/539">#539</a>)</p>
</li>
<li>
<p>Documentation: Fix <code>register_histogram_vec_with_registry</code>
docstring (<a
href="https://redirect.github.com/tikv/rust-prometheus/issues/528">#528</a>)</p>
</li>
<li>
<p>Documentation: Fix typos in static-metric docstrings (<a
href="https://redirect.github.com/tikv/rust-prometheus/issues/479">#479</a>)</p>
</li>
<li>
<p>Documentation: Add missing <code>protobuf</code> feature to README
list (<a
href="https://redirect.github.com/tikv/rust-prometheus/issues/531">#531</a>)</p>
</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="e07efb4f37"><code>e07efb4</code></a>
prometheus: release 0.14.0 (<a
href="https://redirect.github.com/tikv/rust-prometheus/issues/545">#545</a>)</li>
<li><a
href="26e46ec03a"><code>26e46ec</code></a>
Hashing improvements (<a
href="https://redirect.github.com/tikv/rust-prometheus/issues/532">#532</a>)</li>
<li><a
href="e17c5ced2b"><code>e17c5ce</code></a>
build(deps): update procfs requirement from ^0.16 to ^0.17 (<a
href="https://redirect.github.com/tikv/rust-prometheus/issues/543">#543</a>)</li>
<li><a
href="e5809b7ab9"><code>e5809b7</code></a>
build(deps): update hyper requirement from ^0.14 to ^1.4 (<a
href="https://redirect.github.com/tikv/rust-prometheus/issues/524">#524</a>)</li>
<li><a
href="4a0e282888"><code>4a0e282</code></a>
Use AsRef&lt;str&gt; for owned label values (<a
href="https://redirect.github.com/tikv/rust-prometheus/issues/537">#537</a>)</li>
<li><a
href="c3865f3c40"><code>c3865f3</code></a>
cargo: upgrade to protobuf 3.7 (<a
href="https://redirect.github.com/tikv/rust-prometheus/issues/541">#541</a>)</li>
<li><a
href="7e4e6f2d33"><code>7e4e6f2</code></a>
docs: fix <code>register_histogram_vec_with_registry</code> docstring
(<a
href="https://redirect.github.com/tikv/rust-prometheus/issues/528">#528</a>)</li>
<li><a
href="5b62f4b78b"><code>5b62f4b</code></a>
Fix LSP and Clippy warnings and errors (<a
href="https://redirect.github.com/tikv/rust-prometheus/issues/540">#540</a>)</li>
<li><a
href="52d76fc2d8"><code>52d76fc</code></a>
cargo: bump MSRV to 1.81 (<a
href="https://redirect.github.com/tikv/rust-prometheus/issues/539">#539</a>)</li>
<li><a
href="3bd0e82f1f"><code>3bd0e82</code></a>
Upgrade <code>thiserror</code> crate from 1.0 to 2.0 version (<a
href="https://redirect.github.com/tikv/rust-prometheus/issues/534">#534</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/tikv/rust-prometheus/compare/v0.13.4...v0.14.0">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=prometheus&package-manager=cargo&previous-version=0.13.4&new-version=0.14.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-11 10:33:54 -05:00
dependabot[bot]
e448d7ba4d
deps: bump thiserror from 1.0.69 to 2.0.18 (#1519)
Bumps [thiserror](https://github.com/dtolnay/thiserror) from 1.0.69 to
2.0.18.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/dtolnay/thiserror/releases">thiserror's
releases</a>.</em></p>
<blockquote>
<h2>2.0.18</h2>
<ul>
<li>Make compatible with project-level <code>needless_lifetimes =
&quot;forbid&quot;</code> (<a
href="https://redirect.github.com/dtolnay/thiserror/issues/443">#443</a>,
thanks <a
href="https://github.com/LucaCappelletti94"><code>@​LucaCappelletti94</code></a>)</li>
</ul>
<h2>2.0.17</h2>
<ul>
<li>Use differently named __private module per patch release (<a
href="https://redirect.github.com/dtolnay/thiserror/issues/434">#434</a>)</li>
</ul>
<h2>2.0.16</h2>
<ul>
<li>Add to &quot;no-std&quot; crates.io category (<a
href="https://redirect.github.com/dtolnay/thiserror/issues/429">#429</a>)</li>
</ul>
<h2>2.0.15</h2>
<ul>
<li>Prevent <code>Error::provide</code> API becoming unavailable from a
future new compiler lint (<a
href="https://redirect.github.com/dtolnay/thiserror/issues/427">#427</a>)</li>
</ul>
<h2>2.0.14</h2>
<ul>
<li>Allow build-script cleanup failure with NFSv3 output directory to be
non-fatal (<a
href="https://redirect.github.com/dtolnay/thiserror/issues/426">#426</a>)</li>
</ul>
<h2>2.0.13</h2>
<ul>
<li>Documentation improvements</li>
</ul>
<h2>2.0.12</h2>
<ul>
<li>Prevent elidable_lifetime_names pedantic clippy lint in generated
impl (<a
href="https://redirect.github.com/dtolnay/thiserror/issues/413">#413</a>)</li>
</ul>
<h2>2.0.11</h2>
<ul>
<li>Add feature gate to tests that use std (<a
href="https://redirect.github.com/dtolnay/thiserror/issues/409">#409</a>,
<a
href="https://redirect.github.com/dtolnay/thiserror/issues/410">#410</a>,
thanks <a
href="https://github.com/Maytha8"><code>@​Maytha8</code></a>)</li>
</ul>
<h2>2.0.10</h2>
<ul>
<li>Support errors containing a generic type parameter's associated type
in a field (<a
href="https://redirect.github.com/dtolnay/thiserror/issues/408">#408</a>)</li>
</ul>
<h2>2.0.9</h2>
<ul>
<li>Work around <code>missing_inline_in_public_items</code> clippy
restriction being triggered in macro-generated code (<a
href="https://redirect.github.com/dtolnay/thiserror/issues/404">#404</a>)</li>
</ul>
<h2>2.0.8</h2>
<ul>
<li>Improve support for macro-generated <code>derive(Error)</code> call
sites (<a
href="https://redirect.github.com/dtolnay/thiserror/issues/399">#399</a>)</li>
</ul>
<h2>2.0.7</h2>
<ul>
<li>Work around conflict with #[deny(clippy::allow_attributes)] (<a
href="https://redirect.github.com/dtolnay/thiserror/issues/397">#397</a>,
thanks <a
href="https://github.com/zertosh"><code>@​zertosh</code></a>)</li>
</ul>
<h2>2.0.6</h2>
<ul>
<li>Suppress deprecation warning on generated From impls (<a
href="https://redirect.github.com/dtolnay/thiserror/issues/396">#396</a>)</li>
</ul>
<h2>2.0.5</h2>
<ul>
<li>Prevent deprecation warning on generated impl for deprecated type
(<a
href="https://redirect.github.com/dtolnay/thiserror/issues/394">#394</a>)</li>
</ul>
<h2>2.0.4</h2>
<ul>
<li>Eliminate needless_lifetimes clippy lint in generated
<code>From</code> impls (<a
href="https://redirect.github.com/dtolnay/thiserror/issues/391">#391</a>,
thanks <a
href="https://github.com/matt-phylum"><code>@​matt-phylum</code></a>)</li>
</ul>
<h2>2.0.3</h2>
<ul>
<li>Support the same Path field being repeated in both Debug and Display
representation in error message (<a
href="https://redirect.github.com/dtolnay/thiserror/issues/383">#383</a>)</li>
<li>Improve error message when a format trait used in error message is
not implemented by some field (<a
href="https://redirect.github.com/dtolnay/thiserror/issues/384">#384</a>)</li>
</ul>
<h2>2.0.2</h2>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="dc0f6a23a3"><code>dc0f6a2</code></a>
Release 2.0.18</li>
<li><a
href="0275292735"><code>0275292</code></a>
Touch up PR 443</li>
<li><a
href="3c33bc60ad"><code>3c33bc6</code></a>
Merge pull request <a
href="https://redirect.github.com/dtolnay/thiserror/issues/443">#443</a>
from LucaCappelletti94/master</li>
<li><a
href="995939cc2e"><code>995939c</code></a>
Reproduce issue 442</li>
<li><a
href="21653d1d33"><code>21653d1</code></a>
Made clippy lifetime allows conditional</li>
<li><a
href="45e5388009"><code>45e5388</code></a>
Update actions/upload-artifact@v5 -&gt; v6</li>
<li><a
href="386aac126a"><code>386aac1</code></a>
Update actions/upload-artifact@v4 -&gt; v5</li>
<li><a
href="ec50561375"><code>ec50561</code></a>
Update actions/checkout@v5 -&gt; v6</li>
<li><a
href="247eab5d79"><code>247eab5</code></a>
Update name of empty_enum clippy lint</li>
<li><a
href="91b181f089"><code>91b181f</code></a>
Raise required compiler to Rust 1.68</li>
<li>Additional commits viewable in <a
href="https://github.com/dtolnay/thiserror/compare/1.0.69...2.0.18">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=thiserror&package-manager=cargo&previous-version=1.0.69&new-version=2.0.18)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-11 10:33:38 -05:00
dependabot[bot]
98f7f1c2a3
deps: bump tower-http from 0.6.11 to 0.7.0 (#1520)
Bumps [tower-http](https://github.com/tower-rs/tower-http) from 0.6.11
to 0.7.0.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/tower-rs/tower-http/releases">tower-http's
releases</a>.</em></p>
<blockquote>
<h2>tower-http-0.7.0</h2>
<p><a
href="https://github.com/tower-rs/tower-http/compare/tower-http-0.6.11...tower-http-0.7.0">Changes
since 0.6.11</a></p>
<h2>Added</h2>
<ul>
<li>
<p><code>csrf</code>: add cross-site request forgery (CSRF) protection
middleware, porting the cross-origin protection scheme introduced in Go
1.25 (<a
href="https://redirect.github.com/tower-rs/tower-http/issues/699">#699</a>)</p>
<pre lang="rust"><code>use tower::ServiceBuilder;
use tower_http::csrf::CsrfLayer;
<p>// Rejects cross-origin state-changing requests using
<code>Sec-Fetch-Site</code>,<br />
// an <code>Origin</code> allow-list, and an
<code>Origin</code>/<code>Host</code> fallback. No per-request<br />
// token state required.<br />
let layer = CsrfLayer::new().add_trusted_origin(&quot;<a
href="https://example.com">https://example.com</a>&quot;)?;</p>
<p>let service =
ServiceBuilder::new().layer(layer).service_fn(handler);<br />
</code></pre></p>
</li>
<li>
<p><code>timeout</code>: add <code>DeadlineBody</code> for non-resetting
body timeouts, applied via the new <code>RequestBodyDeadlineLayer</code>
and <code>ResponseBodyDeadlineLayer</code> (<a
href="https://redirect.github.com/tower-rs/tower-http/issues/688">#688</a>)</p>
<p>Unlike <code>TimeoutBody</code>, which resets its deadline on every
frame, <code>DeadlineBody</code> caps the total time of a body transfer.
A slow client trickling one byte at a time never trips an idle timeout
but will trip a deadline.</p>
<pre lang="rust"><code>use std::time::Duration;
use tower::ServiceBuilder;
use tower_http::timeout::RequestBodyDeadlineLayer;
<p>// Abort the request body transfer after 30s total, regardless of
how<br />
// frequently data arrives.<br />
let service = ServiceBuilder::new()<br />
.layer(RequestBodyDeadlineLayer::new(Duration::from_secs(30)))<br />
.service_fn(handler);<br />
</code></pre></p>
</li>
<li>
<p><code>fs</code>: add strong <code>ETag</code> support to
<code>ServeDir</code>, including <code>If-Match</code> and
<code>If-None-Match</code> precondition handling per RFC 9110. <code>304
Not Modified</code> responses now carry the <code>ETag</code> and
<code>Last-Modified</code> validators (<a
href="https://redirect.github.com/tower-rs/tower-http/issues/691">#691</a>)</p>
</li>
<li>
<p><code>fs</code>: add a <code>Backend</code> trait to make
<code>ServeDir</code> work with non-filesystem sources (e.g. embedded
assets or object storage). The default <code>TokioBackend</code>
preserves existing behavior. Use <code>ServeDir::with_backend()</code>
to plug in custom implementations (<a
href="https://redirect.github.com/tower-rs/tower-http/issues/684">#684</a>)</p>
<pre lang="rust"><code>use tower_http::services::fs::ServeDir;
<p>// <code>MyBackend</code> implements
<code>tower_http::services::fs::Backend</code>.<br />
// The default <code>ServeDir::new()</code> continues to use
<code>TokioBackend</code> (local FS).<br />
let service = ServeDir::with_backend(&quot;assets&quot;,
MyBackend::new());<br />
</code></pre></p>
</li>
<li>
<p><code>fs</code>: add <code>html_as_default_extension</code> option to
<code>ServeDir</code>, appending <code>.html</code> when the request
path has no extension (<a
href="https://redirect.github.com/tower-rs/tower-http/issues/519">#519</a>)</p>
</li>
<li>
<p><code>fs</code>: add <code>redirect_path_prefix</code> option to
<code>ServeDir</code>, prepending a prefix on trailing-slash redirects
so the service can be mounted under a sub-path (<a
href="https://redirect.github.com/tower-rs/tower-http/issues/486">#486</a>)</p>
</li>
<li>
<p><code>validate-request</code>: add
<code>ValidateRequestHeaderLayer::has_header_value()</code> to reject
requests when a header does not have an expected value (<a
href="https://redirect.github.com/tower-rs/tower-http/issues/360">#360</a>)</p>
</li>
<li>
<p><code>body</code>: <code>UnsyncBoxBody::new()</code> constructor and
<code>From&lt;ServeFileSystemResponseBody&gt;</code> conversion to avoid
double-boxing when combining <code>ServeDir</code> responses with other
body types (<a
href="https://redirect.github.com/tower-rs/tower-http/issues/537">#537</a>)</p>
</li>
<li>
<p><code>limit</code>: implement <code>Default</code> for
<code>limit::ResponseBody</code> when the wrapped body also implements
<code>Default</code> (<a
href="https://redirect.github.com/tower-rs/tower-http/issues/679">#679</a>)</p>
</li>
</ul>
<h2>Changed</h2>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="b194fcfef3"><code>b194fcf</code></a>
v0.7.0</li>
<li><a
href="af828a6ec9"><code>af828a6</code></a>
feat(follow_redirect)!: preserve request extensions across redirects (<a
href="https://redirect.github.com/tower-rs/tower-http/issues/706">#706</a>)</li>
<li><a
href="8cb8d99a84"><code>8cb8d99</code></a>
feat(ValidateRequestHeaderLayer): add
has_header(&quot;...&quot;).with_value(&quot;...&quot;) fun...</li>
<li><a
href="3b56d2d2e8"><code>3b56d2d</code></a>
feat!: Add configurable Backend trait for ServeDir, bump MSRV 1.65 (<a
href="https://redirect.github.com/tower-rs/tower-http/issues/684">#684</a>)</li>
<li><a
href="8508716431"><code>8508716</code></a>
Add <code>redirect_path_prefix</code> option (<a
href="https://redirect.github.com/tower-rs/tower-http/issues/486">#486</a>)</li>
<li><a
href="56327b27f4"><code>56327b2</code></a>
Add Windows drive-prefix path regression test (<a
href="https://redirect.github.com/tower-rs/tower-http/issues/705">#705</a>)</li>
<li><a
href="54c6db8590"><code>54c6db8</code></a>
feat(compression)!: upgrade SizeAbove threshold from u16 to u64 (<a
href="https://redirect.github.com/tower-rs/tower-http/issues/704">#704</a>)</li>
<li><a
href="68cd6d8f3c"><code>68cd6d8</code></a>
Add DeadlineBody for non-resetting body timeouts (<a
href="https://redirect.github.com/tower-rs/tower-http/issues/688">#688</a>)</li>
<li><a
href="fa8a98cb3e"><code>fa8a98c</code></a>
feat(fs): add strong ETag support to ServeDir (<a
href="https://redirect.github.com/tower-rs/tower-http/issues/691">#691</a>)</li>
<li><a
href="36d2205eb6"><code>36d2205</code></a>
fix: Make SetMultiple*Header Clone for !Clone http bodies (<a
href="https://redirect.github.com/tower-rs/tower-http/issues/703">#703</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/tower-rs/tower-http/compare/tower-http-0.6.11...tower-http-0.7.0">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=tower-http&package-manager=cargo&previous-version=0.6.11&new-version=0.7.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-11 10:33:28 -05:00
dependabot[bot]
6c705b4066
deps: bump toml from 0.8.23 to 1.1.2+spec-1.1.0 (#1517)
Bumps [toml](https://github.com/toml-rs/toml) from 0.8.23 to
1.1.2+spec-1.1.0.
<details>
<summary>Commits</summary>
<ul>
<li><a
href="a3d0047c95"><code>a3d0047</code></a>
chore: Release</li>
<li><a
href="cc37615fc8"><code>cc37615</code></a>
docs: Update changelog</li>
<li><a
href="7f5e9e130a"><code>7f5e9e1</code></a>
fix(parser): Consolidate invalid unquoted key into one error (<a
href="https://redirect.github.com/toml-rs/toml/issues/1138">#1138</a>)</li>
<li><a
href="52feb9070c"><code>52feb90</code></a>
fix(parser): Consolidate invalid unquoted key into one error</li>
<li><a
href="aad85d4921"><code>aad85d4</code></a>
chore(deps): Update j178/prek-action action to v2 (<a
href="https://redirect.github.com/toml-rs/toml/issues/1136">#1136</a>)</li>
<li><a
href="8b1ac44bca"><code>8b1ac44</code></a>
chore(deps): Update compatible (dev) (<a
href="https://redirect.github.com/toml-rs/toml/issues/1135">#1135</a>)</li>
<li><a
href="9effd79ff2"><code>9effd79</code></a>
chore(deps): Update j178/prek-action action to v2</li>
<li><a
href="9db8aad6ea"><code>9db8aad</code></a>
chore: Release</li>
<li><a
href="e55a6633d9"><code>e55a663</code></a>
docs: Update changelog</li>
<li><a
href="c11d7d7ad3"><code>c11d7d7</code></a>
Optimisations (<a
href="https://redirect.github.com/toml-rs/toml/issues/1133">#1133</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/toml-rs/toml/compare/toml-v0.8.23...toml-v1.1.2">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=toml&package-manager=cargo&previous-version=0.8.23&new-version=1.1.2+spec-1.1.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-11 10:32:53 -05:00
JD Davis
98842b847b
Extract loop callback failure policy (#1977)
## Description

Extracts the event-loop callback failure classifier for a known
WebSocket disconnect regression from `server.py` into
`headroom.proxy.loop_callback_failure_policy`. The server keeps
`_is_known_websocket_callback_failure` as a compatibility alias for the
existing loop-health path.

Closes #

## Type of Change

- [ ] 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
- [ ] Performance improvement
- [x] Code refactoring (no functional changes)

## Changes Made

- Added `loop_callback_failure_policy.py` with constants for the known
message and exception shape.
- Replaced the inline server helper body with a compatibility alias to
the extracted classifier.
- Added direct classifier tests and ran the existing loop-health
regression tests.
- Carried forward the LiteLLM callback compatibility shim needed for
current mypy on `main`.

## Testing

- [x] Unit tests pass (`pytest`)
- [x] Linting passes (`ruff check .`)
- [x] Type checking passes (`mypy headroom`)
- [x] New tests added for new functionality
- [ ] Manual testing performed

### Test Output

```text
python -m pytest tests\test_loop_callback_failure_policy.py tests\test_proxy_loop_exception_health.py
5 passed in 4.89s

python -m ruff check .
All checks passed!

python -m ruff format --check .
1095 files already formatted

python -m mypy headroom --ignore-missing-imports
Success: no issues found in 409 source files

gitleaks protect --staged --no-banner --redact
no leaks found
```

## Real Behavior Proof

- Environment: Windows, Python 3.13.13, branch
`jd/architecture-slice-27`.
- Exact command / steps: ran new loop callback classifier tests,
existing loop-health tests, ruff, ruff format check, mypy, and staged
gitleaks scan.
- Observed result: classifier behavior and endpoint-level loop-health
behavior remain covered; local lint/type/security checks pass.
- Not tested: live WebSocket disconnect reproduction; this slice
preserves the existing server alias.

## 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
- [ ] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [x] I have added tests that prove my fix is effective or that my
feature works
- [x] New and existing unit tests pass locally with my changes
- [ ] I have updated the CHANGELOG.md if applicable

## Screenshots (if applicable)

N/A

## Additional Notes

Documentation and changelog updates are N/A for this internal
architecture-only refactor. The push reported existing default-branch
Dependabot alerts; no staged secret leaks were found for this PR.
2026-07-11 10:27:00 -05:00
JD Davis
b5b59bcd77
Extract project name policy (#1974)
## Description

Extracts project-name normalization for proxy attribution from
`savings_tracker.py` into `headroom.proxy.project_name_policy`.
`savings_tracker.sanitize_project_name` and `PROJECT_NAME_MAX_LENGTH`
remain compatibility aliases for existing callers.

Closes #

## Type of Change

- [ ] 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
- [ ] Performance improvement
- [x] Code refactoring (no functional changes)

## Changes Made

- Added `project_name_policy.py` for project-name decoding,
printable-character filtering, trimming, and length capping.
- Kept `savings_tracker` compatibility aliases for existing imports and
project-context callers.
- Added focused policy tests plus re-export coverage.
- Carried forward the LiteLLM callback compatibility shim needed for
current mypy on `main`.

## Testing

- [x] Unit tests pass (`pytest`)
- [x] Linting passes (`ruff check .`)
- [x] Type checking passes (`mypy headroom`)
- [x] New tests added for new functionality
- [ ] Manual testing performed

### Test Output

```text
python -m pytest tests\test_project_name_policy.py tests\test_proxy_project_savings.py
20 passed in 17.05s

python -m ruff check .
All checks passed!

python -m ruff format --check .
1095 files already formatted

python -m mypy headroom --ignore-missing-imports
Success: no issues found in 409 source files

gitleaks protect --staged --no-banner --redact
no leaks found
```

## Real Behavior Proof

- Environment: Windows, Python 3.13.13, branch
`jd/architecture-slice-26`.
- Exact command / steps: ran new project-name policy tests, existing
project savings tests, ruff, ruff format check, mypy, and staged
gitleaks scan.
- Observed result: project attribution/savings behavior remains covered
and local lint/type/security checks pass.
- Not tested: full proxy runtime; this slice preserves existing
`savings_tracker` imports.

## 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
- [ ] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [x] I have added tests that prove my fix is effective or that my
feature works
- [x] New and existing unit tests pass locally with my changes
- [ ] I have updated the CHANGELOG.md if applicable

## Screenshots (if applicable)

N/A

## Additional Notes

Documentation and changelog updates are N/A for this internal
architecture-only refactor. The push reported existing default-branch
Dependabot alerts; no staged secret leaks were found for this PR.
2026-07-11 10:26:20 -05:00
JD Davis
2b09ecea76
refactor(proxy): isolate image compression policy (#1958)
## Description

Extracts image-compression gating and tag stamping into a pure policy
module while preserving the public `ImageCompressionDecision.decide` API
used by handlers. This keeps the frozen decision value type separate
from the canonical precedence rules it wraps.

Closes #

## Type of Change

- [ ] 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
- [ ] Performance improvement
- [x] Code refactoring (no functional changes)

## Changes Made

- Added `headroom.proxy.image_compression_policy` for pure
image-compression precedence and tag stamping helpers.
- Updated `ImageCompressionDecision.decide` and
`ImageCompressionDecision.apply_to_tags` to delegate to the extracted
policy.
- Added direct tests for the extracted policy boundary.
- Kept the LiteLLM callback compatibility shim required for repo-wide
type checking on fresh branches.

## Testing

- [x] Unit tests pass (`pytest`)
- [x] Linting passes (`ruff check .`)
- [x] Type checking passes (`mypy headroom`)
- [x] New tests added for new functionality
- [ ] Manual testing performed

### Test Output

```text
python -m pytest tests/test_image_compression_policy.py tests/test_image_compression_decision.py tests/test_handler_outcome_tag_invariant.py tests/test_litellm_callback.py tests/test_compress_api.py::TestLiteLLMCallback -q
34 passed in 6.77s

python -m ruff check .
All checks passed!

python -m ruff format --check .
1095 files already formatted

python -m mypy headroom --ignore-missing-imports
Success: no issues found in 409 source files

gitleaks protect --staged --no-banner --redact
no leaks found
```

## Real Behavior Proof

- Environment: Windows, Python 3.13.13, local worktree based on
`headroomlabs/main`.
- Exact command / steps: ran focused image compression policy/decision
tests, handler outcome tag invariant tests, LiteLLM callback tests, Ruff
lint/format checks, mypy over `headroom`, and staged gitleaks scan.
- Observed result: all local checks passed; staged secret scan found no
leaks.
- Not tested: full CI matrix and deployment flows; those are covered by
GitHub Actions.

## 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
- [ ] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [x] I have added tests that prove my fix is effective or that my
feature works
- [x] New and existing unit tests pass locally with my changes
- [ ] I have updated the CHANGELOG.md if applicable

## Screenshots (if applicable)

N/A

## Additional Notes

Documentation and changelog updates are not applicable for this internal
refactor. GitHub reported existing Dependabot alerts on the default
branch during push; this PR does not change dependencies, and the staged
secret scan is clean.
2026-07-11 10:25:15 -05:00
GUOHAO LIU
9db8a6bbf6
fix(proxy): handle ClientDisconnect in passthrough body reads (#2033)
## Description

Catch `starlette.requests.ClientDisconnect` when reading request bodies
in passthrough/forwarding handlers. Closes #2019

Without this, a client that disconnects mid-request causes an unhandled
`ClientDisconnect` to propagate through the entire middleware stack,
crashing the ASGI TaskGroup and contributing to proxy instability over
long sessions (memory growth, freeze, unresponsive to SIGTERM).

**Adversarial review uncovered 3 additional unprotected sites** in
`proxy_routes.py` — same pattern (body read before try/except). Now
fixed.

## Type of Change

- [x] Bug fix (non-breaking change which fixes an issue)

## Changes Made

**Proxy handlers** (6 sites, first commit):
- openai `handle_passthrough`: wrap `await request.body()` in try/except
ClientDisconnect (main crash site)
- openai `_handle_streaming_passthrough`: same protection
- anthropic batch passthrough: same protection
- batch `_google_batch_passthrough`: same protection
- batch `handle_google_batch_passthrough`: same protection
- bedrock fallback-forward path: early-return on ClientDisconnect
instead of attempting verbatim forward

**Proxy routes** (3 sites, second commit — found by adversarial design
scan):
- `_handle_chatgpt_model_metadata` (proxy_routes.py:398)
- `_handle_chatgpt_codex_images` (proxy_routes.py:438)
- `openai_responses_sub` nested handler (proxy_routes.py:597)

All nine sites return HTTP 204 on disconnect to allow the request to
terminate cleanly.

## Testing

- [x] **Existing tests**: 34/34 pass in `test_proxy_handler_helpers.py`
- [x] **Unit tests**: 2 new tests — passthrough + streaming passthrough
disconnect
- [x] **Adversarial concurrency**: 50 threads × 10 iterations = 500
concurrent disconnect requests — zero crashes, all return 204
- [x] **Adversarial edge cases**: minimal request state, regression
check (normal request path unaffected)
- [x] **PBT (Hypothesis)**: 250 random method/path combinations, 3
properties verified:
  - All disconnect requests return 204
  - ClientDisconnect never leaks out of handler
  - Response is always valid HTTP 2xx

```text
# Unit tests
tests/test_proxy_handler_helpers.py::test_handle_passthrough_client_disconnect PASSED
tests/test_proxy_handler_helpers.py::test_handle_streaming_passthrough_client_disconnect PASSED

# PBT (3 properties × 100-250 examples each)
/tmp/pbt_client_disconnect.py::test_disconnect_always_returns_204 PASSED
/tmp/pbt_client_disconnect.py::test_disconnect_does_not_crash_asgi PASSED
/tmp/pbt_client_disconnect.py::test_response_is_valid_http PASSED

# Adversarial
/tmp/adversarial_client_disconnect.py → 500 concurrent requests: 0 errors, all 204
```

- [x] `ruff check` and `ruff format --check` pass on all changed files

## Real Behavior Proof

- Environment: Linux, Python 3.12, headroom main @ a617455
- Exact command / steps: 
  - `uv run pytest tests/test_proxy_handler_helpers.py -v` — 34 passed
- `uv run python /tmp/adversarial_client_disconnect.py` — 500
concurrent, 0 errors
- `uv run python /tmp/pbt_client_disconnect.py` — 250 random inputs, 3/3
properties hold
- `uv run ruff check . && uv run ruff format --check .` — All checks
passed
- Observed result: ClientDisconnect caught gracefully at all 9 sites,
204 returned, no ExceptionGroup crash, no data corruption
- Not tested: Full E2E with real client disconnect (requires integration
test infrastructure). Manual confirmation from issue reporter would
validate the real-world fix.

## Review Readiness

- [x] I have performed a self-review
- [x] This PR is ready for human review

---------

Co-authored-by: lennney <lennney@users.noreply.github.com>
2026-07-11 10:22:05 -05:00
Ben Younes
fd0d29c92d
fix(packaging): guard torch extras on intel macos (#2011)
## Description

Closes #1931

Guard the `ml` and `voice` `torch` optional dependencies on macOS x86_64
so `headroom-ai[all]` remains resolvable on Intel Macs where PyTorch
does not publish compatible wheels for this version floor. The lockfile
metadata is updated with the same markers.

## Type of Change

- [x] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change that adds functionality)
- [ ] Breaking change (fix or feature that would cause existing
functionality to not work as expected)
- [ ] Documentation update
- [ ] Refactoring
- [ ] Performance improvement
- [ ] Test update
- [ ] Other

## Changes Made

- Added macOS x86_64 environment markers to `torch` in the `ml` and
`voice` extras.
- Updated `uv.lock` optional dependency metadata to match the guarded
extras.
- Added a packaging regression test that checks `[all]` keeps `ml` and
`voice` while guarding `torch` on macOS x86_64.

## Testing

- [x] Unit tests pass (`pytest`)
- [x] Linting passes (`ruff check`)
- [x] Formatting verified (`ruff format --check`)
- [ ] Manual testing performed

### Test Output

```text
$ python3 -m pytest tests/test_optional_dependencies.py -q
collected 1 item

tests/test_optional_dependencies.py .                                    [100%]

============================== 1 passed in 0.26s ===============================

$ .venv/bin/ruff check tests/test_optional_dependencies.py
All checks passed!

$ .venv/bin/ruff format --check tests/test_optional_dependencies.py pyproject.toml
1 file already formatted
```

## Test verification (RED -> GREEN)

RED, with the `torch` markers temporarily removed from `pyproject.toml`:

```text
tests/test_optional_dependencies.py F                                    [100%]
FAILED tests/test_optional_dependencies.py::test_all_extra_does_not_require_torch_on_macos_x86_64
E   assert False
```

GREEN, with this patch applied:

```text
tests/test_optional_dependencies.py .                                    [100%]
============================== 1 passed in 0.26s ===============================
```

## Real Behavior Proof

- Environment: Linux, Python 3.12.3, pytest 9.1.1, ruff 0.14.14.
- Exact command / steps: Removed the environment markers from `torch`,
ran the new packaging test, restored the markers, and reran the test
plus targeted ruff checks.
- Observed result: The test fails without the macOS x86_64 guard and
passes once the `ml` and `voice` `torch` requirements are guarded.
- Not tested: Full `uv run pytest`, full-project `uv run ruff check .`,
full-project `uv run ruff format --check .`, and `uv run mypy headroom`
were not run locally for this targeted packaging change.

## Review Readiness

- [x] I have performed a self-review
- [x] This PR is ready for human review

## Checklist

- [x] My code follows the style guidelines of this project
- [x] I have performed a self-review of my code
- [x] I have added tests that prove my fix is effective
- [x] New and existing targeted tests pass locally with my changes
- [x] Any dependent changes have been merged and published in downstream
modules

## Screenshots (if applicable)

N/A

## Additional Notes

No new dependency is added; this only narrows when the existing `torch`
optional dependency is selected.
2026-07-11 10:20:33 -05:00
Rod Boev
f536aa0801
fix(wrap): keep Claude context-tool setup explicit (#1999)
## Description

`headroom wrap claude` currently installs RTK's global Claude hook and
instruction imports on a flag-free launch, even though the wrapped
session already routes through Headroom's proxy. The wrapper now
requires an explicit Claude context-tool opt-in before it runs the
existing RTK or lean-ctx setup path. Existing negative flags remain
accepted, and other wrapped agents keep their current behavior.

Closes #1915

## 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
- [ ] Performance improvement
- [ ] Code refactoring (no functional changes)

## Changes Made

- Made Claude context-tool installation explicit instead of running it
on every default wrap.
- Preserved the existing RTK and lean-ctx installers behind the positive
opt-in.
- Kept `--no-context-tool` and `--no-rtk` compatible and left other
agent wrappers unchanged.
- Added focused command-parser coverage for default, opt-in, selector,
and negative-space behavior.
- Documented the changed default and opt-in command in `CHANGELOG.md`.

## Testing

- [x] Unit tests pass (`uv run --no-project pytest
tests/test_cli/test_wrap_helpers.py -q`)
- [x] Linting passes (`uv run --no-project ruff check
headroom/cli/wrap.py tests/test_cli/test_wrap_helpers.py`)
- [ ] Type checking passes (`uv run mypy headroom`)
- [x] New tests added for new functionality when applicable
- [ ] Manual testing performed

### Test Output

```text
uv run --no-project pytest tests/test_cli/test_wrap_helpers.py -q
65 passed

uv run --no-project ruff check headroom/cli/wrap.py tests/test_cli/test_wrap_helpers.py
All checks passed

uv run --no-project ruff format --check headroom/cli/wrap.py tests/test_cli/test_wrap_helpers.py
2 files already formatted
```

## Real Behavior Proof

- Environment: isolated HOME on Linux or macOS, Python 3.12+, Claude CLI
available.
- Exact command / steps: run `headroom wrap claude --prepare-only`
without a context-tool flag, inspect the isolated Claude config, then
repeat with the explicit context-tool opt-in.
- Observed result: the focused Click harness now proves the default run
creates no RTK setup calls, the explicit opt-in performs the existing
RTK setup, `--no-context-tool` still wins if both flags are present, and
Copilot still keeps its default context-tool behavior.
- Not tested: a live `headroom wrap claude` run against a real Claude
installation and a real RTK or lean-ctx hook write on this host.
- Scope: Claude context-tool activation and global configuration
artifacts.

## 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
- [ ] I have commented my code, particularly in hard-to-understand areas
- [x] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [x] I have added tests that prove my fix is effective or that my
feature works
- [x] New and existing unit tests pass locally with my changes
- [x] I have updated the CHANGELOG.md if applicable

## Screenshots (if applicable)

N/A

## Additional Notes

The exact project-bound `uv sync --extra dev` flow was blocked on this
host by a `rustup.exe` access error, so the focused checks used `uv run
--no-project` against the existing environment. This PR does not change
RTK installation internals, proxy compression, or context-tool defaults
for other agents.
2026-07-11 10:18:57 -05:00
JD Davis
e92c253977
refactor(proxy): extract ccr session tracker (#2003)
## Description

Extracts the sticky CCR session tracker from `headroom.proxy.helpers`
into a focused state module. `helpers.SessionCcrTracker` remains as an
env-aware compatibility wrapper so existing CCR tool injection and
singleton call sites keep the same API.

Closes #

## Type of Change

- [ ] 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
- [ ] Performance improvement
- [x] Code refactoring (no functional changes)

## Changes Made

- Added `headroom.proxy.ccr_session_tracker.SessionCcrTracker` as the
pure bounded LRU CCR state holder.
- Replaced the in-helper CCR tracker implementation with a small
env-aware wrapper.
- Added direct tracker tests for unknown sessions, monotonic done state,
first-write golden bytes, provider isolation, LRU eviction, reset, and
input validation.

## Testing

- [x] Unit tests pass (`pytest`)
- [x] Linting passes (`ruff check .`)
- [x] Type checking passes (`mypy headroom`)
- [x] New tests added for new functionality
- [ ] Manual testing performed

### Test Output

```text
python -m pytest tests/test_ccr_session_tracker.py tests/test_ccr_tool_always_on.py tests/test_corrupt_golden_bytes_recovery.py tests/test_issue_728_empty_tools_injection.py
35 passed in 0.69s

python -m ruff check .
All checks passed!

python -m ruff format --check .
1069 files already formatted

python -m mypy headroom --ignore-missing-imports
Success: no issues found in 410 source files

gitleaks protect --staged --no-banner --redact
no leaks found
```

## Real Behavior Proof

- Environment: Windows, Python 3.13.13
- Exact command / steps: Ran direct CCR tracker tests, CCR always-on
tests, corrupt golden byte recovery tests, empty tools injection
regression tests, full ruff, format check, mypy, and staged gitleaks
scan.
- Observed result: Existing sticky CCR tool behavior and recovery
behavior remain green while the CCR session state domain is directly
covered.
- Not tested: Full repository pytest suite locally; CI covers the
broader matrix.

## 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
- [ ] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [x] I have added tests that prove my fix is effective or that my
feature works
- [x] New and existing unit tests pass locally with my changes
- [ ] I have updated the CHANGELOG.md if applicable

## Screenshots (if applicable)

N/A

## Additional Notes

Documentation and changelog updates are not applicable for this internal
refactor. The default-branch Dependabot alerts reported during push are
pre-existing and unrelated to this PR.
2026-07-11 10:17:53 -05:00
JD Davis
4e19bcf6ce
test(memory): skip decorators on offline model misses (#2020)
## Description

Current `main` already has the shared `external_model_skip_reason`
helper and pytest hooks for transient/offline model dependency failures.
This follow-up applies the same classifier to the async memory
integration test decorators in `test_core_operations.py` and
`test_easy.py`, so decorated tests also skip offline Hugging Face
cache-miss errors instead of only `httpx.ReadTimeout`.

Supersedes #1017 with a clean branch based on current `main`.

## Type of Change

- [x] Bug fix (non-breaking change that fixes an issue)

## Changes Made

- Updated `network_timeout_handler` in
`tests/test_memory/test_core_operations.py` to call
`external_model_skip_reason` and re-raise unrelated exceptions.
- Updated `network_timeout_handler` in `tests/test_memory/test_easy.py`
the same way.
- Removed now-unnecessary direct `httpx` imports from those files.

## Testing

- [x] Unit tests pass (`pytest`)
- [x] Linting passes (`ruff check`)
- [x] Type checking passes (`mypy` via commit hook)
- [x] New tests added for new functionality

### Test Output

```text
$ python -m pytest tests/test_memory/test_skip_helpers.py -q
4 passed in 0.12s

$ python -m ruff check tests/test_memory/test_core_operations.py tests/test_memory/test_easy.py tests/test_memory/test_skip_helpers.py
All checks passed!

$ python -m ruff format --check tests/test_memory/test_core_operations.py tests/test_memory/test_easy.py tests/test_memory/test_skip_helpers.py
3 files already formatted

$ git commit -m "test(memory): skip decorators on offline model misses"
Sync plugin versions.....................................................Passed
check for merge conflicts................................................Passed
ruff.....................................................................Passed
ruff-format..............................................................Passed
mypy.....................................................................Passed
```

## Real Behavior Proof

- Environment: Windows, Python 3.13.13, local `C:\git\headroom`
checkout.
- Exact command / steps: ran `python -m pytest
tests/test_memory/test_skip_helpers.py -q` against the skip classifier
used by these decorators.
- Observed result: `4 passed`, covering `httpx.ReadTimeout`,
`LocalEntryNotFoundError`, offline Hugging Face `OSError`, and unrelated
errors.
- Not tested: live memory integration against an intentionally missing
Hugging Face cache.

## Review Readiness

- [x] I have performed a self-review
- [x] This PR is ready for human review
2026-07-11 10:14:05 -05:00
GUOHAO LIU
12aa2cbf6c
fix(kompress): surface model-not-ready state via logs and health endpoint (#2034)
## Description

Kompress model download failures (HuggingFace unreachable, corporate
firewall, SSL errors) previously caused **silent 0% compression** — the
model isn't loaded, `is_ready()` returns `False`, and the proxy passes
through with no warning, no health indicator, nothing. Operators cannot
detect degraded operation without manually comparing
`x-headroom-tokens-before` vs `x-headroom-tokens-after` headers.

Closes #2029

## Type of Change

- [x] Bug fix (non-breaking change which fixes an issue)

## Changes Made

Three surfaces where silent failure is now visible:

- **Request-time (hot path)**: `ContentRouter.compress_text()` now logs
a WARNING once per router instance when `is_ready()` is False and the
model is not cached. Rate-limited to one log per session to avoid spam.

- **Startup (eager preload)**: `ContentRouter.eager_load_compressors()`
now logs WARNING (was INFO) when `KompressModelNotCached` is raised,
with actionable guidance ("Check HuggingFace connectivity or
pre-download with headroom-ai[ml]").

- **Health endpoint**: `/health` response now includes a `kompress`
component with the standard `enabled`/`ready`/`status`/`backend` fields.
Kompress is treated as **optional** in the aggregate readiness check — a
cold model cache does NOT degrade the overall proxy health status
(matching the semantics of `cache` and `rate_limiter` which report
`ready=True` when disabled).

## Testing

- [x] **Existing tests**: 13/13 pass in
`test_kompress_preload_deferral.py` + `test_proxy_disable_kompress.py` +
`test_proxy_ccr.py` + `test_proxy_debug_endpoints.py`
- [x] **Adversarial**: 5 endpoint-level tests verify `/health`,
`/healthz`, `/livez`, `/readyz` all return 200 even when kompress model
is not cached
- [x] **PBT (Hypothesis)**: 3 properties × 136 random combinations
confirm aggregate readiness ignores kompress, `_component_health()`
invariants hold, and kompress is never the sole cause of `unhealthy`
- [x] **Lint**: `ruff check` and `ruff format --check` pass on all
changed files

```text
$ uv run pytest tests/test_kompress_preload_deferral.py tests/test_proxy_disable_kompress.py \
    tests/test_proxy_ccr.py::TestCCRIntegration::test_health_endpoint \
    tests/test_proxy_debug_endpoints.py::test_existing_health_routes_unchanged -q
.............                              [100%]
13 passed in 2.90s

$ uv run pytest /tmp/adversarial_kompress_health.py -q
.....                                      [100%]
5 passed in 9.60s
# Includes: /healthz, /livez, /readyz all 200; disable_kompress=True → status=disabled

$ uv run pytest /tmp/pbt_kompress_health.py -q
...                                        [100%]
3 passed in 0.56s
# Hypothesis: 128 aggregate combos + 4 invariant combos + 4 isolation combos
```

## Real Behavior Proof

- Environment: Linux, Python 3.12, headroom main @ a617455f
- Exact command / steps: `uv run pytest
tests/test_kompress_preload_deferral.py
tests/test_proxy_disable_kompress.py -q` → 13 passed; adversarial 5/5;
PBT 3/3 (136 combos); `uv run ruff format --check
headroom/transforms/content_router.py headroom/proxy/server.py` → 2
files already formatted
- Observed result: Startup warning fires when model not cached.
Request-time warning fires once when `is_ready()` returns False.
`/health` returns `kompress` with `enabled`, `ready`, and `backend`
fields. Overall proxy health remains `healthy` regardless of kompress
cold-cache state.
- Not tested: Full E2E with HuggingFace blocked (network simulation).
Manual testing recommended for operators behind corporate firewalls.

## Review Readiness

- [x] I have performed a self-review
- [x] This PR is ready for human review

---------

Co-authored-by: lennney <lennney@users.noreply.github.com>
2026-07-11 10:12:08 -05:00
Abhay Singh
d8783ab89b
fix(cache/semantic): key entries by context hash, not query text (#2022)
## Description

`SemanticCache` (`headroom/cache/semantic.py`) derives each entry's key
from the **query text
only** — where `query` is just the trailing user message — and its
exact-match lookup returns
the slot without checking the stored entry's `messages_hash`:

```python
# put()
key = self._generate_key(query)          # sha256(query)[:16]
self._cache[key] = entry
if messages_hash:
    self._hash_index[messages_hash] = key

# get() — exact-match branch
key = self._hash_index.get(messages_hash)
if key and key in self._cache:
    entry = self._cache[key]
    ...
    return entry                         # never checks entry.messages_hash
```

So two requests that share a trailing user message but differ in earlier
context map to the
**same** key. The second `put` overwrites the first, and the first
request's `messages_hash`
still points at that (now overwritten) slot — so it is served the
**other conversation's**
cached response.

Trailing messages like `"continue"`, `"yes"`, `"fix it"`, `"run the
tests"` are extremely
common in agentic/coding sessions, so this collides constantly. It's
independent of the
proxy-level `_compute_key` fix (that's about what goes *into*
`messages_hash`; here the entry
is stored under a query-only key regardless of how good the hash is).
This `SemanticCache` is
the one used by the SDK client's `enable_semantic_cache` path.

Concretely:
1. `put("run the tests", A, messages_hash=HA)` → key `K = sha256("run
the tests")`; `_cache[K]=A`.
2. `put("run the tests", B, messages_hash=HB)` → same `K`; `_cache[K]`
overwritten with `B`.
3. `get("run the tests", HA)` → `_hash_index[HA]=K`, `K in _cache` →
returns **B**.

Closes: no issue filed — found while auditing the cache key derivation.

## Fix

1. Key entries by the full-context `messages_hash` when present, falling
back to the query hash
   only when no hash is supplied:
   ```python
   key = messages_hash or self._generate_key(query)
   ```
2. Defensively verify `entry.messages_hash == messages_hash` in the
exact-match branch of `get`,
   so any residual stale mapping becomes a miss rather than wrong data.

## Type of Change

- [x] Bug fix (non-breaking change that fixes an issue)

## Changes Made

- `headroom/cache/semantic.py`: key `put` entries by `messages_hash`
when present; verify `entry.messages_hash` in the `get` exact-match
branch.
- `tests/test_cache/test_semantic.py`: add
`test_same_query_different_context_does_not_collide` and
`test_exact_match_verifies_messages_hash`.

## Testing

- [x] New regression tests added (`tests/test_cache/test_semantic.py`)
- [x] Linting/formatting clean — run with the CI-pinned `ruff==0.15.17`
- [ ] Full `pytest` deferred to CI (local-OOM reason below).

```text
$ uvx ruff@0.15.17 check headroom/cache/semantic.py tests/test_cache/test_semantic.py
All checks passed!
```

## Real Behavior Proof

- Environment: Windows 11, Python 3.10, headroom from this branch.
Importing `headroom` pulls in the torch/transformers stack and a full
`pytest` gets OOM-killed on this box, so I verified the `put`/`get`
logic with a dependency-free script and left the full pytest to CI.
- Exact command / steps: stored responses A and B under the same query
`"run the tests"` with different `messages_hash`, then read each hash
back — through both the old (query-keyed) and new (hash-keyed) logic.
- Observed result: the old logic serves B's response to request A; the
new logic isolates them:

```text
OLD: A->RESPONSE_B  B->RESPONSE_B
NEW: A->RESPONSE_A  B->RESPONSE_B
SEMANTIC CACHE COLLISION FIX VERIFIED (OLD served B to A; NEW isolates)
```

- Not tested: the full SDK `HeadroomClient` round-trip with
`enable_semantic_cache=True` (needs the heavy stack). The fix is
confined to `SemanticCache.put`/`get` and the new tests drive them
directly. Full local `pytest` deferred to CI (OOM, per above).

## 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
- [ ] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [x] I have added tests that prove my fix is effective or that my
feature works
- [ ] New and existing unit tests pass locally with my changes — ran
lint + a standalone logic check; full pytest deferred to CI (local OOM,
disclosed above)
- [x] I have updated the CHANGELOG.md if applicable

## Additional Notes

- Small, contained fix — the key derivation plus a verification guard,
no new dependencies.
- @JerrettDavis tagging you — this one can serve one conversation's
cached response to another when the last message matches, so it seemed
worth surfacing. Thanks!
2026-07-11 10:11:09 -05:00
JD Davis
f8431240b9
Extract tool schema savings policy (#1971)
## Description

Extracts the pure tool-schema savings attribution logic from `server.py`
into `headroom.proxy.tool_schema_savings_policy`. The server keeps the
`_tool_schema_saved_from_tags` compatibility alias used by the existing
stats payload path.

Closes #

## Type of Change

- [ ] 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
- [ ] Performance improvement
- [x] Code refactoring (no functional changes)

## Changes Made

- Added `tool_schema_savings_policy.py` with stable savings tag names
and pure summation behavior.
- Replaced the inline `server.py` helper body with a compatibility alias
to the extracted policy.
- Added direct tests for valid tag summing, invalid values, non-mapping
input, and stable tag names.
- Carried forward the LiteLLM callback compatibility shim needed for
current mypy on `main`.

## Testing

- [x] Unit tests pass (`pytest`)
- [x] Linting passes (`ruff check .`)
- [x] Type checking passes (`mypy headroom`)
- [x] New tests added for new functionality
- [ ] Manual testing performed

### Test Output

```text
python -m pytest tests\test_tool_schema_savings_policy.py
4 passed in 0.14s

python -m ruff check .
All checks passed!

python -m ruff format --check .
1095 files already formatted

python -m mypy headroom --ignore-missing-imports
Success: no issues found in 409 source files

gitleaks protect --staged --no-banner --redact
no leaks found
```

## Real Behavior Proof

- Environment: Windows, Python 3.13.13, branch
`jd/architecture-slice-24`.
- Exact command / steps: ran focused tool-schema savings policy tests,
ruff, ruff format check, mypy, and staged gitleaks scan.
- Observed result: pure policy behavior is directly covered and local
lint/type/security checks pass.
- Not tested: full proxy runtime; this slice only moves pure stats
attribution logic while preserving the server alias.

## 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
- [ ] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [x] I have added tests that prove my fix is effective or that my
feature works
- [x] New and existing unit tests pass locally with my changes
- [ ] I have updated the CHANGELOG.md if applicable

## Screenshots (if applicable)

N/A

## Additional Notes

Documentation and changelog updates are N/A for this internal
architecture-only refactor. The push reported existing default-branch
Dependabot alerts; no staged secret leaks were found for this PR.
2026-07-11 10:10:21 -05:00
Ben Younes
a617455f02
fix(proxy): preserve chatgpt responses streaming (#2012)
## Description

Closes #1956

Keep ChatGPT OAuth `/v1/responses` requests streaming when CCR retrieve
tools are present. The buffered `stream:false` conversion is still used
for regular OpenAI Responses CCR requests, but ChatGPT Codex routing now
bypasses that conversion so the upstream receives the streaming request
shape it expects.

## Type of Change

- [x] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change that adds functionality)
- [ ] Breaking change (fix or feature that would cause existing
functionality to not work as expected)
- [ ] Documentation update
- [ ] Refactoring
- [ ] Performance improvement
- [ ] Test update
- [ ] Other

## Changes Made

- Extracted the OpenAI Responses CCR stream-buffering decision into a
small helper.
- Excluded ChatGPT OAuth/Codex-routed requests from the buffered
`stream:false` path.
- Added tests proving regular OpenAI CCR still buffers while ChatGPT
OAuth CCR remains streaming.

## Testing

- [x] Unit tests pass (`pytest`)
- [x] Linting passes (`ruff check`)
- [x] Formatting verified (`ruff format --check`)
- [ ] Manual testing performed

### Test Output

```text
$ python3 -m pytest tests/test_proxy_openai_responses_stream_ccr.py -q
collected 3 items

tests/test_proxy_openai_responses_stream_ccr.py ...                      [100%]

============================== 3 passed in 0.59s ===============================

$ .venv/bin/ruff check headroom/proxy/handlers/openai.py tests/test_proxy_openai_responses_stream_ccr.py
All checks passed!

$ .venv/bin/ruff format --check headroom/proxy/handlers/openai.py tests/test_proxy_openai_responses_stream_ccr.py
2 files already formatted
```

## Test verification (RED -> GREEN)

RED, with the ChatGPT OAuth guard temporarily removed from the buffering
decision:

```text
tests/test_proxy_openai_responses_stream_ccr.py .F.                      [100%]
FAILED tests/test_proxy_openai_responses_stream_ccr.py::test_responses_ccr_keeps_chatgpt_oauth_requests_streaming
E   AssertionError: assert not True
E    +  where True = _should_buffer(tools=[{'type': 'function', 'name': 'headroom_retrieve'}], is_chatgpt_auth=True)
```

GREEN, with this patch applied:

```text
tests/test_proxy_openai_responses_stream_ccr.py ...                      [100%]
============================== 3 passed in 0.59s ===============================
```

## Real Behavior Proof

- Environment: Linux, Python 3.12.3, pytest 9.1.1, ruff 0.14.14.
- Exact command / steps: Removed the `not is_chatgpt_auth` guard from
the CCR buffering decision, ran the targeted tests, restored the guard,
and reran the tests plus targeted ruff checks.
- Observed result: The ChatGPT OAuth streaming regression test fails
without the guard and passes with the guard, while regular OpenAI CCR
buffering remains covered.
- Not tested: Full `uv run pytest`, full-project `uv run ruff check .`,
full-project `uv run ruff format --check .`, and `uv run mypy headroom`
were not run locally; `uv run --extra dev ruff` attempted to build the
Rust extension in this worktree, so targeted checks used the existing
`.venv/bin/ruff`.

## Review Readiness

- [x] I have performed a self-review
- [x] This PR is ready for human review

## Checklist

- [x] My code follows the style guidelines of this project
- [x] I have performed a self-review of my code
- [x] I have added tests that prove my fix is effective
- [x] New and existing targeted tests pass locally with my changes
- [x] Any dependent changes have been merged and published in downstream
modules

## Screenshots (if applicable)

N/A

## Additional Notes

The existing buffered CCR path is preserved for non-ChatGPT OpenAI
Responses requests.
2026-07-11 00:10:02 -05:00
JD Davis
70b98b6485
Extract Python forwarder mode policy (#1987)
## Description

Extracts Python-forwarder mode resolution from `helpers.py` into
`headroom.proxy.python_forwarder_mode_policy`. The forwarding helpers
still read `HEADROOM_PROXY_PYTHON_FORWARDER_MODE` at request time, while
the allowed values/default/error contract is now pure and directly
tested.

Closes #

## Type of Change

- [ ] 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
- [ ] Performance improvement
- [x] Code refactoring (no functional changes)

## Changes Made

- Added `python_forwarder_mode_policy.py` with the allowed mode type,
env name/default, and resolver.
- Kept `helpers.get_python_forwarder_mode` as the request-time env
reader and compatibility entry point.
- Added direct policy tests for defaults, accepted values,
normalization, and invalid mode rejection.

## Testing

- [x] Unit tests pass (`pytest`)
- [x] Linting passes (`ruff check .`)
- [x] Type checking passes (`mypy headroom`)
- [x] New tests added for new functionality
- [ ] Manual testing performed

### Test Output

```text
python -m pytest tests\test_python_forwarder_mode_policy.py tests\test_proxy_byte_faithful_forwarding.py
41 passed in 4.00s

python -m ruff check .
All checks passed!

python -m ruff format --check .
1069 files already formatted

python -m mypy headroom --ignore-missing-imports
Success: no issues found in 410 source files

gitleaks protect --staged --no-banner --redact
no leaks found
```

## Real Behavior Proof

- Environment: Windows, Python 3.13.13, branch
`jd/architecture-slice-34`.
- Exact command / steps: ran new Python-forwarder mode policy tests,
existing byte-faithful forwarding tests, ruff, ruff format check, mypy,
and staged gitleaks scan.
- Observed result: forwarder mode behavior and byte-faithful forwarding
tests remain covered; local lint/type/security checks pass.
- Not tested: live proxy forwarding; existing helper entry point remains
intact.

## 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
- [ ] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [x] I have added tests that prove my fix is effective or that my
feature works
- [x] New and existing unit tests pass locally with my changes
- [ ] I have updated the CHANGELOG.md if applicable

## Screenshots (if applicable)

N/A

## Additional Notes

Documentation and changelog updates are N/A for this internal
architecture-only refactor. The push reported existing default-branch
Dependabot alerts; no staged secret leaks were found for this PR.
2026-07-11 00:06:37 -05:00
JD Davis
0f846e5a8f
refactor(proxy): extract tool injection config (#2010)
## Description

Extracts memory tool-injection operator config parsing from
`headroom.proxy.helpers` into a focused config policy module. Existing
helper functions and imports remain available while the environment
parsing is now directly testable.

Closes #

## Type of Change

- [ ] 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
- [ ] Performance improvement
- [x] Code refactoring (no functional changes)

## Changes Made

- Added `headroom.proxy.tool_injection_config` for
`HEADROOM_TOOL_INJECTION_STICKY` and
`HEADROOM_TOOL_TRACKER_MAX_SESSIONS` parsing.
- Updated `helpers.get_tool_injection_sticky_mode` and
`helpers.get_tool_tracker_max_sessions` to delegate to the config module
while preserving existing import paths.
- Added direct tests for defaults, valid values, invalid values, and
helper wrapper compatibility.

## Testing

- [x] Unit tests pass (`pytest`)
- [x] Linting passes (`ruff check .`)
- [x] Type checking passes (`mypy headroom`)
- [x] New tests added for new functionality
- [ ] Manual testing performed

### Test Output

```text
python -m pytest tests/test_tool_injection_config.py tests/test_memory_tool_session_sticky.py tests/test_issue_728_empty_tools_injection.py
46 passed in 0.53s

python -m ruff check .
All checks passed!

python -m ruff format --check .
1078 files already formatted

python -m mypy headroom --ignore-missing-imports
Success: no issues found in 415 source files

gitleaks protect --staged --no-banner --redact
no leaks found
```

## Real Behavior Proof

- Environment: Windows, Python 3.13.13, clean worktree from
`headroomlabs/main` at `cb38f793`.
- Exact command / steps: Ran targeted tool-injection config, memory
session sticky, and empty-tool regression tests plus ruff, ruff-format,
mypy, and staged gitleaks scan.
- Observed result: All targeted tests and local gates passed; staged
secret scan found no leaks.
- Not tested: Full Docker/native wrapper CI locally; covered by
repository CI.

## 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
- [ ] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [x] I have added tests that prove my fix is effective or that my
feature works
- [x] New and existing unit tests pass locally with my changes
- [ ] I have updated the CHANGELOG.md if applicable

## Screenshots (if applicable)

N/A.

## Additional Notes

Documentation and changelog updates are not applicable for this internal
refactor. The push reported existing default-branch Dependabot
vulnerabilities; this PR's staged gitleaks scan passed and CI security
checks are expected to validate the branch.
2026-07-11 00:03:15 -05:00
Ztkent
b3a559ba56
fix(savings): cap ledger retention at 30 days (#1985)
## Description
The durable savings ledger (`headroom savings`) retained up to 365 days
of history with an unbounded-sounding "All time" window. Long-lived
installs accumulate an ever-growing `~/.headroom/savings_events.jsonl`,
and `--days` had no upper bound so a caller could request an arbitrarily
large lookback. This caps retention at 30 days everywhere it's read,
shrinks the compaction threshold to match, and renames the "All time"
window to reflect what it actually is now: `Last 30 days`.

## Type of Change
- [x] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing
functionality to change)
- [ ] Documentation update
- [ ] Performance improvement
- [ ] Code refactoring (no functional changes)

## Changes Made
- `headroom/savings_ledger.py`: `DEFAULT_RETENTION_DAYS` 365 → 30; add
`MAX_RETENTION_DAYS = 30` and hard-clamp the lookback inside
`aggregate_savings` so no caller (CLI or programmatic) can read back
further than 30 days, regardless of the `retention_days` argument passed
in.
- `headroom/savings_ledger.py`: report window `all_time` →
`last_30_days` (the bucket is exactly 30-day-bounded now, so it doubles
as the lifetime view too). `_COMPACT_SIZE_BYTES` 8 MiB → 1 MiB, since a
30-day-bounded ledger should never need to grow large.
- `headroom/cli/savings.py`: `--days` is now `click.IntRange(min=1,
max=30)` (was unbounded); help text states the max. Window label `"All
time"` → `"Last 30 days"`, and the label column width bumped 11 → 12 so
the longer label stays aligned with the other rows' progress bars.
- `tests/test_savings_ledger.py`: updated window-label assertions; added
a hard-cap regression test (`retention_days=365` passed explicitly still
excludes a 60-day-old event) and a `--days` range-rejection test
(31/60/365 all rejected).

## Testing
- [x] Unit tests pass (`pytest`)
- [x] Linting passes (`ruff check .`)
- [x] Type checking passes (`mypy headroom`)
- [x] New tests added for new functionality
- [x] Manual testing performed

### Test Output

```text
$ ruff check headroom/savings_ledger.py headroom/cli/savings.py tests/test_savings_ledger.py
All checks passed!

$ ruff format --check headroom/savings_ledger.py headroom/cli/savings.py tests/test_savings_ledger.py
3 files already formatted

$ mypy headroom --ignore-missing-imports
Success: no issues found in 409 source files

$ pytest tests/test_savings_ledger.py -q
............ss....                                                     [100%]
16 passed, 2 skipped in 6.11s
```

(ruff `0.15.17`, mypy `1.20.2` — pinned to match
`.github/workflows/ci.yml`'s `lint` job. Full multi-shard suite left to
CI; ran the full touched-module suite locally.)

## Real Behavior Proof
- Environment: macOS (Darwin 25.5.0), Python 3.13.14, local `uv` venv;
branch built and installed via `uv tool install --force`.
- Exact command / steps: ran `headroom savings` against a ledger holding
multiple models' events (claude-opus-4-8, claude-sonnet-5,
claude-haiku-4-5) recorded across the retention window, then ran
`headroom savings --days 60` to exercise the new upper bound.
- Observed result: all three windows (Today / Last 7 days / Last 30
days) populate and are each bounded to at most 30 days; cost-avoided
breaks down per model; `--days 60` is rejected by the new `1..30` range
instead of silently accepted.
- Not tested: Windows/macOS native-wrapper e2e jobs — left to CI.

```text
$ headroom savings

Today        █████░░░░░░░░░░░  33.8%  saved 8,702,348 / 25,781,326 tokens  $25.5830
Last 7 days  ██████░░░░░░░░░░  36.3%  saved 11,289,737 / 31,072,254 tokens  $34.8287
Last 30 days ██████░░░░░░░░░░  38.2%  saved 14,449,516 / 37,821,634 tokens  $48.5385

Cost avoided per model:
  claude-opus-4-8          $33.0494
  claude-sonnet-5          $15.2989
  claude-haiku-4-5-20251001 $0.1902

$ headroom savings --days 60
Usage: headroom savings [OPTIONS]
Try 'headroom savings --help' for help.

Error: Invalid value for '--days': 60 is not in the range 1<=x<=30.
```

- Not tested: Windows/macOS native-wrapper e2e jobs — left to CI.

## Review Readiness
- [x] I have performed a self-review
- [x] This PR is ready for human review

## Checklist
- [x] My code follows the style guidelines of this project
- [x] I have performed a self-review of my code
- [x] I have commented my code, particularly in hard-to-understand areas
- [x] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [x] I have added tests that prove my fix is effective or that my
feature works
- [x] New and existing unit tests pass locally with my changes
- [x] I have updated the CHANGELOG.md if applicable

## Screenshots (if applicable)
N/A — CLI text output only, see Real Behavior Proof above.
2026-07-11 00:02:23 -05:00
JD Davis
82af5cdfe2
refactor(proxy): isolate proxy mode policy (#1965)
## Description

Extracts proxy mode normalization into a pure `proxy_mode_policy`
module. `modes.py` keeps the existing public API and logging, while
alias/default/unknown-mode decisions are now represented by a
deterministic value object with direct tests.

Closes #

## Type of Change

- [ ] 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
- [ ] Performance improvement
- [x] Code refactoring (no functional changes)

## Changes Made

- Added `headroom.proxy.proxy_mode_policy` with canonical mode
constants, alias mapping, `ProxyModeDecision`, and pure normalization
helpers.
- Updated `headroom.proxy.modes` to delegate normalization decisions
while preserving existing constants, predicates, fallback behavior, and
logging.
- Added direct policy tests for canonical modes, legacy aliases, blank
values, unknown values, and value-only normalization.
- Included the current LiteLLM callback signature compatibility shim
required for repo-wide mypy on main-based slices.

## Testing

- [x] Unit tests pass (`pytest`)
- [x] Linting passes (`ruff check .`)
- [x] Type checking passes (`mypy headroom`)
- [x] New tests added for new functionality
- [ ] Manual testing performed

### Test Output

```text
python -m pytest tests/test_proxy_mode_policy.py tests/test_proxy_modes.py tests/test_litellm_callback.py -q
17 passed in 7.84s

python -m ruff check .
All checks passed!

python -m ruff format --check .
1095 files already formatted

python -m mypy headroom --ignore-missing-imports
Success: no issues found in 409 source files

gitleaks protect --staged --no-banner --redact
no leaks found
```

## Real Behavior Proof

- Environment: Windows, Python 3.13.13, clean worktree based on
`headroomlabs/main`.
- Exact command / steps: targeted pytest, ruff, format check, repo-wide
mypy, staged gitleaks scan.
- Observed result: proxy mode policy/modes/callback tests pass; static
checks pass; no staged secrets detected.
- Not tested: live proxy run; this slice preserves existing public mode
helpers and only moves pure normalization policy.

## 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
- [ ] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [x] I have added tests that prove my fix is effective or that my
feature works
- [x] New and existing unit tests pass locally with my changes
- [ ] I have updated the CHANGELOG.md if applicable

## Screenshots (if applicable)

N/A

## Additional Notes

Documentation and changelog updates are not applicable for this internal
architecture slice. PR-specific GHAS checks will be monitored after
opening.
2026-07-11 00:00:03 -05:00
Abhay Singh
27ddde1f5e
fix(transforms/code): coerce language aliases instead of raising (#1975)
## Description

`CodeAwareCompressor.compress()` picks the language for AST-based
compression like this
(`headroom/transforms/code_compressor.py`):

```python
if language:
    detected_lang = CodeLanguage(language.lower())   # <-- raises on anything not an exact enum value
    confidence = 1.0
elif self.config.language_hint:
    detected_lang = CodeLanguage(self.config.language_hint.lower())
    confidence = 1.0
else:
    detected_lang, confidence = detect_language(code)
```

`CodeLanguage` only accepts
`python`/`javascript`/`typescript`/`go`/`rust`/`java`/`c`/`cpp`/`perl`.
The very common markdown fence tags and hints — `js`, `ts`, `py`, `jsx`,
`tsx`, `node`, `rs`,
`c++` — are **not** enum values, so `CodeLanguage("js")` raises
`ValueError`. That construction
is *above* the method's own `try/except`, so:

- **Direct callers** — `CodeAwareCompressor().compress(code,
language="js")` and the module-level
`compress_code(code, language="js")` — crash with an uncaught
`ValueError`.
- **In the router (mixed content):** `split_into_sections` extracts the
raw fence tag
(`_CODE_FENCE_PATTERN` captures `\w*`, e.g. `js`) into
`ContentSection.language`, and that string
is passed straight into `compress(...)`. The `ValueError` is swallowed
by the outer `try/except`
in the strategy dispatch, so a ` ```js ` / ` ```ts ` / ` ```py ` block
silently **skips
code-aware compression** even when `enable_code_aware=True`, falling
back to the generic path.

So the three most common web/scripting languages, written with their
usual fence tags, never get
the structure-aware compressor.

Closes: no issue filed — found while auditing the code-compression
language path.

## Fix

Add a `coerce_language()` helper that maps common aliases/fence tags to
the canonical
`CodeLanguage` and returns `CodeLanguage.UNKNOWN` (never raises) for
anything unrecognized.
`compress()` now coerces the hint and, when the result is `UNKNOWN`,
falls back to
content-based `detect_language(code)` instead of constructing the enum
directly:

```python
if language:
    detected_lang = coerce_language(language)
    if detected_lang == CodeLanguage.UNKNOWN:
        detected_lang, confidence = detect_language(code)
    else:
        confidence = 1.0
```

## Type of Change

- [x] Bug fix (non-breaking change that fixes an issue)

## Changes Made

- `headroom/transforms/code_compressor.py`: add `_LANGUAGE_ALIASES` and
`coerce_language()`; use them in `compress()` for both the `language`
argument and `config.language_hint`, with a content-detection fallback
on `UNKNOWN`.
- `tests/test_code_compressor_language_alias.py`: cover alias mapping,
canonical passthrough, case/whitespace handling, unknown-returns-UNKNOWN
(no `ValueError`), and that `compress(language="js")` no longer raises.

## Testing

- [x] New regression tests added
(`tests/test_code_compressor_language_alias.py`)
- [x] Linting passes (`ruff check`) and formatting is clean (`ruff
format --check`)
- [ ] Full `pytest` deferred to CI (local-OOM reason below).

```text
$ uv run ruff check headroom/transforms/code_compressor.py tests/test_code_compressor_language_alias.py
All checks passed!
```

## Real Behavior Proof

- Environment: Windows 11, Python 3.10, headroom from this branch.
Importing `headroom` pulls in the torch/transformers stack and a full
`pytest` gets OOM-killed on this box, so I verified the coercion logic
with a dependency-free script (replicating the enum + helper) and left
the full pytest to CI.
- Exact command / steps: ran the common aliases and the canonical values
through both the old `CodeLanguage(value.lower())` construction and the
new `coerce_language()`.
- Observed result: the old construction raises `ValueError` on every
alias (the crash / silent-skip); the new helper maps them and never
raises:

```text
OK alias 'js': old raised ValueError -> new maps to javascript
OK alias 'ts': old raised ValueError -> new maps to typescript
OK alias 'py': old raised ValueError -> new maps to python
OK alias 'jsx': old raised ValueError -> new maps to javascript
OK alias 'node': old raised ValueError -> new maps to javascript
OK canonical values pass through
OK case-insensitive + trimmed
OK unknown -> UNKNOWN (no ValueError)
LANGUAGE COERCION VERIFIED
```

- Not tested: running a full mixed-content document with ` ```js `
fences through a booted compression pipeline (needs the heavy stack).
The unit tests exercise the coercion directly and the
`compress(language="js")` entry point. Full local `pytest` deferred to
CI (OOM, per above).

## 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
- [ ] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [x] I have added tests that prove my fix is effective or that my
feature works
- [ ] New and existing unit tests pass locally with my changes — ran
lint + a standalone logic check; full pytest deferred to CI (local OOM,
disclosed above)
- [x] I have updated the CHANGELOG.md if applicable

## Additional Notes

- No new dependencies; a small lookup table plus a helper and a
call-site change.
- @JerrettDavis tagging you — this one silently disables code-aware
compression for the most common fence tags (`js`/`ts`/`py`), so it may
be worth a look when you have a moment.
2026-07-10 23:57:33 -05:00
JD Davis
69fd2189a3
Extract request limit policy (#1982)
## Description

Extracts request/stream limit validation from `helpers.py` into
`headroom.proxy.request_limit_policy`. The helpers still read
environment variables at request time, but validation of SSE event size
and body-too-large status values is now pure and directly tested.

Closes #

## Type of Change

- [ ] 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
- [ ] Performance improvement
- [x] Code refactoring (no functional changes)

## Changes Made

- Added `request_limit_policy.py` for resolving SSE event max bytes and
body-too-large HTTP status values.
- Kept `helpers.get_sse_event_max_bytes` and
`helpers.get_body_too_large_status` reading env vars and delegating to
the pure policy.
- Added direct tests for defaults, valid override values, and invalid
values.
- Carried forward the LiteLLM callback compatibility shim needed for
current mypy on `main`.

## Testing

- [x] Unit tests pass (`pytest`)
- [x] Linting passes (`ruff check .`)
- [x] Type checking passes (`mypy headroom`)
- [x] New tests added for new functionality
- [ ] Manual testing performed

### Test Output

```text
python -m pytest tests\test_request_limit_policy.py
10 passed in 0.17s

python -m ruff check .
All checks passed!

python -m ruff format --check .
1095 files already formatted

python -m mypy headroom --ignore-missing-imports
Success: no issues found in 409 source files

gitleaks protect --staged --no-banner --redact
no leaks found
```

## Real Behavior Proof

- Environment: Windows, Python 3.13.13, branch
`jd/architecture-slice-31`.
- Exact command / steps: ran focused request-limit policy tests, ruff,
ruff format check, mypy, and staged gitleaks scan.
- Observed result: limit validation behavior is directly covered and
local lint/type/security checks pass.
- Not tested: live proxy request rejection; existing helper entry points
remain intact.

## 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
- [ ] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [x] I have added tests that prove my fix is effective or that my
feature works
- [x] New and existing unit tests pass locally with my changes
- [ ] I have updated the CHANGELOG.md if applicable

## Screenshots (if applicable)

N/A

## Additional Notes

Documentation and changelog updates are N/A for this internal
architecture-only refactor. The push reported existing default-branch
Dependabot alerts; no staged secret leaks were found for this PR.
2026-07-10 23:55:11 -05:00
JD Davis
094a53c047
refactor(proxy): isolate output effort policy (#1961)
## Description

Extracts provider-neutral output effort decisions into a pure
`output_effort_policy` module. `output_shaper` still owns request
mutation and labels, while the rank comparisons, legacy thinking clamp,
and OpenAI text verbosity eligibility now live behind small
deterministic functions.

Closes #

## Type of Change

- [ ] 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
- [ ] Performance improvement
- [x] Code refactoring (no functional changes)

## Changes Made

- Added `headroom.proxy.output_effort_policy` for effort lowering,
legacy thinking budget clamping, and OpenAI text verbosity decisions.
- Updated `output_shaper` to delegate those pure decisions while
preserving existing labels and request mutation behavior.
- Added focused policy tests for effort rank transitions, thinking clamp
boundaries, and verbosity creation/lowering.
- Included the current LiteLLM callback signature compatibility shim
required for repo-wide mypy on main-based slices.

## Testing

- [x] Unit tests pass (`pytest`)
- [x] Linting passes (`ruff check .`)
- [x] Type checking passes (`mypy headroom`)
- [x] New tests added for new functionality
- [ ] Manual testing performed

### Test Output

```text
python -m pytest tests/test_output_effort_policy.py tests/test_output_shaper.py tests/test_litellm_callback.py -q
56 passed in 6.34s

python -m ruff check .
All checks passed!

python -m ruff format --check .
1095 files already formatted

python -m mypy headroom --ignore-missing-imports
Success: no issues found in 409 source files

gitleaks protect --staged --no-banner --redact
no leaks found
```

## Real Behavior Proof

- Environment: Windows, Python 3.13.13, clean worktree based on
`headroomlabs/main`.
- Exact command / steps: targeted pytest, ruff, format check, repo-wide
mypy, staged gitleaks scan.
- Observed result: output effort policy/shaper/callback tests pass;
static checks pass; no staged secrets detected.
- Not tested: live provider calls; this slice preserves existing request
mutation behavior and only moves pure policy decisions.

## 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
- [ ] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [x] I have added tests that prove my fix is effective or that my
feature works
- [x] New and existing unit tests pass locally with my changes
- [ ] I have updated the CHANGELOG.md if applicable

## Screenshots (if applicable)

N/A

## Additional Notes

Documentation and changelog updates are not applicable for this internal
architecture slice. PR-specific GHAS checks will be monitored after
opening.
2026-07-10 23:53:00 -05:00
JD Davis
b1e871d51c
refactor(proxy): isolate memory rank policy (#1960)
## Description

Extracts the proxy memory ranking formulas into a pure
`memory_rank_policy` module and keeps `MemoryCandidate` /
`RecencyBoostRanker` as the public adapter-facing API. Also preserves
backend memory IDs when ranked candidates are rebuilt, so downstream
memory update/delete handles survive the ranking boundary.

Closes #

## Type of Change

- [ ] 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
- [ ] Performance improvement
- [x] Code refactoring (no functional changes)

## Changes Made

- Added `headroom.proxy.memory_rank_policy` for timestamp parsing,
recency factor calculation, and score boosting.
- Updated `RecencyBoostRanker` to delegate policy math while preserving
the existing public API.
- Preserved `MemoryCandidate.id` when rank output candidates are
rebuilt.
- Added focused policy tests plus an ID-preservation regression test.
- Included the current LiteLLM callback signature compatibility shim
required for repo-wide mypy on main-based slices.

## Testing

- [x] Unit tests pass (`pytest`)
- [x] Linting passes (`ruff check .`)
- [x] Type checking passes (`mypy headroom`)
- [x] New tests added for new functionality
- [ ] Manual testing performed

### Test Output

```text
python -m pytest tests/test_memory_rank_policy.py tests/test_memory_ranker.py tests/test_litellm_callback.py -q
32 passed in 6.22s

python -m ruff check .
All checks passed!

python -m ruff format --check .
1095 files already formatted

python -m mypy headroom --ignore-missing-imports
Success: no issues found in 409 source files

gitleaks protect --staged --no-banner --redact
no leaks found
```

## Real Behavior Proof

- Environment: Windows, Python 3.13.13, local worktree based on
`headroomlabs/main`.
- Exact command / steps: targeted pytest, ruff, ruff format check,
repo-wide mypy, staged gitleaks scan.
- Observed result: memory rank policy/ranker/callback tests pass; static
checks pass; no staged secrets detected.
- Not tested: full provider/API integration; this slice only changes
pure policy delegation and candidate shape preservation.

## 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
- [ ] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [x] I have added tests that prove my fix is effective or that my
feature works
- [x] New and existing unit tests pass locally with my changes
- [ ] I have updated the CHANGELOG.md if applicable

## Screenshots (if applicable)

N/A

## Additional Notes

Documentation and changelog updates are not applicable for this internal
architecture slice. PR-specific GHAS checks will be monitored after
opening.
2026-07-10 23:51:51 -05:00
JD Davis
1c1e360112
refactor(proxy): isolate project attribution policy (#1957)
## Description

Extracts pure project attribution policy from the runtime project
context holder. Header classification, project path splitting, and
project-prefixed base URL construction now live in a policy module while
`project_context` keeps the ContextVar and ASGI scope adapter
responsibilities.

Closes #

## Type of Change

- [ ] 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
- [ ] Performance improvement
- [x] Code refactoring (no functional changes)

## Changes Made

- Added `headroom.proxy.project_policy` for pure project attribution
header/path/base-URL helpers.
- Updated `headroom.proxy.project_context` to re-export the pure helpers
and retain only request context binding and ASGI scope mutation.
- Added direct tests for the extracted project attribution policy
boundary.
- Kept the LiteLLM callback compatibility shim required for repo-wide
type checking on fresh branches.

## Testing

- [x] Unit tests pass (`pytest`)
- [x] Linting passes (`ruff check .`)
- [x] Type checking passes (`mypy headroom`)
- [x] New tests added for new functionality
- [ ] Manual testing performed

### Test Output

```text
python -m pytest tests/test_project_policy.py tests/test_proxy_project_savings.py tests/test_litellm_callback.py tests/test_compress_api.py::TestLiteLLMCallback -q
29 passed in 13.70s

python -m ruff check .
All checks passed!

python -m ruff format --check .
1095 files already formatted

python -m mypy headroom --ignore-missing-imports
Success: no issues found in 409 source files

gitleaks protect --staged --no-banner --redact
no leaks found
```

## Real Behavior Proof

- Environment: Windows, Python 3.13.13, local worktree based on
`headroomlabs/main`.
- Exact command / steps: ran focused project policy tests, project
savings tests, LiteLLM callback tests, Ruff lint/format checks, mypy
over `headroom`, and staged gitleaks scan.
- Observed result: all local checks passed; staged secret scan found no
leaks.
- Not tested: full CI matrix and deployment flows; those are covered by
GitHub Actions.

## 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
- [ ] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [x] I have added tests that prove my fix is effective or that my
feature works
- [x] New and existing unit tests pass locally with my changes
- [ ] I have updated the CHANGELOG.md if applicable

## Screenshots (if applicable)

N/A

## Additional Notes

Documentation and changelog updates are not applicable for this internal
refactor. GitHub reported existing Dependabot alerts on the default
branch during push; this PR does not change dependencies, and the staged
secret scan is clean.
2026-07-10 23:50:27 -05:00
JD Davis
740fb9bc16
refactor(cache): isolate semantic key policy (#1953)
## Description

Extracts proxy semantic-cache key normalization and hashing into a pure
policy module while preserving `SemanticCache._compute_key` for existing
callers and tests. This separates deterministic cache-key construction
from the async cache adapter and LRU storage concerns.

Closes #

## Type of Change

- [ ] 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
- [ ] Performance improvement
- [x] Code refactoring (no functional changes)

## Changes Made

- Added `headroom.proxy.semantic_cache_key` for pure cache-control
stripping and semantic cache key construction.
- Updated `SemanticCache._compute_key` to delegate to the extracted
policy while preserving the local `_strip_cache_control` compatibility
alias.
- Added direct tests for the extracted semantic-cache key policy.
- Kept the LiteLLM callback compatibility shim required for repo-wide
type checking on fresh branches.

## Testing

- [x] Unit tests pass (`pytest`)
- [x] Linting passes (`ruff check .`)
- [x] Type checking passes (`mypy headroom`)
- [x] New tests added for new functionality
- [ ] Manual testing performed

### Test Output

```text
python -m pytest tests/test_proxy_semantic_cache_key_policy.py tests/test_proxy_semantic_cache_key.py tests/test_proxy_semantic_cache_key_integration.py tests/test_proxy_openai_cache_key_integration.py tests/test_litellm_callback.py tests/test_compress_api.py::TestLiteLLMCallback -q
46 passed in 11.83s

python -m ruff check .
All checks passed!

python -m ruff format --check .
1095 files already formatted

python -m mypy headroom --ignore-missing-imports
Success: no issues found in 409 source files

gitleaks protect --staged --no-banner --redact
no leaks found
```

## Real Behavior Proof

- Environment: Windows, Python 3.13.13, local worktree based on
`headroomlabs/main`.
- Exact command / steps: ran focused semantic cache key tests, handler
cache-key integration tests, LiteLLM callback tests, Ruff lint/format
checks, mypy over `headroom`, and staged gitleaks scan.
- Observed result: all local checks passed; staged secret scan found no
leaks.
- Not tested: full CI matrix and deployment flows; those are covered by
GitHub Actions.

## 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
- [ ] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [x] I have added tests that prove my fix is effective or that my
feature works
- [x] New and existing unit tests pass locally with my changes
- [ ] I have updated the CHANGELOG.md if applicable

## Screenshots (if applicable)

N/A

## Additional Notes

Documentation and changelog updates are not applicable for this internal
refactor. GitHub reported existing Dependabot alerts on the default
branch during push; this PR does not change dependencies, and the staged
secret scan is clean.
2026-07-10 23:49:27 -05:00
JD Davis
ea1951508b
refactor(proxy): isolate rate limit policy (#1954)
## Description

Extracts token-bucket refill, consume, wait-time, and stale-bucket
selection formulas into a pure rate-limit policy module while preserving
the async `TokenBucketRateLimiter` adapter for locks and mutable bucket
storage.

Closes #

## Type of Change

- [ ] 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
- [ ] Performance improvement
- [x] Code refactoring (no functional changes)

## Changes Made

- Added `headroom.proxy.rate_limit_policy` for pure token-bucket
calculations.
- Updated `TokenBucketRateLimiter` to delegate refill, consume, and
stale-key selection to the extracted policy.
- Added direct tests for the rate-limit policy boundary.
- Kept the LiteLLM callback compatibility shim required for repo-wide
type checking on fresh branches.

## Testing

- [x] Unit tests pass (`pytest`)
- [x] Linting passes (`ruff check .`)
- [x] Type checking passes (`mypy headroom`)
- [x] New tests added for new functionality
- [ ] Manual testing performed

### Test Output

```text
python -m pytest tests/test_rate_limit_policy.py tests/test_proxy_healthchecks.py tests/test_litellm_callback.py tests/test_compress_api.py::TestLiteLLMCallback -q
27 passed in 17.82s

python -m ruff check .
All checks passed!

python -m ruff format --check .
1095 files already formatted

python -m mypy headroom --ignore-missing-imports
Success: no issues found in 409 source files

gitleaks protect --staged --no-banner --redact
no leaks found
```

## Real Behavior Proof

- Environment: Windows, Python 3.13.13, local worktree based on
`headroomlabs/main`.
- Exact command / steps: ran focused rate-limit policy tests, proxy
health checks, LiteLLM callback tests, Ruff lint/format checks, mypy
over `headroom`, and staged gitleaks scan.
- Observed result: all local checks passed; staged secret scan found no
leaks.
- Not tested: full CI matrix and deployment flows; those are covered by
GitHub Actions.

## 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
- [ ] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [x] I have added tests that prove my fix is effective or that my
feature works
- [x] New and existing unit tests pass locally with my changes
- [ ] I have updated the CHANGELOG.md if applicable

## Screenshots (if applicable)

N/A

## Additional Notes

Documentation and changelog updates are not applicable for this internal
refactor. GitHub reported existing Dependabot alerts on the default
branch during push; this PR does not change dependencies, and the staged
secret scan is clean.
2026-07-10 23:47:33 -05:00