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,481 @@
1
+ """Authenticated laboratory audit for required keyword-only direct calls."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import hashlib
6
+ import json
7
+ import subprocess
8
+ import sys
9
+ from pathlib import Path
10
+ from typing import Any
11
+
12
+ from .keyword_audit import _facts, _lowering_proof, _table
13
+
14
+
15
+ def audit_keyword_only(root: Path) -> dict[str, Any]:
16
+ """Gate the bounded Phase 14D profile without invoking a C toolchain."""
17
+
18
+ from pycforge import (
19
+ ConversionRequest,
20
+ PythonToCConverter,
21
+ ResultStatus,
22
+ SourceBundle,
23
+ SourceDocumentInput,
24
+ )
25
+ from pycforge.converter.c_output import validate_c_text
26
+ from pycforge.converter.contracts.configuration import (
27
+ DEFAULT_RENDERER,
28
+ DEFAULT_RULE_SET,
29
+ PHASE14C_RENDERER,
30
+ PHASE14C_RULE_SET,
31
+ )
32
+ from pycforge.converter.contracts.versions import (
33
+ C_IR_SCHEMA,
34
+ CONVERSION_PLAN_SCHEMA,
35
+ CONVERSION_SUMMARY_SCHEMA,
36
+ DECISION_TRACE_SCHEMA,
37
+ GENERATED_C_SCHEMA,
38
+ KEYWORD_ONLY_CALL_FACT_SCHEMA,
39
+ PHASE14C_CONVERSION_PLAN_SCHEMA,
40
+ PHASE14C_CONVERSION_SUMMARY_SCHEMA,
41
+ PHASE14C_DECISION_TRACE_SCHEMA,
42
+ )
43
+ from pycforge.converter.core.request import ObservationOptions
44
+ from pycforge.converter.core.serialization import result_to_json
45
+ from pycforge.converter.keyword_only_calls import (
46
+ KEYWORD_ONLY_CALL_KEY_DOMAIN,
47
+ KEYWORD_ONLY_CALL_LOWERING_SHAPE,
48
+ KEYWORD_ONLY_CALL_OBLIGATIONS,
49
+ KEYWORD_ONLY_CALL_PROVENANCE_EVIDENCE,
50
+ KEYWORD_ONLY_CALL_RULE_ID,
51
+ KEYWORD_ONLY_CALL_RULE_VERSION,
52
+ KEYWORD_ONLY_CALL_TABLE_DEPENDENCIES,
53
+ KEYWORD_ONLY_CALL_TABLE_ID,
54
+ validate_keyword_only_call_binding_facts,
55
+ )
56
+ from pycforge.converter.support_templates import default_helper_registry
57
+
58
+ source = (
59
+ "def mark_int(value: int) -> int:\n return value\n\n"
60
+ "def mark_bool(value: bool) -> bool:\n return value\n\n"
61
+ "def mark_float(value: float) -> float:\n return value\n\n"
62
+ "def choose(left: int, *, flag: bool, ratio: float) -> int:\n"
63
+ " return left\n\n"
64
+ "def run(x: int, y: bool, z: float) -> int:\n"
65
+ " return choose(ratio=mark_float(z), left=mark_int(x), flag=mark_bool(y))\n"
66
+ )
67
+ cross_primary = (
68
+ "from lib import choose\n\n"
69
+ "def run(value: int, flag: bool, ratio: float) -> int:\n"
70
+ " return choose(ratio=ratio, flag=flag, left=value)\n"
71
+ )
72
+ cross_companion = (
73
+ "def choose(left: int, *, flag: bool, ratio: float) -> int:\n"
74
+ " return left\n"
75
+ )
76
+ required_sink = (
77
+ "def sink(value: int, *, flag: bool) -> int:\n"
78
+ " return value\n\n"
79
+ )
80
+ rejections = {
81
+ "missing-keyword": (
82
+ "PYC2904",
83
+ required_sink
84
+ + "def run(value: int) -> int:\n return sink(value)\n",
85
+ ),
86
+ "keyword-only-positional": (
87
+ "PYC2904",
88
+ required_sink
89
+ + "def run(value: int, flag: bool) -> int:\n"
90
+ " return sink(value, flag)\n",
91
+ ),
92
+ "unknown-keyword": (
93
+ "PYC2912",
94
+ required_sink
95
+ + "def run(value: int, flag: bool) -> int:\n"
96
+ " return sink(value=value, missing=flag)\n",
97
+ ),
98
+ "duplicate-binding": (
99
+ "PYC2912",
100
+ required_sink
101
+ + "def run(value: int, flag: bool) -> int:\n"
102
+ " return sink(value, value=value, flag=flag)\n",
103
+ ),
104
+ "keyword-unpacking": (
105
+ "PYC2910",
106
+ required_sink
107
+ + "def run(value: int, flag: bool) -> int:\n"
108
+ " return sink(value=value, **flag)\n",
109
+ ),
110
+ "mapped-category": (
111
+ "PYC2905",
112
+ required_sink
113
+ + "def run(value: int, flag: bool) -> int:\n"
114
+ " return sink(value=flag, flag=value)\n",
115
+ ),
116
+ "defaulted-keyword-only": (
117
+ "PYC2911",
118
+ "def sink(value: int, *, flag: bool = True) -> int:\n"
119
+ " return value\n\n"
120
+ "def run(value: int, flag: bool) -> int:\n"
121
+ " return sink(value=value, flag=flag)\n",
122
+ ),
123
+ "variadic-target": (
124
+ "PYC2911",
125
+ "def sink(value: int, *rest: int, flag: bool) -> int:\n"
126
+ " return value\n\n"
127
+ "def run(value: int, flag: bool) -> int:\n"
128
+ " return sink(value=value, flag=flag)\n",
129
+ ),
130
+ "recursive-target": (
131
+ "PYC2920",
132
+ "def run(value: int, *, flag: bool) -> int:\n"
133
+ " return run(value=value, flag=flag)\n",
134
+ ),
135
+ }
136
+ expected_contracts = {
137
+ "rule_set": "phase14-required-keyword-only-calls-v0.14.3",
138
+ "renderer": "c-renderer-v0.14.3",
139
+ "keyword_only_call_facts": "fact-table/0.14.3",
140
+ "conversion_plan": "conversion-plan/0.14.3",
141
+ "c_ir": "c-ir/0.14.3",
142
+ "generated_c": "generated-c/0.14.3",
143
+ "conversion_summary": "pycforge.conversion-summary/0.14.3",
144
+ "decision_trace": "pycforge.decision-trace/0.14.3",
145
+ }
146
+
147
+ try:
148
+ converter = PythonToCConverter()
149
+ observation = ObservationOptions("Full", False)
150
+ accepted = converter.convert(
151
+ ConversionRequest.from_source(source),
152
+ observation=observation,
153
+ )
154
+ repeated = converter.convert(
155
+ ConversionRequest.from_source(source),
156
+ observation=observation,
157
+ )
158
+ if accepted.stage_artifact is None:
159
+ raise ValueError("keyword-only conversion omitted its final artifact")
160
+ payload = accepted.stage_artifact.payload
161
+ summary = accepted.conversion_summary or {}
162
+ trace = accepted.decision_trace or {}
163
+ contracts = {
164
+ "rule_set": DEFAULT_RULE_SET,
165
+ "renderer": DEFAULT_RENDERER,
166
+ "keyword_only_call_facts": KEYWORD_ONLY_CALL_FACT_SCHEMA,
167
+ "conversion_plan": CONVERSION_PLAN_SCHEMA,
168
+ "c_ir": C_IR_SCHEMA,
169
+ "generated_c": GENERATED_C_SCHEMA,
170
+ "conversion_summary": CONVERSION_SUMMARY_SCHEMA,
171
+ "decision_trace": DECISION_TRACE_SCHEMA,
172
+ }
173
+ identity_valid = (
174
+ contracts == expected_contracts
175
+ and accepted.status is ResultStatus.CONVERTED
176
+ and accepted.stage_artifact.kind == "generated_c"
177
+ and accepted.stage_artifact.schema_version == "0.14.3"
178
+ and payload.get("schema_version") == GENERATED_C_SCHEMA
179
+ and payload.get("c_ir_schema") == C_IR_SCHEMA
180
+ and payload.get("rule_set_version") == DEFAULT_RULE_SET
181
+ and payload.get("renderer_version") == DEFAULT_RENDERER
182
+ and summary.get("schema_version") == CONVERSION_SUMMARY_SCHEMA
183
+ and trace.get("schema_version") == DECISION_TRACE_SCHEMA
184
+ )
185
+ table = _table(payload, KEYWORD_ONLY_CALL_TABLE_ID)
186
+ records = list(table.get("records", ()))
187
+ facts = _facts(payload, KEYWORD_ONLY_CALL_TABLE_ID)
188
+ fact = facts[0] if len(facts) == 1 else {}
189
+ constants_valid = (
190
+ KEYWORD_ONLY_CALL_TABLE_ID == "keyword-only-call-binding-facts"
191
+ and KEYWORD_ONLY_CALL_KEY_DOMAIN == "keyword-only-call-node-id"
192
+ and KEYWORD_ONLY_CALL_RULE_ID
193
+ == "phase14.keyword_only_call.exact_binding"
194
+ and KEYWORD_ONLY_CALL_RULE_VERSION == "0.14.3"
195
+ and KEYWORD_ONLY_CALL_LOWERING_SHAPE
196
+ == "source-order-actual-temporaries-formal-order-references-v1"
197
+ )
198
+ table_valid = (
199
+ table.get("schema_version") == KEYWORD_ONLY_CALL_FACT_SCHEMA
200
+ and table.get("producer_stage") == "analysis.plan"
201
+ and table.get("key_domain") == KEYWORD_ONLY_CALL_KEY_DOMAIN
202
+ and table.get("completeness") == "complete"
203
+ and tuple(table.get("invalidation_dependencies", ()))
204
+ == KEYWORD_ONLY_CALL_TABLE_DEPENDENCIES
205
+ and len(records) == 1
206
+ and records[0].get("key") == fact.get("call_node_id")
207
+ and tuple(records[0].get("provenance", {}).get("evidence", ()))
208
+ == KEYWORD_ONLY_CALL_PROVENANCE_EVIDENCE
209
+ )
210
+ fact_valid = (
211
+ fact.get("supported") is True
212
+ and fact.get("parameter_names") == ["left", "flag", "ratio"]
213
+ and fact.get("parameter_kinds")
214
+ == ["positional-or-keyword", "keyword-only", "keyword-only"]
215
+ and fact.get("keyword_names") == ["ratio", "left", "flag"]
216
+ and fact.get("source_to_parameter_ordinals") == [2, 0, 1]
217
+ and fact.get("parameter_to_source_ordinals") == [1, 2, 0]
218
+ and fact.get("evaluation_order")
219
+ == fact.get("source_argument_node_ids")
220
+ and fact.get("arguments_evaluated_once") is True
221
+ and fact.get("parameter_coverage_exact") is True
222
+ and fact.get("keyword_only_coverage_exact") is True
223
+ and fact.get("lowering_shape") == KEYWORD_ONLY_CALL_LOWERING_SHAPE
224
+ and fact.get("allocation_model") == "none"
225
+ and fact.get("cleanup_model") == "none"
226
+ and fact.get("runtime_binding_failure") == "proved-absent"
227
+ )
228
+ independent_valid, independent_reason = (
229
+ validate_keyword_only_call_binding_facts(payload)
230
+ )
231
+ plans = [
232
+ item
233
+ for item in payload.get("rule_plans", ())
234
+ if item.get("rule_id") == KEYWORD_ONLY_CALL_RULE_ID
235
+ ]
236
+ plan_valid = (
237
+ len(plans) == 1
238
+ and plans[0].get("source_node_id") == fact.get("call_node_id")
239
+ and plans[0].get("rule_version") == KEYWORD_ONLY_CALL_RULE_VERSION
240
+ and plans[0].get("support_state") == "SupportedDirect"
241
+ and plans[0].get("helper_requirements") == []
242
+ and plans[0].get("semantic_obligations")[: len(KEYWORD_ONLY_CALL_OBLIGATIONS)]
243
+ == list(KEYWORD_ONLY_CALL_OBLIGATIONS)
244
+ and plans[0].get("resolved_obligations")
245
+ == plans[0].get("semantic_obligations")
246
+ and plans[0].get("unresolved_obligations") == []
247
+ )
248
+ lowering_valid, lowering_evidence = _lowering_proof(payload, facts)
249
+ observer_valid = (
250
+ list(summary.get("keyword_only_calls", ())) == facts
251
+ and [
252
+ item
253
+ for item in trace.get("rule_decisions", ())
254
+ if item.get("rule_id") == KEYWORD_ONLY_CALL_RULE_ID
255
+ ]
256
+ == plans
257
+ and trace.get("trace_level") == "Full"
258
+ and trace.get("completeness") == "complete"
259
+ and trace.get("truncated") is False
260
+ and trace.get("observer_failed") is False
261
+ )
262
+ helper_registry = default_helper_registry()
263
+ helper_valid = (
264
+ payload.get("helper_manifest") == []
265
+ and payload.get("helper_requirements") == []
266
+ and helper_registry.fingerprint
267
+ == "fcb719f07984f3b79e17262e90f93823a9a0139a60529f8486ab09f6c3663d98"
268
+ )
269
+ deterministic = (
270
+ result_to_json(accepted) == result_to_json(repeated)
271
+ and accepted.request_fingerprint == repeated.request_fingerprint
272
+ and accepted.output_fingerprint == repeated.output_fingerprint
273
+ and accepted.stage_artifact.artifact_fingerprint
274
+ == repeated.stage_artifact.artifact_fingerprint
275
+ )
276
+ fresh_code = (
277
+ "import hashlib,json; "
278
+ "from pycforge import ConversionRequest,PythonToCConverter; "
279
+ "from pycforge.converter.core.request import ObservationOptions; "
280
+ "from pycforge.converter.core.serialization import result_to_json; "
281
+ f"s={source!r}; "
282
+ "r=PythonToCConverter().convert(ConversionRequest.from_source(s),"
283
+ "observation=ObservationOptions('Full',False)); "
284
+ "print(json.dumps({'serialized_sha256':hashlib.sha256(result_to_json(r).encode()).hexdigest(),"
285
+ "'generated_sha256':hashlib.sha256((r.generated_c or '').encode()).hexdigest(),"
286
+ "'artifact_fingerprint':r.stage_artifact.artifact_fingerprint.value},sort_keys=True))"
287
+ )
288
+ fresh = json.loads(
289
+ subprocess.check_output(
290
+ [sys.executable, "-c", fresh_code],
291
+ cwd=root,
292
+ env={**__import__("os").environ, "PYTHONPATH": str(root)},
293
+ text=True,
294
+ )
295
+ )
296
+ fresh_expected = {
297
+ "serialized_sha256": hashlib.sha256(
298
+ result_to_json(accepted).encode()
299
+ ).hexdigest(),
300
+ "generated_sha256": hashlib.sha256(
301
+ (accepted.generated_c or "").encode()
302
+ ).hexdigest(),
303
+ "artifact_fingerprint": accepted.stage_artifact.artifact_fingerprint.value,
304
+ }
305
+ fresh_valid = fresh == fresh_expected
306
+
307
+ cross_request = ConversionRequest(
308
+ SourceBundle(
309
+ SourceDocumentInput("app.py", cross_primary, "app"),
310
+ (SourceDocumentInput("lib.py", cross_companion, "lib"),),
311
+ )
312
+ )
313
+ cross = converter.convert(cross_request, observation=observation)
314
+ cross_repeated = converter.convert(cross_request, observation=observation)
315
+ cross_payload = (
316
+ cross.stage_artifact.payload if cross.stage_artifact is not None else {}
317
+ )
318
+ cross_facts = (
319
+ _facts(cross_payload, KEYWORD_ONLY_CALL_TABLE_ID)
320
+ if cross_payload
321
+ else []
322
+ )
323
+ cross_fact = cross_facts[0] if len(cross_facts) == 1 else {}
324
+ cross_independent, cross_reason = (
325
+ validate_keyword_only_call_binding_facts(cross_payload)
326
+ if cross_payload
327
+ else (False, "cross-module conversion omitted its payload")
328
+ )
329
+ cross_lowering, cross_lowering_evidence = _lowering_proof(
330
+ cross_payload,
331
+ cross_facts,
332
+ )
333
+ cross_valid = (
334
+ cross.status is ResultStatus.CONVERTED
335
+ and cross.stage_artifact is not None
336
+ and cross.stage_artifact.schema_version == "0.14.3"
337
+ and cross_fact.get("source_to_parameter_ordinals") == [2, 1, 0]
338
+ and cross_fact.get("parameter_to_source_ordinals") == [2, 1, 0]
339
+ and (cross.conversion_summary or {})
340
+ .get("module_initialization", {})
341
+ .get("module_order")
342
+ == ["lib", "app"]
343
+ and cross_independent
344
+ and cross_lowering
345
+ and result_to_json(cross) == result_to_json(cross_repeated)
346
+ and validate_c_text(cross.generated_c or "").accepted
347
+ and cross_payload.get("helper_manifest") == []
348
+ )
349
+
350
+ rejected = {
351
+ label: converter.convert(ConversionRequest.from_source(text))
352
+ for label, (_, text) in rejections.items()
353
+ }
354
+ rejection_codes = {
355
+ label: [item.code for item in result.diagnostics]
356
+ for label, result in rejected.items()
357
+ }
358
+ rejections_valid = all(
359
+ result.status is ResultStatus.REJECTED
360
+ and rejection_codes[label] == [code]
361
+ and result.generated_c is None
362
+ and result.output_fingerprint is None
363
+ and result.stage_artifact is not None
364
+ and "c_ir" not in result.stage_artifact.payload
365
+ and "helper_manifest" not in result.stage_artifact.payload
366
+ for label, (code, _) in rejections.items()
367
+ for result in (rejected[label],)
368
+ )
369
+
370
+ historical = converter.convert(
371
+ ConversionRequest.from_source(
372
+ source,
373
+ rule_set_version=PHASE14C_RULE_SET,
374
+ renderer_version=PHASE14C_RENDERER,
375
+ ),
376
+ observation=observation,
377
+ )
378
+ historical_payload = (
379
+ historical.stage_artifact.payload
380
+ if historical.stage_artifact is not None
381
+ else {}
382
+ )
383
+ historical_exact = (
384
+ historical.status is ResultStatus.REJECTED
385
+ and [item.code for item in historical.diagnostics] == ["PYC2911"]
386
+ and historical.stage_artifact is not None
387
+ and historical.stage_artifact.kind == "conversion_plan"
388
+ and historical.stage_artifact.schema_version == "0.14.2"
389
+ and historical_payload.get("schema_version")
390
+ == PHASE14C_CONVERSION_PLAN_SCHEMA
391
+ and (historical.conversion_summary or {}).get("schema_version")
392
+ == PHASE14C_CONVERSION_SUMMARY_SCHEMA
393
+ and (historical.decision_trace or {}).get("schema_version")
394
+ == PHASE14C_DECISION_TRACE_SCHEMA
395
+ and KEYWORD_ONLY_CALL_TABLE_ID
396
+ not in {
397
+ item.get("table_id")
398
+ for item in historical_payload.get("fact_tables", ())
399
+ }
400
+ and historical.generated_c is None
401
+ and historical.output_fingerprint is None
402
+ )
403
+ generated_conformance = validate_c_text(accepted.generated_c or "").accepted
404
+ passed = all(
405
+ (
406
+ identity_valid,
407
+ constants_valid,
408
+ table_valid,
409
+ fact_valid,
410
+ independent_valid,
411
+ plan_valid,
412
+ lowering_valid,
413
+ observer_valid,
414
+ helper_valid,
415
+ generated_conformance,
416
+ deterministic,
417
+ fresh_valid,
418
+ cross_valid,
419
+ rejections_valid,
420
+ historical_exact,
421
+ )
422
+ )
423
+ return {
424
+ "audit": "keyword-only",
425
+ "passed": passed,
426
+ "active_contracts": contracts,
427
+ "active_contract_identities_valid": identity_valid,
428
+ "keyword_only_rule_id": KEYWORD_ONLY_CALL_RULE_ID,
429
+ "keyword_only_rule_version": KEYWORD_ONLY_CALL_RULE_VERSION,
430
+ "keyword_only_fact_schema": KEYWORD_ONLY_CALL_FACT_SCHEMA,
431
+ "keyword_only_contract_constants_valid": constants_valid,
432
+ "keyword_only_fact_count": len(facts),
433
+ "keyword_only_fact_table_valid": table_valid,
434
+ "keyword_only_facts_valid": fact_valid,
435
+ "keyword_only_rule_plan_count": len(plans),
436
+ "keyword_only_rule_plans_valid": plan_valid,
437
+ "independent_fact_and_plan_validation": independent_valid,
438
+ "independent_validation_message": independent_reason,
439
+ "source_to_parameter_ordinals": fact.get(
440
+ "source_to_parameter_ordinals"
441
+ ),
442
+ "parameter_to_source_ordinals": fact.get(
443
+ "parameter_to_source_ordinals"
444
+ ),
445
+ "source_order_staging_and_formal_reference_permutation_valid": lowering_valid,
446
+ "lowering_evidence": lowering_evidence,
447
+ "observer_evidence_valid": observer_valid,
448
+ "cross_module_valid": cross_valid,
449
+ "cross_module_independent_validation": cross_independent,
450
+ "cross_module_independent_validation_message": cross_reason,
451
+ "cross_module_lowering_evidence": cross_lowering_evidence,
452
+ "rejection_codes": rejection_codes,
453
+ "rejections_exact_and_publish_no_c": rejections_valid,
454
+ "historical_phase14c_exact_rejection": historical_exact,
455
+ "helper_manifest_empty": payload.get("helper_manifest") == [],
456
+ "helper_registry_fingerprint": helper_registry.fingerprint,
457
+ "helper_contract_unchanged": helper_valid,
458
+ "allocation": "none",
459
+ "cleanup": "none",
460
+ "runtime_binding_failure": "proved-absent",
461
+ "generated_c_conformance": generated_conformance,
462
+ "deterministic": deterministic,
463
+ "fresh_process_deterministic": fresh_valid,
464
+ "serialized_sha256": fresh_expected["serialized_sha256"],
465
+ "generated_c_sha256": fresh_expected["generated_sha256"],
466
+ "python_subprocess_used_for_determinism_only": True,
467
+ "c_toolchain_invoked": False,
468
+ "generated_c_compiled_or_executed": False,
469
+ }
470
+ except Exception as exc:
471
+ return {
472
+ "audit": "keyword-only",
473
+ "passed": False,
474
+ "error": f"{type(exc).__name__}: {exc}",
475
+ "python_subprocess_used_for_determinism_only": True,
476
+ "c_toolchain_invoked": False,
477
+ "generated_c_compiled_or_executed": False,
478
+ }
479
+
480
+
481
+ __all__ = ["audit_keyword_only"]
@@ -0,0 +1,209 @@
1
+ Metadata-Version: 2.4
2
+ Name: pycforge
3
+ Version: 0.15.2
4
+ Summary: Deterministic bounded Python-to-C source transpiler
5
+ License-Expression: GPL-3.0-only
6
+ Project-URL: Homepage, https://github.com/lastforkbender/pycforge
7
+ Project-URL: Repository, https://github.com/lastforkbender/pycforge
8
+ Project-URL: Issues, https://github.com/lastforkbender/pycforge/issues
9
+ Project-URL: Changelog, https://github.com/lastforkbender/pycforge/blob/main/CHANGELOG.md
10
+ Keywords: python,c,transpiler,pyqt5,desktop
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Environment :: X11 Applications :: Qt
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3 :: Only
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Topic :: Software Development :: Code Generators
19
+ Requires-Python: >=3.11
20
+ Description-Content-Type: text/markdown
21
+ License-File: LICENSE
22
+ Requires-Dist: PyQt5<6,>=5.15.11
23
+ Dynamic: license-file
24
+
25
+ # PyCForge
26
+
27
+ [![PyPI](https://img.shields.io/pypi/v/pycforge.svg)](https://pypi.org/project/pycforge/)
28
+ [![Python](https://img.shields.io/pypi/pyversions/pycforge.svg)](https://pypi.org/project/pycforge/)
29
+ [![CI](https://github.com/lastforkbender/pycforge/actions/workflows/ci.yml/badge.svg)](https://github.com/lastforkbender/pycforge/actions/workflows/ci.yml)
30
+ [![License: GPL v3](https://img.shields.io/badge/License-GPLv3-blue.svg)](https://github.com/lastforkbender/pycforge/blob/main/LICENSE)
31
+
32
+ PyCForge is a deterministic Python-to-C source transpiler with a full PyQt5
33
+ desktop workspace, a command-line interface, and a Python API. It converts a
34
+ documented, deliberately bounded Python subset into readable C11 source while
35
+ producing diagnostics, source mappings, decision traces, and reproducible
36
+ fingerprints.
37
+
38
+ ![PyCForge 0.15.2 desktop workspace showing a Python source bundle, generated C, and the transpilation summary](https://raw.githubusercontent.com/lastforkbender/pycforge/6ad94a7963720772b60ae16b46da46396b1e616d/docs/images/pycforge-workspace-0.15.2.png)
39
+
40
+ **[Open the 32-page PyCForge Programmer's Conversion Guide (PDF)](https://raw.githubusercontent.com/lastforkbender/pycforge/6ad94a7963720772b60ae16b46da46396b1e616d/docs/PyCForge_v0_15_2_Conversion_Examples_Reference_Edition.pdf)**
41
+
42
+ ## Quick start
43
+
44
+ PyCForge requires Python 3.11 or newer. Install it from PyPI:
45
+
46
+ ```bash
47
+ python -m pip install pycforge
48
+ ```
49
+
50
+ That one command installs **PyQt5 and the desktop application as required
51
+ dependencies**. There is no GUI extra and no separate desktop package.
52
+
53
+ Launch the desktop workspace:
54
+
55
+ ```bash
56
+ pycforge-workspace
57
+ ```
58
+
59
+ The equivalent module command is:
60
+
61
+ ```bash
62
+ python -m pycforge.ide
63
+ ```
64
+
65
+ ## A 60-second conversion
66
+
67
+ Save this as `example.py`:
68
+
69
+ ```python
70
+ def add(left: int, right: int) -> int:
71
+ return left + right
72
+ ```
73
+
74
+ Convert it:
75
+
76
+ ```bash
77
+ pycforge convert example.py --output example.c
78
+ ```
79
+
80
+ PyCForge produces:
81
+
82
+ ```c
83
+ #include <stdint.h>
84
+
85
+ int64_t add(int64_t left, int64_t right);
86
+
87
+ int64_t add(int64_t left, int64_t right)
88
+ {
89
+ return left + right;
90
+ }
91
+ ```
92
+
93
+ The generated C is deterministic for the same authenticated source bundle and
94
+ converter configuration.
95
+
96
+ ## What PyCForge provides
97
+
98
+ - A Python-first PyQt5 workspace with document tabs, source splitting,
99
+ navigation, search, outline, command palette, and conversion history.
100
+ - Explicit source bundles containing 1 to 64 Python documents, including
101
+ bounded cross-module function imports.
102
+ - Read-only generated C with diagnostics, source mappings, conversion summary,
103
+ decision trace, and telemetry inspectors.
104
+ - Isolated, cancellable desktop conversion so the converter does not run on
105
+ the GUI thread.
106
+ - A headless CLI for scripts and build pipelines.
107
+ - A Python API for applications that need structured conversion results.
108
+ - Stable diagnostics and fail-closed rejection of unsupported Python.
109
+
110
+ ## Command-line interface
111
+
112
+ Write generated C to a file:
113
+
114
+ ```bash
115
+ pycforge convert input.py --output generated.c
116
+ ```
117
+
118
+ Emit the structured result as JSON:
119
+
120
+ ```bash
121
+ pycforge --format json convert input.py
122
+ ```
123
+
124
+ See all commands and options:
125
+
126
+ ```bash
127
+ pycforge --help
128
+ ```
129
+
130
+ ## Python API
131
+
132
+ ```python
133
+ from pycforge import ConversionRequest, PythonToCConverter
134
+
135
+ source = """\
136
+ def add(left: int, right: int) -> int:
137
+ return left + right
138
+ """
139
+
140
+ result = PythonToCConverter().convert(
141
+ ConversionRequest.from_source(source)
142
+ )
143
+
144
+ if result.generated_c is not None:
145
+ print(result.generated_c)
146
+ else:
147
+ for diagnostic in result.diagnostics:
148
+ print(diagnostic)
149
+ ```
150
+
151
+ The desktop workspace, CLI, and Python API use the same converter and result
152
+ contracts.
153
+
154
+ ## Supported Python
155
+
156
+ PyCForge is intentionally not a general Python runtime. Its current subset
157
+ includes strictly annotated top-level functions using selected scalar values,
158
+ arithmetic and comparisons, `if`/`elif`/`else`, bounded `while` and `range`
159
+ loops, direct eligible function calls, fixed homogeneous containers, and a
160
+ bounded static-record profile.
161
+
162
+ Anything outside the documented subset is unsupported by default and is
163
+ rejected with diagnostics rather than silently approximated. Read the exact
164
+ [supported-Python specification](https://github.com/lastforkbender/pycforge/blob/main/specifications/supported_python.md)
165
+ before adopting PyCForge for production input.
166
+
167
+ ## Safety boundary
168
+
169
+ PyCForge parses supplied source as data. It does not import or execute the
170
+ input Python, scan the host environment for modules, or resolve undeclared
171
+ files. It stops after C source generation and does not compile, assemble, link,
172
+ load, or execute the generated C.
173
+
174
+ Python `int` values map to the documented signed 64-bit representation domain;
175
+ other supported Python values likewise follow explicit target-C contracts.
176
+ Review the
177
+ [product boundary](https://github.com/lastforkbender/pycforge/blob/main/specifications/product_boundary.md)
178
+ and the Programmer's Conversion Guide for the complete limitations.
179
+
180
+ ## Documentation
181
+
182
+ - **[Programmer's Conversion Guide — 0.15.2 Reference Edition (PDF)](https://raw.githubusercontent.com/lastforkbender/pycforge/6ad94a7963720772b60ae16b46da46396b1e616d/docs/PyCForge_v0_15_2_Conversion_Examples_Reference_Edition.pdf)**
183
+ - [Supported Python](https://github.com/lastforkbender/pycforge/blob/main/specifications/supported_python.md)
184
+ - [Workspace specification](https://github.com/lastforkbender/pycforge/blob/main/specifications/pycforge_workspace.md)
185
+ - [Current state](https://github.com/lastforkbender/pycforge/blob/main/CURRENT_STATE.md)
186
+ - [Release notes](https://github.com/lastforkbender/pycforge/blob/main/RELEASE_NOTES.md)
187
+ - [Changelog](https://github.com/lastforkbender/pycforge/blob/main/CHANGELOG.md)
188
+ - [Security policy](https://github.com/lastforkbender/pycforge/blob/main/SECURITY.md)
189
+
190
+ ## Development
191
+
192
+ ```bash
193
+ git clone https://github.com/lastforkbender/pycforge.git
194
+ cd pycforge
195
+ python -m venv .venv
196
+ python -m pip install --upgrade pip
197
+ python -m pip install -e .
198
+ python -m pip install pytest
199
+ QT_QPA_PLATFORM=offscreen python -m pytest -q
200
+ ```
201
+
202
+ The normal editable installation includes PyQt5, matching the package users
203
+ receive from PyPI. Automated release checks cover Python 3.11 and 3.12 on
204
+ Linux with real PyQt5 widgets using Qt's offscreen platform.
205
+
206
+ ## License
207
+
208
+ PyCForge is free software released under the
209
+ [GNU General Public License v3.0 only](https://github.com/lastforkbender/pycforge/blob/main/LICENSE).