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,2685 @@
1
+ """Independent Checkpoint E source-only audits.
2
+
3
+ The fuzz audit drives the public converter and inspects emitted text with the
4
+ independent C-text conformance parser. It deliberately does not invoke a C
5
+ compiler, linker, loader, debugger, or generated program.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import ast
11
+ from collections import Counter
12
+ from collections.abc import Iterable, Mapping
13
+ from dataclasses import dataclass
14
+ import hashlib
15
+ import json
16
+ from pathlib import Path
17
+ import random
18
+ import re
19
+ import tomllib
20
+ from types import MappingProxyType
21
+ from typing import Any
22
+
23
+ from pycforge import (
24
+ ConversionRequest,
25
+ PythonToCConverter,
26
+ ResultStatus,
27
+ SourceBundle,
28
+ SourceDocumentInput,
29
+ )
30
+ from pycforge.converter.c_output import validate_c_text
31
+ from pycforge.converter.contracts.configuration import (
32
+ DEFAULT_CONTAINER_POLICY,
33
+ DEFAULT_HELPER_POLICY,
34
+ DEFAULT_MODULE_POLICY,
35
+ DEFAULT_NUMERIC_POLICY,
36
+ DEFAULT_RECORD_POLICY,
37
+ DEFAULT_RENDERER,
38
+ DEFAULT_RULE_SET,
39
+ DEFAULT_SEMANTIC_POLICY,
40
+ DEFAULT_TARGET_CONTRACT,
41
+ PHASE9_RULE_SET,
42
+ )
43
+ from pycforge.converter.contracts.versions import (
44
+ C_IR_SCHEMA,
45
+ CONDITIONAL_FACT_SCHEMA,
46
+ CONTAINER_FACT_SCHEMA,
47
+ CONVERSION_PLAN_SCHEMA,
48
+ CONVERSION_SUMMARY_SCHEMA,
49
+ CONVERTER_CONTRACT_VERSION,
50
+ DECISION_TRACE_SCHEMA,
51
+ GENERATED_C_SCHEMA,
52
+ KEYWORD_CALL_FACT_SCHEMA,
53
+ KEYWORD_ONLY_CALL_FACT_SCHEMA,
54
+ MODULE_FACT_SCHEMA,
55
+ NUMERIC_FACT_SCHEMA,
56
+ PYTHON_IR_BUNDLE_SCHEMA,
57
+ RECORD_FACT_SCHEMA,
58
+ RESULT_SCHEMA_VERSION,
59
+ SOURCE_BUNDLE_SCHEMA,
60
+ )
61
+ from pycforge.converter.core.request import ObservationOptions
62
+ from pycforge.converter.core.serialization import result_to_json
63
+ from pycforge.converter.keyword_calls import KEYWORD_CALL_RULE_ID
64
+ from pycforge.converter.keyword_only_calls import KEYWORD_ONLY_CALL_RULE_ID
65
+
66
+
67
+ FUZZ_SEED = 0x14E2026
68
+ DEFAULT_FUZZ_CASE_COUNT = 64
69
+ FEATURE_MATRIX_SCHEMA = "pycforge.feature-matrix/0.14.3"
70
+ FEATURE_MATRIX_ENTRY_COUNT = 69
71
+ FEATURE_MATRIX_SHA256 = (
72
+ "ca78dff3ea203130781f5e0fde879c0ca9d7b7a0e550a05ab5d46ea3432cc01a"
73
+ )
74
+
75
+ EXPECTED_CONTRACT_IDENTITIES: dict[str, str] = {
76
+ "converter_contract": "0.14.3",
77
+ "source_bundle": "source-bundle/0.2",
78
+ "python_ir_bundle": "python-ir/0.4",
79
+ "container_facts": "fact-table/0.11",
80
+ "module_facts": "fact-table/0.12",
81
+ "record_facts": "fact-table/0.13",
82
+ "numeric_facts": "fact-table/0.14",
83
+ "conditional_facts": "fact-table/0.14.1",
84
+ "keyword_call_facts": "fact-table/0.14.2",
85
+ "keyword_only_call_facts": "fact-table/0.14.3",
86
+ "conversion_plan": "conversion-plan/0.14.3",
87
+ "c_ir": "c-ir/0.14.3",
88
+ "generated_c": "generated-c/0.14.3",
89
+ "conversion_summary": "pycforge.conversion-summary/0.14.3",
90
+ "decision_trace": "pycforge.decision-trace/0.14.3",
91
+ "result": "0.5",
92
+ "rule_set": "phase14-required-keyword-only-calls-v0.14.3",
93
+ "renderer": "c-renderer-v0.14.3",
94
+ "keyword_call_rule": "phase14.keyword_call.exact_binding",
95
+ "keyword_only_call_rule": "phase14.keyword_only_call.exact_binding",
96
+ "target_contract": "c11-portable-fixed-v1",
97
+ "semantic_policy": "strict-source-v1",
98
+ "helper_policy": "phase10-support-templates-v0.10",
99
+ "container_policy": "phase11-fixed-local-containers-v0.11",
100
+ "module_policy": "phase13-explicit-record-modules-v0.13",
101
+ "record_policy": "phase13-immutable-automatic-records-v0.13",
102
+ "numeric_policy": "phase14-proved-floor-arithmetic-v0.14",
103
+ }
104
+
105
+ SUPPORTED_SUBSET_FAMILIES = frozenset(
106
+ {
107
+ "literals",
108
+ "assignments-and-arithmetic",
109
+ "functions-and-positional-calls",
110
+ "if-else",
111
+ "while-break-continue",
112
+ "bounded-range-for",
113
+ "list",
114
+ "tuple",
115
+ "dict",
116
+ "explicit-module-bundle",
117
+ "static-record",
118
+ "bounded-floor-arithmetic",
119
+ "boolean-conditional-region",
120
+ "comparison-conditional-region",
121
+ "direct-keyword-call",
122
+ "required-keyword-only-call",
123
+ }
124
+ )
125
+
126
+
127
+ @dataclass(frozen=True, slots=True)
128
+ class SupportedSubsetCase:
129
+ """One deterministic public-converter witness."""
130
+
131
+ case_id: str
132
+ family: str
133
+ primary_text: str
134
+ primary_name: str = "main.py"
135
+ primary_module: str = "main"
136
+ companions: tuple[tuple[str, str, str], ...] = ()
137
+
138
+ def request(self) -> ConversionRequest:
139
+ return ConversionRequest(
140
+ SourceBundle(
141
+ SourceDocumentInput(
142
+ self.primary_name,
143
+ self.primary_text,
144
+ self.primary_module,
145
+ ),
146
+ tuple(
147
+ SourceDocumentInput(logical_name, text, module_id)
148
+ for module_id, logical_name, text in self.companions
149
+ ),
150
+ )
151
+ )
152
+
153
+ def manifest(self) -> dict[str, object]:
154
+ return {
155
+ "case_id": self.case_id,
156
+ "family": self.family,
157
+ "primary": {
158
+ "logical_name": self.primary_name,
159
+ "module_id": self.primary_module,
160
+ "text": self.primary_text,
161
+ },
162
+ "companions": [
163
+ {
164
+ "module_id": module_id,
165
+ "logical_name": logical_name,
166
+ "text": text,
167
+ }
168
+ for module_id, logical_name, text in self.companions
169
+ ],
170
+ }
171
+
172
+
173
+ @dataclass(frozen=True, slots=True)
174
+ class FeatureMatrixExecutionProfile:
175
+ """One explicit public-request profile for a matrix witness."""
176
+
177
+ profile_id: str
178
+ request_options: tuple[tuple[str, object], ...] = ()
179
+ expected_diagnostic: str | None = None
180
+ reason_contains: str | None = None
181
+
182
+ def options(self) -> dict[str, object]:
183
+ return dict(self.request_options)
184
+
185
+
186
+ @dataclass(frozen=True, slots=True)
187
+ class FeatureMatrixWitness:
188
+ """Executable evidence for one exact frozen feature-matrix row."""
189
+
190
+ witness_id: str
191
+ construct: str
192
+ context: str
193
+ state: str
194
+ source: str
195
+ exercise: str
196
+ diagnostic: str | None = None
197
+ actual_diagnostic: str | None = None
198
+ actual_reason_contains: str | None = None
199
+ required_ast_kinds: tuple[str, ...] = ()
200
+ logical_name: str = "main.py"
201
+ module_id: str = "main"
202
+ companions: tuple[tuple[str, str, str], ...] = ()
203
+ request_options: tuple[tuple[str, object], ...] = ()
204
+ precedence_profiles: tuple[FeatureMatrixExecutionProfile, ...] = ()
205
+
206
+ @property
207
+ def key(self) -> tuple[str, str]:
208
+ return self.construct, self.context
209
+
210
+ @property
211
+ def expected_diagnostic(self) -> str | None:
212
+ return self.actual_diagnostic or self.diagnostic
213
+
214
+ def request(
215
+ self,
216
+ options: Mapping[str, object] | None = None,
217
+ ) -> ConversionRequest:
218
+ request_options = (
219
+ dict(self.request_options) if options is None else dict(options)
220
+ )
221
+ return ConversionRequest(
222
+ SourceBundle(
223
+ SourceDocumentInput(
224
+ self.logical_name,
225
+ self.source,
226
+ self.module_id,
227
+ ),
228
+ tuple(
229
+ SourceDocumentInput(logical_name, text, module_id)
230
+ for module_id, logical_name, text in self.companions
231
+ ),
232
+ ),
233
+ **request_options,
234
+ )
235
+
236
+ def manifest(self) -> dict[str, object]:
237
+ return {
238
+ "witness_id": self.witness_id,
239
+ "construct": self.construct,
240
+ "context": self.context,
241
+ "state": self.state,
242
+ "diagnostic": self.diagnostic,
243
+ "actual_diagnostic": self.expected_diagnostic,
244
+ "actual_reason_contains": self.actual_reason_contains,
245
+ "exercise": self.exercise,
246
+ "required_ast_kinds": list(self.required_ast_kinds),
247
+ "request_options": [
248
+ [key, value] for key, value in self.request_options
249
+ ],
250
+ "primary": {
251
+ "logical_name": self.logical_name,
252
+ "module_id": self.module_id,
253
+ "source_sha256": hashlib.sha256(
254
+ self.source.encode("utf-8")
255
+ ).hexdigest(),
256
+ },
257
+ "companions": [
258
+ {
259
+ "module_id": module_id,
260
+ "logical_name": logical_name,
261
+ "source_sha256": hashlib.sha256(
262
+ text.encode("utf-8")
263
+ ).hexdigest(),
264
+ }
265
+ for module_id, logical_name, text in self.companions
266
+ ],
267
+ "precedence_profiles": [
268
+ {
269
+ "profile_id": profile.profile_id,
270
+ "request_options": [
271
+ [key, value] for key, value in profile.request_options
272
+ ],
273
+ "expected_diagnostic": profile.expected_diagnostic,
274
+ "reason_contains": profile.reason_contains,
275
+ }
276
+ for profile in self.precedence_profiles
277
+ ],
278
+ }
279
+
280
+
281
+ _SIMPLE_FUNCTION = (
282
+ "def identity(value: int, /) -> int:\n"
283
+ " result = value\n"
284
+ " return result\n"
285
+ )
286
+ _POSITIONAL_CALL = (
287
+ "def add(left: int, right: int) -> int:\n"
288
+ " return left + right\n\n"
289
+ "def run(value: int) -> int:\n"
290
+ " return add(value, 3)\n"
291
+ )
292
+ _RANGE_LOOP = (
293
+ "def total() -> int:\n"
294
+ " value = 0\n"
295
+ " for item in range(1, 6, 2):\n"
296
+ " value = value + item\n"
297
+ " return value\n"
298
+ )
299
+ _KEYWORD_CALL = (
300
+ "def choose(left: int, middle: int, flag: bool) -> int:\n"
301
+ " if flag:\n"
302
+ " return left\n"
303
+ " return middle\n\n"
304
+ "def run(value: int, flag: bool) -> int:\n"
305
+ " return choose(value, flag=flag, middle=2)\n"
306
+ )
307
+ _KEYWORD_ONLY_CALL = (
308
+ "def choose(left: int, /, middle: int, *, scale: int) -> int:\n"
309
+ " return (left + middle) * scale\n\n"
310
+ "def run() -> int:\n"
311
+ " return choose(1, middle=2, scale=3)\n"
312
+ )
313
+ _LITERALS = (
314
+ "def integer_value() -> int:\n"
315
+ " return 7\n\n"
316
+ "def real_value() -> float:\n"
317
+ " return 1.5\n\n"
318
+ "def flag_value() -> bool:\n"
319
+ " return True\n\n"
320
+ "def text_value() -> str:\n"
321
+ ' return "PyCForge"\n'
322
+ )
323
+ _FLOATING_ARITHMETIC = (
324
+ "def arithmetic(left: float, right: float) -> float:\n"
325
+ " value = left + right\n"
326
+ " value = value - 1.0\n"
327
+ " value = value * 2.0\n"
328
+ " return value / 3.0\n"
329
+ )
330
+ _FLOOR_DIV = (
331
+ "def floor_value(value: int) -> int:\n"
332
+ " return value // -3\n"
333
+ )
334
+ _FLOOR_MOD = (
335
+ "def mod_value(value: int) -> int:\n"
336
+ " return value % 5\n"
337
+ )
338
+ _BOOL_DIRECT = (
339
+ "def both(left: bool, right: bool) -> bool:\n"
340
+ " return left and right\n"
341
+ )
342
+ _BOOL_GUARDED = (
343
+ "def flag(value: bool) -> bool:\n"
344
+ " return value\n\n"
345
+ "def decide(left: bool, right: bool) -> bool:\n"
346
+ " return left and flag(right)\n"
347
+ )
348
+ _COMPARE_DIRECT = (
349
+ "def compare(left: int, right: int) -> bool:\n"
350
+ " return left <= right\n"
351
+ )
352
+ _COMPARE_GUARDED = (
353
+ "def identity(value: int) -> int:\n"
354
+ " return value\n\n"
355
+ "def compare(left: int, middle: int, right: int) -> bool:\n"
356
+ " return left < middle < identity(right)\n"
357
+ )
358
+ _IF_STATEMENT = (
359
+ "def choose(value: int, flag: bool) -> int:\n"
360
+ " if flag:\n"
361
+ " value = value + 1\n"
362
+ " else:\n"
363
+ " value = value - 1\n"
364
+ " return value\n"
365
+ )
366
+ _WHILE_STATEMENT = (
367
+ "def scan(limit: int) -> int:\n"
368
+ " value = 0\n"
369
+ " while value < limit:\n"
370
+ " value = value + 1\n"
371
+ " if value == 2:\n"
372
+ " continue\n"
373
+ " if value == 5:\n"
374
+ " break\n"
375
+ " return value\n"
376
+ )
377
+ _LIST_VALUE = (
378
+ "def pick() -> int:\n"
379
+ " values = [1, 2, 3]\n"
380
+ " return values[-1]\n"
381
+ )
382
+ _TUPLE_VALUE = (
383
+ "def pick() -> str:\n"
384
+ ' values = ("a", "b")\n'
385
+ " return values[0]\n"
386
+ )
387
+ _DICT_VALUE = (
388
+ "def pick() -> int:\n"
389
+ ' values = {"a": 1, "b": 2}\n'
390
+ ' return values["b"]\n'
391
+ )
392
+ _CONTAINER_ITERATION = (
393
+ "def last() -> int:\n"
394
+ " values = [2, 4, 6]\n"
395
+ " result = 0\n"
396
+ " for item in values:\n"
397
+ " result = item\n"
398
+ " return result\n"
399
+ )
400
+ _MODULE_PRIMARY = (
401
+ "from lib import increment as step\n\n"
402
+ "class Point:\n"
403
+ " x: int\n"
404
+ " def __init__(self, x: int) -> None:\n"
405
+ " self.x = x\n\n"
406
+ "def run(value: int) -> int:\n"
407
+ " point = Point(step(value))\n"
408
+ " return point.x\n"
409
+ )
410
+ _MODULE_COMPANIONS = (
411
+ (
412
+ "lib",
413
+ "lib.py",
414
+ "def increment(value: int) -> int:\n"
415
+ " return value + 1\n",
416
+ ),
417
+ )
418
+ _RECORD = (
419
+ "class Sample:\n"
420
+ " count: int\n"
421
+ " ratio: float\n"
422
+ " enabled: bool\n"
423
+ " def __init__(self, count: int, ratio: float, enabled: bool) -> None:\n"
424
+ " self.count = count\n"
425
+ " self.ratio = ratio\n"
426
+ " self.enabled = enabled\n\n"
427
+ "def read() -> int:\n"
428
+ " sample = Sample(7, 1.5, True)\n"
429
+ " return sample.count\n"
430
+ )
431
+ _POINT_RECORD = (
432
+ "class Point:\n"
433
+ " x: int\n"
434
+ " def __init__(self, x: int) -> None:\n"
435
+ " self.x = x\n"
436
+ )
437
+
438
+
439
+ def _feature_matrix_witness_rows() -> tuple[FeatureMatrixWitness, ...]:
440
+ """Return the ordered executable closure of the frozen 69-row matrix."""
441
+
442
+ def witness(
443
+ ordinal: int,
444
+ construct: str,
445
+ context: str,
446
+ state: str,
447
+ source: str,
448
+ exercise: str,
449
+ *,
450
+ diagnostic: str | None = None,
451
+ actual_diagnostic: str | None = None,
452
+ actual_reason_contains: str | None = None,
453
+ ast_kinds: tuple[str, ...] = (),
454
+ logical_name: str = "main.py",
455
+ module_id: str = "main",
456
+ companions: tuple[tuple[str, str, str], ...] = (),
457
+ request_options: tuple[tuple[str, object], ...] = (),
458
+ precedence_profiles: tuple[FeatureMatrixExecutionProfile, ...] = (),
459
+ ) -> FeatureMatrixWitness:
460
+ return FeatureMatrixWitness(
461
+ f"feature-matrix-{ordinal:02d}",
462
+ construct,
463
+ context,
464
+ state,
465
+ source,
466
+ exercise,
467
+ diagnostic,
468
+ actual_diagnostic,
469
+ actual_reason_contains,
470
+ ast_kinds,
471
+ logical_name,
472
+ module_id,
473
+ companions,
474
+ request_options,
475
+ precedence_profiles,
476
+ )
477
+
478
+ return (
479
+ witness(
480
+ 0,
481
+ "Module",
482
+ "explicit-1-to-64-document-bundle/import-preamble-then-records-then-top-level-functions",
483
+ "supported",
484
+ _MODULE_PRIMARY,
485
+ "A two-document explicit bundle places an absolute import first, a static record second, and an annotated top-level function last.",
486
+ ast_kinds=("Module", "ImportFrom", "ClassDef", "FunctionDef"),
487
+ logical_name="app.py",
488
+ module_id="app",
489
+ companions=_MODULE_COMPANIONS,
490
+ ),
491
+ witness(
492
+ 1,
493
+ "FunctionDef",
494
+ "top-level/exact-annotated-signature",
495
+ "supported",
496
+ _SIMPLE_FUNCTION,
497
+ "The module contains one top-level function whose parameter and return are exactly annotated.",
498
+ ast_kinds=("FunctionDef",),
499
+ ),
500
+ witness(
501
+ 2,
502
+ "FunctionDef",
503
+ "nested-or-closure",
504
+ "unsupported",
505
+ (
506
+ "def outer(value: int) -> int:\n"
507
+ " def nested(other: int) -> int:\n"
508
+ " return value + other\n"
509
+ " return nested(value)\n"
510
+ ),
511
+ "The inner FunctionDef closes over the outer parameter.",
512
+ diagnostic="PYC2915",
513
+ ast_kinds=("FunctionDef",),
514
+ ),
515
+ witness(
516
+ 3,
517
+ "arguments",
518
+ "positional-or-positional-only/no-default",
519
+ "supported",
520
+ _SIMPLE_FUNCTION,
521
+ "The identity signature has one positional-only parameter and no default.",
522
+ ast_kinds=("arguments", "arg"),
523
+ ),
524
+ witness(
525
+ 4,
526
+ "arguments",
527
+ "required-keyword-only/exact-annotations/no-defaults-or-variadics",
528
+ "supported",
529
+ _KEYWORD_ONLY_CALL,
530
+ "The choose signature includes an exactly annotated required keyword-only scale parameter with no default or variadic.",
531
+ ast_kinds=("arguments", "arg"),
532
+ ),
533
+ witness(
534
+ 5,
535
+ "arguments",
536
+ "positional-default-keyword-only-default-variadic-or-keyword-only-outside-required-profile",
537
+ "unsupported",
538
+ (
539
+ "def choose(value: int, *, flag: bool = True) -> int:\n"
540
+ " return value\n\n"
541
+ "def run(value: int, flag: bool) -> int:\n"
542
+ " return choose(value, flag=flag)\n"
543
+ ),
544
+ "The keyword-only flag formal has a default, placing the signature outside the required-only profile.",
545
+ diagnostic="PYC2911",
546
+ ast_kinds=("arguments",),
547
+ ),
548
+ witness(
549
+ 6,
550
+ "Call",
551
+ "direct-understood-source-function/exact-positional-signature",
552
+ "supported",
553
+ _POSITIONAL_CALL,
554
+ "run directly calls the understood add definition with two exact positional arguments.",
555
+ ast_kinds=("Call",),
556
+ ),
557
+ witness(
558
+ 7,
559
+ "Call",
560
+ "unshadowed-range-for-iterator",
561
+ "supported",
562
+ _RANGE_LOOP,
563
+ "The For iterator is a direct call to the unshadowed built-in range.",
564
+ ast_kinds=("Call", "For"),
565
+ ),
566
+ witness(
567
+ 8,
568
+ "Call",
569
+ "unknown-indirect-aliased-dynamic",
570
+ "unsupported",
571
+ (
572
+ "def run(target: int, value: int) -> int:\n"
573
+ " return target(value)\n"
574
+ ),
575
+ "The call target is a parameter value rather than a direct understood source function.",
576
+ diagnostic="PYC2901",
577
+ ast_kinds=("Call",),
578
+ ),
579
+ witness(
580
+ 9,
581
+ "Call",
582
+ "direct-understood-source-function/leading-positional-then-explicit-keywords/exact-required-parameter-coverage",
583
+ "supported",
584
+ _KEYWORD_CALL,
585
+ "run binds choose with one leading positional argument followed by explicit keywords that cover every remaining required parameter.",
586
+ ast_kinds=("Call", "keyword"),
587
+ ),
588
+ witness(
589
+ 10,
590
+ "Call",
591
+ "direct-understood-source-function/required-keyword-only-formals/exact-positional-and-named-coverage",
592
+ "supported",
593
+ _KEYWORD_ONLY_CALL,
594
+ "run calls choose with its positional-only, positional-or-keyword, and required keyword-only formals covered exactly.",
595
+ ast_kinds=("Call", "keyword"),
596
+ ),
597
+ witness(
598
+ 11,
599
+ "Call",
600
+ "missing-required-keyword-only-or-positional-overflow-into-keyword-only-range",
601
+ "unsupported",
602
+ (
603
+ "def sink(value: int, *, flag: bool) -> int:\n"
604
+ " return value\n\n"
605
+ "def run(value: int) -> int:\n"
606
+ " return sink(value)\n"
607
+ ),
608
+ "The direct sink call omits its required keyword-only flag.",
609
+ diagnostic="PYC2904",
610
+ ast_kinds=("Call",),
611
+ ),
612
+ witness(
613
+ 12,
614
+ "Call",
615
+ "starred-or-unpacked-keywords",
616
+ "unsupported",
617
+ (
618
+ "def sink(value: int, *, flag: bool) -> int:\n"
619
+ " return value\n\n"
620
+ "def run(value: int, flag: bool) -> int:\n"
621
+ " return sink(*value, flag=flag)\n"
622
+ ),
623
+ "The sink call contains a starred positional argument.",
624
+ diagnostic="PYC2910",
625
+ ast_kinds=("Call", "Starred"),
626
+ ),
627
+ witness(
628
+ 13,
629
+ "Call",
630
+ "unknown-duplicate-colliding-or-positional-only-keyword-name",
631
+ "unsupported",
632
+ (
633
+ "def sink(value: int, /, *, flag: bool) -> int:\n"
634
+ " return value\n\n"
635
+ "def run(value: int, flag: bool) -> int:\n"
636
+ " return sink(value=value, flag=flag)\n"
637
+ ),
638
+ "The call illegally names the positional-only value formal as a keyword.",
639
+ diagnostic="PYC2912",
640
+ ast_kinds=("Call", "keyword"),
641
+ ),
642
+ witness(
643
+ 14,
644
+ "Return",
645
+ "explicit-compatible-all-paths",
646
+ "supported",
647
+ _IF_STATEMENT,
648
+ "choose has an explicit compatible return after its exhaustive conditional assignment.",
649
+ ast_kinds=("Return",),
650
+ ),
651
+ witness(
652
+ 15,
653
+ "Return",
654
+ "mismatch",
655
+ "unsupported",
656
+ "def run() -> int:\n return True\n",
657
+ "The explicit Boolean return conflicts with the annotated integer representation.",
658
+ diagnostic="PYC2930",
659
+ ast_kinds=("Return",),
660
+ ),
661
+ witness(
662
+ 16,
663
+ "FunctionFallthrough",
664
+ "annotated-non-none",
665
+ "unsupported",
666
+ (
667
+ "def run(flag: bool) -> int:\n"
668
+ " if flag:\n"
669
+ " return 1\n"
670
+ ),
671
+ "The annotated non-None function has a reachable implicit-None fallthrough path.",
672
+ diagnostic="PYC2931",
673
+ ast_kinds=("FunctionDef", "If"),
674
+ ),
675
+ witness(
676
+ 17,
677
+ "Assign",
678
+ "single-local-name/stable-representation",
679
+ "supported",
680
+ _SIMPLE_FUNCTION,
681
+ "result is bound once as a direct function-local integer-like name.",
682
+ ast_kinds=("Assign", "Name"),
683
+ ),
684
+ witness(
685
+ 18,
686
+ "Assign",
687
+ "representation-conflict",
688
+ "unsupported",
689
+ (
690
+ "def run() -> int:\n"
691
+ " value = 1\n"
692
+ " value = True\n"
693
+ " return 0\n"
694
+ ),
695
+ "The same local is assigned integer-like and Boolean-like representations.",
696
+ diagnostic="PYC2943",
697
+ ast_kinds=("Assign",),
698
+ ),
699
+ witness(
700
+ 19,
701
+ "Name",
702
+ "resolved-parameter-local-or-function",
703
+ "supported",
704
+ _POSITIONAL_CALL,
705
+ "The witness resolves parameter names, the local function binding add, and their return use.",
706
+ ast_kinds=("Name",),
707
+ ),
708
+ witness(
709
+ 20,
710
+ "Name",
711
+ "local-use-before-binding",
712
+ "unsupported",
713
+ (
714
+ "def run(value: int) -> int:\n"
715
+ " result = result + value\n"
716
+ " return result\n"
717
+ ),
718
+ "result is read on the right-hand side before its first local binding.",
719
+ diagnostic="PYC2940",
720
+ ast_kinds=("Name",),
721
+ ),
722
+ witness(
723
+ 21,
724
+ "Constant",
725
+ "bounded-int-finite-float-bool-utf8-string",
726
+ "supported",
727
+ _LITERALS,
728
+ "Four functions exercise bounded int, finite float, bool, and UTF-8 string constants.",
729
+ ast_kinds=("Constant",),
730
+ ),
731
+ witness(
732
+ 22,
733
+ "BinOp",
734
+ "selected-stable-numeric-plus-minus-multiply-and-floating-divide",
735
+ "supported",
736
+ _FLOATING_ARITHMETIC,
737
+ "The floating-like function exercises plus, minus, multiply, and true division with stable representations.",
738
+ ast_kinds=("BinOp", "Div", "Add", "Sub", "Mult"),
739
+ ),
740
+ witness(
741
+ 23,
742
+ "BinOp.FloorDiv",
743
+ "integer-like-left/direct-safe-signed-int64-literal-divisor",
744
+ "supported",
745
+ _FLOOR_DIV,
746
+ "The integer-like left operand uses a direct safe signed int64 literal divisor of -3.",
747
+ ast_kinds=("BinOp", "FloorDiv"),
748
+ ),
749
+ witness(
750
+ 24,
751
+ "BinOp.Mod",
752
+ "integer-like-left/direct-safe-signed-int64-literal-divisor",
753
+ "supported",
754
+ _FLOOR_MOD,
755
+ "The integer-like left operand uses a direct safe signed int64 literal divisor of 5.",
756
+ ast_kinds=("BinOp", "Mod"),
757
+ ),
758
+ witness(
759
+ 25,
760
+ "BinOp.FloorDivOrMod",
761
+ "noninteger-or-unsupported-expression-context",
762
+ "unsupported",
763
+ "def run(value: float) -> float:\n return value // 2\n",
764
+ "Floor division is applied to a floating-like left operand.",
765
+ diagnostic="PYC3701",
766
+ ast_kinds=("BinOp", "FloorDiv"),
767
+ ),
768
+ witness(
769
+ 26,
770
+ "BinOp.FloorDivOrMod",
771
+ "zero-negative-one-INT64_MIN-dynamic-calculated-or-out-of-range-divisor",
772
+ "unsupported",
773
+ (
774
+ "def run(value: int, divisor: int) -> int:\n"
775
+ " return value // divisor\n"
776
+ ),
777
+ "The divisor is dynamic rather than a directly proved safe signed int64 literal.",
778
+ diagnostic="PYC3702",
779
+ ast_kinds=("BinOp", "FloorDiv"),
780
+ ),
781
+ witness(
782
+ 27,
783
+ "BoolOp",
784
+ "boolean-represented/direct-expression-form",
785
+ "supported",
786
+ _BOOL_DIRECT,
787
+ "A direct and-expression combines two already Boolean-represented parameters.",
788
+ ast_kinds=("BoolOp", "And"),
789
+ ),
790
+ witness(
791
+ 28,
792
+ "BoolOp",
793
+ "boolean-represented/already-supported-scalar-prerequisites/flat-guarded-placement",
794
+ "supported",
795
+ _BOOL_GUARDED,
796
+ "The later Boolean operand is an understood scalar function call placed in one flat guarded region.",
797
+ ast_kinds=("BoolOp", "Call"),
798
+ ),
799
+ witness(
800
+ 29,
801
+ "Compare",
802
+ "numeric-or-boolean/direct-sequence-preservable",
803
+ "supported",
804
+ _COMPARE_DIRECT,
805
+ "A direct numeric comparison has a trivially preservable operand sequence.",
806
+ ast_kinds=("Compare",),
807
+ ),
808
+ witness(
809
+ 30,
810
+ "Compare",
811
+ "chained-numeric-or-boolean/already-supported-later-scalar-prerequisites/flat-guarded-placement",
812
+ "supported",
813
+ _COMPARE_GUARDED,
814
+ "The chained numeric comparison places an understood later scalar call in a flat guarded region.",
815
+ ast_kinds=("Compare", "Call"),
816
+ ),
817
+ witness(
818
+ 31,
819
+ "If",
820
+ "supported-truthiness",
821
+ "supported",
822
+ _IF_STATEMENT,
823
+ "The If condition is an exactly Boolean-represented parameter.",
824
+ ast_kinds=("If",),
825
+ ),
826
+ witness(
827
+ 32,
828
+ "While",
829
+ "supported-truthiness/no-else",
830
+ "supported",
831
+ _WHILE_STATEMENT,
832
+ "The While condition is a supported numeric comparison and the loop has no else.",
833
+ ast_kinds=("While",),
834
+ ),
835
+ witness(
836
+ 33,
837
+ "For",
838
+ "unshadowed-builtin-range/bounded-policy",
839
+ "supported",
840
+ _RANGE_LOOP,
841
+ "The For loop iterates an unshadowed range with direct bounded start, stop, and step.",
842
+ ast_kinds=("For", "Call"),
843
+ ),
844
+ witness(
845
+ 34,
846
+ "Break",
847
+ "supported-loop",
848
+ "supported",
849
+ _WHILE_STATEMENT,
850
+ "Break occurs lexically inside the supported no-else While loop.",
851
+ ast_kinds=("Break", "While"),
852
+ ),
853
+ witness(
854
+ 35,
855
+ "Continue",
856
+ "supported-loop",
857
+ "supported",
858
+ _WHILE_STATEMENT,
859
+ "Continue occurs lexically inside the supported no-else While loop.",
860
+ ast_kinds=("Continue", "While"),
861
+ ),
862
+ witness(
863
+ 36,
864
+ "List",
865
+ "nonempty-1-to-64/homogeneous-scalar/direct-local-single-binding",
866
+ "supported",
867
+ _LIST_VALUE,
868
+ "A nonempty three-element homogeneous integer list has one direct local binding.",
869
+ ast_kinds=("List", "Assign"),
870
+ ),
871
+ witness(
872
+ 37,
873
+ "Tuple",
874
+ "nonempty-1-to-64/homogeneous-scalar/direct-local-single-binding",
875
+ "supported",
876
+ _TUPLE_VALUE,
877
+ "A nonempty two-element homogeneous string tuple has one direct local binding.",
878
+ ast_kinds=("Tuple", "Assign"),
879
+ ),
880
+ witness(
881
+ 38,
882
+ "Dict",
883
+ "nonempty-1-to-64/distinct-homogeneous-literal-int-or-str-keys/homogeneous-scalar-values",
884
+ "supported",
885
+ _DICT_VALUE,
886
+ "A nonempty dictionary has distinct homogeneous literal string keys and homogeneous integer values.",
887
+ ast_kinds=("Dict", "Assign"),
888
+ ),
889
+ witness(
890
+ 39,
891
+ "Subscript",
892
+ "fixed-list-or-tuple/signed-literal-in-bounds",
893
+ "supported",
894
+ _LIST_VALUE,
895
+ "The fixed list is indexed by the signed literal -1, proved in bounds.",
896
+ ast_kinds=("Subscript", "List"),
897
+ ),
898
+ witness(
899
+ 40,
900
+ "Subscript",
901
+ "fixed-dict/present-literal-key",
902
+ "supported",
903
+ _DICT_VALUE,
904
+ "The fixed dictionary is indexed by its present literal key 'b'.",
905
+ ast_kinds=("Subscript", "Dict"),
906
+ ),
907
+ witness(
908
+ 41,
909
+ "For",
910
+ "direct-fixed-container-name/single-target/no-else",
911
+ "supported",
912
+ _CONTAINER_ITERATION,
913
+ "The loop directly names a fixed list, has one target, and has no else.",
914
+ ast_kinds=("For", "List"),
915
+ ),
916
+ witness(
917
+ 42,
918
+ "ContainerMutation",
919
+ "any",
920
+ "unsupported",
921
+ (
922
+ "def run() -> int:\n"
923
+ " values = [1, 2]\n"
924
+ " values[0] = 3\n"
925
+ " return values[0]\n"
926
+ ),
927
+ "A subscript assignment mutates a fixed list after binding.",
928
+ diagnostic="PYC3406",
929
+ ast_kinds=("Assign", "Subscript", "List"),
930
+ ),
931
+ witness(
932
+ 43,
933
+ "ContainerAliasOrEscape",
934
+ "any",
935
+ "unsupported",
936
+ (
937
+ "def run() -> int:\n"
938
+ " values = [1, 2]\n"
939
+ " alias = values\n"
940
+ " return alias[0]\n"
941
+ ),
942
+ "The fixed list escapes its single-binding proof through a second local alias.",
943
+ diagnostic="PYC3403",
944
+ ast_kinds=("Assign", "List", "Name"),
945
+ ),
946
+ witness(
947
+ 44,
948
+ "Comprehension",
949
+ "any",
950
+ "unsupported",
951
+ (
952
+ "def run() -> int:\n"
953
+ " values = [item for item in range(3)]\n"
954
+ " return 1\n"
955
+ ),
956
+ "A ListComp attempts to construct a container dynamically.",
957
+ diagnostic="PYC3406",
958
+ ast_kinds=("ListComp", "comprehension"),
959
+ ),
960
+ witness(
961
+ 45,
962
+ "ImportFrom",
963
+ "absolute-exact-sourcebundle-module/direct-eligible-functions/optional-aliases/preamble",
964
+ "supported",
965
+ (
966
+ "from lib import increment as step\n\n"
967
+ "def run(value: int) -> int:\n"
968
+ " return step(value)\n"
969
+ ),
970
+ "An absolute preamble import resolves an optionally aliased eligible function from the explicit companion.",
971
+ ast_kinds=("ImportFrom", "alias"),
972
+ logical_name="app.py",
973
+ module_id="app",
974
+ companions=_MODULE_COMPANIONS,
975
+ ),
976
+ witness(
977
+ 46,
978
+ "Import",
979
+ "plain-module-import",
980
+ "unsupported",
981
+ "import lib\n\ndef run() -> int:\n return 1\n",
982
+ "A plain Import statement names an otherwise explicit companion module.",
983
+ diagnostic="PYC3504",
984
+ ast_kinds=("Import",),
985
+ logical_name="app.py",
986
+ module_id="app",
987
+ companions=_MODULE_COMPANIONS,
988
+ ),
989
+ witness(
990
+ 47,
991
+ "ImportFrom",
992
+ "relative-star-late-local-conditional-or-dynamic",
993
+ "unsupported",
994
+ (
995
+ "if True:\n"
996
+ " from lib import increment\n\n"
997
+ "def run() -> int:\n"
998
+ " return 1\n"
999
+ ),
1000
+ "The ImportFrom is conditional instead of an unconditional module preamble item.",
1001
+ diagnostic="PYC3504",
1002
+ ast_kinds=("ImportFrom", "If"),
1003
+ logical_name="app.py",
1004
+ module_id="app",
1005
+ companions=_MODULE_COMPANIONS,
1006
+ ),
1007
+ witness(
1008
+ 48,
1009
+ "ImportFrom",
1010
+ "missing-explicit-module",
1011
+ "unsupported",
1012
+ (
1013
+ "from absent import value\n\n"
1014
+ "def run() -> int:\n"
1015
+ " return value()\n"
1016
+ ),
1017
+ "The absolute import target absent is not present in the explicit source bundle.",
1018
+ diagnostic="PYC3503",
1019
+ ast_kinds=("ImportFrom",),
1020
+ logical_name="app.py",
1021
+ module_id="app",
1022
+ ),
1023
+ witness(
1024
+ 49,
1025
+ "ImportFrom",
1026
+ "missing-or-ineligible-direct-member",
1027
+ "unsupported",
1028
+ (
1029
+ "from lib import absent\n\n"
1030
+ "def run() -> int:\n"
1031
+ " return 1\n"
1032
+ ),
1033
+ "The explicit companion exists but does not define the directly imported member absent.",
1034
+ diagnostic="PYC3505",
1035
+ ast_kinds=("ImportFrom",),
1036
+ logical_name="app.py",
1037
+ module_id="app",
1038
+ companions=_MODULE_COMPANIONS,
1039
+ ),
1040
+ witness(
1041
+ 50,
1042
+ "ModuleDependency",
1043
+ "self-or-strongly-connected-cycle",
1044
+ "unsupported",
1045
+ (
1046
+ "from lib import increment\n\n"
1047
+ "def run(value: int) -> int:\n"
1048
+ " return increment(value)\n"
1049
+ ),
1050
+ "The app-to-lib and lib-to-app imports form a two-module strongly connected dependency cycle.",
1051
+ diagnostic="PYC3507",
1052
+ ast_kinds=("ImportFrom",),
1053
+ logical_name="app.py",
1054
+ module_id="app",
1055
+ companions=(
1056
+ (
1057
+ "lib",
1058
+ "lib.py",
1059
+ (
1060
+ "from app import run\n\n"
1061
+ "def increment(value: int) -> int:\n"
1062
+ " return run(value)\n"
1063
+ ),
1064
+ ),
1065
+ ),
1066
+ ),
1067
+ witness(
1068
+ 51,
1069
+ "ModuleInitialization",
1070
+ "compile-time-namespace-only",
1071
+ "supported",
1072
+ _MODULE_PRIMARY,
1073
+ "The module body contains only namespace-defining imports, a record declaration, and function declarations.",
1074
+ ast_kinds=("Module", "ImportFrom", "ClassDef", "FunctionDef"),
1075
+ logical_name="app.py",
1076
+ module_id="app",
1077
+ companions=_MODULE_COMPANIONS,
1078
+ ),
1079
+ witness(
1080
+ 52,
1081
+ "ModuleInitialization",
1082
+ "executable-top-level-state",
1083
+ "unsupported",
1084
+ (
1085
+ "value = 1\n\n"
1086
+ "def run() -> int:\n"
1087
+ " return value\n"
1088
+ ),
1089
+ "The top-level assignment creates executable module initialization state.",
1090
+ diagnostic="PYC3509",
1091
+ ast_kinds=("Assign", "Module"),
1092
+ ),
1093
+ witness(
1094
+ 53,
1095
+ "ClassDef",
1096
+ "top-level-undecorated-base-free-static-record-before-functions",
1097
+ "supported",
1098
+ _RECORD,
1099
+ "Sample is a top-level undecorated base-free static record placed before the read function.",
1100
+ ast_kinds=("ClassDef",),
1101
+ ),
1102
+ witness(
1103
+ 54,
1104
+ "ClassDef",
1105
+ "nested-decorated-inherited-keyworded-or-general-class",
1106
+ "unsupported",
1107
+ (
1108
+ "class Point(Base):\n"
1109
+ " x: int\n"
1110
+ " def __init__(self, x: int) -> None:\n"
1111
+ " self.x = x\n\n"
1112
+ "def run() -> int:\n"
1113
+ " return 1\n"
1114
+ ),
1115
+ "Point has an inherited base and therefore is outside the base-free static-record profile.",
1116
+ diagnostic="PYC3601",
1117
+ ast_kinds=("ClassDef",),
1118
+ ),
1119
+ witness(
1120
+ 55,
1121
+ "AnnAssign",
1122
+ "record-value-less-distinct-int-float-bool-field/1-to-64",
1123
+ "supported",
1124
+ _RECORD,
1125
+ "Sample declares three distinct value-less int, float, and bool record fields within capacity.",
1126
+ ast_kinds=("AnnAssign",),
1127
+ ),
1128
+ witness(
1129
+ 56,
1130
+ "AnnAssign",
1131
+ "record-default-unsupported-type-duplicate-or-capacity",
1132
+ "unsupported",
1133
+ (
1134
+ "class Point:\n"
1135
+ " x: str\n"
1136
+ " def __init__(self, x: str) -> None:\n"
1137
+ " self.x = x\n\n"
1138
+ "def run() -> int:\n"
1139
+ " return 1\n"
1140
+ ),
1141
+ "The value-less record field uses unsupported str storage.",
1142
+ diagnostic="PYC3602",
1143
+ ast_kinds=("AnnAssign",),
1144
+ ),
1145
+ witness(
1146
+ 57,
1147
+ "FunctionDef",
1148
+ "record-exact-structural-__init__",
1149
+ "supported",
1150
+ _RECORD,
1151
+ "Sample.__init__ exactly assigns every declared field from its corresponding annotated parameter.",
1152
+ ast_kinds=("FunctionDef", "Attribute"),
1153
+ ),
1154
+ witness(
1155
+ 58,
1156
+ "FunctionDef",
1157
+ "record-nonstructural-__init__",
1158
+ "unsupported",
1159
+ (
1160
+ "class Point:\n"
1161
+ " x: int\n"
1162
+ " def __init__(self, x: int) -> int:\n"
1163
+ " self.x = x\n\n"
1164
+ "def run() -> int:\n"
1165
+ " return 1\n"
1166
+ ),
1167
+ "Point.__init__ declares int rather than the exact structural None return.",
1168
+ diagnostic="PYC3603",
1169
+ ast_kinds=("FunctionDef",),
1170
+ ),
1171
+ witness(
1172
+ 59,
1173
+ "FunctionDef",
1174
+ "ordinary-record-method",
1175
+ "unsupported",
1176
+ (
1177
+ _POINT_RECORD
1178
+ + " def get(self) -> int:\n"
1179
+ " return self.x\n\n"
1180
+ "def run() -> int:\n"
1181
+ " return 1\n"
1182
+ ),
1183
+ "The record class declares an ordinary get method in addition to structural __init__.",
1184
+ diagnostic="PYC3604",
1185
+ ast_kinds=("FunctionDef",),
1186
+ ),
1187
+ witness(
1188
+ 60,
1189
+ "Call",
1190
+ "same-module-record-direct-positional-fresh-local-construction",
1191
+ "supported",
1192
+ _RECORD,
1193
+ "read directly constructs same-module Sample positionally into a fresh local.",
1194
+ ast_kinds=("Call", "ClassDef"),
1195
+ ),
1196
+ witness(
1197
+ 61,
1198
+ "Call",
1199
+ "record-indirect-keyword-nested-conditional-returned-or-mismatched-construction",
1200
+ "unsupported",
1201
+ (
1202
+ _POINT_RECORD
1203
+ + "\ndef run() -> int:\n"
1204
+ " point = Point(x=1)\n"
1205
+ " return point.x\n"
1206
+ ),
1207
+ "The record constructor is invoked with a keyword rather than the exact direct positional profile.",
1208
+ diagnostic="PYC3605",
1209
+ ast_kinds=("Call", "keyword"),
1210
+ ),
1211
+ witness(
1212
+ 62,
1213
+ "RecordValue",
1214
+ "alias-rebind-copy-escape-parameter-return-call-container-identity-or-truth",
1215
+ "unsupported",
1216
+ (
1217
+ _POINT_RECORD
1218
+ + "\ndef run() -> int:\n"
1219
+ " point = Point(1)\n"
1220
+ " alias = point\n"
1221
+ " return point.x\n"
1222
+ ),
1223
+ "The fresh record value is copied into alias, violating immutable no-alias ownership.",
1224
+ diagnostic="PYC3606",
1225
+ ast_kinds=("Assign", "Call"),
1226
+ ),
1227
+ witness(
1228
+ 63,
1229
+ "Attribute",
1230
+ "direct-statically-bound-record-field-read",
1231
+ "supported",
1232
+ _RECORD,
1233
+ "read returns sample.count through a direct statically bound field access.",
1234
+ ast_kinds=("Attribute",),
1235
+ ),
1236
+ witness(
1237
+ 64,
1238
+ "Attribute",
1239
+ "record-mutation-unknown-dynamic-or-chained-attribute",
1240
+ "unsupported",
1241
+ (
1242
+ _POINT_RECORD
1243
+ + "\ndef run() -> int:\n"
1244
+ " point = Point(1)\n"
1245
+ " point.x = 2\n"
1246
+ " return point.x\n"
1247
+ ),
1248
+ "An Attribute target mutates the immutable record field point.x.",
1249
+ diagnostic="PYC3607",
1250
+ ast_kinds=("Attribute", "Assign"),
1251
+ ),
1252
+ witness(
1253
+ 65,
1254
+ "ImportFrom",
1255
+ "record-class-import",
1256
+ "unsupported",
1257
+ (
1258
+ "from records import Point\n\n"
1259
+ "def run() -> int:\n"
1260
+ " point = Point(1)\n"
1261
+ " return point.x\n"
1262
+ ),
1263
+ "The ImportFrom attempts to import a record class across module boundaries.",
1264
+ diagnostic="PYC3610",
1265
+ ast_kinds=("ImportFrom",),
1266
+ logical_name="app.py",
1267
+ module_id="app",
1268
+ companions=(
1269
+ (
1270
+ "records",
1271
+ "records.py",
1272
+ (
1273
+ _POINT_RECORD
1274
+ + "\ndef local() -> int:\n"
1275
+ " point = Point(2)\n"
1276
+ " return point.x\n"
1277
+ ),
1278
+ ),
1279
+ ),
1280
+ ),
1281
+ witness(
1282
+ 66,
1283
+ "Lambda",
1284
+ "call-target-or-value",
1285
+ "unsupported",
1286
+ (
1287
+ "def run(value: int) -> int:\n"
1288
+ " target = lambda item: item\n"
1289
+ " return target(value)\n"
1290
+ ),
1291
+ "A Lambda is bound as an indirect call target value.",
1292
+ diagnostic="PYC2901",
1293
+ ast_kinds=("Lambda", "Call"),
1294
+ ),
1295
+ witness(
1296
+ 67,
1297
+ "AsyncFunctionDef",
1298
+ "any",
1299
+ "unsupported",
1300
+ "async def run() -> int:\n return 1\n",
1301
+ "The Phase 9 profile owns the original AsyncFunctionDef boundary and emits PYC2902; the current module profile rejects it earlier as ineligible module initialization.",
1302
+ diagnostic="PYC2902",
1303
+ ast_kinds=("AsyncFunctionDef",),
1304
+ request_options=(
1305
+ ("rule_set_version", PHASE9_RULE_SET),
1306
+ ("renderer_version", "c-renderer-v0.9"),
1307
+ ),
1308
+ precedence_profiles=(
1309
+ FeatureMatrixExecutionProfile(
1310
+ "current-default-precedence",
1311
+ (),
1312
+ "PYC3509",
1313
+ "Only eligible synchronous top-level functions",
1314
+ ),
1315
+ ),
1316
+ ),
1317
+ witness(
1318
+ 68,
1319
+ "Match",
1320
+ "any",
1321
+ "deferred",
1322
+ (
1323
+ "def run(value: int) -> int:\n"
1324
+ " result = 0\n"
1325
+ " match value:\n"
1326
+ " case 1:\n"
1327
+ " result = 1\n"
1328
+ " return result\n"
1329
+ ),
1330
+ "A Match statement is present while an explicit trailing return prevents fallthrough from masking the deferred boundary.",
1331
+ actual_diagnostic="PYC2812",
1332
+ actual_reason_contains="Unsupported statement in the selected subset: Match",
1333
+ ast_kinds=("Match",),
1334
+ ),
1335
+ )
1336
+
1337
+
1338
+ FEATURE_MATRIX_WITNESS_ORDER = _feature_matrix_witness_rows()
1339
+ FEATURE_MATRIX_WITNESSES: Mapping[
1340
+ tuple[str, str], FeatureMatrixWitness
1341
+ ] = MappingProxyType({item.key: item for item in FEATURE_MATRIX_WITNESS_ORDER})
1342
+
1343
+ UNLISTED_DEFAULT_WITNESS = FeatureMatrixWitness(
1344
+ "feature-matrix-unlisted-default",
1345
+ "Try",
1346
+ "unlisted-node/default-unsupported",
1347
+ "unsupported",
1348
+ (
1349
+ "def run() -> int:\n"
1350
+ " result = 0\n"
1351
+ " try:\n"
1352
+ " result = 1\n"
1353
+ " except Exception:\n"
1354
+ " result = 2\n"
1355
+ " return result\n"
1356
+ ),
1357
+ "Try is intentionally absent from the 69-entry matrix; an explicit trailing return prevents fallthrough from masking the default unsupported boundary.",
1358
+ actual_diagnostic="PYC2812",
1359
+ actual_reason_contains="Unsupported statement in the selected subset: Try",
1360
+ required_ast_kinds=("Try",),
1361
+ )
1362
+
1363
+
1364
+ def current_contract_identities() -> dict[str, str]:
1365
+ """Return the converter identities that Checkpoint E must not churn."""
1366
+
1367
+ return {
1368
+ "converter_contract": CONVERTER_CONTRACT_VERSION,
1369
+ "source_bundle": SOURCE_BUNDLE_SCHEMA,
1370
+ "python_ir_bundle": PYTHON_IR_BUNDLE_SCHEMA,
1371
+ "container_facts": CONTAINER_FACT_SCHEMA,
1372
+ "module_facts": MODULE_FACT_SCHEMA,
1373
+ "record_facts": RECORD_FACT_SCHEMA,
1374
+ "numeric_facts": NUMERIC_FACT_SCHEMA,
1375
+ "conditional_facts": CONDITIONAL_FACT_SCHEMA,
1376
+ "keyword_call_facts": KEYWORD_CALL_FACT_SCHEMA,
1377
+ "keyword_only_call_facts": KEYWORD_ONLY_CALL_FACT_SCHEMA,
1378
+ "conversion_plan": CONVERSION_PLAN_SCHEMA,
1379
+ "c_ir": C_IR_SCHEMA,
1380
+ "generated_c": GENERATED_C_SCHEMA,
1381
+ "conversion_summary": CONVERSION_SUMMARY_SCHEMA,
1382
+ "decision_trace": DECISION_TRACE_SCHEMA,
1383
+ "result": RESULT_SCHEMA_VERSION,
1384
+ "rule_set": DEFAULT_RULE_SET,
1385
+ "renderer": DEFAULT_RENDERER,
1386
+ "keyword_call_rule": KEYWORD_CALL_RULE_ID,
1387
+ "keyword_only_call_rule": KEYWORD_ONLY_CALL_RULE_ID,
1388
+ "target_contract": DEFAULT_TARGET_CONTRACT,
1389
+ "semantic_policy": DEFAULT_SEMANTIC_POLICY,
1390
+ "helper_policy": DEFAULT_HELPER_POLICY,
1391
+ "container_policy": DEFAULT_CONTAINER_POLICY,
1392
+ "module_policy": DEFAULT_MODULE_POLICY,
1393
+ "record_policy": DEFAULT_RECORD_POLICY,
1394
+ "numeric_policy": DEFAULT_NUMERIC_POLICY,
1395
+ }
1396
+
1397
+
1398
+ def fixed_supported_subset_cases() -> tuple[SupportedSubsetCase, ...]:
1399
+ """Return one positive witness for every promoted construct family."""
1400
+
1401
+ return (
1402
+ SupportedSubsetCase(
1403
+ "fixed-literals",
1404
+ "literals",
1405
+ (
1406
+ "def integer_value() -> int:\n"
1407
+ " return 7\n\n"
1408
+ "def real_value() -> float:\n"
1409
+ " return 1.5\n\n"
1410
+ "def flag_value() -> bool:\n"
1411
+ " return True\n\n"
1412
+ "def text_value() -> str:\n"
1413
+ ' return "PyCForge"\n'
1414
+ ),
1415
+ ),
1416
+ SupportedSubsetCase(
1417
+ "fixed-arithmetic",
1418
+ "assignments-and-arithmetic",
1419
+ (
1420
+ "def calculate(a: int, b: int) -> int:\n"
1421
+ " value = a + b * 2\n"
1422
+ " value = value - 1\n"
1423
+ " return value\n\n"
1424
+ "def ratio(value: float) -> float:\n"
1425
+ " return value / 2.0\n"
1426
+ ),
1427
+ ),
1428
+ SupportedSubsetCase(
1429
+ "fixed-positional-call",
1430
+ "functions-and-positional-calls",
1431
+ (
1432
+ "def add(a: int, b: int) -> int:\n"
1433
+ " return a + b\n\n"
1434
+ "def run(value: int) -> int:\n"
1435
+ " return add(value, 3)\n"
1436
+ ),
1437
+ ),
1438
+ SupportedSubsetCase(
1439
+ "fixed-if-else",
1440
+ "if-else",
1441
+ (
1442
+ "def choose(value: int, flag: bool) -> int:\n"
1443
+ " if flag:\n"
1444
+ " value = value + 1\n"
1445
+ " else:\n"
1446
+ " value = value - 1\n"
1447
+ " return value\n"
1448
+ ),
1449
+ ),
1450
+ SupportedSubsetCase(
1451
+ "fixed-while",
1452
+ "while-break-continue",
1453
+ (
1454
+ "def scan(limit: int) -> int:\n"
1455
+ " value = 0\n"
1456
+ " while value < limit:\n"
1457
+ " value = value + 1\n"
1458
+ " if value == 2:\n"
1459
+ " continue\n"
1460
+ " if value == 5:\n"
1461
+ " break\n"
1462
+ " return value\n"
1463
+ ),
1464
+ ),
1465
+ SupportedSubsetCase(
1466
+ "fixed-range-for",
1467
+ "bounded-range-for",
1468
+ (
1469
+ "def total() -> int:\n"
1470
+ " value = 0\n"
1471
+ " for item in range(1, 6, 2):\n"
1472
+ " value = value + item\n"
1473
+ " return value\n"
1474
+ ),
1475
+ ),
1476
+ SupportedSubsetCase(
1477
+ "fixed-list",
1478
+ "list",
1479
+ (
1480
+ "def pick() -> int:\n"
1481
+ " values = [1, 2, 3]\n"
1482
+ " return values[-1]\n\n"
1483
+ "def iterate() -> int:\n"
1484
+ " values = [2, 4, 6]\n"
1485
+ " result = 0\n"
1486
+ " for item in values:\n"
1487
+ " result = item\n"
1488
+ " return result\n"
1489
+ ),
1490
+ ),
1491
+ SupportedSubsetCase(
1492
+ "fixed-tuple",
1493
+ "tuple",
1494
+ (
1495
+ "def pick() -> str:\n"
1496
+ ' values = ("a", "b")\n'
1497
+ " return values[0]\n"
1498
+ ),
1499
+ ),
1500
+ SupportedSubsetCase(
1501
+ "fixed-dict",
1502
+ "dict",
1503
+ (
1504
+ "def pick() -> int:\n"
1505
+ ' values = {"a": 1, "b": 2}\n'
1506
+ ' return values["b"]\n'
1507
+ ),
1508
+ ),
1509
+ SupportedSubsetCase(
1510
+ "fixed-module-bundle",
1511
+ "explicit-module-bundle",
1512
+ (
1513
+ "from lib import increment\n\n"
1514
+ "def run(value: int) -> int:\n"
1515
+ " return increment(value)\n"
1516
+ ),
1517
+ primary_name="app.py",
1518
+ primary_module="app",
1519
+ companions=(
1520
+ (
1521
+ "lib",
1522
+ "lib.py",
1523
+ (
1524
+ "def increment(value: int) -> int:\n"
1525
+ " return value + 1\n"
1526
+ ),
1527
+ ),
1528
+ ),
1529
+ ),
1530
+ SupportedSubsetCase(
1531
+ "fixed-record",
1532
+ "static-record",
1533
+ (
1534
+ "class Point:\n"
1535
+ " x: int\n"
1536
+ " y: int\n"
1537
+ " def __init__(self, x: int, y: int) -> None:\n"
1538
+ " self.x = x\n"
1539
+ " self.y = y\n\n"
1540
+ "def read() -> int:\n"
1541
+ " point = Point(2, 3)\n"
1542
+ " return point.x\n"
1543
+ ),
1544
+ ),
1545
+ SupportedSubsetCase(
1546
+ "fixed-floor-arithmetic",
1547
+ "bounded-floor-arithmetic",
1548
+ (
1549
+ "def arithmetic(value: int) -> int:\n"
1550
+ " return (value // -3) + (value % 5)\n"
1551
+ ),
1552
+ ),
1553
+ SupportedSubsetCase(
1554
+ "fixed-boolean-region",
1555
+ "boolean-conditional-region",
1556
+ (
1557
+ "def flag(value: bool) -> bool:\n"
1558
+ " return value\n\n"
1559
+ "def decide(a: bool, b: bool) -> bool:\n"
1560
+ " return a and flag(b)\n"
1561
+ ),
1562
+ ),
1563
+ SupportedSubsetCase(
1564
+ "fixed-comparison-region",
1565
+ "comparison-conditional-region",
1566
+ (
1567
+ "def identity(value: int) -> int:\n"
1568
+ " return value\n\n"
1569
+ "def compare(a: int, b: int, c: int) -> bool:\n"
1570
+ " return a < b < identity(c)\n"
1571
+ ),
1572
+ ),
1573
+ SupportedSubsetCase(
1574
+ "fixed-keyword-call",
1575
+ "direct-keyword-call",
1576
+ (
1577
+ "def choose(left: int, flag: bool) -> int:\n"
1578
+ " return left\n\n"
1579
+ "def run(value: int, flag: bool) -> int:\n"
1580
+ " return choose(flag=flag, left=value)\n"
1581
+ ),
1582
+ ),
1583
+ SupportedSubsetCase(
1584
+ "fixed-keyword-only-call",
1585
+ "required-keyword-only-call",
1586
+ (
1587
+ "def choose(left: int, /, middle: int, *, scale: int) -> int:\n"
1588
+ " return (left + middle) * scale\n\n"
1589
+ "def run() -> int:\n"
1590
+ " return choose(1, middle=2, scale=3)\n"
1591
+ ),
1592
+ ),
1593
+ )
1594
+
1595
+
1596
+ def generated_fuzz_cases(
1597
+ *,
1598
+ seed: int = FUZZ_SEED,
1599
+ count: int = DEFAULT_FUZZ_CASE_COUNT,
1600
+ ) -> tuple[SupportedSubsetCase, ...]:
1601
+ """Generate bounded, reproducible cases across every promoted family.
1602
+
1603
+ Each complete 16-case cycle contains every member of
1604
+ :data:`SUPPORTED_SUBSET_FAMILIES` exactly once in a seeded shuffled order.
1605
+ The family templates perturb safe constants, names, keys, bounds, and
1606
+ operator choices without crossing the frozen subset boundary.
1607
+ """
1608
+
1609
+ if count < 0:
1610
+ raise ValueError("count must be non-negative")
1611
+ rng = random.Random(seed)
1612
+ cases: list[SupportedSubsetCase] = []
1613
+ family_cycle: list[str] = []
1614
+
1615
+ def next_family() -> str:
1616
+ nonlocal family_cycle
1617
+ if not family_cycle:
1618
+ family_cycle = sorted(SUPPORTED_SUBSET_FAMILIES)
1619
+ rng.shuffle(family_cycle)
1620
+ return family_cycle.pop()
1621
+
1622
+ for ordinal in range(count):
1623
+ family = next_family()
1624
+ first = rng.randint(1, 31)
1625
+ second = rng.randint(1, 31)
1626
+ third = rng.randint(1, 15)
1627
+ primary_name = "main.py"
1628
+ primary_module = "main"
1629
+ companions: tuple[tuple[str, str, str], ...] = ()
1630
+ if family == "literals":
1631
+ source = (
1632
+ f"def integer_{ordinal:03d}() -> int:\n"
1633
+ f" return {first}\n\n"
1634
+ f"def real_{ordinal:03d}() -> float:\n"
1635
+ f" return {second}.{third}\n\n"
1636
+ f"def flag_{ordinal:03d}() -> bool:\n"
1637
+ f" return {'True' if first % 2 else 'False'}\n\n"
1638
+ f"def text_{ordinal:03d}() -> str:\n"
1639
+ f' return "fuzz-{ordinal:03d}-{third}"\n'
1640
+ )
1641
+ elif family == "assignments-and-arithmetic":
1642
+ divisor = rng.randint(2, 9)
1643
+ source = (
1644
+ f"def arithmetic_{ordinal:03d}(value: int) -> int:\n"
1645
+ f" result = value + {first}\n"
1646
+ f" result = result * {third}\n"
1647
+ f" return result - {second}\n\n"
1648
+ f"def divide_{ordinal:03d}(value: float) -> float:\n"
1649
+ f" return value / {divisor}.0\n"
1650
+ )
1651
+ elif family == "functions-and-positional-calls":
1652
+ source = (
1653
+ f"def add_{ordinal:03d}(left: int, right: int) -> int:\n"
1654
+ " return left + right\n\n"
1655
+ f"def run_{ordinal:03d}(value: int) -> int:\n"
1656
+ f" return add_{ordinal:03d}(value, {first})\n"
1657
+ )
1658
+ elif family == "if-else":
1659
+ source = (
1660
+ f"def choose_{ordinal:03d}(value: int, flag: bool) -> int:\n"
1661
+ " if flag:\n"
1662
+ f" value = value + {first}\n"
1663
+ " else:\n"
1664
+ f" value = value - {second}\n"
1665
+ " return value\n"
1666
+ )
1667
+ elif family == "while-break-continue":
1668
+ continue_at = rng.randint(1, 3)
1669
+ break_at = rng.randint(4, 8)
1670
+ source = (
1671
+ f"def scan_{ordinal:03d}(limit: int) -> int:\n"
1672
+ " value = 0\n"
1673
+ " while value < limit:\n"
1674
+ " value = value + 1\n"
1675
+ f" if value == {continue_at}:\n"
1676
+ " continue\n"
1677
+ f" if value == {break_at}:\n"
1678
+ " break\n"
1679
+ " return value\n"
1680
+ )
1681
+ elif family == "bounded-range-for":
1682
+ start = rng.randint(0, 3)
1683
+ step = rng.randint(1, 3)
1684
+ stop = start + step * rng.randint(2, 7)
1685
+ source = (
1686
+ f"def total_{ordinal:03d}() -> int:\n"
1687
+ " result = 0\n"
1688
+ f" for item in range({start}, {stop}, {step}):\n"
1689
+ " result = result + item\n"
1690
+ " return result\n"
1691
+ )
1692
+ elif family == "list":
1693
+ values = (first, second, third)
1694
+ source = (
1695
+ f"def list_{ordinal:03d}() -> int:\n"
1696
+ f" values = [{values[0]}, {values[1]}, {values[2]}]\n"
1697
+ " result = 0\n"
1698
+ " for item in values:\n"
1699
+ " result = item\n"
1700
+ " return result\n"
1701
+ )
1702
+ elif family == "tuple":
1703
+ source = (
1704
+ f"def tuple_{ordinal:03d}() -> str:\n"
1705
+ f' values = ("left-{first}", "right-{second}")\n'
1706
+ f" return values[{ordinal % 2}]\n"
1707
+ )
1708
+ elif family == "dict":
1709
+ source = (
1710
+ f"def dict_{ordinal:03d}() -> str:\n"
1711
+ f' values = {{"left-{first}": {first}, '
1712
+ f'"right-{second}": {second}}}\n'
1713
+ ' result = ""\n'
1714
+ " for key in values:\n"
1715
+ " result = key\n"
1716
+ " return result\n"
1717
+ )
1718
+ elif family == "explicit-module-bundle":
1719
+ primary_name = f"app_{ordinal:03d}.py"
1720
+ primary_module = f"app_{ordinal:03d}"
1721
+ companion_module = f"lib_{ordinal:03d}"
1722
+ companion_name = f"{companion_module}.py"
1723
+ helper_name = f"increment_{ordinal:03d}"
1724
+ source = (
1725
+ f"from {companion_module} import {helper_name}\n\n"
1726
+ f"def run_{ordinal:03d}(value: int) -> int:\n"
1727
+ f" return {helper_name}(value)\n"
1728
+ )
1729
+ companions = (
1730
+ (
1731
+ companion_module,
1732
+ companion_name,
1733
+ (
1734
+ f"def {helper_name}(value: int) -> int:\n"
1735
+ f" return value + {first}\n"
1736
+ ),
1737
+ ),
1738
+ )
1739
+ elif family == "static-record":
1740
+ class_name = f"Sample{ordinal:03d}"
1741
+ source = (
1742
+ f"class {class_name}:\n"
1743
+ " value: int\n"
1744
+ " def __init__(self, value: int) -> None:\n"
1745
+ " self.value = value\n\n"
1746
+ f"def record_{ordinal:03d}() -> int:\n"
1747
+ f" sample = {class_name}({first})\n"
1748
+ " return sample.value\n"
1749
+ )
1750
+ elif family == "bounded-floor-arithmetic":
1751
+ safe_divisors = (-9, -7, -5, -3, 2, 3, 5, 7, 9)
1752
+ floor_divisor = rng.choice(safe_divisors)
1753
+ mod_divisor = rng.choice(safe_divisors)
1754
+ source = (
1755
+ f"def floor_{ordinal:03d}(value: int) -> int:\n"
1756
+ f" return (value // {floor_divisor}) + "
1757
+ f"(value % {mod_divisor})\n"
1758
+ )
1759
+ elif family == "boolean-conditional-region":
1760
+ operator = "and" if ordinal % 2 else "or"
1761
+ source = (
1762
+ f"def flag_{ordinal:03d}(value: bool) -> bool:\n"
1763
+ " return value\n\n"
1764
+ f"def boolean_{ordinal:03d}(left: bool, right: bool) -> bool:\n"
1765
+ f" return left {operator} flag_{ordinal:03d}(right)\n"
1766
+ )
1767
+ elif family == "comparison-conditional-region":
1768
+ source = (
1769
+ f"def identity_{ordinal:03d}(value: int) -> int:\n"
1770
+ " return value\n\n"
1771
+ f"def compare_{ordinal:03d}("
1772
+ "left: int, middle: int, right: int) -> bool:\n"
1773
+ f" return left < middle < identity_{ordinal:03d}(right)\n"
1774
+ )
1775
+ elif family == "direct-keyword-call":
1776
+ source = (
1777
+ f"def choose_{ordinal:03d}("
1778
+ "left: int, middle: int, flag: bool) -> int:\n"
1779
+ " if flag:\n"
1780
+ " return left\n"
1781
+ " return middle\n\n"
1782
+ f"def keyword_{ordinal:03d}(value: int, flag: bool) -> int:\n"
1783
+ f" return choose_{ordinal:03d}("
1784
+ f"value, flag=flag, middle={first})\n"
1785
+ )
1786
+ elif family == "required-keyword-only-call":
1787
+ source = (
1788
+ f"def scale_{ordinal:03d}("
1789
+ "left: int, /, middle: int, *, factor: int) -> int:\n"
1790
+ " return (left + middle) * factor\n\n"
1791
+ f"def keyword_only_{ordinal:03d}() -> int:\n"
1792
+ f" return scale_{ordinal:03d}("
1793
+ f"{first}, middle={second}, factor={third})\n"
1794
+ )
1795
+ else: # pragma: no cover - the frozen family set is exhaustively routed.
1796
+ raise AssertionError(f"unrouted generated family: {family}")
1797
+ cases.append(
1798
+ SupportedSubsetCase(
1799
+ f"fuzz-{ordinal:03d}",
1800
+ family,
1801
+ source,
1802
+ primary_name,
1803
+ primary_module,
1804
+ companions,
1805
+ )
1806
+ )
1807
+ return tuple(cases)
1808
+
1809
+
1810
+ def _canonical_bytes(value: object) -> bytes:
1811
+ return json.dumps(
1812
+ value,
1813
+ sort_keys=True,
1814
+ separators=(",", ":"),
1815
+ ensure_ascii=False,
1816
+ allow_nan=False,
1817
+ ).encode("utf-8")
1818
+
1819
+
1820
+ def _sha256_json(value: object) -> str:
1821
+ return hashlib.sha256(_canonical_bytes(value)).hexdigest()
1822
+
1823
+
1824
+ def _ast_kinds_for_witness(witness: FeatureMatrixWitness) -> set[str]:
1825
+ kinds: set[str] = set()
1826
+ documents = (
1827
+ (witness.logical_name, witness.source),
1828
+ *((logical_name, text) for _, logical_name, text in witness.companions),
1829
+ )
1830
+ for logical_name, source in documents:
1831
+ tree = ast.parse(source, filename=logical_name, mode="exec")
1832
+ kinds.update(type(node).__name__ for node in ast.walk(tree))
1833
+ return kinds
1834
+
1835
+
1836
+ def _feature_matrix_entry_contract(
1837
+ entry: Mapping[str, object],
1838
+ ) -> dict[str, object]:
1839
+ return {
1840
+ "construct": entry.get("construct"),
1841
+ "context": entry.get("context"),
1842
+ "state": entry.get("state"),
1843
+ "diagnostic": entry.get("diagnostic"),
1844
+ }
1845
+
1846
+
1847
+ def _execute_feature_matrix_witness(
1848
+ converter: PythonToCConverter,
1849
+ witness: FeatureMatrixWitness,
1850
+ *,
1851
+ profile_id: str,
1852
+ request_options: Mapping[str, object] | None,
1853
+ expected_diagnostic: str | None,
1854
+ reason_contains: str | None,
1855
+ ) -> tuple[dict[str, object], list[str]]:
1856
+ """Execute one profile twice and return closed, source-only evidence."""
1857
+
1858
+ errors: list[str] = []
1859
+ request = witness.request(request_options)
1860
+ first = converter.convert(request)
1861
+ repeated = converter.convert(request)
1862
+ first_json = result_to_json(first)
1863
+ repeated_json = result_to_json(repeated)
1864
+ deterministic = first_json == repeated_json
1865
+ if not deterministic:
1866
+ errors.append(
1867
+ f"{witness.witness_id}/{profile_id}: repeated conversion is not "
1868
+ "byte-stable"
1869
+ )
1870
+
1871
+ diagnostic_codes = [item.code for item in first.diagnostics]
1872
+ diagnostic_messages = [item.message for item in first.diagnostics]
1873
+ expected_status = (
1874
+ ResultStatus.CONVERTED
1875
+ if witness.state == "supported"
1876
+ else ResultStatus.REJECTED
1877
+ )
1878
+ if first.status is not expected_status:
1879
+ errors.append(
1880
+ f"{witness.witness_id}/{profile_id}: expected "
1881
+ f"{expected_status.value}, got {first.status.value}"
1882
+ )
1883
+
1884
+ c_conformance_accepted: bool | None = None
1885
+ c_conformance_message: str | None = None
1886
+ if witness.state == "supported":
1887
+ if first.generated_c is None:
1888
+ errors.append(
1889
+ f"{witness.witness_id}/{profile_id}: supported witness did "
1890
+ "not publish C source"
1891
+ )
1892
+ else:
1893
+ conformance = validate_c_text(first.generated_c)
1894
+ c_conformance_accepted = conformance.accepted
1895
+ c_conformance_message = conformance.message
1896
+ if not conformance.accepted:
1897
+ errors.append(
1898
+ f"{witness.witness_id}/{profile_id}: emitted C source "
1899
+ f"failed independent conformance: {conformance.message}"
1900
+ )
1901
+ if first.diagnostics:
1902
+ errors.append(
1903
+ f"{witness.witness_id}/{profile_id}: supported witness "
1904
+ f"published diagnostics {diagnostic_codes}"
1905
+ )
1906
+ else:
1907
+ if first.generated_c is not None:
1908
+ errors.append(
1909
+ f"{witness.witness_id}/{profile_id}: rejected witness "
1910
+ "published C source"
1911
+ )
1912
+ if first.output_fingerprint is not None:
1913
+ errors.append(
1914
+ f"{witness.witness_id}/{profile_id}: rejected witness "
1915
+ "published an output fingerprint"
1916
+ )
1917
+ if expected_diagnostic is not None and expected_diagnostic not in diagnostic_codes:
1918
+ errors.append(
1919
+ f"{witness.witness_id}/{profile_id}: expected diagnostic "
1920
+ f"{expected_diagnostic}, got {diagnostic_codes}"
1921
+ )
1922
+ if reason_contains is not None and not any(
1923
+ reason_contains in message for message in diagnostic_messages
1924
+ ):
1925
+ errors.append(
1926
+ f"{witness.witness_id}/{profile_id}: no diagnostic reason "
1927
+ f"contained {reason_contains!r}"
1928
+ )
1929
+
1930
+ evidence: dict[str, object] = {
1931
+ "profile_id": profile_id,
1932
+ "request_options": dict(request_options or {}),
1933
+ "expected_status": expected_status.value,
1934
+ "actual_status": first.status.value,
1935
+ "expected_diagnostic": expected_diagnostic,
1936
+ "actual_diagnostics": diagnostic_codes,
1937
+ "actual_reasons": diagnostic_messages,
1938
+ "deterministic": deterministic,
1939
+ "generated_c_published": first.generated_c is not None,
1940
+ "output_fingerprint_published": first.output_fingerprint is not None,
1941
+ "independent_c_conformance_accepted": c_conformance_accepted,
1942
+ "independent_c_conformance_message": c_conformance_message,
1943
+ "result_sha256": hashlib.sha256(first_json.encode("utf-8")).hexdigest(),
1944
+ }
1945
+ return evidence, errors
1946
+
1947
+
1948
+ def audit_feature_matrix(root: Path) -> dict[str, object]:
1949
+ """Execute all 69 frozen rows and the unlisted-node default.
1950
+
1951
+ Every row is driven through :class:`PythonToCConverter`. The one
1952
+ cumulative diagnostic whose owning profile differs from the current
1953
+ default (``AsyncFunctionDef``) names its Phase 9 request explicitly and
1954
+ also runs the current-profile precedence companion.
1955
+ """
1956
+
1957
+ matrix_path = root / "specifications" / "feature_matrix.json"
1958
+ errors: list[str] = []
1959
+ matrix_contract_mismatches: list[dict[str, object]] = []
1960
+ raw_matrix = b""
1961
+ matrix: dict[str, object] = {}
1962
+ if not matrix_path.is_file():
1963
+ errors.append(f"feature matrix is missing: {matrix_path}")
1964
+ else:
1965
+ raw_matrix = matrix_path.read_bytes()
1966
+ try:
1967
+ loaded = json.loads(raw_matrix.decode("utf-8"))
1968
+ except (UnicodeDecodeError, json.JSONDecodeError) as exc:
1969
+ errors.append(f"feature matrix is not valid canonical UTF-8 JSON: {exc}")
1970
+ else:
1971
+ if not isinstance(loaded, dict):
1972
+ errors.append("feature matrix root must be an object")
1973
+ else:
1974
+ matrix = loaded
1975
+
1976
+ matrix_sha256 = hashlib.sha256(raw_matrix).hexdigest()
1977
+ if matrix_sha256 != FEATURE_MATRIX_SHA256:
1978
+ errors.append(
1979
+ "feature matrix SHA-256 drift: expected "
1980
+ f"{FEATURE_MATRIX_SHA256}, got {matrix_sha256}"
1981
+ )
1982
+ if matrix.get("schema") != FEATURE_MATRIX_SCHEMA:
1983
+ errors.append(
1984
+ "feature matrix schema drift: expected "
1985
+ f"{FEATURE_MATRIX_SCHEMA!r}, got {matrix.get('schema')!r}"
1986
+ )
1987
+ expected_metadata = {
1988
+ "grammar": "python-3.11",
1989
+ "rule_set": EXPECTED_CONTRACT_IDENTITIES["rule_set"],
1990
+ "renderer": EXPECTED_CONTRACT_IDENTITIES["renderer"],
1991
+ "helper_policy": EXPECTED_CONTRACT_IDENTITIES["helper_policy"],
1992
+ "container_policy": EXPECTED_CONTRACT_IDENTITIES["container_policy"],
1993
+ "module_policy": EXPECTED_CONTRACT_IDENTITIES["module_policy"],
1994
+ "record_policy": EXPECTED_CONTRACT_IDENTITIES["record_policy"],
1995
+ "numeric_policy": EXPECTED_CONTRACT_IDENTITIES["numeric_policy"],
1996
+ "conditional_fact_schema": EXPECTED_CONTRACT_IDENTITIES[
1997
+ "conditional_facts"
1998
+ ],
1999
+ "keyword_call_fact_schema": EXPECTED_CONTRACT_IDENTITIES[
2000
+ "keyword_call_facts"
2001
+ ],
2002
+ "keyword_only_call_fact_schema": EXPECTED_CONTRACT_IDENTITIES[
2003
+ "keyword_only_call_facts"
2004
+ ],
2005
+ "default_state": "unsupported",
2006
+ }
2007
+ for key, expected in expected_metadata.items():
2008
+ actual = matrix.get(key)
2009
+ if actual != expected:
2010
+ errors.append(
2011
+ f"feature matrix {key} drift: expected {expected!r}, "
2012
+ f"got {actual!r}"
2013
+ )
2014
+
2015
+ raw_entries = matrix.get("entries", ())
2016
+ entries: list[Mapping[str, object]] = []
2017
+ if not isinstance(raw_entries, list):
2018
+ errors.append("feature matrix entries must be an array")
2019
+ else:
2020
+ for ordinal, entry in enumerate(raw_entries):
2021
+ if not isinstance(entry, Mapping):
2022
+ errors.append(
2023
+ f"feature matrix entry {ordinal} must be an object"
2024
+ )
2025
+ else:
2026
+ entries.append(entry)
2027
+ if len(entries) != FEATURE_MATRIX_ENTRY_COUNT:
2028
+ errors.append(
2029
+ "feature matrix entry-count drift: expected "
2030
+ f"{FEATURE_MATRIX_ENTRY_COUNT}, got {len(entries)}"
2031
+ )
2032
+
2033
+ witnesses = FEATURE_MATRIX_WITNESS_ORDER
2034
+ witness_keys = [item.key for item in witnesses]
2035
+ if len(witnesses) != FEATURE_MATRIX_ENTRY_COUNT:
2036
+ errors.append(
2037
+ "feature witness count drift: expected "
2038
+ f"{FEATURE_MATRIX_ENTRY_COUNT}, got {len(witnesses)}"
2039
+ )
2040
+ if len(FEATURE_MATRIX_WITNESSES) != len(witnesses):
2041
+ errors.append("feature witness keys are not unique")
2042
+
2043
+ actual_keys = [
2044
+ (str(entry.get("construct")), str(entry.get("context")))
2045
+ for entry in entries
2046
+ ]
2047
+ if actual_keys != witness_keys:
2048
+ matrix_contract_mismatches.append(
2049
+ {
2050
+ "kind": "order-or-key-drift",
2051
+ "expected": [list(key) for key in witness_keys],
2052
+ "actual": [list(key) for key in actual_keys],
2053
+ }
2054
+ )
2055
+
2056
+ entry_by_key: dict[tuple[str, str], Mapping[str, object]] = {}
2057
+ for entry in entries:
2058
+ key = (str(entry.get("construct")), str(entry.get("context")))
2059
+ if key in entry_by_key:
2060
+ matrix_contract_mismatches.append(
2061
+ {
2062
+ "kind": "duplicate-entry-key",
2063
+ "key": list(key),
2064
+ }
2065
+ )
2066
+ entry_by_key[key] = entry
2067
+
2068
+ for witness in witnesses:
2069
+ entry = entry_by_key.get(witness.key)
2070
+ if entry is None:
2071
+ matrix_contract_mismatches.append(
2072
+ {
2073
+ "kind": "missing-entry",
2074
+ "key": list(witness.key),
2075
+ }
2076
+ )
2077
+ continue
2078
+ expected_contract = {
2079
+ "construct": witness.construct,
2080
+ "context": witness.context,
2081
+ "state": witness.state,
2082
+ "diagnostic": witness.diagnostic,
2083
+ }
2084
+ actual_contract = _feature_matrix_entry_contract(entry)
2085
+ if actual_contract != expected_contract:
2086
+ matrix_contract_mismatches.append(
2087
+ {
2088
+ "kind": "entry-contract-drift",
2089
+ "key": list(witness.key),
2090
+ "expected": expected_contract,
2091
+ "actual": actual_contract,
2092
+ }
2093
+ )
2094
+ unexpected_keys = sorted(set(entry_by_key) - set(witness_keys))
2095
+ for key in unexpected_keys:
2096
+ matrix_contract_mismatches.append(
2097
+ {
2098
+ "kind": "unexpected-entry",
2099
+ "key": list(key),
2100
+ }
2101
+ )
2102
+
2103
+ converter = PythonToCConverter()
2104
+ execution_rows: list[dict[str, object]] = []
2105
+ witness_errors: list[str] = []
2106
+ diagnostic_counts: Counter[str] = Counter()
2107
+ state_counts: Counter[str] = Counter(item.state for item in witnesses)
2108
+ actual_status_counts: Counter[str] = Counter()
2109
+ for witness in (*witnesses, UNLISTED_DEFAULT_WITNESS):
2110
+ try:
2111
+ ast_kinds = _ast_kinds_for_witness(witness)
2112
+ except SyntaxError as exc:
2113
+ witness_errors.append(
2114
+ f"{witness.witness_id}: witness source is invalid Python: {exc}"
2115
+ )
2116
+ ast_kinds = set()
2117
+ missing_ast_kinds = sorted(
2118
+ set(witness.required_ast_kinds) - ast_kinds
2119
+ )
2120
+ if missing_ast_kinds:
2121
+ witness_errors.append(
2122
+ f"{witness.witness_id}: source does not contain required AST "
2123
+ f"kinds {missing_ast_kinds}"
2124
+ )
2125
+
2126
+ primary, primary_errors = _execute_feature_matrix_witness(
2127
+ converter,
2128
+ witness,
2129
+ profile_id="matrix-owner",
2130
+ request_options=dict(witness.request_options),
2131
+ expected_diagnostic=witness.expected_diagnostic,
2132
+ reason_contains=witness.actual_reason_contains,
2133
+ )
2134
+ witness_errors.extend(primary_errors)
2135
+ actual_status_counts[str(primary["actual_status"])] += 1
2136
+ diagnostic_counts.update(
2137
+ str(item) for item in primary["actual_diagnostics"]
2138
+ )
2139
+ profiles = [primary]
2140
+ for profile in witness.precedence_profiles:
2141
+ evidence, profile_errors = _execute_feature_matrix_witness(
2142
+ converter,
2143
+ witness,
2144
+ profile_id=profile.profile_id,
2145
+ request_options=profile.options(),
2146
+ expected_diagnostic=profile.expected_diagnostic,
2147
+ reason_contains=profile.reason_contains,
2148
+ )
2149
+ profiles.append(evidence)
2150
+ witness_errors.extend(profile_errors)
2151
+
2152
+ if (
2153
+ witness.diagnostic is not None
2154
+ and witness.diagnostic
2155
+ not in set(str(item) for item in primary["actual_diagnostics"])
2156
+ ):
2157
+ matrix_contract_mismatches.append(
2158
+ {
2159
+ "kind": "declared-diagnostic-drift",
2160
+ "key": list(witness.key),
2161
+ "profile_id": "matrix-owner",
2162
+ "expected": witness.diagnostic,
2163
+ "actual": primary["actual_diagnostics"],
2164
+ "actual_reasons": primary["actual_reasons"],
2165
+ }
2166
+ )
2167
+ execution_rows.append(
2168
+ {
2169
+ "witness_id": witness.witness_id,
2170
+ "matrix_key": list(witness.key),
2171
+ "state": witness.state,
2172
+ "exercise": witness.exercise,
2173
+ "required_ast_kinds": list(witness.required_ast_kinds),
2174
+ "observed_ast_kinds_sha256": _sha256_json(
2175
+ sorted(ast_kinds)
2176
+ ),
2177
+ "profiles": profiles,
2178
+ }
2179
+ )
2180
+
2181
+ if matrix_contract_mismatches:
2182
+ errors.append(
2183
+ "feature matrix contract mismatches were found: "
2184
+ f"{len(matrix_contract_mismatches)}"
2185
+ )
2186
+ errors.extend(witness_errors)
2187
+ witness_manifest = [item.manifest() for item in witnesses]
2188
+ report: dict[str, object] = {
2189
+ "audit": "checkpoint-e-executable-feature-matrix",
2190
+ "passed": not errors,
2191
+ "errors": errors,
2192
+ "matrix_path": "specifications/feature_matrix.json",
2193
+ "matrix_schema": matrix.get("schema"),
2194
+ "matrix_sha256": matrix_sha256,
2195
+ "expected_matrix_sha256": FEATURE_MATRIX_SHA256,
2196
+ "matrix_entry_count": len(entries),
2197
+ "expected_matrix_entry_count": FEATURE_MATRIX_ENTRY_COUNT,
2198
+ "matrix_state_counts": dict(sorted(state_counts.items())),
2199
+ "matrix_witness_count": len(witnesses),
2200
+ "unlisted_default_witness_count": 1,
2201
+ "total_primary_execution_count": len(execution_rows),
2202
+ "precedence_profile_execution_count": sum(
2203
+ len(row["profiles"]) - 1 for row in execution_rows
2204
+ ),
2205
+ "actual_status_counts": dict(sorted(actual_status_counts.items())),
2206
+ "actual_diagnostic_counts": dict(sorted(diagnostic_counts.items())),
2207
+ "matrix_contract_mismatches": matrix_contract_mismatches,
2208
+ "witness_errors": witness_errors,
2209
+ "witness_manifest_sha256": _sha256_json(witness_manifest),
2210
+ "execution_rows": execution_rows,
2211
+ "execution_sha256": _sha256_json(execution_rows),
2212
+ "coverage_complete": (
2213
+ len(witnesses) == FEATURE_MATRIX_ENTRY_COUNT
2214
+ and len(FEATURE_MATRIX_WITNESSES) == FEATURE_MATRIX_ENTRY_COUNT
2215
+ and not matrix_contract_mismatches
2216
+ and not witness_errors
2217
+ ),
2218
+ "c_toolchain_invoked": False,
2219
+ "generated_c_compiled_or_executed": False,
2220
+ }
2221
+ report["report_sha256"] = _sha256_json(report)
2222
+ return report
2223
+
2224
+
2225
+ def _walk_mappings(value: object) -> Iterable[Mapping[str, object]]:
2226
+ if isinstance(value, Mapping):
2227
+ yield value
2228
+ for child in value.values():
2229
+ yield from _walk_mappings(child)
2230
+ elif isinstance(value, (list, tuple)):
2231
+ for child in value:
2232
+ yield from _walk_mappings(child)
2233
+
2234
+
2235
+ _GENERATED_TEMPORARY = re.compile(
2236
+ r"\bpycf_[A-Za-z][A-Za-z0-9_]*_[0-9a-f]{12}\b"
2237
+ )
2238
+ _GENERATED_COLLISION_SUFFIX = re.compile(
2239
+ r"\b([A-Za-z][A-Za-z0-9_]*)_[2-9][0-9]*\b"
2240
+ )
2241
+
2242
+
2243
+ def _normalized_generated_c(text: str | None) -> str | None:
2244
+ """Alpha-rename source-ID-derived temporaries while retaining C semantics."""
2245
+
2246
+ if text is None:
2247
+ return None
2248
+ names: dict[str, str] = {}
2249
+
2250
+ def replace(match: re.Match[str]) -> str:
2251
+ name = match.group(0)
2252
+ replacement = names.get(name)
2253
+ if replacement is None:
2254
+ prefix = name.rsplit("_", 1)[0]
2255
+ replacement = f"{prefix}_METAVAR_{len(names):04d}"
2256
+ names[name] = replacement
2257
+ return replacement
2258
+
2259
+ temporaries_normalized = _GENERATED_TEMPORARY.sub(replace, text)
2260
+ return _GENERATED_COLLISION_SUFFIX.sub(r"\1", temporaries_normalized)
2261
+
2262
+
2263
+ def _semantic_shape(result: Any) -> dict[str, object]:
2264
+ """Discard source-derived IDs while preserving observable structure."""
2265
+
2266
+ summary = result.conversion_summary or {}
2267
+ artifact = result.stage_artifact
2268
+ payload = artifact.payload if artifact is not None else {}
2269
+ plans = payload.get("rule_plans", ())
2270
+ tables = payload.get("fact_tables", ())
2271
+ c_ir = payload.get("c_ir", {})
2272
+ plan_counts = Counter(
2273
+ (
2274
+ str(item.get("rule_id")),
2275
+ str(item.get("rule_version")),
2276
+ str(item.get("support_state")),
2277
+ )
2278
+ for item in plans
2279
+ if isinstance(item, Mapping)
2280
+ )
2281
+ c_ir_kinds = Counter(
2282
+ str(item["kind"])
2283
+ for item in _walk_mappings(c_ir)
2284
+ if isinstance(item.get("kind"), str)
2285
+ )
2286
+ feature_names = (
2287
+ "helpers",
2288
+ "functions",
2289
+ "calls",
2290
+ "containers",
2291
+ "container_accesses",
2292
+ "container_iterations",
2293
+ "modules",
2294
+ "module_imports",
2295
+ "records",
2296
+ "record_fields",
2297
+ "record_initializers",
2298
+ "record_instances",
2299
+ "record_bindings",
2300
+ "record_accesses",
2301
+ "numeric_operations",
2302
+ "conditional_regions",
2303
+ "keyword_calls",
2304
+ "keyword_only_calls",
2305
+ )
2306
+ return {
2307
+ "status": result.status.value,
2308
+ "diagnostics": [
2309
+ (item.code, item.severity.value, item.stage) for item in result.diagnostics
2310
+ ],
2311
+ "last_completed_stage": result.last_completed_stage,
2312
+ "stage_order": list(result.stage_order),
2313
+ "normalized_generated_c_sha256": (
2314
+ None
2315
+ if result.generated_c is None
2316
+ else hashlib.sha256(
2317
+ _normalized_generated_c(result.generated_c).encode("utf-8")
2318
+ ).hexdigest()
2319
+ ),
2320
+ "summary_contracts": {
2321
+ key: summary.get(key)
2322
+ for key in (
2323
+ "schema_version",
2324
+ "target_contract",
2325
+ "semantic_policy",
2326
+ "rule_set_version",
2327
+ "renderer_version",
2328
+ "helper_policy_version",
2329
+ "container_policy_version",
2330
+ "module_policy_version",
2331
+ "record_policy_version",
2332
+ "numeric_policy_version",
2333
+ "translation_unit_count",
2334
+ )
2335
+ },
2336
+ "feature_counts": {
2337
+ name: len(summary.get(name, ())) for name in feature_names
2338
+ },
2339
+ "artifact": None
2340
+ if artifact is None
2341
+ else {
2342
+ "kind": artifact.kind,
2343
+ "schema_version": artifact.schema_version,
2344
+ "payload_schema": payload.get("schema_version"),
2345
+ "c_ir_schema": payload.get("c_ir_schema"),
2346
+ },
2347
+ "helpers": list(payload.get("helper_requirements", ())),
2348
+ "rule_plan_counts": [
2349
+ [*key, count] for key, count in sorted(plan_counts.items())
2350
+ ],
2351
+ "fact_tables": sorted(
2352
+ [
2353
+ [
2354
+ str(item.get("table_id")),
2355
+ str(item.get("schema_version")),
2356
+ len(item.get("records", ())),
2357
+ ]
2358
+ for item in tables
2359
+ if isinstance(item, Mapping)
2360
+ ]
2361
+ ),
2362
+ "c_ir_kind_counts": [
2363
+ [kind, count] for kind, count in sorted(c_ir_kinds.items())
2364
+ ],
2365
+ }
2366
+
2367
+
2368
+ def _transform_case(
2369
+ case: SupportedSubsetCase,
2370
+ transformation: str,
2371
+ ) -> SupportedSubsetCase:
2372
+ def transform(text: str) -> str:
2373
+ if transformation == "leading-comment":
2374
+ return "# Checkpoint E metamorphic witness\n" + text
2375
+ if transformation == "crlf":
2376
+ return text.replace("\n", "\r\n")
2377
+ if transformation == "blank-lines":
2378
+ return text.replace("\n\n", "\n\n\n") + "\n"
2379
+ raise ValueError(f"unknown transformation: {transformation}")
2380
+
2381
+ return SupportedSubsetCase(
2382
+ case_id=f"{case.case_id}:{transformation}",
2383
+ family=case.family,
2384
+ primary_text=transform(case.primary_text),
2385
+ primary_name=case.primary_name,
2386
+ primary_module=case.primary_module,
2387
+ companions=tuple(
2388
+ (module_id, logical_name, transform(text))
2389
+ for module_id, logical_name, text in case.companions
2390
+ ),
2391
+ )
2392
+
2393
+
2394
+ def audit_full_supported_subset(
2395
+ *,
2396
+ seed: int = FUZZ_SEED,
2397
+ fuzz_case_count: int = DEFAULT_FUZZ_CASE_COUNT,
2398
+ ) -> dict[str, object]:
2399
+ """Run deterministic, observer-invariant, metamorphic source-only checks."""
2400
+
2401
+ fixed = fixed_supported_subset_cases()
2402
+ generated = generated_fuzz_cases(seed=seed, count=fuzz_case_count)
2403
+ cases = (*fixed, *generated)
2404
+ errors: list[str] = []
2405
+ families = {case.family for case in fixed}
2406
+ generated_family_counts = Counter(case.family for case in generated)
2407
+ generated_missing_families = sorted(
2408
+ SUPPORTED_SUBSET_FAMILIES - set(generated_family_counts)
2409
+ )
2410
+ missing_families = sorted(SUPPORTED_SUBSET_FAMILIES - families)
2411
+ if missing_families:
2412
+ errors.append(
2413
+ "fixed corpus lacks supported families: " + ", ".join(missing_families)
2414
+ )
2415
+ if (
2416
+ fuzz_case_count >= len(SUPPORTED_SUBSET_FAMILIES)
2417
+ and generated_missing_families
2418
+ ):
2419
+ errors.append(
2420
+ "generated corpus lacks promoted families despite a complete "
2421
+ "family cycle: " + ", ".join(generated_missing_families)
2422
+ )
2423
+
2424
+ converter = PythonToCConverter()
2425
+ transformations = ("leading-comment", "crlf", "blank-lines")
2426
+ case_digests: list[dict[str, str]] = []
2427
+ for case in cases:
2428
+ full_observation = ObservationOptions("Full", True)
2429
+ none_observation = ObservationOptions("None", False)
2430
+ first = converter.convert(case.request(), observation=full_observation)
2431
+ repeated = converter.convert(case.request(), observation=full_observation)
2432
+ first_json = result_to_json(first)
2433
+ repeated_json = result_to_json(repeated)
2434
+ if first_json != repeated_json:
2435
+ errors.append(f"{case.case_id}: repeated conversion is not byte-stable")
2436
+ if first.status is not ResultStatus.CONVERTED:
2437
+ errors.append(
2438
+ f"{case.case_id}: expected Converted, got {first.status.value} "
2439
+ f"({','.join(item.code for item in first.diagnostics)})"
2440
+ )
2441
+ if first.generated_c is None:
2442
+ errors.append(f"{case.case_id}: converted result did not publish C text")
2443
+ else:
2444
+ conformance = validate_c_text(first.generated_c)
2445
+ if not conformance.accepted:
2446
+ errors.append(
2447
+ f"{case.case_id}: emitted C text failed source conformance: "
2448
+ f"{conformance.message}"
2449
+ )
2450
+
2451
+ unobserved = converter.convert(case.request(), observation=none_observation)
2452
+ if (
2453
+ result_to_json(first, include_observers=False)
2454
+ != result_to_json(unobserved, include_observers=False)
2455
+ ):
2456
+ errors.append(f"{case.case_id}: observer settings changed semantics")
2457
+
2458
+ base_shape = _semantic_shape(first)
2459
+ for transformation in transformations:
2460
+ variant = _transform_case(case, transformation)
2461
+ transformed = converter.convert(
2462
+ variant.request(),
2463
+ observation=full_observation,
2464
+ )
2465
+ if _semantic_shape(transformed) != base_shape:
2466
+ errors.append(
2467
+ f"{case.case_id}: {transformation} changed semantic shape"
2468
+ )
2469
+ if transformed.generated_c is not None:
2470
+ conformance = validate_c_text(transformed.generated_c)
2471
+ if not conformance.accepted:
2472
+ errors.append(
2473
+ f"{case.case_id}: {transformation} emitted "
2474
+ f"nonconforming C text: {conformance.message}"
2475
+ )
2476
+ case_digests.append(
2477
+ {
2478
+ "case_id": case.case_id,
2479
+ "result_sha256": hashlib.sha256(
2480
+ first_json.encode("utf-8")
2481
+ ).hexdigest(),
2482
+ "shape_sha256": _sha256_json(base_shape),
2483
+ }
2484
+ )
2485
+
2486
+ corpus_manifest = [case.manifest() for case in cases]
2487
+ report: dict[str, object] = {
2488
+ "audit": "checkpoint-e-full-supported-subset",
2489
+ "passed": not errors,
2490
+ "errors": errors,
2491
+ "seed": seed,
2492
+ "fixed_case_count": len(fixed),
2493
+ "generated_case_count": len(generated),
2494
+ "case_count": len(cases),
2495
+ "metamorphic_transformations": list(transformations),
2496
+ "metamorphic_conversion_count": len(cases) * len(transformations),
2497
+ "supported_families": sorted(families),
2498
+ "missing_supported_families": missing_families,
2499
+ "generated_family_counts": dict(sorted(generated_family_counts.items())),
2500
+ "generated_missing_families": generated_missing_families,
2501
+ "generated_complete_family_cycles": (
2502
+ fuzz_case_count // len(SUPPORTED_SUBSET_FAMILIES)
2503
+ ),
2504
+ "generated_feature_counts": {
2505
+ "fixed-container-iteration": sum(
2506
+ count
2507
+ for family, count in generated_family_counts.items()
2508
+ if family in {"dict", "list"}
2509
+ ),
2510
+ "floating-division": generated_family_counts.get(
2511
+ "assignments-and-arithmetic",
2512
+ 0,
2513
+ ),
2514
+ },
2515
+ "fixed_feature_witnesses": {
2516
+ "fixed-container-iteration": "fixed-list",
2517
+ "floating-division": "fixed-arithmetic",
2518
+ },
2519
+ "corpus_sha256": _sha256_json(corpus_manifest),
2520
+ "case_digests": case_digests,
2521
+ "c_toolchain_invoked": False,
2522
+ "generated_c_compiled_or_executed": False,
2523
+ }
2524
+ report["report_sha256"] = _sha256_json(report)
2525
+ return report
2526
+
2527
+
2528
+ _FORBIDDEN_IMPORT_ROOTS = {
2529
+ "cffi",
2530
+ "ctypes",
2531
+ "pexpect",
2532
+ "subprocess",
2533
+ }
2534
+ _FORBIDDEN_BUILTIN_CALL_NAMES = {
2535
+ "compile",
2536
+ "eval",
2537
+ "exec",
2538
+ }
2539
+ _FORBIDDEN_ATTRIBUTE_CALL_NAMES = {
2540
+ "popen",
2541
+ "system",
2542
+ }
2543
+ _FORBIDDEN_ACTION_LABELS = {
2544
+ "Build",
2545
+ "Debug",
2546
+ "Plugin Marketplace",
2547
+ "Project Explorer",
2548
+ "Run",
2549
+ "Terminal",
2550
+ }
2551
+ _EXTERNAL_BRANDS = {
2552
+ "chatgpt",
2553
+ "codex",
2554
+ "jetbrains",
2555
+ "openai",
2556
+ "pycharm",
2557
+ "visual studio code",
2558
+ "vscode",
2559
+ }
2560
+ def scan_product_boundary(root: Path) -> list[str]:
2561
+ """Find runtime imports, calls, labels, and brands outside product scope."""
2562
+
2563
+ violations: list[str] = []
2564
+ runtime_roots = (
2565
+ root / "pycforge" / "converter",
2566
+ root / "pycforge" / "ide",
2567
+ )
2568
+ for runtime_root in runtime_roots:
2569
+ if not runtime_root.exists():
2570
+ continue
2571
+ for path in sorted(runtime_root.rglob("*.py")):
2572
+ relative = path.relative_to(root).as_posix()
2573
+ try:
2574
+ tree = ast.parse(path.read_text(encoding="utf-8"), filename=relative)
2575
+ except (OSError, SyntaxError) as exc:
2576
+ violations.append(f"{relative}: cannot inspect source: {exc}")
2577
+ continue
2578
+ for node in ast.walk(tree):
2579
+ if isinstance(node, ast.Import):
2580
+ imported = [alias.name for alias in node.names]
2581
+ elif isinstance(node, ast.ImportFrom) and node.module:
2582
+ imported = [node.module]
2583
+ else:
2584
+ imported = []
2585
+ for name in imported:
2586
+ if name.split(".", 1)[0] in _FORBIDDEN_IMPORT_ROOTS:
2587
+ violations.append(
2588
+ f"{relative}:{node.lineno}: forbidden runtime import {name}"
2589
+ )
2590
+ if isinstance(node, ast.Call):
2591
+ if isinstance(node.func, ast.Name):
2592
+ call_name = node.func.id
2593
+ forbidden_call = (
2594
+ call_name in _FORBIDDEN_BUILTIN_CALL_NAMES
2595
+ )
2596
+ elif isinstance(node.func, ast.Attribute):
2597
+ call_name = node.func.attr
2598
+ forbidden_call = (
2599
+ call_name.casefold()
2600
+ in _FORBIDDEN_ATTRIBUTE_CALL_NAMES
2601
+ )
2602
+ else:
2603
+ call_name = ""
2604
+ forbidden_call = False
2605
+ if forbidden_call:
2606
+ violations.append(
2607
+ f"{relative}:{node.lineno}: forbidden runtime call "
2608
+ f"{call_name}"
2609
+ )
2610
+ if isinstance(node, ast.Constant) and isinstance(node.value, str):
2611
+ normalized = " ".join(node.value.split()).casefold()
2612
+ if node.value.strip() in _FORBIDDEN_ACTION_LABELS:
2613
+ violations.append(
2614
+ f"{relative}:{node.lineno}: out-of-scope action label "
2615
+ f"{node.value!r}"
2616
+ )
2617
+ for brand in sorted(_EXTERNAL_BRANDS):
2618
+ if brand in normalized:
2619
+ violations.append(
2620
+ f"{relative}:{node.lineno}: external brand {brand!r}"
2621
+ )
2622
+ pyproject = root / "pyproject.toml"
2623
+ if not pyproject.is_file():
2624
+ violations.append("missing pyproject.toml")
2625
+ else:
2626
+ try:
2627
+ metadata = tomllib.loads(pyproject.read_text(encoding="utf-8"))
2628
+ except (OSError, tomllib.TOMLDecodeError) as exc:
2629
+ violations.append(f"pyproject.toml cannot be inspected: {exc}")
2630
+ else:
2631
+ project = metadata.get("project", {})
2632
+ if project.get("name") != "pycforge":
2633
+ violations.append("project name is not exactly pycforge")
2634
+ scripts = project.get("scripts", {})
2635
+ if not isinstance(scripts, dict):
2636
+ violations.append("project scripts are not a TOML table")
2637
+ else:
2638
+ allowed_scripts = {"pycforge", "pycforge-workspace"}
2639
+ unexpected = sorted(set(scripts) - allowed_scripts)
2640
+ if unexpected:
2641
+ violations.append(
2642
+ "out-of-scope project scripts: " + ", ".join(unexpected)
2643
+ )
2644
+ return sorted(set(violations))
2645
+
2646
+
2647
+ def audit_architecture_branding_product_boundary(root: Path) -> dict[str, object]:
2648
+ """Combine the cumulative architecture gate with Checkpoint E boundaries."""
2649
+
2650
+ from pycforge.laboratory.audits import audit_architecture
2651
+
2652
+ cumulative = audit_architecture(root)
2653
+ identities = current_contract_identities()
2654
+ identity_mismatches = {
2655
+ key: {"expected": expected, "actual": identities.get(key)}
2656
+ for key, expected in EXPECTED_CONTRACT_IDENTITIES.items()
2657
+ if identities.get(key) != expected
2658
+ }
2659
+ boundary_violations = scan_product_boundary(root)
2660
+ errors = [
2661
+ *(
2662
+ ["cumulative architecture audit failed"]
2663
+ if not cumulative.get("passed")
2664
+ else []
2665
+ ),
2666
+ *(
2667
+ ["frozen 0.14.3 identities changed"]
2668
+ if identity_mismatches
2669
+ else []
2670
+ ),
2671
+ *boundary_violations,
2672
+ ]
2673
+ return {
2674
+ "audit": "checkpoint-e-architecture-branding-product-boundary",
2675
+ "passed": not errors,
2676
+ "errors": errors,
2677
+ "identity_mismatches": identity_mismatches,
2678
+ "contract_identities": identities,
2679
+ "cumulative_architecture": cumulative,
2680
+ "boundary_violations": boundary_violations,
2681
+ "front_facing_brand": "PyCForge",
2682
+ "product_boundary": "deterministic Python-to-C source transpiler",
2683
+ "c_toolchain_invoked": False,
2684
+ "generated_c_compiled_or_executed": False,
2685
+ }