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,388 @@
1
+ """Frozen support-template registry and exact dependency resolver."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import heapq
6
+ from dataclasses import fields, is_dataclass
7
+ from functools import lru_cache
8
+ from types import MappingProxyType
9
+ from typing import Iterable
10
+
11
+ from pycforge.converter.c_output import CRenderer, validate_c_text
12
+ from pycforge.converter.contracts.configuration import SUPPORTED_TARGET_CONTRACTS
13
+ from pycforge.converter.core.fingerprint import fingerprint
14
+ from pycforge.converter.ir.c_ir import (
15
+ CProvenance,
16
+ CStorage,
17
+ CTranslationUnitBuilder,
18
+ HELPER_SCHEMA_VERSION,
19
+ serialize_translation_unit,
20
+ validate_translation_unit,
21
+ )
22
+
23
+ from .factories import builtin_definitions
24
+ from .model import (
25
+ HELPER_INTERFACE_ID,
26
+ HELPER_REGISTRY_VERSION,
27
+ HelperCIRAsset,
28
+ HelperDefinition,
29
+ HelperManifestEntry,
30
+ HelperReference,
31
+ ResolvedHelperPlan,
32
+ )
33
+
34
+
35
+ class HelperRegistryError(ValueError):
36
+ def __init__(self, code: str, message: str) -> None:
37
+ super().__init__(message)
38
+ self.code = code
39
+ self.message = message
40
+
41
+
42
+ class HelperResolutionCanceled(Exception):
43
+ pass
44
+
45
+
46
+ class FrozenHelperRegistry:
47
+ """Immutable project registry; registration order has no semantic effect."""
48
+
49
+ def __init__(
50
+ self,
51
+ definitions: Iterable[HelperDefinition],
52
+ *,
53
+ registry_version: str = HELPER_REGISTRY_VERSION,
54
+ interface_id: str = HELPER_INTERFACE_ID,
55
+ ) -> None:
56
+ ordered = tuple(sorted(definitions, key=lambda item: item.reference.canonical))
57
+ references = [item.reference.canonical for item in ordered]
58
+ if len(references) != len(set(references)):
59
+ duplicate = next(item for item in references if references.count(item) > 1)
60
+ raise HelperRegistryError("PYC3307", f"duplicate helper identity: {duplicate}")
61
+ if not registry_version:
62
+ raise HelperRegistryError("PYC3306", "helper registry version is empty")
63
+ self.registry_version = registry_version
64
+ self.interface_id = interface_id
65
+ definitions_by_reference: dict[str, HelperDefinition] = {}
66
+ assets_by_reference: dict[str, HelperCIRAsset] = {}
67
+ entries_by_reference: dict[str, HelperManifestEntry] = {}
68
+ rendered_by_reference: dict[str, str] = {}
69
+ for definition in ordered:
70
+ self._validate_contract(definition)
71
+ try:
72
+ asset = definition.factory()
73
+ except Exception as exc:
74
+ raise HelperRegistryError(
75
+ "PYC3306",
76
+ f"helper factory failed for {definition.reference.canonical}: {type(exc).__name__}",
77
+ ) from None
78
+ unit, rendered = self._validate_asset(definition, asset)
79
+ asset_fingerprint = fingerprint(
80
+ "helper-c-ir-asset",
81
+ {
82
+ "contract": definition.contract_dict(),
83
+ "c_ir": serialize_translation_unit(unit),
84
+ },
85
+ ).value
86
+ key = definition.reference.canonical
87
+ definitions_by_reference[key] = definition
88
+ assets_by_reference[key] = asset
89
+ entries_by_reference[key] = HelperManifestEntry(definition, asset_fingerprint)
90
+ rendered_by_reference[key] = rendered
91
+ self._definitions = MappingProxyType(definitions_by_reference)
92
+ self._assets = MappingProxyType(assets_by_reference)
93
+ self._entries = MappingProxyType(entries_by_reference)
94
+ self._rendered = MappingProxyType(rendered_by_reference)
95
+ self._manifest = tuple(self._entries[key] for key in sorted(self._entries))
96
+ self._fingerprint = fingerprint(
97
+ "helper-registry-manifest",
98
+ {
99
+ "registry_version": self.registry_version,
100
+ "interface_id": self.interface_id,
101
+ "helpers": [item.to_dict() for item in self._manifest],
102
+ },
103
+ ).value
104
+
105
+ @property
106
+ def manifest(self) -> tuple[dict[str, object], ...]:
107
+ return tuple(item.to_dict() for item in self._manifest)
108
+
109
+ @property
110
+ def fingerprint(self) -> str:
111
+ return self._fingerprint
112
+
113
+ def rendered_asset(self, reference: HelperReference | str) -> str:
114
+ parsed = self._reference(reference)
115
+ try:
116
+ return self._rendered[parsed.canonical]
117
+ except KeyError:
118
+ raise HelperRegistryError("PYC3302", f"missing helper: {parsed.canonical}") from None
119
+
120
+ def resolve(
121
+ self,
122
+ requirements: Iterable[HelperReference | str],
123
+ *,
124
+ target_contract: str,
125
+ cancellation: object | None = None,
126
+ ) -> ResolvedHelperPlan:
127
+ if target_contract not in SUPPORTED_TARGET_CONTRACTS:
128
+ raise HelperRegistryError(
129
+ "PYC3304",
130
+ f"unknown helper target contract: {target_contract}",
131
+ )
132
+ roots: dict[str, HelperReference] = {}
133
+ for requirement in requirements:
134
+ self._check_cancellation(cancellation)
135
+ reference = self._reference(requirement)
136
+ roots[reference.canonical] = reference
137
+ root_references = tuple(roots[key] for key in sorted(roots))
138
+
139
+ closure: dict[str, HelperDefinition] = {}
140
+ pending = list(roots)
141
+ heapq.heapify(pending)
142
+ while pending:
143
+ self._check_cancellation(cancellation)
144
+ key = heapq.heappop(pending)
145
+ if key in closure:
146
+ continue
147
+ definition = self._definitions.get(key)
148
+ if definition is None:
149
+ raise HelperRegistryError("PYC3302", f"missing helper dependency: {key}")
150
+ if definition.interface_id != self.interface_id:
151
+ raise HelperRegistryError(
152
+ "PYC3305",
153
+ f"helper interface mismatch for {key}: {definition.interface_id}",
154
+ )
155
+ if target_contract not in definition.target_contracts:
156
+ raise HelperRegistryError(
157
+ "PYC3304",
158
+ f"helper {key} is incompatible with target contract {target_contract}",
159
+ )
160
+ closure[key] = definition
161
+ for dependency in definition.dependencies:
162
+ if dependency.canonical not in closure:
163
+ heapq.heappush(pending, dependency.canonical)
164
+
165
+ indegree = {key: 0 for key in closure}
166
+ consumers: dict[str, list[str]] = {key: [] for key in closure}
167
+ for key, definition in closure.items():
168
+ for dependency in definition.dependencies:
169
+ dependency_key = dependency.canonical
170
+ if dependency_key not in closure:
171
+ raise HelperRegistryError(
172
+ "PYC3302",
173
+ f"missing helper dependency: {dependency_key} required by {key}",
174
+ )
175
+ indegree[key] += 1
176
+ consumers[dependency_key].append(key)
177
+ ready = [key for key, count in indegree.items() if count == 0]
178
+ heapq.heapify(ready)
179
+ ordered: list[str] = []
180
+ while ready:
181
+ self._check_cancellation(cancellation)
182
+ key = heapq.heappop(ready)
183
+ ordered.append(key)
184
+ for consumer in sorted(consumers[key]):
185
+ indegree[consumer] -= 1
186
+ if indegree[consumer] == 0:
187
+ heapq.heappush(ready, consumer)
188
+ if len(ordered) != len(closure):
189
+ remaining = {key for key, count in indegree.items() if count > 0}
190
+ cycle = self._cycle_path(remaining, closure)
191
+ raise HelperRegistryError(
192
+ "PYC3303",
193
+ "helper dependency cycle: " + " -> ".join(cycle),
194
+ )
195
+
196
+ assets = tuple(self._assets[key] for key in ordered)
197
+ manifest = tuple(self._entries[key] for key in ordered)
198
+ manifest_dicts = [item.to_dict() for item in manifest]
199
+ manifest_fingerprint = fingerprint(
200
+ "helper-manifest",
201
+ {
202
+ "registry_version": self.registry_version,
203
+ "target_contract": target_contract,
204
+ "requirements": [item.canonical for item in root_references],
205
+ "helpers": manifest_dicts,
206
+ },
207
+ ).value
208
+ return ResolvedHelperPlan(
209
+ target_contract,
210
+ self.registry_version,
211
+ root_references,
212
+ assets,
213
+ manifest,
214
+ self.fingerprint,
215
+ manifest_fingerprint,
216
+ )
217
+
218
+ def _validate_contract(self, definition: HelperDefinition) -> None:
219
+ if definition.interface_id != self.interface_id:
220
+ raise HelperRegistryError(
221
+ "PYC3305",
222
+ f"helper interface mismatch for {definition.reference.canonical}: {definition.interface_id}",
223
+ )
224
+ if not definition.target_contracts or tuple(sorted(set(definition.target_contracts))) != definition.target_contracts:
225
+ raise HelperRegistryError("PYC3304", f"helper target contracts must be unique and sorted: {definition.reference.canonical}")
226
+ if any(item not in SUPPORTED_TARGET_CONTRACTS for item in definition.target_contracts):
227
+ raise HelperRegistryError("PYC3304", f"helper declares an unknown target contract: {definition.reference.canonical}")
228
+ dependency_keys = tuple(item.canonical for item in definition.dependencies)
229
+ if dependency_keys != tuple(sorted(set(dependency_keys))):
230
+ raise HelperRegistryError("PYC3306", f"helper dependencies must be exact, unique, and sorted: {definition.reference.canonical}")
231
+ if definition.reference in definition.dependencies:
232
+ raise HelperRegistryError("PYC3303", f"helper directly depends on itself: {definition.reference.canonical}")
233
+ if not definition.semantic_obligations or tuple(sorted(set(definition.semantic_obligations))) != definition.semantic_obligations:
234
+ raise HelperRegistryError("PYC3306", f"helper lacks semantic obligations: {definition.reference.canonical}")
235
+ if not definition.prospective_consumer or not definition.earliest_eligible_phase:
236
+ raise HelperRegistryError("PYC3306", f"helper lacks prospective-consumer gate data: {definition.reference.canonical}")
237
+ if not callable(definition.factory):
238
+ raise HelperRegistryError("PYC3306", f"helper lacks a C IR factory: {definition.reference.canonical}")
239
+ contract_strings = (
240
+ *definition.ownership.to_dict().values(),
241
+ definition.failure.strategy,
242
+ definition.failure.violation_policy,
243
+ definition.failure.runtime_failure_channel,
244
+ definition.cancellation_policy,
245
+ )
246
+ if any(not isinstance(item, str) or not item for item in contract_strings) or not definition.failure.preconditions:
247
+ raise HelperRegistryError("PYC3306", f"helper ownership/failure contract is incomplete: {definition.reference.canonical}")
248
+
249
+ def _validate_asset(
250
+ self,
251
+ definition: HelperDefinition,
252
+ asset: HelperCIRAsset,
253
+ ) -> tuple[object, str]:
254
+ reference = definition.reference
255
+ if not isinstance(asset, HelperCIRAsset) or asset.reference != reference:
256
+ raise HelperRegistryError("PYC3306", f"helper factory identity mismatch: {reference.canonical}")
257
+ if asset.prototype.identifier.binding_id != asset.definition.identifier.binding_id:
258
+ raise HelperRegistryError("PYC3306", f"helper prototype/definition binding mismatch: {reference.canonical}")
259
+ if asset.prototype.storage is not CStorage.STATIC or asset.definition.storage is not CStorage.STATIC:
260
+ raise HelperRegistryError("PYC3306", f"helper must use internal static linkage: {reference.canonical}")
261
+ provenance_values = tuple(self._provenance_values(asset))
262
+ if any(
263
+ not isinstance(item, CProvenance)
264
+ or item.origin_kind != "support-template"
265
+ or item.source_document_id is not None
266
+ or item.source_node_ids
267
+ or item.rule_plan_id is not None
268
+ for item in provenance_values
269
+ ):
270
+ raise HelperRegistryError("PYC3306", f"helper provenance is not isolated from source provenance: {reference.canonical}")
271
+ primary_unit = None
272
+ primary_rendered = None
273
+ for target_contract in definition.target_contracts:
274
+ builder = CTranslationUnitBuilder(
275
+ target_contract,
276
+ node_id=f"c-helper-tu-{reference.helper_id.replace('.', '-')}-{reference.version.replace('.', '-')}",
277
+ provenance=CProvenance("support-template"),
278
+ schema_version=HELPER_SCHEMA_VERSION,
279
+ )
280
+ for include in asset.includes:
281
+ builder.add_include(include)
282
+ builder.add_declaration(asset.prototype)
283
+ builder.add_declaration(asset.definition)
284
+ unit = builder.build()
285
+ validation = validate_translation_unit(unit)
286
+ if not validation.accepted:
287
+ raise HelperRegistryError(
288
+ "PYC3306",
289
+ f"invalid structured C IR helper {reference.canonical}: {'; '.join(validation.errors)}",
290
+ )
291
+ rendered = CRenderer().render(unit).text
292
+ conformance = validate_c_text(rendered)
293
+ if not conformance.accepted:
294
+ raise HelperRegistryError(
295
+ "PYC3306",
296
+ f"helper text conformance failed for {reference.canonical}: {conformance.message}",
297
+ )
298
+ if primary_unit is None:
299
+ primary_unit = unit
300
+ primary_rendered = rendered
301
+ assert primary_unit is not None and primary_rendered is not None
302
+ return primary_unit, primary_rendered
303
+
304
+ @classmethod
305
+ def _provenance_values(cls, value: object):
306
+ if isinstance(value, CProvenance):
307
+ yield value
308
+ return
309
+ if isinstance(value, tuple):
310
+ for item in value:
311
+ yield from cls._provenance_values(item)
312
+ return
313
+ if is_dataclass(value):
314
+ for field in fields(value):
315
+ yield from cls._provenance_values(getattr(value, field.name))
316
+
317
+ @staticmethod
318
+ def _reference(value: HelperReference | str) -> HelperReference:
319
+ if isinstance(value, HelperReference):
320
+ return value
321
+ try:
322
+ return HelperReference.parse(value)
323
+ except (TypeError, ValueError) as exc:
324
+ raise HelperRegistryError("PYC3301", str(exc)) from None
325
+
326
+ @staticmethod
327
+ def _check_cancellation(cancellation: object | None) -> None:
328
+ if cancellation is not None and bool(getattr(cancellation, "is_canceled", False)):
329
+ raise HelperResolutionCanceled
330
+
331
+ @staticmethod
332
+ def _cycle_path(
333
+ remaining: set[str],
334
+ closure: dict[str, HelperDefinition],
335
+ ) -> tuple[str, ...]:
336
+ state: dict[str, int] = {}
337
+ for start in sorted(remaining):
338
+ if state.get(start, 0):
339
+ continue
340
+ path: list[str] = [start]
341
+ positions = {start: 0}
342
+ state[start] = 1
343
+ stack: list[tuple[str, int, tuple[str, ...]]] = [
344
+ (
345
+ start,
346
+ 0,
347
+ tuple(
348
+ sorted(
349
+ dependency.canonical
350
+ for dependency in closure[start].dependencies
351
+ if dependency.canonical in remaining
352
+ )
353
+ ),
354
+ )
355
+ ]
356
+ while stack:
357
+ node, index, dependencies = stack[-1]
358
+ if index >= len(dependencies):
359
+ stack.pop()
360
+ state[node] = 2
361
+ positions.pop(node, None)
362
+ if path and path[-1] == node:
363
+ path.pop()
364
+ continue
365
+ dependency = dependencies[index]
366
+ stack[-1] = (node, index + 1, dependencies)
367
+ dependency_state = state.get(dependency, 0)
368
+ if dependency_state == 1:
369
+ return tuple(path[positions[dependency]:] + [dependency])
370
+ if dependency_state == 2:
371
+ continue
372
+ state[dependency] = 1
373
+ positions[dependency] = len(path)
374
+ path.append(dependency)
375
+ next_dependencies = tuple(
376
+ sorted(
377
+ item.canonical
378
+ for item in closure[dependency].dependencies
379
+ if item.canonical in remaining
380
+ )
381
+ )
382
+ stack.append((dependency, 0, next_dependencies))
383
+ return tuple(sorted(remaining))
384
+
385
+
386
+ @lru_cache(maxsize=1)
387
+ def default_helper_registry() -> FrozenHelperRegistry:
388
+ return FrozenHelperRegistry(builtin_definitions())
File without changes
@@ -0,0 +1,39 @@
1
+ from __future__ import annotations
2
+
3
+ from copy import deepcopy
4
+ from threading import Lock
5
+
6
+
7
+ class TelemetrySink:
8
+ """Best-effort operational observer isolated from semantic products."""
9
+
10
+ def __init__(self, enabled: bool, limit: int, fail_on_record: bool = False) -> None:
11
+ self.enabled = enabled
12
+ self.limit = limit
13
+ self.fail_on_record = fail_on_record
14
+ self._events: list[dict[str, object]] = []
15
+ self._dropped = 0
16
+ self._failed = False
17
+ self._lock = Lock()
18
+
19
+ def record(self, event: dict[str, object]) -> None:
20
+ if not self.enabled:
21
+ return
22
+ try:
23
+ if self.fail_on_record:
24
+ raise RuntimeError("injected observer failure")
25
+ with self._lock:
26
+ if len(self._events) >= self.limit:
27
+ self._dropped += 1
28
+ else:
29
+ self._events.append(deepcopy(event))
30
+ except Exception:
31
+ self._failed = True
32
+
33
+ def snapshot(self) -> dict[str, object]:
34
+ with self._lock:
35
+ return {
36
+ "events": tuple(deepcopy(self._events)),
37
+ "dropped": self._dropped,
38
+ "observer_failed": self._failed,
39
+ }
@@ -0,0 +1,19 @@
1
+ from .controller import WorkspaceController
2
+ from .model import WorkspaceDocument, WorkspaceSnapshot, WorkspaceState
3
+ from .qt import QT_AVAILABLE, run
4
+
5
+ WORKSPACE_CONTRACT_VERSION = "pycforge-workspace/0.5"
6
+ ACTION_REGISTRY_VERSION = "pycforge.action-registry/0.2"
7
+ VISUAL_SYSTEM_VERSION = "pycforge.visual-system/0.2"
8
+
9
+ __all__ = [
10
+ "ACTION_REGISTRY_VERSION",
11
+ "VISUAL_SYSTEM_VERSION",
12
+ "WorkspaceController",
13
+ "WorkspaceDocument",
14
+ "WorkspaceSnapshot",
15
+ "WorkspaceState",
16
+ "WORKSPACE_CONTRACT_VERSION",
17
+ "QT_AVAILABLE",
18
+ "run",
19
+ ]
@@ -0,0 +1,5 @@
1
+ from .qt import run
2
+
3
+
4
+ if __name__ == "__main__":
5
+ raise SystemExit(run())