Commit b3f4436
authored
fix(proxy): align signed-thinking wire accounting (#3015)
## Description
Signed-thinking histories force byte-faithful passthrough because
re-serializing signed Anthropic blocks can invalidate their signatures.
Headroom correctly forwarded the original client bytes, but continued
reporting mutations, transforms, savings, response headers, and prefix
state from a different body that never reached the provider. Separately,
the final Anthropic guard hoisted every `role: system` message into the
top-level prompt, including valid mid-conversation system sections,
changing their semantics and destroying the cached prefix if that
mutation ever shipped.
This coupled fix makes downstream accounting use the actual wire body
whenever the signed-thinking lock discards edits, and narrows system
relocation to the current Anthropic model and placement contract.
Closes #2990
Closes #2991
## 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
- [x] Performance improvement
- [x] Code refactoring (no functional changes)
## Changes Made
- Detects signed thinking in the original request as well as the mutated
body, so a transform cannot remove the block and accidentally bypass the
byte lock.
- Keeps the original-body signature probe best-effort under malformed,
recursive, and `MemoryError` conditions.
- Carries discarded mutation reasons through the streaming forwarder and
emits the existing structured warning on HTTP streaming paths too.
- When signed passthrough wins, resets message savings, tool-schema
savings, attribution ledgers, transform labels, response headers, and
prefix tracking to the original client wire body.
- Adds bounded public diagnostic tags naming/counting discarded mutation
reasons without exposing body content.
- Preserves valid mid-conversation system sections on currently
supported Claude models and official Anthropic, Bedrock, and parsed
`*.googleapis.com` routes; hostname-boundary validation rejects
lookalike and userinfo URLs.
- Preserves consecutive system sections and enforces documented
predecessor/successor placement rules.
- Continues relocating initial, invalidly placed, unsupported-model, and
conservative third-party-gateway system messages to avoid upstream 400s.
- Includes current `main`, including #2996, #2997, #2971, #3009, #3012,
and the MCP dependency cap.
## 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
uv run pytest -q <wire/cache/savings/system focused suite>
379 passed
uv run pytest -q tests/test_proxy/test_anthropic_recount_and_reparse_safety.py tests/test_proxy_byte_faithful_forwarding.py tests/test_proxy_handler_helpers.py
99 passed
pytest tests scripts/tests --splits 4 --group N --tb=short -q
All four CI-shaped fresh-process groups passed locally after correcting the MemoryError regression; each completed in roughly 75-83 seconds.
Post-CodeQL correction: 91 focused tests passed; all four exact-head CI-shaped shards passed in roughly 82-95 seconds.
uv run ruff format --check .
1411 files already formatted
uv run ruff check .
All checks passed
uv run mypy headroom
Success: no issues found in 520 source files
```
## Real Behavior Proof
- Environment: macOS arm64, Python 3.13, branch rebased onto current
`main`.
- Exact command / steps: sent a signed-thinking request whose tool
schema is measurably compacted inside the handler, captured the exact
upstream bytes, wrapped the real outcome funnel, and inspected response
headers, aggregate metrics, attribution tags, transforms, and
prefix-tracker state. Exercised valid, consecutive, invalid, initial,
supported-model, and unsupported-model system placements.
- Observed result: upstream bytes remain byte-identical to the client;
discarded edits contribute zero tokens, zero tool savings, no transform
header, and no attribution while the prefix tracker stores the actual
wire messages. Valid mid-conversation system sections remain in place;
only out-of-contract sections relocate.
- Not tested: live paid Anthropic traffic with production credentials.
The placement/model contract was verified against the current official
documentation and wire behavior is covered with a byte-capturing
transport.
## Runtime Rollout Safety
- Rollout-managed feature(s): signed-thinking wire-truth accounting and
Anthropic mid-conversation system preservation.
- Minimum rollout channel: normal patch release after exact-head CI is
entirely green.
- Stable/default behavior changed: discarded mutations no longer inflate
savings; supported valid system sections are no longer hoisted into the
top-level prompt.
- Kill switch / disable path: no unsafe runtime override; human revert
restores the previous conservative relocation/accounting behavior.
- Unsafe override required: none.
- Qualification impact: all Python shards, byte-forwarding,
cache-prefix, outcome/savings, signed-thinking, Anthropic handler,
static, Docker, and security checks must remain green.
- Rollback path: fix forward through a human-reviewed corrective PR; no
persisted data or configuration migration is involved.
## 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 — inline
wire-contract documentation; no separate guide is required
- [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 did **not** edit `CHANGELOG.md` — it is generated by
release-please from my Conventional Commit PR title (a CI guard enforces
this)
## Screenshots (if applicable)
Not applicable; proxy wire behavior and accounting only.
## Additional Notes
Human review only. No merge or auto-merge is configured. Current
provider contract reference:
https://platform.claude.com/docs/en/build-with-claude/mid-conversation-system-messages1 parent 942af56 commit b3f4436
6 files changed
Lines changed: 351 additions & 17 deletions
File tree
- headroom/proxy
- handlers
- tests
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
81 | 81 | | |
82 | 82 | | |
83 | 83 | | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
84 | 94 | | |
85 | 95 | | |
86 | 96 | | |
| |||
123 | 133 | | |
124 | 134 | | |
125 | 135 | | |
126 | | - | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
127 | 140 | | |
128 | 141 | | |
129 | 142 | | |
| |||
180 | 193 | | |
181 | 194 | | |
182 | 195 | | |
183 | | - | |
| 196 | + | |
| 197 | + | |
| 198 | + | |
| 199 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
14 | 14 | | |
15 | 15 | | |
16 | 16 | | |
| 17 | + | |
17 | 18 | | |
18 | 19 | | |
19 | 20 | | |
| |||
50 | 51 | | |
51 | 52 | | |
52 | 53 | | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
53 | 71 | | |
54 | 72 | | |
55 | 73 | | |
| |||
3012 | 3030 | | |
3013 | 3031 | | |
3014 | 3032 | | |
3015 | | - | |
| 3033 | + | |
| 3034 | + | |
| 3035 | + | |
| 3036 | + | |
| 3037 | + | |
| 3038 | + | |
| 3039 | + | |
| 3040 | + | |
| 3041 | + | |
| 3042 | + | |
| 3043 | + | |
| 3044 | + | |
| 3045 | + | |
3016 | 3046 | | |
3017 | 3047 | | |
3018 | 3048 | | |
| |||
3393 | 3423 | | |
3394 | 3424 | | |
3395 | 3425 | | |
| 3426 | + | |
| 3427 | + | |
| 3428 | + | |
| 3429 | + | |
| 3430 | + | |
| 3431 | + | |
| 3432 | + | |
| 3433 | + | |
| 3434 | + | |
| 3435 | + | |
| 3436 | + | |
| 3437 | + | |
| 3438 | + | |
| 3439 | + | |
| 3440 | + | |
| 3441 | + | |
| 3442 | + | |
| 3443 | + | |
| 3444 | + | |
| 3445 | + | |
| 3446 | + | |
| 3447 | + | |
| 3448 | + | |
| 3449 | + | |
| 3450 | + | |
| 3451 | + | |
| 3452 | + | |
| 3453 | + | |
| 3454 | + | |
| 3455 | + | |
| 3456 | + | |
| 3457 | + | |
| 3458 | + | |
| 3459 | + | |
| 3460 | + | |
| 3461 | + | |
| 3462 | + | |
| 3463 | + | |
3396 | 3464 | | |
3397 | 3465 | | |
3398 | 3466 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1121 | 1121 | | |
1122 | 1122 | | |
1123 | 1123 | | |
1124 | | - | |
| 1124 | + | |
1125 | 1125 | | |
1126 | 1126 | | |
1127 | 1127 | | |
1128 | 1128 | | |
1129 | 1129 | | |
1130 | 1130 | | |
1131 | | - | |
| 1131 | + | |
1132 | 1132 | | |
1133 | 1133 | | |
1134 | 1134 | | |
| 1135 | + | |
1135 | 1136 | | |
| 1137 | + | |
1136 | 1138 | | |
1137 | 1139 | | |
1138 | 1140 | | |
| |||
1143 | 1145 | | |
1144 | 1146 | | |
1145 | 1147 | | |
| 1148 | + | |
1146 | 1149 | | |
1147 | 1150 | | |
1148 | 1151 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
905 | 905 | | |
906 | 906 | | |
907 | 907 | | |
| 908 | + | |
908 | 909 | | |
909 | | - | |
| 910 | + | |
910 | 911 | | |
911 | | - | |
912 | | - | |
913 | | - | |
914 | | - | |
915 | | - | |
916 | | - | |
917 | | - | |
| 912 | + | |
| 913 | + | |
| 914 | + | |
| 915 | + | |
| 916 | + | |
918 | 917 | | |
919 | 918 | | |
920 | 919 | | |
| |||
924 | 923 | | |
925 | 924 | | |
926 | 925 | | |
927 | | - | |
928 | | - | |
929 | | - | |
| 926 | + | |
| 927 | + | |
| 928 | + | |
| 929 | + | |
| 930 | + | |
| 931 | + | |
| 932 | + | |
| 933 | + | |
| 934 | + | |
| 935 | + | |
| 936 | + | |
| 937 | + | |
| 938 | + | |
| 939 | + | |
| 940 | + | |
| 941 | + | |
| 942 | + | |
| 943 | + | |
| 944 | + | |
| 945 | + | |
| 946 | + | |
| 947 | + | |
| 948 | + | |
| 949 | + | |
| 950 | + | |
| 951 | + | |
| 952 | + | |
| 953 | + | |
| 954 | + | |
| 955 | + | |
| 956 | + | |
| 957 | + | |
| 958 | + | |
| 959 | + | |
| 960 | + | |
| 961 | + | |
| 962 | + | |
| 963 | + | |
| 964 | + | |
| 965 | + | |
| 966 | + | |
| 967 | + | |
| 968 | + | |
| 969 | + | |
| 970 | + | |
| 971 | + | |
| 972 | + | |
| 973 | + | |
| 974 | + | |
| 975 | + | |
| 976 | + | |
| 977 | + | |
930 | 978 | | |
931 | 979 | | |
932 | 980 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
282 | 282 | | |
283 | 283 | | |
284 | 284 | | |
| 285 | + | |
| 286 | + | |
| 287 | + | |
| 288 | + | |
| 289 | + | |
| 290 | + | |
| 291 | + | |
| 292 | + | |
| 293 | + | |
| 294 | + | |
| 295 | + | |
| 296 | + | |
| 297 | + | |
| 298 | + | |
| 299 | + | |
| 300 | + | |
| 301 | + | |
| 302 | + | |
| 303 | + | |
| 304 | + | |
| 305 | + | |
| 306 | + | |
| 307 | + | |
| 308 | + | |
| 309 | + | |
285 | 310 | | |
286 | 311 | | |
287 | 312 | | |
| |||
566 | 591 | | |
567 | 592 | | |
568 | 593 | | |
| 594 | + | |
| 595 | + | |
| 596 | + | |
| 597 | + | |
| 598 | + | |
| 599 | + | |
| 600 | + | |
| 601 | + | |
| 602 | + | |
| 603 | + | |
| 604 | + | |
| 605 | + | |
| 606 | + | |
| 607 | + | |
| 608 | + | |
| 609 | + | |
| 610 | + | |
| 611 | + | |
| 612 | + | |
| 613 | + | |
| 614 | + | |
| 615 | + | |
| 616 | + | |
| 617 | + | |
| 618 | + | |
| 619 | + | |
| 620 | + | |
| 621 | + | |
| 622 | + | |
| 623 | + | |
| 624 | + | |
| 625 | + | |
| 626 | + | |
| 627 | + | |
| 628 | + | |
| 629 | + | |
| 630 | + | |
| 631 | + | |
| 632 | + | |
| 633 | + | |
| 634 | + | |
| 635 | + | |
| 636 | + | |
| 637 | + | |
| 638 | + | |
| 639 | + | |
| 640 | + | |
| 641 | + | |
| 642 | + | |
| 643 | + | |
| 644 | + | |
| 645 | + | |
| 646 | + | |
| 647 | + | |
| 648 | + | |
| 649 | + | |
| 650 | + | |
| 651 | + | |
| 652 | + | |
| 653 | + | |
| 654 | + | |
| 655 | + | |
| 656 | + | |
| 657 | + | |
| 658 | + | |
| 659 | + | |
| 660 | + | |
| 661 | + | |
| 662 | + | |
| 663 | + | |
| 664 | + | |
| 665 | + | |
| 666 | + | |
| 667 | + | |
| 668 | + | |
| 669 | + | |
| 670 | + | |
| 671 | + | |
| 672 | + | |
| 673 | + | |
| 674 | + | |
| 675 | + | |
569 | 676 | | |
570 | 677 | | |
571 | 678 | | |
| |||
0 commit comments