Merge pull request #15 from DsThakurRawat/add-env-bank
Browse files- codereview_env/scenario_bank.py +887 -0
codereview_env/scenario_bank.py
ADDED
|
@@ -0,0 +1,887 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import random
|
| 2 |
+
from codereview_env.models import (
|
| 3 |
+
Scenario, FileChange, GroundTruthIssue, Category, Severity, TaskId, Verdict
|
| 4 |
+
)
|
| 5 |
+
|
| 6 |
+
def get_scenario(task_id: TaskId, seed: int) -> Scenario:
|
| 7 |
+
rng = random.Random(seed)
|
| 8 |
+
bank = SCENARIOS.get(task_id, [])
|
| 9 |
+
if not bank:
|
| 10 |
+
raise ValueError(f"No scenarios found for task: {task_id}")
|
| 11 |
+
|
| 12 |
+
import hashlib
|
| 13 |
+
import json
|
| 14 |
+
idx = rng.randint(0, len(bank) - 1)
|
| 15 |
+
scenario = bank[idx]
|
| 16 |
+
# Dynamic hash as per roadmap
|
| 17 |
+
scenario_dict = scenario.model_dump()
|
| 18 |
+
content = json.dumps(scenario_dict, sort_keys=True).encode()
|
| 19 |
+
scenario.hash = hashlib.md5(content).hexdigest()
|
| 20 |
+
return scenario
|
| 21 |
+
|
| 22 |
+
# --- BUG DETECTION SCENARIOS (10) ---
|
| 23 |
+
BUG_SCENARIOS = [
|
| 24 |
+
Scenario(
|
| 25 |
+
task_id=TaskId.BUG_DETECTION,
|
| 26 |
+
pr_title="Fix: Off-by-one error in list processing",
|
| 27 |
+
pr_description="Processing elements in the list but missing the last one due to range(len(x)-1).",
|
| 28 |
+
files_changed=[
|
| 29 |
+
FileChange(
|
| 30 |
+
filename="utils.py",
|
| 31 |
+
patch="""@@ -10,1 +10,1 @@
|
| 32 |
+
- for i in range(len(items) - 1):
|
| 33 |
+
+ for i in range(len(items)):
|
| 34 |
+
+ print(items[i])""",
|
| 35 |
+
additions=2,
|
| 36 |
+
deletions=1
|
| 37 |
+
)
|
| 38 |
+
],
|
| 39 |
+
ground_truth_issues=[
|
| 40 |
+
GroundTruthIssue(
|
| 41 |
+
id="bug_001",
|
| 42 |
+
category=Category.BUG,
|
| 43 |
+
severity=Severity.MEDIUM,
|
| 44 |
+
filename="utils.py",
|
| 45 |
+
line_number=10,
|
| 46 |
+
description="Off-by-one error in list processing loop. Should use range(len(items)).",
|
| 47 |
+
keywords=["off-by-one", "index", "out of range", "boundary", "loop"]
|
| 48 |
+
)
|
| 49 |
+
],
|
| 50 |
+
hash="bug_001_h"
|
| 51 |
+
),
|
| 52 |
+
Scenario(
|
| 53 |
+
task_id=TaskId.BUG_DETECTION,
|
| 54 |
+
pr_title="Add: Mutable default argument to fetch_data",
|
| 55 |
+
pr_description="New helper to fetch data with a default empty list for items.",
|
| 56 |
+
files_changed=[
|
| 57 |
+
FileChange(
|
| 58 |
+
filename="api_client.py",
|
| 59 |
+
patch="""@@ -5,1 +5,1 @@
|
| 60 |
+
-def fetch_data(url: str, headers: dict = None):
|
| 61 |
+
+def fetch_data(url: str, items: list = []):
|
| 62 |
+
+ items.append(url)
|
| 63 |
+
+ return items""",
|
| 64 |
+
additions=2,
|
| 65 |
+
deletions=1
|
| 66 |
+
)
|
| 67 |
+
],
|
| 68 |
+
ground_truth_issues=[
|
| 69 |
+
GroundTruthIssue(
|
| 70 |
+
id="bug_002",
|
| 71 |
+
category=Category.BUG,
|
| 72 |
+
severity=Severity.HIGH,
|
| 73 |
+
filename="api_client.py",
|
| 74 |
+
line_number=5,
|
| 75 |
+
description="Mutable default argument in Python. Items list will be shared across calls.",
|
| 76 |
+
keywords=["mutable", "default", "argument", "persistent", "shared state"]
|
| 77 |
+
)
|
| 78 |
+
],
|
| 79 |
+
hash="bug_002_h"
|
| 80 |
+
),
|
| 81 |
+
Scenario(
|
| 82 |
+
task_id=TaskId.BUG_DETECTION,
|
| 83 |
+
pr_title="None dereference in user lookup",
|
| 84 |
+
pr_description="Lookup user by ID and access properties without guard.",
|
| 85 |
+
files_changed=[
|
| 86 |
+
FileChange(
|
| 87 |
+
filename="auth.py",
|
| 88 |
+
patch="""@@ -15,1 +15,2 @@
|
| 89 |
+
def get_user_role(uid):
|
| 90 |
+
- user = db.users.get(uid)
|
| 91 |
+
+ user = db.users.get(uid)
|
| 92 |
+
+ return user.role""",
|
| 93 |
+
additions=1,
|
| 94 |
+
deletions=1
|
| 95 |
+
)
|
| 96 |
+
],
|
| 97 |
+
ground_truth_issues=[
|
| 98 |
+
GroundTruthIssue(
|
| 99 |
+
id="bug_003",
|
| 100 |
+
category=Category.BUG,
|
| 101 |
+
severity=Severity.HIGH,
|
| 102 |
+
filename="auth.py",
|
| 103 |
+
line_number=16,
|
| 104 |
+
description="Potential None dereference. user might be None if ID is not found.",
|
| 105 |
+
keywords=["None", "null check", "KeyError", "AttributeError", "guard clause"]
|
| 106 |
+
)
|
| 107 |
+
],
|
| 108 |
+
hash="bug_003_h"
|
| 109 |
+
),
|
| 110 |
+
Scenario(
|
| 111 |
+
task_id=TaskId.BUG_DETECTION,
|
| 112 |
+
pr_title="Assignment in conditional",
|
| 113 |
+
pr_description="Check if setting is enabled and update status.",
|
| 114 |
+
files_changed=[
|
| 115 |
+
FileChange(
|
| 116 |
+
filename="config_manager.py",
|
| 117 |
+
patch="""@@ -8,1 +8,1 @@
|
| 118 |
+
- if config.enabled == True:
|
| 119 |
+
+ if config.status = "active":
|
| 120 |
+
+ process_config(config)""",
|
| 121 |
+
additions=1,
|
| 122 |
+
deletions=1
|
| 123 |
+
)
|
| 124 |
+
],
|
| 125 |
+
ground_truth_issues=[
|
| 126 |
+
GroundTruthIssue(
|
| 127 |
+
id="bug_004",
|
| 128 |
+
category=Category.BUG,
|
| 129 |
+
severity=Severity.MEDIUM,
|
| 130 |
+
filename="config_manager.py",
|
| 131 |
+
line_number=8,
|
| 132 |
+
description="Assignment operator used in conditional statement. Should be '=='.",
|
| 133 |
+
keywords=["assignment", "comparison", "conditional", "operator", "typo"]
|
| 134 |
+
)
|
| 135 |
+
],
|
| 136 |
+
hash="bug_004_h"
|
| 137 |
+
),
|
| 138 |
+
Scenario(
|
| 139 |
+
task_id=TaskId.BUG_DETECTION,
|
| 140 |
+
pr_title="Integer overflow in loop counter",
|
| 141 |
+
pr_description="Counter for processed records doesn't reset.",
|
| 142 |
+
files_changed=[
|
| 143 |
+
FileChange(
|
| 144 |
+
filename="processor.py",
|
| 145 |
+
patch="""@@ -25,1 +25,3 @@
|
| 146 |
+
- processed_count = 0
|
| 147 |
+
+ processed_count += 1
|
| 148 |
+
+ if processed_count > 1000000:
|
| 149 |
+
+ log.warning("High volume")""",
|
| 150 |
+
additions=2,
|
| 151 |
+
deletions=1
|
| 152 |
+
)
|
| 153 |
+
],
|
| 154 |
+
ground_truth_issues=[
|
| 155 |
+
GroundTruthIssue(
|
| 156 |
+
id="bug_005",
|
| 157 |
+
category=Category.BUG,
|
| 158 |
+
severity=Severity.MEDIUM,
|
| 159 |
+
filename="processor.py",
|
| 160 |
+
line_number=25,
|
| 161 |
+
description="Integer overflow or lack of reset in counter. Can lead to boundary issues.",
|
| 162 |
+
keywords=["overflow", "counter", "integer", "reset", "boundary", "infinite"]
|
| 163 |
+
)
|
| 164 |
+
],
|
| 165 |
+
hash="bug_005_h"
|
| 166 |
+
),
|
| 167 |
+
Scenario(
|
| 168 |
+
task_id=TaskId.BUG_DETECTION,
|
| 169 |
+
pr_title="Race condition in cache update",
|
| 170 |
+
pr_description="Parallel threads updating shared cache without locking.",
|
| 171 |
+
files_changed=[
|
| 172 |
+
FileChange(
|
| 173 |
+
filename="cache_store.py",
|
| 174 |
+
patch="""@@ -12,1 +12,2 @@
|
| 175 |
+
def update_cache(key, val):
|
| 176 |
+
- cache[key] = val
|
| 177 |
+
+ old_val = cache[key]
|
| 178 |
+
+ cache[key] = old_val + val""",
|
| 179 |
+
additions=1,
|
| 180 |
+
deletions=1
|
| 181 |
+
)
|
| 182 |
+
],
|
| 183 |
+
ground_truth_issues=[
|
| 184 |
+
GroundTruthIssue(
|
| 185 |
+
id="bug_006",
|
| 186 |
+
category=Category.BUG,
|
| 187 |
+
severity=Severity.HIGH,
|
| 188 |
+
filename="cache_store.py",
|
| 189 |
+
line_number=13,
|
| 190 |
+
description="Race condition in cache update. Multiple threads may overwrite each other's increments.",
|
| 191 |
+
keywords=["race condition", "thread", "concurrent", "lock", "atomic", "synchronization"]
|
| 192 |
+
)
|
| 193 |
+
],
|
| 194 |
+
hash="bug_006_h"
|
| 195 |
+
),
|
| 196 |
+
Scenario(
|
| 197 |
+
task_id=TaskId.BUG_DETECTION,
|
| 198 |
+
pr_title="Broad exception catch",
|
| 199 |
+
pr_description="Swallow all errors during data import.",
|
| 200 |
+
files_changed=[
|
| 201 |
+
FileChange(
|
| 202 |
+
filename="importer.py",
|
| 203 |
+
patch="""@@ -30,1 +30,2 @@
|
| 204 |
+
- import_data(file)
|
| 205 |
+
+ try: import_data(file)
|
| 206 |
+
+ except Exception: pass""",
|
| 207 |
+
additions=1,
|
| 208 |
+
deletions=1
|
| 209 |
+
)
|
| 210 |
+
],
|
| 211 |
+
ground_truth_issues=[
|
| 212 |
+
GroundTruthIssue(
|
| 213 |
+
id="bug_007",
|
| 214 |
+
category=Category.BUG,
|
| 215 |
+
severity=Severity.MEDIUM,
|
| 216 |
+
filename="importer.py",
|
| 217 |
+
line_number=31,
|
| 218 |
+
description="Broad exception catch-all. Swallows all errors including keyboard interrupts.",
|
| 219 |
+
keywords=["exception", "broad", "catch-all", "specific", "silent", "swallow"]
|
| 220 |
+
)
|
| 221 |
+
],
|
| 222 |
+
hash="bug_007_h"
|
| 223 |
+
),
|
| 224 |
+
Scenario(
|
| 225 |
+
task_id=TaskId.BUG_DETECTION,
|
| 226 |
+
pr_title="Float equality comparison",
|
| 227 |
+
pr_description="Check if sensor reading is exactly 0.1.",
|
| 228 |
+
files_changed=[
|
| 229 |
+
FileChange(
|
| 230 |
+
filename="sensors.py",
|
| 231 |
+
patch="""@@ -7,1 +7,1 @@
|
| 232 |
+
- if reading < 0.1:
|
| 233 |
+
+ if reading == 0.1:
|
| 234 |
+
+ trigger_alarm()""",
|
| 235 |
+
additions=1,
|
| 236 |
+
deletions=1
|
| 237 |
+
)
|
| 238 |
+
],
|
| 239 |
+
ground_truth_issues=[
|
| 240 |
+
GroundTruthIssue(
|
| 241 |
+
id="bug_008",
|
| 242 |
+
category=Category.BUG,
|
| 243 |
+
severity=Severity.LOW,
|
| 244 |
+
filename="sensors.py",
|
| 245 |
+
line_number=7,
|
| 246 |
+
description="Floating point equality comparison is unreliable due to precision.",
|
| 247 |
+
keywords=["float", "equality", "precision", "epsilon", "comparison", "IEEE 754"]
|
| 248 |
+
)
|
| 249 |
+
],
|
| 250 |
+
hash="bug_008_h"
|
| 251 |
+
),
|
| 252 |
+
Scenario(
|
| 253 |
+
task_id=TaskId.BUG_DETECTION,
|
| 254 |
+
pr_title="Return inside finally block",
|
| 255 |
+
pr_description="Override potential errors with a success status.",
|
| 256 |
+
files_changed=[
|
| 257 |
+
FileChange(
|
| 258 |
+
filename="worker.py",
|
| 259 |
+
patch="""@@ -44,1 +44,3 @@
|
| 260 |
+
- process()
|
| 261 |
+
+ try: process()
|
| 262 |
+
+ finally:
|
| 263 |
+
+ return "success" """,
|
| 264 |
+
additions=2,
|
| 265 |
+
deletions=1
|
| 266 |
+
)
|
| 267 |
+
],
|
| 268 |
+
ground_truth_issues=[
|
| 269 |
+
GroundTruthIssue(
|
| 270 |
+
id="bug_009",
|
| 271 |
+
category=Category.BUG,
|
| 272 |
+
severity=Severity.MEDIUM,
|
| 273 |
+
filename="worker.py",
|
| 274 |
+
line_number=46,
|
| 275 |
+
description="Return inside finally block overrides and suppresses exceptions.",
|
| 276 |
+
keywords=["finally", "return", "exception", "control flow", "override", "suppress"]
|
| 277 |
+
)
|
| 278 |
+
],
|
| 279 |
+
hash="bug_009_h"
|
| 280 |
+
),
|
| 281 |
+
Scenario(
|
| 282 |
+
task_id=TaskId.BUG_DETECTION,
|
| 283 |
+
pr_title="Type coercion bug",
|
| 284 |
+
pr_description="Compare incoming string ID with integer constant.",
|
| 285 |
+
files_changed=[
|
| 286 |
+
FileChange(
|
| 287 |
+
filename="validator.py",
|
| 288 |
+
patch="""@@ -12,1 +12,1 @@
|
| 289 |
+
- if int(obj_id) == 5:
|
| 290 |
+
+ if obj_id == 5:
|
| 291 |
+
+ return True""",
|
| 292 |
+
additions=1,
|
| 293 |
+
deletions=1
|
| 294 |
+
)
|
| 295 |
+
],
|
| 296 |
+
ground_truth_issues=[
|
| 297 |
+
GroundTruthIssue(
|
| 298 |
+
id="bug_010",
|
| 299 |
+
category=Category.BUG,
|
| 300 |
+
severity=Severity.MEDIUM,
|
| 301 |
+
filename="validator.py",
|
| 302 |
+
line_number=12,
|
| 303 |
+
description="Type mismatch: comparing string obj_id with integer 5 will always be False.",
|
| 304 |
+
keywords=["type", "coercion", "comparison", "string", "integer", "implicit"]
|
| 305 |
+
)
|
| 306 |
+
],
|
| 307 |
+
hash="bug_010_h"
|
| 308 |
+
)
|
| 309 |
+
]
|
| 310 |
+
|
| 311 |
+
# --- SECURITY AUDIT SCENARIOS (10) ---
|
| 312 |
+
SECURITY_SCENARIOS = [
|
| 313 |
+
Scenario(
|
| 314 |
+
task_id=TaskId.SECURITY_AUDIT,
|
| 315 |
+
pr_title="Security: Implement raw SQL query for performance",
|
| 316 |
+
pr_description="Bypassing ORM for a specific complex query to improve performance.",
|
| 317 |
+
files_changed=[
|
| 318 |
+
FileChange(
|
| 319 |
+
filename="db/queries.py",
|
| 320 |
+
patch="""@@ -42,1 +42,1 @@
|
| 321 |
+
- return User.objects.filter(username=name)
|
| 322 |
+
+ return User.objects.raw(f"SELECT * FROM users WHERE username = '{name}'")""",
|
| 323 |
+
additions=1,
|
| 324 |
+
deletions=1
|
| 325 |
+
)
|
| 326 |
+
],
|
| 327 |
+
ground_truth_issues=[
|
| 328 |
+
GroundTruthIssue(
|
| 329 |
+
id="sec_001",
|
| 330 |
+
category=Category.SECURITY,
|
| 331 |
+
severity=Severity.CRITICAL,
|
| 332 |
+
filename="db/queries.py",
|
| 333 |
+
line_number=42,
|
| 334 |
+
description="SQL injection vulnerability via f-string in raw query. Use parameterized queries.",
|
| 335 |
+
keywords=["SQL injection", "parameterized", "f-string", "raw query", "exploit"]
|
| 336 |
+
)
|
| 337 |
+
],
|
| 338 |
+
hash="sec_001_h"
|
| 339 |
+
),
|
| 340 |
+
Scenario(
|
| 341 |
+
task_id=TaskId.SECURITY_AUDIT,
|
| 342 |
+
pr_title="Config: Add development secret key",
|
| 343 |
+
pr_description="Setting a default secret key for local development convenience.",
|
| 344 |
+
files_changed=[
|
| 345 |
+
FileChange(
|
| 346 |
+
filename="settings.py",
|
| 347 |
+
patch="""@@ -20,1 +20,1 @@
|
| 348 |
+
-SECRET_KEY = os.environ.get('SECRET_KEY')
|
| 349 |
+
+SECRET_KEY = "django-insecure-dev-key-12345" """,
|
| 350 |
+
additions=1,
|
| 351 |
+
deletions=1
|
| 352 |
+
)
|
| 353 |
+
],
|
| 354 |
+
ground_truth_issues=[
|
| 355 |
+
GroundTruthIssue(
|
| 356 |
+
id="sec_002",
|
| 357 |
+
category=Category.SECURITY,
|
| 358 |
+
severity=Severity.HIGH,
|
| 359 |
+
filename="settings.py",
|
| 360 |
+
line_number=20,
|
| 361 |
+
description="Hardcoded secret key in configuration. Should use environment variables.",
|
| 362 |
+
keywords=["hardcoded", "secret", "environment variable", ".env", "credential", "exposure"]
|
| 363 |
+
)
|
| 364 |
+
],
|
| 365 |
+
hash="sec_002_h"
|
| 366 |
+
),
|
| 367 |
+
Scenario(
|
| 368 |
+
task_id=TaskId.SECURITY_AUDIT,
|
| 369 |
+
pr_title="JWT: Disable verification for internal testing",
|
| 370 |
+
pr_description="Allow bypassing JWT checks for faster local development loop.",
|
| 371 |
+
files_changed=[
|
| 372 |
+
FileChange(
|
| 373 |
+
filename="tokens.py",
|
| 374 |
+
patch="""@@ -10,1 +10,1 @@
|
| 375 |
+
- payload = jwt.decode(token, secret, algorithms=["HS256"])
|
| 376 |
+
+ payload = jwt.decode(token, verify=False, algorithms=["HS256"])""",
|
| 377 |
+
additions=1,
|
| 378 |
+
deletions=1
|
| 379 |
+
)
|
| 380 |
+
],
|
| 381 |
+
ground_truth_issues=[
|
| 382 |
+
GroundTruthIssue(
|
| 383 |
+
id="sec_003",
|
| 384 |
+
category=Category.SECURITY,
|
| 385 |
+
severity=Severity.CRITICAL,
|
| 386 |
+
filename="tokens.py",
|
| 387 |
+
line_number=10,
|
| 388 |
+
description="JWT decoded without verification. Attackers can bypass authentication.",
|
| 389 |
+
keywords=["JWT", "signature", "verification", "algorithm", "none", "bypass"]
|
| 390 |
+
)
|
| 391 |
+
],
|
| 392 |
+
hash="sec_003_h"
|
| 393 |
+
),
|
| 394 |
+
Scenario(
|
| 395 |
+
task_id=TaskId.SECURITY_AUDIT,
|
| 396 |
+
pr_title="XSS: Render user bio directly",
|
| 397 |
+
pr_description="Enabling rich text in user bios by using mark_safe.",
|
| 398 |
+
files_changed=[
|
| 399 |
+
FileChange(
|
| 400 |
+
filename="templates/profile.html",
|
| 401 |
+
patch="""@@ -5,1 +5,1 @@
|
| 402 |
+
- <div class="bio">{{ user.bio }}</div>
|
| 403 |
+
+ <div class="bio">{{ user.bio | mark_safe }}</div>""",
|
| 404 |
+
additions=1,
|
| 405 |
+
deletions=1
|
| 406 |
+
)
|
| 407 |
+
],
|
| 408 |
+
ground_truth_issues=[
|
| 409 |
+
GroundTruthIssue(
|
| 410 |
+
id="sec_004",
|
| 411 |
+
category=Category.SECURITY,
|
| 412 |
+
severity=Severity.HIGH,
|
| 413 |
+
filename="templates/profile.html",
|
| 414 |
+
line_number=5,
|
| 415 |
+
description="Cross-site scripting (XSS) via unescaped template variable. sanitize user input.",
|
| 416 |
+
keywords=["XSS", "cross-site scripting", "mark_safe", "escape", "sanitize", "inject"]
|
| 417 |
+
)
|
| 418 |
+
],
|
| 419 |
+
hash="sec_004_h"
|
| 420 |
+
),
|
| 421 |
+
Scenario(
|
| 422 |
+
task_id=TaskId.SECURITY_AUDIT,
|
| 423 |
+
pr_title="File: Open local logs",
|
| 424 |
+
pr_description="New endpoint to read local audit logs based on path.",
|
| 425 |
+
files_changed=[
|
| 426 |
+
FileChange(
|
| 427 |
+
filename="logs_viewer.py",
|
| 428 |
+
patch="""@@ -12,1 +12,2 @@
|
| 429 |
+
def get_log(path):
|
| 430 |
+
- return open('/var/log/app.log').read()
|
| 431 |
+
+ return open('/var/log/' + path).read()""",
|
| 432 |
+
additions=1,
|
| 433 |
+
deletions=1
|
| 434 |
+
)
|
| 435 |
+
],
|
| 436 |
+
ground_truth_issues=[
|
| 437 |
+
GroundTruthIssue(
|
| 438 |
+
id="sec_005",
|
| 439 |
+
category=Category.SECURITY,
|
| 440 |
+
severity=Severity.HIGH,
|
| 441 |
+
filename="logs_viewer.py",
|
| 442 |
+
line_number=13,
|
| 443 |
+
description="Path traversal vulnerability. Allow reading any file using ../ notation.",
|
| 444 |
+
keywords=["path traversal", "directory", "normalization", "join", "sanitize", "escape"]
|
| 445 |
+
)
|
| 446 |
+
],
|
| 447 |
+
hash="sec_005_h"
|
| 448 |
+
),
|
| 449 |
+
Scenario(
|
| 450 |
+
task_id=TaskId.SECURITY_AUDIT,
|
| 451 |
+
pr_title="Serialization: Load user state from pickle",
|
| 452 |
+
pr_description="Faster state loading by using pickle format for internal caches.",
|
| 453 |
+
files_changed=[
|
| 454 |
+
FileChange(
|
| 455 |
+
filename="cache_util.py",
|
| 456 |
+
patch="""@@ -8,1 +8,1 @@
|
| 457 |
+
- return json.loads(data)
|
| 458 |
+
+ return pickle.loads(data)""",
|
| 459 |
+
additions=1,
|
| 460 |
+
deletions=1
|
| 461 |
+
)
|
| 462 |
+
],
|
| 463 |
+
ground_truth_issues=[
|
| 464 |
+
GroundTruthIssue(
|
| 465 |
+
id="sec_006",
|
| 466 |
+
category=Category.SECURITY,
|
| 467 |
+
severity=Severity.CRITICAL,
|
| 468 |
+
filename="cache_util.py",
|
| 469 |
+
line_number=8,
|
| 470 |
+
description="Insecure deserialization using pickle leads to Arbitrary Code Execution (RCE).",
|
| 471 |
+
keywords=["deserialization", "pickle", "arbitrary code", "RCE", "untrusted", "injection"]
|
| 472 |
+
)
|
| 473 |
+
],
|
| 474 |
+
hash="sec_006_h"
|
| 475 |
+
),
|
| 476 |
+
Scenario(
|
| 477 |
+
task_id=TaskId.SECURITY_AUDIT,
|
| 478 |
+
pr_title="CORS: Allow all origins for API",
|
| 479 |
+
pr_description="Resolving frontend browser errors by allowing all origins.",
|
| 480 |
+
files_changed=[
|
| 481 |
+
FileChange(
|
| 482 |
+
filename="api_gateway.py",
|
| 483 |
+
patch="""@@ -15,1 +15,1 @@
|
| 484 |
+
- allow_origins=["https://myapp.com"],
|
| 485 |
+
+ allow_origins=["*"],""",
|
| 486 |
+
additions=1,
|
| 487 |
+
deletions=1
|
| 488 |
+
)
|
| 489 |
+
],
|
| 490 |
+
ground_truth_issues=[
|
| 491 |
+
GroundTruthIssue(
|
| 492 |
+
id="sec_007",
|
| 493 |
+
category=Category.SECURITY,
|
| 494 |
+
severity=Severity.MEDIUM,
|
| 495 |
+
filename="api_gateway.py",
|
| 496 |
+
line_number=15,
|
| 497 |
+
description="Broad CORS policy (*) allows sensitive data exposure to arbitrary websites.",
|
| 498 |
+
keywords=["CORS", "wildcard", "origin", "cross-origin", "authentication", "header"]
|
| 499 |
+
)
|
| 500 |
+
],
|
| 501 |
+
hash="sec_007_h"
|
| 502 |
+
),
|
| 503 |
+
Scenario(
|
| 504 |
+
task_id=TaskId.SECURITY_AUDIT,
|
| 505 |
+
pr_title="Pass: Compare hashes directly",
|
| 506 |
+
pr_description="Faster password check by using native equality.",
|
| 507 |
+
files_changed=[
|
| 508 |
+
FileChange(
|
| 509 |
+
filename="pass_verify.py",
|
| 510 |
+
patch="""@@ -10,1 +10,1 @@
|
| 511 |
+
- return hmac.compare_digest(h1, h2)
|
| 512 |
+
+ return h1 == h2""",
|
| 513 |
+
additions=1,
|
| 514 |
+
deletions=1
|
| 515 |
+
)
|
| 516 |
+
],
|
| 517 |
+
ground_truth_issues=[
|
| 518 |
+
GroundTruthIssue(
|
| 519 |
+
id="sec_008",
|
| 520 |
+
category=Category.SECURITY,
|
| 521 |
+
severity=Severity.MEDIUM,
|
| 522 |
+
filename="pass_verify.py",
|
| 523 |
+
line_number=10,
|
| 524 |
+
description="Timing attack vulnerability in password comparison. Use constant-time comparison.",
|
| 525 |
+
keywords=["timing attack", "constant time", "hmac", "comparison", "side channel"]
|
| 526 |
+
)
|
| 527 |
+
],
|
| 528 |
+
hash="sec_008_h"
|
| 529 |
+
),
|
| 530 |
+
Scenario(
|
| 531 |
+
task_id=TaskId.SECURITY_AUDIT,
|
| 532 |
+
pr_title="Auth: Remove login limit",
|
| 533 |
+
pr_description="Allowing multiple login attempts for users who forgot passwords.",
|
| 534 |
+
files_changed=[
|
| 535 |
+
FileChange(
|
| 536 |
+
filename="login_handler.py",
|
| 537 |
+
patch="""@@ -12,1 +12,0 @@
|
| 538 |
+
- if check_rate_limit(ip): return error()""",
|
| 539 |
+
additions=0,
|
| 540 |
+
deletions=1
|
| 541 |
+
)
|
| 542 |
+
],
|
| 543 |
+
ground_truth_issues=[
|
| 544 |
+
GroundTruthIssue(
|
| 545 |
+
id="sec_009",
|
| 546 |
+
category=Category.SECURITY,
|
| 547 |
+
severity=Severity.MEDIUM,
|
| 548 |
+
filename="login_handler.py",
|
| 549 |
+
line_number=12,
|
| 550 |
+
description="Missing rate limiting on login endpoint enables brute-force attacks.",
|
| 551 |
+
keywords=["rate limit", "brute force", "throttle", "attempt", "lockout", "login"]
|
| 552 |
+
)
|
| 553 |
+
],
|
| 554 |
+
hash="sec_009_h"
|
| 555 |
+
),
|
| 556 |
+
Scenario(
|
| 557 |
+
task_id=TaskId.SECURITY_AUDIT,
|
| 558 |
+
pr_title="Debug: Enable production stack traces",
|
| 559 |
+
pr_description="Better debugging in prod by enabling stack traces for 500 errors.",
|
| 560 |
+
files_changed=[
|
| 561 |
+
FileChange(
|
| 562 |
+
filename="prod_settings.py",
|
| 563 |
+
patch="""@@ -30,1 +30,1 @@
|
| 564 |
+
-DEBUG = False
|
| 565 |
+
+DEBUG = True""",
|
| 566 |
+
additions=1,
|
| 567 |
+
deletions=1
|
| 568 |
+
)
|
| 569 |
+
],
|
| 570 |
+
ground_truth_issues=[
|
| 571 |
+
GroundTruthIssue(
|
| 572 |
+
id="sec_010",
|
| 573 |
+
category=Category.SECURITY,
|
| 574 |
+
severity=Severity.HIGH,
|
| 575 |
+
filename="prod_settings.py",
|
| 576 |
+
line_number=30,
|
| 577 |
+
description="DEBUG mode enabled in production. Exposes sensitive system information.",
|
| 578 |
+
keywords=["debug", "production", "sensitive", "stack trace", "information disclosure"]
|
| 579 |
+
)
|
| 580 |
+
],
|
| 581 |
+
hash="sec_010_h"
|
| 582 |
+
)
|
| 583 |
+
]
|
| 584 |
+
|
| 585 |
+
# --- ARCHITECTURAL REVIEW SCENARIOS (10) ---
|
| 586 |
+
ARCH_SCENARIOS = [
|
| 587 |
+
Scenario(
|
| 588 |
+
task_id=TaskId.ARCHITECTURAL_REVIEW,
|
| 589 |
+
pr_title="Refactor: Frontend direct DB access",
|
| 590 |
+
pr_description="Optimizing frontend by allowing direct database reads for dashboard data.",
|
| 591 |
+
files_changed=[
|
| 592 |
+
FileChange(
|
| 593 |
+
filename="services/dashboard.py",
|
| 594 |
+
patch="""@@ -5,1 +5,4 @@
|
| 595 |
+
- return requests.get(API_URL + '/stats').json()
|
| 596 |
+
+ import psycopg2
|
| 597 |
+
+ conn = psycopg2.connect(DB_URL)
|
| 598 |
+
+ cur = conn.cursor()
|
| 599 |
+
+ cur.execute('SELECT * FROM stats')
|
| 600 |
+
+ return cur.fetchall()""",
|
| 601 |
+
additions=5,
|
| 602 |
+
deletions=1
|
| 603 |
+
)
|
| 604 |
+
],
|
| 605 |
+
ground_truth_issues=[
|
| 606 |
+
GroundTruthIssue(
|
| 607 |
+
id="arch_001",
|
| 608 |
+
category=Category.ARCHITECTURE,
|
| 609 |
+
severity=Severity.CRITICAL,
|
| 610 |
+
filename="services/dashboard.py",
|
| 611 |
+
line_number=5,
|
| 612 |
+
description="Frontend service calling database directly bypassing the API layer. Violates separation of concerns.",
|
| 613 |
+
keywords=["direct access", "coupling", "separation of concerns", "architectural violation"],
|
| 614 |
+
required_verdict=Verdict.REQUEST_CHANGES
|
| 615 |
+
)
|
| 616 |
+
],
|
| 617 |
+
hash="arch_001_h"
|
| 618 |
+
),
|
| 619 |
+
Scenario(
|
| 620 |
+
task_id=TaskId.ARCHITECTURAL_REVIEW,
|
| 621 |
+
pr_title="Sync: HTTP call inside event handler",
|
| 622 |
+
pr_description="Ensuring user status is verified during login event processing.",
|
| 623 |
+
files_changed=[
|
| 624 |
+
FileChange(
|
| 625 |
+
filename="handlers/events.py",
|
| 626 |
+
patch="""@@ -15,1 +15,2 @@
|
| 627 |
+
def on_user_login(user_id):
|
| 628 |
+
- log.info(f"User {user_id} logged in")
|
| 629 |
+
+ resp = requests.get(f"http://auth-service/verify/{user_id}")
|
| 630 |
+
+ log.info(f"User {user_id} logged in: {resp.status_code}")""",
|
| 631 |
+
additions=2,
|
| 632 |
+
deletions=1
|
| 633 |
+
)
|
| 634 |
+
],
|
| 635 |
+
ground_truth_issues=[
|
| 636 |
+
GroundTruthIssue(
|
| 637 |
+
id="arch_002",
|
| 638 |
+
category=Category.ARCHITECTURE,
|
| 639 |
+
severity=Severity.HIGH,
|
| 640 |
+
filename="handlers/events.py",
|
| 641 |
+
line_number=15,
|
| 642 |
+
description="Synchronous HTTP call inside event handler blocks the event loop.",
|
| 643 |
+
keywords=["synchronous", "blocking", "event loop", "async", "non-blocking", "timeout"],
|
| 644 |
+
required_verdict=Verdict.REQUEST_CHANGES
|
| 645 |
+
)
|
| 646 |
+
],
|
| 647 |
+
hash="arch_002_h"
|
| 648 |
+
),
|
| 649 |
+
Scenario(
|
| 650 |
+
task_id=TaskId.ARCHITECTURAL_REVIEW,
|
| 651 |
+
pr_title="Reliability: Direct API call without retries",
|
| 652 |
+
pr_description="Call downstream billing service directly.",
|
| 653 |
+
files_changed=[
|
| 654 |
+
FileChange(
|
| 655 |
+
filename="billing_proxy.py",
|
| 656 |
+
patch="""@@ -10,1 +10,1 @@
|
| 657 |
+
- return resiliency.call_with_retry(BILLING_URL)
|
| 658 |
+
+ return requests.post(BILLING_URL, data=payload)""",
|
| 659 |
+
additions=1,
|
| 660 |
+
deletions=1
|
| 661 |
+
)
|
| 662 |
+
],
|
| 663 |
+
ground_truth_issues=[
|
| 664 |
+
GroundTruthIssue(
|
| 665 |
+
id="arch_003",
|
| 666 |
+
category=Category.ARCHITECTURE,
|
| 667 |
+
severity=Severity.MEDIUM,
|
| 668 |
+
filename="billing_proxy.py",
|
| 669 |
+
line_number=10,
|
| 670 |
+
description="Missing retry logic and circuit breaker on external API call.",
|
| 671 |
+
keywords=["retry", "circuit breaker", "resilience", "idempotent", "backoff", "failure"],
|
| 672 |
+
required_verdict=Verdict.REQUEST_CHANGES
|
| 673 |
+
)
|
| 674 |
+
],
|
| 675 |
+
hash="arch_003_h"
|
| 676 |
+
),
|
| 677 |
+
Scenario(
|
| 678 |
+
task_id=TaskId.ARCHITECTURAL_REVIEW,
|
| 679 |
+
pr_title="Design: Implement GlobalManager",
|
| 680 |
+
pr_description="Consolidating all managers into one for easier access.",
|
| 681 |
+
files_changed=[
|
| 682 |
+
FileChange(
|
| 683 |
+
filename="app_core.py",
|
| 684 |
+
patch="""@@ -1,1 +1,4 @@
|
| 685 |
+
-class App: pass
|
| 686 |
+
+class GlobalManager:
|
| 687 |
+
+ def handle_auth(self): pass
|
| 688 |
+
+ def handle_billing(self): pass
|
| 689 |
+
+ def handle_users(self): pass""",
|
| 690 |
+
additions=4,
|
| 691 |
+
deletions=1
|
| 692 |
+
)
|
| 693 |
+
],
|
| 694 |
+
ground_truth_issues=[
|
| 695 |
+
GroundTruthIssue(
|
| 696 |
+
id="arch_004",
|
| 697 |
+
category=Category.ARCHITECTURE,
|
| 698 |
+
severity=Severity.MEDIUM,
|
| 699 |
+
filename="app_core.py",
|
| 700 |
+
line_number=2,
|
| 701 |
+
description="God object pattern: one class handles unrelated domains (auth, billing, users).",
|
| 702 |
+
keywords=["single responsibility", "god object", "cohesion", "separation", "refactor"],
|
| 703 |
+
required_verdict=Verdict.REQUEST_CHANGES
|
| 704 |
+
)
|
| 705 |
+
],
|
| 706 |
+
hash="arch_004_h"
|
| 707 |
+
),
|
| 708 |
+
Scenario(
|
| 709 |
+
task_id=TaskId.ARCHITECTURAL_REVIEW,
|
| 710 |
+
pr_title="Db: Fetch users in loop",
|
| 711 |
+
pr_description="Process audit for all users one by one.",
|
| 712 |
+
files_changed=[
|
| 713 |
+
FileChange(
|
| 714 |
+
filename="audit_job.py",
|
| 715 |
+
patch="""@@ -5,2 +5,2 @@
|
| 716 |
+
- users = User.objects.all().prefetch_related('logs')
|
| 717 |
+
- for u in users: process(u)
|
| 718 |
+
+ for u_id in user_ids:
|
| 719 |
+
+ user = User.objects.get(id=u_id)
|
| 720 |
+
+ process(user)""",
|
| 721 |
+
additions=2,
|
| 722 |
+
deletions=2
|
| 723 |
+
)
|
| 724 |
+
],
|
| 725 |
+
ground_truth_issues=[
|
| 726 |
+
GroundTruthIssue(
|
| 727 |
+
id="arch_005",
|
| 728 |
+
category=Category.ARCHITECTURE,
|
| 729 |
+
severity=Severity.HIGH,
|
| 730 |
+
filename="audit_job.py",
|
| 731 |
+
line_number=6,
|
| 732 |
+
description="N+1 query problem: fetching user objects inside a loop.",
|
| 733 |
+
keywords=["N+1", "query", "loop", "batch", "eager load", "select_related"],
|
| 734 |
+
required_verdict=Verdict.REQUEST_CHANGES
|
| 735 |
+
)
|
| 736 |
+
],
|
| 737 |
+
hash="arch_005_h"
|
| 738 |
+
),
|
| 739 |
+
Scenario(
|
| 740 |
+
task_id=TaskId.ARCHITECTURAL_REVIEW,
|
| 741 |
+
pr_title="Api: Get all logs endpoint",
|
| 742 |
+
pr_description="Simple endpoint to fetch current log state.",
|
| 743 |
+
files_changed=[
|
| 744 |
+
FileChange(
|
| 745 |
+
filename="handlers/api.py",
|
| 746 |
+
patch="""@@ -20,1 +20,1 @@
|
| 747 |
+
-def get_logs(page, limit): return db.logs.all()[page*limit:(page+1)*limit]
|
| 748 |
+
+def get_logs(): return db.logs.all()""",
|
| 749 |
+
additions=1,
|
| 750 |
+
deletions=1
|
| 751 |
+
)
|
| 752 |
+
],
|
| 753 |
+
ground_truth_issues=[
|
| 754 |
+
GroundTruthIssue(
|
| 755 |
+
id="arch_006",
|
| 756 |
+
category=Category.ARCHITECTURE,
|
| 757 |
+
severity=Severity.MEDIUM,
|
| 758 |
+
filename="handlers/api.py",
|
| 759 |
+
line_number=20,
|
| 760 |
+
description="Missing pagination on endpoint. Can cause memory exhaustion on large datasets.",
|
| 761 |
+
keywords=["pagination", "limit", "offset", "memory", "unbounded", "cursor"],
|
| 762 |
+
required_verdict=Verdict.REQUEST_CHANGES
|
| 763 |
+
)
|
| 764 |
+
],
|
| 765 |
+
hash="arch_006_h"
|
| 766 |
+
),
|
| 767 |
+
Scenario(
|
| 768 |
+
task_id=TaskId.ARCHITECTURAL_REVIEW,
|
| 769 |
+
pr_title="File: Upload blocking",
|
| 770 |
+
pr_description="Directly saving large file uploads to disk in request thread.",
|
| 771 |
+
files_changed=[
|
| 772 |
+
FileChange(
|
| 773 |
+
filename="upload_service.py",
|
| 774 |
+
patch="""@@ -12,1 +12,1 @@
|
| 775 |
+
- await background_save(file)
|
| 776 |
+
+ file.save('/tmp/large_file')""",
|
| 777 |
+
additions=1,
|
| 778 |
+
deletions=1
|
| 779 |
+
)
|
| 780 |
+
],
|
| 781 |
+
ground_truth_issues=[
|
| 782 |
+
GroundTruthIssue(
|
| 783 |
+
id="arch_007",
|
| 784 |
+
category=Category.ARCHITECTURE,
|
| 785 |
+
severity=Severity.MEDIUM,
|
| 786 |
+
filename="upload_service.py",
|
| 787 |
+
line_number=13,
|
| 788 |
+
description="Synchronous file upload blocking the request thread. Use background tasks.",
|
| 789 |
+
keywords=["async", "upload", "background task", "streaming", "thread", "non-blocking"],
|
| 790 |
+
required_verdict=Verdict.REQUEST_CHANGES
|
| 791 |
+
)
|
| 792 |
+
],
|
| 793 |
+
hash="arch_007_h"
|
| 794 |
+
),
|
| 795 |
+
Scenario(
|
| 796 |
+
task_id=TaskId.ARCHITECTURAL_REVIEW,
|
| 797 |
+
pr_title="Payment: Direct mutation",
|
| 798 |
+
pr_description="Update balance directly on payment request.",
|
| 799 |
+
files_changed=[
|
| 800 |
+
FileChange(
|
| 801 |
+
filename="checkout.py",
|
| 802 |
+
patch="""@@ -8,1 +8,1 @@
|
| 803 |
+
- process_payment_with_idempotency(req)
|
| 804 |
+
+ user.balance -= req.amount""",
|
| 805 |
+
additions=1,
|
| 806 |
+
deletions=1
|
| 807 |
+
)
|
| 808 |
+
],
|
| 809 |
+
ground_truth_issues=[
|
| 810 |
+
GroundTruthIssue(
|
| 811 |
+
id="arch_008",
|
| 812 |
+
category=Category.ARCHITECTURE,
|
| 813 |
+
severity=Severity.HIGH,
|
| 814 |
+
filename="checkout.py",
|
| 815 |
+
line_number=8,
|
| 816 |
+
description="Missing idempotency key on payment mutation endpoint. Dangerous on retries.",
|
| 817 |
+
keywords=["idempotency", "duplicate", "payment", "retry", "key", "mutation"],
|
| 818 |
+
required_verdict=Verdict.REQUEST_CHANGES
|
| 819 |
+
)
|
| 820 |
+
],
|
| 821 |
+
hash="arch_008_h"
|
| 822 |
+
),
|
| 823 |
+
Scenario(
|
| 824 |
+
task_id=TaskId.ARCHITECTURAL_REVIEW,
|
| 825 |
+
pr_title="Service: Shared DB state",
|
| 826 |
+
pr_description="Service B updates Service A's table directly for speed.",
|
| 827 |
+
files_changed=[
|
| 828 |
+
FileChange(
|
| 829 |
+
filename="service_b/sync.py",
|
| 830 |
+
patch="""@@ -22,1 +22,1 @@
|
| 831 |
+
- send_event_to_service_a(data)
|
| 832 |
+
+ db.execute('UPDATE service_a_table SET x = 1')""",
|
| 833 |
+
additions=1,
|
| 834 |
+
deletions=1
|
| 835 |
+
)
|
| 836 |
+
],
|
| 837 |
+
ground_truth_issues=[
|
| 838 |
+
GroundTruthIssue(
|
| 839 |
+
id="arch_009",
|
| 840 |
+
category=Category.ARCHITECTURE,
|
| 841 |
+
severity=Severity.HIGH,
|
| 842 |
+
filename="service_b/sync.py",
|
| 843 |
+
line_number=23,
|
| 844 |
+
description="Shared mutable state between microservices via direct DB write. Breaks encapsulation.",
|
| 845 |
+
keywords=["shared state", "microservice", "event", "eventual consistency", "ownership", "coupling"],
|
| 846 |
+
required_verdict=Verdict.REQUEST_CHANGES
|
| 847 |
+
)
|
| 848 |
+
],
|
| 849 |
+
hash="arch_009_h"
|
| 850 |
+
),
|
| 851 |
+
Scenario(
|
| 852 |
+
task_id=TaskId.ARCHITECTURAL_REVIEW,
|
| 853 |
+
pr_title="Clean: Domain logic in handler",
|
| 854 |
+
pr_description="Complex interest calculation directly in the GET endpoint.",
|
| 855 |
+
files_changed=[
|
| 856 |
+
FileChange(
|
| 857 |
+
filename="api/finance.py",
|
| 858 |
+
patch="""@@ -15,1 +15,3 @@
|
| 859 |
+
- return finance_service.calc_interest(u)
|
| 860 |
+
+ interest = u.balance * 0.05
|
| 861 |
+
+ if u.type == 'GOLD': interest += 10
|
| 862 |
+
+ return interest""",
|
| 863 |
+
additions=3,
|
| 864 |
+
deletions=1
|
| 865 |
+
)
|
| 866 |
+
],
|
| 867 |
+
ground_truth_issues=[
|
| 868 |
+
GroundTruthIssue(
|
| 869 |
+
id="arch_010",
|
| 870 |
+
category=Category.ARCHITECTURE,
|
| 871 |
+
severity=Severity.MEDIUM,
|
| 872 |
+
filename="api/finance.py",
|
| 873 |
+
line_number=16,
|
| 874 |
+
description="Clean architecture violation: domain logic leaked into HTTP handler.",
|
| 875 |
+
keywords=["clean architecture", "domain", "handler", "concern", "presentation", "business logic"],
|
| 876 |
+
required_verdict=Verdict.REQUEST_CHANGES
|
| 877 |
+
)
|
| 878 |
+
],
|
| 879 |
+
hash="arch_010_h"
|
| 880 |
+
)
|
| 881 |
+
]
|
| 882 |
+
|
| 883 |
+
SCENARIOS = {
|
| 884 |
+
TaskId.BUG_DETECTION: BUG_SCENARIOS,
|
| 885 |
+
TaskId.SECURITY_AUDIT: SECURITY_SCENARIOS,
|
| 886 |
+
TaskId.ARCHITECTURAL_REVIEW: ARCH_SCENARIOS,
|
| 887 |
+
}
|