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.
Files changed (87) hide show
  1. onec_interactive_runtime_core-0.1.19.dist-info/METADATA +12 -0
  2. onec_interactive_runtime_core-0.1.19.dist-info/RECORD +87 -0
  3. onec_interactive_runtime_core-0.1.19.dist-info/WHEEL +4 -0
  4. onec_interactive_runtime_core-0.1.19.dist-info/licenses/COPYRIGHT +9 -0
  5. onec_interactive_runtime_core-0.1.19.dist-info/licenses/LICENSE +232 -0
  6. onec_interactive_runtime_core-0.1.19.dist-info/licenses/THIRD_PARTY_NOTICES.txt +32 -0
  7. onec_runtime/AGENTS.md +7 -0
  8. onec_runtime/__init__.py +3 -0
  9. onec_runtime/artifacts.py +59 -0
  10. onec_runtime/bootstrap.py +310 -0
  11. onec_runtime/breakpoint_workspace.py +249 -0
  12. onec_runtime/bsl/__init__.py +139 -0
  13. onec_runtime/bsl/diagnostics.py +1046 -0
  14. onec_runtime/bsl/full_ast_worker_projection.py +522 -0
  15. onec_runtime/bsl/generated_semantic_parser.py +6322 -0
  16. onec_runtime/bsl/lexer.py +164 -0
  17. onec_runtime/bsl/module_catalog.py +420 -0
  18. onec_runtime/bsl/module_delta.py +1079 -0
  19. onec_runtime/bsl/module_universe.py +1877 -0
  20. onec_runtime/bsl/notebook_cells.py +232 -0
  21. onec_runtime/bsl/notebook_method_globals.py +74 -0
  22. onec_runtime/bsl/notebook_methods.py +175 -0
  23. onec_runtime/bsl/parser_artifact_identity.py +119 -0
  24. onec_runtime/bsl/parser_target.py +388 -0
  25. onec_runtime/bsl/preprocessor.py +16 -0
  26. onec_runtime/bsl/semantic_lowering.py +1539 -0
  27. onec_runtime/bsl/source_maps.py +2411 -0
  28. onec_runtime/bsl/worker_dependency_resolver.py +289 -0
  29. onec_runtime/bsl/worker_preprocessor.py +247 -0
  30. onec_runtime/bsl/worker_projection_model.py +137 -0
  31. onec_runtime/bsl/worker_reload_source_map.py +1697 -0
  32. onec_runtime/capture.py +113 -0
  33. onec_runtime/capture_source.py +453 -0
  34. onec_runtime/compact_table.py +376 -0
  35. onec_runtime/compact_table_backend.py +500 -0
  36. onec_runtime/config.py +198 -0
  37. onec_runtime/configurator_agent.py +520 -0
  38. onec_runtime/controller_worker.py +400 -0
  39. onec_runtime/credentials.py +18 -0
  40. onec_runtime/epf_container.py +196 -0
  41. onec_runtime/errors.py +205 -0
  42. onec_runtime/experiment.py +130 -0
  43. onec_runtime/extension_bundle.py +944 -0
  44. onec_runtime/extension_lifecycle.py +402 -0
  45. onec_runtime/extension_state.py +222 -0
  46. onec_runtime/fault_injection.py +30 -0
  47. onec_runtime/kernel.py +179 -0
  48. onec_runtime/lease.py +123 -0
  49. onec_runtime/observation.py +287 -0
  50. onec_runtime/performance_profile.py +245 -0
  51. onec_runtime/privacy.py +263 -0
  52. onec_runtime/processes.py +265 -0
  53. onec_runtime/prototype_runtime.py +2644 -0
  54. onec_runtime/rdbg/__init__.py +1 -0
  55. onec_runtime/rdbg/models.py +130 -0
  56. onec_runtime/rdbg/reconnect.py +59 -0
  57. onec_runtime/rdbg/session.py +913 -0
  58. onec_runtime/rdbg/transport.py +138 -0
  59. onec_runtime/rdbg/xml_codec.py +980 -0
  60. onec_runtime/recovery.py +106 -0
  61. onec_runtime/recovery_journal.py +79 -0
  62. onec_runtime/resources/extension/OnecInteractiveRuntime.cfe +0 -0
  63. onec_runtime/resources/extension/extension-manifest.json +108 -0
  64. onec_runtime/runtime_api.py +5272 -0
  65. onec_runtime/runtime_contracts.py +196 -0
  66. onec_runtime/server_worker.py +2034 -0
  67. onec_runtime/session.py +2123 -0
  68. onec_runtime/startup_diagnostics.py +45 -0
  69. onec_runtime/stop_routing.py +65 -0
  70. onec_runtime/supervised_processes.py +232 -0
  71. onec_runtime/supervised_runtime.py +512 -0
  72. onec_runtime/supervisor.py +363 -0
  73. onec_runtime/supervisor_model.py +148 -0
  74. onec_runtime/supervisor_protocol.py +77 -0
  75. onec_runtime/table_materialization.py +471 -0
  76. onec_runtime/table_transfer_backend.py +203 -0
  77. onec_runtime/table_value.py +311 -0
  78. onec_runtime/toolchain.py +730 -0
  79. onec_runtime/value_materialization.py +432 -0
  80. onec_runtime/value_transfer_backend.py +199 -0
  81. onec_runtime/worker_breakpoints.py +1431 -0
  82. onec_runtime/worker_epf.py +313 -0
  83. onec_runtime/worker_epf_template.py +53 -0
  84. onec_runtime/worker_stage_protocol.py +516 -0
  85. onec_runtime/worker_universe.py +3772 -0
  86. onec_runtime_build/__init__.py +1 -0
  87. onec_runtime_build/parser_target_development.py +116 -0
