File size: 28,882 Bytes
b7f3933
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
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
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
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
# ☠️ ADVERSARIAL AUDIT β€” meta_hack Customer Support RL Environment

> **Auditor:** Antigravity AI  
> **Date:** 2026-04-08  
> **Codebase:** `github.com/lebiraja/meta_hack`  
> **Total Files Audited:** 15 source files, ~2,100 LoC  
> **Audit Type:** Zero-compromise adversarial, 12-phase, assuming 10,000 concurrent users under hostile conditions

---

## EXECUTIVE SUMMARY

| Metric | Rating |
|--------|:------:|
| **Architecture** | 🟒 7.5/10 |
| **Security** | πŸ”΄ 2/10 |
| **Scalability** | 🟑 4/10 |
| **RL Quality** | 🟒 7/10 |
| **Code Quality** | 🟒 8/10 |
| **Test Coverage** | 🟑 6/10 |
| **Production Readiness** | 🟑 5/10 |
| **Overall Verdict** | **5.5/10 β€” Solid Hackathon Entry, Not Production** |

> [!IMPORTANT]
> This codebase is **dramatically better** than the original `customer_support_env` repository (rated 1.3/10). It demonstrates genuine software engineering competence. However, it has critical security gaps and scalability limitations that prevent production deployment.

---

## PHASE 1: SURFACE-LEVEL AUDIT

### βœ… What's Right

| Item | Status |
|------|:------:|
| Clean module structure (`env/`, `server/`, `tests/`) | βœ… |
| Pydantic v2 models with proper validation | βœ… |
| `ActionType` as `str, Enum` β€” allows JSON serialization | βœ… |
| Single server entrypoint (`server/app.py`) | βœ… |
| `inference.py` is a client, not embedded in server | βœ… |
| `pyproject.toml` and `requirements.txt` aligned | βœ… |
| Dockerfile uses layer caching, `pip install -e .` | βœ… |
| Docker HEALTHCHECK configured | βœ… |
| `openenv.yaml` matches actual implementation | βœ… |

### ⚠️ Minor Concerns

| Item | Severity |
|------|:--------:|
| `re` import inside `_compute_unresolved_issues()` at L244 of `environment.py` β€” should be top-level | Low |
| `random.choice()` not seeded β€” slightly non-deterministic test behavior | Low |
| Test file imports `from env.models import Message` redundantly (already in `env`) | Cosmetic |

---

## PHASE 2: LOGIC & CORRECTNESS AUDIT

### βœ… Core Logic Is Sound

The code path `reset() β†’ step() β†’ compute_step_reward() β†’ grade()` is **fully connected and functional**:

```
server/app.py β†’ CustomerSupportEnv.reset()
                β†’ ticket_store.get_random_by_task() β†’ picks ticket
                β†’ _build_observation() β†’ returns Observation

server/app.py β†’ CustomerSupportEnv.step(action)
                β†’ compute_step_reward() β†’ VADER + cosine + resolution + accuracy
                β†’ _simulate_customer_reply() β†’ persona-driven follow-up
                β†’ _update_sentiment() β†’ sentiment tracking
                β†’ returns (obs, reward, done, info)

On done β†’ run_grader(task, state) β†’ task-specific deterministic grading
        β†’ del _sessions[session_id] β†’ cleanup
```

### ⚠️ Logic Issues Found

**Issue 1: Reward can exceed 1.0 before clamping**

In `reward_engine.py` L209-217, the composite reward is:
```python
raw = (0.40 * resolution + 0.20 * tone + 0.20 * efficiency + 0.20 * accuracy
       + loop_penalty + escalation_penalty + info_gathering_bonus)
```

With `info_gathering_bonus = 0.1`, max theoretical:
`0.40 + 0.20 + 0.20 + 0.20 + 0.0 + 0.0 + 0.1 = 1.1`

The `np.clip(raw, 0.0, 1.0)` saves this, but the bonus is an **unweighted additive on top of a weighted sum** β€” architecturally sloppy.

**Issue 2: `_compute_unresolved_issues` fallback is questionable**

```python
# L253-254: If no regex pattern, assume unresolved until customer replies β‰₯2 times
elif sum(1 for m in self._history if m.role == "customer") < 2:
    unresolved.append(info_type)
```

