codegraph-engine 2.1.2__py3-none-any.whl → 2.1.7__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.
Files changed (61) hide show
  1. codegraph/__init__.py +1 -1
  2. codegraph/agent_brain.py +1961 -0
  3. codegraph/agent_capabilities.py +3500 -0
  4. codegraph/agent_rules.py +530 -0
  5. codegraph/architecture.py +48 -5
  6. codegraph/binding_resolver.py +616 -0
  7. codegraph/cli.py +345 -5
  8. codegraph/config.py +2 -0
  9. codegraph/context.py +845 -93
  10. codegraph/database/__init__.py +58 -0
  11. codegraph/database/extractor.py +2956 -0
  12. codegraph/database/interrogation.py +1085 -0
  13. codegraph/database/models.py +310 -0
  14. codegraph/epistemic.py +95 -0
  15. codegraph/errors.py +7 -0
  16. codegraph/evidence/citations.py +22 -1
  17. codegraph/evidence_contract.py +455 -0
  18. codegraph/frameworks.py +372 -11
  19. codegraph/git.py +92 -10
  20. codegraph/graph/models.py +15 -0
  21. codegraph/graph/traversal.py +680 -121
  22. codegraph/indexing/__init__.py +15 -1
  23. codegraph/indexing/classifier.py +343 -58
  24. codegraph/indexing/indexer.py +1515 -141
  25. codegraph/indexing/models.py +75 -0
  26. codegraph/indexing/parser.py +1843 -70
  27. codegraph/indexing/scanner.py +4 -1
  28. codegraph/indexing/telemetry.py +326 -0
  29. codegraph/installer.py +1652 -0
  30. codegraph/interrogation.py +708 -122
  31. codegraph/mcp/server.py +525 -107
  32. codegraph/mcp_diagnostics.py +879 -0
  33. codegraph/models.py +14 -0
  34. codegraph/monorepo.py +922 -0
  35. codegraph/observability.py +113 -2
  36. codegraph/optimizer.py +647 -42
  37. codegraph/ranking.py +61 -16
  38. codegraph/resolver.py +2006 -77
  39. codegraph/resources/monitor.py +1 -1
  40. codegraph/retrieval_policy.py +96 -17
  41. codegraph/route_composer.py +363 -0
  42. codegraph/runtime/__init__.py +32 -0
  43. codegraph/runtime/ingestor.py +672 -0
  44. codegraph/runtime/models.py +130 -0
  45. codegraph/runtime/reconciliation.py +492 -0
  46. codegraph/search/__init__.py +2 -0
  47. codegraph/search/hybrid.py +572 -53
  48. codegraph/security/__init__.py +50 -2
  49. codegraph/security/paths.py +162 -8
  50. codegraph/security/redaction.py +633 -0
  51. codegraph/semantic_decorators.py +341 -0
  52. codegraph/target_resolver.py +6 -6
  53. codegraph/tool_selection_eval.py +1158 -0
  54. codegraph_engine-2.1.7.dist-info/METADATA +617 -0
  55. codegraph_engine-2.1.7.dist-info/RECORD +84 -0
  56. {codegraph_engine-2.1.2.dist-info → codegraph_engine-2.1.7.dist-info}/licenses/LICENSE +1 -1
  57. codegraph_engine-2.1.2.dist-info/METADATA +0 -336
  58. codegraph_engine-2.1.2.dist-info/RECORD +0 -63
  59. {codegraph_engine-2.1.2.dist-info → codegraph_engine-2.1.7.dist-info}/WHEEL +0 -0
  60. {codegraph_engine-2.1.2.dist-info → codegraph_engine-2.1.7.dist-info}/entry_points.txt +0 -0
  61. {codegraph_engine-2.1.2.dist-info → codegraph_engine-2.1.7.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,130 @@
1
+ """Canonical Runtime Observation & Static-Runtime Reconciliation Models (Phases 10-13, 16).
2
+
3
+ Architectural Rules:
4
+ 1. Runtime mapping is an OPTIONAL observation layer.
5
+ 2. Static and runtime evidence remain strictly distinguishable (`evidence_class="RUNTIME_OBSERVED"`).
6
+ 3. Never convert runtime evidence into static proof (`AST_VERIFIED`).
7
+ 4. Never convert static inference into runtime fact.
8
+ 5. `NOT_OBSERVED_AT_RUNTIME` means no runtime observation was ingested for the static edge;
9
+ it NEVER means the path cannot happen.
10
+ """
11
+ from __future__ import annotations
12
+
13
+ from dataclasses import dataclass, field
14
+ from enum import StrEnum
15
+ from typing import Any
16
+
17
+ from codegraph.security.redaction import normalize_and_redact_sql, redact_payload, redact_secrets
18
+
19
+
20
+ class ReconciliationStatus(StrEnum):
21
+ CONFIRMED_RUNTIME_PATH = "CONFIRMED_RUNTIME_PATH"
22
+ STATIC_RUNTIME_CONFLICT = "STATIC_RUNTIME_CONFLICT"
23
+ NOT_OBSERVED_AT_RUNTIME = "NOT_OBSERVED_AT_RUNTIME"
24
+ RUNTIME_ONLY_OBSERVED = "RUNTIME_ONLY_OBSERVED"
25
+
26
+
27
+ @dataclass(frozen=True)
28
+ class RuntimeEvent:
29
+ """Normalized, privacy-safe runtime observation event."""
30
+
31
+ event_id: str
32
+ trace_id: str
33
+ span_id: str
34
+ parent_span_id: str = ""
35
+ timestamp: str = ""
36
+ source_format: str = "json" # otel | json | sql_log
37
+ http_method: str = ""
38
+ route_path: str = ""
39
+ handler_symbol: str = ""
40
+ caller_symbol: str = ""
41
+ callee_symbol: str = ""
42
+ service_name: str = ""
43
+ db_operation: str = "" # SELECT | INSERT | UPDATE | DELETE | ""
44
+ db_table: str = ""
45
+ db_columns: tuple[str, ...] = ()
46
+ normalized_sql: str = ""
47
+ status_code: int | None = None
48
+ exception_type: str = ""
49
+ duration_ms: float = 0.0
50
+ evidence_class: str = "RUNTIME_OBSERVED"
51
+ runtime_generation: int = 1
52
+ metadata: dict[str, Any] = field(default_factory=dict)
53
+
54
+ def __post_init__(self) -> None:
55
+ if self.normalized_sql:
56
+ object.__setattr__(self, "normalized_sql", normalize_and_redact_sql(self.normalized_sql))
57
+ if self.metadata:
58
+ object.__setattr__(self, "metadata", redact_payload(dict(self.metadata)))
59
+
60
+ def as_dict(self) -> dict[str, Any]:
61
+ return {
62
+ "event_id": self.event_id,
63
+ "trace_id": self.trace_id,
64
+ "span_id": self.span_id,
65
+ "parent_span_id": self.parent_span_id,
66
+ "timestamp": self.timestamp,
67
+ "source_format": self.source_format,
68
+ "http_method": self.http_method,
69
+ "route_path": redact_secrets(self.route_path),
70
+ "handler_symbol": self.handler_symbol,
71
+ "caller_symbol": self.caller_symbol,
72
+ "callee_symbol": self.callee_symbol,
73
+ "service_name": self.service_name,
74
+ "db_operation": self.db_operation,
75
+ "db_table": self.db_table,
76
+ "db_columns": list(self.db_columns),
77
+ "normalized_sql": self.normalized_sql,
78
+ "status_code": self.status_code,
79
+ "exception_type": redact_secrets(self.exception_type),
80
+ "duration_ms": self.duration_ms,
81
+ "evidence_class": "RUNTIME_OBSERVED",
82
+ "runtime_generation": self.runtime_generation,
83
+ "metadata": dict(self.metadata),
84
+ }
85
+
86
+
87
+ @dataclass(frozen=True)
88
+ class RuntimeAggregatedEdge:
89
+ """Compact, deduplicated runtime edge stored in SQLite (Phase 16)."""
90
+
91
+ edge_id: str
92
+ source: str
93
+ target: str
94
+ relationship: str # HANDLED_BY | CALLS | READS_TABLE | WRITES_TABLE | READS_COLUMN | WRITES_COLUMN
95
+ operation: str = ""
96
+ evidence_class: str = "RUNTIME_OBSERVED"
97
+ observation_count: int = 1
98
+ first_seen: str = ""
99
+ last_seen: str = ""
100
+ avg_duration_ms: float = 0.0
101
+ max_duration_ms: float = 0.0
102
+ sample_trace_id: str = ""
103
+ sample_span_id: str = ""
104
+ normalized_sql: str = ""
105
+ status_code: int | None = None
106
+ exception_type: str = ""
107
+ runtime_generation: int = 1
108
+
109
+ def as_dict(self) -> dict[str, Any]:
110
+ return {
111
+ "edge_id": self.edge_id,
112
+ "source": self.source,
113
+ "target": self.target,
114
+ "relationship": self.relationship,
115
+ "operation": self.operation,
116
+ "evidence_class": "RUNTIME_OBSERVED",
117
+ "confidence": "HIGH",
118
+ "status": "RUNTIME_OBSERVED",
119
+ "observation_count": self.observation_count,
120
+ "first_seen": self.first_seen,
121
+ "last_seen": self.last_seen,
122
+ "avg_duration_ms": round(self.avg_duration_ms, 3),
123
+ "max_duration_ms": round(self.max_duration_ms, 3),
124
+ "sample_trace_id": self.sample_trace_id,
125
+ "sample_span_id": self.sample_span_id,
126
+ "normalized_sql": self.normalized_sql,
127
+ "status_code": self.status_code,
128
+ "exception_type": self.exception_type,
129
+ "runtime_generation": self.runtime_generation,
130
+ }
@@ -0,0 +1,492 @@
1
+ """Runtime Map & Static-vs-Runtime Reconciliation Engine (Phases 12 & 13).
2
+
3
+ Enables CodeGraph to:
4
+ 1. Query the canonical runtime graph (`HTTP -> ROUTE -> HANDLER -> FUNCTION -> SERVICE -> DB OP -> TABLE -> COLUMN`)
5
+ and reverse runtime mappings (`TABLE -> WRITERS`, `TABLE -> READERS`, `ROUTE -> DATABASE`,
6
+ `DATABASE -> ROUTES`, `FUNCTION -> QUERIES`, `QUERY -> SOURCE LOCATION`).
7
+ 2. Compare static code+database expectations against runtime observations:
8
+ - `CONFIRMED_RUNTIME_PATH`
9
+ - `STATIC_RUNTIME_CONFLICT`
10
+ - `NOT_OBSERVED_AT_RUNTIME` (observational only; never implies impossibility)
11
+ - `RUNTIME_ONLY_OBSERVED` (never promoted to static `AST_VERIFIED`)
12
+ """
13
+ from __future__ import annotations
14
+
15
+ import json
16
+ import sqlite3
17
+ from typing import Any
18
+
19
+ from codegraph.security.redaction import redact_payload, redact_secrets
20
+
21
+ from .models import ReconciliationStatus
22
+
23
+
24
+ def get_runtime_trace(
25
+ con: sqlite3.Connection,
26
+ repo_or_trace_id: Any = None,
27
+ route: str | None = None,
28
+ symbol: str | None = None,
29
+ table: str | None = None,
30
+ *,
31
+ trace_id: str | None = None,
32
+ ) -> dict[str, Any]:
33
+ """Query runtime observations and the aggregated runtime graph (Phase 13)."""
34
+ if isinstance(repo_or_trace_id, str) and not trace_id:
35
+ trace_id = repo_or_trace_id
36
+ obs_rows = con.execute(
37
+ "SELECT event_id, trace_id, span_id, parent_span_id, timestamp, source_format, "
38
+ "http_method, route_path, handler_symbol, caller_symbol, callee_symbol, service_name, "
39
+ "db_operation, db_table, db_columns_json, normalized_sql, status_code, exception_type, "
40
+ "duration_ms, evidence_class, runtime_generation "
41
+ "FROM runtime_observations ORDER BY runtime_generation DESC, event_id ASC"
42
+ ).fetchall()
43
+
44
+ edge_rows = con.execute(
45
+ "SELECT edge_id, source, target, relationship, operation, evidence_class, "
46
+ "observation_count, first_seen, last_seen, avg_duration_ms, max_duration_ms, "
47
+ "sample_trace_id, sample_span_id, normalized_sql, status_code, exception_type, runtime_generation "
48
+ "FROM runtime_edges ORDER BY observation_count DESC, edge_id ASC"
49
+ ).fetchall()
50
+
51
+ q_trace = (trace_id or "").strip()
52
+ q_route = (route or "").strip().lower()
53
+ q_sym = (symbol or "").strip().lower()
54
+ q_tbl = (table or "").strip().lower().split(".")[-1]
55
+
56
+ # Pre-collect trace_ids that match q_route so child spans in the same trace are retained
57
+ route_matched_traces: set[str] = set()
58
+ if q_route:
59
+ for r in obs_rows:
60
+ if q_route in str(r["route_path"]).lower():
61
+ route_matched_traces.add(str(r["trace_id"]))
62
+
63
+ filtered_obs: list[dict[str, Any]] = []
64
+ for r in obs_rows:
65
+ if q_trace and str(r["trace_id"]) != q_trace:
66
+ continue
67
+ if (
68
+ q_route
69
+ and q_route not in str(r["route_path"]).lower()
70
+ and str(r["trace_id"]) not in route_matched_traces
71
+ ):
72
+ continue
73
+ if q_sym and not any(
74
+ q_sym in str(r[col]).lower()
75
+ for col in ("handler_symbol", "caller_symbol", "callee_symbol", "service_name")
76
+ ):
77
+ continue
78
+ if q_tbl and q_tbl != str(r["db_table"]).lower():
79
+ continue
80
+
81
+ # Attempt to map query back to static source location if symbol is known
82
+ caller_sym = str(r["caller_symbol"] or r["handler_symbol"] or "")
83
+ src_loc: dict[str, Any] | None = None
84
+ if caller_sym:
85
+ srow = con.execute(
86
+ "SELECT path, start_line, end_line FROM symbols WHERE canonical_id = ? OR qualified_name = ? OR name = ? LIMIT 1",
87
+ (caller_sym, caller_sym, caller_sym.split(".")[-1]),
88
+ ).fetchone()
89
+ if srow:
90
+ src_loc = {
91
+ "file": str(srow["path"]),
92
+ "start_line": int(srow["start_line"]),
93
+ "end_line": int(srow["end_line"]),
94
+ }
95
+
96
+ filtered_obs.append({
97
+ "event_id": str(r["event_id"]),
98
+ "trace_id": str(r["trace_id"]),
99
+ "span_id": str(r["span_id"]),
100
+ "parent_span_id": str(r["parent_span_id"]),
101
+ "timestamp": str(r["timestamp"]),
102
+ "source_format": str(r["source_format"]),
103
+ "http_method": str(r["http_method"]),
104
+ "route_path": redact_secrets(str(r["route_path"])),
105
+ "handler_symbol": str(r["handler_symbol"]),
106
+ "caller_symbol": caller_sym,
107
+ "callee_symbol": str(r["callee_symbol"]),
108
+ "service_name": str(r["service_name"]),
109
+ "db_operation": str(r["db_operation"]),
110
+ "db_table": str(r["db_table"]),
111
+ "db_columns": json.loads(r["db_columns_json"] or "[]"),
112
+ "normalized_sql": redact_secrets(str(r["normalized_sql"])),
113
+ "status_code": r["status_code"],
114
+ "exception_type": redact_secrets(str(r["exception_type"])),
115
+ "duration_ms": float(r["duration_ms"] or 0.0),
116
+ "evidence_class": "RUNTIME_OBSERVED",
117
+ "runtime_generation": int(r["runtime_generation"]),
118
+ "source_location": src_loc,
119
+ })
120
+
121
+ filtered_edges: list[dict[str, Any]] = []
122
+ table_writers: dict[str, list[str]] = {}
123
+ table_readers: dict[str, list[str]] = {}
124
+ route_to_tables: dict[str, list[str]] = {}
125
+ table_to_routes: dict[str, list[str]] = {}
126
+ function_to_queries: dict[str, list[str]] = {}
127
+
128
+ for er in edge_rows:
129
+ src = str(er["source"])
130
+ tgt = str(er["target"])
131
+ rel = str(er["relationship"])
132
+ op = str(er["operation"])
133
+ norm_sql = redact_secrets(str(er["normalized_sql"] or ""))
134
+
135
+ if rel == "WRITES_TABLE":
136
+ table_writers.setdefault(tgt, [])
137
+ if src not in table_writers[tgt]:
138
+ table_writers[tgt].append(src)
139
+ elif rel == "READS_TABLE":
140
+ table_readers.setdefault(tgt, [])
141
+ if src not in table_readers[tgt]:
142
+ table_readers[tgt].append(src)
143
+
144
+ if norm_sql:
145
+ function_to_queries.setdefault(src, [])
146
+ if norm_sql not in function_to_queries[src]:
147
+ function_to_queries[src].append(norm_sql)
148
+
149
+ if q_trace and str(er["sample_trace_id"]) != q_trace:
150
+ continue
151
+ if (
152
+ q_route
153
+ and q_route not in src.lower()
154
+ and q_route not in tgt.lower()
155
+ and str(er["sample_trace_id"]) not in route_matched_traces
156
+ ):
157
+ continue
158
+ if q_sym and q_sym not in src.lower() and q_sym not in tgt.lower():
159
+ continue
160
+ if q_tbl and q_tbl not in tgt.lower():
161
+ continue
162
+
163
+ filtered_edges.append({
164
+ "edge_id": str(er["edge_id"]),
165
+ "source": src,
166
+ "target": tgt,
167
+ "relationship": rel,
168
+ "operation": op,
169
+ "evidence_class": "RUNTIME_OBSERVED",
170
+ "confidence": "HIGH",
171
+ "status": "RUNTIME_OBSERVED",
172
+ "observation_count": int(er["observation_count"]),
173
+ "first_seen": str(er["first_seen"]),
174
+ "last_seen": str(er["last_seen"]),
175
+ "avg_duration_ms": round(float(er["avg_duration_ms"] or 0.0), 3),
176
+ "max_duration_ms": round(float(er["max_duration_ms"] or 0.0), 3),
177
+ "sample_trace_id": str(er["sample_trace_id"]),
178
+ "sample_span_id": str(er["sample_span_id"]),
179
+ "normalized_sql": norm_sql,
180
+ "status_code": er["status_code"],
181
+ "exception_type": redact_secrets(str(er["exception_type"] or "")),
182
+ "runtime_generation": int(er["runtime_generation"]),
183
+ })
184
+
185
+ # Compute ROUTE <-> DATABASE from observations sharing a trace_id
186
+ traces_routes: dict[str, set[str]] = {}
187
+ traces_tables: dict[str, set[str]] = {}
188
+ for obs in filtered_obs or [
189
+ {
190
+ "trace_id": str(r["trace_id"]),
191
+ "route_path": str(r["route_path"]),
192
+ "http_method": str(r["http_method"]),
193
+ "db_table": str(r["db_table"]),
194
+ }
195
+ for r in obs_rows
196
+ ]:
197
+ tid = str(obs["trace_id"])
198
+ rp = str(obs["route_path"])
199
+ hm = str(obs["http_method"] or "ANY")
200
+ dt = str(obs["db_table"])
201
+ if rp:
202
+ traces_routes.setdefault(tid, set()).add(f"{hm} {rp}")
203
+ if dt:
204
+ traces_tables.setdefault(tid, set()).add(dt)
205
+
206
+ for tid, rset in traces_routes.items():
207
+ tset = traces_tables.get(tid, set())
208
+ for r_item in sorted(rset):
209
+ for t_item in sorted(tset):
210
+ route_to_tables.setdefault(r_item, [])
211
+ if t_item not in route_to_tables[r_item]:
212
+ route_to_tables[r_item].append(t_item)
213
+ table_to_routes.setdefault(t_item, [])
214
+ if r_item not in table_to_routes[t_item]:
215
+ table_to_routes[t_item].append(r_item)
216
+
217
+ return redact_payload({
218
+ "status": "ok",
219
+ "count": len(filtered_edges),
220
+ "observation_count": len(filtered_obs),
221
+ "edge_count": len(filtered_edges),
222
+ "evidence_class": "RUNTIME_OBSERVED",
223
+ "observations": filtered_obs,
224
+ "edges": filtered_edges,
225
+ "runtime_edges": filtered_edges,
226
+ "reverse_maps": {
227
+ "table_to_writers": table_writers,
228
+ "table_to_readers": table_readers,
229
+ "route_to_database": route_to_tables,
230
+ "route_to_tables": route_to_tables,
231
+ "database_to_routes": table_to_routes,
232
+ "table_to_routes": table_to_routes,
233
+ "function_to_queries": function_to_queries,
234
+ },
235
+ "results": filtered_edges,
236
+ })
237
+
238
+
239
+ def reconcile_static_runtime(
240
+ con: sqlite3.Connection,
241
+ repo_or_symbol: Any = None,
242
+ route: str | None = None,
243
+ table: str | None = None,
244
+ *,
245
+ symbol: str | None = None,
246
+ ) -> dict[str, Any]:
247
+ """Compare static code+database expectations against runtime observations (Phase 12).
248
+
249
+ Classifies each relationship into:
250
+ - `CONFIRMED_RUNTIME_PATH`
251
+ - `STATIC_RUNTIME_CONFLICT`
252
+ - `NOT_OBSERVED_AT_RUNTIME`
253
+ - `RUNTIME_ONLY_OBSERVED`
254
+ """
255
+ if isinstance(repo_or_symbol, str) and not symbol:
256
+ symbol = repo_or_symbol
257
+ q_sym = (symbol or "").strip().lower()
258
+ q_route = (route or "").strip().lower()
259
+ q_tbl = (table or "").strip().lower().split(".")[-1]
260
+
261
+ # 1. Load static database edges from `graph_edges`
262
+ static_rows = con.execute(
263
+ "SELECT source, target, relationship, confidence, file, start_line, end_line, evidence, evidence_class "
264
+ "FROM graph_edges WHERE relationship IN ('READS_TABLE', 'WRITES_TABLE', 'POSSIBLE_TABLE', 'UNKNOWN_TABLE') "
265
+ "ORDER BY source, relationship, target"
266
+ ).fetchall()
267
+
268
+ # 2. Load runtime database edges from `runtime_edges`
269
+ runtime_rows = con.execute(
270
+ "SELECT edge_id, source, target, relationship, operation, observation_count, "
271
+ "first_seen, last_seen, avg_duration_ms, sample_trace_id, normalized_sql "
272
+ "FROM runtime_edges WHERE relationship IN ('READS_TABLE', 'WRITES_TABLE') "
273
+ "ORDER BY source, relationship, target"
274
+ ).fetchall()
275
+
276
+ def _norm_table(target_id: str) -> str:
277
+ return target_id.strip().lower().split(".")[-1]
278
+
279
+ # Group static edges by normalized source symbol
280
+ static_by_source: dict[str, list[dict[str, Any]]] = {}
281
+ for sr in static_rows:
282
+ src = str(sr["source"])
283
+ tgt = str(sr["target"])
284
+ if q_sym and q_sym not in src.lower() and q_sym != src.split(".")[-1].lower():
285
+ continue
286
+ if q_route and q_route not in src.lower():
287
+ continue
288
+ if q_tbl and q_tbl != _norm_table(tgt):
289
+ continue
290
+ static_by_source.setdefault(src, []).append({
291
+ "source": src,
292
+ "target": tgt,
293
+ "table_short": _norm_table(tgt),
294
+ "relationship": str(sr["relationship"]),
295
+ "confidence": str(sr["confidence"]),
296
+ "evidence_class": str(sr["evidence_class"]),
297
+ "file": str(sr["file"]),
298
+ "start_line": int(sr["start_line"]),
299
+ "evidence": redact_secrets(str(sr["evidence"])),
300
+ })
301
+
302
+ # Group runtime edges by source symbol (matching exact or short name against static symbols)
303
+ runtime_by_source: dict[str, list[dict[str, Any]]] = {}
304
+ for rr in runtime_rows:
305
+ r_src = str(rr["source"])
306
+ r_tgt = str(rr["target"])
307
+ if q_sym and q_sym not in r_src.lower() and q_sym != r_src.split(".")[-1].lower():
308
+ continue
309
+ if q_route and q_route not in r_src.lower():
310
+ continue
311
+ if q_tbl and q_tbl != _norm_table(r_tgt):
312
+ continue
313
+
314
+ # Match against static_by_source keys if short name matches
315
+ matched_key = r_src
316
+ for s_key in static_by_source:
317
+ if s_key.lower() == r_src.lower() or s_key.split(".")[-1].lower() == r_src.split(".")[-1].lower():
318
+ matched_key = s_key
319
+ break
320
+
321
+ runtime_by_source.setdefault(matched_key, []).append({
322
+ "edge_id": str(rr["edge_id"]),
323
+ "source": r_src,
324
+ "target": r_tgt,
325
+ "table_short": _norm_table(r_tgt),
326
+ "relationship": str(rr["relationship"]),
327
+ "operation": str(rr["operation"]),
328
+ "evidence_class": "RUNTIME_OBSERVED",
329
+ "observation_count": int(rr["observation_count"]),
330
+ "avg_duration_ms": round(float(rr["avg_duration_ms"] or 0.0), 3),
331
+ "sample_trace_id": str(rr["sample_trace_id"]),
332
+ "normalized_sql": redact_secrets(str(rr["normalized_sql"] or "")),
333
+ })
334
+
335
+ confirmed: list[dict[str, Any]] = []
336
+ conflicts: list[dict[str, Any]] = []
337
+ not_observed: list[dict[str, Any]] = []
338
+ runtime_only: list[dict[str, Any]] = []
339
+ all_items: list[dict[str, Any]] = []
340
+
341
+ all_sources = sorted(set(static_by_source.keys()) | set(runtime_by_source.keys()))
342
+
343
+ for src in all_sources:
344
+ s_edges = static_by_source.get(src, [])
345
+ r_edges = runtime_by_source.get(src, [])
346
+
347
+ matched_r_indices: set[int] = set()
348
+
349
+ for s_edge in s_edges:
350
+ s_tbl = s_edge["table_short"]
351
+ s_rel = s_edge["relationship"]
352
+
353
+ # 1. Exact (table, relationship) match -> CONFIRMED_RUNTIME_PATH
354
+ exact_idx = next(
355
+ (
356
+ i
357
+ for i, re_item in enumerate(r_edges)
358
+ if re_item["table_short"] == s_tbl and re_item["relationship"] == s_rel
359
+ ),
360
+ None,
361
+ )
362
+ if exact_idx is not None:
363
+ matched_r_indices.add(exact_idx)
364
+ re_item = r_edges[exact_idx]
365
+ item = {
366
+ "reconciliation_status": ReconciliationStatus.CONFIRMED_RUNTIME_PATH.value,
367
+ "reconciliation_state": ReconciliationStatus.CONFIRMED_RUNTIME_PATH.value,
368
+ "runtime_evidence_class": "RUNTIME_OBSERVED",
369
+ "source": src,
370
+ "table": s_tbl,
371
+ "static": s_edge,
372
+ "runtime": re_item,
373
+ "explanation": (
374
+ f"Static expectation ({src} -> {s_rel} {s_tbl}) confirmed by runtime observation "
375
+ f"({re_item['operation']} {s_tbl}, count={re_item['observation_count']})."
376
+ ),
377
+ }
378
+ confirmed.append(item)
379
+ all_items.append(item)
380
+ continue
381
+
382
+ # 2. If static had UNKNOWN_TABLE or POSSIBLE_TABLE and runtime observed a concrete table:
383
+ # Keep static as UNKNOWN/POSSIBLE and report RUNTIME_ONLY_OBSERVED without promoting static proof
384
+ if s_rel in ("UNKNOWN_TABLE", "POSSIBLE_TABLE") and r_edges:
385
+ for i, re_item in enumerate(r_edges):
386
+ matched_r_indices.add(i)
387
+ item = {
388
+ "reconciliation_status": ReconciliationStatus.RUNTIME_ONLY_OBSERVED.value,
389
+ "reconciliation_state": ReconciliationStatus.RUNTIME_ONLY_OBSERVED.value,
390
+ "runtime_evidence_class": "RUNTIME_OBSERVED",
391
+ "source": src,
392
+ "table": re_item["table_short"],
393
+ "static": s_edge,
394
+ "runtime": re_item,
395
+ "explanation": (
396
+ f"Static analysis was {s_rel} ({s_edge['evidence_class']}); runtime observed "
397
+ f"{re_item['operation']} on '{re_item['table_short']}'. Runtime observation is NOT "
398
+ f"converted into static AST proof."
399
+ ),
400
+ }
401
+ runtime_only.append(item)
402
+ all_items.append(item)
403
+ continue
404
+
405
+ # 3. Static expected table A, but runtime observed this function accessing a DIFFERENT table B -> STATIC_RUNTIME_CONFLICT
406
+ unmatched_r = [
407
+ (i, re_item)
408
+ for i, re_item in enumerate(r_edges)
409
+ if re_item["table_short"] != s_tbl
410
+ ]
411
+ if unmatched_r and not any(re_item["table_short"] == s_tbl for re_item in r_edges):
412
+ idx0, re_conflict = unmatched_r[0]
413
+ matched_r_indices.add(idx0)
414
+ item = {
415
+ "reconciliation_status": ReconciliationStatus.STATIC_RUNTIME_CONFLICT.value,
416
+ "reconciliation_state": ReconciliationStatus.STATIC_RUNTIME_CONFLICT.value,
417
+ "runtime_evidence_class": "RUNTIME_OBSERVED",
418
+ "source": src,
419
+ "expected_table": s_tbl,
420
+ "observed_table": re_conflict["table_short"],
421
+ "static": s_edge,
422
+ "runtime": re_conflict,
423
+ "explanation": (
424
+ f"Static analysis expected {src} -> {s_rel} '{s_tbl}', but runtime observed "
425
+ f"{re_conflict['relationship']} '{re_conflict['table_short']}'."
426
+ ),
427
+ }
428
+ conflicts.append(item)
429
+ all_items.append(item)
430
+ continue
431
+
432
+ # 4. Otherwise, static edge was not observed in the supplied runtime telemetry
433
+ item = {
434
+ "reconciliation_status": ReconciliationStatus.NOT_OBSERVED_AT_RUNTIME.value,
435
+ "reconciliation_state": ReconciliationStatus.NOT_OBSERVED_AT_RUNTIME.value,
436
+ "runtime_evidence_class": "RUNTIME_UNOBSERVED",
437
+ "source": src,
438
+ "table": s_tbl,
439
+ "static": s_edge,
440
+ "runtime": None,
441
+ "explanation": (
442
+ f"Static relationship ({src} -> {s_rel} {s_tbl}) has no matching event in supplied "
443
+ f"runtime telemetry. Note: runtime telemetry is observational, not exhaustive; "
444
+ f"'not observed' does NOT mean 'does not happen'."
445
+ ),
446
+ }
447
+ not_observed.append(item)
448
+ all_items.append(item)
449
+
450
+ # Any remaining runtime edges for this source that had no static expectation at all
451
+ for i, re_item in enumerate(r_edges):
452
+ if i not in matched_r_indices:
453
+ item = {
454
+ "reconciliation_status": ReconciliationStatus.RUNTIME_ONLY_OBSERVED.value,
455
+ "reconciliation_state": ReconciliationStatus.RUNTIME_ONLY_OBSERVED.value,
456
+ "runtime_evidence_class": "RUNTIME_OBSERVED",
457
+ "source": src,
458
+ "table": re_item["table_short"],
459
+ "static": None,
460
+ "runtime": re_item,
461
+ "explanation": (
462
+ f"Runtime observed {src} -> {re_item['relationship']} '{re_item['table_short']}' "
463
+ f"with no static database edge. Recorded as RUNTIME_OBSERVED without fabricating static proof."
464
+ ),
465
+ }
466
+ runtime_only.append(item)
467
+ all_items.append(item)
468
+
469
+ return redact_payload({
470
+ "status": "ok",
471
+ "summary": {
472
+ "total_reconciled": len(all_items),
473
+ "confirmed_count": len(confirmed),
474
+ "conflict_count": len(conflicts),
475
+ "not_observed_count": len(not_observed),
476
+ "runtime_only_count": len(runtime_only),
477
+ },
478
+ "epistemic_note": (
479
+ "Runtime observations reflect only the supplied telemetry traces. "
480
+ "NOT_OBSERVED_AT_RUNTIME never implies a static path cannot execute, and "
481
+ "RUNTIME_ONLY_OBSERVED is never promoted to static AST_VERIFIED proof."
482
+ ),
483
+ "confirmed": confirmed,
484
+ "confirmed_runtime_paths": confirmed,
485
+ "conflicts": conflicts,
486
+ "static_runtime_conflicts": conflicts,
487
+ "not_observed": not_observed,
488
+ "not_observed_at_runtime": not_observed,
489
+ "runtime_only": runtime_only,
490
+ "runtime_only_observed": runtime_only,
491
+ "results": all_items,
492
+ })
@@ -1,6 +1,7 @@
1
1
  from .hybrid import (
2
2
  SearchResult,
3
3
  search,
4
+ search_code,
4
5
  search_exact_canonical_id,
5
6
  search_exact_qualified,
6
7
  search_exact_symbol,
@@ -14,6 +15,7 @@ __all__ = [
14
15
  "SearchResult",
15
16
  "SemanticSearchUnavailable",
16
17
  "search",
18
+ "search_code",
17
19
  "search_exact_canonical_id",
18
20
  "search_exact_qualified",
19
21
  "search_exact_symbol",