codegraph-engine 2.3.0__py3-none-any.whl → 3.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- codegraph/__init__.py +1 -1
- codegraph/agent_brain.py +172 -2
- codegraph/agent_capabilities.py +320 -3
- codegraph/agent_rules.py +1 -1
- codegraph/api_drift.py +455 -0
- codegraph/async_queue/__init__.py +50 -0
- codegraph/async_queue/detectors/__init__.py +48 -0
- codegraph/async_queue/detectors/celery.py +160 -0
- codegraph/async_queue/detectors/kafka.py +168 -0
- codegraph/async_queue/detectors/rabbitmq.py +116 -0
- codegraph/async_queue/detectors/redis.py +130 -0
- codegraph/async_queue/detectors/sqs.py +135 -0
- codegraph/async_queue/models.py +161 -0
- codegraph/async_queue/otel_models.py +74 -0
- codegraph/async_queue/otel_parser.py +337 -0
- codegraph/async_queue/reconciler.py +427 -0
- codegraph/async_queue/scanner.py +291 -0
- codegraph/async_queue/traversal.py +447 -0
- codegraph/change_impact.py +320 -110
- codegraph/cli.py +776 -25
- codegraph/cli_output.py +17 -2
- codegraph/context.py +38 -11
- codegraph/context_freshness.py +2 -0
- codegraph/database/__init__.py +14 -0
- codegraph/database/extractor.py +712 -17
- codegraph/database/interrogation.py +388 -2
- codegraph/database/schema_drift.py +862 -0
- codegraph/epistemic.py +7 -0
- codegraph/evidence_contract.py +21 -0
- codegraph/frameworks.py +171 -6
- codegraph/frameworks_nestjs.py +502 -0
- codegraph/frameworks_nextjs.py +337 -0
- codegraph/frameworks_react.py +457 -0
- codegraph/freshness.py +2 -0
- codegraph/frontend_html.py +109 -0
- codegraph/git.py +2 -0
- codegraph/git_state.py +2 -0
- codegraph/indexing/__init__.py +7 -1
- codegraph/indexing/indexer.py +291 -38
- codegraph/indexing/models.py +6 -0
- codegraph/indexing/parser.py +239 -0
- codegraph/indexing/pool.py +136 -0
- codegraph/indexing/scanner.py +6 -0
- codegraph/indexing/templates.py +418 -0
- codegraph/interrogation.py +128 -20
- codegraph/mcp/server.py +190 -28
- codegraph/models.py +7 -0
- codegraph/monorepo.py +3 -0
- codegraph/optimizer.py +42 -5
- codegraph/process_lifecycle.py +31 -13
- codegraph/refactor/__init__.py +36 -0
- codegraph/refactor/models.py +114 -0
- codegraph/refactor/renamer.py +844 -0
- codegraph/refactor/transactions.py +403 -0
- codegraph/resolver.py +1202 -159
- codegraph/retrieval_policy.py +6 -0
- codegraph/route_topology.py +172 -0
- codegraph/runtime/__init__.py +19 -0
- codegraph/runtime/collector.py +155 -0
- codegraph/runtime/db_watcher.py +191 -0
- codegraph/runtime/ingestor.py +30 -10
- codegraph/runtime/port_inspector.py +304 -0
- codegraph/runtime/runner.py +155 -6
- codegraph/runtime/trace_linker.py +175 -0
- codegraph/search/hybrid.py +26 -0
- codegraph/semantic_decorators.py +34 -3
- codegraph/structural_diff.py +2 -0
- codegraph/styles.py +144 -0
- codegraph/symbol_history.py +2 -0
- codegraph/tsconfig.py +236 -0
- codegraph/ui/__init__.py +6 -0
- codegraph/ui/server.py +715 -0
- codegraph/watcher.py +323 -0
- {codegraph_engine-2.3.0.dist-info → codegraph_engine-3.0.0.dist-info}/METADATA +150 -38
- codegraph_engine-3.0.0.dist-info/RECORD +129 -0
- codegraph_engine-2.3.0.dist-info/RECORD +0 -94
- {codegraph_engine-2.3.0.dist-info → codegraph_engine-3.0.0.dist-info}/WHEEL +0 -0
- {codegraph_engine-2.3.0.dist-info → codegraph_engine-3.0.0.dist-info}/entry_points.txt +0 -0
- {codegraph_engine-2.3.0.dist-info → codegraph_engine-3.0.0.dist-info}/licenses/LICENSE +0 -0
- {codegraph_engine-2.3.0.dist-info → codegraph_engine-3.0.0.dist-info}/top_level.txt +0 -0
|
@@ -485,7 +485,7 @@ def find_db_writers(
|
|
|
485
485
|
res = _find_table_accessors(
|
|
486
486
|
con,
|
|
487
487
|
target,
|
|
488
|
-
("WRITES_TABLE", "WRITES_COLUMN"),
|
|
488
|
+
("WRITES_TABLE", "WRITES_COLUMN", "WRITES_COLLECTION"),
|
|
489
489
|
include_upstream_routes=True,
|
|
490
490
|
)
|
|
491
491
|
res["writers"] = res["direct_accessors"]
|
|
@@ -505,7 +505,7 @@ def find_db_readers(
|
|
|
505
505
|
res = _find_table_accessors(
|
|
506
506
|
con,
|
|
507
507
|
target,
|
|
508
|
-
("READS_TABLE", "READS_COLUMN"),
|
|
508
|
+
("READS_TABLE", "READS_COLUMN", "READS_COLLECTION"),
|
|
509
509
|
include_upstream_routes=True,
|
|
510
510
|
)
|
|
511
511
|
res["readers"] = res["direct_accessors"]
|
|
@@ -1106,3 +1106,389 @@ def get_db_impact(
|
|
|
1106
1106
|
"impacts": impact_items,
|
|
1107
1107
|
"results": impact_items,
|
|
1108
1108
|
})
|
|
1109
|
+
|
|
1110
|
+
|
|
1111
|
+
def _normalize_table_name(raw: str) -> str:
|
|
1112
|
+
if not raw:
|
|
1113
|
+
return ""
|
|
1114
|
+
clean = raw.strip().strip('"`')
|
|
1115
|
+
if clean.startswith("db."):
|
|
1116
|
+
parts = clean.split(".")
|
|
1117
|
+
cand = parts[-1].strip('"`')
|
|
1118
|
+
return "UNKNOWN" if cand.upper() == "UNKNOWN" else cand.lower()
|
|
1119
|
+
cand = clean.split(".")[-1].strip('"`')
|
|
1120
|
+
return "UNKNOWN" if cand.upper() == "UNKNOWN" else cand.lower()
|
|
1121
|
+
|
|
1122
|
+
|
|
1123
|
+
def _normalize_col_name(raw: str) -> str:
|
|
1124
|
+
clean = raw.strip().strip('"`')
|
|
1125
|
+
return clean.split(".")[-1].strip('"`').lower()
|
|
1126
|
+
|
|
1127
|
+
|
|
1128
|
+
def _infer_table_from_column_target(tgt: str) -> str:
|
|
1129
|
+
clean = tgt.strip().strip('"`')
|
|
1130
|
+
parts = clean.split(".")
|
|
1131
|
+
if len(parts) >= 2:
|
|
1132
|
+
cand = parts[-2].strip('"`')
|
|
1133
|
+
return "UNKNOWN" if cand.upper() == "UNKNOWN" else cand.lower()
|
|
1134
|
+
return ""
|
|
1135
|
+
|
|
1136
|
+
|
|
1137
|
+
def _aggregate_table_access(raw_items: list[dict[str, Any]], max_cols: int) -> list[dict[str, Any]]:
|
|
1138
|
+
by_table: dict[str, dict[str, Any]] = {}
|
|
1139
|
+
for item in raw_items:
|
|
1140
|
+
t = item["table"]
|
|
1141
|
+
if not t:
|
|
1142
|
+
continue
|
|
1143
|
+
if t not in by_table:
|
|
1144
|
+
by_table[t] = {
|
|
1145
|
+
"table": t,
|
|
1146
|
+
"columns": set(),
|
|
1147
|
+
"operations": set(),
|
|
1148
|
+
"evidence_classes": set(),
|
|
1149
|
+
"confidences": set(),
|
|
1150
|
+
"call_chains": [],
|
|
1151
|
+
"evidence": [],
|
|
1152
|
+
}
|
|
1153
|
+
entry = by_table[t]
|
|
1154
|
+
for c in item.get("columns", []):
|
|
1155
|
+
if c:
|
|
1156
|
+
entry["columns"].add(c)
|
|
1157
|
+
if item.get("operation"):
|
|
1158
|
+
entry["operations"].add(item["operation"])
|
|
1159
|
+
entry["evidence_classes"].add(item.get("evidence_class", "AST_VERIFIED"))
|
|
1160
|
+
entry["confidences"].add(item.get("confidence", "HIGH"))
|
|
1161
|
+
if item.get("call_chain"):
|
|
1162
|
+
entry["call_chains"].append(item["call_chain"])
|
|
1163
|
+
if item.get("evidence"):
|
|
1164
|
+
entry["evidence"].append({
|
|
1165
|
+
"file": item.get("file", ""),
|
|
1166
|
+
"line": item.get("line", 0),
|
|
1167
|
+
"snippet": item.get("evidence", ""),
|
|
1168
|
+
})
|
|
1169
|
+
|
|
1170
|
+
results: list[dict[str, Any]] = []
|
|
1171
|
+
for t_name in sorted(by_table.keys()):
|
|
1172
|
+
data = by_table[t_name]
|
|
1173
|
+
sorted_cols = sorted(list(data["columns"]))[:max_cols]
|
|
1174
|
+
ops = set(data["operations"])
|
|
1175
|
+
# If specific mutation operations exist, drop generic "WRITE"
|
|
1176
|
+
if len(ops) > 1 and "WRITE" in ops and any(x in ops for x in ("INSERT", "UPDATE", "DELETE", "CREATE", "DROP", "ALTER")):
|
|
1177
|
+
ops.remove("WRITE")
|
|
1178
|
+
# If specific read operations exist, drop generic "READ"
|
|
1179
|
+
if len(ops) > 1 and "READ" in ops and "SELECT" in ops:
|
|
1180
|
+
ops.remove("READ")
|
|
1181
|
+
|
|
1182
|
+
sorted_ops = sorted(list(ops))
|
|
1183
|
+
ev_classes = data["evidence_classes"]
|
|
1184
|
+
|
|
1185
|
+
if "UNKNOWN" in ev_classes:
|
|
1186
|
+
synth_ev = "UNKNOWN"
|
|
1187
|
+
synth_conf = "UNKNOWN"
|
|
1188
|
+
elif "POSSIBLE" in ev_classes or "AMBIGUOUS" in ev_classes:
|
|
1189
|
+
synth_ev = "POSSIBLE"
|
|
1190
|
+
synth_conf = "LOW"
|
|
1191
|
+
elif "DATAFLOW_VERIFIED" in ev_classes:
|
|
1192
|
+
synth_ev = "DATAFLOW_VERIFIED"
|
|
1193
|
+
synth_conf = "HIGH"
|
|
1194
|
+
elif "FRAMEWORK_VERIFIED" in ev_classes:
|
|
1195
|
+
synth_ev = "FRAMEWORK_VERIFIED"
|
|
1196
|
+
synth_conf = "HIGH"
|
|
1197
|
+
else:
|
|
1198
|
+
synth_ev = "AST_VERIFIED"
|
|
1199
|
+
synth_conf = "HIGH"
|
|
1200
|
+
|
|
1201
|
+
shortest_chain = min(data["call_chains"], key=len) if data["call_chains"] else []
|
|
1202
|
+
|
|
1203
|
+
results.append({
|
|
1204
|
+
"table": t_name,
|
|
1205
|
+
"columns": sorted_cols,
|
|
1206
|
+
"operations": sorted_ops,
|
|
1207
|
+
"evidence_class": synth_ev,
|
|
1208
|
+
"confidence": synth_conf,
|
|
1209
|
+
"call_chain": shortest_chain,
|
|
1210
|
+
"evidence": data["evidence"][:10],
|
|
1211
|
+
})
|
|
1212
|
+
|
|
1213
|
+
return results
|
|
1214
|
+
|
|
1215
|
+
|
|
1216
|
+
def get_route_db_lineage(
|
|
1217
|
+
con: sqlite3.Connection,
|
|
1218
|
+
route_or_handler: str,
|
|
1219
|
+
method: str | None = None,
|
|
1220
|
+
*,
|
|
1221
|
+
max_depth: int = 4,
|
|
1222
|
+
max_tables: int = 25,
|
|
1223
|
+
max_columns_per_table: int = 50,
|
|
1224
|
+
) -> dict[str, Any]:
|
|
1225
|
+
"""Deterministically trace route or handler through services, repositories, and ORM/SQL down to database mutations and reads.
|
|
1226
|
+
|
|
1227
|
+
Enforces bounded BFS (max_depth=4), cycle protection via visited set, deterministic ordering (table ASC, column ASC),
|
|
1228
|
+
strict output budgets, and preserves epistemic states (AST_VERIFIED, STATIC_VERIFIED, DATAFLOW_VERIFIED, FRAMEWORK_VERIFIED, POSSIBLE, UNKNOWN).
|
|
1229
|
+
"""
|
|
1230
|
+
raw = (route_or_handler or "").strip()
|
|
1231
|
+
if not raw:
|
|
1232
|
+
return {
|
|
1233
|
+
"status": "ok",
|
|
1234
|
+
"route": "",
|
|
1235
|
+
"method": (method or "").upper() or "ANY",
|
|
1236
|
+
"handler": "",
|
|
1237
|
+
"db_reads": [],
|
|
1238
|
+
"db_writes": [],
|
|
1239
|
+
"tables": [],
|
|
1240
|
+
"evidence_class": "STATIC_VERIFIED",
|
|
1241
|
+
}
|
|
1242
|
+
|
|
1243
|
+
clean_target = raw
|
|
1244
|
+
req_method = (method or "").strip().upper() or None
|
|
1245
|
+
|
|
1246
|
+
if " " in clean_target:
|
|
1247
|
+
parts = clean_target.split(" ", 1)
|
|
1248
|
+
if parts[0].upper() in ("GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS"):
|
|
1249
|
+
req_method = req_method or parts[0].upper()
|
|
1250
|
+
clean_target = parts[1].strip()
|
|
1251
|
+
|
|
1252
|
+
route_query = (
|
|
1253
|
+
"SELECT route_path, http_method, handler_name, handler_canonical_id, file_path, line "
|
|
1254
|
+
"FROM framework_routes WHERE route_path = ? OR endpoint_id = ?"
|
|
1255
|
+
)
|
|
1256
|
+
params: list[Any] = [clean_target, clean_target]
|
|
1257
|
+
if req_method:
|
|
1258
|
+
route_query += " AND UPPER(http_method) = ?"
|
|
1259
|
+
params.append(req_method)
|
|
1260
|
+
|
|
1261
|
+
try:
|
|
1262
|
+
route_rows = con.execute(route_query, params).fetchall()
|
|
1263
|
+
except sqlite3.OperationalError:
|
|
1264
|
+
route_rows = []
|
|
1265
|
+
|
|
1266
|
+
if not route_rows and req_method:
|
|
1267
|
+
try:
|
|
1268
|
+
route_rows = con.execute(
|
|
1269
|
+
"SELECT route_path, http_method, handler_name, handler_canonical_id, file_path, line "
|
|
1270
|
+
"FROM framework_routes WHERE route_path = ? OR endpoint_id = ?",
|
|
1271
|
+
(clean_target, clean_target),
|
|
1272
|
+
).fetchall()
|
|
1273
|
+
except sqlite3.OperationalError:
|
|
1274
|
+
route_rows = []
|
|
1275
|
+
|
|
1276
|
+
seeds: list[tuple[str, str]] = []
|
|
1277
|
+
route_info: dict[str, Any] = {}
|
|
1278
|
+
|
|
1279
|
+
if route_rows:
|
|
1280
|
+
primary_r = route_rows[0]
|
|
1281
|
+
route_info = {
|
|
1282
|
+
"route": str(primary_r["route_path"]),
|
|
1283
|
+
"method": str(primary_r["http_method"] or req_method or "").upper(),
|
|
1284
|
+
"file": str(primary_r["file_path"]),
|
|
1285
|
+
"line": int(primary_r["line"]),
|
|
1286
|
+
}
|
|
1287
|
+
for rr in route_rows:
|
|
1288
|
+
h_cid = str(rr["handler_canonical_id"] or "")
|
|
1289
|
+
h_name = str(rr["handler_name"] or "")
|
|
1290
|
+
if h_cid:
|
|
1291
|
+
seeds.append((h_cid, h_name or h_cid.split(".")[-1]))
|
|
1292
|
+
elif h_name:
|
|
1293
|
+
seeds.append((h_name, h_name))
|
|
1294
|
+
else:
|
|
1295
|
+
try:
|
|
1296
|
+
sym_rows = con.execute(
|
|
1297
|
+
"SELECT canonical_id, qualified_name, name, file_path, start_line FROM symbols "
|
|
1298
|
+
"WHERE canonical_id = ? OR qualified_name = ? OR name = ?",
|
|
1299
|
+
(clean_target, clean_target, clean_target),
|
|
1300
|
+
).fetchall()
|
|
1301
|
+
except sqlite3.OperationalError:
|
|
1302
|
+
sym_rows = []
|
|
1303
|
+
if sym_rows:
|
|
1304
|
+
for sr in sym_rows:
|
|
1305
|
+
cid = str(sr["canonical_id"] or sr["qualified_name"] or sr["name"])
|
|
1306
|
+
seeds.append((cid, str(sr["name"])))
|
|
1307
|
+
else:
|
|
1308
|
+
seeds.append((clean_target, clean_target))
|
|
1309
|
+
|
|
1310
|
+
visited: set[str] = set()
|
|
1311
|
+
queue: deque[tuple[str, int, list[str]]] = deque()
|
|
1312
|
+
for s_id, s_disp in seeds:
|
|
1313
|
+
if s_id not in visited:
|
|
1314
|
+
visited.add(s_id)
|
|
1315
|
+
queue.append((s_id, 0, [s_disp]))
|
|
1316
|
+
|
|
1317
|
+
raw_reads: list[dict[str, Any]] = []
|
|
1318
|
+
raw_writes: list[dict[str, Any]] = []
|
|
1319
|
+
|
|
1320
|
+
while queue:
|
|
1321
|
+
curr_sym, depth, chain = queue.popleft()
|
|
1322
|
+
if depth > max_depth:
|
|
1323
|
+
continue
|
|
1324
|
+
|
|
1325
|
+
curr_bare = curr_sym.split(".")[-1]
|
|
1326
|
+
|
|
1327
|
+
# 1. Query db_queries table
|
|
1328
|
+
try:
|
|
1329
|
+
q_rows = con.execute(
|
|
1330
|
+
"SELECT query_id, caller_symbol_id, operation, relationship, table_name, table_canonical_id, "
|
|
1331
|
+
"columns_json, file_path, start_line, end_line, evidence, confidence, evidence_class, status "
|
|
1332
|
+
"FROM db_queries "
|
|
1333
|
+
"WHERE caller_symbol_id = ? OR caller_symbol_id LIKE ? OR caller_symbol_id LIKE ? OR caller_symbol_id = ?",
|
|
1334
|
+
(curr_sym, f"%.{curr_sym}", f"{curr_sym}.%", curr_bare),
|
|
1335
|
+
).fetchall()
|
|
1336
|
+
except sqlite3.OperationalError:
|
|
1337
|
+
q_rows = []
|
|
1338
|
+
|
|
1339
|
+
for qr in q_rows:
|
|
1340
|
+
op = str(qr["operation"] or "").upper()
|
|
1341
|
+
rel = str(qr["relationship"] or "")
|
|
1342
|
+
tbl_raw = str(qr["table_name"] or qr["table_canonical_id"] or "")
|
|
1343
|
+
tbl_name = _normalize_table_name(tbl_raw)
|
|
1344
|
+
if not tbl_name:
|
|
1345
|
+
continue
|
|
1346
|
+
|
|
1347
|
+
cols_raw: list[Any] = []
|
|
1348
|
+
try:
|
|
1349
|
+
cols_raw = json.loads(qr["columns_json"] or "[]")
|
|
1350
|
+
except Exception:
|
|
1351
|
+
cols_raw = []
|
|
1352
|
+
cols = [_normalize_col_name(str(c)) for c in cols_raw if str(c).strip()]
|
|
1353
|
+
|
|
1354
|
+
ev_cls = str(qr["evidence_class"] or "AST_VERIFIED")
|
|
1355
|
+
conf = str(qr["confidence"] or "HIGH")
|
|
1356
|
+
item = {
|
|
1357
|
+
"table": tbl_name,
|
|
1358
|
+
"columns": cols,
|
|
1359
|
+
"operation": op,
|
|
1360
|
+
"evidence_class": ev_cls,
|
|
1361
|
+
"confidence": conf,
|
|
1362
|
+
"call_chain": list(chain),
|
|
1363
|
+
"file": str(qr["file_path"]),
|
|
1364
|
+
"line": int(qr["start_line"]),
|
|
1365
|
+
"evidence": redact_secrets(str(qr["evidence"])),
|
|
1366
|
+
}
|
|
1367
|
+
|
|
1368
|
+
if op in ("SELECT", "FETCH", "READ", "FIND") or rel in ("READS_TABLE", "READS_COLUMN"):
|
|
1369
|
+
raw_reads.append(item)
|
|
1370
|
+
elif op in ("INSERT", "UPDATE", "DELETE", "WRITE", "SAVE", "CREATE", "DROP", "ALTER") or rel in ("WRITES_TABLE", "WRITES_COLUMN"):
|
|
1371
|
+
raw_writes.append(item)
|
|
1372
|
+
elif op == "UNKNOWN" or rel == "UNKNOWN_TABLE" or ev_cls == "UNKNOWN":
|
|
1373
|
+
raw_writes.append(item)
|
|
1374
|
+
raw_reads.append(item)
|
|
1375
|
+
|
|
1376
|
+
# 2. Query graph_edges table for outgoing edges from curr_sym
|
|
1377
|
+
try:
|
|
1378
|
+
edge_rows = con.execute(
|
|
1379
|
+
"SELECT source, target, relationship, confidence, file, start_line, end_line, evidence, evidence_class, reason "
|
|
1380
|
+
"FROM graph_edges WHERE source = ? OR source = ?",
|
|
1381
|
+
(curr_sym, curr_bare),
|
|
1382
|
+
).fetchall()
|
|
1383
|
+
except sqlite3.OperationalError:
|
|
1384
|
+
edge_rows = []
|
|
1385
|
+
|
|
1386
|
+
for er in edge_rows:
|
|
1387
|
+
rel = str(er["relationship"])
|
|
1388
|
+
tgt = str(er["target"])
|
|
1389
|
+
ev_cls = str(er["evidence_class"] or "AST_VERIFIED")
|
|
1390
|
+
conf = str(er["confidence"] or "HIGH")
|
|
1391
|
+
ev_str = redact_secrets(str(er["evidence"] or ""))
|
|
1392
|
+
|
|
1393
|
+
if rel in ("READS_TABLE", "READS_COLUMN"):
|
|
1394
|
+
tbl_name = _normalize_table_name(tgt) if rel == "READS_TABLE" else _infer_table_from_column_target(tgt)
|
|
1395
|
+
col_name = _normalize_col_name(tgt) if rel == "READS_COLUMN" else None
|
|
1396
|
+
if tbl_name:
|
|
1397
|
+
raw_reads.append({
|
|
1398
|
+
"table": tbl_name,
|
|
1399
|
+
"columns": [col_name] if col_name else [],
|
|
1400
|
+
"operation": "SELECT",
|
|
1401
|
+
"evidence_class": ev_cls,
|
|
1402
|
+
"confidence": conf,
|
|
1403
|
+
"call_chain": list(chain),
|
|
1404
|
+
"file": str(er["file"]),
|
|
1405
|
+
"line": int(er["start_line"]),
|
|
1406
|
+
"evidence": ev_str,
|
|
1407
|
+
})
|
|
1408
|
+
elif rel in ("WRITES_TABLE", "WRITES_COLUMN"):
|
|
1409
|
+
tbl_name = _normalize_table_name(tgt) if rel == "WRITES_TABLE" else _infer_table_from_column_target(tgt)
|
|
1410
|
+
col_name = _normalize_col_name(tgt) if rel == "WRITES_COLUMN" else None
|
|
1411
|
+
if tbl_name:
|
|
1412
|
+
raw_writes.append({
|
|
1413
|
+
"table": tbl_name,
|
|
1414
|
+
"columns": [col_name] if col_name else [],
|
|
1415
|
+
"operation": "WRITE",
|
|
1416
|
+
"evidence_class": ev_cls,
|
|
1417
|
+
"confidence": conf,
|
|
1418
|
+
"call_chain": list(chain),
|
|
1419
|
+
"file": str(er["file"]),
|
|
1420
|
+
"line": int(er["start_line"]),
|
|
1421
|
+
"evidence": ev_str,
|
|
1422
|
+
})
|
|
1423
|
+
elif rel in ("UNKNOWN_TABLE", "POSSIBLE_TABLE"):
|
|
1424
|
+
tbl_name = _normalize_table_name(tgt)
|
|
1425
|
+
if tbl_name:
|
|
1426
|
+
u_item = {
|
|
1427
|
+
"table": tbl_name,
|
|
1428
|
+
"columns": [],
|
|
1429
|
+
"operation": "UNKNOWN",
|
|
1430
|
+
"evidence_class": "UNKNOWN" if rel == "UNKNOWN_TABLE" else "POSSIBLE",
|
|
1431
|
+
"confidence": "UNKNOWN" if rel == "UNKNOWN_TABLE" else "LOW",
|
|
1432
|
+
"call_chain": list(chain),
|
|
1433
|
+
"file": str(er["file"]),
|
|
1434
|
+
"line": int(er["start_line"]),
|
|
1435
|
+
"evidence": ev_str,
|
|
1436
|
+
}
|
|
1437
|
+
raw_writes.append(u_item)
|
|
1438
|
+
raw_reads.append(u_item)
|
|
1439
|
+
elif rel in (
|
|
1440
|
+
"CALLS",
|
|
1441
|
+
"INJECTS",
|
|
1442
|
+
"PROVIDES",
|
|
1443
|
+
"RESOLVES_DEPENDENCY",
|
|
1444
|
+
"DISPATCHES_TO",
|
|
1445
|
+
"ROUTES_TO",
|
|
1446
|
+
"HANDLED_BY",
|
|
1447
|
+
"CALLS_CELERY_TASK",
|
|
1448
|
+
"DISPATCHES_SIGNAL",
|
|
1449
|
+
):
|
|
1450
|
+
if depth + 1 <= max_depth and tgt not in visited:
|
|
1451
|
+
visited.add(tgt)
|
|
1452
|
+
next_disp = tgt.split(".")[-1]
|
|
1453
|
+
queue.append((tgt, depth + 1, chain + [next_disp]))
|
|
1454
|
+
|
|
1455
|
+
# 3. Class method expansion
|
|
1456
|
+
try:
|
|
1457
|
+
def_rows = con.execute(
|
|
1458
|
+
"SELECT target FROM graph_edges WHERE (source = ? OR source = ?) AND relationship = 'DEFINES'",
|
|
1459
|
+
(curr_sym, curr_bare),
|
|
1460
|
+
).fetchall()
|
|
1461
|
+
for dr in def_rows:
|
|
1462
|
+
dtgt = str(dr["target"])
|
|
1463
|
+
if depth + 1 <= max_depth and dtgt not in visited:
|
|
1464
|
+
visited.add(dtgt)
|
|
1465
|
+
queue.append((dtgt, depth + 1, chain + [dtgt.split(".")[-1]]))
|
|
1466
|
+
except sqlite3.OperationalError:
|
|
1467
|
+
pass
|
|
1468
|
+
|
|
1469
|
+
aggregated_reads = _aggregate_table_access(raw_reads, max_columns_per_table)[:max_tables]
|
|
1470
|
+
aggregated_writes = _aggregate_table_access(raw_writes, max_columns_per_table)[:max_tables]
|
|
1471
|
+
|
|
1472
|
+
all_tables = sorted(list(set(r["table"] for r in aggregated_reads) | set(w["table"] for w in aggregated_writes)))
|
|
1473
|
+
|
|
1474
|
+
all_ev_classes = set(r["evidence_class"] for r in aggregated_reads) | set(w["evidence_class"] for w in aggregated_writes)
|
|
1475
|
+
if "UNKNOWN" in all_ev_classes:
|
|
1476
|
+
overall_ev = "UNKNOWN"
|
|
1477
|
+
elif "POSSIBLE" in all_ev_classes:
|
|
1478
|
+
overall_ev = "POSSIBLE"
|
|
1479
|
+
elif all_ev_classes:
|
|
1480
|
+
overall_ev = "AST_VERIFIED"
|
|
1481
|
+
else:
|
|
1482
|
+
overall_ev = "STATIC_VERIFIED"
|
|
1483
|
+
|
|
1484
|
+
return {
|
|
1485
|
+
"status": "ok",
|
|
1486
|
+
"route": route_info.get("route", clean_target),
|
|
1487
|
+
"method": route_info.get("method", req_method or "ANY"),
|
|
1488
|
+
"handler": seeds[0][0] if seeds else clean_target,
|
|
1489
|
+
"db_reads": aggregated_reads,
|
|
1490
|
+
"db_writes": aggregated_writes,
|
|
1491
|
+
"tables": all_tables,
|
|
1492
|
+
"evidence_class": overall_ev,
|
|
1493
|
+
}
|
|
1494
|
+
|