This means any unknown `info_type` is considered "resolved" after 2 customer messages regardless of content. A customer saying "I don't know" twice would satisfy this.

**Issue 3: Resolution score keyword matching is order-dependent**

`_RESOLUTION_SIGNALS["refund_initiated"]` has 7 keywords. The score is:
```python
score = min(matched / max(len(signals) * 0.4, 1), 1.0)
# = min(matched / 2.8, 1.0) β€” so 3 keyword matches = score 1.0
```

An agent saying "refund credit money back" scores 1.0 without actually initiating anything. The system measures **intent language**, not action execution.

**Issue 4: Escalation penalty vs. resolution score double-penalize**

In `reward_engine.py`:
- `compute_escalation_penalty()` returns `-0.3` for escalating low/medium tickets
- `compute_resolution_score()` returns `max(score - 0.4, 0.0)` for non-escalation-expected escalations

Both are applied simultaneously via `raw = ... + escalation_penalty` and `0.40 * resolution_score`. This means escalating an easy task is penalized **twice**: once by `-0.3` absolute and once by `-0.4` on the 40%-weighted resolution score. Effective penalty: `-0.46` β€” harsh but arguably intentional.

---

## PHASE 3: API & ENDPOINT AUDIT

### βœ… Endpoints Well-Designed

| Endpoint | Method | Auth | Validation | Error Handling |
|----------|:------:|:----:|:----------:|:--------------:|
| `/reset?task=easy` | POST | ❌ None | βœ… `Literal["easy","medium","hard"]` | βœ… |
| `/step?session_id=...` | POST | ❌ None | βœ… Pydantic `Action` model | βœ… 404/409 |
| `/state/{session_id}` | GET | ❌ None | βœ… 404 if not found | βœ… |
| `/health` | GET | ❌ None | N/A | βœ… |
| `/` | GET | ❌ None | N/A | βœ… |

### ❌ Critical API Issues

**No Authentication Whatsoever**

Every endpoint is publicly accessible. Anyone can:
- Create unlimited sessions (`POST /reset`)
- Step any session they know the ID of
- Read full internal state including ticket data (`GET /state/{id}`)

**No Rate Limiting**

Zero rate limiting on any endpoint. A single curl loop can:
```bash
while true; do curl -X POST http://host:7860/reset; done
```
This will exhaust server memory in minutes.

**Session ID is UUID β€” Not Secret**

UUIDs are not cryptographically secret. They're guessable with enough samples. However, in practice, UUID4 has 122 bits of entropy which is sufficient for a hackathon. Not sufficient for production with PII.

---

## PHASE 4: FAILURE PATH AUDIT

### What Happens When Things Go Wrong

| Failure Mode | Server Behavior | Severity |
|-------------|----------------|:--------:|
| Invalid `task` in `/reset` | Pydantic validates β†’ 422 | βœ… Handled |
| Invalid `action_type` in `/step` | Pydantic validates β†’ 422 | βœ… Handled |
| Unknown `session_id` | 404 `HTTPException` | βœ… Handled |
| Step after done | `RuntimeError` β†’ 409 `HTTPException` | βœ… Handled |
| `env.reset()` not called | `RuntimeError` β†’ 409 | βœ… Handled |
| Grader throws exception | `try/except` β†’ falls back to `reward.value` | βœ… Handled |
| Malformed JSON body | FastAPI/Pydantic β†’ 422 | βœ… Handled |
| 1MB payload | ❌ No size limit β€” processed normally | πŸ”΄ Critical |
| Empty message string | Tone score β†’ 0.5 (neutral) | ⚠️ Benign |
| `None` message + `None` reason | Falls back to `f"[{action_type}]"` | βœ… Handled |

### ❌ Unhandled Failure Paths

**1. Ticket Store Singleton Mutation**

```python
# ticket_store.py L420
def get_random_by_task(self, task: str) -> dict:
    return dict(random.choice(pool))  # shallow copy
```

`dict()` is a **shallow copy**. The `required_info_before_close` list inside is shared. If any code mutated this list (currently none do), all sessions using that ticket would be affected. Safe today, fragile tomorrow.

**2. TF-IDF Vectorizer Instantiation Per Call**

```python
# reward_engine.py L72
vec = TfidfVectorizer().fit_transform(last_two)
```

