onec-interactive-runtime-core 0.1.19__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.
- onec_interactive_runtime_core-0.1.19.dist-info/METADATA +12 -0
- onec_interactive_runtime_core-0.1.19.dist-info/RECORD +87 -0
- onec_interactive_runtime_core-0.1.19.dist-info/WHEEL +4 -0
- onec_interactive_runtime_core-0.1.19.dist-info/licenses/COPYRIGHT +9 -0
- onec_interactive_runtime_core-0.1.19.dist-info/licenses/LICENSE +232 -0
- onec_interactive_runtime_core-0.1.19.dist-info/licenses/THIRD_PARTY_NOTICES.txt +32 -0
- onec_runtime/AGENTS.md +7 -0
- onec_runtime/__init__.py +3 -0
- onec_runtime/artifacts.py +59 -0
- onec_runtime/bootstrap.py +310 -0
- onec_runtime/breakpoint_workspace.py +249 -0
- onec_runtime/bsl/__init__.py +139 -0
- onec_runtime/bsl/diagnostics.py +1046 -0
- onec_runtime/bsl/full_ast_worker_projection.py +522 -0
- onec_runtime/bsl/generated_semantic_parser.py +6322 -0
- onec_runtime/bsl/lexer.py +164 -0
- onec_runtime/bsl/module_catalog.py +420 -0
- onec_runtime/bsl/module_delta.py +1079 -0
- onec_runtime/bsl/module_universe.py +1877 -0
- onec_runtime/bsl/notebook_cells.py +232 -0
- onec_runtime/bsl/notebook_method_globals.py +74 -0
- onec_runtime/bsl/notebook_methods.py +175 -0
- onec_runtime/bsl/parser_artifact_identity.py +119 -0
- onec_runtime/bsl/parser_target.py +388 -0
- onec_runtime/bsl/preprocessor.py +16 -0
- onec_runtime/bsl/semantic_lowering.py +1539 -0
- onec_runtime/bsl/source_maps.py +2411 -0
- onec_runtime/bsl/worker_dependency_resolver.py +289 -0
- onec_runtime/bsl/worker_preprocessor.py +247 -0
- onec_runtime/bsl/worker_projection_model.py +137 -0
- onec_runtime/bsl/worker_reload_source_map.py +1697 -0
- onec_runtime/capture.py +113 -0
- onec_runtime/capture_source.py +453 -0
- onec_runtime/compact_table.py +376 -0
- onec_runtime/compact_table_backend.py +500 -0
- onec_runtime/config.py +198 -0
- onec_runtime/configurator_agent.py +520 -0
- onec_runtime/controller_worker.py +400 -0
- onec_runtime/credentials.py +18 -0
- onec_runtime/epf_container.py +196 -0
- onec_runtime/errors.py +205 -0
- onec_runtime/experiment.py +130 -0
- onec_runtime/extension_bundle.py +944 -0
- onec_runtime/extension_lifecycle.py +402 -0
- onec_runtime/extension_state.py +222 -0
- onec_runtime/fault_injection.py +30 -0
- onec_runtime/kernel.py +179 -0
- onec_runtime/lease.py +123 -0
- onec_runtime/observation.py +287 -0
- onec_runtime/performance_profile.py +245 -0
- onec_runtime/privacy.py +263 -0
- onec_runtime/processes.py +265 -0
- onec_runtime/prototype_runtime.py +2644 -0
- onec_runtime/rdbg/__init__.py +1 -0
- onec_runtime/rdbg/models.py +130 -0
- onec_runtime/rdbg/reconnect.py +59 -0
- onec_runtime/rdbg/session.py +913 -0
- onec_runtime/rdbg/transport.py +138 -0
- onec_runtime/rdbg/xml_codec.py +980 -0
- onec_runtime/recovery.py +106 -0
- onec_runtime/recovery_journal.py +79 -0
- onec_runtime/resources/extension/OnecInteractiveRuntime.cfe +0 -0
- onec_runtime/resources/extension/extension-manifest.json +108 -0
- onec_runtime/runtime_api.py +5272 -0
- onec_runtime/runtime_contracts.py +196 -0
- onec_runtime/server_worker.py +2034 -0
- onec_runtime/session.py +2123 -0
- onec_runtime/startup_diagnostics.py +45 -0
- onec_runtime/stop_routing.py +65 -0
- onec_runtime/supervised_processes.py +232 -0
- onec_runtime/supervised_runtime.py +512 -0
- onec_runtime/supervisor.py +363 -0
- onec_runtime/supervisor_model.py +148 -0
- onec_runtime/supervisor_protocol.py +77 -0
- onec_runtime/table_materialization.py +471 -0
- onec_runtime/table_transfer_backend.py +203 -0
- onec_runtime/table_value.py +311 -0
- onec_runtime/toolchain.py +730 -0
- onec_runtime/value_materialization.py +432 -0
- onec_runtime/value_transfer_backend.py +199 -0
- onec_runtime/worker_breakpoints.py +1431 -0
- onec_runtime/worker_epf.py +313 -0
- onec_runtime/worker_epf_template.py +53 -0
- onec_runtime/worker_stage_protocol.py +516 -0
- onec_runtime/worker_universe.py +3772 -0
- onec_runtime_build/__init__.py +1 -0
- onec_runtime_build/parser_target_development.py +116 -0
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections.abc import Callable
|
|
4
|
+
from dataclasses import asdict, dataclass
|
|
5
|
+
from time import monotonic
|
|
6
|
+
from typing import Protocol
|
|
7
|
+
from uuid import UUID
|
|
8
|
+
|
|
9
|
+
from onec_runtime.errors import (
|
|
10
|
+
CommandTimeout,
|
|
11
|
+
ExtensionHandshakeError,
|
|
12
|
+
ProtocolError,
|
|
13
|
+
UnexpectedStop,
|
|
14
|
+
)
|
|
15
|
+
from onec_runtime.extension_bundle import EXTENSION_NAME, ExtensionHandshakeEvidence
|
|
16
|
+
from onec_runtime.kernel import SESSION_MODULE_PROPERTY_ID
|
|
17
|
+
from onec_runtime.rdbg.models import (
|
|
18
|
+
DebugTarget,
|
|
19
|
+
EvaluationResult,
|
|
20
|
+
ModifyResult,
|
|
21
|
+
ModuleLocation,
|
|
22
|
+
StopEvent,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class BootstrapSession(Protocol):
|
|
27
|
+
break_on_next: bool
|
|
28
|
+
|
|
29
|
+
def wait_for_any_stop(
|
|
30
|
+
self,
|
|
31
|
+
*,
|
|
32
|
+
timeout_s: float,
|
|
33
|
+
on_poll: Callable[[], None] | None = None,
|
|
34
|
+
) -> StopEvent: ...
|
|
35
|
+
|
|
36
|
+
def continue_(self) -> None: ...
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class ServerBootstrapSession(Protocol):
|
|
40
|
+
expected_location: ModuleLocation
|
|
41
|
+
target: DebugTarget | None
|
|
42
|
+
|
|
43
|
+
def set_service_breakpoint(self) -> None: ...
|
|
44
|
+
|
|
45
|
+
def continue_(self) -> None: ...
|
|
46
|
+
|
|
47
|
+
def wait_for_service_stop(self, *, timeout_s: float) -> StopEvent: ...
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class ServerGuardSession(Protocol):
|
|
51
|
+
def evaluate(self, expression: str) -> EvaluationResult: ...
|
|
52
|
+
|
|
53
|
+
def modify(self, variable: str, value_expression: str) -> ModifyResult: ...
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
@dataclass(frozen=True, slots=True)
|
|
57
|
+
class ExtensionHandshakeContract:
|
|
58
|
+
product_id: str
|
|
59
|
+
artifact_version: str
|
|
60
|
+
protocol_version: str
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
_HANDSHAKE_EXPRESSIONS = (
|
|
64
|
+
("product_id", "ИдентификаторПродуктаRuntime"),
|
|
65
|
+
("artifact_version", "ВерсияАртефактаRuntime"),
|
|
66
|
+
("protocol_version", "ВерсияПротоколаRuntime"),
|
|
67
|
+
)
|
|
68
|
+
_MAX_HANDSHAKE_VALUE_LENGTH = 128
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _handshake_string(result: EvaluationResult, variable: str) -> str:
|
|
72
|
+
if result.error_occurred:
|
|
73
|
+
raise ExtensionHandshakeError(
|
|
74
|
+
f"Extension handshake evaluation failed for {variable}"
|
|
75
|
+
)
|
|
76
|
+
if result.type_name != "Строка":
|
|
77
|
+
raise ExtensionHandshakeError(
|
|
78
|
+
f"Extension handshake variable {variable} is not a string"
|
|
79
|
+
)
|
|
80
|
+
presentation = result.presentation
|
|
81
|
+
if (
|
|
82
|
+
not presentation
|
|
83
|
+
or len(presentation) > _MAX_HANDSHAKE_VALUE_LENGTH
|
|
84
|
+
or any(ord(character) < 32 for character in presentation)
|
|
85
|
+
):
|
|
86
|
+
raise ExtensionHandshakeError(
|
|
87
|
+
f"Extension handshake variable {variable} has an unsafe value"
|
|
88
|
+
)
|
|
89
|
+
if presentation.startswith('"') or presentation.endswith('"'):
|
|
90
|
+
if not (presentation.startswith('"') and presentation.endswith('"')):
|
|
91
|
+
raise ExtensionHandshakeError(
|
|
92
|
+
f"Extension handshake variable {variable} has an invalid presentation"
|
|
93
|
+
)
|
|
94
|
+
inner = presentation[1:-1]
|
|
95
|
+
if '"' in inner.replace('""', ""):
|
|
96
|
+
raise ExtensionHandshakeError(
|
|
97
|
+
f"Extension handshake variable {variable} has an invalid presentation"
|
|
98
|
+
)
|
|
99
|
+
presentation = inner.replace('""', '"')
|
|
100
|
+
if not presentation:
|
|
101
|
+
raise ExtensionHandshakeError(
|
|
102
|
+
f"Extension handshake variable {variable} has an empty value"
|
|
103
|
+
)
|
|
104
|
+
if len(presentation) > _MAX_HANDSHAKE_VALUE_LENGTH:
|
|
105
|
+
raise ExtensionHandshakeError(
|
|
106
|
+
f"Extension handshake variable {variable} has an unsafe value"
|
|
107
|
+
)
|
|
108
|
+
return presentation
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def observe_extension_handshake(
|
|
112
|
+
session: ServerGuardSession,
|
|
113
|
+
*,
|
|
114
|
+
target_type: str,
|
|
115
|
+
location: ModuleLocation,
|
|
116
|
+
) -> ExtensionHandshakeEvidence:
|
|
117
|
+
values = {
|
|
118
|
+
field: _handshake_string(session.evaluate(variable), variable)
|
|
119
|
+
for field, variable in _HANDSHAKE_EXPRESSIONS
|
|
120
|
+
}
|
|
121
|
+
return ExtensionHandshakeEvidence(
|
|
122
|
+
target_type=target_type,
|
|
123
|
+
product_id=values["product_id"],
|
|
124
|
+
artifact_version=values["artifact_version"],
|
|
125
|
+
protocol_version=values["protocol_version"],
|
|
126
|
+
location=location,
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def verify_extension_handshake(
|
|
131
|
+
session: ServerGuardSession,
|
|
132
|
+
contract: ExtensionHandshakeContract,
|
|
133
|
+
*,
|
|
134
|
+
target_type: str,
|
|
135
|
+
location: ModuleLocation,
|
|
136
|
+
) -> ExtensionHandshakeEvidence:
|
|
137
|
+
evidence = observe_extension_handshake(
|
|
138
|
+
session,
|
|
139
|
+
target_type=target_type,
|
|
140
|
+
location=location,
|
|
141
|
+
)
|
|
142
|
+
expected = (
|
|
143
|
+
("ИдентификаторПродуктаRuntime", evidence.product_id, contract.product_id),
|
|
144
|
+
("ВерсияАртефактаRuntime", evidence.artifact_version, contract.artifact_version),
|
|
145
|
+
("ВерсияПротоколаRuntime", evidence.protocol_version, contract.protocol_version),
|
|
146
|
+
)
|
|
147
|
+
for variable, actual, required in expected:
|
|
148
|
+
if actual != required:
|
|
149
|
+
raise ExtensionHandshakeError(
|
|
150
|
+
f"Extension handshake mismatch for {variable}"
|
|
151
|
+
)
|
|
152
|
+
return evidence
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def verify_extension_safe_mode_disabled(session: ServerGuardSession) -> None:
|
|
156
|
+
"""Verify the installed extension property in the live server infobase."""
|
|
157
|
+
expression = (
|
|
158
|
+
'РасширенияКонфигурации.Получить(Новый Структура("Имя", '
|
|
159
|
+
f'"{EXTENSION_NAME}"))[0].БезопасныйРежим'
|
|
160
|
+
)
|
|
161
|
+
result = session.evaluate(expression)
|
|
162
|
+
if (
|
|
163
|
+
result.error_occurred
|
|
164
|
+
or result.type_name not in {"Булево", "Boolean"}
|
|
165
|
+
or result.presentation not in {"Ложь", "False"}
|
|
166
|
+
):
|
|
167
|
+
raise ExtensionHandshakeError(
|
|
168
|
+
"Runtime extension safe mode is enabled or could not be verified"
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
@dataclass(frozen=True)
|
|
173
|
+
class GuardValueEvidence:
|
|
174
|
+
type_name: str
|
|
175
|
+
presentation: str
|
|
176
|
+
error_occurred: bool
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
@dataclass(frozen=True)
|
|
180
|
+
class ServerLoopGuardEvidence:
|
|
181
|
+
variable: str
|
|
182
|
+
before: GuardValueEvidence
|
|
183
|
+
write: GuardValueEvidence
|
|
184
|
+
after: GuardValueEvidence
|
|
185
|
+
|
|
186
|
+
def as_dict(self) -> dict[str, object]:
|
|
187
|
+
return {
|
|
188
|
+
"variable": self.variable,
|
|
189
|
+
"before": asdict(self.before),
|
|
190
|
+
"write": {"expression": "Истина", **asdict(self.write)},
|
|
191
|
+
"after": asdict(self.after),
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def _guard_value(result: EvaluationResult | ModifyResult) -> GuardValueEvidence:
|
|
196
|
+
return GuardValueEvidence(
|
|
197
|
+
type_name=result.type_name,
|
|
198
|
+
presentation=result.presentation,
|
|
199
|
+
error_occurred=result.error_occurred,
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
def _is_boolean(result: EvaluationResult | ModifyResult, presentation: str) -> bool:
|
|
204
|
+
return (
|
|
205
|
+
not result.error_occurred
|
|
206
|
+
and result.type_name == "Булево"
|
|
207
|
+
and result.presentation == presentation
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def enable_server_kernel_loop(
|
|
212
|
+
session: ServerGuardSession,
|
|
213
|
+
) -> ServerLoopGuardEvidence:
|
|
214
|
+
variable = "ПродолжатьЦикл"
|
|
215
|
+
before = session.evaluate(variable)
|
|
216
|
+
if not _is_boolean(before, "Ложь"):
|
|
217
|
+
raise ProtocolError("Server KernelLoop guard was not Ложь before bootstrap")
|
|
218
|
+
write = session.modify(variable, "Истина")
|
|
219
|
+
if not _is_boolean(write, "Истина"):
|
|
220
|
+
detail = f": {write.error_text}" if write.error_text else ""
|
|
221
|
+
raise ProtocolError(f"Unable to enable server KernelLoop guard{detail}")
|
|
222
|
+
after = session.evaluate(variable)
|
|
223
|
+
if not _is_boolean(after, "Истина"):
|
|
224
|
+
raise ProtocolError("Server KernelLoop guard readback was not Истина")
|
|
225
|
+
return ServerLoopGuardEvidence(
|
|
226
|
+
variable=variable,
|
|
227
|
+
before=_guard_value(before),
|
|
228
|
+
write=_guard_value(write),
|
|
229
|
+
after=_guard_value(after),
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
def _is_known_break_on_next_transient(
|
|
234
|
+
session: BootstrapSession,
|
|
235
|
+
location: ModuleLocation,
|
|
236
|
+
expected: ModuleLocation,
|
|
237
|
+
) -> bool:
|
|
238
|
+
if not session.break_on_next:
|
|
239
|
+
return False
|
|
240
|
+
return bool(
|
|
241
|
+
(
|
|
242
|
+
location.module_type == "ConfigModule"
|
|
243
|
+
and location.property_id == UUID(SESSION_MODULE_PROPERTY_ID)
|
|
244
|
+
and location.line > 0
|
|
245
|
+
)
|
|
246
|
+
or (
|
|
247
|
+
location.module_type == expected.module_type
|
|
248
|
+
and location.url == expected.url
|
|
249
|
+
and location.object_id == expected.object_id
|
|
250
|
+
and location.property_id == expected.property_id
|
|
251
|
+
and location.extension_name == expected.extension_name
|
|
252
|
+
and location.ext_id == expected.ext_id
|
|
253
|
+
and 0 < location.line < expected.line
|
|
254
|
+
)
|
|
255
|
+
)
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
def wait_for_managed_startup_stop(
|
|
259
|
+
session: BootstrapSession,
|
|
260
|
+
expected: ModuleLocation,
|
|
261
|
+
*,
|
|
262
|
+
timeout_s: float,
|
|
263
|
+
on_poll: Callable[[], None] | None = None,
|
|
264
|
+
) -> StopEvent:
|
|
265
|
+
deadline = monotonic() + timeout_s
|
|
266
|
+
while monotonic() < deadline:
|
|
267
|
+
remaining = max(0.1, deadline - monotonic())
|
|
268
|
+
if on_poll is None:
|
|
269
|
+
stop = session.wait_for_any_stop(timeout_s=remaining)
|
|
270
|
+
else:
|
|
271
|
+
stop = session.wait_for_any_stop(timeout_s=remaining, on_poll=on_poll)
|
|
272
|
+
if stop.location == expected:
|
|
273
|
+
return stop
|
|
274
|
+
if _is_known_break_on_next_transient(session, stop.location, expected):
|
|
275
|
+
session.continue_()
|
|
276
|
+
continue
|
|
277
|
+
raise UnexpectedStop(f"Unexpected managed bootstrap stop: {stop.location!r}")
|
|
278
|
+
raise CommandTimeout("Timed out waiting for managed startup breakpoint")
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
def wait_for_server_entry_then_service(
|
|
282
|
+
session: ServerBootstrapSession,
|
|
283
|
+
entry_location: ModuleLocation,
|
|
284
|
+
service_location: ModuleLocation,
|
|
285
|
+
*,
|
|
286
|
+
timeout_s: float,
|
|
287
|
+
on_entry: Callable[[ServerBootstrapSession], None] | None = None,
|
|
288
|
+
server_target_type: str = "ServerEmulation",
|
|
289
|
+
) -> tuple[StopEvent, StopEvent]:
|
|
290
|
+
deadline = monotonic() + timeout_s
|
|
291
|
+
|
|
292
|
+
def remaining() -> float:
|
|
293
|
+
return max(0.1, deadline - monotonic())
|
|
294
|
+
|
|
295
|
+
session.expected_location = entry_location
|
|
296
|
+
session.set_service_breakpoint()
|
|
297
|
+
session.continue_()
|
|
298
|
+
entry_stop = session.wait_for_service_stop(timeout_s=remaining())
|
|
299
|
+
if session.target is None or session.target.target_type != server_target_type:
|
|
300
|
+
raise ProtocolError(f"Runtime server entry did not stop on {server_target_type}")
|
|
301
|
+
if on_entry is not None:
|
|
302
|
+
on_entry(session)
|
|
303
|
+
|
|
304
|
+
session.expected_location = service_location
|
|
305
|
+
session.set_service_breakpoint()
|
|
306
|
+
session.continue_()
|
|
307
|
+
service_stop = session.wait_for_service_stop(timeout_s=remaining())
|
|
308
|
+
if session.target is None or session.target.target_type != server_target_type:
|
|
309
|
+
raise ProtocolError(f"Runtime service loop did not stop on {server_target_type}")
|
|
310
|
+
return entry_stop, service_stop
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
"""Single-owner, full-replacement RDBG breakpoint workspace."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from hashlib import sha256
|
|
7
|
+
import json
|
|
8
|
+
from threading import RLock
|
|
9
|
+
from typing import Protocol
|
|
10
|
+
|
|
11
|
+
from onec_runtime.errors import ProtocolError
|
|
12
|
+
from onec_runtime.rdbg.models import ModuleLocation
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class BreakpointWorkspaceOutcomeUnknown(ProtocolError):
|
|
16
|
+
"""The debugger may have applied a workspace whose receipt was not observed."""
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class _BreakpointSession(Protocol):
|
|
20
|
+
def set_breakpoints(self, locations: tuple[ModuleLocation, ...]) -> None: ...
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _location_key(location: ModuleLocation) -> tuple[object, ...]:
|
|
24
|
+
return (
|
|
25
|
+
location.module_type,
|
|
26
|
+
location.url,
|
|
27
|
+
location.object_id.hex,
|
|
28
|
+
location.property_id.hex,
|
|
29
|
+
location.line,
|
|
30
|
+
location.extension_name,
|
|
31
|
+
location.ext_id,
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _location_payload(location: ModuleLocation) -> dict[str, object]:
|
|
36
|
+
return {
|
|
37
|
+
"module_type": location.module_type,
|
|
38
|
+
"url": location.url,
|
|
39
|
+
"object_id": str(location.object_id),
|
|
40
|
+
"property_id": str(location.property_id),
|
|
41
|
+
"line": location.line,
|
|
42
|
+
"extension_name": location.extension_name,
|
|
43
|
+
"ext_id": location.ext_id,
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _validate_locations(
|
|
48
|
+
value: tuple[ModuleLocation, ...],
|
|
49
|
+
group: str,
|
|
50
|
+
) -> None:
|
|
51
|
+
if (
|
|
52
|
+
type(value) is not tuple
|
|
53
|
+
or any(type(location) is not ModuleLocation for location in value)
|
|
54
|
+
or len(set(value)) != len(value)
|
|
55
|
+
):
|
|
56
|
+
raise ValueError(f"{group} breakpoint locations are invalid")
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
@dataclass(frozen=True, slots=True, repr=False)
|
|
60
|
+
class WorkspaceSnapshot:
|
|
61
|
+
version: int
|
|
62
|
+
service: ModuleLocation
|
|
63
|
+
captures: tuple[ModuleLocation, ...]
|
|
64
|
+
ordinary_users: tuple[ModuleLocation, ...]
|
|
65
|
+
worker_slots: tuple[ModuleLocation, ...]
|
|
66
|
+
shielded: bool
|
|
67
|
+
|
|
68
|
+
def __post_init__(self) -> None:
|
|
69
|
+
if type(self.version) is not int or self.version < 0:
|
|
70
|
+
raise ValueError("breakpoint workspace version is invalid")
|
|
71
|
+
if type(self.service) is not ModuleLocation:
|
|
72
|
+
raise ValueError("breakpoint workspace service location is invalid")
|
|
73
|
+
_validate_locations(self.captures, "capture")
|
|
74
|
+
_validate_locations(self.ordinary_users, "ordinary user")
|
|
75
|
+
_validate_locations(self.worker_slots, "Worker")
|
|
76
|
+
if type(self.shielded) is not bool:
|
|
77
|
+
raise ValueError("breakpoint workspace shield is invalid")
|
|
78
|
+
groups = (
|
|
79
|
+
{self.service},
|
|
80
|
+
set(self.captures),
|
|
81
|
+
set(self.ordinary_users),
|
|
82
|
+
set(self.worker_slots),
|
|
83
|
+
)
|
|
84
|
+
for left_index, left in enumerate(groups):
|
|
85
|
+
for right in groups[left_index + 1 :]:
|
|
86
|
+
if left & right:
|
|
87
|
+
raise ProtocolError("Breakpoint workspace groups overlap")
|
|
88
|
+
|
|
89
|
+
@property
|
|
90
|
+
def effective_locations(self) -> tuple[ModuleLocation, ...]:
|
|
91
|
+
return (
|
|
92
|
+
(self.service,)
|
|
93
|
+
+ (() if self.shielded else self.captures)
|
|
94
|
+
+ self.ordinary_users
|
|
95
|
+
+ self.worker_slots
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
@property
|
|
99
|
+
def digest(self) -> str:
|
|
100
|
+
payload = json.dumps(
|
|
101
|
+
[_location_payload(item) for item in self.effective_locations],
|
|
102
|
+
ensure_ascii=False,
|
|
103
|
+
sort_keys=True,
|
|
104
|
+
separators=(",", ":"),
|
|
105
|
+
).encode("utf-8")
|
|
106
|
+
return sha256(payload).hexdigest()
|
|
107
|
+
|
|
108
|
+
def __repr__(self) -> str:
|
|
109
|
+
return (
|
|
110
|
+
"WorkspaceSnapshot("
|
|
111
|
+
f"version={self.version}, service=<redacted>, "
|
|
112
|
+
f"captures={len(self.captures)}, "
|
|
113
|
+
f"ordinary_users={len(self.ordinary_users)}, "
|
|
114
|
+
f"worker_slots={len(self.worker_slots)}, "
|
|
115
|
+
f"shielded={self.shielded}, digest={self.digest!r})"
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
@dataclass(frozen=True, slots=True)
|
|
120
|
+
class WorkspaceInstallReceipt:
|
|
121
|
+
version: int
|
|
122
|
+
requested_digest: str
|
|
123
|
+
|
|
124
|
+
def __post_init__(self) -> None:
|
|
125
|
+
if (
|
|
126
|
+
type(self.version) is not int
|
|
127
|
+
or self.version < 0
|
|
128
|
+
or type(self.requested_digest) is not str
|
|
129
|
+
or len(self.requested_digest) != 64
|
|
130
|
+
):
|
|
131
|
+
raise ValueError("breakpoint workspace receipt is invalid")
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
class BreakpointWorkspaceController:
|
|
135
|
+
"""Serialize complete debugger workspace replacements and their evidence."""
|
|
136
|
+
|
|
137
|
+
__slots__ = (
|
|
138
|
+
"_confirmed",
|
|
139
|
+
"_lock",
|
|
140
|
+
"_prepared",
|
|
141
|
+
"_session",
|
|
142
|
+
"_unknown",
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
def __init__(
|
|
146
|
+
self,
|
|
147
|
+
session: _BreakpointSession,
|
|
148
|
+
initial: WorkspaceSnapshot,
|
|
149
|
+
) -> None:
|
|
150
|
+
if type(initial) is not WorkspaceSnapshot:
|
|
151
|
+
raise TypeError("initial breakpoint workspace is required")
|
|
152
|
+
self._session = session
|
|
153
|
+
self._confirmed = initial
|
|
154
|
+
self._unknown = False
|
|
155
|
+
self._prepared: dict[int, WorkspaceSnapshot] = {}
|
|
156
|
+
self._lock = RLock()
|
|
157
|
+
|
|
158
|
+
@property
|
|
159
|
+
def confirmed_snapshot(self) -> WorkspaceSnapshot:
|
|
160
|
+
with self._lock:
|
|
161
|
+
return self._confirmed
|
|
162
|
+
|
|
163
|
+
def prepare(
|
|
164
|
+
self,
|
|
165
|
+
*,
|
|
166
|
+
captures: tuple[ModuleLocation, ...],
|
|
167
|
+
ordinary_users: tuple[ModuleLocation, ...],
|
|
168
|
+
worker_slots: tuple[ModuleLocation, ...],
|
|
169
|
+
shielded: bool,
|
|
170
|
+
) -> WorkspaceSnapshot:
|
|
171
|
+
if (
|
|
172
|
+
type(worker_slots) is not tuple
|
|
173
|
+
or any(type(item) is not ModuleLocation for item in worker_slots)
|
|
174
|
+
):
|
|
175
|
+
raise ValueError("Worker breakpoint locations are invalid")
|
|
176
|
+
unique_worker_slots = tuple(sorted(set(worker_slots), key=_location_key))
|
|
177
|
+
with self._lock:
|
|
178
|
+
self.require_confirmed()
|
|
179
|
+
snapshot = WorkspaceSnapshot(
|
|
180
|
+
self._confirmed.version + 1,
|
|
181
|
+
self._confirmed.service,
|
|
182
|
+
captures,
|
|
183
|
+
ordinary_users,
|
|
184
|
+
unique_worker_slots,
|
|
185
|
+
shielded,
|
|
186
|
+
)
|
|
187
|
+
self._prepared[id(snapshot)] = snapshot
|
|
188
|
+
return snapshot
|
|
189
|
+
|
|
190
|
+
def install(self, snapshot: WorkspaceSnapshot) -> WorkspaceInstallReceipt:
|
|
191
|
+
if type(snapshot) is not WorkspaceSnapshot:
|
|
192
|
+
raise TypeError("breakpoint workspace snapshot is required")
|
|
193
|
+
with self._lock:
|
|
194
|
+
self.require_confirmed()
|
|
195
|
+
if (
|
|
196
|
+
self._prepared.get(id(snapshot)) is not snapshot
|
|
197
|
+
or snapshot.version != self._confirmed.version + 1
|
|
198
|
+
):
|
|
199
|
+
raise ProtocolError("Breakpoint workspace proposal is stale or foreign")
|
|
200
|
+
del self._prepared[id(snapshot)]
|
|
201
|
+
if snapshot.effective_locations != self._confirmed.effective_locations:
|
|
202
|
+
setter = getattr(self._session, "set_breakpoints", None)
|
|
203
|
+
if not callable(setter):
|
|
204
|
+
raise ProtocolError("Breakpoint workspace session is unavailable")
|
|
205
|
+
try:
|
|
206
|
+
setter(snapshot.effective_locations)
|
|
207
|
+
except BaseException as error:
|
|
208
|
+
self._unknown = True
|
|
209
|
+
self._prepared.clear()
|
|
210
|
+
raise BreakpointWorkspaceOutcomeUnknown(
|
|
211
|
+
"Worker breakpoint workspace outcome is unknown"
|
|
212
|
+
) from error
|
|
213
|
+
self._confirmed = snapshot
|
|
214
|
+
self._prepared = {
|
|
215
|
+
key: value
|
|
216
|
+
for key, value in self._prepared.items()
|
|
217
|
+
if value.version > snapshot.version
|
|
218
|
+
}
|
|
219
|
+
return WorkspaceInstallReceipt(snapshot.version, snapshot.digest)
|
|
220
|
+
|
|
221
|
+
def require_confirmed(self) -> None:
|
|
222
|
+
with self._lock:
|
|
223
|
+
if self._unknown:
|
|
224
|
+
raise BreakpointWorkspaceOutcomeUnknown(
|
|
225
|
+
"Worker breakpoint workspace outcome is unknown"
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
def quarantine(self) -> None:
|
|
229
|
+
with self._lock:
|
|
230
|
+
self._unknown = True
|
|
231
|
+
self._prepared.clear()
|
|
232
|
+
|
|
233
|
+
def _adopt_confirmed_session(
|
|
234
|
+
self,
|
|
235
|
+
session: _BreakpointSession,
|
|
236
|
+
effective_locations: tuple[ModuleLocation, ...],
|
|
237
|
+
) -> None:
|
|
238
|
+
"""Bind a recovered session after it replayed the exact confirmed workspace."""
|
|
239
|
+
with self._lock:
|
|
240
|
+
if not callable(getattr(session, "set_breakpoints", None)):
|
|
241
|
+
raise TypeError("breakpoint workspace session is invalid")
|
|
242
|
+
if (
|
|
243
|
+
type(effective_locations) is not tuple
|
|
244
|
+
or effective_locations != self._confirmed.effective_locations
|
|
245
|
+
):
|
|
246
|
+
raise ProtocolError("Recovered breakpoint workspace does not match")
|
|
247
|
+
self._session = session
|
|
248
|
+
self._unknown = False
|
|
249
|
+
self._prepared.clear()
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
"""Minimal Python lexer/parser target used by the BSL grammar spike."""
|
|
2
|
+
|
|
3
|
+
from onec_runtime.bsl.diagnostics import (
|
|
4
|
+
DiagnosticCoordinateSpace,
|
|
5
|
+
DiagnosticStage,
|
|
6
|
+
LoweredSourceLocation,
|
|
7
|
+
MappingConfidence,
|
|
8
|
+
NormalizedDiagnostic,
|
|
9
|
+
ParsedPlatformDiagnostic,
|
|
10
|
+
PlatformCoordinateCodec,
|
|
11
|
+
VisibleSourceContext,
|
|
12
|
+
VisibleSourceLocation,
|
|
13
|
+
normalize_source_error,
|
|
14
|
+
parse_platform_diagnostic,
|
|
15
|
+
remap_platform_diagnostic,
|
|
16
|
+
)
|
|
17
|
+
from onec_runtime.bsl.preprocessor import preprocess_server_source
|
|
18
|
+
from onec_runtime.bsl.semantic_lowering import (
|
|
19
|
+
LoweringMode,
|
|
20
|
+
MethodScope,
|
|
21
|
+
ModuleBinding,
|
|
22
|
+
NameBinding,
|
|
23
|
+
SemanticLoweringError,
|
|
24
|
+
SemanticLoweringResult,
|
|
25
|
+
SemanticNotebookLowerer,
|
|
26
|
+
SourceEdit,
|
|
27
|
+
WorkerExport,
|
|
28
|
+
)
|
|
29
|
+
from onec_runtime.bsl.source_maps import (
|
|
30
|
+
LineIndex,
|
|
31
|
+
MappedSource,
|
|
32
|
+
MappingRelation,
|
|
33
|
+
SourceArtifactKind,
|
|
34
|
+
SourceArtifactRef,
|
|
35
|
+
SourceMap,
|
|
36
|
+
SourceSpan,
|
|
37
|
+
SourceTransformBuilder,
|
|
38
|
+
SourceUnitKind,
|
|
39
|
+
SourceUnitRef,
|
|
40
|
+
mapped_visible_source,
|
|
41
|
+
source_sha256,
|
|
42
|
+
)
|
|
43
|
+
from onec_runtime.bsl.worker_projection_model import (
|
|
44
|
+
BareName,
|
|
45
|
+
BareNameKind,
|
|
46
|
+
ParsedMethodModel,
|
|
47
|
+
ParsedModuleModel,
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
__all__ = [
|
|
51
|
+
"DiagnosticCoordinateSpace",
|
|
52
|
+
"DiagnosticStage",
|
|
53
|
+
"LoweredSourceLocation",
|
|
54
|
+
"LoweringMode",
|
|
55
|
+
"MappingConfidence",
|
|
56
|
+
"MethodScope",
|
|
57
|
+
"ModuleUniverseAdmissionError",
|
|
58
|
+
"ModuleBinding",
|
|
59
|
+
"NameBinding",
|
|
60
|
+
"NormalizedDiagnostic",
|
|
61
|
+
"ParsedPlatformDiagnostic",
|
|
62
|
+
"PlatformCoordinateCodec",
|
|
63
|
+
"BareName",
|
|
64
|
+
"BareNameKind",
|
|
65
|
+
"ParsedMethodModel",
|
|
66
|
+
"ParsedModuleModel",
|
|
67
|
+
"SemanticLoweringError",
|
|
68
|
+
"SemanticLoweringResult",
|
|
69
|
+
"SemanticNotebookLowerer",
|
|
70
|
+
"SourceEdit",
|
|
71
|
+
"WorkerExport",
|
|
72
|
+
"WorkerModuleUnit",
|
|
73
|
+
"VisibleSourceContext",
|
|
74
|
+
"VisibleSourceLocation",
|
|
75
|
+
"normalize_source_error",
|
|
76
|
+
"parse_platform_diagnostic",
|
|
77
|
+
"remap_platform_diagnostic",
|
|
78
|
+
"preprocess_server_source",
|
|
79
|
+
"CommonModuleCatalogSnapshot",
|
|
80
|
+
"CommonModuleDescriptor",
|
|
81
|
+
"CommonModuleScope",
|
|
82
|
+
"SessionCommonModuleCatalog",
|
|
83
|
+
"LineIndex",
|
|
84
|
+
"MappedSource",
|
|
85
|
+
"MappingRelation",
|
|
86
|
+
"SourceArtifactKind",
|
|
87
|
+
"SourceArtifactRef",
|
|
88
|
+
"SourceMap",
|
|
89
|
+
"SourceSpan",
|
|
90
|
+
"SourceTransformBuilder",
|
|
91
|
+
"SourceUnitKind",
|
|
92
|
+
"SourceUnitRef",
|
|
93
|
+
"mapped_visible_source",
|
|
94
|
+
"source_sha256",
|
|
95
|
+
"MODULE_SCOPE_DEPENDENCY",
|
|
96
|
+
"ResolvedMethodPlan",
|
|
97
|
+
"ResolvedModulePlan",
|
|
98
|
+
"resolve_worker_dependencies",
|
|
99
|
+
"worker_model_candidate_names",
|
|
100
|
+
]
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def __getattr__(name: str) -> object:
|
|
104
|
+
if name not in {
|
|
105
|
+
"CommonModuleCatalogSnapshot",
|
|
106
|
+
"CommonModuleDescriptor",
|
|
107
|
+
"CommonModuleScope",
|
|
108
|
+
"ModuleUniverseAdmissionError",
|
|
109
|
+
"SessionCommonModuleCatalog",
|
|
110
|
+
"WorkerModuleUnit",
|
|
111
|
+
"MODULE_SCOPE_DEPENDENCY",
|
|
112
|
+
"ResolvedMethodPlan",
|
|
113
|
+
"ResolvedModulePlan",
|
|
114
|
+
"resolve_worker_dependencies",
|
|
115
|
+
"worker_model_candidate_names",
|
|
116
|
+
}:
|
|
117
|
+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
|
118
|
+
if name in {
|
|
119
|
+
"CommonModuleCatalogSnapshot",
|
|
120
|
+
"CommonModuleDescriptor",
|
|
121
|
+
"CommonModuleScope",
|
|
122
|
+
"SessionCommonModuleCatalog",
|
|
123
|
+
}:
|
|
124
|
+
from onec_runtime.bsl import module_catalog
|
|
125
|
+
|
|
126
|
+
return getattr(module_catalog, name)
|
|
127
|
+
if name in {
|
|
128
|
+
"MODULE_SCOPE_DEPENDENCY",
|
|
129
|
+
"ResolvedMethodPlan",
|
|
130
|
+
"ResolvedModulePlan",
|
|
131
|
+
"resolve_worker_dependencies",
|
|
132
|
+
"worker_model_candidate_names",
|
|
133
|
+
}:
|
|
134
|
+
from onec_runtime.bsl import worker_dependency_resolver
|
|
135
|
+
|
|
136
|
+
return getattr(worker_dependency_resolver, name)
|
|
137
|
+
from onec_runtime.bsl import module_universe
|
|
138
|
+
|
|
139
|
+
return getattr(module_universe, name)
|