codegraph-engine 3.0.0__1-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 +37 -0
- codegraph/agent.py +26 -0
- codegraph/agent_brain.py +2212 -0
- codegraph/agent_capabilities.py +4058 -0
- codegraph/agent_rules.py +531 -0
- codegraph/api_drift.py +455 -0
- codegraph/architecture.py +371 -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/audit.py +106 -0
- codegraph/binding_resolver.py +616 -0
- codegraph/branch_comparison.py +197 -0
- codegraph/cache.py +95 -0
- codegraph/change_impact.py +547 -0
- codegraph/cli.py +2436 -0
- codegraph/cli_output.py +205 -0
- codegraph/config.py +45 -0
- codegraph/constraints.py +238 -0
- codegraph/context.py +2182 -0
- codegraph/context_freshness.py +351 -0
- codegraph/database/__init__.py +72 -0
- codegraph/database/extractor.py +3651 -0
- codegraph/database/interrogation.py +1494 -0
- codegraph/database/models.py +310 -0
- codegraph/database/schema_drift.py +862 -0
- codegraph/epistemic.py +200 -0
- codegraph/errors.py +282 -0
- codegraph/evidence/__init__.py +15 -0
- codegraph/evidence/citations.py +418 -0
- codegraph/evidence_contract.py +486 -0
- codegraph/frameworks.py +960 -0
- codegraph/frameworks_nestjs.py +502 -0
- codegraph/frameworks_nextjs.py +337 -0
- codegraph/frameworks_react.py +457 -0
- codegraph/freshness.py +335 -0
- codegraph/frontend_html.py +109 -0
- codegraph/git.py +362 -0
- codegraph/git_state.py +545 -0
- codegraph/graph/__init__.py +46 -0
- codegraph/graph/models.py +56 -0
- codegraph/graph/traversal.py +1993 -0
- codegraph/indexing/__init__.py +24 -0
- codegraph/indexing/classifier.py +564 -0
- codegraph/indexing/indexer.py +2578 -0
- codegraph/indexing/models.py +427 -0
- codegraph/indexing/parser.py +3404 -0
- codegraph/indexing/pool.py +136 -0
- codegraph/indexing/scanner.py +211 -0
- codegraph/indexing/telemetry.py +326 -0
- codegraph/indexing/templates.py +418 -0
- codegraph/indexing/test_framework.py +116 -0
- codegraph/installer.py +1702 -0
- codegraph/interrogation.py +2355 -0
- codegraph/llm/__init__.py +3 -0
- codegraph/llm/base.py +15 -0
- codegraph/llm/context.py +20 -0
- codegraph/mcp/__init__.py +3 -0
- codegraph/mcp/server.py +1476 -0
- codegraph/mcp_diagnostics.py +882 -0
- codegraph/memory/__init__.py +3 -0
- codegraph/memory/store.py +46 -0
- codegraph/models.py +310 -0
- codegraph/monorepo.py +925 -0
- codegraph/observability.py +262 -0
- codegraph/optimizer.py +1014 -0
- codegraph/planner.py +417 -0
- codegraph/process_lifecycle.py +1153 -0
- codegraph/py.typed +1 -0
- codegraph/query_expansion.py +199 -0
- codegraph/ranking.py +408 -0
- codegraph/refactor/__init__.py +36 -0
- codegraph/refactor/models.py +114 -0
- codegraph/refactor/renamer.py +844 -0
- codegraph/refactor/transactions.py +414 -0
- codegraph/resolver.py +3968 -0
- codegraph/resources/__init__.py +46 -0
- codegraph/resources/cache.py +117 -0
- codegraph/resources/coalescer.py +83 -0
- codegraph/resources/debouncer.py +98 -0
- codegraph/resources/governor.py +219 -0
- codegraph/resources/monitor.py +102 -0
- codegraph/resources/policy.py +123 -0
- codegraph/retrieval_policy.py +320 -0
- codegraph/roblox.py +2174 -0
- codegraph/route_composer.py +363 -0
- codegraph/route_topology.py +172 -0
- codegraph/runtime/__init__.py +51 -0
- codegraph/runtime/collector.py +155 -0
- codegraph/runtime/db_watcher.py +191 -0
- codegraph/runtime/ingestor.py +692 -0
- codegraph/runtime/models.py +130 -0
- codegraph/runtime/port_inspector.py +304 -0
- codegraph/runtime/reconciliation.py +492 -0
- codegraph/runtime/runner.py +353 -0
- codegraph/runtime/trace_linker.py +175 -0
- codegraph/search/__init__.py +25 -0
- codegraph/search/hybrid.py +846 -0
- codegraph/search/semantic.py +28 -0
- codegraph/security/__init__.py +51 -0
- codegraph/security/paths.py +189 -0
- codegraph/security/redaction.py +633 -0
- codegraph/semantic_conflicts.py +270 -0
- codegraph/semantic_decorators.py +372 -0
- codegraph/structural_diff.py +542 -0
- codegraph/styles.py +144 -0
- codegraph/symbol_history.py +277 -0
- codegraph/target_resolver.py +348 -0
- codegraph/task.py +637 -0
- codegraph/tool_selection_eval.py +1158 -0
- codegraph/tsconfig.py +236 -0
- codegraph/ui/__init__.py +6 -0
- codegraph/ui/server.py +755 -0
- codegraph/watcher.py +323 -0
- codegraph_engine-3.0.0.dist-info/METADATA +859 -0
- codegraph_engine-3.0.0.dist-info/RECORD +130 -0
- codegraph_engine-3.0.0.dist-info/WHEEL +6 -0
- codegraph_engine-3.0.0.dist-info/entry_points.txt +2 -0
- codegraph_engine-3.0.0.dist-info/licenses/LICENSE +21 -0
- codegraph_engine-3.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,427 @@
|
|
|
1
|
+
"""Static-Runtime OpenTelemetry Reconciliation Engine.
|
|
2
|
+
|
|
3
|
+
Epistemic Invariants:
|
|
4
|
+
1. Do not replace static edges with runtime edges: Add runtime evidence to them.
|
|
5
|
+
2. If static topic match exists, upgrade evidence to OTEL_DISTRIBUTED_VERIFIED.
|
|
6
|
+
3. If consumer is external (e.g. Repo B), record as RUNTIME_ONLY_OBSERVED with service identity:
|
|
7
|
+
service = <c.service_name>
|
|
8
|
+
operation = <c.name>
|
|
9
|
+
repository = UNKNOWN
|
|
10
|
+
Do not invent a local AST symbol.
|
|
11
|
+
4. Bounded aggregation: Bounded sliding reservoir of at most 100 recent durations per link.
|
|
12
|
+
Nearest-rank percentile estimation method with documented precision.
|
|
13
|
+
5. Idempotent: Re-ingesting previously observed spans is a no-op and never duplicates counts.
|
|
14
|
+
"""
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import hashlib
|
|
18
|
+
import json
|
|
19
|
+
import sqlite3
|
|
20
|
+
import time
|
|
21
|
+
from typing import Any
|
|
22
|
+
|
|
23
|
+
from codegraph.async_queue.otel_models import (
|
|
24
|
+
OTelEvidenceStatus,
|
|
25
|
+
OTelSpanRecord,
|
|
26
|
+
TraceReconciliationResult,
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
OTEL_SCHEMA = """
|
|
30
|
+
CREATE TABLE IF NOT EXISTS otel_ingested_spans (
|
|
31
|
+
composite_id TEXT PRIMARY KEY,
|
|
32
|
+
trace_id TEXT NOT NULL,
|
|
33
|
+
span_id TEXT NOT NULL,
|
|
34
|
+
parent_span_id TEXT NOT NULL DEFAULT '',
|
|
35
|
+
service_name TEXT NOT NULL DEFAULT '',
|
|
36
|
+
name TEXT NOT NULL DEFAULT '',
|
|
37
|
+
kind TEXT NOT NULL DEFAULT '',
|
|
38
|
+
duration_ms REAL NOT NULL DEFAULT 0.0,
|
|
39
|
+
status_code TEXT NOT NULL DEFAULT 'OK',
|
|
40
|
+
messaging_system TEXT NOT NULL DEFAULT '',
|
|
41
|
+
messaging_dest TEXT NOT NULL DEFAULT '',
|
|
42
|
+
messaging_op TEXT NOT NULL DEFAULT '',
|
|
43
|
+
corr_id_hash TEXT NOT NULL DEFAULT '',
|
|
44
|
+
ingested_at INTEGER NOT NULL
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
CREATE INDEX IF NOT EXISTS idx_otel_spans_trace ON otel_ingested_spans(trace_id);
|
|
48
|
+
CREATE INDEX IF NOT EXISTS idx_otel_spans_dest ON otel_ingested_spans(messaging_dest);
|
|
49
|
+
CREATE INDEX IF NOT EXISTS idx_otel_spans_corr ON otel_ingested_spans(corr_id_hash);
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def ensure_otel_tables(con: sqlite3.Connection) -> None:
|
|
54
|
+
"""Ensure otel storage tables exist."""
|
|
55
|
+
con.executescript(OTEL_SCHEMA)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def compute_percentiles(samples: list[float]) -> tuple[float, float, float]:
|
|
59
|
+
"""Compute (p50, p95, p99) using the nearest-rank method on a bounded reservoir (N <= 100)."""
|
|
60
|
+
if not samples:
|
|
61
|
+
return 0.0, 0.0, 0.0
|
|
62
|
+
sorted_s = sorted(samples)
|
|
63
|
+
n = len(sorted_s)
|
|
64
|
+
|
|
65
|
+
def _rank(p: float) -> float:
|
|
66
|
+
idx = max(0, min(int(round(p * n - 0.5)), n - 1))
|
|
67
|
+
return round(sorted_s[idx], 2)
|
|
68
|
+
|
|
69
|
+
return _rank(0.50), _rank(0.95), _rank(0.99)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class OTelTraceReconciler:
|
|
73
|
+
"""Idempotent reconciliation engine for OpenTelemetry traces and CodeGraph async queues."""
|
|
74
|
+
|
|
75
|
+
def __init__(self, con: sqlite3.Connection) -> None:
|
|
76
|
+
self.con = con
|
|
77
|
+
self.con.row_factory = sqlite3.Row
|
|
78
|
+
ensure_otel_tables(self.con)
|
|
79
|
+
|
|
80
|
+
def reconcile_spans(
|
|
81
|
+
self,
|
|
82
|
+
spans: list[OTelSpanRecord],
|
|
83
|
+
) -> TraceReconciliationResult:
|
|
84
|
+
"""Idempotently ingest spans, pair publishers and consumers, and reconcile with graph."""
|
|
85
|
+
if not spans:
|
|
86
|
+
return TraceReconciliationResult(
|
|
87
|
+
spans_ingested=0,
|
|
88
|
+
spans_deduplicated=0,
|
|
89
|
+
links_upgraded=0,
|
|
90
|
+
links_created_runtime_only=0,
|
|
91
|
+
conflicts_detected=0,
|
|
92
|
+
details=[],
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
now_ts = int(time.time())
|
|
96
|
+
|
|
97
|
+
# 1. Filter out previously ingested spans (idempotence guard)
|
|
98
|
+
existing_keys = {
|
|
99
|
+
str(r[0])
|
|
100
|
+
for r in self.con.execute(
|
|
101
|
+
f"SELECT composite_id FROM otel_ingested_spans WHERE composite_id IN ({','.join(['?']*len(spans))})",
|
|
102
|
+
[s.composite_id for s in spans],
|
|
103
|
+
).fetchall()
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
fresh_spans = [s for s in spans if s.composite_id not in existing_keys]
|
|
107
|
+
spans_deduplicated = len(spans) - len(fresh_spans)
|
|
108
|
+
|
|
109
|
+
if not fresh_spans:
|
|
110
|
+
return TraceReconciliationResult(
|
|
111
|
+
spans_ingested=0,
|
|
112
|
+
spans_deduplicated=spans_deduplicated,
|
|
113
|
+
links_upgraded=0,
|
|
114
|
+
links_created_runtime_only=0,
|
|
115
|
+
conflicts_detected=0,
|
|
116
|
+
details=[{"message": "All spans were previously ingested; zero new updates."}],
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
# 2. Insert fresh spans into otel_ingested_spans
|
|
120
|
+
insert_rows = [
|
|
121
|
+
(
|
|
122
|
+
s.composite_id,
|
|
123
|
+
s.trace_id,
|
|
124
|
+
s.span_id,
|
|
125
|
+
s.parent_span_id,
|
|
126
|
+
s.service_name,
|
|
127
|
+
s.name,
|
|
128
|
+
s.kind,
|
|
129
|
+
s.duration_ms,
|
|
130
|
+
s.status_code,
|
|
131
|
+
s.messaging_system,
|
|
132
|
+
s.messaging_destination,
|
|
133
|
+
s.messaging_operation,
|
|
134
|
+
s.correlation_id_hash,
|
|
135
|
+
now_ts,
|
|
136
|
+
)
|
|
137
|
+
for s in fresh_spans
|
|
138
|
+
]
|
|
139
|
+
self.con.executemany(
|
|
140
|
+
"""
|
|
141
|
+
INSERT OR IGNORE INTO otel_ingested_spans (
|
|
142
|
+
composite_id, trace_id, span_id, parent_span_id, service_name, name, kind,
|
|
143
|
+
duration_ms, status_code, messaging_system, messaging_dest, messaging_op,
|
|
144
|
+
corr_id_hash, ingested_at
|
|
145
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
146
|
+
""",
|
|
147
|
+
insert_rows,
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
# 3. Categorize into producers and consumers
|
|
151
|
+
producers: list[OTelSpanRecord] = []
|
|
152
|
+
consumers: list[OTelSpanRecord] = []
|
|
153
|
+
|
|
154
|
+
for s in fresh_spans:
|
|
155
|
+
if s.messaging_operation == "publish" or s.kind == "PRODUCER":
|
|
156
|
+
producers.append(s)
|
|
157
|
+
elif s.messaging_operation in ("process", "receive") or s.kind == "CONSUMER":
|
|
158
|
+
consumers.append(s)
|
|
159
|
+
|
|
160
|
+
# 4. Correlate producers to consumers
|
|
161
|
+
# Matching index:
|
|
162
|
+
# Key: (trace_id, destination) OR parent_span_id OR correlation_id_hash
|
|
163
|
+
paired_links: list[tuple[OTelSpanRecord, OTelSpanRecord, float]] = []
|
|
164
|
+
|
|
165
|
+
for p in producers:
|
|
166
|
+
for c in consumers:
|
|
167
|
+
matched = False
|
|
168
|
+
# Match A: Parent-child span link
|
|
169
|
+
if c.parent_span_id and c.parent_span_id == p.span_id:
|
|
170
|
+
matched = True
|
|
171
|
+
# Match B: Span links
|
|
172
|
+
elif any(l_trace == p.trace_id and l_span == p.span_id for l_trace, l_span in c.links):
|
|
173
|
+
matched = True
|
|
174
|
+
# Match C: Same trace ID + same destination
|
|
175
|
+
elif (
|
|
176
|
+
p.trace_id == c.trace_id
|
|
177
|
+
and p.messaging_destination
|
|
178
|
+
and p.messaging_destination == c.messaging_destination
|
|
179
|
+
):
|
|
180
|
+
matched = True
|
|
181
|
+
# Match D: Same correlation ID hash
|
|
182
|
+
elif p.correlation_id_hash and p.correlation_id_hash == c.correlation_id_hash:
|
|
183
|
+
matched = True
|
|
184
|
+
|
|
185
|
+
if matched:
|
|
186
|
+
# Transit/execution latency:
|
|
187
|
+
if c.start_time_unix_nano > 0 and p.start_time_unix_nano > 0:
|
|
188
|
+
lat = max(0.0, (c.start_time_unix_nano - p.start_time_unix_nano) / 1_000_000.0)
|
|
189
|
+
else:
|
|
190
|
+
lat = max(0.0, p.duration_ms + c.duration_ms)
|
|
191
|
+
paired_links.append((p, c, lat))
|
|
192
|
+
|
|
193
|
+
upgraded_count = 0
|
|
194
|
+
runtime_only_count = 0
|
|
195
|
+
conflicts_count = 0
|
|
196
|
+
details: list[dict[str, Any]] = []
|
|
197
|
+
|
|
198
|
+
# 5. Reconcile pairs against static async_links
|
|
199
|
+
for p, c, lat_ms in paired_links:
|
|
200
|
+
dest = p.messaging_destination or c.messaging_destination
|
|
201
|
+
sys_name = p.messaging_system or c.messaging_system or "generic"
|
|
202
|
+
|
|
203
|
+
# Check if static link exists for destination
|
|
204
|
+
static_rows = self.con.execute(
|
|
205
|
+
"SELECT * FROM async_links WHERE destination = ?",
|
|
206
|
+
(dest,),
|
|
207
|
+
).fetchall()
|
|
208
|
+
|
|
209
|
+
if static_rows:
|
|
210
|
+
# Upgrade existing static links with runtime empirical evidence
|
|
211
|
+
for row in static_rows:
|
|
212
|
+
l_id = str(row["link_id"])
|
|
213
|
+
meta_raw = str(row["metadata_json"] or "{}")
|
|
214
|
+
try:
|
|
215
|
+
meta = json.loads(meta_raw)
|
|
216
|
+
except Exception:
|
|
217
|
+
meta = {}
|
|
218
|
+
|
|
219
|
+
reservoir: list[float] = meta.get("durations_reservoir", [])
|
|
220
|
+
reservoir.append(lat_ms)
|
|
221
|
+
if len(reservoir) > 100:
|
|
222
|
+
reservoir = reservoir[-100:] # Bounded reservoir
|
|
223
|
+
|
|
224
|
+
p50, p95, p99 = compute_percentiles(reservoir)
|
|
225
|
+
new_trace_count = int(row["trace_count"] or 0) + 1
|
|
226
|
+
err_inc = 1 if (p.status_code == "ERROR" or c.status_code == "ERROR") else 0
|
|
227
|
+
new_error_count = int(row["error_count"] or 0) + err_inc
|
|
228
|
+
avg_lat = round(sum(reservoir) / len(reservoir), 2)
|
|
229
|
+
|
|
230
|
+
meta["durations_reservoir"] = reservoir
|
|
231
|
+
meta["last_observed_trace_id"] = p.trace_id
|
|
232
|
+
meta["producer_service"] = p.service_name
|
|
233
|
+
meta["consumer_service"] = c.service_name
|
|
234
|
+
|
|
235
|
+
self.con.execute(
|
|
236
|
+
"""
|
|
237
|
+
UPDATE async_links SET
|
|
238
|
+
evidence_class = ?,
|
|
239
|
+
trace_count = ?,
|
|
240
|
+
avg_latency_ms = ?,
|
|
241
|
+
p50_ms = ?,
|
|
242
|
+
p95_ms = ?,
|
|
243
|
+
p99_ms = ?,
|
|
244
|
+
error_count = ?,
|
|
245
|
+
metadata_json = ?
|
|
246
|
+
WHERE link_id = ?
|
|
247
|
+
""",
|
|
248
|
+
(
|
|
249
|
+
OTelEvidenceStatus.OTEL_DISTRIBUTED_VERIFIED.value,
|
|
250
|
+
new_trace_count,
|
|
251
|
+
avg_lat,
|
|
252
|
+
p50,
|
|
253
|
+
p95,
|
|
254
|
+
p99,
|
|
255
|
+
new_error_count,
|
|
256
|
+
json.dumps(meta),
|
|
257
|
+
l_id,
|
|
258
|
+
),
|
|
259
|
+
)
|
|
260
|
+
|
|
261
|
+
# Update graph_edges to reflect OTEL_DISTRIBUTED_VERIFIED
|
|
262
|
+
self.con.execute(
|
|
263
|
+
"""
|
|
264
|
+
UPDATE graph_edges SET
|
|
265
|
+
evidence_class = 'OTEL_DISTRIBUTED_VERIFIED',
|
|
266
|
+
evidence = ?
|
|
267
|
+
WHERE source = ? AND target = ? AND relationship = 'STATIC_QUEUE_LINKED'
|
|
268
|
+
""",
|
|
269
|
+
(
|
|
270
|
+
f"Verified via OpenTelemetry trace {p.trace_id} (p50={p50}ms, p95={p95}ms)",
|
|
271
|
+
str(row["producer_id"]),
|
|
272
|
+
str(row["consumer_id"]),
|
|
273
|
+
),
|
|
274
|
+
)
|
|
275
|
+
upgraded_count += 1
|
|
276
|
+
details.append({
|
|
277
|
+
"link_id": l_id,
|
|
278
|
+
"destination": dest,
|
|
279
|
+
"action": "UPGRADED_TO_OTEL_DISTRIBUTED_VERIFIED",
|
|
280
|
+
"p50_ms": p50,
|
|
281
|
+
"p95_ms": p95,
|
|
282
|
+
"p99_ms": p99,
|
|
283
|
+
"trace_count": new_trace_count,
|
|
284
|
+
})
|
|
285
|
+
else:
|
|
286
|
+
# No static link in repository index (e.g. cross-repository worker in Repo B)
|
|
287
|
+
# Invariant: Record as RUNTIME_ONLY_OBSERVED with service identity. Do NOT invent a local symbol.
|
|
288
|
+
seed = f"rt:{p.service_name}:{dest}:{c.service_name}:{c.name}"
|
|
289
|
+
link_id = hashlib.sha256(seed.encode("utf-8")).hexdigest()[:16]
|
|
290
|
+
q_id = f"{sys_name}:{dest}"
|
|
291
|
+
|
|
292
|
+
meta = {
|
|
293
|
+
"service": c.service_name or "external_worker",
|
|
294
|
+
"operation": c.name or "process",
|
|
295
|
+
"repository": "UNKNOWN",
|
|
296
|
+
"durations_reservoir": [lat_ms],
|
|
297
|
+
"producer_service": p.service_name,
|
|
298
|
+
"last_observed_trace_id": p.trace_id,
|
|
299
|
+
}
|
|
300
|
+
p50, p95, p99 = lat_ms, lat_ms, lat_ms
|
|
301
|
+
|
|
302
|
+
# Ensure queue exists in async_queues
|
|
303
|
+
self.con.execute(
|
|
304
|
+
"""
|
|
305
|
+
INSERT OR IGNORE INTO async_queues (queue_id, system, destination, service)
|
|
306
|
+
VALUES (?, ?, ?, ?)
|
|
307
|
+
""",
|
|
308
|
+
(q_id, sys_name, dest, p.service_name),
|
|
309
|
+
)
|
|
310
|
+
|
|
311
|
+
# Check if this repository has a local producer for destination
|
|
312
|
+
prod_record = self.con.execute(
|
|
313
|
+
"SELECT caller_canonical_id, file_path, line FROM async_producers WHERE destination = ? LIMIT 1",
|
|
314
|
+
(dest,),
|
|
315
|
+
).fetchone()
|
|
316
|
+
|
|
317
|
+
if prod_record:
|
|
318
|
+
producer_id = str(prod_record["caller_canonical_id"])
|
|
319
|
+
prod_file = str(prod_record["file_path"])
|
|
320
|
+
prod_line = int(prod_record["line"])
|
|
321
|
+
else:
|
|
322
|
+
producer_id = p.name or f"{p.service_name}:publisher"
|
|
323
|
+
prod_file = ""
|
|
324
|
+
prod_line = 0
|
|
325
|
+
|
|
326
|
+
consumer_id = f"external:{c.service_name or 'unknown'}:{c.name or 'handler'}"
|
|
327
|
+
|
|
328
|
+
self.con.execute(
|
|
329
|
+
"""
|
|
330
|
+
INSERT INTO async_links (
|
|
331
|
+
link_id, queue_id, system, destination,
|
|
332
|
+
producer_id, producer_file, producer_line,
|
|
333
|
+
consumer_id, consumer_file, consumer_line,
|
|
334
|
+
evidence_class, confidence, trace_count,
|
|
335
|
+
avg_latency_ms, p50_ms, p95_ms, p99_ms,
|
|
336
|
+
error_count, metadata_json
|
|
337
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
338
|
+
ON CONFLICT(link_id) DO UPDATE SET
|
|
339
|
+
trace_count = trace_count + 1,
|
|
340
|
+
avg_latency_ms = excluded.avg_latency_ms,
|
|
341
|
+
p50_ms = excluded.p50_ms,
|
|
342
|
+
p95_ms = excluded.p95_ms,
|
|
343
|
+
p99_ms = excluded.p99_ms
|
|
344
|
+
""",
|
|
345
|
+
(
|
|
346
|
+
link_id,
|
|
347
|
+
q_id,
|
|
348
|
+
sys_name,
|
|
349
|
+
dest,
|
|
350
|
+
producer_id,
|
|
351
|
+
prod_file,
|
|
352
|
+
prod_line,
|
|
353
|
+
consumer_id,
|
|
354
|
+
"",
|
|
355
|
+
0,
|
|
356
|
+
OTelEvidenceStatus.RUNTIME_ONLY_OBSERVED.value,
|
|
357
|
+
"HIGH",
|
|
358
|
+
1,
|
|
359
|
+
lat_ms,
|
|
360
|
+
p50,
|
|
361
|
+
p95,
|
|
362
|
+
p99,
|
|
363
|
+
1 if (p.status_code == "ERROR" or c.status_code == "ERROR") else 0,
|
|
364
|
+
json.dumps(meta),
|
|
365
|
+
),
|
|
366
|
+
)
|
|
367
|
+
|
|
368
|
+
# If local producer file exists, insert into graph_edges; otherwise insert into runtime_edges
|
|
369
|
+
if prod_file:
|
|
370
|
+
self.con.execute(
|
|
371
|
+
"""
|
|
372
|
+
INSERT OR IGNORE INTO graph_edges (
|
|
373
|
+
source, target, relationship, confidence, file,
|
|
374
|
+
start_line, end_line, evidence, evidence_class
|
|
375
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
376
|
+
""",
|
|
377
|
+
(
|
|
378
|
+
producer_id,
|
|
379
|
+
consumer_id,
|
|
380
|
+
"OTEL_DISTRIBUTED_VERIFIED",
|
|
381
|
+
"HIGH",
|
|
382
|
+
prod_file,
|
|
383
|
+
prod_line,
|
|
384
|
+
prod_line,
|
|
385
|
+
f"Cross-service runtime link via {q_id} (service: {c.service_name})",
|
|
386
|
+
OTelEvidenceStatus.RUNTIME_ONLY_OBSERVED.value,
|
|
387
|
+
),
|
|
388
|
+
)
|
|
389
|
+
else:
|
|
390
|
+
edge_id = hashlib.sha256(f"{producer_id}:{consumer_id}:{q_id}".encode()).hexdigest()[:16]
|
|
391
|
+
self.con.execute(
|
|
392
|
+
"""
|
|
393
|
+
INSERT OR IGNORE INTO runtime_edges (
|
|
394
|
+
edge_id, source, target, relationship, operation, evidence_class, observation_count
|
|
395
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
396
|
+
""",
|
|
397
|
+
(
|
|
398
|
+
edge_id,
|
|
399
|
+
producer_id,
|
|
400
|
+
consumer_id,
|
|
401
|
+
"OTEL_DISTRIBUTED_VERIFIED",
|
|
402
|
+
dest,
|
|
403
|
+
OTelEvidenceStatus.RUNTIME_ONLY_OBSERVED.value,
|
|
404
|
+
1,
|
|
405
|
+
),
|
|
406
|
+
)
|
|
407
|
+
runtime_only_count += 1
|
|
408
|
+
details.append({
|
|
409
|
+
"link_id": link_id,
|
|
410
|
+
"destination": dest,
|
|
411
|
+
"action": "CREATED_RUNTIME_ONLY_OBSERVED",
|
|
412
|
+
"consumer_service": c.service_name,
|
|
413
|
+
"consumer_operation": c.name,
|
|
414
|
+
"repository": "UNKNOWN",
|
|
415
|
+
"latency_ms": lat_ms,
|
|
416
|
+
})
|
|
417
|
+
|
|
418
|
+
self.con.commit()
|
|
419
|
+
|
|
420
|
+
return TraceReconciliationResult(
|
|
421
|
+
spans_ingested=len(fresh_spans),
|
|
422
|
+
spans_deduplicated=spans_deduplicated,
|
|
423
|
+
links_upgraded=upgraded_count,
|
|
424
|
+
links_created_runtime_only=runtime_only_count,
|
|
425
|
+
conflicts_detected=conflicts_count,
|
|
426
|
+
details=details,
|
|
427
|
+
)
|