pycforge 0.15.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 (221) hide show
  1. pycforge/__init__.py +8 -0
  2. pycforge/__main__.py +2 -0
  3. pycforge/_version.py +1 -0
  4. pycforge/converter/__init__.py +0 -0
  5. pycforge/converter/analysis/__init__.py +5 -0
  6. pycforge/converter/analysis/functions.py +723 -0
  7. pycforge/converter/analysis/model.py +158 -0
  8. pycforge/converter/analysis/planning.py +464 -0
  9. pycforge/converter/analysis/stage.py +1976 -0
  10. pycforge/converter/analysis/symbols.py +237 -0
  11. pycforge/converter/analysis/validation.py +2166 -0
  12. pycforge/converter/c_output/__init__.py +2 -0
  13. pycforge/converter/c_output/conformance.py +297 -0
  14. pycforge/converter/c_output/renderer.py +220 -0
  15. pycforge/converter/conditional_regions/__init__.py +71 -0
  16. pycforge/converter/conditional_regions/analysis.py +481 -0
  17. pycforge/converter/conditional_regions/lowering.py +579 -0
  18. pycforge/converter/conditional_regions/model.py +243 -0
  19. pycforge/converter/conditional_regions/validation.py +690 -0
  20. pycforge/converter/containers/__init__.py +19 -0
  21. pycforge/converter/containers/analysis.py +542 -0
  22. pycforge/converter/containers/lowering.py +290 -0
  23. pycforge/converter/containers/model.py +146 -0
  24. pycforge/converter/contracts/__init__.py +0 -0
  25. pycforge/converter/contracts/configuration.py +178 -0
  26. pycforge/converter/contracts/identifiers.py +68 -0
  27. pycforge/converter/contracts/versions.py +71 -0
  28. pycforge/converter/core/__init__.py +0 -0
  29. pycforge/converter/core/artifact_io.py +87 -0
  30. pycforge/converter/core/cancellation.py +7 -0
  31. pycforge/converter/core/canonicalization.py +254 -0
  32. pycforge/converter/core/context.py +13 -0
  33. pycforge/converter/core/diagnostics.py +95 -0
  34. pycforge/converter/core/enums.py +23 -0
  35. pycforge/converter/core/fingerprint.py +22 -0
  36. pycforge/converter/core/pipeline.py +8 -0
  37. pycforge/converter/core/progress.py +25 -0
  38. pycforge/converter/core/request.py +50 -0
  39. pycforge/converter/core/resource_policy.py +31 -0
  40. pycforge/converter/core/result.py +23 -0
  41. pycforge/converter/core/serialization.py +36 -0
  42. pycforge/converter/core/stage.py +10 -0
  43. pycforge/converter/core/stage_artifact.py +80 -0
  44. pycforge/converter/core/stage_outcome.py +15 -0
  45. pycforge/converter/decision_trace/__init__.py +0 -0
  46. pycforge/converter/decision_trace/recorder.py +52 -0
  47. pycforge/converter/facade.py +459 -0
  48. pycforge/converter/frontend/__init__.py +4 -0
  49. pycforge/converter/frontend/normalizer.py +59 -0
  50. pycforge/converter/frontend/parser.py +20 -0
  51. pycforge/converter/frontend/source_document.py +93 -0
  52. pycforge/converter/frontend/stages.py +518 -0
  53. pycforge/converter/frontend/validation.py +76 -0
  54. pycforge/converter/io/__init__.py +0 -0
  55. pycforge/converter/io/atomic_writer.py +33 -0
  56. pycforge/converter/ir/__init__.py +1 -0
  57. pycforge/converter/ir/c_ir/__init__.py +4 -0
  58. pycforge/converter/ir/c_ir/builder.py +26 -0
  59. pycforge/converter/ir/c_ir/model.py +318 -0
  60. pycforge/converter/ir/c_ir/serialization.py +88 -0
  61. pycforge/converter/ir/c_ir/validation.py +834 -0
  62. pycforge/converter/ir/python_ir/__init__.py +17 -0
  63. pycforge/converter/ir/python_ir/nodes.py +86 -0
  64. pycforge/converter/keyword_calls/__init__.py +68 -0
  65. pycforge/converter/keyword_calls/analysis.py +458 -0
  66. pycforge/converter/keyword_calls/lowering.py +271 -0
  67. pycforge/converter/keyword_calls/model.py +304 -0
  68. pycforge/converter/keyword_calls/validation.py +802 -0
  69. pycforge/converter/keyword_only_calls/__init__.py +68 -0
  70. pycforge/converter/keyword_only_calls/analysis.py +640 -0
  71. pycforge/converter/keyword_only_calls/lowering.py +376 -0
  72. pycforge/converter/keyword_only_calls/model.py +328 -0
  73. pycforge/converter/keyword_only_calls/validation.py +1357 -0
  74. pycforge/converter/lowering.py +1000 -0
  75. pycforge/converter/modules/__init__.py +13 -0
  76. pycforge/converter/modules/analysis.py +1250 -0
  77. pycforge/converter/modules/lowering.py +200 -0
  78. pycforge/converter/modules/model.py +73 -0
  79. pycforge/converter/modules/stage.py +192 -0
  80. pycforge/converter/numeric_semantics/__init__.py +23 -0
  81. pycforge/converter/numeric_semantics/analysis.py +420 -0
  82. pycforge/converter/numeric_semantics/lowering.py +153 -0
  83. pycforge/converter/numeric_semantics/model.py +123 -0
  84. pycforge/converter/records/__init__.py +34 -0
  85. pycforge/converter/records/analysis.py +1394 -0
  86. pycforge/converter/records/lowering.py +393 -0
  87. pycforge/converter/records/model.py +319 -0
  88. pycforge/converter/rules/__init__.py +3 -0
  89. pycforge/converter/support_templates/__init__.py +49 -0
  90. pycforge/converter/support_templates/assembly.py +106 -0
  91. pycforge/converter/support_templates/factories.py +348 -0
  92. pycforge/converter/support_templates/model.py +159 -0
  93. pycforge/converter/support_templates/registry.py +388 -0
  94. pycforge/converter/telemetry/__init__.py +0 -0
  95. pycforge/converter/telemetry/sink.py +39 -0
  96. pycforge/ide/__init__.py +19 -0
  97. pycforge/ide/__main__.py +5 -0
  98. pycforge/ide/_worker_event_codec.py +276 -0
  99. pycforge/ide/_worker_protocol_json.py +197 -0
  100. pycforge/ide/_worker_protocol_types.py +90 -0
  101. pycforge/ide/_worker_request_codec.py +200 -0
  102. pycforge/ide/_worker_request_data.py +260 -0
  103. pycforge/ide/_worker_result_data.py +343 -0
  104. pycforge/ide/action_contract.py +469 -0
  105. pycforge/ide/action_validation.py +229 -0
  106. pycforge/ide/command_palette.py +223 -0
  107. pycforge/ide/controller.py +904 -0
  108. pycforge/ide/controller_conversion.py +546 -0
  109. pycforge/ide/controller_io.py +205 -0
  110. pycforge/ide/editor.py +951 -0
  111. pycforge/ide/editor_commands_qt.py +274 -0
  112. pycforge/ide/editor_lexical.py +43 -0
  113. pycforge/ide/editor_sidebars.py +236 -0
  114. pycforge/ide/editor_syntax.py +243 -0
  115. pycforge/ide/find_replace.py +535 -0
  116. pycforge/ide/icons.py +91 -0
  117. pycforge/ide/io_service.py +196 -0
  118. pycforge/ide/model.py +164 -0
  119. pycforge/ide/panels.py +903 -0
  120. pycforge/ide/positions.py +220 -0
  121. pycforge/ide/process_worker.py +130 -0
  122. pycforge/ide/qt.py +230 -0
  123. pycforge/ide/qt_actions.py +418 -0
  124. pycforge/ide/qt_close.py +169 -0
  125. pycforge/ide/qt_command_palette.py +297 -0
  126. pycforge/ide/qt_contract.py +220 -0
  127. pycforge/ide/qt_documents.py +590 -0
  128. pycforge/ide/qt_editor_buffers.py +113 -0
  129. pycforge/ide/qt_editor_surfaces.py +112 -0
  130. pycforge/ide/qt_menus.py +324 -0
  131. pycforge/ide/qt_projection.py +500 -0
  132. pycforge/ide/qt_shell.py +348 -0
  133. pycforge/ide/qt_shell_interactions.py +320 -0
  134. pycforge/ide/qt_state.py +393 -0
  135. pycforge/ide/qt_workspace_features.py +398 -0
  136. pycforge/ide/qt_workspace_navigation.py +208 -0
  137. pycforge/ide/qt_workspace_observers.py +173 -0
  138. pycforge/ide/qt_workspace_panels.py +545 -0
  139. pycforge/ide/qt_workspace_widgets.py +228 -0
  140. pycforge/ide/resources/icons/about.svg +1 -0
  141. pycforge/ide/resources/icons/add-document.svg +1 -0
  142. pycforge/ide/resources/icons/brand-mark.svg +1 -0
  143. pycforge/ide/resources/icons/bundle-search.svg +1 -0
  144. pycforge/ide/resources/icons/cancel.svg +1 -0
  145. pycforge/ide/resources/icons/check.svg +1 -0
  146. pycforge/ide/resources/icons/chevron-right.svg +1 -0
  147. pycforge/ide/resources/icons/close.svg +1 -0
  148. pycforge/ide/resources/icons/collapse-all.svg +1 -0
  149. pycforge/ide/resources/icons/command-palette.svg +1 -0
  150. pycforge/ide/resources/icons/convert.svg +1 -0
  151. pycforge/ide/resources/icons/copy.svg +1 -0
  152. pycforge/ide/resources/icons/cut.svg +1 -0
  153. pycforge/ide/resources/icons/decision-trace.svg +1 -0
  154. pycforge/ide/resources/icons/details.svg +1 -0
  155. pycforge/ide/resources/icons/diagnostics.svg +1 -0
  156. pycforge/ide/resources/icons/duplicate-line.svg +1 -0
  157. pycforge/ide/resources/icons/exit.svg +1 -0
  158. pycforge/ide/resources/icons/expand-all.svg +1 -0
  159. pycforge/ide/resources/icons/export.svg +1 -0
  160. pycforge/ide/resources/icons/find.svg +1 -0
  161. pycforge/ide/resources/icons/go-to-line.svg +1 -0
  162. pycforge/ide/resources/icons/go-to-output.svg +1 -0
  163. pycforge/ide/resources/icons/go-to-source.svg +1 -0
  164. pycforge/ide/resources/icons/history.svg +1 -0
  165. pycforge/ide/resources/icons/indent.svg +1 -0
  166. pycforge/ide/resources/icons/link-c.svg +1 -0
  167. pycforge/ide/resources/icons/mappings.svg +1 -0
  168. pycforge/ide/resources/icons/module.svg +1 -0
  169. pycforge/ide/resources/icons/move-down.svg +1 -0
  170. pycforge/ide/resources/icons/move-line-down.svg +1 -0
  171. pycforge/ide/resources/icons/move-line-up.svg +1 -0
  172. pycforge/ide/resources/icons/move-up.svg +1 -0
  173. pycforge/ide/resources/icons/next.svg +1 -0
  174. pycforge/ide/resources/icons/open.svg +1 -0
  175. pycforge/ide/resources/icons/outdent.svg +1 -0
  176. pycforge/ide/resources/icons/outline.svg +1 -0
  177. pycforge/ide/resources/icons/paste.svg +1 -0
  178. pycforge/ide/resources/icons/previous.svg +1 -0
  179. pycforge/ide/resources/icons/primary-module.svg +1 -0
  180. pycforge/ide/resources/icons/redo.svg +1 -0
  181. pycforge/ide/resources/icons/remove-document.svg +1 -0
  182. pycforge/ide/resources/icons/replace.svg +1 -0
  183. pycforge/ide/resources/icons/save-as.svg +1 -0
  184. pycforge/ide/resources/icons/save.svg +1 -0
  185. pycforge/ide/resources/icons/select-all.svg +1 -0
  186. pycforge/ide/resources/icons/settings.svg +1 -0
  187. pycforge/ide/resources/icons/show-c.svg +1 -0
  188. pycforge/ide/resources/icons/split-view.svg +1 -0
  189. pycforge/ide/resources/icons/summary.svg +1 -0
  190. pycforge/ide/resources/icons/telemetry.svg +1 -0
  191. pycforge/ide/resources/icons/toggle-comment.svg +1 -0
  192. pycforge/ide/resources/icons/toggle-fold.svg +1 -0
  193. pycforge/ide/resources/icons/undo.svg +1 -0
  194. pycforge/ide/resources/icons/whitespace.svg +1 -0
  195. pycforge/ide/revisions.py +400 -0
  196. pycforge/ide/search_service.py +280 -0
  197. pycforge/ide/session_history.py +150 -0
  198. pycforge/ide/source_editing.py +417 -0
  199. pycforge/ide/source_structure.py +558 -0
  200. pycforge/ide/source_structure_async.py +191 -0
  201. pycforge/ide/supervisor.py +503 -0
  202. pycforge/ide/theme.py +119 -0
  203. pycforge/ide/theme_stylesheet.py +598 -0
  204. pycforge/ide/theme_workspace_stylesheet.py +155 -0
  205. pycforge/ide/visual_tokens.py +142 -0
  206. pycforge/ide/worker_protocol.py +80 -0
  207. pycforge/ide/workspace_search.py +462 -0
  208. pycforge/ide/workspace_session.py +256 -0
  209. pycforge/laboratory/__init__.py +6 -0
  210. pycforge/laboratory/audits.py +2244 -0
  211. pycforge/laboratory/checkpoint_e.py +2685 -0
  212. pycforge/laboratory/cli.py +119 -0
  213. pycforge/laboratory/exit_codes.py +11 -0
  214. pycforge/laboratory/keyword_audit.py +661 -0
  215. pycforge/laboratory/keyword_only_audit.py +481 -0
  216. pycforge-0.15.2.dist-info/METADATA +209 -0
  217. pycforge-0.15.2.dist-info/RECORD +221 -0
  218. pycforge-0.15.2.dist-info/WHEEL +5 -0
  219. pycforge-0.15.2.dist-info/entry_points.txt +5 -0
  220. pycforge-0.15.2.dist-info/licenses/LICENSE +674 -0
  221. pycforge-0.15.2.dist-info/top_level.txt +1 -0
