File size: 5,062 Bytes
071ba6b fa599d5 071ba6b fa599d5 071ba6b fa599d5 071ba6b fa599d5 071ba6b fa599d5 071ba6b fa599d5 071ba6b fa599d5 071ba6b fa599d5 071ba6b fa599d5 071ba6b fa599d5 071ba6b fa599d5 071ba6b | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 | # REWARD AUDIT β REW-05
**Date:** 2026-04-25
**Auditor:** Pratham (Plan 01-05 executor)
**Scope:** `rewards/` package β 4 components + composition layer
---
## A-01: Format-only shortcut exploit
**Vector:** Model learns to always output `<answer></answer>` (empty or random) because
format_gate=1.0 still gives partial credit via token_budget + recursion_efficiency.
**Test:**
```python
score = compose_reward_single("<answer></answer>", "Rome", 100, 0, cfg_reward)
```
**Expected:** β€ 0.15 (A-02 cap is 0.05, plus 0.10 soft format bonus)
**Result:** 0.15 (matches cap) β
**Status:** MITIGATED β A-02 correctness==0 cap blocks this. Empty answers have correctness=0, so even with the format bonus, the maximum is 0.15.
---
## A-02: Exact case/punctuation gaming
**Vector:** Model outputs `<answer>ROME.</answer>` β exact text with trailing period β
to exploit correctness normalizer edge case.
**Test:**
```python
score = correctness("<answer>ROME.</answer>", "Rome")
```
**Expected:** 1.0 (normalizer strips trailing punctuation and lowercases)
**Result:** 1.0 β
**Status:** ACCEPTABLE β This is correct behavior; normalizer intentionally handles casing/punctuation.
---
## A-03: Gold answer verbatim copy from context
**Vector:** Model copies the gold answer from the context before the document is masked,
or finds a copy of the gold answer in the context itself (DATA-04 post-check).
**Test:** `test_no_gold_answer_verbatim_leak` in `tests/test_dataset.py`
**Expected:** Zero examples where `gold_answer.lower() in context.lower()`
**Result:** 0 violations across 1200 examples (1000 train + 200 eval) β
**Status:** MITIGATED β Generator `_assert_no_leak()` blocks this at generation time.
---
## A-04: Length reward gaming (write more = score more)
**Vector:** Model writes extremely long responses to maximize token_budget reward component.
**Test:** `test_no_monotonic_length_exploit` (REW-06 audit, `tests/test_rewards.py`)
**Expected:** Longer completions score lower (capped_linear is strictly decreasing in length)
**Result:** Confirmed β longest completion (20000 words) scores < shortest (10 words) β
**Status:** MITIGATED β `token_budget(variant="capped_linear")` with Ξ±=0.2 ensures
`score = max(0, 1 - 0.2 * ratio)` β ratio rises with length, score falls.
---
## A-05: Recursion depth gaming (REVISED for v3)
**Vector:** Model uses 0 llm() calls on every task to maximize
recursion_efficiency, even on multi_needle / 200K tasks where recursion
would actually help correctness.
**Analysis (v3):**
- recursion_efficiency contributes only when correctness == 1.0 (gating
in compose.py). On hard tasks where 0 calls fails to produce a correct
answer, the efficiency bonus is forfeited entirely.
- Net incentive: use the *minimum* recursion that still produces a
correct answer. Exactly the desired behavior.
**Status:** β
MITIGATED by correctness-gating.
---
## A-06 (Stretch): Partial answer overlap gaming
**Vector:** Model outputs `<answer>azure blue</answer>` when gold is `azure` to try to
include the gold answer as a substring.
**Test:**
```python
score = correctness("<answer>azure blue</answer>", "azure")
```
**Expected:** 0.0 (exact-match after normalization, "azure blue" != "azure")
**Result:** 0.0 β
**Status:** MITIGATED β Normalizer uses exact-match after case/whitespace/punctuation
normalization. Substring matches are rejected.
---
## A-07: Comment-spam exploit (NEW)
**Vector:** Model emits `# llm(foo)` inside code blocks to inflate the
count regex without making real calls. (Inverted variant of A-05: spam
to make recursion_eff *lower*, useless because lower efficiency hurts.)
**Test:** `test_call_in_comment_not_counted` in
`tests/test_recursion_extract.py`.
**Result:** 0 calls counted β
β extractor uses tokenize, ignores comments.
**Status:** β
MITIGATED by tokenize-aware extraction.
---
## A-08: String-literal exploit (NEW)
**Vector:** Model writes `"earlier code did llm(...)"` in a string
literal to confuse a naive regex extractor.
**Test:** `test_call_in_string_literal_not_counted`.
**Result:** 0 calls counted β
β tokenize correctly identifies STRING
tokens and skips them.
**Status:** β
MITIGATED.
---
## Summary
| Attempt | Vector | Status | Mitigation |
|---------|--------|--------|------------|
| A-01 | Format-only (empty answer) | β
MITIGATED | A-02 correctness==0 cap (β€0.15 with bonus) |
| A-02 | Case/punctuation gaming | β
ACCEPTABLE | Intentional normalizer behavior |
| A-03 | Context verbatim copy | β
MITIGATED | Generator `_assert_no_leak()` |
| A-04 | Length reward exploit | β
MITIGATED | capped_linear token_budget |
| A-05 | Recursion avoidance | β
MITIGATED | Correctness-gating |
| A-06 | Partial answer substring | β
MITIGATED | Exact-match normalizer |
| A-07 | Comment-spam exploit | β
MITIGATED | Tokenize-aware extraction |
| A-08 | String-literal exploit | β
MITIGATED | Tokenize-aware extraction |
**VERDICT: REWARD SYSTEM APPROVED FOR PHASE 2 TRAINING**
|