A new `TfidfVectorizer` is created for every step of every session. At 10,000 concurrent users Γ— ~5 steps each = 50,000 vectorizer instantiations per minute. Not catastrophic (it's lightweight for 2 documents), but wasteful.

**3. VADER Analyzer is a Module-Level Singleton**

```python
_analyzer = SentimentIntensityAnalyzer()
```

This is fine for single-threaded `uvicorn`. If deployed with `--workers N` (multi-process), each worker gets its own instance. If using `asyncio` with threads, VADER is not thread-safe (it reads/writes internal state). Currently safe because `uvicorn` default is single-process + asyncio.

---

## PHASE 5: SCALABILITY AUDIT (10,000 CONCURRENT USERS)

### Memory Analysis

Per session:
- `CustomerSupportEnv` object: ~2KB base
- Ticket dict (shallow copy): ~1KB
- History (up to max_steps messages): ~500 bytes Γ— 10 = 5KB
- Action log: ~200 bytes Γ— 10 = 2KB
- **Total per session: ~10KB**

At 10,000 concurrent sessions: **~100MB** β€” survivable if sessions are cleaned up.

### ❌ The `_sessions` Dict Problem

```python
# server/app.py L28
_sessions: dict[str, CustomerSupportEnv] = {}
```

**Sessions ARE cleaned up on `done`** (L85: `del _sessions[session_id]`). This is a critical improvement over the original codebase. However:

1. **Abandoned sessions are never cleaned up.** If a client calls `/reset` but never reaches `done`, the session lives forever. There is no TTL, no eviction, no cleanup sweep.

2. **At 10,000 users with 5% abandonment rate:** 500 zombie sessions per hour Γ— 24 hours = 12,000 zombies/day Γ— 10KB = 120MB/day of leaked memory. Server dies in days.

3. **No max session limit.** A single attacker can call `/reset` in a loop and create millions of sessions.

### Concurrency Safety

The server uses synchronous FastAPI endpoints (no `async def`). With default `uvicorn`:
- Single worker, single thread
- Requests are processed sequentially (no parallelism)
- `_sessions` dict is safe (no race conditions)
- But throughput is terrible under load

With `uvicorn --workers N`:
- Each worker has its own `_sessions` dict
- Sessions are **not shared** between workers
- A session created in worker 1 will 404 in worker 2
- **Multi-worker deployment is broken by design**

### Verdict At Scale

| Metric | At 10,000 Users |
|--------|:---------------:|
| Memory (active sessions) | ~100MB βœ… |
| Memory (zombie sessions/day) | ~120MB/day ⚠️ |
| CPU (VADER + TF-IDF per step) | High but survivable ⚠️ |
| Throughput (single worker) | ~100 req/s max πŸ”΄ |
| Multi-worker support | ❌ Broken |
| Session cleanup | βœ… On done, ❌ On abandon |

---

## PHASE 6: SECURITY AUDIT

### πŸ”΄ Critical Vulnerabilities

**1. Zero Authentication β€” CRITICAL**
- No API keys, no tokens, no auth headers
- Anyone can create sessions, step them, and read state
- Severity: **CRITICAL** in any deployment with real users

**2. State Endpoint Leaks Full Internal Data β€” HIGH**
- `GET /state/{session_id}` returns full ticket data, conversation history, sentiment, action log
- Includes customer PII (emails, order IDs, phone numbers) embedded in ticket `follow_up_info`
- No access control β€” any session ID can be read by anyone

**3. No Input Size Limit β€” HIGH**
- No FastAPI request body size limit configured
- A 100MB message payload would be accepted and stored in history
- OOM attack: send 1000 requests with 10MB messages = 10GB memory consumed

**4. No CORS Configuration β€” MEDIUM**
- No `CORSMiddleware` configured
- Not exploitable in server-to-server scenarios
- Exploitable if any web frontend is added

**5. Ticket Data Contains Fake PII β€” LOW (for hackathon)**
- Tickets contain realistic email addresses and names
- In a production system, this would be real PII requiring encryption at rest
- For hackathon evaluation, this is expected behavior

### βœ… Security Positives

| Item | Status |
|------|:------:|
| No SQL injection (no database) | βœ… |
| No command injection | βœ… |
| No path traversal | βœ… |
| No SSRF | βœ… |
| No deserialization attacks (Pydantic validates) | βœ… |
| Session IDs are UUID4 (not sequential) | βœ… |
| No secrets in source code | βœ… |
| Environment variables for API keys | βœ… |

---

## PHASE 7: ARCHITECTURE AUDIT

### βœ… Architecture Is Clean

```
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  server/app.py (117 LoC)                                     β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚  β”‚ _sessions: dict[str, CustomerSupportEnv]                β”‚ β”‚
β”‚  β”‚ POST /reset β†’ new env β†’ store β†’ return obs              β”‚ β”‚
β”‚  β”‚ POST /step β†’ lookup env β†’ step β†’ cleanup if done        β”‚ β”‚
β”‚  β”‚ GET /state β†’ lookup env β†’ return state                  β”‚ β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β”‚                            β”‚                                 β”‚
β”‚  env/environment.py (258 LoC)                                β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚  β”‚ CustomerSupportEnv                                      β”‚ β”‚
β”‚  β”‚  .reset() β†’ pick ticket, build obs                      β”‚ β”‚
β”‚  β”‚  .step(action) β†’ reward + sentiment + customer reply    β”‚ β”‚
β”‚  β”‚  .state() β†’ full internal state                         β”‚ β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β”‚         β”‚              β”‚              β”‚                      β”‚
β”‚  env/reward_engine.py  env/ticket_store.py  env/graders/     β”‚
β”‚  (235 LoC)             (434 LoC)            (293 LoC)        β”‚
β”‚  VADER + TF-IDF        30 tickets           3 task graders   β”‚
β”‚  cosine loop detect    3 difficulty levels  deterministic     β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
```

**Key architectural wins:**
1. **No dead code.** Every file is imported and used.
2. **No contradictions.** `server/app.py` uses `CustomerSupportEnv` which uses `reward_engine` which uses `graders`. The pipeline is end-to-end connected.
3. **Single responsibility.** Each module does one thing.
4. **Inference is a client.** `inference.py` talks to the server via HTTP, not by importing env directly.

### ⚠️ Architectural Weaknesses

1. **In-memory only.** No persistence. Server restart = all sessions gone.
2. **Single-process only.** Cannot scale horizontally.
3. **No middleware.** No logging middleware, no timing middleware, no error tracking.

---

## PHASE 8: OBSERVABILITY AUDIT

### ❌ Minimal Observability

| Feature | Status |
|---------|:------:|
| Structured logging | ❌ None β€” no logging at all in server |
| Request timing | ❌ Not tracked |
| Error tracking (Sentry etc.) | ❌ Not configured |
| Metrics (Prometheus) | ❌ Not configured |
| Health check | βœ… `/health` returns `active_sessions` count |
| Request IDs | ❌ Not generated |
| Audit trail | ⚠️ Action log per session, but lost on cleanup |

The **only** observability is the `/health` endpoint reporting `active_sessions`. In a production incident, you would have:
- No logs to investigate
- No metrics to correlate
- No request traces
- No error rates

However, `uvicorn` does emit its own access logs to stdout, which Docker captures. This provides basic request-level visibility.

---

## PHASE 9: PERFORMANCE AUDIT

### Hot Path Analysis

For each `/step` request:
1. Dict lookup: O(1) βœ…
2. VADER sentiment: ~0.1ms per message βœ…
3. TF-IDF vectorizer: ~1-5ms (create + fit + transform on 2 docs) ⚠️
4. Regex matching for info patterns: O(n) where n = conversation length βœ…
5. Resolution signal matching: O(k) where k = num signals βœ…
6. Grader execution (on done only): ~1ms βœ…

**Bottleneck:** TF-IDF is the heaviest operation but still fast (~5ms).  
**Estimated max throughput:** ~200 req/s single-threaded.

### βœ… No Blocking I/O

The server is entirely CPU-bound. No database calls, no external API calls, no file I/O during request handling. This is a significant advantage for predictable latency.

---

## PHASE 10: RL ENVIRONMENT QUALITY AUDIT

### βœ… Reward Function Is Legitimate

| Signal | Implementation | Quality |
|--------|---------------|:-------:|
| **Tone** (20%) | VADER compound score, mapped [βˆ’1,1]β†’[0,1] | βœ… Real NLP |
| **Resolution** (40%) | Category-aware keyword matching | ⚠️ Passable |
| **Efficiency** (20%) | `1 - steps/max_steps` | βœ… Clean |
| **Accuracy** (20%) | Regex check for email, order ID, etc. | βœ… Deterministic |
| **Loop penalty** | Cosine similarity > 0.85 between consecutive agent messages | βœ… Real NLP |
| **Escalation penalty** | -0.3 for escalating low/medium tickets | βœ… Correct incentive |
| **Info bonus** | +0.1 for REQUEST_INFO when info is required | βœ… Good shaping |

### βœ… Graders Are Correct

Each task has a deterministic grader that checks specific behavioral criteria:
- **Easy:** Close + resolution language + no unnecessary escalation + info gathered
- **Medium:** Info gathering + resolution attempted + sentiment β‰₯ -0.5 + multi-turn
- **Hard:** Escalate + early (≀2 steps) + urgency in reason + no self-resolve attempt

The hard task is **genuinely counter-intuitive** β€” the correct behavior is to immediately escalate, which is the opposite of what most LLMs will attempt.

### ⚠️ RL Weaknesses

1. **Resolution scoring is still keyword-based.** The system says it's not keyword-stuffing, but `_RESOLUTION_SIGNALS` is literally a keyword list. An agent that says "refund credit reimburse" without context scores 1.0.

2. **Customer simulation is random.** `random.choice(replies)` means the same action can get different customer reactions. This adds noise to training signal.

3. **Tone scoring via VADER has known limitations.** VADER rates "I understand you are frustrated" as negative (because of "frustrated"), even though it's empathetic in context.

4. **No semantic understanding.** The accuracy check uses regex (email pattern, order ID pattern), not comprehension. An agent that says "fake@fake.com" satisfies the email requirement.

---

## PHASE 11: EDGE CASE AUDIT

### Tested Edge Cases

| Edge Case | Behavior | Status |
|-----------|----------|:------:|
| `action_type: "respond"` with `message: None` | Falls back to `"[respond]"` | βœ… Handled |
| `action_type: "escalate"` with no `reason` | Falls back to `"[escalate]"` | βœ… Handled |
| Empty string message `""` | Tone = 0.5, loop detection safe | βœ… Handled |
| All tickets exhausted | Not applicable β€” random choice from pool | βœ… N/A |
| Same ticket picked twice | Shallow copy prevents cross-session mutation | βœ… Safe |
| `max_steps = 0` | Efficiency score returns 0.0 (guarded) | βœ… Handled |
| Unknown `info_type` in ticket | Fallback to customer message count | ⚠️ Weak but handled |
| Concurrent dict modification | Not possible with single-threaded uvicorn | βœ… Safe (current config) |

### ❌ Untested Edge Cases

1. **Unicode/emoji in messages:** VADER may not handle emoji well. TF-IDF may tokenize them unexpectedly.
2. **Extremely long messages (10K+ chars):** No input sanitization. History grows unbounded within a session.
3. **HTML/script injection in messages:** Messages stored as strings, not sanitized. If rendered in a web UI, XSS is possible.

---

## PHASE 12: FINAL VERDICT

### Comparison with Original Codebase

| Criterion | Original (`customer_support_env`) | Meta Hack (`meta_hack`) |
|-----------|:-:|:-:|
| Architecture coherence | ❌ Dead code, contradictions | βœ… Clean, connected |
| Session isolation | ❌ Global mutable state | βœ… UUID-based, per-env |
| Session cleanup | ❌ Never cleaned | βœ… Cleaned on done |
| Reward function | ❌ Hardcoded 1.0 | βœ… Real multi-dimensional |
| Episode termination | ❌ Hardcoded False | βœ… Proper termination |
| Grading system | ❌ Non-functional | βœ… 3 deterministic graders |
| Tests | ❌ None | βœ… 363-line test suite |
| Observability | ❌ Lying logs | ⚠️ Minimal but honest |
| Authentication | ❌ None | ❌ None |
| Rate limiting | ❌ None | ❌ None |
| Scalability | ❌ Crashes at 50 sessions | ⚠️ Survives moderate load |

### Scorecard

```
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  FINAL AUDIT VERDICT: meta_hack                              β”‚
β”‚                                                              β”‚
β”‚  Overall Score: 5.5/10                                       β”‚
β”‚  Classification: Strong Hackathon Entry                      β”‚
β”‚                                                              β”‚
β”‚  βœ… Architecture:     7.5/10 β€” Clean, modular, connected     β”‚
β”‚  βœ… RL Quality:       7.0/10 β€” Legitimate reward shaping     β”‚
β”‚  βœ… Code Quality:     8.0/10 β€” Well-written, Pythonic        β”‚
β”‚  βœ… Test Coverage:    6.0/10 β€” Good unit tests, no e2e       β”‚
β”‚  🟑 Scalability:     4.0/10 β€” Single-process, no eviction   β”‚
β”‚  🟑 Observability:   3.0/10 β€” Minimal logging               β”‚
β”‚  πŸ”΄ Security:        2.0/10 β€” Zero auth, zero rate limit    β”‚
β”‚  πŸ”΄ Prod. Readiness: 3.0/10 β€” Missing critical infra        β”‚
β”‚                                                              β”‚
β”‚  Compared to original repo: β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–‘β–‘ (1.3 β†’ 5.5)          β”‚
β”‚  A 4.2-point improvement.                                    β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
```

---

## RECOMMENDATIONS

### Must-Fix for Production

1. **Add authentication** β€” JWT/API key middleware
2. **Add rate limiting** β€” `slowapi` or custom middleware (e.g., 10 resets/min per IP)
3. **Add session TTL** β€” Background task to evict sessions older than N minutes
4. **Add max session limit** β€” Reject `/reset` when `len(_sessions) > MAX`
5. **Add request body size limit** β€” FastAPI `Request` body limit or nginx proxy
6. **Externalize state** β€” Redis/Postgres for multi-worker deployment

### Should-Fix for Quality

7. **Add structured logging** β€” `structlog` or `python-json-logger`
8. **Add request timing middleware**
9. **Add CORS configuration**
10. **Seed `random` for reproducible tests**
11. **Move `re` import to top-level in `environment.py`**
12. **Add integration/e2e tests** β€” test the full HTTP flow, not just unit tests

### Nice-to-Have

13. **Prometheus metrics endpoint**
14. **OpenTelemetry tracing**
15. **Session persistence across restarts**
16. **Semantic resolution scoring** (small embedding model instead of keywords)

---

## SALVAGEABLE COMPONENTS

Unlike the original codebase, **almost everything in meta_hack is worth keeping:**

| Component | Verdict |
|-----------|:-------:|
| `env/models.py` | βœ… Keep as-is |
| `env/environment.py` | βœ… Keep β€” add TTL tracking |
| `env/reward_engine.py` | βœ… Keep β€” well-engineered |
| `env/ticket_store.py` | βœ… Keep β€” rich ticket data |
| `env/graders/` | βœ… Keep β€” deterministic, correct |
| `server/app.py` | ⚠️ Keep but add middleware layers |
| `inference.py` | βœ… Keep β€” clean client with failover |
| `tests/test_env.py` | βœ… Keep β€” extend with e2e tests |
| Dockerfile | βœ… Keep |
| docker-compose.yml | βœ… Keep |

**Nothing needs to be deleted. This is a real codebase, not a proof-of-concept.**

---

## PHASE 13: LIVE SERVER TESTS (Real-Time Proof)

> **Target:** `http://10.153.115.219:7860/`  
> **Tested:** 2026-04-08T22:55+05:30  
> **Method:** curl + Python `urllib` against deployed Docker instance

### 13.1 Customer Interaction Tests β€” All 3 Difficulty Levels

#### Easy: TKT-001 β€” Double Charge on Invoice #4521

| Step | Action | Reward | Tone | Resolution | Efficiency | Accuracy |
|:----:|--------|:------:|:----:|:----------:|:----------:|:--------:|
| 1 | `request_info` β€” ask for account email | β€” | β€” | β€” | β€” | β€” |
| 2 | `respond` β€” confirm refund processing | β€” | β€” | β€” | β€” | β€” |
| 3 | `close` β€” confirm $49.99 refund initiated | **0.7327** | 0.8348 | 0.7143 | 0.4000 | 1.0000 |

**Final Score: 1.0** βœ… β€” Grader awarded perfect score for: close action + refund language + email gathered + no escalation.

#### Medium: Multi-Turn Complaint Handling

| Step | Action | Summary |
|:----:|--------|---------|
| 1 | `respond` β€” empathize with frustration | Reward: varied, sentiment tracked |
| 2 | `request_info` β€” ask for account email + device info | Info bonus: 0.1 |
| 3 | `respond` β€” provide fix (cache clear + reset) | Resolution keywords hit |
| 4 | `close` β€” confirm fix applied | **Reward: 0.7373** |

**Final Score: 1.0** βœ… β€” Grader verified: info gathered, resolution attempted, sentiment β‰₯ -0.5, multi-turn β‰₯ 4.

#### Hard: SLA-Critical Escalation (Counter-Intuitive)

| Step | Action | Summary |
|:----:|--------|---------|
| 1 | `escalate` β€” immediate P0 escalation with urgency language | **Reward: 0.6482** |

**Final Score: 1.0** βœ… β€” Grader verified: escalated on step 1, SLA/critical/P0 in reason, no self-resolve before escalation.

> [!TIP]
> All 3 graders produce legitimate, differentiated scores. The reward function is **real** β€” not hardcoded. This is a 100% improvement over the original repo which returned `reward: 1.0` on every step regardless of agent behavior.

---

### 13.2 Adversarial Validation Tests

| Test | Input | Expected | Actual | Verdict |
|------|-------|:--------:|:------:|:-------:|
| A. Invalid session | `session_id=FAKE` | 404 | **404** | βœ… PASS |
| B. Invalid action | `action_type=DROP_TABLES` | 422 | **422** | βœ… PASS |
| C. Invalid task | `task=impossible` | 422 | **422** | βœ… PASS |
| D. Step after done | Step on closed session | 404/409 | **404** | βœ… PASS |
| E. Session cleanup | Create β†’ close β†’ check | count+1 β†’ count | **19β†’20β†’19** | βœ… PASS |
| F. No authentication | Reset with no credentials | Should reject | **HTTP 200** | ❌ FAIL |
| G. Rate limiting | 20 resets in rapid fire | Should limit | **0.3s, no limit** | ❌ FAIL |
| H. PII via /state | Send "SSN: 123-45-6789" then GET /state | Should redact | **Exposed** | ❌ FAIL |

**Error handling: 5/5 PASSED** β€” The server correctly validates all inputs.  
**Security: 0/3 PASSED** β€” Zero authentication, zero rate limiting, full PII exposure via `/state`.

---

### 13.3 Session Flood Stress Test

```
╔═══════════════════════════════════════╗
β•‘  STRESS TEST: Session Flood           β•‘
β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•
Starting sessions: 41
After 200 resets:  241 sessions   (200 success / 0 fail)  β€” 0.20s (986 req/s)
After 700 resets:  741 sessions   (500 success / 0 fail)  β€” 0.52s (959 req/s)

FINAL: Server alive βœ… | 741 zombie sessions
Estimated memory leak: ~7,410 KB (~7.2 MB)
```

**Key findings:**
- Server absorbed **700 zombie sessions at ~960 req/s** without degradation
- All 700 sessions were created by a **single attacker** with zero resistance
- **No session eviction** β€” all 741 zombies persist indefinitely
- Memory leak at ~10KB/session is linear β€” at this rate, 1 million sessions = ~10GB
- Server **did NOT crash** β€” a massive improvement over the original repo (which crashed at 50)

> [!CAUTION]
> While the server survived 700 sessions, the attack surface is trivial. A sustained `while true; do curl -X POST .../reset; done` would create ~960 sessions/second, consuming ~9.6MB/s of memory. The server would OOM in approximately **17 minutes** on a 1GB container.

---

### 13.4 Comparative Live Test Results

| Test | Original Repo | Meta Hack |
|------|:---:|:---:|
| Health check | βœ… | βœ… |
| Customer interaction (easy) | ❌ Hardcoded reward | βœ… Real score 1.0 |
| Customer interaction (hard) | ❌ No grader | βœ… Counter-intuitive grading works |
| Invalid input handling | ❌ Crashes | βœ… Proper 4xx codes |
| Session cleanup | ❌ Never | βœ… On done |
| 50 concurrent sessions | ❌ CRASHED | βœ… Survived |
| 700 concurrent sessions | πŸ’€ Would be dead | βœ… Survived |
| Authentication | ❌ None | ❌ None |
| Rate limiting | ❌ None | ❌ None |
| Crash under normal use | βœ… Crashed in 2 min | βœ… Stable |

---

*End of adversarial audit. The system is improvable, not replaceable.*