customer-support-env / docs /test_usage_report_2.md
lebiraja's picture
docs: reorganize .md files into docs/ and rewrite README for Round 2
a8f3713
|
Raw
History Blame Contribute Delete
28.9 kB

☠️ 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

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:

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

# 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:

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:

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

# 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

# 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

_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

# 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

  1. Add structured logging β€” structlog or python-json-logger
  2. Add request timing middleware
  3. Add CORS configuration
  4. Seed random for reproducible tests
  5. Move re import to top-level in environment.py
  6. Add integration/e2e tests β€” test the full HTTP flow, not just unit tests

Nice-to-Have

  1. Prometheus metrics endpoint
  2. OpenTelemetry tracing
  3. Session persistence across restarts
  4. 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.

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)

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.