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
|
@@ -0,0 +1,447 @@
|
|
|
1
|
+
"""Query and graph traversal engine for asynchronous message queues and tasks.
|
|
2
|
+
|
|
3
|
+
Epistemic Invariants:
|
|
4
|
+
1. Preserves distinct semantic relationships:
|
|
5
|
+
- PRODUCES_TO_QUEUE
|
|
6
|
+
- CONSUMES_FROM_QUEUE
|
|
7
|
+
- STATIC_QUEUE_LINKED
|
|
8
|
+
- OTEL_DISTRIBUTED_VERIFIED
|
|
9
|
+
2. Bridges entrypoint handlers -> producers -> queues -> consumer workers -> downstream DB/operations.
|
|
10
|
+
3. Fully deterministic ordering.
|
|
11
|
+
"""
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import sqlite3
|
|
15
|
+
from typing import Any
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def list_async_queues(con: sqlite3.Connection) -> list[dict[str, Any]]:
|
|
19
|
+
"""List all statically detected async queues, topics, and channels."""
|
|
20
|
+
con.row_factory = sqlite3.Row
|
|
21
|
+
try:
|
|
22
|
+
rows = con.execute(
|
|
23
|
+
"""
|
|
24
|
+
SELECT q.queue_id, q.system, q.destination, q.service, q.environment, q.is_dlq,
|
|
25
|
+
COUNT(DISTINCT p.id) as producer_count,
|
|
26
|
+
COUNT(DISTINCT c.id) as consumer_count,
|
|
27
|
+
COUNT(DISTINCT l.link_id) as link_count
|
|
28
|
+
FROM async_queues q
|
|
29
|
+
LEFT JOIN async_producers p ON q.queue_id = p.queue_id
|
|
30
|
+
LEFT JOIN async_consumers c ON q.queue_id = c.queue_id
|
|
31
|
+
LEFT JOIN async_links l ON q.queue_id = l.queue_id
|
|
32
|
+
GROUP BY q.queue_id
|
|
33
|
+
ORDER BY q.queue_id ASC
|
|
34
|
+
"""
|
|
35
|
+
).fetchall()
|
|
36
|
+
return [
|
|
37
|
+
{
|
|
38
|
+
"queue_id": str(r["queue_id"]),
|
|
39
|
+
"system": str(r["system"]),
|
|
40
|
+
"destination": str(r["destination"]),
|
|
41
|
+
"service": str(r["service"]),
|
|
42
|
+
"environment": str(r["environment"]),
|
|
43
|
+
"is_dlq": bool(r["is_dlq"]),
|
|
44
|
+
"producer_count": int(r["producer_count"]),
|
|
45
|
+
"consumer_count": int(r["consumer_count"]),
|
|
46
|
+
"link_count": int(r["link_count"]),
|
|
47
|
+
}
|
|
48
|
+
for r in rows
|
|
49
|
+
]
|
|
50
|
+
except sqlite3.OperationalError:
|
|
51
|
+
return []
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def find_queue_producers(
|
|
55
|
+
con: sqlite3.Connection,
|
|
56
|
+
queue_or_topic: str,
|
|
57
|
+
) -> list[dict[str, Any]]:
|
|
58
|
+
"""Find all producer call sites for a given queue, topic, or destination."""
|
|
59
|
+
con.row_factory = sqlite3.Row
|
|
60
|
+
clean = queue_or_topic.strip()
|
|
61
|
+
if not clean:
|
|
62
|
+
return []
|
|
63
|
+
|
|
64
|
+
try:
|
|
65
|
+
rows = con.execute(
|
|
66
|
+
"""
|
|
67
|
+
SELECT p.id, p.queue_id, p.system, p.destination, p.caller_canonical_id,
|
|
68
|
+
p.file_path, p.line, p.call_snippet, p.confidence, s.name as caller_name
|
|
69
|
+
FROM async_producers p
|
|
70
|
+
LEFT JOIN symbols s ON p.caller_canonical_id = s.canonical_id
|
|
71
|
+
WHERE p.queue_id = ? OR p.destination = ? OR p.destination LIKE ?
|
|
72
|
+
ORDER BY p.file_path ASC, p.line ASC
|
|
73
|
+
""",
|
|
74
|
+
(clean, clean, f"%{clean}%"),
|
|
75
|
+
).fetchall()
|
|
76
|
+
return [
|
|
77
|
+
{
|
|
78
|
+
"id": int(r["id"]),
|
|
79
|
+
"queue_id": str(r["queue_id"]),
|
|
80
|
+
"system": str(r["system"]),
|
|
81
|
+
"destination": str(r["destination"]),
|
|
82
|
+
"caller_canonical_id": str(r["caller_canonical_id"]),
|
|
83
|
+
"caller_name": str(r["caller_name"] or r["caller_canonical_id"]),
|
|
84
|
+
"file_path": str(r["file_path"]),
|
|
85
|
+
"line": int(r["line"]),
|
|
86
|
+
"call_snippet": str(r["call_snippet"]),
|
|
87
|
+
"confidence": str(r["confidence"]),
|
|
88
|
+
}
|
|
89
|
+
for r in rows
|
|
90
|
+
]
|
|
91
|
+
except sqlite3.OperationalError:
|
|
92
|
+
return []
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def find_queue_consumers(
|
|
96
|
+
con: sqlite3.Connection,
|
|
97
|
+
queue_or_topic: str,
|
|
98
|
+
) -> list[dict[str, Any]]:
|
|
99
|
+
"""Find all consumer workers and handlers subscribed to a given queue or topic."""
|
|
100
|
+
con.row_factory = sqlite3.Row
|
|
101
|
+
clean = queue_or_topic.strip()
|
|
102
|
+
if not clean:
|
|
103
|
+
return []
|
|
104
|
+
|
|
105
|
+
try:
|
|
106
|
+
rows = con.execute(
|
|
107
|
+
"""
|
|
108
|
+
SELECT c.id, c.queue_id, c.system, c.destination, c.handler_canonical_id,
|
|
109
|
+
c.file_path, c.line, c.handler_snippet, c.confidence, s.name as handler_name
|
|
110
|
+
FROM async_consumers c
|
|
111
|
+
LEFT JOIN symbols s ON c.handler_canonical_id = s.canonical_id
|
|
112
|
+
WHERE c.queue_id = ? OR c.destination = ? OR c.destination LIKE ?
|
|
113
|
+
ORDER BY c.file_path ASC, c.line ASC
|
|
114
|
+
""",
|
|
115
|
+
(clean, clean, f"%{clean}%"),
|
|
116
|
+
).fetchall()
|
|
117
|
+
return [
|
|
118
|
+
{
|
|
119
|
+
"id": int(r["id"]),
|
|
120
|
+
"queue_id": str(r["queue_id"]),
|
|
121
|
+
"system": str(r["system"]),
|
|
122
|
+
"destination": str(r["destination"]),
|
|
123
|
+
"handler_canonical_id": str(r["handler_canonical_id"]),
|
|
124
|
+
"handler_name": str(r["handler_name"] or r["handler_canonical_id"]),
|
|
125
|
+
"file_path": str(r["file_path"]),
|
|
126
|
+
"line": int(r["line"]),
|
|
127
|
+
"handler_snippet": str(r["handler_snippet"]),
|
|
128
|
+
"confidence": str(r["confidence"]),
|
|
129
|
+
}
|
|
130
|
+
for r in rows
|
|
131
|
+
]
|
|
132
|
+
except sqlite3.OperationalError:
|
|
133
|
+
return []
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def trace_async_flow(
|
|
137
|
+
con: sqlite3.Connection,
|
|
138
|
+
entrypoint: str,
|
|
139
|
+
max_depth: int = 5,
|
|
140
|
+
) -> dict[str, Any]:
|
|
141
|
+
"""Trace cross-process asynchronous execution flow starting from an entrypoint.
|
|
142
|
+
|
|
143
|
+
Entrypoint can be:
|
|
144
|
+
- An HTTP endpoint or service function that publishes to a queue
|
|
145
|
+
- A queue or topic name directly
|
|
146
|
+
- A background consumer worker function
|
|
147
|
+
|
|
148
|
+
Returns deterministic flow steps across the async boundary, including
|
|
149
|
+
downstream database queries and callees invoked by consumer workers.
|
|
150
|
+
"""
|
|
151
|
+
con.row_factory = sqlite3.Row
|
|
152
|
+
clean = entrypoint.strip()
|
|
153
|
+
if not clean:
|
|
154
|
+
return {
|
|
155
|
+
"status": "error",
|
|
156
|
+
"message": "Entrypoint cannot be empty.",
|
|
157
|
+
"entrypoint": entrypoint,
|
|
158
|
+
"steps": [],
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
# 1. Check if entrypoint is a queue or topic directly
|
|
162
|
+
queue_matches = con.execute(
|
|
163
|
+
"SELECT queue_id, system, destination FROM async_queues WHERE queue_id=? OR destination=?",
|
|
164
|
+
(clean, clean),
|
|
165
|
+
).fetchall()
|
|
166
|
+
|
|
167
|
+
producers: list[dict[str, Any]] = []
|
|
168
|
+
consumers: list[dict[str, Any]] = []
|
|
169
|
+
flow_steps: list[dict[str, Any]] = []
|
|
170
|
+
downstream_ops: list[dict[str, Any]] = []
|
|
171
|
+
|
|
172
|
+
if queue_matches:
|
|
173
|
+
for q in queue_matches:
|
|
174
|
+
q_id = str(q["queue_id"])
|
|
175
|
+
q_prods = find_queue_producers(con, q_id)
|
|
176
|
+
q_cons = find_queue_consumers(con, q_id)
|
|
177
|
+
producers.extend(q_prods)
|
|
178
|
+
consumers.extend(q_cons)
|
|
179
|
+
|
|
180
|
+
for p in q_prods:
|
|
181
|
+
flow_steps.append({
|
|
182
|
+
"step": len(flow_steps) + 1,
|
|
183
|
+
"source": p["caller_canonical_id"],
|
|
184
|
+
"target": q_id,
|
|
185
|
+
"relationship": "PRODUCES_TO_QUEUE",
|
|
186
|
+
"system": p["system"],
|
|
187
|
+
"evidence_class": "STATIC_QUEUE_LINKED",
|
|
188
|
+
"confidence": p["confidence"],
|
|
189
|
+
"file": p["file_path"],
|
|
190
|
+
"line": p["line"],
|
|
191
|
+
"snippet": p["call_snippet"],
|
|
192
|
+
})
|
|
193
|
+
|
|
194
|
+
for c in q_cons:
|
|
195
|
+
flow_steps.append({
|
|
196
|
+
"step": len(flow_steps) + 1,
|
|
197
|
+
"source": q_id,
|
|
198
|
+
"target": c["handler_canonical_id"],
|
|
199
|
+
"relationship": "CONSUMES_FROM_QUEUE",
|
|
200
|
+
"system": c["system"],
|
|
201
|
+
"evidence_class": "STATIC_QUEUE_LINKED",
|
|
202
|
+
"confidence": c["confidence"],
|
|
203
|
+
"file": c["file_path"],
|
|
204
|
+
"line": c["line"],
|
|
205
|
+
"snippet": c["handler_snippet"],
|
|
206
|
+
})
|
|
207
|
+
else:
|
|
208
|
+
# 2. Check if entrypoint is a producer symbol or calls a producer
|
|
209
|
+
# Look up directly matching producers
|
|
210
|
+
direct_prods = con.execute(
|
|
211
|
+
"""
|
|
212
|
+
SELECT p.id, p.queue_id, p.system, p.destination, p.caller_canonical_id,
|
|
213
|
+
p.file_path, p.line, p.call_snippet, p.confidence
|
|
214
|
+
FROM async_producers p
|
|
215
|
+
WHERE p.caller_canonical_id = ? OR p.caller_canonical_id LIKE ?
|
|
216
|
+
ORDER BY p.file_path ASC, p.line ASC
|
|
217
|
+
""",
|
|
218
|
+
(clean, f"%{clean}%"),
|
|
219
|
+
).fetchall()
|
|
220
|
+
|
|
221
|
+
# If none direct, check if entrypoint calls a producer function (e.g. controller calling service)
|
|
222
|
+
if not direct_prods:
|
|
223
|
+
callee_prods = con.execute(
|
|
224
|
+
"""
|
|
225
|
+
SELECT p.id, p.queue_id, p.system, p.destination, p.caller_canonical_id,
|
|
226
|
+
p.file_path, p.line, p.call_snippet, p.confidence, ge.source as entry_source
|
|
227
|
+
FROM graph_edges ge
|
|
228
|
+
JOIN async_producers p ON ge.target = p.caller_canonical_id
|
|
229
|
+
WHERE (ge.source = ? OR ge.source LIKE ?) AND ge.relationship = 'CALLS'
|
|
230
|
+
ORDER BY p.file_path ASC, p.line ASC
|
|
231
|
+
""",
|
|
232
|
+
(clean, f"%{clean}%"),
|
|
233
|
+
).fetchall()
|
|
234
|
+
for cp in callee_prods:
|
|
235
|
+
flow_steps.append({
|
|
236
|
+
"step": len(flow_steps) + 1,
|
|
237
|
+
"source": str(cp["entry_source"]),
|
|
238
|
+
"target": str(cp["caller_canonical_id"]),
|
|
239
|
+
"relationship": "CALLS",
|
|
240
|
+
"evidence_class": "AST_VERIFIED",
|
|
241
|
+
"confidence": "HIGH",
|
|
242
|
+
"file": str(cp["file_path"]),
|
|
243
|
+
"line": int(cp["line"]),
|
|
244
|
+
"snippet": "",
|
|
245
|
+
})
|
|
246
|
+
direct_prods.append(cp)
|
|
247
|
+
|
|
248
|
+
for p in direct_prods:
|
|
249
|
+
q_id = str(p["queue_id"])
|
|
250
|
+
producers.append({
|
|
251
|
+
"queue_id": q_id,
|
|
252
|
+
"system": str(p["system"]),
|
|
253
|
+
"destination": str(p["destination"]),
|
|
254
|
+
"caller_canonical_id": str(p["caller_canonical_id"]),
|
|
255
|
+
"file_path": str(p["file_path"]),
|
|
256
|
+
"line": int(p["line"]),
|
|
257
|
+
"call_snippet": str(p["call_snippet"]),
|
|
258
|
+
"confidence": str(p["confidence"]),
|
|
259
|
+
})
|
|
260
|
+
flow_steps.append({
|
|
261
|
+
"step": len(flow_steps) + 1,
|
|
262
|
+
"source": str(p["caller_canonical_id"]),
|
|
263
|
+
"target": q_id,
|
|
264
|
+
"relationship": "PRODUCES_TO_QUEUE",
|
|
265
|
+
"system": str(p["system"]),
|
|
266
|
+
"evidence_class": "STATIC_QUEUE_LINKED",
|
|
267
|
+
"confidence": str(p["confidence"]),
|
|
268
|
+
"file": str(p["file_path"]),
|
|
269
|
+
"line": int(p["line"]),
|
|
270
|
+
"snippet": str(p["call_snippet"]),
|
|
271
|
+
})
|
|
272
|
+
|
|
273
|
+
# Find matching static consumers for this queue
|
|
274
|
+
q_cons = find_queue_consumers(con, q_id)
|
|
275
|
+
consumers.extend(q_cons)
|
|
276
|
+
|
|
277
|
+
# Look up runtime links to augment evidence and latency metrics
|
|
278
|
+
try:
|
|
279
|
+
rt_links = con.execute(
|
|
280
|
+
"SELECT * FROM async_links WHERE queue_id = ? OR destination = ?",
|
|
281
|
+
(q_id, str(p["destination"])),
|
|
282
|
+
).fetchall()
|
|
283
|
+
except sqlite3.OperationalError:
|
|
284
|
+
rt_links = []
|
|
285
|
+
|
|
286
|
+
links_by_consumer = {str(rk["consumer_id"]): rk for rk in rt_links}
|
|
287
|
+
|
|
288
|
+
for c in q_cons:
|
|
289
|
+
matched_link = links_by_consumer.get(c["handler_canonical_id"])
|
|
290
|
+
ev_class = str(matched_link["evidence_class"]) if matched_link else "STATIC_QUEUE_LINKED"
|
|
291
|
+
step_dict: dict[str, Any] = {
|
|
292
|
+
"step": len(flow_steps) + 1,
|
|
293
|
+
"source": q_id,
|
|
294
|
+
"target": c["handler_canonical_id"],
|
|
295
|
+
"relationship": "CONSUMES_FROM_QUEUE",
|
|
296
|
+
"system": c["system"],
|
|
297
|
+
"evidence_class": ev_class,
|
|
298
|
+
"confidence": c["confidence"],
|
|
299
|
+
"file": c["file_path"],
|
|
300
|
+
"line": c["line"],
|
|
301
|
+
"snippet": c["handler_snippet"],
|
|
302
|
+
}
|
|
303
|
+
if matched_link and ev_class == "OTEL_DISTRIBUTED_VERIFIED":
|
|
304
|
+
step_dict["latency_p50_ms"] = float(matched_link["p50_ms"])
|
|
305
|
+
step_dict["latency_p95_ms"] = float(matched_link["p95_ms"])
|
|
306
|
+
step_dict["trace_count"] = int(matched_link["trace_count"])
|
|
307
|
+
flow_steps.append(step_dict)
|
|
308
|
+
|
|
309
|
+
# Include cross-repository / external runtime consumers
|
|
310
|
+
for rk in rt_links:
|
|
311
|
+
c_id = str(rk["consumer_id"])
|
|
312
|
+
if c_id.startswith("external:"):
|
|
313
|
+
consumers.append({
|
|
314
|
+
"queue_id": q_id,
|
|
315
|
+
"system": str(rk["system"]),
|
|
316
|
+
"destination": str(rk["destination"]),
|
|
317
|
+
"handler_canonical_id": c_id,
|
|
318
|
+
"file_path": "",
|
|
319
|
+
"line": 0,
|
|
320
|
+
"handler_snippet": f"External consumer ({rk['system']})",
|
|
321
|
+
"confidence": "HIGH",
|
|
322
|
+
})
|
|
323
|
+
flow_steps.append({
|
|
324
|
+
"step": len(flow_steps) + 1,
|
|
325
|
+
"source": q_id,
|
|
326
|
+
"target": c_id,
|
|
327
|
+
"relationship": "CONSUMES_FROM_QUEUE",
|
|
328
|
+
"system": str(rk["system"]),
|
|
329
|
+
"evidence_class": str(rk["evidence_class"]),
|
|
330
|
+
"confidence": "HIGH",
|
|
331
|
+
"file": "",
|
|
332
|
+
"line": 0,
|
|
333
|
+
"snippet": f"Observed via OTel (p50={rk['p50_ms']}ms, p95={rk['p95_ms']}ms)",
|
|
334
|
+
"latency_p50_ms": float(rk["p50_ms"]),
|
|
335
|
+
"latency_p95_ms": float(rk["p95_ms"]),
|
|
336
|
+
"trace_count": int(rk["trace_count"]),
|
|
337
|
+
})
|
|
338
|
+
|
|
339
|
+
# 3. If still no producers found, check if entrypoint is a consumer worker handler
|
|
340
|
+
if not producers:
|
|
341
|
+
direct_cons = con.execute(
|
|
342
|
+
"""
|
|
343
|
+
SELECT c.id, c.queue_id, c.system, c.destination, c.handler_canonical_id,
|
|
344
|
+
c.file_path, c.line, c.handler_snippet, c.confidence
|
|
345
|
+
FROM async_consumers c
|
|
346
|
+
WHERE c.handler_canonical_id = ? OR c.handler_canonical_id LIKE ?
|
|
347
|
+
ORDER BY c.file_path ASC, c.line ASC
|
|
348
|
+
""",
|
|
349
|
+
(clean, f"%{clean}%"),
|
|
350
|
+
).fetchall()
|
|
351
|
+
|
|
352
|
+
for c in direct_cons:
|
|
353
|
+
q_id = str(c["queue_id"])
|
|
354
|
+
consumers.append({
|
|
355
|
+
"queue_id": q_id,
|
|
356
|
+
"system": str(c["system"]),
|
|
357
|
+
"destination": str(c["destination"]),
|
|
358
|
+
"handler_canonical_id": str(c["handler_canonical_id"]),
|
|
359
|
+
"file_path": str(c["file_path"]),
|
|
360
|
+
"line": int(c["line"]),
|
|
361
|
+
"handler_snippet": str(c["handler_snippet"]),
|
|
362
|
+
"confidence": str(c["confidence"]),
|
|
363
|
+
})
|
|
364
|
+
# Find upstream producers
|
|
365
|
+
q_prods = find_queue_producers(con, q_id)
|
|
366
|
+
producers.extend(q_prods)
|
|
367
|
+
for p in q_prods:
|
|
368
|
+
flow_steps.append({
|
|
369
|
+
"step": len(flow_steps) + 1,
|
|
370
|
+
"source": p["caller_canonical_id"],
|
|
371
|
+
"target": q_id,
|
|
372
|
+
"relationship": "PRODUCES_TO_QUEUE",
|
|
373
|
+
"system": p["system"],
|
|
374
|
+
"evidence_class": "STATIC_QUEUE_LINKED",
|
|
375
|
+
"confidence": p["confidence"],
|
|
376
|
+
"file": p["file_path"],
|
|
377
|
+
"line": p["line"],
|
|
378
|
+
"snippet": p["call_snippet"],
|
|
379
|
+
})
|
|
380
|
+
flow_steps.append({
|
|
381
|
+
"step": len(flow_steps) + 1,
|
|
382
|
+
"source": q_id,
|
|
383
|
+
"target": str(c["handler_canonical_id"]),
|
|
384
|
+
"relationship": "CONSUMES_FROM_QUEUE",
|
|
385
|
+
"system": str(c["system"]),
|
|
386
|
+
"evidence_class": "STATIC_QUEUE_LINKED",
|
|
387
|
+
"confidence": str(c["confidence"]),
|
|
388
|
+
"file": str(c["file_path"]),
|
|
389
|
+
"line": int(c["line"]),
|
|
390
|
+
"snippet": str(c["handler_snippet"]),
|
|
391
|
+
})
|
|
392
|
+
|
|
393
|
+
# For each consumer handler discovered, find downstream database queries and operations
|
|
394
|
+
visited_handlers: set[str] = set()
|
|
395
|
+
for c in consumers:
|
|
396
|
+
h_id = c["handler_canonical_id"]
|
|
397
|
+
if h_id in visited_handlers:
|
|
398
|
+
continue
|
|
399
|
+
visited_handlers.add(h_id)
|
|
400
|
+
|
|
401
|
+
try:
|
|
402
|
+
db_rows = con.execute(
|
|
403
|
+
"""
|
|
404
|
+
SELECT query_id, operation, relationship, table_name, file_path, start_line, evidence
|
|
405
|
+
FROM db_queries
|
|
406
|
+
WHERE caller_symbol_id = ?
|
|
407
|
+
ORDER BY file_path ASC, start_line ASC
|
|
408
|
+
""",
|
|
409
|
+
(h_id,),
|
|
410
|
+
).fetchall()
|
|
411
|
+
for db in db_rows:
|
|
412
|
+
op_dict = {
|
|
413
|
+
"consumer": h_id,
|
|
414
|
+
"operation": str(db["operation"]),
|
|
415
|
+
"relationship": str(db["relationship"]),
|
|
416
|
+
"table_name": str(db["table_name"]),
|
|
417
|
+
"file": str(db["file_path"]),
|
|
418
|
+
"line": int(db["start_line"]),
|
|
419
|
+
"evidence": str(db["evidence"]),
|
|
420
|
+
}
|
|
421
|
+
downstream_ops.append(op_dict)
|
|
422
|
+
flow_steps.append({
|
|
423
|
+
"step": len(flow_steps) + 1,
|
|
424
|
+
"source": h_id,
|
|
425
|
+
"target": f"table:{db['table_name']}",
|
|
426
|
+
"relationship": str(db["relationship"]),
|
|
427
|
+
"evidence_class": "AST_VERIFIED",
|
|
428
|
+
"confidence": "HIGH",
|
|
429
|
+
"file": str(db["file_path"]),
|
|
430
|
+
"line": int(db["start_line"]),
|
|
431
|
+
"snippet": f"{db['operation']} on {db['table_name']}",
|
|
432
|
+
})
|
|
433
|
+
except sqlite3.OperationalError:
|
|
434
|
+
pass
|
|
435
|
+
|
|
436
|
+
return {
|
|
437
|
+
"status": "ok" if flow_steps else "not_found",
|
|
438
|
+
"entrypoint": clean,
|
|
439
|
+
"step_count": len(flow_steps),
|
|
440
|
+
"producers_count": len(producers),
|
|
441
|
+
"consumers_count": len(consumers),
|
|
442
|
+
"downstream_ops_count": len(downstream_ops),
|
|
443
|
+
"producers": producers,
|
|
444
|
+
"consumers": consumers,
|
|
445
|
+
"downstream_ops": downstream_ops,
|
|
446
|
+
"flow_steps": flow_steps,
|
|
447
|
+
}
|