codegraph-engine 3.0.0__2-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 (130) hide show
  1. codegraph/__init__.py +37 -0
  2. codegraph/agent.py +26 -0
  3. codegraph/agent_brain.py +2212 -0
  4. codegraph/agent_capabilities.py +4058 -0
  5. codegraph/agent_rules.py +531 -0
  6. codegraph/api_drift.py +455 -0
  7. codegraph/architecture.py +371 -0
  8. codegraph/async_queue/__init__.py +50 -0
  9. codegraph/async_queue/detectors/__init__.py +48 -0
  10. codegraph/async_queue/detectors/celery.py +160 -0
  11. codegraph/async_queue/detectors/kafka.py +168 -0
  12. codegraph/async_queue/detectors/rabbitmq.py +116 -0
  13. codegraph/async_queue/detectors/redis.py +130 -0
  14. codegraph/async_queue/detectors/sqs.py +135 -0
  15. codegraph/async_queue/models.py +161 -0
  16. codegraph/async_queue/otel_models.py +74 -0
  17. codegraph/async_queue/otel_parser.py +337 -0
  18. codegraph/async_queue/reconciler.py +427 -0
  19. codegraph/async_queue/scanner.py +291 -0
  20. codegraph/async_queue/traversal.py +447 -0
  21. codegraph/audit.py +106 -0
  22. codegraph/binding_resolver.py +616 -0
  23. codegraph/branch_comparison.py +197 -0
  24. codegraph/cache.py +95 -0
  25. codegraph/change_impact.py +547 -0
  26. codegraph/cli.py +2436 -0
  27. codegraph/cli_output.py +205 -0
  28. codegraph/config.py +45 -0
  29. codegraph/constraints.py +238 -0
  30. codegraph/context.py +2182 -0
  31. codegraph/context_freshness.py +351 -0
  32. codegraph/database/__init__.py +72 -0
  33. codegraph/database/extractor.py +3651 -0
  34. codegraph/database/interrogation.py +1494 -0
  35. codegraph/database/models.py +310 -0
  36. codegraph/database/schema_drift.py +862 -0
  37. codegraph/epistemic.py +200 -0
  38. codegraph/errors.py +282 -0
  39. codegraph/evidence/__init__.py +15 -0
  40. codegraph/evidence/citations.py +418 -0
  41. codegraph/evidence_contract.py +486 -0
  42. codegraph/frameworks.py +960 -0
  43. codegraph/frameworks_nestjs.py +502 -0
  44. codegraph/frameworks_nextjs.py +337 -0
  45. codegraph/frameworks_react.py +457 -0
  46. codegraph/freshness.py +335 -0
  47. codegraph/frontend_html.py +109 -0
  48. codegraph/git.py +362 -0
  49. codegraph/git_state.py +545 -0
  50. codegraph/graph/__init__.py +46 -0
  51. codegraph/graph/models.py +56 -0
  52. codegraph/graph/traversal.py +1993 -0
  53. codegraph/indexing/__init__.py +24 -0
  54. codegraph/indexing/classifier.py +564 -0
  55. codegraph/indexing/indexer.py +2578 -0
  56. codegraph/indexing/models.py +427 -0
  57. codegraph/indexing/parser.py +3404 -0
  58. codegraph/indexing/pool.py +136 -0
  59. codegraph/indexing/scanner.py +211 -0
  60. codegraph/indexing/telemetry.py +326 -0
  61. codegraph/indexing/templates.py +418 -0
  62. codegraph/indexing/test_framework.py +116 -0
  63. codegraph/installer.py +1702 -0
  64. codegraph/interrogation.py +2355 -0
  65. codegraph/llm/__init__.py +3 -0
  66. codegraph/llm/base.py +15 -0
  67. codegraph/llm/context.py +20 -0
  68. codegraph/mcp/__init__.py +3 -0
  69. codegraph/mcp/server.py +1476 -0
  70. codegraph/mcp_diagnostics.py +882 -0
  71. codegraph/memory/__init__.py +3 -0
  72. codegraph/memory/store.py +46 -0
  73. codegraph/models.py +310 -0
  74. codegraph/monorepo.py +925 -0
  75. codegraph/observability.py +262 -0
  76. codegraph/optimizer.py +1014 -0
  77. codegraph/planner.py +417 -0
  78. codegraph/process_lifecycle.py +1153 -0
  79. codegraph/py.typed +1 -0
  80. codegraph/query_expansion.py +199 -0
  81. codegraph/ranking.py +408 -0
  82. codegraph/refactor/__init__.py +36 -0
  83. codegraph/refactor/models.py +114 -0
  84. codegraph/refactor/renamer.py +844 -0
  85. codegraph/refactor/transactions.py +414 -0
  86. codegraph/resolver.py +3968 -0
  87. codegraph/resources/__init__.py +46 -0
  88. codegraph/resources/cache.py +117 -0
  89. codegraph/resources/coalescer.py +83 -0
  90. codegraph/resources/debouncer.py +98 -0
  91. codegraph/resources/governor.py +219 -0
  92. codegraph/resources/monitor.py +102 -0
  93. codegraph/resources/policy.py +123 -0
  94. codegraph/retrieval_policy.py +320 -0
  95. codegraph/roblox.py +2174 -0
  96. codegraph/route_composer.py +363 -0
  97. codegraph/route_topology.py +172 -0
  98. codegraph/runtime/__init__.py +51 -0
  99. codegraph/runtime/collector.py +155 -0
  100. codegraph/runtime/db_watcher.py +191 -0
  101. codegraph/runtime/ingestor.py +692 -0
  102. codegraph/runtime/models.py +130 -0
  103. codegraph/runtime/port_inspector.py +304 -0
  104. codegraph/runtime/reconciliation.py +492 -0
  105. codegraph/runtime/runner.py +353 -0
  106. codegraph/runtime/trace_linker.py +175 -0
  107. codegraph/search/__init__.py +25 -0
  108. codegraph/search/hybrid.py +846 -0
  109. codegraph/search/semantic.py +28 -0
  110. codegraph/security/__init__.py +51 -0
  111. codegraph/security/paths.py +189 -0
  112. codegraph/security/redaction.py +633 -0
  113. codegraph/semantic_conflicts.py +270 -0
  114. codegraph/semantic_decorators.py +372 -0
  115. codegraph/structural_diff.py +542 -0
  116. codegraph/styles.py +144 -0
  117. codegraph/symbol_history.py +277 -0
  118. codegraph/target_resolver.py +348 -0
  119. codegraph/task.py +637 -0
  120. codegraph/tool_selection_eval.py +1158 -0
  121. codegraph/tsconfig.py +236 -0
  122. codegraph/ui/__init__.py +6 -0
  123. codegraph/ui/server.py +755 -0
  124. codegraph/watcher.py +323 -0
  125. codegraph_engine-3.0.0.dist-info/METADATA +868 -0
  126. codegraph_engine-3.0.0.dist-info/RECORD +130 -0
  127. codegraph_engine-3.0.0.dist-info/WHEEL +6 -0
  128. codegraph_engine-3.0.0.dist-info/entry_points.txt +2 -0
  129. codegraph_engine-3.0.0.dist-info/licenses/LICENSE +21 -0
  130. codegraph_engine-3.0.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,486 @@