@@ -0,0 +1,1046 @@
1
+ """Bounded, typed normalization for deterministic and 1C BSL diagnostics."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import Mapping
6
+ from dataclasses import dataclass, field as dataclass_field, replace
7
+ from enum import StrEnum
8
+ from hashlib import sha256
9
+ import re
10
+
11
+ from onec_runtime.bsl.source_maps import (
12
+ LineIndex,
13
+ MappedSource,
14
+ MappingRelation,
15
+ SourceMapSegment,
16
+ SourceSpan,
17
+ SourceUnitRef,
18
+ source_sha256,
19
+ )
20
+
21
+
22
+ _PLATFORM_DIAGNOSTIC_LIMIT = 4096
23
+ _MODULE_LOCATOR_LIMIT = 512
24
+ _MODULE_COMPONENT_LIMIT = 32
25
+ _MODULE_IDENTIFIER = r"[A-Za-zА-Яа-яЁё_][0-9A-Za-zА-Яа-яЁё_]*"
26
+ _LOCATION_RE = re.compile(
27
+ rf"^\{{(?P<module><Неизвестный модуль>|Неизвестный модуль|{_MODULE_IDENTIFIER}(?:\.{_MODULE_IDENTIFIER})*)"
28
+ r"\((?P<line>[0-9]{1,10})\s*,\s*(?P<column>[0-9]{1,10})\)\}",
29
+ re.MULTILINE,
30
+ )
31
+ _WORKER_LINE_ONLY_LOCATION_RE = re.compile(
32
+ r"^\{(?P<module>ВнешняяОбработка\."
33
+ r"(?P<registration>OnecRuntime_[0-9a-f]{8}_[0-9a-f]{16})\."
34
+ r"МодульОбъекта)\((?P<line>[1-9][0-9]{0,9})\)\}",
35
+ re.IGNORECASE | re.MULTILINE,
36
+ )
37
+ _COMPILATION_MARKER_RE = re.compile(
38
+ r"(?:[ \t]+|\r?\n[ \t]*)"
39
+ r"\[ОшибкаКомпиляцииВстроенногоЯзыка\]"
40
+ r"[ \t]*(?:\r?\n)?\Z",
41
+ )
42
+ _NESTED_COMPILE_CAUSE_PREFIX_RE = re.compile(
43
+ r"(?:^|\r?\n)[ \t]*по причине:[ \t]*\r?\n\Z",
44
+ re.IGNORECASE,
45
+ )
46
+ _UNKNOWN_MODULES = frozenset({"<Неизвестный модуль>", "Неизвестный модуль"})
47
+
48
+
49
+ class DiagnosticStage(StrEnum):
50
+ PARSING = "parsing"
51
+ LOWERING = "lowering"
52
+ COMPILATION = "compilation"
53
+ EXECUTION = "execution"
54
+
55
+
56
+ class DiagnosticCoordinateSpace(StrEnum):
57
+ UNKNOWN = "unknown"
58
+ EXECUTED_BSL = "executed_bsl"
59
+ HOST_MODULE = "host_module"
60
+
61
+
62
+ class MappingConfidence(StrEnum):
63
+ EXACT = "exact"
64
+ NEAREST = "nearest"
65
+ SYNTHETIC = "synthetic"
66
+ UNKNOWN = "unknown"
67
+
68
+
69
+ class _PrivatePlatformEvidence:
70
+ """Bounded expert prose that generic serializers cannot traverse."""
71
+
72
+ __slots__ = ("_text",)
73
+
74
+ def __init__(self, text: str) -> None:
75
+ self._text = text
76
+
77
+ @property
78
+ def text(self) -> str:
79
+ return self._text
80
+
81
+ def __repr__(self) -> str:
82
+ return "<redacted platform diagnostic>"
83
+
84
+ def __deepcopy__(self, memo: dict[int, object]) -> object:
85
+ return _REDACTED_PLATFORM_EVIDENCE
86
+
87
+
88
+ class _RedactedPlatformEvidence:
89
+ __slots__ = ()
90
+
91
+ def __repr__(self) -> str:
92
+ return "<redacted platform diagnostic>"
93
+
94
+
95
+ _REDACTED_PLATFORM_EVIDENCE = _RedactedPlatformEvidence()
96
+
97
+
98
+ @dataclass(frozen=True, slots=True)
99
+ class ParsedPlatformDiagnostic:
100
+ _platform_evidence: _PrivatePlatformEvidence = dataclass_field(repr=False)
101
+ platform_diagnostic_sha256: str
102
+ platform_diagnostic_truncated: bool
103
+ platform_diagnostic_redacted: bool
104
+ line: int | None
105
+ column: int | None
106
+ coordinate_space: DiagnosticCoordinateSpace
107
+ module_name: str | None
108
+ additional_locations: tuple[tuple[int, int], ...]
109
+ has_compilation_marker: bool
110
+ locations: tuple[PlatformDiagnosticLocation, ...]
111
+
112
+ @property
113
+ def platform_diagnostic(self) -> str:
114
+ return self._platform_evidence.text
115
+
116
+
117
+ @dataclass(frozen=True, slots=True)
118
+ class VisibleSourceLocation:
119
+ source_unit: SourceUnitRef
120
+ line: int
121
+ column: int
122
+ span: SourceSpan
123
+
124
+
125
+ class VisibleSourceContext:
126
+ """Hash-fenced visible line indexes that retain no source text."""
127
+
128
+ __slots__ = ("_indices",)
129
+
130
+ def __init__(self, sources: Mapping[SourceUnitRef, str]) -> None:
131
+ if not isinstance(sources, Mapping):
132
+ raise ValueError("visible sources must be a mapping")
133
+ indices: dict[
134
+ tuple[object, str, int],
135
+ tuple[str, LineIndex],
136
+ ] = {}
137
+ for unit, source in sources.items():
138
+ if not isinstance(unit, SourceUnitRef) or type(source) is not str:
139
+ raise ValueError("visible sources require SourceUnitRef and string pairs")
140
+ indices[_visible_unit_key(unit)] = (
141
+ source_sha256(source),
142
+ LineIndex(source),
143
+ )
144
+ self._indices = indices
145
+
146
+ def line_column(
147
+ self,
148
+ unit: SourceUnitRef,
149
+ offset: int,
150
+ ) -> tuple[int, int] | None:
151
+ entry = self._indices.get(_visible_unit_key(unit))
152
+ if entry is None or entry[0] != unit.source_sha256:
153
+ return None
154
+ try:
155
+ return entry[1].offset_to_line_column(offset)
156
+ except ValueError:
157
+ return None
158
+
159
+ def line_range(
160
+ self,
161
+ unit: SourceUnitRef,
162
+ line: int,
163
+ ) -> SourceSpan | None:
164
+ entry = self._indices.get(_visible_unit_key(unit))
165
+ if entry is None or entry[0] != unit.source_sha256:
166
+ return None
167
+ try:
168
+ return entry[1].line_range(line)
169
+ except ValueError:
170
+ return None
171
+
172
+ def __repr__(self) -> str:
173
+ return f"VisibleSourceContext(entries={len(self._indices)}, source=<redacted>)"
174
+
175
+
176
+ @dataclass(frozen=True, slots=True)
177
+ class WorkerArtifactPlatformLocation:
178
+ registration_name: str
179
+
180
+
181
+ @dataclass(frozen=True, slots=True)
182
+ class PlatformDiagnosticLocation:
183
+ module_name: str
184
+ module_components: tuple[str, ...]
185
+ worker_artifact_location: WorkerArtifactPlatformLocation | None
186
+ line: int
187
+ column: int | None
188
+ coordinate_space: DiagnosticCoordinateSpace
189
+
190
+
191
+ @dataclass(frozen=True, slots=True, repr=False)
192
+ class WorkerDiagnosticArtifact:
193
+ """Private, manifest-fenced source-map input for Worker diagnostics."""
194
+
195
+ logical_name: str
196
+ revision: int
197
+ artifact_sha256: str
198
+ registration_name: str
199
+ manifest_sha256: str
200
+ source_map_sha256: str
201
+ mapped_source: MappedSource = dataclass_field(repr=False)
202
+ visible_source_context: VisibleSourceContext | None = dataclass_field(
203
+ default=None,
204
+ repr=False,
205
+ )
206
+
207
+ def __post_init__(self) -> None:
208
+ if (
209
+ not isinstance(self.logical_name, str)
210
+ or not self.logical_name
211
+ or type(self.revision) is not int
212
+ or self.revision < 0
213
+ or re.fullmatch(r"[0-9a-f]{64}", self.artifact_sha256) is None
214
+ or not isinstance(self.registration_name, str)
215
+ or not self.registration_name
216
+ or re.fullmatch(r"[0-9a-f]{64}", self.manifest_sha256) is None
217
+ or re.fullmatch(r"[0-9a-f]{64}", self.source_map_sha256) is None
218
+ or not isinstance(self.mapped_source, MappedSource)
219
+ or self.source_map_sha256 != self.mapped_source.source_map_sha256
220
+ or (
221
+ self.visible_source_context is not None
222
+ and not isinstance(self.visible_source_context, VisibleSourceContext)
223
+ )
224
+ ):
225
+ raise ValueError("worker diagnostic artifact source map identity is invalid")
226
+
227
+ def __repr__(self) -> str:
228
+ return (
229
+ "WorkerDiagnosticArtifact("
230
+ f"logical_name={self.logical_name!r}, revision={self.revision}, "
231
+ f"artifact_sha256={self.artifact_sha256!r}, source=<redacted>)"
232
+ )
233
+
234
+
235
+ @dataclass(frozen=True, slots=True)
236
+ class WorkerRuntimeFrameDiagnostic:
237
+ registration_name: str
238
+ logical_name: str | None
239
+ revision: int | None
240
+ artifact_sha256: str | None
241
+ mapping_confidence: MappingConfidence
242
+ source_unit: SourceUnitRef | None = None
243
+ visible_location: VisibleSourceLocation | None = None
244
+ related_visible_span: SourceSpan | None = None
245
+ lowered_location: LoweredSourceLocation | None = None
246
+ synthetic_region: str | None = None
247
+ dependency_anchor: SourceSpan | None = None
248
+ method_anchor: SourceSpan | None = None
249
+
250
+
251
+ @dataclass(frozen=True, slots=True)
252
+ class LoweredSourceLocation:
253
+ line: int
254
+ column: int
255
+ offset: int
256
+ span: SourceSpan
257
+
258
+
259
+ @dataclass(frozen=True, slots=True)
260
+ class NormalizedDiagnostic:
261
+ diagnostic_id: str
262
+ runtime_summary: str
263
+ stage: DiagnosticStage
264
+ mapping_confidence: MappingConfidence
265
+ code: str | None = None
266
+ source_unit: SourceUnitRef | None = None
267
+ visible_location: VisibleSourceLocation | None = None
268
+ related_visible_span: SourceSpan | None = None
269
+ lowered_location: LoweredSourceLocation | None = None
270
+ synthetic_region: str | None = None
271
+ _platform_evidence: _PrivatePlatformEvidence | None = dataclass_field(
272
+ default=None,
273
+ repr=False,
274
+ )
275
+ platform_diagnostic_sha256: str | None = None
276
+ platform_diagnostic_truncated: bool = False
277
+ platform_diagnostic_redacted: bool = False
278
+ execution_artifact_sha256: str | None = None
279
+ source_map_sha256: str | None = None
280
+ dependency_anchor: SourceSpan | None = None
281
+ method_anchor: SourceSpan | None = None
282
+ worker_frames: tuple[WorkerRuntimeFrameDiagnostic, ...] = ()
283
+
284
+ @property
285
+ def platform_diagnostic(self) -> str | None:
286
+ return (
287
+ self._platform_evidence.text
288
+ if self._platform_evidence is not None
289
+ else None
290
+ )
291
+
292
+
293
+ class PlatformCoordinateCodec:
294
+ """Decode one-based 1C coordinates against one exact artifact string."""
295
+
296
+ def __init__(self, executed_source: str) -> None:
297
+ self._index = LineIndex(executed_source)
298
+
299
+ def to_offset(self, line: int, column: int) -> int | None:
300
+ try:
301
+ return self._index.line_column_to_offset(line, column)
302
+ except ValueError:
303
+ return None
304
+
305
+
306
+ def parse_platform_diagnostic(message: str) -> ParsedPlatformDiagnostic:
307
+ """Structurally parse allowlisted 1C locations from bounded diagnostic text."""
308
+ if type(message) is not str:
309
+ raise ValueError("platform diagnostic must be a string")
310
+ bounded = message[:_PLATFORM_DIAGNOSTIC_LIMIT]
311
+ digest = sha256(message.encode("utf-8")).hexdigest()
312
+ accepted: list[tuple[re.Match[str], PlatformDiagnosticLocation]] = []
313
+ for match in _LOCATION_RE.finditer(bounded):
314
+ module = match.group("module")
315
+ components = (module,) if module in _UNKNOWN_MODULES else tuple(module.split("."))
316
+ if (
317
+ len(module) > _MODULE_LOCATOR_LIMIT
318
+ or len(components) > _MODULE_COMPONENT_LIMIT
319
+ ):
320
+ continue
321
+ accepted.append(
322
+ (
323
+ match,
324
+ PlatformDiagnosticLocation(
325
+ module,
326
+ components,
327
+ _parse_worker_artifact_location(components),
328
+ int(match.group("line")),
329
+ int(match.group("column")),
330
+ (
331
+ DiagnosticCoordinateSpace.EXECUTED_BSL
332
+ if module in _UNKNOWN_MODULES
333
+ else DiagnosticCoordinateSpace.HOST_MODULE
334
+ ),
335
+ ),
336
+ )
337
+ )
338
+ line_only_locations = tuple(
339
+ (
340
+ match.start(),
341
+ PlatformDiagnosticLocation(
342
+ match.group("module"),
343
+ tuple(match.group("module").split(".")),
344
+ WorkerArtifactPlatformLocation(match.group("registration")),
345
+ int(match.group("line")),
346
+ None,
347
+ DiagnosticCoordinateSpace.HOST_MODULE,
348
+ ),
349
+ )
350
+ for match in _WORKER_LINE_ONLY_LOCATION_RE.finditer(bounded)
351
+ )
352
+ locations = tuple(
353
+ location
354
+ for _offset, location in sorted(
355
+ (
356
+ *((match.start(), location) for match, location in accepted),
357
+ *line_only_locations,
358
+ ),
359
+ key=lambda item: item[0],
360
+ )
361
+ )
362
+ terminal_compilation = _COMPILATION_MARKER_RE.search(bounded) is not None
363
+ primary = accepted[0][0] if accepted and accepted[0][0].start() == 0 else None
364
+ if (
365
+ primary is None
366
+ and terminal_compilation
367
+ and len(accepted) == 1
368
+ and (
369
+ accepted[0][1].coordinate_space is DiagnosticCoordinateSpace.EXECUTED_BSL
370
+ or accepted[0][1].worker_artifact_location is not None
371
+ )
372
+ and _NESTED_COMPILE_CAUSE_PREFIX_RE.search(
373
+ bounded[: accepted[0][0].start()]
374
+ )
375
+ is not None
376
+ ):
377
+ primary = accepted[0][0]
378
+ if primary is None:
379
+ module_name = None
380
+ line = None
381
+ column = None
382
+ space = DiagnosticCoordinateSpace.UNKNOWN
383
+ additional: tuple[tuple[int, int], ...] = ()
384
+ else:
385
+ module_name = primary.group("module")
386
+ line = int(primary.group("line"))
387
+ column = int(primary.group("column"))
388
+ space = (
389
+ DiagnosticCoordinateSpace.EXECUTED_BSL
390
+ if module_name in _UNKNOWN_MODULES
391
+ else DiagnosticCoordinateSpace.HOST_MODULE
392
+ )
393
+ additional = tuple(
394
+ (int(match.group("line")), int(match.group("column")))
395
+ for match, _location in accepted[1:]
396
+ )
397
+ return ParsedPlatformDiagnostic(
398
+ _PrivatePlatformEvidence(bounded),
399
+ digest,
400
+ len(message) > _PLATFORM_DIAGNOSTIC_LIMIT,
401
+ False,
402
+ line,
403
+ column,
404
+ space,
405
+ module_name,
406
+ additional,
407
+ primary is not None and terminal_compilation,
408
+ locations,
409
+ )
410
+
411
+
412
+ def remap_worker_stage_diagnostic(
413
+ parsed: ParsedPlatformDiagnostic,
414
+ *,
415
+ artifact_sha256: str,
416
+ phase: str,
417
+ candidate_manifest_sha256: str,
418
+ candidate_artifacts: tuple[WorkerDiagnosticArtifact, ...],
419
+ ) -> NormalizedDiagnostic:
420
+ """Map a stage failure only through one exact candidate artifact."""
421
+ _validate_worker_diagnostic_request(
422
+ parsed,
423
+ candidate_manifest_sha256,
424
+ candidate_artifacts,
425
+ )
426
+ if (
427
+ re.fullmatch(r"[0-9a-f]{64}", artifact_sha256) is None
428
+ or phase not in {"decode", "upload", "connect", "registration-result", "create"}
429
+ ):
430
+ raise ValueError("worker artifact stage identity is invalid")
431
+ stage = (
432
+ DiagnosticStage.EXECUTION
433
+ if phase == "create" and not parsed.has_compilation_marker
434
+ else DiagnosticStage.COMPILATION
435
+ )
436
+ matches = tuple(
437
+ artifact
438
+ for artifact in candidate_artifacts
439
+ if artifact.manifest_sha256 == candidate_manifest_sha256
440
+ and artifact.artifact_sha256 == artifact_sha256
441
+ )
442
+ if len(matches) != 1:
443
+ return _unmapped_worker_diagnostic(
444
+ parsed,
445
+ stage=stage,
446
+ code="worker_artifact_identity_unmapped",
447
+ identity=(candidate_manifest_sha256, artifact_sha256, phase),
448
+ )
449
+ artifact = matches[0]
450
+ locations = _worker_stage_locations(parsed, artifact)
451
+ if not locations:
452
+ return _unmapped_worker_diagnostic(
453
+ parsed,
454
+ stage=stage,
455
+ code="worker_artifact_location_unmapped",
456
+ identity=(candidate_manifest_sha256, artifact_sha256, phase),
457
+ )
458
+ if len(locations) != 1:
459
+ return _unmapped_worker_diagnostic(
460
+ parsed,
461
+ stage=stage,
462
+ code="worker_artifact_location_ambiguous",
463
+ identity=(candidate_manifest_sha256, artifact_sha256, phase),
464
+ )
465
+ location = locations[0]
466
+ diagnostic = remap_platform_diagnostic(
467
+ replace(
468
+ parsed,
469
+ line=location.line,
470
+ column=location.column,
471
+ coordinate_space=DiagnosticCoordinateSpace.EXECUTED_BSL,
472
+ module_name=location.module_name,
473
+ ),
474
+ artifact.mapped_source,
475
+ stage=stage,
476
+ visible_source_context=artifact.visible_source_context,
477
+ )
478
+ return _with_dependency_binding(diagnostic, artifact.mapped_source)
479
+
480
+
481
+ def remap_worker_runtime_diagnostic(
482
+ parsed: ParsedPlatformDiagnostic,
483
+ *,
484
+ pinned_manifest_sha256: str,
485
+ pinned_artifacts: tuple[WorkerDiagnosticArtifact, ...],
486
+ ) -> NormalizedDiagnostic:
487
+ """Resolve every Worker stack frame through one immutable operation pin."""
488
+ _validate_worker_diagnostic_request(
489
+ parsed,
490
+ pinned_manifest_sha256,
491
+ pinned_artifacts,
492
+ )
493
+ frames: list[WorkerRuntimeFrameDiagnostic] = []
494
+ for location in parsed.locations:
495
+ worker_location = location.worker_artifact_location
496
+ if worker_location is None:
497
+ if location.module_name in _UNKNOWN_MODULES:
498
+ frames.append(
499
+ WorkerRuntimeFrameDiagnostic(
500
+ location.module_name,
501
+ None,
502
+ None,
503
+ None,
504
+ MappingConfidence.UNKNOWN,
505
+ )
506
+ )
507
+ continue
508
+ matches = tuple(
509
+ artifact
510
+ for artifact in pinned_artifacts
511
+ if artifact.manifest_sha256 == pinned_manifest_sha256
512
+ and worker_location.registration_name.casefold()
513
+ == artifact.registration_name.casefold()
514
+ )
515
+ if len(matches) != 1:
516
+ frames.append(
517
+ WorkerRuntimeFrameDiagnostic(
518
+ worker_location.registration_name,
519
+ None,
520
+ None,
521
+ None,
522
+ MappingConfidence.UNKNOWN,
523
+ )
524
+ )
525
+ continue
526
+ artifact = matches[0]
527
+ frames.append(
528
+ _worker_runtime_frame(
529
+ location,
530
+ artifact,
531
+ worker_location.registration_name,
532
+ )
533
+ )
534
+ if not frames:
535
+ return _unmapped_worker_diagnostic(
536
+ parsed,
537
+ stage=DiagnosticStage.EXECUTION,
538
+ code="worker_runtime_frame_unmapped",
539
+ identity=(pinned_manifest_sha256,),
540
+ )
541
+ primary = frames[0]
542
+ source_map_sha256 = None
543
+ execution_artifact_sha256 = None
544
+ if primary.artifact_sha256 is not None:
545
+ artifact = next(
546
+ (
547
+ item
548
+ for item in pinned_artifacts
549
+ if item.manifest_sha256 == pinned_manifest_sha256
550
+ and item.artifact_sha256 == primary.artifact_sha256
551
+ and item.registration_name.casefold()
552
+ == primary.registration_name.casefold()
553
+ ),
554
+ None,
555
+ )
556
+ if artifact is not None:
557
+ source_map_sha256 = artifact.mapped_source.source_map_sha256
558
+ execution_artifact_sha256 = artifact.mapped_source.artifact.source_sha256
559
+ return NormalizedDiagnostic(
560
+ diagnostic_id=_worker_diagnostic_id(
561
+ DiagnosticStage.EXECUTION,
562
+ "worker_runtime_frames",
563
+ parsed.platform_diagnostic_sha256,
564
+ (pinned_manifest_sha256, *(frame.registration_name for frame in frames)),
565
+ ),
566
+ runtime_summary=_summary(DiagnosticStage.EXECUTION),
567
+ stage=DiagnosticStage.EXECUTION,
568
+ mapping_confidence=primary.mapping_confidence,
569
+ code=(
570
+ "dependency_binding"
571
+ if primary.synthetic_region == "dependency_binding"
572
+ else (
573
+ "worker_runtime_frame_unmapped"
574
+ if primary.mapping_confidence is MappingConfidence.UNKNOWN
575
+ else None
576
+ )
577
+ ),
578
+ source_unit=primary.source_unit,
579
+ visible_location=primary.visible_location,
580
+ related_visible_span=primary.related_visible_span,
581
+ lowered_location=primary.lowered_location,
582
+ synthetic_region=primary.synthetic_region,
583
+ _platform_evidence=parsed._platform_evidence,
584
+ platform_diagnostic_sha256=parsed.platform_diagnostic_sha256,
585
+ platform_diagnostic_truncated=parsed.platform_diagnostic_truncated,
586
+ platform_diagnostic_redacted=parsed.platform_diagnostic_redacted,
587
+ execution_artifact_sha256=execution_artifact_sha256,
588
+ source_map_sha256=source_map_sha256,
589
+ dependency_anchor=primary.dependency_anchor,
590
+ method_anchor=primary.method_anchor,
591
+ worker_frames=tuple(frames),
592
+ )
593
+
594
+
595
+ def remap_platform_diagnostic(
596
+ parsed: ParsedPlatformDiagnostic,
597
+ executed: MappedSource,
598
+ *,
599
+ stage: DiagnosticStage,
600
+ visible_source_context: VisibleSourceContext | None = None,
601
+ ) -> NormalizedDiagnostic:
602
+ if not isinstance(parsed, ParsedPlatformDiagnostic):
603
+ raise ValueError("parsed must be a ParsedPlatformDiagnostic")
604
+ if not isinstance(executed, MappedSource):
605
+ raise ValueError("executed must be a MappedSource")
606
+ if type(stage) is not DiagnosticStage:
607
+ raise ValueError("stage must be a DiagnosticStage")
608
+ if visible_source_context is not None and not isinstance(
609
+ visible_source_context,
610
+ VisibleSourceContext,
611
+ ):
612
+ raise ValueError("visible_source_context must be a VisibleSourceContext")
613
+ effective_stage = (
614
+ DiagnosticStage.COMPILATION if parsed.has_compilation_marker else stage
615
+ )
616
+ offset = None
617
+ if (
618
+ parsed.coordinate_space is DiagnosticCoordinateSpace.EXECUTED_BSL
619
+ and parsed.line is not None
620
+ and parsed.column is not None
621
+ ):
622
+ offset = PlatformCoordinateCodec(executed.text).to_offset(
623
+ parsed.line,
624
+ parsed.column,
625
+ )
626
+ mapping = _map_executed_offset(executed, offset, visible_source_context)
627
+ lowered = None
628
+ if offset is not None and parsed.line is not None and parsed.column is not None:
629
+ width = 0 if offset == len(executed.text) else 1
630
+ lowered = LoweredSourceLocation(
631
+ parsed.line,
632
+ parsed.column,
633
+ offset,
634
+ SourceSpan(offset, offset + width),
635
+ )
636
+ diagnostic_id = _diagnostic_id(
637
+ effective_stage,
638
+ None,
639
+ parsed.platform_diagnostic_sha256,
640
+ executed,
641
+ offset,
642
+ )
643
+ return NormalizedDiagnostic(
644
+ diagnostic_id=diagnostic_id,
645
+ runtime_summary=_summary(effective_stage),
646
+ stage=effective_stage,
647
+ mapping_confidence=mapping.confidence,
648
+ source_unit=mapping.source_unit,
649
+ visible_location=mapping.visible,
650
+ related_visible_span=mapping.related,
651
+ lowered_location=lowered,
652
+ synthetic_region=mapping.synthetic_region,
653
+ _platform_evidence=parsed._platform_evidence,
654
+ platform_diagnostic_sha256=parsed.platform_diagnostic_sha256,
655
+ platform_diagnostic_truncated=parsed.platform_diagnostic_truncated,
656
+ platform_diagnostic_redacted=parsed.platform_diagnostic_redacted,
657
+ execution_artifact_sha256=executed.artifact.source_sha256,
658
+ source_map_sha256=executed.source_map_sha256,
659
+ )
660
+
661
+
662
+ def normalize_source_error(
663
+ error: Exception,
664
+ source: MappedSource,
665
+ *,
666
+ stage: DiagnosticStage,
667
+ visible_source_context: VisibleSourceContext | None = None,
668
+ ) -> NormalizedDiagnostic:
669
+ """Normalize a deterministic source error without a platform round trip."""
670
+ if not isinstance(source, MappedSource):
671
+ raise ValueError("source must be a MappedSource")
672
+ if type(stage) is not DiagnosticStage:
673
+ raise ValueError("stage must be a DiagnosticStage")
674
+ if visible_source_context is not None and not isinstance(
675
+ visible_source_context,
676
+ VisibleSourceContext,
677
+ ):
678
+ raise ValueError("visible_source_context must be a VisibleSourceContext")
679
+ span = getattr(error, "span", None)
680
+ code = getattr(error, "code", None)
681
+ if not isinstance(span, SourceSpan) or type(code) is not str or not code:
682
+ raise ValueError("source error must provide a SourceSpan and stable code")
683
+ offset = span.start if span.end <= len(source.text) else None
684
+ mapping = _map_executed_offset(source, offset, visible_source_context)
685
+ lowered = None
686
+ if offset is not None:
687
+ line, column = LineIndex(source.text).offset_to_line_column(offset)
688
+ lowered = LoweredSourceLocation(line, column, offset, span)
689
+ message_sha = sha256(str(error).encode("utf-8")).hexdigest()
690
+ return NormalizedDiagnostic(
691
+ diagnostic_id=_diagnostic_id(stage, code, message_sha, source, offset),
692
+ runtime_summary=_summary(stage),
693
+ stage=stage,
694
+ mapping_confidence=mapping.confidence,
695
+ code=code,
696
+ source_unit=mapping.source_unit,
697
+ visible_location=mapping.visible,
698
+ related_visible_span=mapping.related,
699
+ lowered_location=lowered,
700
+ synthetic_region=mapping.synthetic_region,
701
+ execution_artifact_sha256=source.artifact.source_sha256,
702
+ source_map_sha256=source.source_map_sha256,
703
+ )
704
+
705
+
706
+ @dataclass(frozen=True, slots=True)
707
+ class _MappedDiagnosticOffset:
708
+ confidence: MappingConfidence
709
+ source_unit: SourceUnitRef | None = None
710
+ visible: VisibleSourceLocation | None = None
711
+ related: SourceSpan | None = None
712
+ synthetic_region: str | None = None
713
+
714
+
715
+ def _map_executed_offset(
716
+ source: MappedSource,
717
+ offset: int | None,
718
+ visible_source_context: VisibleSourceContext | None,
719
+ ) -> _MappedDiagnosticOffset:
720
+ if offset is None:
721
+ return _MappedDiagnosticOffset(MappingConfidence.UNKNOWN)
722
+ try:
723
+ mapped = source.source_map.map_offset(offset)
724
+ except ValueError:
725
+ return _MappedDiagnosticOffset(MappingConfidence.UNKNOWN)
726
+ if (
727
+ mapped.relation is MappingRelation.EXACT
728
+ and mapped.unit is not None
729
+ and mapped.origin_span is not None
730
+ ):
731
+ coordinates = (
732
+ visible_source_context.line_column(
733
+ mapped.unit,
734
+ mapped.origin_span.start,
735
+ )
736
+ if visible_source_context is not None
737
+ else None
738
+ )
739
+ if coordinates is None:
740
+ return _MappedDiagnosticOffset(
741
+ MappingConfidence.NEAREST,
742
+ mapped.unit,
743
+ related=mapped.origin_span,
744
+ )
745
+ return _MappedDiagnosticOffset(
746
+ MappingConfidence.EXACT,
747
+ mapped.unit,
748
+ VisibleSourceLocation(
749
+ mapped.unit,
750
+ coordinates[0],
751
+ coordinates[1],
752
+ mapped.origin_span,
753
+ ),
754
+ )
755
+ if (
756
+ mapped.relation is MappingRelation.DERIVED
757
+ and mapped.unit is not None
758
+ and mapped.origin_span is not None
759
+ ):
760
+ return _MappedDiagnosticOffset(
761
+ MappingConfidence.NEAREST,
762
+ mapped.unit,
763
+ related=mapped.origin_span,
764
+ )
765
+ if mapped.relation is MappingRelation.SYNTHETIC:
766
+ return _MappedDiagnosticOffset(
767
+ MappingConfidence.SYNTHETIC,
768
+ mapped.anchor_unit,
769
+ related=mapped.anchor_span,
770
+ synthetic_region=mapped.synthetic_region,
771
+ )
772
+ return _MappedDiagnosticOffset(MappingConfidence.UNKNOWN)
773
+
774
+
775
+ def _validate_worker_diagnostic_request(
776
+ parsed: ParsedPlatformDiagnostic,
777
+ manifest_sha256: str,
778
+ artifacts: tuple[WorkerDiagnosticArtifact, ...],
779
+ ) -> None:
780
+ if not isinstance(parsed, ParsedPlatformDiagnostic):
781
+ raise ValueError("parsed must be a ParsedPlatformDiagnostic")
782
+ if re.fullmatch(r"[0-9a-f]{64}", manifest_sha256) is None:
783
+ raise ValueError("worker diagnostic manifest identity is invalid")
784
+ if type(artifacts) is not tuple or any(
785
+ not isinstance(item, WorkerDiagnosticArtifact) for item in artifacts
786
+ ):
787
+ raise ValueError("worker diagnostic artifacts must be an immutable tuple")
788
+
789
+
790
+ def _unmapped_worker_diagnostic(
791
+ parsed: ParsedPlatformDiagnostic,
792
+ *,
793
+ stage: DiagnosticStage,
794
+ code: str,
795
+ identity: tuple[str, ...],
796
+ ) -> NormalizedDiagnostic:
797
+ return NormalizedDiagnostic(
798
+ diagnostic_id=_worker_diagnostic_id(
799
+ stage,
800
+ code,
801
+ parsed.platform_diagnostic_sha256,
802
+ identity,
803
+ ),
804
+ runtime_summary=_summary(stage),
805
+ stage=stage,
806
+ mapping_confidence=MappingConfidence.UNKNOWN,
807
+ code=code,
808
+ _platform_evidence=parsed._platform_evidence,
809
+ platform_diagnostic_sha256=parsed.platform_diagnostic_sha256,
810
+ platform_diagnostic_truncated=parsed.platform_diagnostic_truncated,
811
+ platform_diagnostic_redacted=parsed.platform_diagnostic_redacted,
812
+ )
813
+
814
+
815
+ def _with_dependency_binding(
816
+ diagnostic: NormalizedDiagnostic,
817
+ source: MappedSource,
818
+ ) -> NormalizedDiagnostic:
819
+ lowered = diagnostic.lowered_location
820
+ if lowered is None:
821
+ return diagnostic
822
+ try:
823
+ mapped = source.source_map.map_offset(lowered.offset)
824
+ except ValueError:
825
+ return diagnostic
826
+ segment = _source_map_segment_at(source, lowered.offset)
827
+ region = None if segment is None else segment.synthetic_region
828
+ if region not in {
829
+ "worker_dependency_field",
830
+ "worker_dependency_alias_declaration",
831
+ "worker_dependency_alias_initializer",
832
+ }:
833
+ return diagnostic
834
+ dependency_anchor = (
835
+ mapped.origin_span
836
+ if mapped.relation is MappingRelation.DERIVED
837
+ else mapped.anchor_span
838
+ )
839
+ method_anchor = (
840
+ mapped.anchor_span
841
+ if mapped.relation is MappingRelation.DERIVED
842
+ else None
843
+ )
844
+ return replace(
845
+ diagnostic,
846
+ code="dependency_binding",
847
+ synthetic_region="dependency_binding",
848
+ dependency_anchor=dependency_anchor,
849
+ method_anchor=method_anchor,
850
+ )
851
+
852
+
853
+ def _line_only_worker_position(
854
+ source: MappedSource,
855
+ line: int,
856
+ ) -> tuple[int, int] | None:
857
+ """Resolve a line-only Worker frame only through one exact code span."""
858
+ if type(line) is not int or line <= 0:
859
+ return None
860
+ index = LineIndex(source.text)
861
+ try:
862
+ line_start = index.line_column_to_offset(line, 1)
863
+ except ValueError:
864
+ return None
865
+ newline = source.text.find("\n", line_start)
866
+ line_end = len(source.text) if newline < 0 else newline
867
+ code_offsets = tuple(
868
+ offset
869
+ for offset in range(line_start, line_end)
870
+ if not source.text[offset].isspace()
871
+ )
872
+ if not code_offsets:
873
+ return None
874
+ first = code_offsets[0]
875
+ last = code_offsets[-1] + 1
876
+ spans = tuple(
877
+ segment
878
+ for segment in source.source_map.segments
879
+ if segment.generated.start < last and first < segment.generated.end
880
+ )
881
+ if (
882
+ len(spans) != 1
883
+ or spans[0].relation is not MappingRelation.EXACT
884
+ or not isinstance(spans[0].origin_ref, SourceUnitRef)
885
+ or spans[0].origin is None
886
+ ):
887
+ return None
888
+ try:
889
+ mapped = source.source_map.map_offset(first)
890
+ except ValueError:
891
+ return None
892
+ if (
893
+ mapped.relation is not MappingRelation.EXACT
894
+ or mapped.unit is None
895
+ or mapped.origin_span is None
896
+ ):
897
+ return None
898
+ return first - line_start + 1, first
899
+
900
+
901
+ def _worker_runtime_frame(
902
+ location: PlatformDiagnosticLocation,
903
+ artifact: WorkerDiagnosticArtifact,
904
+ observed_registration: str,
905
+ ) -> WorkerRuntimeFrameDiagnostic:
906
+ if location.column is None:
907
+ normalized = _line_only_worker_position(
908
+ artifact.mapped_source,
909
+ location.line,
910
+ )
911
+ column = None if normalized is None else normalized[0]
912
+ offset = None if normalized is None else normalized[1]
913
+ else:
914
+ column = location.column
915
+ offset = PlatformCoordinateCodec(artifact.mapped_source.text).to_offset(
916
+ location.line,
917
+ location.column,
918
+ )
919
+ mapping = _map_executed_offset(
920
+ artifact.mapped_source,
921
+ offset,
922
+ artifact.visible_source_context,
923
+ )
924
+ lowered = None
925
+ if offset is not None and column is not None:
926
+ width = 0 if offset == len(artifact.mapped_source.text) else 1
927
+ lowered = LoweredSourceLocation(
928
+ location.line,
929
+ column,
930
+ offset,
931
+ SourceSpan(offset, offset + width),
932
+ )
933
+ dependency_anchor = None
934
+ method_anchor = None
935
+ synthetic_region = mapping.synthetic_region
936
+ if offset is not None:
937
+ try:
938
+ mapped = artifact.mapped_source.source_map.map_offset(offset)
939
+ except ValueError:
940
+ mapped = None
941
+ segment = _source_map_segment_at(artifact.mapped_source, offset)
942
+ if mapped is not None and segment is not None and segment.synthetic_region in {
943
+ "worker_dependency_field",
944
+ "worker_dependency_alias_declaration",
945
+ "worker_dependency_alias_initializer",
946
+ }:
947
+ synthetic_region = "dependency_binding"
948
+ dependency_anchor = (
949
+ mapped.origin_span
950
+ if mapped.relation is MappingRelation.DERIVED
951
+ else mapped.anchor_span
952
+ )
953
+ method_anchor = (
954
+ mapped.anchor_span
955
+ if mapped.relation is MappingRelation.DERIVED
956
+ else None
957
+ )
958
+ return WorkerRuntimeFrameDiagnostic(
959
+ observed_registration,
960
+ artifact.logical_name,
961
+ artifact.revision,
962
+ artifact.artifact_sha256,
963
+ mapping.confidence,
964
+ mapping.source_unit,
965
+ mapping.visible,
966
+ mapping.related,
967
+ lowered,
968
+ synthetic_region,
969
+ dependency_anchor,
970
+ method_anchor,
971
+ )
972
+
973
+
974
+ def _worker_stage_locations(
975
+ parsed: ParsedPlatformDiagnostic,
976
+ artifact: WorkerDiagnosticArtifact,
977
+ ) -> tuple[PlatformDiagnosticLocation, ...]:
978
+ return tuple(
979
+ location
980
+ for location in parsed.locations
981
+ if location.worker_artifact_location is not None
982
+ and location.worker_artifact_location.registration_name.casefold()
983
+ == artifact.registration_name.casefold()
984
+ )
985
+
986
+
987
+ def _parse_worker_artifact_location(
988
+ components: tuple[str, ...],
989
+ ) -> WorkerArtifactPlatformLocation | None:
990
+ if (
991
+ len(components) == 3
992
+ and components[0].casefold() == "внешняяобработка"
993
+ and components[1].casefold().startswith("onecruntime_")
994
+ and components[2].casefold() == "модульобъекта"
995
+ ):
996
+ return WorkerArtifactPlatformLocation(components[1])
997
+ return None
998
+
999
+
1000
+ def _source_map_segment_at(
1001
+ source: MappedSource,
1002
+ offset: int,
1003
+ ) -> SourceMapSegment | None:
1004
+ for segment in source.source_map.segments:
1005
+ if segment.generated.start <= offset < segment.generated.end:
1006
+ return segment
1007
+ return None
1008
+
1009
+
1010
+ def _worker_diagnostic_id(
1011
+ stage: DiagnosticStage,
1012
+ code: str,
1013
+ message_sha256: str,
1014
+ identity: tuple[str, ...],
1015
+ ) -> str:
1016
+ return sha256(
1017
+ "|".join((stage.value, code, message_sha256, *identity)).encode("utf-8")
1018
+ ).hexdigest()
1019
+
1020
+
1021
+ def _summary(stage: DiagnosticStage) -> str:
1022
+ return f"BSL {stage.value} failed"
1023
+
1024
+
1025
+ def _visible_unit_key(unit: SourceUnitRef) -> tuple[object, str, int]:
1026
+ return unit.kind, unit.unit_id, unit.revision
1027
+
1028
+
1029
+ def _diagnostic_id(
1030
+ stage: DiagnosticStage,
1031
+ code: str | None,
1032
+ message_sha256: str,
1033
+ source: MappedSource,
1034
+ offset: int | None,
1035
+ ) -> str:
1036
+ identity = "|".join(
1037
+ (
1038
+ stage.value,
1039
+ code or "platform",
1040
+ message_sha256,
1041
+ source.artifact.source_sha256,
1042
+ source.source_map_sha256,
1043
+ "unknown" if offset is None else str(offset),
1044
+ )
1045
+ )
1046
+ return sha256(identity.encode("utf-8")).hexdigest()