@@ -0,0 +1,2166 @@
1
+ from __future__ import annotations
2
+ from collections import deque
3
+ import hashlib
4
+ import re
5
+ from typing import Any
6
+
7
+ from pycforge.converter.contracts.configuration import (
8
+ DEFAULT_MODULE_POLICY,
9
+ DEFAULT_NUMERIC_POLICY,
10
+ DEFAULT_RECORD_POLICY,
11
+ PHASE12_MODULE_POLICY,
12
+ )
13
+ from pycforge.converter.contracts.identifiers import (
14
+ C11_EXTERNAL_IDENTIFIERS,
15
+ C_KEYWORDS,
16
+ TARGET_RESERVED_NAMES,
17
+ )
18
+ from pycforge.converter.contracts.versions import (
19
+ CONDITIONAL_FACT_SCHEMA,
20
+ CONVERSION_PLAN_SCHEMA,
21
+ KEYWORD_CALL_FACT_SCHEMA,
22
+ KEYWORD_ONLY_CALL_FACT_SCHEMA,
23
+ MODULE_FACT_SCHEMA,
24
+ NUMERIC_FACT_SCHEMA,
25
+ PHASE14A_CONVERSION_PLAN_SCHEMA,
26
+ PHASE14B_CONVERSION_PLAN_SCHEMA,
27
+ PHASE14C_CONVERSION_PLAN_SCHEMA,
28
+ PHASE13_CONVERSION_PLAN_SCHEMA,
29
+ PHASE12_CONVERSION_PLAN_SCHEMA,
30
+ RECORD_FACT_SCHEMA,
31
+ )
32
+ from pycforge.converter.conditional_regions.validation import (
33
+ validate_conditional_region_facts,
34
+ )
35
+ from pycforge.converter.keyword_calls.validation import (
36
+ validate_keyword_call_binding_facts,
37
+ )
38
+ from pycforge.converter.keyword_calls.model import KeywordCallValidationCanceled
39
+ from pycforge.converter.keyword_only_calls.validation import (
40
+ validate_keyword_only_call_binding_facts,
41
+ )
42
+ from pycforge.converter.ir.python_ir import python_ir_reference_ids
43
+
44
+
45
+ _HELPER_REQUIREMENT = re.compile(
46
+ r"^pycf(?:\.[a-z][a-z0-9_]*)+@(?:0|[1-9][0-9]*)\.(?:0|[1-9][0-9]*)\.(?:0|[1-9][0-9]*)$"
47
+ )
48
+ _SHA256 = re.compile(r"^[0-9a-f]{64}$")
49
+
50
+ _RECORD_TABLE_CONTRACTS = {
51
+ "record-definition-facts": ("record-id", "record_id"),
52
+ "record-field-facts": ("record-field-id", "field_id"),
53
+ "record-initializer-facts": ("record-initializer-id", "initializer_id"),
54
+ "record-instance-facts": ("record-instance-id", "instance_id"),
55
+ "record-binding-facts": ("binding-id", "binding_id"),
56
+ "record-access-facts": ("attribute-node-id", "access_node_id"),
57
+ }
58
+
59
+ _RECORD_FIELD_CATEGORIES = {
60
+ "integer-like": "int",
61
+ "floating-like": "float",
62
+ "boolean-like": "bool",
63
+ }
64
+
65
+
66
+ def _cumulatively_ineligible_target_function_node_ids(
67
+ payload: dict[str, Any],
68
+ tables: list[dict[str, Any]],
69
+ cancellation: Any = None,
70
+ ) -> frozenset[str]:
71
+ def check_cancellation() -> None:
72
+ if cancellation is not None and bool(
73
+ getattr(cancellation, "is_canceled", False)
74
+ ):
75
+ raise KeywordCallValidationCanceled
76
+
77
+ check_cancellation()
78
+ table_map = {
79
+ table.get("table_id"): table
80
+ for table in tables
81
+ if isinstance(table, dict) and isinstance(table.get("table_id"), str)
82
+ }
83
+
84
+ def values(table_id: str) -> tuple[dict[str, Any], ...]:
85
+ return tuple(
86
+ record["value"]
87
+ for record in table_map.get(table_id, {}).get("records", ())
88
+ if isinstance(record, dict) and isinstance(record.get("value"), dict)
89
+ )
90
+
91
+ signatures = {
92
+ item.get("function_node_id"): item
93
+ for item in values("function-signature-facts")
94
+ if isinstance(item.get("function_node_id"), str)
95
+ }
96
+ returns = {
97
+ item.get("function_node_id"): item
98
+ for item in values("return-path-facts")
99
+ if isinstance(item.get("function_node_id"), str)
100
+ }
101
+ locals_by_function = {
102
+ item.get("function_node_id"): item
103
+ for item in values("local-declaration-facts")
104
+ if isinstance(item.get("function_node_id"), str)
105
+ }
106
+ calls = values("call-target-facts")
107
+ graph_values = values("call-graph-facts")
108
+ recursive_functions = set(
109
+ graph_values[0].get("recursive_function_node_ids", ())
110
+ if graph_values
111
+ else ()
112
+ )
113
+
114
+ module = payload.get("python_ir")
115
+ node_values = module.get("nodes") if isinstance(module, dict) else None
116
+ if not isinstance(node_values, list):
117
+ return frozenset(signatures)
118
+ nodes = {
119
+ node.get("node_id"): node
120
+ for node in node_values
121
+ if isinstance(node, dict) and isinstance(node.get("node_id"), str)
122
+ }
123
+ parent_by_node: dict[str, str] = {}
124
+ for parent in node_values:
125
+ check_cancellation()
126
+ if not isinstance(parent, dict) or parent.get("node_id") not in nodes:
127
+ continue
128
+ for field, value in parent.get("fields", {}).items():
129
+ check_cancellation()
130
+ for child in python_ir_reference_ids(
131
+ parent.get("kind"),
132
+ field,
133
+ value,
134
+ nodes,
135
+ ):
136
+ parent_by_node.setdefault(child, parent["node_id"])
137
+
138
+ owner_cache: dict[str, str | None] = {}
139
+
140
+ def owner(node_id: str) -> str | None:
141
+ cached = owner_cache.get(node_id)
142
+ if cached is not None or node_id in owner_cache:
143
+ return cached
144
+ current = node_id
145
+ path: list[str] = []
146
+ seen: set[str] = set()
147
+ while current not in seen:
148
+ check_cancellation()
149
+ seen.add(current)
150
+ path.append(current)
151
+ if current in owner_cache:
152
+ result = owner_cache[current]
153
+ break
154
+ parent_id = parent_by_node.get(current)
155
+ if parent_id is None:
156
+ result = None
157
+ break
158
+ if nodes.get(parent_id, {}).get("kind") == "FunctionDef":
159
+ result = parent_id
160
+ break
161
+ current = parent_id
162
+ else:
163
+ result = None
164
+ for item in path:
165
+ owner_cache[item] = result
166
+ return result
167
+
168
+ calls_by_owner: dict[str, list[dict[str, Any]]] = {}
169
+ callers_by_target: dict[str, list[str]] = {}
170
+ for call in calls:
171
+ check_cancellation()
172
+ call_node_id = call.get("call_node_id")
173
+ if not isinstance(call_node_id, str):
174
+ continue
175
+ function_id = owner(call_node_id)
176
+ if function_id is not None:
177
+ calls_by_owner.setdefault(function_id, []).append(call)
178
+ target_function_id = call.get("target_function_node_id")
179
+ if target_function_id in signatures:
180
+ callers_by_target.setdefault(target_function_id, []).append(
181
+ function_id
182
+ )
183
+
184
+ local_failure_fields = (
185
+ "use_before_binding_node_ids",
186
+ "loop_target_escape_node_ids",
187
+ "first_definitions_in_control_node_ids",
188
+ "representation_conflict_node_ids",
189
+ "loop_target_rebind_node_ids",
190
+ "loop_target_mutation_node_ids",
191
+ )
192
+ eligible: dict[str, bool] = {}
193
+ for function_id, signature in signatures.items():
194
+ check_cancellation()
195
+ path = returns.get(function_id, {})
196
+ local = locals_by_function.get(function_id, {})
197
+ eligible[function_id] = bool(
198
+ signature.get("eligible") is True
199
+ and path.get("compatible") is True
200
+ and path.get("fallthrough_possible") is False
201
+ and local.get("valid") is True
202
+ and not any(local.get(field) for field in local_failure_fields)
203
+ and function_id not in recursive_functions
204
+ and all(
205
+ call.get("supported") is True
206
+ for call in calls_by_owner.get(function_id, ())
207
+ )
208
+ )
209
+
210
+ pending = deque(
211
+ function_id
212
+ for function_id, supported in eligible.items()
213
+ if not supported
214
+ )
215
+ while pending:
216
+ check_cancellation()
217
+ target = pending.popleft()
218
+ for caller in callers_by_target.get(target, ()):
219
+ if eligible.get(caller, False):
220
+ eligible[caller] = False
221
+ pending.append(caller)
222
+ return frozenset(
223
+ function_id for function_id, supported in eligible.items() if not supported
224
+ )
225
+
226
+
227
+ def _module_function_name(module_id: str, source_name: str) -> str:
228
+ base = re.sub(r"[^A-Za-z0-9_]", "_", source_name)
229
+ if not base or not (base[0].isalpha() or base[0] == "_"):
230
+ base = "py_" + base
231
+ if base.startswith("_"):
232
+ base = "py" + base
233
+ if (
234
+ base in C_KEYWORDS
235
+ or base in TARGET_RESERVED_NAMES
236
+ or base in C11_EXTERNAL_IDENTIFIERS
237
+ or base.startswith(("pycf_", "pycm_"))
238
+ ):
239
+ base = "py_" + base
240
+ digest = hashlib.sha256(f"{module_id}.{source_name}".encode("utf-8")).hexdigest()
241
+ module_token = module_id.replace("_", "_u").replace(".", "_d")
242
+ return f"pycm_{digest}__{module_token}__{base}"
243
+
244
+
245
+ def _dependency_first_order(
246
+ module_ids: set[str],
247
+ edges: set[tuple[str, str]],
248
+ ) -> list[str] | None:
249
+ dependencies = {module_id: set() for module_id in module_ids}
250
+ dependents = {module_id: set() for module_id in module_ids}
251
+ for importer, target in edges:
252
+ if importer not in dependencies or target not in dependencies or importer == target:
253
+ return None
254
+ dependencies[importer].add(target)
255
+ dependents[target].add(importer)
256
+ ready = sorted(
257
+ (module_id for module_id, values in dependencies.items() if not values),
258
+ key=lambda item: item.encode("utf-8"),
259
+ )
260
+ order: list[str] = []
261
+ while ready:
262
+ current = ready.pop(0)
263
+ order.append(current)
264
+ for dependent in sorted(dependents[current], key=lambda item: item.encode("utf-8")):
265
+ dependencies[dependent].discard(current)
266
+ if not dependencies[dependent] and dependent not in ready and dependent not in order:
267
+ ready.append(dependent)
268
+ ready.sort(key=lambda item: item.encode("utf-8"))
269
+ return order if len(order) == len(module_ids) else None
270
+
271
+
272
+ def _validate_record_fact_tables(
273
+ payload: dict[str, Any],
274
+ tables: list[dict[str, Any]],
275
+ ) -> tuple[bool, str]:
276
+ """Validate the closed Phase 13 immutable static-record evidence.
277
+
278
+ This validator intentionally consumes only serialized facts and normalized
279
+ Python IR. It therefore acts as an independent boundary check between
280
+ analysis and lowering rather than trusting analyzer implementation types.
281
+ """
282
+
283
+ if payload.get("record_policy_version") != DEFAULT_RECORD_POLICY:
284
+ return False, "Phase 13 conversion plan has an unknown record_policy_version"
285
+
286
+ table_by_id = {table.get("table_id"): table for table in tables}
287
+ if not set(_RECORD_TABLE_CONTRACTS).issubset(table_by_id):
288
+ return False, "Phase 13 conversion plan omits required static-record fact tables"
289
+ record_tables = {
290
+ table_id: table_by_id[table_id] for table_id in _RECORD_TABLE_CONTRACTS
291
+ }
292
+ for table_id, (key_domain, key_field) in _RECORD_TABLE_CONTRACTS.items():
293
+ table = record_tables[table_id]
294
+ if (
295
+ table.get("schema_version") != RECORD_FACT_SCHEMA
296
+ or table.get("producer_stage") != "analysis.plan"
297
+ or table.get("key_domain") != key_domain
298
+ or table.get("completeness") != "complete"
299
+ ):
300
+ return False, f"Phase 13 {table_id} contract identity is invalid"
301
+ for record in table.get("records", ()):
302
+ value = record.get("value")
303
+ if (
304
+ not isinstance(value, dict)
305
+ or not isinstance(record.get("key"), str)
306
+ or not record["key"]
307
+ or record["key"] != value.get(key_field)
308
+ ):
309
+ return False, f"Phase 13 {table_id} key disagrees with its value identity"
310
+
311
+ python_ir = payload.get("python_ir")
312
+ node_values = python_ir.get("nodes") if isinstance(python_ir, dict) else None
313
+ if not isinstance(node_values, list):
314
+ return False, "Phase 13 conversion plan lacks flattened Python IR"
315
+ nodes = {
316
+ node.get("node_id"): node
317
+ for node in node_values
318
+ if isinstance(node, dict) and isinstance(node.get("node_id"), str)
319
+ }
320
+ if len(nodes) != len(node_values):
321
+ return False, "Phase 13 flattened Python IR has missing or duplicate node identities"
322
+
323
+ node_ordinals = {
324
+ node["node_id"]: ordinal for ordinal, node in enumerate(node_values)
325
+ }
326
+ parents: dict[str, list[tuple[str, str]]] = {}
327
+ for parent in node_values:
328
+ for field_name, field_value in parent.get("fields", {}).items():
329
+ for child_id in python_ir_reference_ids(
330
+ parent["kind"], field_name, field_value, nodes
331
+ ):
332
+ parents.setdefault(child_id, []).append(
333
+ (parent["node_id"], field_name)
334
+ )
335
+
336
+ def _parent(node_id: str) -> tuple[str, str] | None:
337
+ values = parents.get(node_id, ())
338
+ if not values:
339
+ return None
340
+ return min(
341
+ values,
342
+ key=lambda item: (node_ordinals.get(item[0], 2**63 - 1), item[1]),
343
+ )
344
+
345
+ def _enclosing_function(node_id: str) -> str | None:
346
+ current = node_id
347
+ seen: set[str] = set()
348
+ while current not in seen:
349
+ seen.add(current)
350
+ parent = _parent(current)
351
+ if parent is None:
352
+ return None
353
+ candidate = nodes[parent[0]]
354
+ if candidate.get("kind") in {"FunctionDef", "AsyncFunctionDef"}:
355
+ return parent[0]
356
+ current = parent[0]
357
+ return None
358
+
359
+ def _owner_body_position(node_id: str, owner_id: str) -> int | None:
360
+ body = nodes.get(owner_id, {}).get("fields", {}).get("body")
361
+ if not isinstance(body, list):
362
+ return None
363
+ current = node_id
364
+ seen: set[str] = set()
365
+ while current not in seen:
366
+ seen.add(current)
367
+ parent = _parent(current)
368
+ if parent is None:
369
+ return None
370
+ if parent == (owner_id, "body"):
371
+ try:
372
+ return body.index(current)
373
+ except ValueError:
374
+ return None
375
+ current = parent[0]
376
+ return None
377
+
378
+ store_parent_slots = {
379
+ ("Assign", "targets"),
380
+ ("AnnAssign", "target"),
381
+ ("AugAssign", "target"),
382
+ ("Delete", "targets"),
383
+ ("NamedExpr", "target"),
384
+ ("For", "target"),
385
+ ("AsyncFor", "target"),
386
+ ("comprehension", "target"),
387
+ ("withitem", "optional_vars"),
388
+ }
389
+
390
+ def _is_store_target(node_id: str) -> bool:
391
+ current = node_id
392
+ while True:
393
+ parent = _parent(current)
394
+ if parent is None:
395
+ return False
396
+ parent_node = nodes[parent[0]]
397
+ if (parent_node.get("kind"), parent[1]) in store_parent_slots:
398
+ return True
399
+ if (
400
+ parent_node.get("kind") in {"Tuple", "List", "Starred"}
401
+ and parent[1] in {"elts", "value"}
402
+ ):
403
+ current = parent[0]
404
+ continue
405
+ return False
406
+
407
+ def _crosses_class_or_lambda(node_id: str, owner_id: str) -> bool:
408
+ current = node_id
409
+ seen: set[str] = set()
410
+ while current not in seen:
411
+ seen.add(current)
412
+ parent = _parent(current)
413
+ if parent is None or parent[0] == owner_id:
414
+ return False
415
+ if nodes[parent[0]].get("kind") in {"ClassDef", "Lambda"}:
416
+ return True
417
+ current = parent[0]
418
+ return True
419
+
420
+ def _comprehension_target(node_id: str) -> bool:
421
+ current = node_id
422
+ seen: set[str] = set()
423
+ while current not in seen:
424
+ seen.add(current)
425
+ parent = _parent(current)
426
+ if parent is None:
427
+ return False
428
+ parent_node = nodes[parent[0]]
429
+ if parent_node.get("kind") == "comprehension":
430
+ return parent[1] == "target"
431
+ if parent_node.get("kind") not in {"Tuple", "List", "Starred"}:
432
+ return False
433
+ current = parent[0]
434
+ return False
435
+
436
+ def _owner_binding_conflict(
437
+ owner_id: str,
438
+ source_name: str,
439
+ declaration_node_id: str,
440
+ ) -> bool:
441
+ for node in node_values:
442
+ node_id = node["node_id"]
443
+ if node_id == declaration_node_id:
444
+ continue
445
+ if _enclosing_function(node_id) != owner_id:
446
+ continue
447
+ if _crosses_class_or_lambda(node_id, owner_id):
448
+ continue
449
+ kind = node.get("kind")
450
+ fields = node.get("fields", {})
451
+ if kind == "Name" and fields.get("id") == source_name:
452
+ if not _comprehension_target(node_id) and _is_store_target(node_id):
453
+ return True
454
+ elif kind in {"Global", "Nonlocal"} and source_name in fields.get("names", ()):
455
+ return True
456
+ elif kind == "ExceptHandler" and fields.get("name") == source_name:
457
+ return True
458
+ elif kind in {"MatchAs", "MatchStar"} and fields.get("name") == source_name:
459
+ return True
460
+ elif kind == "MatchMapping" and fields.get("rest") == source_name:
461
+ return True
462
+ elif (
463
+ kind in {"FunctionDef", "AsyncFunctionDef", "ClassDef"}
464
+ and node_id != owner_id
465
+ and fields.get("name") == source_name
466
+ ):
467
+ return True
468
+ elif kind == "alias":
469
+ imported = str(fields.get("name") or "")
470
+ bound_name = str(fields.get("asname") or imported.split(".", 1)[0])
471
+ if bound_name == source_name:
472
+ return True
473
+ return False
474
+
475
+ def _table_values(table_id: str, key_field: str) -> dict[str, dict[str, Any]]:
476
+ table = table_by_id.get(table_id, {})
477
+ result: dict[str, dict[str, Any]] = {}
478
+ for record in table.get("records", ()):
479
+ value = record.get("value")
480
+ if isinstance(value, dict) and isinstance(value.get(key_field), str):
481
+ result[value[key_field]] = value
482
+ return result
483
+
484
+ lexical_bindings = _table_values("binding-facts", "binding_id")
485
+ lexical_scopes = _table_values("scope-facts", "scope_id")
486
+ module_identities = _table_values("module-identity-facts", "module_id")
487
+ module_sources = _table_values("module-source-facts", "module_id")
488
+ module_functions = _table_values("module-function-facts", "function_node_id")
489
+ value_categories = {
490
+ record.get("key"): record.get("value")
491
+ for record in table_by_id.get("value-category-facts", {}).get("records", ())
492
+ if isinstance(record.get("key"), str)
493
+ }
494
+ module_records = payload.get("module_record_by_node")
495
+ if (
496
+ not lexical_bindings
497
+ or not lexical_scopes
498
+ or not module_identities
499
+ or not module_sources
500
+ or not isinstance(module_records, dict)
501
+ ):
502
+ return False, "Phase 13 record proof lacks lexical or module identity evidence"
503
+
504
+ required_provenance: dict[tuple[str, str], set[str]] = {}
505
+ for table_id, table in record_tables.items():
506
+ for record in table["records"]:
507
+ provenance = record.get("provenance")
508
+ source_node_ids = (
509
+ provenance.get("source_node_ids")
510
+ if isinstance(provenance, dict)
511
+ else None
512
+ )
513
+ if (
514
+ not isinstance(source_node_ids, list)
515
+ or not source_node_ids
516
+ or any(
517
+ not isinstance(node_id, str) or node_id not in nodes
518
+ for node_id in source_node_ids
519
+ )
520
+ ):
521
+ return False, "Phase 13 record fact provenance references an absent node"
522
+ required_provenance[(table_id, record["key"])] = set(source_node_ids)
523
+
524
+ values = {
525
+ table_id: {
526
+ record["key"]: record["value"] for record in table["records"]
527
+ }
528
+ for table_id, table in record_tables.items()
529
+ }
530
+ definitions = values["record-definition-facts"]
531
+ fields = values["record-field-facts"]
532
+ initializers = values["record-initializer-facts"]
533
+ instances = values["record-instance-facts"]
534
+ bindings = values["record-binding-facts"]
535
+ accesses = values["record-access-facts"]
536
+ generated_names = payload.get("generated_name_plans")
537
+ if not isinstance(generated_names, list) or any(
538
+ not isinstance(item, dict)
539
+ or not isinstance(item.get("binding_id"), str)
540
+ or not item["binding_id"]
541
+ or not isinstance(item.get("generated_name"), str)
542
+ or not item["generated_name"]
543
+ for item in generated_names
544
+ ):
545
+ return False, "Phase 13 generated-name plans are malformed"
546
+ generated_name_ids = [item["binding_id"] for item in generated_names]
547
+ if len(generated_name_ids) != len(set(generated_name_ids)):
548
+ return False, "Phase 13 generated-name plan bindings are not unique"
549
+ required_generated_name_ids = {
550
+ item.get("class_binding_id") for item in definitions.values()
551
+ } | set(fields) | set(bindings)
552
+ if not required_generated_name_ids.issubset(generated_name_ids):
553
+ return False, "Phase 13 record lowering lacks a required generated-name plan"
554
+
555
+ def _node_is(node_id: Any, kind: str) -> bool:
556
+ return isinstance(node_id, str) and nodes.get(node_id, {}).get("kind") == kind
557
+
558
+ def _identity(value: dict[str, Any]) -> tuple[Any, Any, Any]:
559
+ return value.get("module_id"), value.get("document_id"), value.get("logical_name")
560
+
561
+ class_node_ids = {
562
+ node_id for node_id, node in nodes.items() if node.get("kind") == "ClassDef"
563
+ }
564
+ if {item.get("class_node_id") for item in definitions.values()} != class_node_ids:
565
+ return False, "Phase 13 record definitions do not completely cover flattened classes"
566
+
567
+ definition_class_bindings: set[str] = set()
568
+ definition_names: set[tuple[str, str]] = set()
569
+ for record_id, definition in definitions.items():
570
+ field_ids = definition.get("field_ids")
571
+ class_node_id = definition.get("class_node_id")
572
+ class_binding_id = definition.get("class_binding_id")
573
+ identity = _identity(definition)
574
+ module_record = module_records.get(class_node_id)
575
+ module_identity = module_identities.get(identity[0])
576
+ module_source = module_sources.get(identity[0])
577
+ lexical_class = lexical_bindings.get(class_binding_id)
578
+ if (
579
+ not _node_is(class_node_id, "ClassDef")
580
+ or not isinstance(class_binding_id, str)
581
+ or not class_binding_id
582
+ or class_binding_id in definition_class_bindings
583
+ or not all(isinstance(item, str) and item for item in identity)
584
+ or not isinstance(definition.get("source_name"), str)
585
+ or not definition["source_name"]
586
+ or definition["source_name"] in {"int", "float", "bool", "str"}
587
+ or not isinstance(definition.get("flattened_name"), str)
588
+ or not definition["flattened_name"]
589
+ or (identity[0], definition["flattened_name"]) in definition_names
590
+ or not isinstance(field_ids, list)
591
+ or not 1 <= len(field_ids) <= 64
592
+ or len(field_ids) != len(set(field_ids))
593
+ or not isinstance(definition.get("initializer_id"), str)
594
+ or definition.get("category") != "immutable-static-record-definition"
595
+ or definition.get("storage_model") != "automatic-inline-record"
596
+ or definition.get("ownership_model") != "unique-lexical-owner"
597
+ or definition.get("lifetime_model") != "enclosing-function-activation"
598
+ or definition.get("aliasing_model") != "forbidden"
599
+ or definition.get("cleanup_model") != "none"
600
+ or definition.get("nullability_model") != "non-null-by-construction"
601
+ or definition.get("mutable") is not False
602
+ or not isinstance(module_record, dict)
603
+ or module_record.get("class_node_id") != class_node_id
604
+ or module_record.get("module_id") != identity[0]
605
+ or module_record.get("document_id") != identity[1]
606
+ or module_record.get("source_name") != definition.get("source_name")
607
+ or module_record.get("flattened_name") != definition.get("flattened_name")
608
+ or not isinstance(module_identity, dict)
609
+ or module_identity.get("document_id") != identity[1]
610
+ or module_identity.get("logical_name") != identity[2]
611
+ or not isinstance(module_source, dict)
612
+ or module_source.get("document_id") != identity[1]
613
+ or module_source.get("logical_name") != identity[2]
614
+ or not isinstance(lexical_class, dict)
615
+ or lexical_class.get("binding_kind") != "record-class"
616
+ or lexical_class.get("declaration_node_id") != class_node_id
617
+ or lexical_class.get("source_name") != definition.get("flattened_name")
618
+ ):
619
+ return False, "Phase 13 record definition violates the closed representation contract"
620
+ class_node = nodes[class_node_id]
621
+ class_fields = class_node.get("fields", {})
622
+ if (
623
+ class_fields.get("name") != definition["flattened_name"]
624
+ or class_fields.get("bases")
625
+ or class_fields.get("keywords")
626
+ or class_fields.get("decorator_list")
627
+ ):
628
+ return False, "Phase 13 record definition name disagrees with flattened Python IR"
629
+ if class_node_id not in required_provenance[("record-definition-facts", record_id)]:
630
+ return False, "Phase 13 record definition provenance is incomplete"
631
+ definition_class_bindings.add(class_binding_id)
632
+ definition_names.add((identity[0], definition["flattened_name"]))
633
+
634
+ fields_by_record: dict[str, list[dict[str, Any]]] = {
635
+ record_id: [] for record_id in definitions
636
+ }
637
+ for field_id, field in fields.items():
638
+ definition = definitions.get(field.get("record_id"))
639
+ declaration_id = field.get("declaration_node_id")
640
+ target_id = field.get("target_node_id")
641
+ annotation_id = field.get("annotation_node_id")
642
+ ordinal = field.get("ordinal")
643
+ category = field.get("category")
644
+ if (
645
+ definition is None
646
+ or field.get("class_node_id") != definition.get("class_node_id")
647
+ or _identity(field) != _identity(definition)
648
+ or not _node_is(declaration_id, "AnnAssign")
649
+ or not _node_is(target_id, "Name")
650
+ or not _node_is(annotation_id, "Name")
651
+ or not isinstance(ordinal, int)
652
+ or isinstance(ordinal, bool)
653
+ or ordinal < 0
654
+ or category not in _RECORD_FIELD_CATEGORIES
655
+ or not isinstance(field.get("source_name"), str)
656
+ or not field["source_name"]
657
+ or (
658
+ field["source_name"].startswith("__")
659
+ and field["source_name"].endswith("__")
660
+ )
661
+ or field.get("mutable") is not False
662
+ ):
663
+ return False, "Phase 13 record field violates the closed field contract"
664
+ declaration = nodes[declaration_id]
665
+ target = nodes[target_id]
666
+ annotation = nodes[annotation_id]
667
+ if (
668
+ declaration.get("fields", {}).get("target") != target_id
669
+ or declaration.get("fields", {}).get("annotation") != annotation_id
670
+ or declaration.get("fields", {}).get("value") is not None
671
+ or declaration.get("fields", {}).get("simple") != 1
672
+ or target.get("fields", {}).get("id") != field["source_name"]
673
+ or annotation.get("fields", {}).get("id") != _RECORD_FIELD_CATEGORIES[category]
674
+ ):
675
+ return False, "Phase 13 record field disagrees with flattened Python IR"
676
+ if not {declaration_id, target_id, annotation_id}.issubset(
677
+ required_provenance[("record-field-facts", field_id)]
678
+ ):
679
+ return False, "Phase 13 record field provenance is incomplete"
680
+ fields_by_record[field["record_id"]].append(field)
681
+
682
+ for record_id, definition in definitions.items():
683
+ ordered_fields = sorted(fields_by_record[record_id], key=lambda item: item["ordinal"])
684
+ if (
685
+ [item["ordinal"] for item in ordered_fields] != list(range(len(ordered_fields)))
686
+ or [item["field_id"] for item in ordered_fields] != definition["field_ids"]
687
+ or len({item["source_name"] for item in ordered_fields}) != len(ordered_fields)
688
+ ):
689
+ return False, "Phase 13 record field coverage, identity, or ordinal order is incomplete"
690
+
691
+ if {item.get("initializer_id") for item in definitions.values()} != set(initializers):
692
+ return False, "Phase 13 record initializer coverage is not exactly one per definition"
693
+ for initializer_id, initializer in initializers.items():
694
+ definition = definitions.get(initializer.get("record_id"))
695
+ function_id = initializer.get("function_node_id")
696
+ arguments_id = initializer.get("arguments_node_id")
697
+ self_id = initializer.get("self_parameter_node_id")
698
+ parameter_ids = initializer.get("parameter_node_ids")
699
+ assignment_ids = initializer.get("assignment_node_ids")
700
+ if (
701
+ definition is None
702
+ or definition.get("initializer_id") != initializer_id
703
+ or _identity(initializer) != _identity(definition)
704
+ or not _node_is(function_id, "FunctionDef")
705
+ or not _node_is(arguments_id, "arguments")
706
+ or not _node_is(self_id, "arg")
707
+ or not isinstance(parameter_ids, list)
708
+ or not isinstance(assignment_ids, list)
709
+ or initializer.get("field_ids") != definition.get("field_ids")
710
+ or len(parameter_ids) != len(definition["field_ids"])
711
+ or len(assignment_ids) != len(definition["field_ids"])
712
+ or len(parameter_ids) != len(set(parameter_ids))
713
+ or len(assignment_ids) != len(set(assignment_ids))
714
+ or any(not _node_is(item, "arg") for item in parameter_ids)
715
+ or any(not _node_is(item, "Assign") for item in assignment_ids)
716
+ or initializer.get("receiver_model") != "direct-addressed-initialization-receiver"
717
+ or initializer.get("evaluation_order")
718
+ != "field-declaration-order-left-to-right-once"
719
+ or initializer.get("initialization_completeness") != "all-fields-exactly-once"
720
+ ):
721
+ return False, "Phase 13 record initializer violates exact field coverage"
722
+ function = nodes[function_id]
723
+ arguments = nodes[arguments_id]
724
+ ordered_fields = sorted(
725
+ fields_by_record[initializer["record_id"]], key=lambda item: item["ordinal"]
726
+ )
727
+ return_annotation = nodes.get(function.get("fields", {}).get("returns"), {})
728
+ if (
729
+ function.get("fields", {}).get("name") != "__init__"
730
+ or function.get("fields", {}).get("args") != arguments_id
731
+ or function.get("fields", {}).get("body") != assignment_ids
732
+ or function.get("fields", {}).get("decorator_list")
733
+ or function.get("fields", {}).get("type_comment") is not None
734
+ or arguments.get("fields", {}).get("args") != [self_id] + parameter_ids
735
+ or any(
736
+ arguments.get("fields", {}).get(field)
737
+ for field in ("posonlyargs", "kwonlyargs", "kw_defaults", "defaults")
738
+ )
739
+ or arguments.get("fields", {}).get("vararg") is not None
740
+ or arguments.get("fields", {}).get("kwarg") is not None
741
+ or nodes[self_id].get("fields", {}).get("arg") != "self"
742
+ or nodes[self_id].get("fields", {}).get("annotation") is not None
743
+ or nodes[self_id].get("fields", {}).get("type_comment") is not None
744
+ or return_annotation.get("kind") != "Constant"
745
+ or return_annotation.get("fields", {}).get("value") is not None
746
+ ):
747
+ return False, "Phase 13 record initializer disagrees with flattened Python IR"
748
+ for parameter_id, assignment_id, field in zip(
749
+ parameter_ids, assignment_ids, ordered_fields
750
+ ):
751
+ parameter = nodes[parameter_id]
752
+ parameter_annotation = nodes.get(
753
+ parameter.get("fields", {}).get("annotation"), {}
754
+ )
755
+ assignment = nodes[assignment_id]
756
+ assignment_targets = assignment.get("fields", {}).get("targets")
757
+ target = (
758
+ nodes.get(assignment_targets[0], {})
759
+ if isinstance(assignment_targets, list) and len(assignment_targets) == 1
760
+ else {}
761
+ )
762
+ receiver = nodes.get(target.get("fields", {}).get("value"), {})
763
+ source = nodes.get(assignment.get("fields", {}).get("value"), {})
764
+ if (
765
+ parameter.get("fields", {}).get("arg") != field["source_name"]
766
+ or parameter.get("fields", {}).get("type_comment") is not None
767
+ or parameter_annotation.get("kind") != "Name"
768
+ or parameter_annotation.get("fields", {}).get("id")
769
+ != _RECORD_FIELD_CATEGORIES[field["category"]]
770
+ or target.get("kind") != "Attribute"
771
+ or target.get("fields", {}).get("attr") != field["source_name"]
772
+ or receiver.get("kind") != "Name"
773
+ or receiver.get("fields", {}).get("id") != "self"
774
+ or source.get("kind") != "Name"
775
+ or source.get("fields", {}).get("id") != field["source_name"]
776
+ or assignment.get("fields", {}).get("type_comment") is not None
777
+ ):
778
+ return False, "Phase 13 record initializer field copies are not exact"
779
+ class_body = nodes[definition["class_node_id"]].get("fields", {}).get("body")
780
+ if class_body != [item["declaration_node_id"] for item in ordered_fields] + [function_id]:
781
+ return False, "Phase 13 record class body exceeds the closed record surface"
782
+ initializer_nodes = {
783
+ function_id,
784
+ arguments_id,
785
+ self_id,
786
+ *parameter_ids,
787
+ *assignment_ids,
788
+ }
789
+ if not initializer_nodes.issubset(
790
+ required_provenance[("record-initializer-facts", initializer_id)]
791
+ ):
792
+ return False, "Phase 13 record initializer provenance is incomplete"
793
+
794
+ if {item.get("binding_id") for item in instances.values()} != set(bindings):
795
+ return False, "Phase 13 record instance bindings are not one-to-one"
796
+ if {item.get("instance_id") for item in bindings.values()} != set(instances):
797
+ return False, "Phase 13 record instance coverage is not one-to-one"
798
+ for instance_id, instance in instances.items():
799
+ definition = definitions.get(instance.get("record_id"))
800
+ binding = bindings.get(instance.get("binding_id"))
801
+ construction_id = instance.get("construction_node_id")
802
+ assignment_id = instance.get("assignment_node_id")
803
+ target_id = instance.get("target_node_id")
804
+ owner_id = instance.get("owner_function_node_id")
805
+ argument_ids = instance.get("argument_node_ids")
806
+ lexical_instance = lexical_bindings.get(instance.get("binding_id"))
807
+ lexical_scope = (
808
+ lexical_scopes.get(lexical_instance.get("scope_id"))
809
+ if isinstance(lexical_instance, dict)
810
+ else None
811
+ )
812
+ module_function = module_functions.get(owner_id)
813
+ module_identity = module_identities.get(instance.get("module_id"))
814
+ module_source = module_sources.get(instance.get("module_id"))
815
+ if (
816
+ definition is None
817
+ or binding is None
818
+ or instance.get("class_node_id") != definition.get("class_node_id")
819
+ or _identity(instance) != _identity(definition)
820
+ or not _node_is(owner_id, "FunctionDef")
821
+ or not _node_is(construction_id, "Call")
822
+ or not _node_is(assignment_id, "Assign")
823
+ or not _node_is(target_id, "Name")
824
+ or not isinstance(argument_ids, list)
825
+ or len(argument_ids) != len(definition["field_ids"])
826
+ or any(item not in nodes for item in argument_ids)
827
+ or instance.get("category") != "immutable-static-record-like"
828
+ or instance.get("storage_model") != "automatic-inline-record"
829
+ or instance.get("ownership_model") != "unique-lexical-owner"
830
+ or instance.get("lifetime_model") != "enclosing-function-activation"
831
+ or instance.get("aliasing_model") != "forbidden"
832
+ or instance.get("cleanup_model") != "none"
833
+ or instance.get("nullability_model") != "non-null-by-construction"
834
+ or instance.get("allocation_model") != "none"
835
+ or instance.get("mutable") is not False
836
+ or not isinstance(lexical_instance, dict)
837
+ or lexical_instance.get("binding_kind") != "local"
838
+ or lexical_instance.get("declaration_node_id") != target_id
839
+ or lexical_instance.get("source_name") != instance.get("source_name")
840
+ or not isinstance(lexical_scope, dict)
841
+ or lexical_scope.get("scope_kind") != "function"
842
+ or lexical_scope.get("owner_node_id") != owner_id
843
+ or not isinstance(module_function, dict)
844
+ or module_function.get("module_id") != instance.get("module_id")
845
+ or module_function.get("document_id") != instance.get("document_id")
846
+ or not isinstance(module_identity, dict)
847
+ or owner_id not in module_identity.get("function_node_ids", ())
848
+ or module_identity.get("document_id") != instance.get("document_id")
849
+ or module_identity.get("logical_name") != instance.get("logical_name")
850
+ or not isinstance(module_source, dict)
851
+ or owner_id not in module_source.get("function_node_ids", ())
852
+ or module_source.get("document_id") != instance.get("document_id")
853
+ or module_source.get("logical_name") != instance.get("logical_name")
854
+ ):
855
+ return False, "Phase 13 record instance violates automatic immutable ownership"
856
+ construction = nodes[construction_id]
857
+ assignment = nodes[assignment_id]
858
+ target = nodes[target_id]
859
+ constructor_name = nodes.get(construction.get("fields", {}).get("func"), {})
860
+ lexical_class = lexical_bindings.get(definition.get("class_binding_id"), {})
861
+ ordered_fields = sorted(
862
+ fields_by_record[instance["record_id"]], key=lambda item: item["ordinal"]
863
+ )
864
+ if (
865
+ construction.get("fields", {}).get("args") != argument_ids
866
+ or construction.get("fields", {}).get("keywords")
867
+ or constructor_name.get("kind") != "Name"
868
+ or constructor_name.get("fields", {}).get("id")
869
+ != definition.get("flattened_name")
870
+ or constructor_name.get("node_id")
871
+ not in lexical_class.get("occurrence_node_ids", ())
872
+ or assignment.get("fields", {}).get("value") != construction_id
873
+ or assignment.get("fields", {}).get("targets") != [target_id]
874
+ or assignment.get("fields", {}).get("type_comment") is not None
875
+ or target.get("fields", {}).get("id") != instance.get("source_name")
876
+ or assignment_id not in nodes[owner_id].get("fields", {}).get("body", ())
877
+ or [value_categories.get(item) for item in argument_ids]
878
+ != [item["category"] for item in ordered_fields]
879
+ or _owner_binding_conflict(
880
+ owner_id,
881
+ instance.get("source_name"),
882
+ target_id,
883
+ )
884
+ ):
885
+ return False, "Phase 13 record construction disagrees with flattened Python IR"
886
+ instance_nodes = {construction_id, assignment_id, target_id, *argument_ids}
887
+ if not instance_nodes.issubset(
888
+ required_provenance[("record-instance-facts", instance_id)]
889
+ ):
890
+ return False, "Phase 13 record instance provenance is incomplete"
891
+
892
+ for definition in definitions.values():
893
+ lexical_class = lexical_bindings[definition["class_binding_id"]]
894
+ expected_constructor_names = {
895
+ nodes[instance["construction_node_id"]]["fields"]["func"]
896
+ for instance in instances.values()
897
+ if instance.get("record_id") == definition.get("record_id")
898
+ }
899
+ if set(lexical_class.get("occurrence_node_ids", ())) != expected_constructor_names:
900
+ return False, "Phase 13 record class binding has an indirect or unproved use"
901
+
902
+ accesses_by_binding: dict[str, set[str]] = {binding_id: set() for binding_id in bindings}
903
+ for access_id, access in accesses.items():
904
+ instance = instances.get(access.get("instance_id"))
905
+ binding = bindings.get(access.get("binding_id"))
906
+ definition = definitions.get(access.get("record_id"))
907
+ field = fields.get(access.get("field_id"))
908
+ if (
909
+ instance is None
910
+ or binding is None
911
+ or definition is None
912
+ or field is None
913
+ or access.get("instance_id") != binding.get("instance_id")
914
+ or access.get("binding_id") != instance.get("binding_id")
915
+ or access.get("record_id") != instance.get("record_id")
916
+ or field.get("record_id") != access.get("record_id")
917
+ or access.get("field_name") != field.get("source_name")
918
+ or access.get("field_category") != field.get("category")
919
+ or access.get("owner_function_node_id") != instance.get("owner_function_node_id")
920
+ or _identity(access) != _identity(instance)
921
+ or not _node_is(access_id, "Attribute")
922
+ or access.get("access_mode") != "read"
923
+ or access.get("statically_bound") is not True
924
+ ):
925
+ return False, "Phase 13 record field access lacks an exact static relationship"
926
+ access_node = nodes[access_id]
927
+ receiver_id = access_node.get("fields", {}).get("value")
928
+ access_position = _owner_body_position(
929
+ access_id,
930
+ instance["owner_function_node_id"],
931
+ )
932
+ construction_position = _owner_body_position(
933
+ instance["assignment_node_id"],
934
+ instance["owner_function_node_id"],
935
+ )
936
+ if (
937
+ access_node.get("fields", {}).get("attr") != field["source_name"]
938
+ or not _node_is(receiver_id, "Name")
939
+ or receiver_id not in binding.get("occurrence_node_ids", ())
940
+ or nodes[receiver_id].get("fields", {}).get("id")
941
+ != binding.get("source_name")
942
+ or _is_store_target(access_id)
943
+ or access_position is None
944
+ or construction_position is None
945
+ or access_position <= construction_position
946
+ ):
947
+ return False, "Phase 13 record field access disagrees with flattened Python IR"
948
+ if access_id not in required_provenance[("record-access-facts", access_id)]:
949
+ return False, "Phase 13 record access provenance is incomplete"
950
+ accesses_by_binding[access["binding_id"]].add(access_id)
951
+
952
+ for binding_id, binding in bindings.items():
953
+ instance = instances.get(binding.get("instance_id"))
954
+ occurrence_ids = binding.get("occurrence_node_ids")
955
+ allowed_access_ids = binding.get("allowed_field_access_node_ids")
956
+ if (
957
+ instance is None
958
+ or binding.get("record_id") != instance.get("record_id")
959
+ or binding.get("source_name") != instance.get("source_name")
960
+ or binding.get("declaration_node_id") != instance.get("target_node_id")
961
+ or binding.get("owner_function_node_id") != instance.get("owner_function_node_id")
962
+ or _identity(binding) != _identity(instance)
963
+ or binding.get("category") != "immutable-static-record-like"
964
+ or binding.get("single_assignment") is not True
965
+ or binding.get("noalias") is not True
966
+ or binding.get("escapes") is not False
967
+ or not isinstance(occurrence_ids, list)
968
+ or not occurrence_ids
969
+ or len(occurrence_ids) != len(set(occurrence_ids))
970
+ or instance.get("target_node_id") not in occurrence_ids
971
+ or any(not _node_is(item, "Name") for item in occurrence_ids)
972
+ or not isinstance(allowed_access_ids, list)
973
+ or len(allowed_access_ids) != len(set(allowed_access_ids))
974
+ or set(allowed_access_ids) != accesses_by_binding[binding_id]
975
+ or set(occurrence_ids)
976
+ != {
977
+ instance.get("target_node_id"),
978
+ *(
979
+ nodes[access_id].get("fields", {}).get("value")
980
+ for access_id in allowed_access_ids
981
+ ),
982
+ }
983
+ ):
984
+ return False, "Phase 13 record binding violates no-alias/no-escape ownership"
985
+ lexical_binding = lexical_bindings.get(binding_id)
986
+ if (
987
+ not isinstance(lexical_binding, dict)
988
+ or lexical_binding.get("binding_kind") != "local"
989
+ or lexical_binding.get("declaration_node_id")
990
+ != binding.get("declaration_node_id")
991
+ or lexical_binding.get("source_name") != binding.get("source_name")
992
+ or set(lexical_binding.get("occurrence_node_ids", ()))
993
+ != set(occurrence_ids)
994
+ ):
995
+ return False, "Phase 13 record binding disagrees with lexical binding facts"
996
+ binding_nodes = {
997
+ binding["declaration_node_id"],
998
+ *occurrence_ids,
999
+ *allowed_access_ids,
1000
+ }
1001
+ if not binding_nodes.issubset(
1002
+ required_provenance[("record-binding-facts", binding_id)]
1003
+ ):
1004
+ return False, "Phase 13 record binding provenance is incomplete"
1005
+
1006
+ return True, ""
1007
+
1008
+
1009
+ def _validate_numeric_fact_table(
1010
+ payload: dict[str, Any],
1011
+ tables: list[dict[str, Any]],
1012
+ ) -> tuple[bool, str]:
1013
+ """Re-prove Phase 14A facts from serialized Python IR and plan data."""
1014
+
1015
+ if payload.get("numeric_policy_version") != DEFAULT_NUMERIC_POLICY:
1016
+ return False, "Phase 14 conversion plan has an unknown numeric_policy_version"
1017
+ if payload.get("target_contract") != "c11-portable-fixed-v1":
1018
+ return False, "Phase 14 numeric proof has an inexact target contract"
1019
+ table = next(
1020
+ (item for item in tables if item.get("table_id") == "numeric-operation-facts"),
1021
+ None,
1022
+ )
1023
+ if not isinstance(table, dict):
1024
+ return False, "Phase 14 conversion plan omits numeric-operation-facts"
1025
+ if (
1026
+ table.get("schema_version") != NUMERIC_FACT_SCHEMA
1027
+ or table.get("producer_stage") != "analysis.plan"
1028
+ or table.get("key_domain") != "binop-node-id"
1029
+ or table.get("completeness") != "complete"
1030
+ or table.get("invalidation_dependencies")
1031
+ != ["value-category-facts", "evaluation-order-facts"]
1032
+ ):
1033
+ return False, "Phase 14 numeric fact-table contract identity is invalid"
1034
+
1035
+ python_ir = payload.get("python_ir")
1036
+ node_values = python_ir.get("nodes") if isinstance(python_ir, dict) else None
1037
+ if not isinstance(node_values, list):
1038
+ return False, "Phase 14 numeric proof lacks flattened Python IR"
1039
+ nodes = {
1040
+ item.get("node_id"): item
1041
+ for item in node_values
1042
+ if isinstance(item, dict) and isinstance(item.get("node_id"), str)
1043
+ }
1044
+ if len(nodes) != len(node_values):
1045
+ return False, "Phase 14 numeric proof has duplicate or absent Python node IDs"
1046
+ ordinals = {item["node_id"]: index for index, item in enumerate(node_values)}
1047
+ parents: dict[str, list[tuple[str, str]]] = {}
1048
+ for parent in node_values:
1049
+ for field_name, value in parent.get("fields", {}).items():
1050
+ for child_id in python_ir_reference_ids(
1051
+ parent["kind"], field_name, value, nodes
1052
+ ):
1053
+ parents.setdefault(child_id, []).append(
1054
+ (parent["node_id"], field_name)
1055
+ )
1056
+
1057
+ module_functions = {
1058
+ record.get("value", {}).get("function_node_id"): record.get("value")
1059
+ for module_table in tables
1060
+ if module_table.get("table_id") == "module-function-facts"
1061
+ for record in module_table.get("records", ())
1062
+ if isinstance(record.get("value"), dict)
1063
+ }
1064
+ module_identities = {
1065
+ record.get("value", {}).get("module_id"): record.get("value")
1066
+ for module_table in tables
1067
+ if module_table.get("table_id") == "module-identity-facts"
1068
+ for record in module_table.get("records", ())
1069
+ if isinstance(record.get("value"), dict)
1070
+ }
1071
+ module_sources = {
1072
+ record.get("value", {}).get("module_id"): record.get("value")
1073
+ for module_table in tables
1074
+ if module_table.get("table_id") == "module-source-facts"
1075
+ for record in module_table.get("records", ())
1076
+ if isinstance(record.get("value"), dict)
1077
+ }
1078
+ prohibited_contexts = {
1079
+ "AsyncFor",
1080
+ "AsyncFunctionDef",
1081
+ "AsyncWith",
1082
+ "Await",
1083
+ "DictComp",
1084
+ "FormattedValue",
1085
+ "GeneratorExp",
1086
+ "JoinedStr",
1087
+ "Lambda",
1088
+ "ListComp",
1089
+ "Match",
1090
+ "NamedExpr",
1091
+ "SetComp",
1092
+ "Try",
1093
+ "TryStar",
1094
+ "With",
1095
+ "Yield",
1096
+ "YieldFrom",
1097
+ "comprehension",
1098
+ }
1099
+
1100
+ def approved_context(node_id: str) -> str | None:
1101
+ current = node_id
1102
+ seen: set[str] = set()
1103
+ while current not in seen:
1104
+ seen.add(current)
1105
+ candidates = parents.get(current, ())
1106
+ if not candidates:
1107
+ return None
1108
+ parent_id, field_name = min(
1109
+ candidates,
1110
+ key=lambda item: (ordinals.get(item[0], 2**63 - 1), item[1]),
1111
+ )
1112
+ parent = nodes[parent_id]
1113
+ if parent.get("kind") == "FunctionDef":
1114
+ return parent_id if field_name == "body" and parent_id in module_functions else None
1115
+ if parent.get("kind") in prohibited_contexts or parent.get("kind") == "ClassDef":
1116
+ return None
1117
+ current = parent_id
1118
+ return None
1119
+
1120
+ def signed_literal(node: dict[str, Any]) -> tuple[int, tuple[str, ...], str] | None:
1121
+ if node.get("kind") == "Constant":
1122
+ value = node.get("fields", {}).get("value")
1123
+ if (
1124
+ isinstance(value, int)
1125
+ and not isinstance(value, bool)
1126
+ and 0 <= value <= 2**63 - 1
1127
+ ):
1128
+ return value, (node["node_id"],), "constant"
1129
+ return None
1130
+ if node.get("kind") != "UnaryOp":
1131
+ return None
1132
+ operator = nodes.get(node.get("fields", {}).get("op"), {})
1133
+ operand = nodes.get(node.get("fields", {}).get("operand"), {})
1134
+ value = operand.get("fields", {}).get("value")
1135
+ if (
1136
+ operator.get("kind") not in {"UAdd", "USub"}
1137
+ or operand.get("kind") != "Constant"
1138
+ or not isinstance(value, int)
1139
+ or isinstance(value, bool)
1140
+ or not 0 <= value <= 2**63 - 1
1141
+ ):
1142
+ return None
1143
+ return (
1144
+ -value if operator["kind"] == "USub" else value,
1145
+ (node["node_id"], operator["node_id"], operand["node_id"]),
1146
+ "unary-minus" if operator["kind"] == "USub" else "unary-plus",
1147
+ )
1148
+
1149
+ category_table = next(
1150
+ (item for item in tables if item.get("table_id") == "value-category-facts"),
1151
+ {},
1152
+ )
1153
+ categories = {
1154
+ record.get("key"): record.get("value")
1155
+ for record in category_table.get("records", ())
1156
+ }
1157
+ numeric_nodes: dict[str, tuple[dict[str, Any], dict[str, Any]]] = {}
1158
+ for node in node_values:
1159
+ if node.get("kind") != "BinOp":
1160
+ continue
1161
+ operator = nodes.get(node.get("fields", {}).get("op"), {})
1162
+ if operator.get("kind") in {"FloorDiv", "Mod"}:
1163
+ numeric_nodes[node["node_id"]] = (node, operator)
1164
+
1165
+ supported_calls = {
1166
+ record.get("value", {}).get("call_node_id")
1167
+ for candidate in tables
1168
+ if candidate.get("table_id") == "call-target-facts"
1169
+ for record in candidate.get("records", ())
1170
+ if isinstance(record.get("value"), dict)
1171
+ and record["value"].get("supported") is True
1172
+ }
1173
+ supported_container_accesses = {
1174
+ record.get("value", {}).get("subscript_node_id")
1175
+ for candidate in tables
1176
+ if candidate.get("table_id") == "container-access-facts"
1177
+ for record in candidate.get("records", ())
1178
+ if isinstance(record.get("value"), dict)
1179
+ and record["value"].get("supported") is True
1180
+ }
1181
+ supported_record_accesses = {
1182
+ record.get("value", {}).get("access_node_id")
1183
+ for candidate in tables
1184
+ if candidate.get("table_id") == "record-access-facts"
1185
+ for record in candidate.get("records", ())
1186
+ if isinstance(record.get("value"), dict)
1187
+ }
1188
+ integer_expression_cache: dict[str, bool] = {}
1189
+
1190
+ def approved_integer_expression(
1191
+ node_id: str,
1192
+ active: frozenset[str] = frozenset(),
1193
+ ) -> bool:
1194
+ if node_id in integer_expression_cache:
1195
+ return integer_expression_cache[node_id]
1196
+ if node_id in active or categories.get(node_id) != "integer-like":
1197
+ return False
1198
+ node = nodes[node_id]
1199
+ kind = node.get("kind")
1200
+ fields = node.get("fields", {})
1201
+ next_active = active | {node_id}
1202
+ accepted = False
1203
+ if kind == "Name":
1204
+ accepted = True
1205
+ elif kind == "Constant":
1206
+ value = fields.get("value")
1207
+ accepted = (
1208
+ isinstance(value, int)
1209
+ and not isinstance(value, bool)
1210
+ and 0 <= value <= 2**63 - 1
1211
+ )
1212
+ elif kind == "UnaryOp":
1213
+ operator = nodes.get(fields.get("op"), {})
1214
+ operand_id = fields.get("operand")
1215
+ accepted = (
1216
+ operator.get("kind") in {"UAdd", "USub"}
1217
+ and isinstance(operand_id, str)
1218
+ and operand_id in nodes
1219
+ and approved_integer_expression(operand_id, next_active)
1220
+ )
1221
+ elif kind == "BinOp":
1222
+ operator = nodes.get(fields.get("op"), {})
1223
+ left_id = fields.get("left")
1224
+ right_id = fields.get("right")
1225
+ if operator.get("kind") in {"FloorDiv", "Mod"}:
1226
+ accepted = node_id in numeric_nodes
1227
+ else:
1228
+ accepted = (
1229
+ operator.get("kind") in {"Add", "Sub", "Mult"}
1230
+ and isinstance(left_id, str)
1231
+ and isinstance(right_id, str)
1232
+ and left_id in nodes
1233
+ and right_id in nodes
1234
+ and approved_integer_expression(left_id, next_active)
1235
+ and approved_integer_expression(right_id, next_active)
1236
+ )
1237
+ elif kind == "Call":
1238
+ accepted = node_id in supported_calls
1239
+ elif kind == "Subscript":
1240
+ accepted = node_id in supported_container_accesses
1241
+ elif kind == "Attribute":
1242
+ accepted = node_id in supported_record_accesses
1243
+ integer_expression_cache[node_id] = accepted
1244
+ return accepted
1245
+
1246
+ records = table.get("records")
1247
+ if not isinstance(records, list):
1248
+ return False, "Phase 14 numeric fact table records are malformed"
1249
+ facts: dict[str, dict[str, Any]] = {}
1250
+ provenance_by_node: dict[str, set[str]] = {}
1251
+ for record in records:
1252
+ value = record.get("value")
1253
+ key = record.get("key")
1254
+ provenance = record.get("provenance")
1255
+ source_ids = provenance.get("source_node_ids") if isinstance(provenance, dict) else None
1256
+ if (
1257
+ not isinstance(key, str)
1258
+ or not isinstance(value, dict)
1259
+ or value.get("binop_node_id") != key
1260
+ or not isinstance(source_ids, list)
1261
+ or any(item not in nodes for item in source_ids)
1262
+ ):
1263
+ return False, "Phase 14 numeric fact identity or provenance is invalid"
1264
+ facts[key] = value
1265
+ provenance_by_node[key] = set(source_ids)
1266
+ if set(facts) != set(numeric_nodes):
1267
+ return False, "Phase 14 numeric facts do not exactly cover // and % nodes"
1268
+
1269
+ plans = payload.get("rule_plans", [])
1270
+ support = {
1271
+ item.get("node_id"): item
1272
+ for item in payload.get("support_decisions", [])
1273
+ if isinstance(item, dict)
1274
+ }
1275
+ numeric_helpers = {
1276
+ "pycf.i64.floor_div@1.0.0",
1277
+ "pycf.i64.floor_mod@1.0.0",
1278
+ }
1279
+ numeric_obligations = [
1280
+ "exact-int64-operands-proved",
1281
+ "direct-safe-divisor-literal-proved",
1282
+ "left-before-right-evaluation-once",
1283
+ "helper-preconditions-proved",
1284
+ "python-floor-semantics-preserved",
1285
+ "no-runtime-failure-channel",
1286
+ "scalar-value-ownership-by-value",
1287
+ "allocation-and-cleanup-absent",
1288
+ "source-provenance-anchored",
1289
+ "cancellation-safe-points-honored",
1290
+ "target-contract-exact",
1291
+ "operands-materialized-left-to-right-once",
1292
+ "helper-result-materialized-once",
1293
+ "zero-and-negative-one-divisors-excluded",
1294
+ ]
1295
+ for plan in plans:
1296
+ if plan.get("rule_id") != "phase14.numeric.floor_arithmetic" and numeric_helpers.intersection(
1297
+ plan.get("helper_requirements", ())
1298
+ ):
1299
+ return False, "A non-numeric RulePlan claims a floor-arithmetic helper"
1300
+
1301
+ for node_id, (node, operator) in sorted(numeric_nodes.items()):
1302
+ fact = facts[node_id]
1303
+ left_id = node.get("fields", {}).get("left")
1304
+ right_id = node.get("fields", {}).get("right")
1305
+ function_node_id = approved_context(node_id)
1306
+ if (
1307
+ left_id not in nodes
1308
+ or right_id not in nodes
1309
+ or function_node_id is None
1310
+ or categories.get(left_id) != "integer-like"
1311
+ or categories.get(right_id) != "integer-like"
1312
+ or categories.get(node_id) != "integer-like"
1313
+ or not approved_integer_expression(left_id)
1314
+ ):
1315
+ return False, "Phase 14 numeric fact lacks exact category or context proof"
1316
+ literal = signed_literal(nodes[right_id])
1317
+ if literal is None or literal[0] in {0, -1}:
1318
+ return False, "Phase 14 numeric divisor proof is unsafe"
1319
+ divisor, literal_ids, literal_shape = literal
1320
+ is_division = operator["kind"] == "FloorDiv"
1321
+ operation_kind = "floor-divide" if is_division else "floor-modulo"
1322
+ helper = (
1323
+ "pycf.i64.floor_div@1.0.0"
1324
+ if is_division
1325
+ else "pycf.i64.floor_mod@1.0.0"
1326
+ )
1327
+ operation_id = "numeric-op-" + hashlib.sha256(
1328
+ f"{node_id}\x1f{operation_kind}".encode("utf-8")
1329
+ ).hexdigest()[:20]
1330
+ owner = module_functions.get(function_node_id, {})
1331
+ identity = module_identities.get(owner.get("module_id"), {})
1332
+ source = module_sources.get(owner.get("module_id"), {})
1333
+ if (
1334
+ not isinstance(owner.get("module_id"), str)
1335
+ or not owner.get("module_id")
1336
+ or owner.get("document_id") != identity.get("document_id")
1337
+ or owner.get("document_id") != source.get("document_id")
1338
+ or identity.get("logical_name") != source.get("logical_name")
1339
+ or function_node_id not in identity.get("function_node_ids", ())
1340
+ or function_node_id not in source.get("function_node_ids", ())
1341
+ ):
1342
+ return False, "Phase 14 numeric ownership evidence is inconsistent"
1343
+ expected = {
1344
+ "operation_id": operation_id,
1345
+ "binop_node_id": node_id,
1346
+ "function_node_id": function_node_id,
1347
+ "module_id": owner.get("module_id"),
1348
+ "document_id": owner.get("document_id"),
1349
+ "logical_name": source.get("logical_name"),
1350
+ "operator_node_id": operator["node_id"],
1351
+ "operator_kind": operation_kind,
1352
+ "left_node_id": left_id,
1353
+ "right_node_id": right_id,
1354
+ "left_category": "integer-like",
1355
+ "right_category": "integer-like",
1356
+ "result_category": "integer-like",
1357
+ "left_c_type": "int64_t",
1358
+ "right_c_type": "int64_t",
1359
+ "result_c_type": "int64_t",
1360
+ "divisor_value": divisor,
1361
+ "divisor_literal_node_ids": list(literal_ids),
1362
+ "literal_shape": literal_shape,
1363
+ "divisor_in_admitted_domain": True,
1364
+ "divisor_nonzero_proved": True,
1365
+ "negative_one_divisor_excluded": True,
1366
+ "minimum_signed_divisor_excluded": True,
1367
+ "helper_requirement": helper,
1368
+ "evaluation_order": [left_id, right_id],
1369
+ "operands_evaluated_once": True,
1370
+ "c_type": "int64_t",
1371
+ "failure_policy": "caller-proved-no-runtime-failure-channel",
1372
+ "support_state": "SupportedWithHelper",
1373
+ "parameter_ownership": "scalar-values-by-value",
1374
+ "result_ownership": "scalar-value-by-value",
1375
+ "allocation_model": "none",
1376
+ "cleanup_model": "none",
1377
+ "runtime_failure_channel": "none",
1378
+ "target_contract": "c11-portable-fixed-v1",
1379
+ }
1380
+ if fact != expected:
1381
+ return False, "Phase 14 numeric fact disagrees with independent proof"
1382
+ if not ({node_id, function_node_id, operator["node_id"], left_id, *literal_ids}).issubset(
1383
+ provenance_by_node[node_id]
1384
+ ):
1385
+ return False, "Phase 14 numeric fact provenance is incomplete"
1386
+ node_plans = [item for item in plans if item.get("source_node_id") == node_id]
1387
+ if len(node_plans) != 1:
1388
+ return False, "Phase 14 numeric operation lacks exactly one RulePlan"
1389
+ plan = node_plans[0]
1390
+ decision = support.get(node_id)
1391
+ expected_facts = {
1392
+ f"numeric-operation:{node_id}",
1393
+ f"numeric-divisor:{divisor}",
1394
+ f"numeric-helper:{helper}",
1395
+ "numeric-target:c11-portable-fixed-v1",
1396
+ "value-category:integer-like",
1397
+ }
1398
+ expected_explanation = [
1399
+ "selected",
1400
+ "phase14.numeric.floor_arithmetic",
1401
+ "for",
1402
+ "BinOp",
1403
+ operation_kind,
1404
+ "via",
1405
+ helper,
1406
+ ]
1407
+ if (
1408
+ plan.get("rule_id") != "phase14.numeric.floor_arithmetic"
1409
+ or plan.get("rule_version") != "0.14"
1410
+ or plan.get("support_state") != "SupportedWithHelper"
1411
+ or plan.get("helper_requirements") != [helper]
1412
+ or set(plan.get("facts_used", ())) != expected_facts
1413
+ or plan.get("semantic_obligations") != numeric_obligations
1414
+ or plan.get("resolved_obligations") != numeric_obligations
1415
+ or plan.get("unresolved_obligations") != []
1416
+ or plan.get("explanation_tokens") != expected_explanation
1417
+ or not isinstance(decision, dict)
1418
+ or decision.get("state") != "SupportedWithHelper"
1419
+ or decision.get("rule_plan_id") != plan.get("plan_id")
1420
+ ):
1421
+ return False, "Phase 14 numeric RulePlan does not close its helper proof"
1422
+ return True, ""
1423
+
1424
+
1425
+ def validate_analysis_payload(
1426
+ payload: dict[str, Any],
1427
+ cancellation: Any = None,
1428
+ ) -> tuple[bool, str]:
1429
+ if not isinstance(payload, dict):
1430
+ return False, "analysis payload is malformed"
1431
+ schema = payload.get("schema_version")
1432
+ if schema not in {
1433
+ "conversion-plan/0.5",
1434
+ "conversion-plan/0.9",
1435
+ "conversion-plan/0.11",
1436
+ PHASE12_CONVERSION_PLAN_SCHEMA,
1437
+ PHASE13_CONVERSION_PLAN_SCHEMA,
1438
+ PHASE14A_CONVERSION_PLAN_SCHEMA,
1439
+ PHASE14B_CONVERSION_PLAN_SCHEMA,
1440
+ PHASE14C_CONVERSION_PLAN_SCHEMA,
1441
+ CONVERSION_PLAN_SCHEMA,
1442
+ }:
1443
+ return False, "unsupported conversion-plan schema"
1444
+ tables = payload.get("fact_tables")
1445
+ if not isinstance(tables, list) or any(
1446
+ not isinstance(table, dict) for table in tables
1447
+ ):
1448
+ return False, "fact tables are malformed"
1449
+ table_ids = [table.get("table_id") for table in tables]
1450
+ if any(not isinstance(table_id, str) or not table_id for table_id in table_ids):
1451
+ return False, "fact-table IDs are malformed"
1452
+ if len(table_ids) != len(set(table_ids)):
1453
+ return False, "duplicate fact-table IDs"
1454
+ for table in tables:
1455
+ required = {"schema_version", "table_id", "producer_stage", "key_domain", "completeness", "invalidation_dependencies", "records"}
1456
+ if not required.issubset(table):
1457
+ return False, "fact table omits required contract fields"
1458
+ records = table["records"]
1459
+ if (
1460
+ not all(
1461
+ isinstance(table.get(field), str) and table.get(field)
1462
+ for field in (
1463
+ "schema_version",
1464
+ "producer_stage",
1465
+ "key_domain",
1466
+ "completeness",
1467
+ )
1468
+ )
1469
+ or not isinstance(table.get("invalidation_dependencies"), list)
1470
+ ):
1471
+ return False, "fact-table contract fields are malformed"
1472
+ if not isinstance(records, list) or any(
1473
+ not isinstance(record, dict) for record in records
1474
+ ):
1475
+ return False, "fact-table records are malformed"
1476
+ keys = [record.get("key") for record in records]
1477
+ if any(not isinstance(key, str) or not key for key in keys):
1478
+ return False, "fact record keys are malformed"
1479
+ if any(left >= right for left, right in zip(keys, keys[1:])):
1480
+ return False, f"fact table {table['table_id']} keys are not unique and sorted"
1481
+ if any(
1482
+ not isinstance(record.get("provenance"), dict) for record in records
1483
+ ):
1484
+ return False, "fact record without provenance"
1485
+ plans = payload.get("rule_plans")
1486
+ if not isinstance(plans, list) or any(
1487
+ not isinstance(plan, dict) for plan in plans
1488
+ ):
1489
+ return False, "RulePlans are malformed"
1490
+ plan_ids = [plan.get("plan_id") for plan in plans]
1491
+ if any(not isinstance(plan_id, str) or not plan_id for plan_id in plan_ids):
1492
+ return False, "RulePlan IDs are malformed"
1493
+ if len(plan_ids) != len(set(plan_ids)):
1494
+ return False, "duplicate RulePlan IDs"
1495
+ plan_by_id = {plan["plan_id"]: plan for plan in plans}
1496
+ decisions = payload.get("support_decisions")
1497
+ if not isinstance(decisions, list) or any(
1498
+ not isinstance(item, dict) for item in decisions
1499
+ ):
1500
+ return False, "support decisions are malformed"
1501
+ decision_keys = [item.get("decision_key") for item in decisions]
1502
+ if any(not isinstance(key, str) or not key for key in decision_keys):
1503
+ return False, "support decision keys are malformed"
1504
+ if len(decision_keys) != len(set(decision_keys)):
1505
+ return False, "duplicate support decision keys"
1506
+ for decision in decisions:
1507
+ state = decision.get("state")
1508
+ if not isinstance(state, str):
1509
+ return False, "support decision state is malformed"
1510
+ plan_id = decision.get("rule_plan_id")
1511
+ if plan_id is not None and not isinstance(plan_id, str):
1512
+ return False, "support decision RulePlan identity is malformed"
1513
+ if state.startswith("Supported") and plan_id not in plan_by_id:
1514
+ return False, "supported decision lacks exactly one RulePlan"
1515
+ if not state.startswith("Supported") and plan_id is not None:
1516
+ return False, "unsupported decision references RulePlan"
1517
+ if plan_id is not None:
1518
+ plan = plan_by_id[plan_id]
1519
+ if plan.get("decision_key") != decision.get("decision_key") or plan.get("source_node_id") != decision.get("node_id"):
1520
+ return False, "support decision and RulePlan identity disagree"
1521
+ for plan in plans:
1522
+ unresolved = plan.get("unresolved_obligations")
1523
+ semantic = plan.get("semantic_obligations")
1524
+ resolved = plan.get("resolved_obligations")
1525
+ if not all(
1526
+ isinstance(value, list) for value in (unresolved, semantic, resolved)
1527
+ ) or any(
1528
+ not isinstance(item, str)
1529
+ for value in (unresolved, semantic, resolved)
1530
+ for item in value
1531
+ ):
1532
+ return False, "RulePlan obligations are malformed"
1533
+ if unresolved:
1534
+ return False, "published RulePlan has unresolved obligations"
1535
+ if sorted(semantic) != sorted(resolved):
1536
+ return False, "RulePlan obligations are not closed"
1537
+ plan_helpers = plan.get("helper_requirements", [])
1538
+ if not isinstance(plan_helpers, list) or any(
1539
+ not isinstance(item, str) for item in plan_helpers
1540
+ ):
1541
+ return False, "RulePlan helper requirements are malformed"
1542
+ if any(
1543
+ left >= right
1544
+ for left, right in zip(plan_helpers, plan_helpers[1:])
1545
+ ) or any(
1546
+ not isinstance(item, str) or not _HELPER_REQUIREMENT.fullmatch(item)
1547
+ for item in plan_helpers
1548
+ ):
1549
+ return False, "RulePlan helper requirements are not exact, unique, and sorted"
1550
+ if plan.get("support_state") == "SupportedWithHelper" and not plan_helpers:
1551
+ return False, "helper-backed RulePlan lacks a helper requirement"
1552
+ helper_requirements = payload.get("helper_requirements")
1553
+ if not isinstance(helper_requirements, list) or any(
1554
+ not isinstance(item, str) for item in helper_requirements
1555
+ ):
1556
+ return False, "conversion-plan helper requirements are malformed"
1557
+ expected_helpers = sorted(
1558
+ {
1559
+ item
1560
+ for plan in plans
1561
+ for item in plan.get("helper_requirements", [])
1562
+ }
1563
+ )
1564
+ if helper_requirements != expected_helpers:
1565
+ return False, "conversion-plan helper requirements disagree with RulePlans"
1566
+ generated_names = payload.get("generated_name_plans")
1567
+ if not isinstance(generated_names, list) or any(
1568
+ not isinstance(item, dict)
1569
+ or not isinstance(item.get("generated_name"), str)
1570
+ for item in generated_names
1571
+ ):
1572
+ return False, "generated-name plans are malformed"
1573
+ names = [item["generated_name"] for item in generated_names]
1574
+ if len(names) != len(set(names)):
1575
+ return False, "generated name collision"
1576
+ representation_values = payload.get("representation_plans")
1577
+ if not isinstance(representation_values, list) or any(
1578
+ not isinstance(item, dict)
1579
+ or not isinstance(item.get("decision_key"), str)
1580
+ for item in representation_values
1581
+ ):
1582
+ return False, "representation plans are malformed"
1583
+ representations = {
1584
+ item["decision_key"]: item for item in representation_values
1585
+ }
1586
+ non_value_rules = {
1587
+ "phase8.if.control",
1588
+ "phase8.while.control",
1589
+ "phase8.for.range",
1590
+ "phase8.break.control",
1591
+ "phase8.continue.control",
1592
+ "phase8.range.bound",
1593
+ "phase11.container.for.bounded",
1594
+ "phase12.module.import_from",
1595
+ "phase12.module.document",
1596
+ "phase12.module.imported_binding",
1597
+ "phase12.module.initialization",
1598
+ "phase12.module.bundle_assembly",
1599
+ "phase13.record.class",
1600
+ "phase13.record.field",
1601
+ "phase13.record.initializer",
1602
+ "phase13.record.binding",
1603
+ }
1604
+ for decision in decisions:
1605
+ if decision["state"].startswith("Supported"):
1606
+ representation = representations.get(decision["decision_key"])
1607
+ plan = plan_by_id[decision["rule_plan_id"]]
1608
+ if not representation or (plan.get("rule_id") not in non_value_rules and (representation.get("c_type") is None or representation.get("unresolved_obligations"))):
1609
+ return False, "supported decision lacks a closed representation plan"
1610
+ if schema in {
1611
+ "conversion-plan/0.9",
1612
+ "conversion-plan/0.11",
1613
+ PHASE12_CONVERSION_PLAN_SCHEMA,
1614
+ PHASE13_CONVERSION_PLAN_SCHEMA,
1615
+ PHASE14A_CONVERSION_PLAN_SCHEMA,
1616
+ PHASE14B_CONVERSION_PLAN_SCHEMA,
1617
+ PHASE14C_CONVERSION_PLAN_SCHEMA,
1618
+ CONVERSION_PLAN_SCHEMA,
1619
+ }:
1620
+ required_tables = {"function-signature-facts", "call-target-facts", "return-path-facts", "local-declaration-facts", "call-graph-facts"}
1621
+ if not required_tables.issubset(table_ids):
1622
+ return False, "Phase 9 conversion plan omits required function/call fact tables"
1623
+ for field in ("target_contract", "semantic_policy", "rule_set_version", "renderer_version", "helper_policy_version"):
1624
+ if not isinstance(payload.get(field), str) or not payload[field]:
1625
+ return False, f"Phase 9 conversion plan omits {field}"
1626
+ if schema in {
1627
+ "conversion-plan/0.11",
1628
+ PHASE12_CONVERSION_PLAN_SCHEMA,
1629
+ PHASE13_CONVERSION_PLAN_SCHEMA,
1630
+ PHASE14A_CONVERSION_PLAN_SCHEMA,
1631
+ PHASE14B_CONVERSION_PLAN_SCHEMA,
1632
+ PHASE14C_CONVERSION_PLAN_SCHEMA,
1633
+ CONVERSION_PLAN_SCHEMA,
1634
+ }:
1635
+ required_tables = {
1636
+ "container-shape-facts",
1637
+ "container-binding-facts",
1638
+ "container-access-facts",
1639
+ "container-iteration-facts",
1640
+ }
1641
+ if not required_tables.issubset(table_ids):
1642
+ return False, "Phase 11 conversion plan omits required container fact tables"
1643
+ if not isinstance(payload.get("container_policy_version"), str) or not payload["container_policy_version"]:
1644
+ return False, "Phase 11 conversion plan omits container_policy_version"
1645
+ records = {
1646
+ table["table_id"]: {
1647
+ record["key"]: record["value"]
1648
+ for record in table["records"]
1649
+ }
1650
+ for table in tables
1651
+ if table["table_id"] in required_tables
1652
+ }
1653
+ bindings = records["container-binding-facts"]
1654
+ for access in records["container-access-facts"].values():
1655
+ binding = bindings.get(access.get("binding_id"))
1656
+ if access.get("supported") and binding is None:
1657
+ return False, "supported container access references an absent binding fact"
1658
+ if access.get("supported") and not binding.get("valid"):
1659
+ return False, "supported container access references an invalid binding fact"
1660
+ if access.get("supported") and not isinstance(access.get("resolved_offset"), int):
1661
+ return False, "supported container access lacks a resolved offset"
1662
+ for iteration in records["container-iteration-facts"].values():
1663
+ binding = bindings.get(iteration.get("binding_id"))
1664
+ if iteration.get("supported") and binding is None:
1665
+ return False, "supported container iteration references an absent binding fact"
1666
+ if iteration.get("supported") and not binding.get("valid"):
1667
+ return False, "supported container iteration references an invalid binding fact"
1668
+ if iteration.get("supported") and (
1669
+ not isinstance(iteration.get("capacity"), int)
1670
+ or iteration["capacity"] <= 0
1671
+ ):
1672
+ return False, "supported container iteration lacks a positive fixed bound"
1673
+ if schema in {
1674
+ PHASE13_CONVERSION_PLAN_SCHEMA,
1675
+ PHASE14A_CONVERSION_PLAN_SCHEMA,
1676
+ PHASE14B_CONVERSION_PLAN_SCHEMA,
1677
+ PHASE14C_CONVERSION_PLAN_SCHEMA,
1678
+ CONVERSION_PLAN_SCHEMA,
1679
+ }:
1680
+ valid, reason = _validate_record_fact_tables(payload, tables)
1681
+ if not valid:
1682
+ return valid, reason
1683
+ if schema in {PHASE14A_CONVERSION_PLAN_SCHEMA, PHASE14B_CONVERSION_PLAN_SCHEMA, PHASE14C_CONVERSION_PLAN_SCHEMA, CONVERSION_PLAN_SCHEMA}:
1684
+ valid, reason = _validate_numeric_fact_table(payload, tables)
1685
+ if not valid:
1686
+ return valid, reason
1687
+ if schema in {PHASE14B_CONVERSION_PLAN_SCHEMA, PHASE14C_CONVERSION_PLAN_SCHEMA, CONVERSION_PLAN_SCHEMA}:
1688
+ valid, reason = validate_conditional_region_facts(
1689
+ payload,
1690
+ tables,
1691
+ expected_fact_schema=CONDITIONAL_FACT_SCHEMA,
1692
+ require_table=True,
1693
+ cancellation=cancellation,
1694
+ )
1695
+ if not valid:
1696
+ return valid, reason
1697
+ if schema in {PHASE14C_CONVERSION_PLAN_SCHEMA, CONVERSION_PLAN_SCHEMA}:
1698
+ cumulatively_ineligible_targets = (
1699
+ _cumulatively_ineligible_target_function_node_ids(
1700
+ payload,
1701
+ tables,
1702
+ cancellation,
1703
+ )
1704
+ )
1705
+ valid, reason = validate_keyword_call_binding_facts(
1706
+ payload,
1707
+ tables,
1708
+ expected_fact_schema=KEYWORD_CALL_FACT_SCHEMA,
1709
+ require_table=True,
1710
+ require_plans=True,
1711
+ cumulatively_ineligible_target_function_node_ids=(
1712
+ cumulatively_ineligible_targets
1713
+ ),
1714
+ cancellation=cancellation,
1715
+ )
1716
+ if not valid:
1717
+ return valid, reason
1718
+ if schema == CONVERSION_PLAN_SCHEMA:
1719
+ valid, reason = validate_keyword_only_call_binding_facts(
1720
+ payload,
1721
+ tables,
1722
+ expected_fact_schema=KEYWORD_ONLY_CALL_FACT_SCHEMA,
1723
+ require_table=True,
1724
+ require_plans=True,
1725
+ cumulatively_ineligible_target_function_node_ids=(
1726
+ cumulatively_ineligible_targets
1727
+ ),
1728
+ cancellation=cancellation,
1729
+ )
1730
+ if not valid:
1731
+ return valid, reason
1732
+ if schema in {
1733
+ PHASE12_CONVERSION_PLAN_SCHEMA,
1734
+ PHASE13_CONVERSION_PLAN_SCHEMA,
1735
+ PHASE14A_CONVERSION_PLAN_SCHEMA,
1736
+ PHASE14B_CONVERSION_PLAN_SCHEMA,
1737
+ PHASE14C_CONVERSION_PLAN_SCHEMA,
1738
+ CONVERSION_PLAN_SCHEMA,
1739
+ }:
1740
+ required_module_tables = {
1741
+ "module-identity-facts",
1742
+ "module-import-facts",
1743
+ "module-function-facts",
1744
+ "module-initialization-facts",
1745
+ "module-namespace-facts",
1746
+ "module-source-facts",
1747
+ }
1748
+ if not required_module_tables.issubset(table_ids):
1749
+ return False, "Phase 12 conversion plan omits required module fact tables"
1750
+ expected_module_policy = (
1751
+ DEFAULT_MODULE_POLICY
1752
+ if schema in {
1753
+ PHASE13_CONVERSION_PLAN_SCHEMA,
1754
+ PHASE14A_CONVERSION_PLAN_SCHEMA,
1755
+ PHASE14B_CONVERSION_PLAN_SCHEMA,
1756
+ PHASE14C_CONVERSION_PLAN_SCHEMA,
1757
+ CONVERSION_PLAN_SCHEMA,
1758
+ }
1759
+ else PHASE12_MODULE_POLICY
1760
+ )
1761
+ if payload.get("module_policy_version") != expected_module_policy:
1762
+ return False, "module conversion plan has an unknown module_policy_version"
1763
+ module_tables = {table["table_id"]: table for table in tables if table["table_id"] in required_module_tables}
1764
+ if any(
1765
+ table.get("schema_version") != MODULE_FACT_SCHEMA
1766
+ or table.get("completeness") != "complete"
1767
+ or table.get("producer_stage") != "modules.resolve"
1768
+ for table in module_tables.values()
1769
+ ):
1770
+ return False, "Phase 12 module fact table contract identity is invalid"
1771
+ module_key_fields = {
1772
+ "module-identity-facts": "module_id",
1773
+ "module-import-facts": "import_item_id",
1774
+ "module-function-facts": "function_node_id",
1775
+ "module-initialization-facts": "initialization_node_id",
1776
+ "module-namespace-facts": "module_id",
1777
+ "module-source-facts": "module_id",
1778
+ }
1779
+ for table_id, table in module_tables.items():
1780
+ key_field = module_key_fields[table_id]
1781
+ if any(
1782
+ not isinstance(record.get("value"), dict)
1783
+ or record.get("key") != record["value"].get(key_field)
1784
+ for record in table["records"]
1785
+ ):
1786
+ return False, "Phase 12 module fact record key disagrees with its value identity"
1787
+ values = {
1788
+ table_id: [record["value"] for record in table["records"]]
1789
+ for table_id, table in module_tables.items()
1790
+ }
1791
+ identities = values["module-identity-facts"]
1792
+ module_id_values = [item.get("module_id") for item in identities]
1793
+ module_ids = set(module_id_values)
1794
+ if (
1795
+ not identities
1796
+ or any(not isinstance(item, str) or not item for item in module_id_values)
1797
+ or len(module_ids) != len(identities)
1798
+ ):
1799
+ return False, "Phase 12 module identities are incomplete or duplicated"
1800
+ bundle_ordinal_values = [item.get("bundle_ordinal") for item in identities]
1801
+ if any(
1802
+ not isinstance(item, int) or isinstance(item, bool) or item < 0
1803
+ for item in bundle_ordinal_values
1804
+ ):
1805
+ return False, "Phase 12 bundle ordinals or primary identity are invalid"
1806
+ bundle_ordinals = sorted(bundle_ordinal_values)
1807
+ if (
1808
+ bundle_ordinals != list(range(len(identities)))
1809
+ or any(not isinstance(item.get("is_primary"), bool) for item in identities)
1810
+ or any(
1811
+ item.get("is_primary") is not (item.get("bundle_ordinal") == 0)
1812
+ for item in identities
1813
+ )
1814
+ ):
1815
+ return False, "Phase 12 bundle ordinals or primary identity are invalid"
1816
+ identity_by_module = {item["module_id"]: item for item in identities}
1817
+ primary_module_id = next(
1818
+ item["module_id"] for item in identities if item["is_primary"]
1819
+ )
1820
+
1821
+ module_bundle = payload.get("module_bundle")
1822
+ bundle_documents = (
1823
+ module_bundle.get("documents") if isinstance(module_bundle, dict) else None
1824
+ )
1825
+ if (
1826
+ not isinstance(module_bundle, dict)
1827
+ or module_bundle.get("schema_version") != "python-ir-bundle/0.4"
1828
+ or module_bundle.get("primary_module_id") != primary_module_id
1829
+ or not isinstance(bundle_documents, list)
1830
+ or len(bundle_documents) != len(identities)
1831
+ ):
1832
+ return False, "Phase 12 module identities disagree with the closed source bundle"
1833
+ source_module_order = [item.get("module_id") for item in bundle_documents]
1834
+ expected_source_module_order = [
1835
+ item["module_id"]
1836
+ for item in sorted(identities, key=lambda item: item["bundle_ordinal"])
1837
+ ]
1838
+ if source_module_order != expected_source_module_order:
1839
+ return False, "Phase 12 source-bundle order disagrees with bundle ordinals"
1840
+ bundle_by_module = {
1841
+ item.get("module_id"): item
1842
+ for item in bundle_documents
1843
+ if isinstance(item, dict)
1844
+ }
1845
+ if set(bundle_by_module) != module_ids:
1846
+ return False, "Phase 12 module identities disagree with the closed source bundle"
1847
+
1848
+ original_nodes_by_module: dict[str, dict[str, dict[str, Any]]] = {}
1849
+ original_import_items: dict[str, list[dict[str, Any]]] = {}
1850
+ for module_id in expected_source_module_order:
1851
+ identity = identity_by_module[module_id]
1852
+ document = bundle_by_module[module_id]
1853
+ if any(
1854
+ document.get(field) != identity.get(field)
1855
+ for field in (
1856
+ "document_id",
1857
+ "logical_name",
1858
+ "bundle_ordinal",
1859
+ "is_primary",
1860
+ )
1861
+ ):
1862
+ return False, "Phase 12 module identity fields disagree with the closed source bundle"
1863
+ python_ir = document.get("python_ir")
1864
+ if (
1865
+ not isinstance(python_ir, dict)
1866
+ or python_ir.get("document_id") != identity.get("document_id")
1867
+ or not isinstance(python_ir.get("nodes"), list)
1868
+ ):
1869
+ return False, "Phase 12 module identity lacks its exact source Python IR"
1870
+ node_values = python_ir["nodes"]
1871
+ nodes = {
1872
+ node.get("node_id"): node
1873
+ for node in node_values
1874
+ if isinstance(node, dict) and isinstance(node.get("node_id"), str)
1875
+ }
1876
+ if len(nodes) != len(node_values):
1877
+ return False, "Phase 12 source Python IR has missing or duplicate node identities"
1878
+ root = nodes.get(python_ir.get("root_node_id"))
1879
+ body = root.get("fields", {}).get("body") if root else None
1880
+ if not isinstance(body, list):
1881
+ return False, "Phase 12 source Python IR lacks an ordered module body"
1882
+ import_node_ids = [
1883
+ node_id for node_id in body if nodes.get(node_id, {}).get("kind") == "ImportFrom"
1884
+ ]
1885
+ function_node_ids = [
1886
+ node_id for node_id in body if nodes.get(node_id, {}).get("kind") == "FunctionDef"
1887
+ ]
1888
+ if (
1889
+ not function_node_ids
1890
+ or identity.get("import_node_ids") != import_node_ids
1891
+ or identity.get("function_node_ids") != function_node_ids
1892
+ ):
1893
+ return False, "Phase 12 module ownership lists disagree with source order"
1894
+ original_nodes_by_module[module_id] = nodes
1895
+ import_items: list[dict[str, Any]] = []
1896
+ source_ordinal = 0
1897
+ for import_node_id in import_node_ids:
1898
+ import_node = nodes[import_node_id]
1899
+ target_module_id = import_node.get("fields", {}).get("module")
1900
+ alias_ids = import_node.get("fields", {}).get("names")
1901
+ if not isinstance(target_module_id, str) or not isinstance(alias_ids, list):
1902
+ return False, "Phase 12 source import lacks an exact ordered alias list"
1903
+ for alias_node_id in alias_ids:
1904
+ alias = nodes.get(alias_node_id)
1905
+ if alias is None or alias.get("kind") != "alias":
1906
+ return False, "Phase 12 source import references an absent alias node"
1907
+ imported_name = alias.get("fields", {}).get("name")
1908
+ asname = alias.get("fields", {}).get("asname")
1909
+ if not isinstance(imported_name, str) or not imported_name:
1910
+ return False, "Phase 12 source import has an invalid imported spelling"
1911
+ import_items.append(
1912
+ {
1913
+ "import_node_id": import_node_id,
1914
+ "alias_node_id": alias_node_id,
1915
+ "target_module_id": target_module_id,
1916
+ "imported_name": imported_name,
1917
+ "local_name": asname or imported_name,
1918
+ "source_ordinal": source_ordinal,
1919
+ }
1920
+ )
1921
+ source_ordinal += 1
1922
+ original_import_items[module_id] = import_items
1923
+
1924
+ functions = values["module-function-facts"]
1925
+ function_by_node = {item.get("function_node_id"): item for item in functions}
1926
+ flattened_names = [item.get("flattened_name") for item in functions]
1927
+ significant_external_prefixes = [str(name)[:31] for name in flattened_names]
1928
+ function_ordinal_values = [item.get("bundle_function_ordinal") for item in functions]
1929
+ if any(
1930
+ not isinstance(item, int) or isinstance(item, bool) or item < 0
1931
+ for item in function_ordinal_values
1932
+ ):
1933
+ return False, "Phase 12 module function identities, names, or order are invalid"
1934
+ function_ordinals = sorted(function_ordinal_values)
1935
+ if (
1936
+ not functions
1937
+ or None in function_by_node
1938
+ or len(function_by_node) != len(functions)
1939
+ or None in flattened_names
1940
+ or len(flattened_names) != len(set(flattened_names))
1941
+ or len(significant_external_prefixes) != len(set(significant_external_prefixes))
1942
+ or function_ordinals != list(range(len(functions)))
1943
+ ):
1944
+ return False, "Phase 12 module function identities, names, or order are invalid"
1945
+ multi_document = len(module_ids) > 1
1946
+ for item in functions:
1947
+ identity = identity_by_module.get(item.get("module_id"))
1948
+ source_node = original_nodes_by_module.get(item.get("module_id"), {}).get(
1949
+ item.get("function_node_id")
1950
+ )
1951
+ source_name = (
1952
+ source_node.get("fields", {}).get("name") if source_node else None
1953
+ )
1954
+ expected_name = (
1955
+ _module_function_name(item["module_id"], source_name)
1956
+ if multi_document and identity is not None and isinstance(source_name, str)
1957
+ else source_name
1958
+ )
1959
+ if (
1960
+ identity is None
1961
+ or item.get("document_id") != identity.get("document_id")
1962
+ or source_node is None
1963
+ or source_node.get("kind") != "FunctionDef"
1964
+ or item.get("function_node_id") not in identity.get("function_node_ids", ())
1965
+ or item.get("source_name") != source_name
1966
+ or item.get("flattened_name") != expected_name
1967
+ or item.get("linkage") != "external"
1968
+ or item.get("module_generated_name") is not multi_document
1969
+ ):
1970
+ return False, "Phase 12 module function linkage or namespace contract is invalid"
1971
+
1972
+ imports = values["module-import-facts"]
1973
+ import_edges: set[tuple[str, str]] = set()
1974
+ source_ordinals: dict[str, list[int]] = {}
1975
+ imports_by_module: dict[str, list[dict[str, Any]]] = {
1976
+ module_id: [] for module_id in module_ids
1977
+ }
1978
+ for value in imports:
1979
+ target = function_by_node.get(value.get("target_function_node_id"))
1980
+ importer = value.get("importer_module_id")
1981
+ target_module = value.get("target_module_id")
1982
+ if (
1983
+ value.get("supported") is not True
1984
+ or importer not in module_ids
1985
+ or target_module not in module_ids
1986
+ or importer == target_module
1987
+ or not value.get("import_node_id")
1988
+ or not value.get("alias_node_id")
1989
+ or not value.get("import_item_id")
1990
+ or not value.get("imported_name")
1991
+ or not value.get("local_name")
1992
+ or target is None
1993
+ or target.get("module_id") != target_module
1994
+ or target.get("source_name") != value.get("imported_name")
1995
+ ):
1996
+ return False, "supported module import lacks an exact direct target"
1997
+ ordinal = value.get("source_ordinal")
1998
+ if not isinstance(ordinal, int) or isinstance(ordinal, bool) or ordinal < 0:
1999
+ return False, "supported module import lacks a normalized source ordinal"
2000
+ source_ordinals.setdefault(importer, []).append(ordinal)
2001
+ imports_by_module[importer].append(value)
2002
+ import_edges.add((importer, target_module))
2003
+ if any(sorted(ordinals) != list(range(len(ordinals))) for ordinals in source_ordinals.values()):
2004
+ return False, "Phase 12 import ordinals are not contiguous within the importer"
2005
+ for module_id in module_ids:
2006
+ published = sorted(
2007
+ imports_by_module[module_id], key=lambda item: item["source_ordinal"]
2008
+ )
2009
+ expected = original_import_items[module_id]
2010
+ if len(published) != len(expected):
2011
+ return False, "Phase 12 import facts do not cover every normalized source alias"
2012
+ for value, source_item in zip(published, expected):
2013
+ if any(value.get(field) != source_item[field] for field in source_item):
2014
+ return False, "Phase 12 import fact disagrees with its source alias"
2015
+
2016
+ initialization = module_tables["module-initialization-facts"]
2017
+ if len(initialization["records"]) != 1:
2018
+ return False, "Phase 12 conversion plan lacks one closed initialization-order fact"
2019
+ init_value = initialization["records"][0]["value"]
2020
+ if init_value.get("cycle_policy") != "reject-all-cycles":
2021
+ return False, "Phase 12 initialization fact lacks the closed cycle policy"
2022
+ if init_value.get("runtime_initialization") != "none":
2023
+ return False, "Phase 12 module profile cannot publish runtime initialization"
2024
+ module_order = init_value.get("module_order")
2025
+ if not isinstance(module_order, list) or len(module_order) != len(module_ids) or set(module_order) != module_ids:
2026
+ return False, "Phase 12 initialization order is not a permutation of bundle modules"
2027
+ declared_edges = init_value.get("dependency_edges")
2028
+ if not isinstance(declared_edges, list):
2029
+ return False, "Phase 12 initialization fact omits dependency edges"
2030
+ declared_edge_values = [
2031
+ (item.get("importer_module_id"), item.get("target_module_id"))
2032
+ for item in declared_edges
2033
+ if isinstance(item, dict)
2034
+ ]
2035
+ edge_values = set(declared_edge_values)
2036
+ expected_edge_values = sorted(import_edges)
2037
+ if (
2038
+ len(declared_edge_values) != len(declared_edges)
2039
+ or declared_edge_values != expected_edge_values
2040
+ ):
2041
+ return False, "Phase 12 initialization edges disagree with resolved imports"
2042
+ expected_module_order = _dependency_first_order(module_ids, edge_values)
2043
+ if expected_module_order is None or module_order != expected_module_order:
2044
+ return False, "Phase 12 initialization order violates deterministic dependency ordering"
2045
+
2046
+ expected_function_ids = [
2047
+ function_node_id
2048
+ for module_id in module_order
2049
+ for function_node_id in identity_by_module[module_id]["function_node_ids"]
2050
+ ]
2051
+ published_function_ids = [
2052
+ item["function_node_id"]
2053
+ for item in sorted(functions, key=lambda item: item["bundle_function_ordinal"])
2054
+ ]
2055
+ if published_function_ids != expected_function_ids:
2056
+ return False, "Phase 12 bundle function order disagrees with module and source order"
2057
+
2058
+ sources = values["module-source-facts"]
2059
+ source_by_module = {item.get("module_id"): item for item in sources}
2060
+ namespaces = values["module-namespace-facts"]
2061
+ namespace_by_module = {item.get("module_id"): item for item in namespaces}
2062
+ if (
2063
+ len(source_by_module) != len(sources)
2064
+ or len(namespace_by_module) != len(namespaces)
2065
+ or set(source_by_module) != module_ids
2066
+ or set(namespace_by_module) != module_ids
2067
+ ):
2068
+ return False, "Phase 12 source or namespace coverage is incomplete"
2069
+ for module_id, identity in identity_by_module.items():
2070
+ source = source_by_module[module_id]
2071
+ namespace = namespace_by_module[module_id]
2072
+ module_functions = [function_by_node[node_id] for node_id in identity["function_node_ids"]]
2073
+ module_imports = sorted(
2074
+ imports_by_module[module_id], key=lambda item: item["source_ordinal"]
2075
+ )
2076
+ if (
2077
+ source.get("source_document_id") != identity.get("document_id")
2078
+ or source.get("logical_source_name") != identity.get("logical_name")
2079
+ or source.get("document_id") != identity.get("document_id")
2080
+ or source.get("logical_name") != identity.get("logical_name")
2081
+ or source.get("bundle_ordinal") != identity.get("bundle_ordinal")
2082
+ or source.get("is_primary") is not identity.get("is_primary")
2083
+ or source.get("document_plan_node_id") != identity.get("document_plan_node_id")
2084
+ or source.get("import_node_ids") != identity.get("import_node_ids")
2085
+ or source.get("function_node_ids") != identity.get("function_node_ids")
2086
+ or not isinstance(source.get("content_fingerprint"), str)
2087
+ or not _SHA256.fullmatch(source["content_fingerprint"])
2088
+ or source.get("eligible") is not True
2089
+ or source.get("diagnostic_code") is not None
2090
+ or source.get("reason") is not None
2091
+ ):
2092
+ return False, "Phase 12 source facts disagree with module identity"
2093
+ expected_imported_bindings = [
2094
+ {
2095
+ "local_name": item["local_name"],
2096
+ "target_module_id": item["target_module_id"],
2097
+ "target_function_node_id": item["target_function_node_id"],
2098
+ }
2099
+ for item in module_imports
2100
+ ]
2101
+ if (
2102
+ namespace.get("document_plan_node_id") != identity.get("document_plan_node_id")
2103
+ or namespace.get("import_node_ids") != identity.get("import_node_ids")
2104
+ or namespace.get("function_node_ids") != identity.get("function_node_ids")
2105
+ or namespace.get("local_function_names")
2106
+ != [item["source_name"] for item in module_functions]
2107
+ or namespace.get("imported_bindings") != expected_imported_bindings
2108
+ or namespace.get("generated_function_names")
2109
+ != [item["flattened_name"] for item in module_functions]
2110
+ ):
2111
+ return False, "Phase 12 namespace facts disagree with owned functions or imports"
2112
+
2113
+ flattened_python_ir = payload.get("python_ir")
2114
+ flattened_nodes = (
2115
+ flattened_python_ir.get("nodes")
2116
+ if isinstance(flattened_python_ir, dict)
2117
+ else None
2118
+ )
2119
+ if not isinstance(flattened_nodes, list):
2120
+ return False, "Phase 12 conversion plan lacks flattened Python IR"
2121
+ flattened_node_ids = {
2122
+ item.get("node_id") for item in flattened_nodes if isinstance(item, dict)
2123
+ }
2124
+ if None in flattened_node_ids or len(flattened_node_ids) != len(flattened_nodes):
2125
+ return False, "Phase 12 flattened Python IR has invalid node identities"
2126
+ if any(
2127
+ not record.get("provenance", {}).get("source_node_ids")
2128
+ or any(
2129
+ node_id not in flattened_node_ids
2130
+ for node_id in record["provenance"]["source_node_ids"]
2131
+ )
2132
+ for table in module_tables.values()
2133
+ for record in table["records"]
2134
+ ):
2135
+ return False, "Phase 12 module fact provenance references an absent node"
2136
+
2137
+ resolution = payload.get("module_resolution")
2138
+ expected_renames = [
2139
+ {
2140
+ "function_node_id": function_by_node[node_id]["function_node_id"],
2141
+ "source_name": function_by_node[node_id]["source_name"],
2142
+ "flattened_name": function_by_node[node_id]["flattened_name"],
2143
+ }
2144
+ for node_id in expected_function_ids
2145
+ ]
2146
+ expected_edges = [
2147
+ {"importer_module_id": importer, "target_module_id": target}
2148
+ for importer, target in expected_edge_values
2149
+ ]
2150
+ if (
2151
+ not isinstance(resolution, dict)
2152
+ or resolution.get("schema_version") != "module-resolution/0.12"
2153
+ or resolution.get("primary_module_id") != primary_module_id
2154
+ or resolution.get("source_module_order") != expected_source_module_order
2155
+ or resolution.get("module_order") != module_order
2156
+ or resolution.get("dependency_edges") != expected_edges
2157
+ or resolution.get("import_item_count") != len(imports)
2158
+ or resolution.get("function_renames") != expected_renames
2159
+ or resolution.get("initialization_node_id") != init_value.get("initialization_node_id")
2160
+ or resolution.get("flattened_root_node_id") != flattened_python_ir.get("root_node_id")
2161
+ or resolution.get("bundle_document_id") != flattened_python_ir.get("document_id")
2162
+ or resolution.get("module_bundle_assembly_node_id")
2163
+ != payload.get("module_bundle_assembly_node_id")
2164
+ ):
2165
+ return False, "Phase 12 resolution summary disagrees with closed module facts"
2166
+ return True, ""