1
+ """Authoritative Evidence Contract for CodeGraph MCP (Phase 12).
2
+
3
+ Enforces a strict, explicit allowlist of relationship types, evidence classes,
4
+ and (relationship, evidence_class) pairs across every relationship-producing,
5
+ traversing, optimizing, and serializing path in CodeGraph.
6
+
7
+ Invariants:
8
+ - Unknown relationship types fail closed.
9
+ - Unknown evidence classes fail closed.
10
+ - Disallowed (relationship, evidence_class) combinations fail closed.
11
+ - Contradictory epistemic records (e.g. CALLS + POSSIBLE, CALLS + UNKNOWN,
12
+ CALLS + FRAMEWORK_VERIFIED, status=FACT with POSSIBLE/UNKNOWN) fail closed.
13
+ - Raw dictionaries must be normalized and validated into `RelationshipRecord`.
14
+ """
15
+ from __future__ import annotations
16
+
17
+ from dataclasses import dataclass, field
18
+ from typing import Any
19
+
20
+ DEFAULT_PARSER_VERSION = "3.0"
21
+ PARSER_VERSION = DEFAULT_PARSER_VERSION
22
+
23
+ ALLOWED_EVIDENCE_CLASSES: frozenset[str] = frozenset({
24
+ "AST_VERIFIED",
25
+ "STATIC_VERIFIED",
26
+ "FRAMEWORK_VERIFIED",
27
+ "ROJO_VERIFIED",
28
+ "DATAFLOW_VERIFIED",
29
+ "RUNTIME_OBSERVED",
30
+ "RUNTIME_UNOBSERVED",
31
+ "POSSIBLE",
32
+ "UNKNOWN",
33
+ "AMBIGUOUS",
34
+ })
35
+
36
+ ALLOWED_CONFIDENCE_LEVELS: frozenset[str] = frozenset({
37
+ "HIGH",
38
+ "MEDIUM",
39
+ "LOW",
40
+ "UNKNOWN",
41
+ })
42
+
43
+ ALLOWED_EPISTEMIC_STATUSES: frozenset[str] = frozenset({
44
+ "FACT",
45
+ "ASSUMPTION",
46
+ "INFERENCE",
47
+ "UNKNOWN",
48
+ "CONFLICT",
49
+ "POSSIBLE",
50
+ "AMBIGUOUS",
51
+ "RUNTIME_OBSERVED",
52
+ })
53
+
54
+ # Explicit ALLOWLIST mapping every canonical relationship type to its valid evidence classes.
55
+ ALLOWED_RELATIONSHIP_EVIDENCE_MATRIX: dict[str, frozenset[str]] = {
56
+ # Direct executable call edges — NEVER FRAMEWORK_VERIFIED, POSSIBLE, or UNKNOWN
57
+ "CALLS": frozenset({"AST_VERIFIED", "DATAFLOW_VERIFIED", "RUNTIME_OBSERVED"}),
58
+ "CALLED_BY": frozenset({"AST_VERIFIED", "DATAFLOW_VERIFIED", "RUNTIME_OBSERVED"}),
59
+ # Uncertain / candidate call edges — NEVER verified
60
+ "POSSIBLE_CALLS": frozenset({"POSSIBLE", "UNKNOWN", "AMBIGUOUS"}),
61
+ # Framework routing & composition
62
+ "MOUNTS": frozenset({"FRAMEWORK_VERIFIED", "POSSIBLE", "UNKNOWN"}),
63
+ "ROUTE_HANDLER": frozenset({"FRAMEWORK_VERIFIED", "RUNTIME_OBSERVED", "POSSIBLE", "UNKNOWN"}),
64
+ "HANDLED_BY": frozenset({"FRAMEWORK_VERIFIED", "RUNTIME_OBSERVED", "POSSIBLE", "UNKNOWN"}),
65
+ "ROUTES_TO": frozenset({"FRAMEWORK_VERIFIED", "RUNTIME_OBSERVED", "POSSIBLE", "UNKNOWN"}),
66
+ # Dependency Injection & Configuration
67
+ "INJECTS": frozenset({"FRAMEWORK_VERIFIED", "DATAFLOW_VERIFIED", "POSSIBLE", "UNKNOWN"}),
68
+ "PROVIDES": frozenset({"FRAMEWORK_VERIFIED", "DATAFLOW_VERIFIED", "POSSIBLE", "UNKNOWN"}),
69
+ "RESOLVES_DEPENDENCY": frozenset({"DATAFLOW_VERIFIED", "FRAMEWORK_VERIFIED", "POSSIBLE", "UNKNOWN"}),
70
+ "CONFIGURES": frozenset({"DATAFLOW_VERIFIED", "FRAMEWORK_VERIFIED", "AST_VERIFIED", "POSSIBLE", "UNKNOWN"}),
71
+ "DI_CYCLE": frozenset({"DATAFLOW_VERIFIED", "FRAMEWORK_VERIFIED", "POSSIBLE", "UNKNOWN"}),
72
+ # Registry, Dispatch, Events, Tasks, Commands
73
+ "REGISTERS": frozenset({"AST_VERIFIED", "FRAMEWORK_VERIFIED", "DATAFLOW_VERIFIED", "POSSIBLE", "UNKNOWN"}),
74
+ "REGISTERED_HANDLER": frozenset({"AST_VERIFIED", "FRAMEWORK_VERIFIED", "DATAFLOW_VERIFIED", "POSSIBLE", "UNKNOWN"}),
75
+ "DISPATCHES_TO": frozenset({"FRAMEWORK_VERIFIED", "DATAFLOW_VERIFIED", "RUNTIME_OBSERVED", "POSSIBLE", "UNKNOWN"}),
76
+ "EVENT_LISTENER": frozenset({"FRAMEWORK_VERIFIED", "DATAFLOW_VERIFIED", "POSSIBLE", "UNKNOWN"}),
77
+ "TASK_HANDLER": frozenset({"FRAMEWORK_VERIFIED", "DATAFLOW_VERIFIED", "POSSIBLE", "UNKNOWN"}),
78
+ "COMMAND_HANDLER": frozenset({"FRAMEWORK_VERIFIED", "DATAFLOW_VERIFIED", "POSSIBLE", "UNKNOWN"}),
79
+ # Monorepo / Package boundaries
80
+ "DEPENDS_ON_PACKAGE": frozenset({"AST_VERIFIED", "FRAMEWORK_VERIFIED", "POSSIBLE", "UNKNOWN"}),
81
+ "PACKAGE_IMPORTS": frozenset({"AST_VERIFIED", "FRAMEWORK_VERIFIED", "POSSIBLE", "UNKNOWN"}),
82
+ "CROSS_PACKAGE_IMPORT": frozenset({"AST_VERIFIED", "FRAMEWORK_VERIFIED", "POSSIBLE", "UNKNOWN"}),
83
+ "CONTAINS_PACKAGE": frozenset({"AST_VERIFIED", "FRAMEWORK_VERIFIED"}),
84
+ # Test intelligence
85
+ "TESTS": frozenset({"AST_VERIFIED", "FRAMEWORK_VERIFIED", "DATAFLOW_VERIFIED", "POSSIBLE", "UNKNOWN"}),
86
+ "TESTS_SYMBOL": frozenset({"AST_VERIFIED", "FRAMEWORK_VERIFIED", "DATAFLOW_VERIFIED", "POSSIBLE", "UNKNOWN"}),
87
+ "TESTS_ROUTE": frozenset({"AST_VERIFIED", "FRAMEWORK_VERIFIED", "DATAFLOW_VERIFIED", "POSSIBLE", "UNKNOWN"}),
88
+ "TESTS_PROVIDER": frozenset({"AST_VERIFIED", "FRAMEWORK_VERIFIED", "DATAFLOW_VERIFIED", "POSSIBLE", "UNKNOWN"}),
89
+ "TESTS_EVENT_HANDLER": frozenset({"AST_VERIFIED", "FRAMEWORK_VERIFIED", "DATAFLOW_VERIFIED", "POSSIBLE", "UNKNOWN"}),
90
+ # Structural & AST relationships
91
+ "IMPORTS": frozenset({"AST_VERIFIED", "ROJO_VERIFIED", "DATAFLOW_VERIFIED", "POSSIBLE", "UNKNOWN"}),
92
+ "EXPORTS": frozenset({"AST_VERIFIED", "DATAFLOW_VERIFIED"}),
93
+ "REEXPORTS": frozenset({"AST_VERIFIED", "DATAFLOW_VERIFIED"}),
94
+ "EXTENDS": frozenset({"AST_VERIFIED", "DATAFLOW_VERIFIED", "POSSIBLE", "UNKNOWN"}),
95
+ "IMPLEMENTS": frozenset({"AST_VERIFIED", "DATAFLOW_VERIFIED", "POSSIBLE", "UNKNOWN"}),
96
+ "DEFINES": frozenset({"AST_VERIFIED", "STATIC_VERIFIED"}),
97
+ "CONTAINS": frozenset({"AST_VERIFIED", "STATIC_VERIFIED"}),
98
+ "RESOLVES_TO": frozenset({"DATAFLOW_VERIFIED", "ROJO_VERIFIED", "AST_VERIFIED", "POSSIBLE", "UNKNOWN"}),
99
+ "BINDS_TO": frozenset({"DATAFLOW_VERIFIED", "AST_VERIFIED", "POSSIBLE", "UNKNOWN"}),
100
+ "ALIASED_TO": frozenset({"DATAFLOW_VERIFIED", "AST_VERIFIED", "POSSIBLE", "UNKNOWN"}),
101
+ "REFERENCES": frozenset({"AST_VERIFIED", "DATAFLOW_VERIFIED", "POSSIBLE", "UNKNOWN"}),
102
+ "USES": frozenset({"AST_VERIFIED", "DATAFLOW_VERIFIED", "POSSIBLE", "UNKNOWN"}),
103
+ "DEPENDS_ON": frozenset({"AST_VERIFIED", "ROJO_VERIFIED", "DATAFLOW_VERIFIED", "FRAMEWORK_VERIFIED", "POSSIBLE", "UNKNOWN"}),
104
+ "UNRESOLVED_REFERENCE": frozenset({"UNKNOWN", "POSSIBLE", "AMBIGUOUS"}),
105
+ # Database & Persistence Intelligence (Phase 6)
106
+ "MAPS_TO_TABLE": frozenset({"AST_VERIFIED", "STATIC_VERIFIED", "FRAMEWORK_VERIFIED", "DATAFLOW_VERIFIED", "POSSIBLE", "UNKNOWN", "AMBIGUOUS"}),
107
+ "MAPS_TO_COLUMN": frozenset({"AST_VERIFIED", "STATIC_VERIFIED", "FRAMEWORK_VERIFIED", "DATAFLOW_VERIFIED", "POSSIBLE", "UNKNOWN", "AMBIGUOUS"}),
108
+ "READS_TABLE": frozenset({"AST_VERIFIED", "STATIC_VERIFIED", "FRAMEWORK_VERIFIED", "DATAFLOW_VERIFIED", "RUNTIME_OBSERVED", "RUNTIME_UNOBSERVED", "POSSIBLE", "UNKNOWN", "AMBIGUOUS"}),
109
+ "WRITES_TABLE": frozenset({"AST_VERIFIED", "STATIC_VERIFIED", "FRAMEWORK_VERIFIED", "DATAFLOW_VERIFIED", "RUNTIME_OBSERVED", "RUNTIME_UNOBSERVED", "POSSIBLE", "UNKNOWN", "AMBIGUOUS"}),
110
+ "READS_COLUMN": frozenset({"AST_VERIFIED", "STATIC_VERIFIED", "FRAMEWORK_VERIFIED", "DATAFLOW_VERIFIED", "RUNTIME_OBSERVED", "RUNTIME_UNOBSERVED", "POSSIBLE", "UNKNOWN", "AMBIGUOUS"}),
111
+ "WRITES_COLUMN": frozenset({"AST_VERIFIED", "STATIC_VERIFIED", "FRAMEWORK_VERIFIED", "DATAFLOW_VERIFIED", "RUNTIME_OBSERVED", "RUNTIME_UNOBSERVED", "POSSIBLE", "UNKNOWN", "AMBIGUOUS"}),
112
+ "REFERENCES_TABLE": frozenset({"AST_VERIFIED", "STATIC_VERIFIED", "FRAMEWORK_VERIFIED", "DATAFLOW_VERIFIED", "POSSIBLE", "UNKNOWN", "AMBIGUOUS"}),
113
+ "REFERENCES_COLUMN": frozenset({"AST_VERIFIED", "STATIC_VERIFIED", "FRAMEWORK_VERIFIED", "DATAFLOW_VERIFIED", "POSSIBLE", "UNKNOWN", "AMBIGUOUS"}),
114
+ "FOREIGN_KEY_TO": frozenset({"AST_VERIFIED", "STATIC_VERIFIED", "FRAMEWORK_VERIFIED", "DATAFLOW_VERIFIED", "POSSIBLE", "UNKNOWN", "AMBIGUOUS"}),
115
+ "HAS_PRIMARY_KEY": frozenset({"AST_VERIFIED", "STATIC_VERIFIED", "FRAMEWORK_VERIFIED"}),
116
+ "HAS_INDEX": frozenset({"AST_VERIFIED", "STATIC_VERIFIED", "FRAMEWORK_VERIFIED"}),
117
+ "HAS_UNIQUE_CONSTRAINT": frozenset({"AST_VERIFIED", "STATIC_VERIFIED", "FRAMEWORK_VERIFIED"}),
118
+ "HAS_CHECK_CONSTRAINT": frozenset({"AST_VERIFIED", "STATIC_VERIFIED", "FRAMEWORK_VERIFIED"}),
119
+ "MIGRATES_TABLE": frozenset({"AST_VERIFIED", "STATIC_VERIFIED", "FRAMEWORK_VERIFIED", "POSSIBLE", "UNKNOWN"}),
120
+ "QUERIES_DATABASE": frozenset({"AST_VERIFIED", "STATIC_VERIFIED", "FRAMEWORK_VERIFIED", "DATAFLOW_VERIFIED", "RUNTIME_OBSERVED", "POSSIBLE", "UNKNOWN", "AMBIGUOUS"}),
121
+ "ORM_RELATION": frozenset({"AST_VERIFIED", "STATIC_VERIFIED", "FRAMEWORK_VERIFIED", "DATAFLOW_VERIFIED", "POSSIBLE", "UNKNOWN", "AMBIGUOUS"}),
122
+ "POSSIBLE_TABLE": frozenset({"POSSIBLE", "AMBIGUOUS", "UNKNOWN"}),
123
+ "UNKNOWN_TABLE": frozenset({"UNKNOWN", "AMBIGUOUS"}),
124
+ # Environment variable dependency tracking (Phase 8 Security)
125
+ "READS_ENV": frozenset({"AST_VERIFIED", "STATIC_VERIFIED", "DATAFLOW_VERIFIED"}),
126
+ # Frontend, UI & Web Architecture (Pillar 4)
127
+ "RENDERS": frozenset({"AST_VERIFIED", "FRAMEWORK_VERIFIED", "DATAFLOW_VERIFIED", "POSSIBLE", "UNKNOWN"}),
128
+ "USES_HOOK": frozenset({"AST_VERIFIED", "FRAMEWORK_VERIFIED", "DATAFLOW_VERIFIED", "POSSIBLE", "UNKNOWN"}),
129
+ "IMPORTS_STYLE": frozenset({"AST_VERIFIED", "STATIC_VERIFIED", "FRAMEWORK_VERIFIED", "POSSIBLE", "UNKNOWN"}),
130
+ "USES_STYLE_CLASS": frozenset({"AST_VERIFIED", "STATIC_VERIFIED", "FRAMEWORK_VERIFIED", "POSSIBLE", "UNKNOWN"}),
131
+ "FETCHES_ROUTE": frozenset({"AST_VERIFIED", "FRAMEWORK_VERIFIED", "DATAFLOW_VERIFIED", "POSSIBLE", "UNKNOWN"}),
132
+ "LOADS_SCRIPT": frozenset({"AST_VERIFIED", "STATIC_VERIFIED", "FRAMEWORK_VERIFIED", "POSSIBLE", "UNKNOWN"}),
133
+ "LOADS_STYLESHEET": frozenset({"AST_VERIFIED", "STATIC_VERIFIED", "FRAMEWORK_VERIFIED", "POSSIBLE", "UNKNOWN"}),
134
+ # NoSQL Document Database & Collections (Mongoose / MongoDB)
135
+ "MAPS_TO_COLLECTION": frozenset({"AST_VERIFIED", "STATIC_VERIFIED", "FRAMEWORK_VERIFIED", "DATAFLOW_VERIFIED", "POSSIBLE", "UNKNOWN", "AMBIGUOUS"}),
136
+ "WRITES_COLLECTION": frozenset({"AST_VERIFIED", "STATIC_VERIFIED", "FRAMEWORK_VERIFIED", "DATAFLOW_VERIFIED", "RUNTIME_OBSERVED", "RUNTIME_UNOBSERVED", "POSSIBLE", "UNKNOWN", "AMBIGUOUS"}),
137
+ "READS_COLLECTION": frozenset({"AST_VERIFIED", "STATIC_VERIFIED", "FRAMEWORK_VERIFIED", "DATAFLOW_VERIFIED", "RUNTIME_OBSERVED", "RUNTIME_UNOBSERVED", "POSSIBLE", "UNKNOWN", "AMBIGUOUS"}),
138
+ # Python Full-Stack (Celery, Django Signals, Templates)
139
+ "DISPATCHES_TASK": frozenset({"FRAMEWORK_VERIFIED", "DATAFLOW_VERIFIED", "AST_VERIFIED", "POSSIBLE", "UNKNOWN"}),
140
+ "TRIGGERS_SIGNAL": frozenset({"FRAMEWORK_VERIFIED", "DATAFLOW_VERIFIED", "AST_VERIFIED", "POSSIBLE", "UNKNOWN"}),
141
+ "HANDLES_SIGNAL": frozenset({"FRAMEWORK_VERIFIED", "DATAFLOW_VERIFIED", "AST_VERIFIED", "POSSIBLE", "UNKNOWN"}),
142
+ "RENDERS_TEMPLATE": frozenset({"FRAMEWORK_VERIFIED", "DATAFLOW_VERIFIED", "AST_VERIFIED", "POSSIBLE", "UNKNOWN"}),
143
+ # Python AI/ML Stack (PyTorch, LangChain, Ray, LlamaIndex)
144
+ "DISPATCHES_FORWARD": frozenset({"AST_VERIFIED", "DATAFLOW_VERIFIED", "FRAMEWORK_VERIFIED", "POSSIBLE", "UNKNOWN", "AMBIGUOUS"}),
145
+ "TOOL_HANDLER": frozenset({"FRAMEWORK_VERIFIED", "AST_VERIFIED", "POSSIBLE", "UNKNOWN"}),
146
+ "PIPELINE_STEP": frozenset({"FRAMEWORK_VERIFIED", "DATAFLOW_VERIFIED", "AST_VERIFIED", "POSSIBLE", "UNKNOWN"}),
147
+ # Roblox / Luau Intelligence (Phases R1-R8)
148
+ "REQUIRES_MODULE": frozenset({"AST_VERIFIED", "ROJO_VERIFIED", "DATAFLOW_VERIFIED", "POSSIBLE", "UNKNOWN"}),
149
+ "CLIENT_DISPATCHES_REMOTE": frozenset({"FRAMEWORK_VERIFIED", "ROJO_VERIFIED", "AST_VERIFIED", "DATAFLOW_VERIFIED", "POSSIBLE", "UNKNOWN"}),
150
+ "SERVER_HANDLES_REMOTE": frozenset({"FRAMEWORK_VERIFIED", "ROJO_VERIFIED", "AST_VERIFIED", "DATAFLOW_VERIFIED", "POSSIBLE", "UNKNOWN"}),
151
+ "GETS_SERVICE": frozenset({"FRAMEWORK_VERIFIED", "ROJO_VERIFIED", "DATAFLOW_VERIFIED", "POSSIBLE", "UNKNOWN"}),
152
+ "PROVIDES_SERVICE": frozenset({"FRAMEWORK_VERIFIED", "ROJO_VERIFIED", "AST_VERIFIED", "DATAFLOW_VERIFIED", "POSSIBLE", "UNKNOWN"}),
153
+ "READS_PERSISTENCE": frozenset({"AST_VERIFIED", "FRAMEWORK_VERIFIED", "DATAFLOW_VERIFIED", "POSSIBLE", "UNKNOWN"}),
154
+ "WRITES_PERSISTENCE": frozenset({"AST_VERIFIED", "FRAMEWORK_VERIFIED", "DATAFLOW_VERIFIED", "POSSIBLE", "UNKNOWN"}),
155
+ "CONFIGURES_PERSISTENCE": frozenset({"AST_VERIFIED", "FRAMEWORK_VERIFIED", "DATAFLOW_VERIFIED", "POSSIBLE", "UNKNOWN"}),
156
+ }
157
+
158
+
159
+ def validate_relationship_type(relationship: str) -> str:
160
+ """Validate that `relationship` is in the explicit allowlist. Fails closed."""
161
+ if not isinstance(relationship, str) or not relationship.strip():
162
+ raise ValueError("Invalid relationship type: must be a non-empty string.")
163
+ norm = relationship.strip()
164
+ if norm not in ALLOWED_RELATIONSHIP_EVIDENCE_MATRIX:
165
+ raise ValueError(
166
+ f"Unknown or disallowed relationship type '{relationship}'. "
167
+ f"Must be one of the canonical allowlist types."
168
+ )
169
+ return norm
170
+
171
+
172
+ def validate_evidence_class(evidence_class: str) -> str:
173
+ """Validate that `evidence_class` is in the explicit allowlist. Fails closed."""
174
+ if not isinstance(evidence_class, str) or not evidence_class.strip():
175
+ raise ValueError("Invalid evidence_class: must be a non-empty string.")
176
+ norm = evidence_class.strip()
177
+ if norm not in ALLOWED_EVIDENCE_CLASSES:
178
+ raise ValueError(
179
+ f"Unknown or disallowed evidence_class '{evidence_class}'. "
180
+ f"Allowed: {sorted(ALLOWED_EVIDENCE_CLASSES)}"
181
+ )
182
+ return norm
183
+
184
+
185
+ def validate_relationship_evidence_pair(
186
+ relationship: str,
187
+ evidence_class: str,
188
+ ) -> tuple[str, str]:
189
+ """Validate that `(relationship, evidence_class)` is explicitly allowed. Fails closed."""
190
+ if not isinstance(relationship, str) or not relationship.strip():
191
+ raise ValueError("Invalid relationship type: must be a non-empty string.")
192
+ raw_rel = relationship.strip()
193
+ ev = validate_evidence_class(evidence_class)
194
+ if ev == "UNKNOWN" and raw_rel in ("CALLS", "CALLED_BY"):
195
+ raise ValueError(
196
+ "Epistemic invariant violation: UNKNOWN relationships cannot be exposed as authoritative 'CALLS'."
197
+ )
198
+ if ev == "POSSIBLE" and raw_rel in ("CALLS", "CALLED_BY"):
199
+ raise ValueError(
200
+ "Epistemic invariant violation: POSSIBLE relationships cannot be labeled as 'CALLS'; use 'POSSIBLE_CALLS'."
201
+ )
202
+ if ev == "AST_VERIFIED" and raw_rel.startswith("POSSIBLE"):
203
+ raise ValueError(
204
+ f"Epistemic invariant violation: POSSIBLE relationship '{raw_rel}' cannot be marked as AST_VERIFIED."
205
+ )
206
+ rel = validate_relationship_type(raw_rel)
207
+ allowed_for_rel = ALLOWED_RELATIONSHIP_EVIDENCE_MATRIX[rel]
208
+ if ev not in allowed_for_rel:
209
+ raise ValueError(
210
+ f"Disallowed (relationship, evidence_class) pair: ('{rel}', '{ev}'). "
211
+ f"Allowed evidence classes for '{rel}': {sorted(allowed_for_rel)}"
212
+ )
213
+ return rel, ev
214
+
215
+
216
+ @dataclass(frozen=True)
217
+ class RelationshipRecord:
218
+ """Canonical validated relationship record preventing raw-dict contract bypass."""
219
+
220
+ source: str
221
+ target: str
222
+ relationship: str
223
+ evidence_class: str = "AST_VERIFIED"
224
+ confidence: str = "HIGH"
225
+ file: str = ""
226
+ start_line: int = 1
227
+ end_line: int = 1
228
+ target_file: str | None = None
229
+ target_line: int | None = None
230
+ evidence: str = ""
231
+ reason: str | None = None
232
+ status: str = "FACT"
233
+ parser_version: str = PARSER_VERSION
234
+ index_generation: int = 0
235
+ freshness: str = "FRESH"
236
+ supporting_locations: tuple[str, ...] = field(default_factory=tuple)
237
+ occurrence_count: int = 1
238
+ metadata: dict[str, Any] = field(default_factory=dict)
239
+
240
+ def __post_init__(self) -> None:
241
+ if not isinstance(self.source, str) or not self.source.strip():
242
+ raise ValueError("RelationshipRecord.source must be a non-empty string.")
243
+ if not isinstance(self.target, str) or not self.target.strip():
244
+ raise ValueError("RelationshipRecord.target must be a non-empty string.")
245
+
246
+ rel, ev = validate_relationship_evidence_pair(self.relationship, self.evidence_class)
247
+ object.__setattr__(self, "relationship", rel)
248
+ object.__setattr__(self, "evidence_class", ev)
249
+
250
+ conf = str(self.confidence or "HIGH").strip().upper()
251
+ if conf not in ALLOWED_CONFIDENCE_LEVELS:
252
+ raise ValueError(f"Invalid confidence level '{self.confidence}'.")
253
+ object.__setattr__(self, "confidence", conf)
254
+
255
+ st = str(self.status or "FACT").strip().upper()
256
+ if st not in ALLOWED_EPISTEMIC_STATUSES:
257
+ raise ValueError(f"Invalid epistemic status '{self.status}'.")
258
+ object.__setattr__(self, "status", st)
259
+
260
+ # Reject contradictory epistemic combinations
261
+ if ev == "UNKNOWN":
262
+ if conf == "HIGH":
263
+ raise ValueError(
264
+ "Contradictory RelationshipRecord: evidence_class='UNKNOWN' contradicts confidence='HIGH'."
265
+ )
266
+ if st == "FACT":
267
+ raise ValueError(
268
+ "Contradictory RelationshipRecord: evidence_class='UNKNOWN' contradicts status='FACT'."
269
+ )
270
+ if ev == "POSSIBLE":
271
+ if st == "FACT":
272
+ raise ValueError(
273
+ "Contradictory RelationshipRecord: evidence_class='POSSIBLE' contradicts status='FACT'."
274
+ )
275
+ if conf == "HIGH":
276
+ raise ValueError(
277
+ "Contradictory RelationshipRecord: evidence_class='POSSIBLE' contradicts confidence='HIGH'."
278
+ )
279
+ if rel == "POSSIBLE_CALLS" and conf == "HIGH":
280
+ raise ValueError(
281
+ "Contradictory RelationshipRecord: relationship='POSSIBLE_CALLS' contradicts confidence='HIGH'."
282
+ )
283
+ if rel in ("CALLS", "CALLED_BY") and ev in ("AST_VERIFIED", "DATAFLOW_VERIFIED") and conf in ("LOW", "UNKNOWN"):
284
+ raise ValueError(
285
+ f"Contradictory RelationshipRecord: verified '{rel}' with evidence_class='{ev}' contradicts confidence='{conf}'."
286
+ )
287
+
288
+ def as_dict(self) -> dict[str, Any]:
289
+ out: dict[str, Any] = {
290
+ "source": self.source,
291
+ "target": self.target,
292
+ "relationship": self.relationship,
293
+ "evidence_class": self.evidence_class,
294
+ "confidence": self.confidence,
295
+ "file": self.file,
296
+ "start_line": self.start_line,
297
+ "end_line": self.end_line,
298
+ "evidence": self.evidence,
299
+ "reason": self.reason,
300
+ "status": self.status,
301
+ "parser_version": self.parser_version,
302
+ "index_generation": self.index_generation,
303
+ "freshness": self.freshness,
304
+ "supporting_locations": list(self.supporting_locations),
305
+ "occurrence_count": self.occurrence_count,
306
+ }
307
+ if self.target_file is not None:
308
+ out["target_file"] = self.target_file
309
+ if self.target_line is not None:
310
+ out["target_line"] = self.target_line
311
+ if self.metadata:
312
+ out["metadata"] = dict(self.metadata)
313
+ return out
314
+
315
+
316
+ def validate_graph_edge_contract(
317
+ source: str,
318
+ target: str,
319
+ relationship: str,
320
+ evidence_class: str,
321
+ confidence: str,
322
+ ) -> tuple[str, str, str]:
323
+ """Fast fail-closed validator for GraphEdge fields enforcing identical epistemic invariants."""
324
+ if not isinstance(source, str) or not source.strip():
325
+ raise ValueError("RelationshipRecord.source must be a non-empty string.")
326
+ if not isinstance(target, str) or not target.strip():
327
+ raise ValueError("RelationshipRecord.target must be a non-empty string.")
328
+
329
+ raw_ev_cls = evidence_class
330
+ raw_conf = confidence
331
+ if raw_ev_cls is None or str(raw_ev_cls).strip() == "":
332
+ from codegraph.epistemic import classify_relationship_evidence
333
+
334
+ inferred_ev, _ = classify_relationship_evidence(
335
+ str(relationship or "").strip(), str(raw_conf or "HIGH").strip().upper()
336
+ )
337
+ ev_cls = inferred_ev.value
338
+ else:
339
+ ev_cls = str(raw_ev_cls).strip()
340
+
341
+ rel, ev = validate_relationship_evidence_pair(relationship, ev_cls)
342
+
343
+ if raw_conf is None or str(raw_conf).strip() == "":
344
+ conf = "UNKNOWN" if ev == "UNKNOWN" else ("LOW" if ev == "POSSIBLE" else "HIGH")
345
+ else:
346
+ conf = str(raw_conf).strip().upper()
347
+
348
+ if conf not in ALLOWED_CONFIDENCE_LEVELS:
349
+ raise ValueError(f"Invalid confidence level '{confidence}'.")
350
+
351
+ if ev == "UNKNOWN" and conf == "HIGH":
352
+ raise ValueError(
353
+ "Contradictory RelationshipRecord: evidence_class='UNKNOWN' contradicts confidence='HIGH'."
354
+ )
355
+ if ev == "POSSIBLE" and conf == "HIGH":
356
+ raise ValueError(
357
+ "Contradictory RelationshipRecord: evidence_class='POSSIBLE' contradicts confidence='HIGH'."
358
+ )
359
+ if rel == "POSSIBLE_CALLS" and conf == "HIGH":
360
+ raise ValueError(
361
+ "Contradictory RelationshipRecord: relationship='POSSIBLE_CALLS' contradicts confidence='HIGH'."
362
+ )
363
+ if rel in ("CALLS", "CALLED_BY") and ev in ("AST_VERIFIED", "DATAFLOW_VERIFIED") and conf in ("LOW", "UNKNOWN"):
364
+ raise ValueError(
365
+ f"Contradictory RelationshipRecord: verified '{rel}' with evidence_class='{ev}' contradicts confidence='{conf}'."
366
+ )
367
+ return rel, ev, conf
368
+
369
+
370
+ def validate_relationship_record(
371
+ record: RelationshipRecord | dict[str, Any] | Any,
372
+ ) -> RelationshipRecord:
373
+ """Validate any relationship object or dictionary and return a canonical `RelationshipRecord`.
374
+
375
+ Fails closed (`ValueError`) if required fields are missing or if the
376
+ `(relationship, evidence_class, confidence, status)` combination is invalid or contradictory.
377
+ """
378
+ if isinstance(record, RelationshipRecord):
379
+ validate_relationship_evidence_pair(record.relationship, record.evidence_class)
380
+ return record
381
+
382
+ if isinstance(record, dict):
383
+ src = str(record.get("source") or record.get("source_symbol_id") or record.get("symbol") or "").strip()
384
+ tgt = str(record.get("target") or record.get("target_symbol_id") or record.get("callee") or "").strip()
385
+ rel = str(record.get("relationship") or "").strip()
386
+ if not src or not tgt or not rel:
387
+ raise ValueError(
388
+ f"Invalid relationship dictionary: 'source', 'target', and 'relationship' are required (got {record!r})."
389
+ )
390
+
391
+ raw_ev_cls = record.get("evidence_class")
392
+ raw_conf = record.get("confidence")
393
+ if raw_ev_cls is None or str(raw_ev_cls).strip() == "":
394
+ # Derive default evidence class only when not explicitly supplied, then validate strictly
395
+ from codegraph.epistemic import classify_relationship_evidence
396
+
397
+ inferred_ev, _ = classify_relationship_evidence(rel, str(raw_conf or "HIGH").strip().upper())
398
+ ev_cls = inferred_ev.value
399
+ else:
400
+ ev_cls = str(raw_ev_cls).strip()
401
+
402
+ if raw_conf is None or str(raw_conf).strip() == "":
403
+ conf = "UNKNOWN" if ev_cls == "UNKNOWN" else ("LOW" if ev_cls == "POSSIBLE" else "HIGH")
404
+ else:
405
+ conf = str(raw_conf).strip().upper()
406
+
407
+ raw_status = record.get("status")
408
+ if raw_status is None or str(raw_status).strip() == "":
409
+ if ev_cls == "UNKNOWN":
410
+ status = "UNKNOWN"
411
+ elif ev_cls == "POSSIBLE":
412
+ status = "INFERENCE"
413
+ else:
414
+ status = "FACT"
415
+ else:
416
+ status = str(raw_status).strip().upper()
417
+
418
+ f_path = str(record.get("file") or record.get("path") or "")
419
+ s_line = int(record.get("start_line") or record.get("line") or 1)
420
+ e_line = int(record.get("end_line") or s_line)
421
+ supp = record.get("supporting_locations") or ()
422
+ occ = int(record.get("occurrence_count") or 1)
423
+
424
+ return RelationshipRecord(
425
+ source=src,
426
+ target=tgt,
427
+ relationship=rel,
428
+ evidence_class=ev_cls,
429
+ confidence=conf,
430
+ file=f_path,
431
+ start_line=s_line,
432
+ end_line=e_line,
433
+ target_file=record.get("target_file"),
434
+ target_line=record.get("target_line"),
435
+ evidence=str(record.get("evidence") or ""),
436
+ reason=record.get("reason"),
437
+ status=status,
438
+ parser_version=str(record.get("parser_version") or PARSER_VERSION),
439
+ index_generation=int(record.get("index_generation") or 0),
440
+ freshness=str(record.get("freshness") or "FRESH"),
441
+ supporting_locations=tuple(str(x) for x in supp),
442
+ occurrence_count=occ,
443
+ )
444
+
445
+ # Object with attributes (e.g., GraphEdge, Relationship Pydantic model)
446
+ src = str(getattr(record, "source", "") or "").strip()
447
+ tgt = str(getattr(record, "target", "") or "").strip()
448
+ rel = str(getattr(record, "relationship", "") or "").strip()
449
+ raw_ev_cls = getattr(record, "evidence_class", None)
450
+ raw_conf = getattr(record, "confidence", None)
451
+ if raw_ev_cls is None or str(raw_ev_cls).strip() == "":
452
+ from codegraph.epistemic import classify_relationship_evidence
453
+
454
+ inferred_ev, _ = classify_relationship_evidence(rel, str(raw_conf or "HIGH").strip().upper())
455
+ ev_cls = inferred_ev.value
456
+ else:
457
+ ev_cls = str(raw_ev_cls).strip()
458
+
459
+ if raw_conf is None or str(raw_conf).strip() == "":
460
+ conf = "UNKNOWN" if ev_cls == "UNKNOWN" else ("LOW" if ev_cls == "POSSIBLE" else "HIGH")
461
+ else:
462
+ conf = str(raw_conf).strip().upper()
463
+
464
+ raw_status = getattr(record, "status", None)
465
+ if raw_status is None or str(raw_status).strip() == "":
466
+ status = "UNKNOWN" if ev_cls == "UNKNOWN" else ("INFERENCE" if ev_cls == "POSSIBLE" else "FACT")
467
+ else:
468
+ status = str(raw_status).strip().upper()
469
+
470
+ return RelationshipRecord(
471
+ source=src,
472
+ target=tgt,
473
+ relationship=rel,
474
+ evidence_class=ev_cls,
475
+ confidence=conf,
476
+ file=str(getattr(record, "file", "") or ""),
477
+ start_line=int(getattr(record, "start_line", 1) or 1),
478
+ end_line=int(getattr(record, "end_line", None) or getattr(record, "start_line", 1) or 1),
479
+ target_file=getattr(record, "target_file", None),
480
+ target_line=getattr(record, "target_line", None),
481
+ evidence=str(getattr(record, "evidence", "") or ""),
482
+ reason=getattr(record, "reason", None),
483
+ status=status,
484
+ supporting_locations=tuple(getattr(record, "supporting_locations", ()) or ()),
485
+ occurrence_count=int(getattr(record, "occurrence_count", 1) or 1),
486
+ )