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**