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,200 @@
1
+ """Request and cancellation envelope codec."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pycforge.converter.core.fingerprint import fingerprint
6
+ from pycforge.converter.core.request import ConversionRequest, ObservationOptions
7
+
8
+ from ._worker_protocol_json import (
9
+ _decode_bounded,
10
+ _encode_bounded,
11
+ _exact_fields,
12
+ _generation,
13
+ _object,
14
+ _receive_frame,
15
+ _send_frame,
16
+ _sha256,
17
+ )
18
+ from ._worker_protocol_types import (
19
+ MAX_CONTROL_BYTES,
20
+ MAX_REQUEST_BYTES,
21
+ PROTOCOL_SCHEMA,
22
+ ByteConnection,
23
+ WorkerControl,
24
+ WorkerProtocolError,
25
+ WorkerRequest,
26
+ )
27
+ from ._worker_request_data import (
28
+ _observation_from_data,
29
+ _observation_to_data,
30
+ _request_from_data,
31
+ _request_to_data,
32
+ bundle_fingerprint_for_request,
33
+ )
34
+
35
+
36
+ def create_worker_request(
37
+ generation: int,
38
+ bundle_fingerprint: str,
39
+ request: ConversionRequest,
40
+ observation: ObservationOptions | None = None,
41
+ ) -> WorkerRequest:
42
+ observation = observation or ObservationOptions()
43
+ core = _request_envelope_core(
44
+ generation, bundle_fingerprint, request, observation
45
+ )
46
+ return WorkerRequest(
47
+ generation,
48
+ bundle_fingerprint,
49
+ request,
50
+ observation,
51
+ _transport_fingerprint(core),
52
+ )
53
+
54
+
55
+ def encode_request(request: WorkerRequest) -> bytes:
56
+ _validate_worker_request(request)
57
+ core = _request_envelope_core(
58
+ request.generation,
59
+ request.bundle_fingerprint,
60
+ request.request,
61
+ request.observation,
62
+ )
63
+ if request.transport_fingerprint != _transport_fingerprint(core):
64
+ raise WorkerProtocolError("worker request transport fingerprint mismatch")
65
+ return _encode_bounded(
66
+ {**core, "transport_fingerprint": request.transport_fingerprint},
67
+ MAX_REQUEST_BYTES,
68
+ "request",
69
+ )
70
+
71
+
72
+ def decode_request(frame: bytes) -> WorkerRequest:
73
+ value = _decode_bounded(frame, MAX_REQUEST_BYTES, "request")
74
+ data = _object(value, "request envelope")
75
+ _exact_fields(
76
+ data,
77
+ {
78
+ "schema",
79
+ "kind",
80
+ "generation",
81
+ "bundle_fingerprint",
82
+ "request",
83
+ "observation",
84
+ "transport_fingerprint",
85
+ },
86
+ "request envelope",
87
+ )
88
+ if data["schema"] != PROTOCOL_SCHEMA or data["kind"] != "request":
89
+ raise WorkerProtocolError("incompatible worker request envelope")
90
+ generation = _generation(data["generation"])
91
+ bundle_fingerprint = _sha256(data["bundle_fingerprint"], "bundle fingerprint")
92
+ conversion_request = _request_from_data(data["request"])
93
+ observation = _observation_from_data(data["observation"])
94
+ supplied = _sha256(data["transport_fingerprint"], "transport fingerprint")
95
+ core = {
96
+ "schema": PROTOCOL_SCHEMA,
97
+ "kind": "request",
98
+ "generation": generation,
99
+ "bundle_fingerprint": bundle_fingerprint,
100
+ "request": _request_to_data(conversion_request),
101
+ "observation": _observation_to_data(observation),
102
+ }
103
+ expected = _transport_fingerprint(core)
104
+ if supplied != expected:
105
+ raise WorkerProtocolError("worker request transport fingerprint mismatch")
106
+ result = WorkerRequest(
107
+ generation,
108
+ bundle_fingerprint,
109
+ conversion_request,
110
+ observation,
111
+ supplied,
112
+ )
113
+ _validate_worker_request(result)
114
+ return result
115
+
116
+
117
+ def encode_cancel(request: WorkerRequest) -> bytes:
118
+ _validate_worker_request(request)
119
+ return _encode_bounded(
120
+ {
121
+ "schema": PROTOCOL_SCHEMA,
122
+ "kind": "cancel",
123
+ "generation": request.generation,
124
+ "bundle_fingerprint": request.bundle_fingerprint,
125
+ "transport_fingerprint": request.transport_fingerprint,
126
+ },
127
+ MAX_CONTROL_BYTES,
128
+ "control",
129
+ )
130
+
131
+
132
+ def decode_control(frame: bytes) -> WorkerControl:
133
+ value = _decode_bounded(frame, MAX_CONTROL_BYTES, "control")
134
+ data = _object(value, "control envelope")
135
+ _exact_fields(
136
+ data,
137
+ {
138
+ "schema",
139
+ "kind",
140
+ "generation",
141
+ "bundle_fingerprint",
142
+ "transport_fingerprint",
143
+ },
144
+ "control envelope",
145
+ )
146
+ if data["schema"] != PROTOCOL_SCHEMA or data["kind"] != "cancel":
147
+ raise WorkerProtocolError("incompatible worker control envelope")
148
+ return WorkerControl(
149
+ _generation(data["generation"]),
150
+ _sha256(data["bundle_fingerprint"], "bundle fingerprint"),
151
+ _sha256(data["transport_fingerprint"], "transport fingerprint"),
152
+ )
153
+
154
+
155
+ def send_request(connection: ByteConnection, request: WorkerRequest) -> None:
156
+ _send_frame(connection, encode_request(request), MAX_REQUEST_BYTES, "request")
157
+
158
+
159
+ def receive_request(connection: ByteConnection) -> WorkerRequest:
160
+ return decode_request(_receive_frame(connection, MAX_REQUEST_BYTES, "request"))
161
+
162
+
163
+ def send_cancel(connection: ByteConnection, request: WorkerRequest) -> None:
164
+ _send_frame(connection, encode_cancel(request), MAX_CONTROL_BYTES, "control")
165
+
166
+
167
+ def receive_control(connection: ByteConnection) -> WorkerControl:
168
+ return decode_control(_receive_frame(connection, MAX_CONTROL_BYTES, "control"))
169
+
170
+
171
+ def _request_envelope_core(
172
+ generation: int,
173
+ bundle_fingerprint: str,
174
+ request: ConversionRequest,
175
+ observation: ObservationOptions,
176
+ ) -> dict[str, object]:
177
+ return {
178
+ "schema": PROTOCOL_SCHEMA,
179
+ "kind": "request",
180
+ "generation": generation,
181
+ "bundle_fingerprint": bundle_fingerprint,
182
+ "request": _request_to_data(request),
183
+ "observation": _observation_to_data(observation),
184
+ }
185
+
186
+
187
+ def _transport_fingerprint(value: object) -> str:
188
+ return fingerprint("worker-request-transport", value).value
189
+
190
+
191
+ def _validate_worker_request(request: WorkerRequest) -> None:
192
+ if not isinstance(request, WorkerRequest):
193
+ raise WorkerProtocolError("worker request envelope is malformed")
194
+ _generation(request.generation)
195
+ _sha256(request.bundle_fingerprint, "bundle fingerprint")
196
+ _sha256(request.transport_fingerprint, "transport fingerprint")
197
+ _request_to_data(request.request)
198
+ _observation_to_data(request.observation)
199
+ if request.bundle_fingerprint != bundle_fingerprint_for_request(request.request):
200
+ raise WorkerProtocolError("worker request bundle fingerprint mismatch")
@@ -0,0 +1,260 @@
1
+ """Strict plain-data mapping for conversion requests and observations."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pycforge.converter.core.fingerprint import fingerprint
6
+ from pycforge.converter.core.request import (
7
+ ConversionRequest,
8
+ ObservationOptions,
9
+ SourceBundle,
10
+ SourceDocumentInput,
11
+ )
12
+ from pycforge.converter.core.resource_policy import ResourcePolicy
13
+
14
+ from ._worker_protocol_json import (
15
+ _contains_surrogate,
16
+ _exact_fields,
17
+ _list,
18
+ _nonempty_string,
19
+ _nonnegative_integer,
20
+ _object,
21
+ _string,
22
+ _string_list,
23
+ )
24
+ from ._worker_protocol_types import (
25
+ MAX_SOURCE_BYTES,
26
+ MAX_SOURCE_DOCUMENTS,
27
+ WorkerProtocolError,
28
+ )
29
+
30
+
31
+ _REQUEST_FIELDS = frozenset(
32
+ {
33
+ "source_bundle",
34
+ "python_version",
35
+ "target_contract",
36
+ "semantic_policy",
37
+ "approximation_allowlist",
38
+ "rule_set_version",
39
+ "renderer_version",
40
+ "helper_policy_version",
41
+ "container_policy_version",
42
+ "resource_policy",
43
+ "module_policy_version",
44
+ "record_policy_version",
45
+ "numeric_policy_version",
46
+ }
47
+ )
48
+ _RESOURCE_FIELDS = frozenset(ResourcePolicy.__slots__)
49
+
50
+
51
+ def bundle_fingerprint_for_request(request: ConversionRequest) -> str:
52
+ """Return the workspace bundle identity represented by ``request``."""
53
+
54
+ if not isinstance(request, ConversionRequest):
55
+ raise WorkerProtocolError("worker request must contain ConversionRequest")
56
+ bundle = request.source_bundle
57
+ if not isinstance(bundle, SourceBundle):
58
+ raise WorkerProtocolError("worker request source bundle is malformed")
59
+ documents = (bundle.primary,) + tuple(bundle.companions)
60
+ if not documents or any(not isinstance(item, SourceDocumentInput) for item in documents):
61
+ raise WorkerProtocolError("worker request source documents are malformed")
62
+ semantic = [
63
+ {
64
+ "module_id": document.module_id,
65
+ "logical_name": document.logical_name,
66
+ "text": document.text,
67
+ "is_primary": ordinal == 0,
68
+ }
69
+ for ordinal, document in enumerate(documents)
70
+ ]
71
+ return fingerprint("workspace-source-bundle", semantic).value
72
+
73
+
74
+ def _request_to_data(request: ConversionRequest) -> dict[str, object]:
75
+ if not isinstance(request, ConversionRequest):
76
+ raise WorkerProtocolError("worker request must contain ConversionRequest")
77
+ bundle = request.source_bundle
78
+ if not isinstance(bundle, SourceBundle):
79
+ raise WorkerProtocolError("worker request source bundle is malformed")
80
+ documents = (bundle.primary,) + tuple(bundle.companions)
81
+ if (
82
+ not documents
83
+ or len(documents) > MAX_SOURCE_DOCUMENTS
84
+ or any(not isinstance(item, SourceDocumentInput) for item in documents)
85
+ ):
86
+ raise WorkerProtocolError("worker request document count is outside bounds")
87
+ total_bytes = 0
88
+
89
+ def document_data(document: SourceDocumentInput) -> dict[str, object]:
90
+ nonlocal total_bytes
91
+ if (
92
+ not isinstance(document.logical_name, str)
93
+ or not document.logical_name
94
+ or _contains_surrogate(document.logical_name)
95
+ or not isinstance(document.text, str)
96
+ or _contains_surrogate(document.text)
97
+ or (
98
+ document.module_id is not None
99
+ and (
100
+ not isinstance(document.module_id, str)
101
+ or _contains_surrogate(document.module_id)
102
+ )
103
+ )
104
+ ):
105
+ raise WorkerProtocolError("worker request source document is malformed")
106
+ try:
107
+ total_bytes += len(document.text.encode("utf-8"))
108
+ except UnicodeEncodeError as exc:
109
+ raise WorkerProtocolError(
110
+ "worker request source text is not valid UTF-8"
111
+ ) from exc
112
+ return {
113
+ "logical_name": document.logical_name,
114
+ "text": document.text,
115
+ "module_id": document.module_id,
116
+ }
117
+
118
+ primary = document_data(documents[0])
119
+ companions = [document_data(item) for item in documents[1:]]
120
+ if total_bytes > MAX_SOURCE_BYTES:
121
+ raise WorkerProtocolError("worker request source bytes exceed protocol bounds")
122
+ if not isinstance(request.approximation_allowlist, tuple) or any(
123
+ not isinstance(item, str) or _contains_surrogate(item)
124
+ for item in request.approximation_allowlist
125
+ ):
126
+ raise WorkerProtocolError("worker request approximation allowlist is malformed")
127
+ string_fields = (
128
+ request.python_version,
129
+ request.target_contract,
130
+ request.semantic_policy,
131
+ request.rule_set_version,
132
+ request.renderer_version,
133
+ request.helper_policy_version,
134
+ request.container_policy_version,
135
+ request.module_policy_version,
136
+ request.record_policy_version,
137
+ request.numeric_policy_version,
138
+ )
139
+ if any(
140
+ not isinstance(item, str) or not item or _contains_surrogate(item)
141
+ for item in string_fields
142
+ ):
143
+ raise WorkerProtocolError("worker request configuration is malformed")
144
+ if not isinstance(request.resource_policy, ResourcePolicy):
145
+ raise WorkerProtocolError("worker request resource policy is malformed")
146
+ resource = request.resource_policy.to_dict()
147
+ if set(resource) != _RESOURCE_FIELDS or any(
148
+ not isinstance(item, int) or isinstance(item, bool) or item < 0
149
+ for item in resource.values()
150
+ ):
151
+ raise WorkerProtocolError("worker request resource policy is malformed")
152
+ return {
153
+ "source_bundle": {
154
+ "primary": primary,
155
+ "companions": companions,
156
+ },
157
+ "python_version": request.python_version,
158
+ "target_contract": request.target_contract,
159
+ "semantic_policy": request.semantic_policy,
160
+ "approximation_allowlist": list(request.approximation_allowlist),
161
+ "rule_set_version": request.rule_set_version,
162
+ "renderer_version": request.renderer_version,
163
+ "helper_policy_version": request.helper_policy_version,
164
+ "container_policy_version": request.container_policy_version,
165
+ "resource_policy": resource,
166
+ "module_policy_version": request.module_policy_version,
167
+ "record_policy_version": request.record_policy_version,
168
+ "numeric_policy_version": request.numeric_policy_version,
169
+ }
170
+
171
+
172
+ def _request_from_data(value: object) -> ConversionRequest:
173
+ data = _object(value, "conversion request")
174
+ _exact_fields(data, _REQUEST_FIELDS, "conversion request")
175
+ source_bundle_data = _object(data["source_bundle"], "source bundle")
176
+ _exact_fields(source_bundle_data, {"primary", "companions"}, "source bundle")
177
+ primary = _document_from_data(source_bundle_data["primary"])
178
+ raw_companions = _list(source_bundle_data["companions"], "source companions")
179
+ if len(raw_companions) + 1 > MAX_SOURCE_DOCUMENTS:
180
+ raise WorkerProtocolError("worker request document count is outside bounds")
181
+ companions = tuple(_document_from_data(item) for item in raw_companions)
182
+ allowlist = _string_list(
183
+ data["approximation_allowlist"], "approximation allowlist"
184
+ )
185
+ resource_data = _object(data["resource_policy"], "resource policy")
186
+ _exact_fields(resource_data, _RESOURCE_FIELDS, "resource policy")
187
+ resource_values = {
188
+ key: _nonnegative_integer(value, f"resource policy {key}")
189
+ for key, value in resource_data.items()
190
+ }
191
+ string_values = {}
192
+ for key in (
193
+ "python_version",
194
+ "target_contract",
195
+ "semantic_policy",
196
+ "rule_set_version",
197
+ "renderer_version",
198
+ "helper_policy_version",
199
+ "container_policy_version",
200
+ "module_policy_version",
201
+ "record_policy_version",
202
+ "numeric_policy_version",
203
+ ):
204
+ string_values[key] = _nonempty_string(data[key], key)
205
+ request = ConversionRequest(
206
+ SourceBundle(primary, companions),
207
+ python_version=string_values["python_version"],
208
+ target_contract=string_values["target_contract"],
209
+ semantic_policy=string_values["semantic_policy"],
210
+ approximation_allowlist=allowlist,
211
+ rule_set_version=string_values["rule_set_version"],
212
+ renderer_version=string_values["renderer_version"],
213
+ helper_policy_version=string_values["helper_policy_version"],
214
+ container_policy_version=string_values["container_policy_version"],
215
+ resource_policy=ResourcePolicy(**resource_values),
216
+ module_policy_version=string_values["module_policy_version"],
217
+ record_policy_version=string_values["record_policy_version"],
218
+ numeric_policy_version=string_values["numeric_policy_version"],
219
+ )
220
+ # Reuse the encoder's aggregate byte and structural bounds.
221
+ _request_to_data(request)
222
+ return request
223
+
224
+
225
+ def _document_from_data(value: object) -> SourceDocumentInput:
226
+ data = _object(value, "source document")
227
+ _exact_fields(data, {"logical_name", "text", "module_id"}, "source document")
228
+ logical_name = _nonempty_string(data["logical_name"], "source logical name")
229
+ text = _string(data["text"], "source text")
230
+ module_id = data["module_id"]
231
+ if module_id is not None:
232
+ module_id = _string(module_id, "source module ID")
233
+ return SourceDocumentInput(logical_name, text, module_id)
234
+
235
+
236
+ def _observation_to_data(observation: ObservationOptions) -> dict[str, object]:
237
+ if (
238
+ not isinstance(observation, ObservationOptions)
239
+ or observation.trace_level not in {"None", "Summary", "Decisions", "Full"}
240
+ or not isinstance(observation.telemetry_enabled, bool)
241
+ ):
242
+ raise WorkerProtocolError("worker observation options are malformed")
243
+ return {
244
+ "trace_level": observation.trace_level,
245
+ "telemetry_enabled": observation.telemetry_enabled,
246
+ }
247
+
248
+
249
+ def _observation_from_data(value: object) -> ObservationOptions:
250
+ data = _object(value, "observation options")
251
+ _exact_fields(
252
+ data, {"trace_level", "telemetry_enabled"}, "observation options"
253
+ )
254
+ trace_level = data["trace_level"]
255
+ telemetry_enabled = data["telemetry_enabled"]
256
+ if trace_level not in {"None", "Summary", "Decisions", "Full"} or not isinstance(
257
+ telemetry_enabled, bool
258
+ ):
259
+ raise WorkerProtocolError("worker observation options are malformed")
260
+ return ObservationOptions(trace_level, telemetry_enabled)