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,3772 @@
1
+ from __future__ import annotations
2
+
3
+ from collections.abc import Mapping
4
+ from dataclasses import dataclass, field
5
+ from enum import Enum
6
+ from hashlib import sha256
7
+ from hmac import compare_digest, digest
8
+ import json
9
+ from re import fullmatch
10
+ from secrets import token_bytes, token_urlsafe
11
+ from threading import RLock
12
+ from typing import Callable, Literal, Protocol
13
+ from uuid import UUID, uuid4
14
+ from weakref import WeakKeyDictionary
15
+
16
+ from onec_runtime.bsl.diagnostics import (
17
+ VisibleSourceContext,
18
+ WorkerDiagnosticArtifact,
19
+ parse_platform_diagnostic,
20
+ remap_worker_stage_diagnostic,
21
+ )
22
+ from onec_runtime.bsl.lexer import KEYWORDS
23
+ from onec_runtime.bsl.module_universe import (
24
+ DependencyUse,
25
+ LoweredWorkerModule,
26
+ ModuleDependencyBinding,
27
+ WorkerSemanticAdmission,
28
+ worker_semantic_admission,
29
+ )
30
+ from onec_runtime.bsl.semantic_lowering import WorkerExport
31
+ from onec_runtime.bsl.source_maps import (
32
+ LineIndex,
33
+ MappedSource,
34
+ SourceUnitKind,
35
+ SourceUnitRef,
36
+ )
37
+ from onec_runtime.errors import (
38
+ BslExecutionError,
39
+ ProtocolError,
40
+ WorkerPromotionOutcomeUnknown,
41
+ )
42
+ from onec_runtime.performance_profile import PhaseRecorder
43
+ from onec_runtime.rdbg.models import ModuleLocation
44
+ from onec_runtime.server_worker import (
45
+ WorkerArtifact,
46
+ WorkerArtifactStage,
47
+ _AdmittedWorkerSnapshot,
48
+ _validated_admitted_snapshot,
49
+ _worker_artifact_diagnostic_source_from_snapshot,
50
+ _worker_reload_platform_message,
51
+ rebind_worker_artifact_binary,
52
+ stage_worker_module_instruction,
53
+ validate_production_worker_artifact,
54
+ validate_worker_export_catalog,
55
+ worker_artifact_stage_failure,
56
+ )
57
+ from onec_runtime.worker_stage_protocol import (
58
+ _temp_storage_session_id,
59
+ WorkerStageBatch,
60
+ WorkerStageBatchOutcome,
61
+ WorkerStageRegistrationReceipt,
62
+ WorkerStageEntry,
63
+ build_worker_stage_batches,
64
+ parse_worker_stage_batch_outcome,
65
+ stage_worker_batch_instruction,
66
+ )
67
+
68
+
69
+ _WORKER_MODULE_OBJECT_ID = UUID("2a00a4fa-8ea9-4dc4-9de1-472044c40101")
70
+ _WORKER_MODULE_PROPERTY_ID = UUID("a637f77f-3840-441d-a1c3-699c8c5cb7e0")
71
+ _WORKER_LOCATOR_PROFILE_ID = "external-processing-object-module-8.3.27"
72
+ _WORKER_LOCATOR_PLATFORM_BUILD = "8.3.27.2170"
73
+ _REGISTRATION_RECEIPT_ADMISSION = object()
74
+
75
+
76
+ _DESCRIPTOR_PROOF_KEY = token_bytes(32)
77
+ _BINARY_CAPSULE_PROOF_KEY = token_bytes(32)
78
+ _PREPARED_ROOT_RECEIPT_PREFIX = "onec-worker-prepared-root-receipt-v1"
79
+ _ROOT_SWAP_RECEIPT_PREFIX = "onec-worker-root-swap-receipt-v1"
80
+ _ROOT_DISCARD_RECEIPT_PREFIX = "onec-worker-root-discard-receipt-v1"
81
+ _BSL_KEYWORDS = frozenset(KEYWORDS)
82
+ _FIXED_UNQUALIFIED_WORKER_IDENTIFIERS = frozenset(
83
+ value.casefold()
84
+ for value in (
85
+ "Результат",
86
+ "Контекст",
87
+ "ВнешниеОбработки",
88
+ "ПоместитьВоВременноеХранилище",
89
+ "Base64Значение",
90
+ "ОписаниеОшибки",
91
+ "ИнформацияОбОшибке",
92
+ "ПодробноеПредставлениеОшибки",
93
+ "СтрДлина",
94
+ "Символы",
95
+ "ТипЗнч",
96
+ "Тип",
97
+ "Соответствие",
98
+ "ФиксированноеСоответствие",
99
+ "Массив",
100
+ "ФиксированныйМассив",
101
+ "Структура",
102
+ "ФиксированнаяСтруктура",
103
+ )
104
+ )
105
+
106
+
107
+ class _NotebookArtifactBuilder(Protocol):
108
+ def __call__(
109
+ self,
110
+ source: MappedSource,
111
+ exports: tuple[WorkerExport, ...],
112
+ *,
113
+ visible_source_context: VisibleSourceContext,
114
+ semantic_admission: WorkerSemanticAdmission | None = None,
115
+ semantic_packer_identity: str | None = None,
116
+ ) -> WorkerArtifact: ...
117
+
118
+
119
+ @dataclass(frozen=True, slots=True)
120
+ class WorkerModuleBinaryKey:
121
+ logical_name: str
122
+ lowered_source_sha256: str
123
+ dependency_bindings_sha256: str
124
+ transform_version: str
125
+ packer_version: str
126
+ target_profile: str
127
+
128
+ def __post_init__(self) -> None:
129
+ if (
130
+ not _safe_identity(self.logical_name)
131
+ or not _sha256_value(self.lowered_source_sha256)
132
+ or not _sha256_value(self.dependency_bindings_sha256)
133
+ or not _safe_identity(self.transform_version)
134
+ or not _safe_identity(self.packer_version)
135
+ or not _safe_identity(self.target_profile)
136
+ ):
137
+ raise ValueError("worker module binary key is invalid")
138
+ object.__setattr__(self, "logical_name", self.logical_name.casefold())
139
+
140
+ @classmethod
141
+ def create(
142
+ cls,
143
+ lowered: LoweredWorkerModule,
144
+ *,
145
+ packer_version: str,
146
+ target_profile: str,
147
+ ) -> WorkerModuleBinaryKey:
148
+ if not isinstance(lowered, LoweredWorkerModule):
149
+ raise TypeError("lowered worker module is required")
150
+ return cls(
151
+ lowered.analysis.unit.logical_name,
152
+ lowered.mapped_source.artifact.source_sha256,
153
+ lowered.dependency_bindings_sha256,
154
+ lowered.transform_version,
155
+ packer_version,
156
+ target_profile,
157
+ )
158
+
159
+
160
+ @dataclass(frozen=True, slots=True, repr=False, weakref_slot=True, eq=False)
161
+ class WorkerModuleArtifact:
162
+ logical_name: str
163
+ revision: int
164
+ source_sha256: str
165
+ binary_key: WorkerModuleBinaryKey
166
+ artifact_sha256: str
167
+ dependency_bindings: tuple[ModuleDependencyBinding, ...]
168
+ exports: tuple[WorkerExport, ...]
169
+ source_map_sha256: str
170
+ worker_artifact: WorkerArtifact
171
+
172
+ def __repr__(self) -> str:
173
+ return (
174
+ "WorkerModuleArtifact("
175
+ f"logical_name={self.logical_name!r}, revision={self.revision}, "
176
+ f"artifact_sha256={self.artifact_sha256!r}, payload=<redacted>)"
177
+ )
178
+
179
+
180
+ @dataclass(frozen=True, slots=True, repr=False)
181
+ class _WorkerModuleBinaryCapsule:
182
+ key: WorkerModuleBinaryKey
183
+ worker_artifact: WorkerArtifact
184
+ proof: bytes
185
+
186
+
187
+ class WorkerModuleArtifactCache:
188
+ """Content-addressed storage for admitted, generation-independent binaries."""
189
+
190
+ __slots__ = ("_capsules",)
191
+
192
+ def __init__(self) -> None:
193
+ self._capsules: dict[WorkerModuleBinaryKey, _WorkerModuleBinaryCapsule] = {}
194
+
195
+ def __len__(self) -> int:
196
+ return len(self._capsules)
197
+
198
+ def _admit_capsule(self, capsule: _WorkerModuleBinaryCapsule) -> None:
199
+ try:
200
+ if not isinstance(capsule, _WorkerModuleBinaryCapsule):
201
+ raise ProtocolError("Worker binary cache admission is invalid")
202
+ key = capsule.key
203
+ worker_artifact = capsule.worker_artifact
204
+ validate_production_worker_artifact(worker_artifact)
205
+ if (
206
+ not compare_digest(
207
+ capsule.proof,
208
+ _binary_capsule_proof(key, worker_artifact),
209
+ )
210
+ or worker_artifact.logical_name.casefold()
211
+ != key.logical_name.casefold()
212
+ or worker_artifact.source_sha256 != key.lowered_source_sha256
213
+ or not _exports_match_receiver(
214
+ key.logical_name,
215
+ worker_artifact.exports,
216
+ )
217
+ ):
218
+ raise ProtocolError("Worker binary cache admission is invalid")
219
+ existing = self._capsules.get(key)
220
+ if existing is not None:
221
+ validate_production_worker_artifact(existing.worker_artifact)
222
+ if (
223
+ not compare_digest(
224
+ existing.proof,
225
+ _binary_capsule_proof(
226
+ existing.key,
227
+ existing.worker_artifact,
228
+ ),
229
+ )
230
+ or existing.key != key
231
+ or existing.worker_artifact.artifact_sha256
232
+ != worker_artifact.artifact_sha256
233
+ ):
234
+ raise ProtocolError("Worker binary cache admission is invalid")
235
+ return
236
+ self._capsules[key] = capsule
237
+ except ProtocolError:
238
+ raise
239
+ except Exception:
240
+ raise ProtocolError("Worker binary cache admission is invalid") from None
241
+
242
+ def _lookup(self, key: WorkerModuleBinaryKey) -> WorkerArtifact | None:
243
+ capsule = self._capsules.get(key)
244
+ if capsule is None:
245
+ return None
246
+ try:
247
+ if capsule.key != key:
248
+ raise ProtocolError("Worker binary cache admission is invalid")
249
+ validate_production_worker_artifact(capsule.worker_artifact)
250
+ if (
251
+ not compare_digest(
252
+ capsule.proof,
253
+ _binary_capsule_proof(key, capsule.worker_artifact),
254
+ )
255
+ or capsule.worker_artifact.logical_name.casefold()
256
+ != key.logical_name.casefold()
257
+ or capsule.worker_artifact.source_sha256
258
+ != key.lowered_source_sha256
259
+ or not _exports_match_receiver(
260
+ key.logical_name,
261
+ capsule.worker_artifact.exports,
262
+ )
263
+ ):
264
+ raise ProtocolError("Worker binary cache admission is invalid")
265
+ return capsule.worker_artifact
266
+ except ProtocolError:
267
+ raise
268
+ except Exception:
269
+ raise ProtocolError("Worker binary cache admission is invalid") from None
270
+
271
+ def _prune(self, live_keys: frozenset[WorkerModuleBinaryKey]) -> None:
272
+ """Drop capsules not reachable from a confirmed generation inventory."""
273
+ if (
274
+ type(live_keys) is not frozenset
275
+ or any(not isinstance(key, WorkerModuleBinaryKey) for key in live_keys)
276
+ ):
277
+ raise TypeError("worker binary cache live keys are invalid")
278
+ self._capsules = {
279
+ key: capsule
280
+ for key, capsule in self._capsules.items()
281
+ if key in live_keys
282
+ }
283
+
284
+
285
+ class WorkerModuleArtifactBuilder:
286
+ __slots__ = (
287
+ "_artifact_builder",
288
+ "_cache",
289
+ "_packer_version",
290
+ "_target_profile",
291
+ )
292
+
293
+ def __init__(
294
+ self,
295
+ artifact_builder: _NotebookArtifactBuilder,
296
+ *,
297
+ cache: WorkerModuleArtifactCache | None = None,
298
+ packer_version: str,
299
+ target_profile: str,
300
+ ) -> None:
301
+ if not callable(artifact_builder):
302
+ raise TypeError("notebook worker artifact builder must be callable")
303
+ if cache is not None and not isinstance(cache, WorkerModuleArtifactCache):
304
+ raise TypeError("worker module artifact cache is invalid")
305
+ if not _safe_identity(packer_version) or not _safe_identity(target_profile):
306
+ raise ValueError("worker module artifact builder identity is invalid")
307
+ self._artifact_builder = artifact_builder
308
+ self._cache = cache if cache is not None else WorkerModuleArtifactCache()
309
+ self._packer_version = packer_version
310
+ self._target_profile = target_profile
311
+
312
+ def build(
313
+ self,
314
+ lowered: LoweredWorkerModule,
315
+ *,
316
+ visible_source_context: VisibleSourceContext,
317
+ profiler: PhaseRecorder | None = None,
318
+ ) -> WorkerModuleArtifact:
319
+ try:
320
+ _validate_lowered_worker_module(lowered)
321
+ if not isinstance(visible_source_context, VisibleSourceContext):
322
+ raise ProtocolError("Worker module artifact admission is invalid")
323
+ key = WorkerModuleBinaryKey.create(
324
+ lowered,
325
+ packer_version=self._packer_version,
326
+ target_profile=self._target_profile,
327
+ )
328
+ semantic_admission = worker_semantic_admission(
329
+ lowered,
330
+ packer_identity=self._packer_version,
331
+ )
332
+ logical_name = lowered.analysis.unit.logical_name
333
+ exports = _qualified_exports(lowered)
334
+ cached = self._cache._lookup(key)
335
+ if cached is None:
336
+ package = lambda: self._artifact_builder(
337
+ lowered.mapped_source,
338
+ exports,
339
+ visible_source_context=visible_source_context,
340
+ semantic_admission=semantic_admission,
341
+ semantic_packer_identity=self._packer_version,
342
+ )
343
+ packed = (
344
+ package()
345
+ if profiler is None
346
+ else profiler.measure(
347
+ "epf_packaging",
348
+ package,
349
+ item_count=lambda _result: 1,
350
+ )
351
+ )
352
+ binary = rebind_worker_artifact_binary(
353
+ packed,
354
+ logical_name=logical_name,
355
+ mapped=lowered.mapped_source,
356
+ visible_source_context=visible_source_context,
357
+ exports=exports,
358
+ )
359
+ self._cache._admit_capsule(_mint_binary_capsule(key, binary))
360
+ worker_artifact = binary
361
+ else:
362
+ worker_artifact = rebind_worker_artifact_binary(
363
+ cached,
364
+ logical_name=logical_name,
365
+ mapped=lowered.mapped_source,
366
+ visible_source_context=visible_source_context,
367
+ exports=exports,
368
+ )
369
+ artifact = WorkerModuleArtifact(
370
+ logical_name=logical_name,
371
+ revision=lowered.analysis.unit.revision,
372
+ source_sha256=key.lowered_source_sha256,
373
+ binary_key=key,
374
+ artifact_sha256=worker_artifact.artifact_sha256,
375
+ dependency_bindings=lowered.analysis.dependencies,
376
+ exports=exports,
377
+ source_map_sha256=worker_artifact.source_map_sha256 or "",
378
+ worker_artifact=worker_artifact,
379
+ )
380
+ unit = lowered.analysis.unit
381
+ _bind_descriptor_admission(
382
+ artifact,
383
+ descriptor_cache_identity=(
384
+ unit.logical_name.casefold(),
385
+ unit.kind,
386
+ unit.revision,
387
+ unit.mapped_source.artifact.source_sha256,
388
+ unit.mapped_source.source_map_sha256,
389
+ ),
390
+ )
391
+ return artifact
392
+ except Exception:
393
+ raise ProtocolError("Worker module artifact admission is invalid") from None
394
+
395
+ def _prune_cache(self, live_keys: frozenset[WorkerModuleBinaryKey]) -> None:
396
+ self._cache._prune(live_keys)
397
+
398
+
399
+ class _WorkerModuleArtifactAdmission:
400
+ __slots__ = ("proof", "compatibility", "descriptor_cache_identity")
401
+
402
+ def __init__(
403
+ self,
404
+ proof: bytes,
405
+ *,
406
+ compatibility: bool = False,
407
+ descriptor_cache_identity: tuple[str, str, int, str, str] | None = None,
408
+ ) -> None:
409
+ object.__setattr__(self, "proof", proof)
410
+ object.__setattr__(self, "compatibility", compatibility)
411
+ object.__setattr__(
412
+ self,
413
+ "descriptor_cache_identity",
414
+ descriptor_cache_identity,
415
+ )
416
+
417
+ def __setattr__(self, name: str, value: object) -> None:
418
+ raise AttributeError("worker module artifact admissions are immutable")
419
+
420
+ def __repr__(self) -> str:
421
+ return "<redacted worker module artifact admission>"
422
+
423
+
424
+ _DESCRIPTOR_ADMISSIONS: WeakKeyDictionary[
425
+ WorkerModuleArtifact,
426
+ _WorkerModuleArtifactAdmission,
427
+ ] = WeakKeyDictionary()
428
+
429
+
430
+ def _validate_descriptor_cache_identity(
431
+ artifact: WorkerModuleArtifact,
432
+ identity: tuple[str, str, int, str, str] | None,
433
+ *,
434
+ compatibility: bool,
435
+ ) -> None:
436
+ if compatibility:
437
+ if identity is not None:
438
+ raise ProtocolError("Worker module descriptor cache identity is invalid")
439
+ return
440
+ if (
441
+ not isinstance(identity, tuple)
442
+ or len(identity) != 5
443
+ or identity[0] != artifact.logical_name.casefold()
444
+ or identity[1] not in ("module", "test-module")
445
+ or identity[2] != artifact.revision
446
+ or not _sha256_value(identity[3])
447
+ or not _sha256_value(identity[4])
448
+ ):
449
+ raise ProtocolError("Worker module descriptor cache identity is invalid")
450
+
451
+
452
+ def _worker_module_descriptor_cache_identity(
453
+ artifact: WorkerModuleArtifact,
454
+ ) -> tuple[str, str, int, str, str] | None:
455
+ admission = _DESCRIPTOR_ADMISSIONS.get(artifact)
456
+ if admission is None:
457
+ raise ProtocolError("Worker module descriptor cache identity is invalid")
458
+ identity = admission.descriptor_cache_identity
459
+ _validate_descriptor_cache_identity(
460
+ artifact,
461
+ identity,
462
+ compatibility=admission.compatibility,
463
+ )
464
+ return identity
465
+
466
+
467
+ def _bind_descriptor_admission(
468
+ artifact: WorkerModuleArtifact,
469
+ *,
470
+ compatibility: bool = False,
471
+ descriptor_cache_identity: tuple[str, str, int, str, str] | None = None,
472
+ ) -> None:
473
+ _validate_descriptor_cache_identity(
474
+ artifact,
475
+ descriptor_cache_identity,
476
+ compatibility=compatibility,
477
+ )
478
+ _DESCRIPTOR_ADMISSIONS[artifact] = _WorkerModuleArtifactAdmission(
479
+ _descriptor_proof(artifact, compatibility=compatibility),
480
+ compatibility=compatibility,
481
+ descriptor_cache_identity=descriptor_cache_identity,
482
+ )
483
+
484
+
485
+ def worker_module_artifact_from_notebook(
486
+ artifact: WorkerArtifact,
487
+ *,
488
+ revision: int,
489
+ target_profile: str,
490
+ ) -> WorkerModuleArtifact:
491
+ """Admit a current notebook Worker binary into the module universe.
492
+
493
+ The returned descriptor retains the exact admitted binary and mapped source;
494
+ only its host-side export descriptors gain the canonical ``Worker`` receiver.
495
+ """
496
+ try:
497
+ catalog = validate_production_worker_artifact(artifact)
498
+ snapshot = _validated_admitted_snapshot(artifact)
499
+ mapped = snapshot.mapped_source
500
+ if (
501
+ artifact.logical_name.casefold() != "worker"
502
+ or type(revision) is not int
503
+ or revision <= 0
504
+ or not _safe_identity(target_profile)
505
+ or any(item.receiver_module is not None for item in catalog)
506
+ ):
507
+ raise ProtocolError("Notebook Worker admission is invalid")
508
+ exports = tuple(
509
+ WorkerExport(
510
+ f"Worker.{item.method}",
511
+ item.method,
512
+ receiver_module="Worker",
513
+ )
514
+ for item in catalog
515
+ )
516
+ validate_worker_export_catalog(exports, require_nonempty=True)
517
+ dependency_sha256 = _dependency_bindings_sha256(
518
+ (),
519
+ validate_bindings=True,
520
+ )
521
+ key = WorkerModuleBinaryKey(
522
+ "Worker",
523
+ artifact.source_sha256,
524
+ dependency_sha256,
525
+ "notebook-worker-adapter-v1",
526
+ "notebook-admitted-binary-v1",
527
+ target_profile,
528
+ )
529
+ descriptor = WorkerModuleArtifact(
530
+ logical_name="Worker",
531
+ revision=revision,
532
+ source_sha256=artifact.source_sha256,
533
+ binary_key=key,
534
+ artifact_sha256=artifact.artifact_sha256,
535
+ dependency_bindings=(),
536
+ exports=exports,
537
+ source_map_sha256=snapshot.source_map_sha256,
538
+ worker_artifact=artifact,
539
+ )
540
+ _bind_descriptor_admission(descriptor, compatibility=True)
541
+ return descriptor
542
+ except ProtocolError as error:
543
+ if str(error) == "Notebook Worker admission is invalid":
544
+ raise
545
+ raise ProtocolError("Notebook Worker admission is invalid") from None
546
+ except Exception:
547
+ raise ProtocolError("Notebook Worker admission is invalid") from None
548
+
549
+
550
+ def _mint_binary_capsule(
551
+ key: WorkerModuleBinaryKey,
552
+ worker_artifact: WorkerArtifact,
553
+ ) -> _WorkerModuleBinaryCapsule:
554
+ validate_production_worker_artifact(worker_artifact)
555
+ return _WorkerModuleBinaryCapsule(
556
+ key,
557
+ worker_artifact,
558
+ _binary_capsule_proof(key, worker_artifact),
559
+ )
560
+
561
+
562
+ def _binary_capsule_proof(
563
+ key: WorkerModuleBinaryKey,
564
+ worker_artifact: WorkerArtifact,
565
+ ) -> bytes:
566
+ artifact_proof = _validated_admitted_snapshot(worker_artifact).proof
567
+ manifest = {
568
+ "schema": "onec-worker-module-binary-capsule-v1",
569
+ "binary_key": {
570
+ "logical_name": key.logical_name,
571
+ "lowered_source_sha256": key.lowered_source_sha256,
572
+ "dependency_bindings_sha256": key.dependency_bindings_sha256,
573
+ "transform_version": key.transform_version,
574
+ "packer_version": key.packer_version,
575
+ "target_profile": key.target_profile,
576
+ },
577
+ "worker_artifact": {
578
+ "logical_name": worker_artifact.logical_name.casefold(),
579
+ "source_sha256": worker_artifact.source_sha256,
580
+ "artifact_sha256": worker_artifact.artifact_sha256,
581
+ "source_map_sha256": worker_artifact.source_map_sha256,
582
+ "admission_proof_hex": artifact_proof.hex(),
583
+ },
584
+ }
585
+ payload = json.dumps(
586
+ manifest,
587
+ ensure_ascii=False,
588
+ sort_keys=True,
589
+ separators=(",", ":"),
590
+ ).encode("utf-8")
591
+ return digest(
592
+ _BINARY_CAPSULE_PROOF_KEY,
593
+ payload,
594
+ "sha256",
595
+ )
596
+
597
+
598
+ @dataclass(frozen=True, slots=True, repr=False)
599
+ class _ValidatedWorkerModuleArtifact:
600
+ """Private result of one complete revision-descriptor admission."""
601
+
602
+ descriptor: WorkerModuleArtifact
603
+ snapshot: _AdmittedWorkerSnapshot
604
+ descriptor_cache_identity: tuple[str, str, int, str, str] | None
605
+
606
+ def __repr__(self) -> str:
607
+ return "<validated worker module artifact>"
608
+
609
+
610
+ def _validated_worker_module_artifact(
611
+ artifact: WorkerModuleArtifact,
612
+ ) -> _ValidatedWorkerModuleArtifact:
613
+ """Fail-closed production admission for a revision descriptor and capsule."""
614
+ try:
615
+ if not isinstance(artifact, WorkerModuleArtifact):
616
+ raise ProtocolError("Worker module artifact admission is invalid")
617
+ admission = _DESCRIPTOR_ADMISSIONS.get(artifact)
618
+ if admission is None or not compare_digest(
619
+ admission.proof,
620
+ _descriptor_proof(
621
+ artifact,
622
+ compatibility=admission.compatibility,
623
+ ),
624
+ ):
625
+ raise ProtocolError("Worker module artifact admission is invalid")
626
+ descriptor_cache_identity = _worker_module_descriptor_cache_identity(artifact)
627
+ if admission.compatibility:
628
+ # The public compatibility factory performed the live-file
629
+ # production admission before it minted this descriptor. From
630
+ # here on the descriptor owns that exact immutable admission
631
+ # snapshot: staging must not reopen a path that may have changed
632
+ # after admission (the stage instruction below consumes the
633
+ # admitted bytes, not the path).
634
+ snapshot = _validated_admitted_snapshot(artifact.worker_artifact)
635
+ mapped = snapshot.mapped_source
636
+ catalog = validate_worker_export_catalog(
637
+ artifact.worker_artifact.exports,
638
+ require_nonempty=True,
639
+ )
640
+ else:
641
+ snapshot = _validated_admitted_snapshot(artifact.worker_artifact)
642
+ mapped = snapshot.mapped_source
643
+ catalog = snapshot.catalog
644
+ validate_worker_export_catalog(artifact.exports, require_nonempty=True)
645
+ dependency_sha256 = _dependency_bindings_sha256(
646
+ artifact.dependency_bindings,
647
+ validate_bindings=True,
648
+ )
649
+ compact_spans = getattr(mapped.source_map, "compact_visible_spans", None)
650
+ references = (
651
+ tuple(reference for _role, reference, _span in compact_spans())
652
+ if compact_spans is not None
653
+ else tuple(
654
+ reference
655
+ for segment in mapped.source_map.segments
656
+ for reference in (segment.origin_ref, segment.anchor_ref)
657
+ )
658
+ )
659
+ visible_revisions = {
660
+ reference.revision
661
+ for reference in references
662
+ if (
663
+ getattr(reference, "kind", None)
664
+ in (SourceUnitKind.MODULE, SourceUnitKind.TEST_MODULE)
665
+ and getattr(reference, "unit_id", "").casefold()
666
+ == artifact.logical_name.casefold()
667
+ )
668
+ }
669
+ compatibility_catalog = (
670
+ admission.compatibility
671
+ and artifact.logical_name == "Worker"
672
+ and artifact.binary_key.transform_version
673
+ == "notebook-worker-adapter-v1"
674
+ and artifact.binary_key.packer_version
675
+ == "notebook-admitted-binary-v1"
676
+ and artifact.dependency_bindings == ()
677
+ and len(artifact.exports) == len(catalog)
678
+ and all(
679
+ descriptor.receiver_module == "Worker"
680
+ and source.receiver_module is None
681
+ and descriptor.public_path.casefold()
682
+ == f"worker.{source.method}".casefold()
683
+ and descriptor.method.casefold() == source.method.casefold()
684
+ for descriptor, source in zip(
685
+ artifact.exports,
686
+ catalog,
687
+ strict=True,
688
+ )
689
+ )
690
+ )
691
+ if (
692
+ artifact.logical_name.casefold()
693
+ != artifact.binary_key.logical_name.casefold()
694
+ or artifact.logical_name.casefold()
695
+ != artifact.worker_artifact.logical_name.casefold()
696
+ or type(artifact.revision) is not int
697
+ or artifact.revision < 0
698
+ or (
699
+ not admission.compatibility
700
+ and visible_revisions != {artifact.revision}
701
+ )
702
+ or artifact.source_sha256
703
+ != artifact.binary_key.lowered_source_sha256
704
+ or artifact.source_sha256 != artifact.worker_artifact.source_sha256
705
+ or artifact.source_sha256 != mapped.artifact.source_sha256
706
+ or artifact.artifact_sha256 != artifact.worker_artifact.artifact_sha256
707
+ or artifact.source_map_sha256 != artifact.worker_artifact.source_map_sha256
708
+ or artifact.source_map_sha256 != snapshot.source_map_sha256
709
+ or dependency_sha256
710
+ != artifact.binary_key.dependency_bindings_sha256
711
+ or (
712
+ not admission.compatibility
713
+ and artifact.exports != artifact.worker_artifact.exports
714
+ )
715
+ or (
716
+ not admission.compatibility
717
+ and artifact.exports != catalog
718
+ )
719
+ or (admission.compatibility and not compatibility_catalog)
720
+ or not _exports_match_receiver(artifact.logical_name, artifact.exports)
721
+ ):
722
+ raise ProtocolError("Worker module artifact admission is invalid")
723
+ return _ValidatedWorkerModuleArtifact(
724
+ artifact,
725
+ snapshot,
726
+ descriptor_cache_identity,
727
+ )
728
+ except ProtocolError as error:
729
+ if str(error) == "Worker module artifact admission is invalid":
730
+ raise
731
+ raise ProtocolError("Worker module artifact admission is invalid") from None
732
+ except Exception:
733
+ raise ProtocolError("Worker module artifact admission is invalid") from None
734
+
735
+
736
+ def validate_worker_module_artifact(
737
+ artifact: WorkerModuleArtifact,
738
+ ) -> tuple[WorkerExport, ...]:
739
+ """Validate one descriptor and return its public export catalog."""
740
+ return _validated_worker_module_artifact(artifact).descriptor.exports
741
+
742
+
743
+ def _validate_lowered_worker_module(lowered: LoweredWorkerModule) -> None:
744
+ if not isinstance(lowered, LoweredWorkerModule):
745
+ raise ProtocolError("Worker module artifact admission is invalid")
746
+ if (
747
+ lowered.mapped_source.artifact.worker_generation is not None
748
+ or lowered.mapped_source.artifact.worker_manifest_sha256 is not None
749
+ or not _safe_identity(lowered.transform_version)
750
+ or lowered.dependency_bindings_sha256
751
+ != _dependency_bindings_sha256(
752
+ lowered.analysis.dependencies,
753
+ validate_bindings=True,
754
+ )
755
+ ):
756
+ raise ProtocolError("Worker module artifact admission is invalid")
757
+
758
+
759
+ def _qualified_exports(lowered: LoweredWorkerModule) -> tuple[WorkerExport, ...]:
760
+ logical_name = lowered.analysis.unit.logical_name
761
+ exports = tuple(
762
+ WorkerExport(
763
+ f"{logical_name}.{method}",
764
+ method,
765
+ receiver_module=logical_name,
766
+ )
767
+ for method in lowered.analysis.exported_methods
768
+ )
769
+ return validate_worker_export_catalog(exports, require_nonempty=True)
770
+
771
+
772
+ def _exports_match_receiver(
773
+ logical_name: str,
774
+ exports: tuple[WorkerExport, ...],
775
+ ) -> bool:
776
+ return all(
777
+ item.receiver_module is not None
778
+ and item.receiver_module.casefold() == logical_name.casefold()
779
+ and item.public_path.casefold()
780
+ == f"{logical_name}.{item.method}".casefold()
781
+ for item in exports
782
+ )
783
+
784
+
785
+ def _binding_manifest(binding: ModuleDependencyBinding) -> dict[str, object]:
786
+ return {
787
+ "target_module": binding.target_module,
788
+ "export_variable": binding.export_variable,
789
+ "uses": [
790
+ {
791
+ "method_name": use.method_name,
792
+ "category": use.category,
793
+ "span": [use.span.start, use.span.end],
794
+ "method_declaration": [
795
+ use.method_declaration.start,
796
+ use.method_declaration.end,
797
+ ],
798
+ }
799
+ for use in binding.uses
800
+ ],
801
+ }
802
+
803
+
804
+ def _binding_sha256(binding: ModuleDependencyBinding) -> str:
805
+ payload = json.dumps(
806
+ _binding_manifest(binding),
807
+ ensure_ascii=False,
808
+ sort_keys=True,
809
+ separators=(",", ":"),
810
+ )
811
+ return sha256(payload.encode("utf-8")).hexdigest()
812
+
813
+
814
+ def _dependency_bindings_sha256(
815
+ bindings: tuple[ModuleDependencyBinding, ...],
816
+ *,
817
+ validate_bindings: bool,
818
+ ) -> str:
819
+ if type(bindings) is not tuple:
820
+ raise ProtocolError("Worker module artifact admission is invalid")
821
+ if validate_bindings:
822
+ for binding in bindings:
823
+ if (
824
+ not isinstance(binding, ModuleDependencyBinding)
825
+ or type(binding.uses) is not tuple
826
+ or any(not isinstance(use, DependencyUse) for use in binding.uses)
827
+ or binding.sha256 != _binding_sha256(binding)
828
+ ):
829
+ raise ProtocolError("Worker module artifact admission is invalid")
830
+ payload = json.dumps(
831
+ [binding.sha256 for binding in bindings],
832
+ ensure_ascii=False,
833
+ separators=(",", ":"),
834
+ )
835
+ return sha256(payload.encode("utf-8")).hexdigest()
836
+
837
+
838
+ def _descriptor_proof(
839
+ artifact: WorkerModuleArtifact,
840
+ *,
841
+ compatibility: bool = False,
842
+ ) -> bytes:
843
+ binding_payload = json.dumps(
844
+ [_binding_manifest(binding) | {"sha256": binding.sha256} for binding in artifact.dependency_bindings],
845
+ ensure_ascii=False,
846
+ sort_keys=True,
847
+ separators=(",", ":"),
848
+ )
849
+ export_payload = json.dumps(
850
+ [
851
+ [item.public_path, item.method, item.receiver_module]
852
+ for item in artifact.exports
853
+ ],
854
+ ensure_ascii=False,
855
+ separators=(",", ":"),
856
+ )
857
+ key_payload = json.dumps(
858
+ [
859
+ artifact.binary_key.logical_name,
860
+ artifact.binary_key.lowered_source_sha256,
861
+ artifact.binary_key.dependency_bindings_sha256,
862
+ artifact.binary_key.transform_version,
863
+ artifact.binary_key.packer_version,
864
+ artifact.binary_key.target_profile,
865
+ ],
866
+ ensure_ascii=False,
867
+ separators=(",", ":"),
868
+ )
869
+ fields = (
870
+ "compatibility" if compatibility else "module",
871
+ artifact.logical_name,
872
+ str(artifact.revision),
873
+ artifact.source_sha256,
874
+ key_payload,
875
+ artifact.artifact_sha256,
876
+ binding_payload,
877
+ export_payload,
878
+ artifact.source_map_sha256,
879
+ artifact.worker_artifact.logical_name,
880
+ artifact.worker_artifact.source_sha256,
881
+ artifact.worker_artifact.artifact_sha256,
882
+ artifact.worker_artifact.source_map_sha256 or "",
883
+ )
884
+ return digest(
885
+ _DESCRIPTOR_PROOF_KEY,
886
+ "\x1f".join(fields).encode("utf-8"),
887
+ "sha256",
888
+ )
889
+
890
+
891
+ def _sha256_value(value: object) -> bool:
892
+ return isinstance(value, str) and fullmatch(r"[0-9a-f]{64}", value) is not None
893
+
894
+
895
+ def _safe_identity(value: object) -> bool:
896
+ return (
897
+ isinstance(value, str)
898
+ and bool(value)
899
+ and "\x00" not in value
900
+ and "\r" not in value
901
+ and "\n" not in value
902
+ and "/" not in value
903
+ and "\\" not in value
904
+ and "|" not in value
905
+ )
906
+
907
+
908
+ class WorkerUniverseState(Enum):
909
+ EMPTY = "empty"
910
+ READY = "ready"
911
+ PREPARING = "preparing"
912
+ BROKEN = "broken"
913
+ CLOSED = "closed"
914
+
915
+
916
+ @dataclass(frozen=True, slots=True)
917
+ class ManifestModule:
918
+ logical_name: str
919
+ revision: int
920
+ artifact_sha256: str
921
+ registration_name: str
922
+
923
+ def __post_init__(self) -> None:
924
+ if (
925
+ not _bsl_identifier(self.logical_name)
926
+ or type(self.revision) is not int
927
+ or self.revision < 0
928
+ or not _sha256_value(self.artifact_sha256)
929
+ or self.registration_name
930
+ != registration_name(self.logical_name, self.artifact_sha256)
931
+ ):
932
+ raise ValueError("worker manifest module is invalid")
933
+
934
+
935
+ @dataclass(frozen=True, slots=True)
936
+ class DependencyTarget:
937
+ source_module: str
938
+ export_variable: str
939
+ target_kind: Literal["original", "overloaded"]
940
+ target_module: str
941
+
942
+ def __post_init__(self) -> None:
943
+ if not _valid_dependency_target(self):
944
+ raise ValueError("worker dependency target is invalid")
945
+
946
+
947
+ @dataclass(frozen=True, slots=True)
948
+ class WorkerUniverseManifest:
949
+ generation: int
950
+ modules: tuple[ManifestModule, ...]
951
+ wiring: tuple[DependencyTarget, ...]
952
+ exports: tuple[WorkerExport, ...]
953
+ sha256: str
954
+
955
+ def __post_init__(self) -> None:
956
+ if (
957
+ type(self.generation) is not int
958
+ or self.generation <= 0
959
+ or type(self.modules) is not tuple
960
+ or not self.modules
961
+ or any(not isinstance(item, ManifestModule) for item in self.modules)
962
+ or self.modules != tuple(sorted(self.modules, key=_manifest_module_sort_key))
963
+ or type(self.wiring) is not tuple
964
+ or any(not isinstance(item, DependencyTarget) for item in self.wiring)
965
+ or any(not _valid_dependency_target(item) for item in self.wiring)
966
+ or self.wiring != tuple(sorted(self.wiring, key=_dependency_target_sort_key))
967
+ or type(self.exports) is not tuple
968
+ or self.exports != tuple(sorted(self.exports, key=_worker_export_sort_key))
969
+ or any(
970
+ not _bsl_identifier(item.method)
971
+ or not _bsl_identifier(item.receiver_module)
972
+ for item in self.exports
973
+ )
974
+ or not _sha256_value(self.sha256)
975
+ ):
976
+ raise ValueError("worker universe manifest is invalid")
977
+ module_names = tuple(item.logical_name.casefold() for item in self.modules)
978
+ registration_names = tuple(
979
+ item.registration_name.casefold() for item in self.modules
980
+ )
981
+ if (
982
+ len(module_names) != len(set(module_names))
983
+ or len(registration_names) != len(set(registration_names))
984
+ ):
985
+ raise ValueError("worker universe manifest is invalid")
986
+ binding_keys = tuple(
987
+ (item.source_module.casefold(), item.export_variable.casefold())
988
+ for item in self.wiring
989
+ )
990
+ if len(binding_keys) != len(set(binding_keys)):
991
+ raise ValueError("worker universe manifest is invalid")
992
+ loaded = set(module_names)
993
+ if any(
994
+ item.source_module.casefold() not in loaded
995
+ or (
996
+ item.target_kind == "overloaded"
997
+ and item.target_module.casefold() not in loaded
998
+ )
999
+ or (
1000
+ item.target_kind == "original"
1001
+ and item.target_module.casefold() in loaded
1002
+ )
1003
+ for item in self.wiring
1004
+ ):
1005
+ raise ValueError("worker universe manifest is invalid")
1006
+ try:
1007
+ validate_worker_export_catalog(self.exports, require_nonempty=True)
1008
+ except (ProtocolError, ValueError) as error:
1009
+ raise ValueError("worker universe manifest is invalid") from error
1010
+ if any(
1011
+ item.receiver_module is None
1012
+ or item.receiver_module.casefold() not in loaded
1013
+ for item in self.exports
1014
+ ):
1015
+ raise ValueError("worker universe manifest is invalid")
1016
+ if self.sha256 != _worker_manifest_sha256(
1017
+ self.generation,
1018
+ self.modules,
1019
+ self.wiring,
1020
+ self.exports,
1021
+ ):
1022
+ raise ValueError("worker universe manifest is invalid")
1023
+
1024
+
1025
+ @dataclass(frozen=True, slots=True, eq=False)
1026
+ class WorkerGenerationHandle:
1027
+ runtime_generation: int
1028
+ context_generation: int
1029
+ generation: int
1030
+ manifest_sha256: str
1031
+
1032
+ def __post_init__(self) -> None:
1033
+ if (
1034
+ type(self.runtime_generation) is not int
1035
+ or self.runtime_generation <= 0
1036
+ or type(self.context_generation) is not int
1037
+ or self.context_generation <= 0
1038
+ or type(self.generation) is not int
1039
+ or self.generation <= 0
1040
+ or not _sha256_value(self.manifest_sha256)
1041
+ ):
1042
+ raise ValueError("worker generation handle is invalid")
1043
+
1044
+
1045
+ @dataclass(frozen=True, slots=True, repr=False)
1046
+ class WorkerGenerationConfirmation:
1047
+ handle: WorkerGenerationHandle
1048
+ released_registrations: tuple[str, ...]
1049
+
1050
+ def __post_init__(self) -> None:
1051
+ if (
1052
+ not isinstance(self.handle, WorkerGenerationHandle)
1053
+ or type(self.released_registrations) is not tuple
1054
+ or any(
1055
+ not _bsl_identifier(name)
1056
+ for name in self.released_registrations
1057
+ )
1058
+ or self.released_registrations
1059
+ != tuple(sorted(self.released_registrations, key=str.casefold))
1060
+ or len(self.released_registrations)
1061
+ != len({name.casefold() for name in self.released_registrations})
1062
+ ):
1063
+ raise ValueError("worker generation confirmation is invalid")
1064
+
1065
+ def __repr__(self) -> str:
1066
+ return (
1067
+ "WorkerGenerationConfirmation("
1068
+ "handle=<redacted>, released_registrations=<redacted>, "
1069
+ f"released_count={len(self.released_registrations)})"
1070
+ )
1071
+
1072
+
1073
+ @dataclass(frozen=True, slots=True, eq=False, repr=False)
1074
+ class OperationGenerationPin:
1075
+ handle: WorkerGenerationHandle
1076
+ manifest: WorkerUniverseManifest
1077
+ root_key: str
1078
+ export_catalog: tuple[WorkerExport, ...]
1079
+ lease_id: str
1080
+
1081
+ def __post_init__(self) -> None:
1082
+ if (
1083
+ not isinstance(self.handle, WorkerGenerationHandle)
1084
+ or not isinstance(self.manifest, WorkerUniverseManifest)
1085
+ or self.handle.generation != self.manifest.generation
1086
+ or self.handle.manifest_sha256 != self.manifest.sha256
1087
+ or not _safe_identity(self.root_key)
1088
+ or type(self.export_catalog) is not tuple
1089
+ or not _effective_export_catalog_matches(
1090
+ self.manifest,
1091
+ self.export_catalog,
1092
+ )
1093
+ or not isinstance(self.lease_id, str)
1094
+ or len(self.lease_id) < 32
1095
+ ):
1096
+ raise ValueError("worker operation generation pin is invalid")
1097
+
1098
+ def __repr__(self) -> str:
1099
+ return (
1100
+ "OperationGenerationPin("
1101
+ f"handle={self.handle!r}, root_key=<redacted>, "
1102
+ f"exports={len(self.export_catalog)}, lease_id=<redacted>)"
1103
+ )
1104
+
1105
+
1106
+ @dataclass(frozen=True, slots=True, eq=False, repr=False)
1107
+ class WorkerUniverseCandidate:
1108
+ handle: WorkerGenerationHandle
1109
+ manifest: WorkerUniverseManifest
1110
+ export_catalog: tuple[WorkerExport, ...]
1111
+ artifacts: tuple[WorkerModuleArtifact, ...]
1112
+ new_artifacts: tuple[WorkerModuleArtifact, ...]
1113
+ previous: WorkerGenerationHandle | None
1114
+
1115
+ def __post_init__(self) -> None:
1116
+ if (
1117
+ not isinstance(self.handle, WorkerGenerationHandle)
1118
+ or not isinstance(self.manifest, WorkerUniverseManifest)
1119
+ or self.handle.generation != self.manifest.generation
1120
+ or self.handle.manifest_sha256 != self.manifest.sha256
1121
+ or not _effective_export_catalog_matches(
1122
+ self.manifest,
1123
+ self.export_catalog,
1124
+ )
1125
+ or type(self.artifacts) is not tuple
1126
+ or not self.artifacts
1127
+ or any(not isinstance(item, WorkerModuleArtifact) for item in self.artifacts)
1128
+ or type(self.new_artifacts) is not tuple
1129
+ or any(
1130
+ not any(item is artifact for artifact in self.artifacts)
1131
+ for item in self.new_artifacts
1132
+ )
1133
+ or (
1134
+ self.previous is not None
1135
+ and not isinstance(self.previous, WorkerGenerationHandle)
1136
+ )
1137
+ ):
1138
+ raise ValueError("worker universe candidate is invalid")
1139
+
1140
+ def __repr__(self) -> str:
1141
+ return (
1142
+ "WorkerUniverseCandidate("
1143
+ f"handle={self.handle!r}, modules={len(self.artifacts)}, "
1144
+ f"new_artifacts={len(self.new_artifacts)}, payload=<redacted>)"
1145
+ )
1146
+
1147
+
1148
+ @dataclass(frozen=True, slots=True, eq=False, repr=False)
1149
+ class _SealedWorkerStageEntry:
1150
+ """Exact target payload and diagnostics retained for one sealed module."""
1151
+
1152
+ descriptor: WorkerModuleArtifact
1153
+ logical_name: str
1154
+ revision: int
1155
+ artifact_sha256: str
1156
+ registration_name: str
1157
+ artifact_bytes: bytes
1158
+ admitted_snapshot: _AdmittedWorkerSnapshot
1159
+
1160
+ def __repr__(self) -> str:
1161
+ return (
1162
+ "_SealedWorkerStageEntry("
1163
+ f"logical_name={self.logical_name!r}, revision={self.revision}, "
1164
+ f"artifact_sha256={self.artifact_sha256!r}, payload=<redacted>)"
1165
+ )
1166
+
1167
+
1168
+ @dataclass(frozen=True, slots=True, eq=False, repr=False)
1169
+ class _SealedWorkerActivation:
1170
+ """Private capability proving one exact pending candidate was admitted."""
1171
+
1172
+ candidate: WorkerUniverseCandidate
1173
+ artifact_views: tuple[_ValidatedWorkerModuleArtifact, ...]
1174
+ stage_entries: tuple[_SealedWorkerStageEntry, ...]
1175
+ diagnostics: tuple[WorkerDiagnosticArtifact, ...]
1176
+ debug_view: WorkerGenerationDebugView | None = None
1177
+
1178
+ def __repr__(self) -> str:
1179
+ return (
1180
+ "_SealedWorkerActivation("
1181
+ f"generation={self.candidate.handle.generation}, "
1182
+ f"modules={len(self.artifact_views)}, payload=<redacted>)"
1183
+ )
1184
+
1185
+
1186
+ def _seal_worker_activation(
1187
+ candidate: WorkerUniverseCandidate,
1188
+ artifact_views: tuple[_ValidatedWorkerModuleArtifact, ...],
1189
+ ) -> _SealedWorkerActivation:
1190
+ """Bind the already-admitted manifest views to their exact candidate."""
1191
+ if (
1192
+ not isinstance(candidate, WorkerUniverseCandidate)
1193
+ or type(artifact_views) is not tuple
1194
+ or len(artifact_views) != len(candidate.manifest.modules)
1195
+ or len(artifact_views) != len(candidate.artifacts)
1196
+ ):
1197
+ raise ProtocolError("Worker sealed activation is invalid")
1198
+ entries: list[_SealedWorkerStageEntry] = []
1199
+ diagnostics: list[WorkerDiagnosticArtifact] = []
1200
+ for view, artifact, module in zip(
1201
+ artifact_views,
1202
+ candidate.artifacts,
1203
+ candidate.manifest.modules,
1204
+ strict=True,
1205
+ ):
1206
+ if (
1207
+ not isinstance(view, _ValidatedWorkerModuleArtifact)
1208
+ or view.descriptor is not artifact
1209
+ or not isinstance(view.snapshot, _AdmittedWorkerSnapshot)
1210
+ or artifact.logical_name != module.logical_name
1211
+ or artifact.revision != module.revision
1212
+ or artifact.artifact_sha256 != module.artifact_sha256
1213
+ or view.snapshot.source_sha256 != artifact.source_sha256
1214
+ or view.snapshot.artifact_sha256 != artifact.artifact_sha256
1215
+ or view.snapshot.source_map_sha256 != artifact.source_map_sha256
1216
+ or view.snapshot.mapped_source.source_map_sha256
1217
+ != artifact.source_map_sha256
1218
+ ):
1219
+ raise ProtocolError("Worker sealed activation is invalid")
1220
+ artifact_bytes = view.snapshot.artifact_bytes
1221
+ if type(artifact_bytes) is not bytes:
1222
+ raise ProtocolError("Worker sealed activation is invalid")
1223
+ entries.append(
1224
+ _SealedWorkerStageEntry(
1225
+ artifact,
1226
+ module.logical_name,
1227
+ module.revision,
1228
+ module.artifact_sha256,
1229
+ module.registration_name,
1230
+ artifact_bytes,
1231
+ view.snapshot,
1232
+ )
1233
+ )
1234
+ diagnostics.append(
1235
+ _worker_artifact_diagnostic_source_from_snapshot(
1236
+ artifact.worker_artifact,
1237
+ view.snapshot,
1238
+ logical_name=module.logical_name,
1239
+ revision=module.revision,
1240
+ registration_name=module.registration_name,
1241
+ manifest_sha256=candidate.manifest.sha256,
1242
+ artifact_sha256=module.artifact_sha256,
1243
+ source_map_sha256=artifact.source_map_sha256,
1244
+ )
1245
+ )
1246
+ return _SealedWorkerActivation(
1247
+ candidate,
1248
+ artifact_views,
1249
+ tuple(entries),
1250
+ tuple(diagnostics),
1251
+ )
1252
+
1253
+
1254
+ @dataclass(frozen=True, slots=True, repr=False)
1255
+ class WorkerPreparedRootReceipt:
1256
+ transaction_id: UUID
1257
+ generation: int
1258
+ manifest_sha256: str
1259
+ candidate_root_key: str
1260
+ previous_root_key: str
1261
+ generation_create_wire_probe_ms: int
1262
+
1263
+ def __post_init__(self) -> None:
1264
+ if (
1265
+ type(self.transaction_id) is not UUID
1266
+ or type(self.generation) is not int
1267
+ or self.generation <= 0
1268
+ or not _sha256_value(self.manifest_sha256)
1269
+ or not _safe_identity(self.candidate_root_key)
1270
+ or (
1271
+ self.previous_root_key != ""
1272
+ and not _safe_identity(self.previous_root_key)
1273
+ )
1274
+ or self.previous_root_key == self.candidate_root_key
1275
+ or type(self.generation_create_wire_probe_ms) is not int
1276
+ or self.generation_create_wire_probe_ms < 0
1277
+ ):
1278
+ raise ValueError("worker prepared root receipt is invalid")
1279
+
1280
+ def __repr__(self) -> str:
1281
+ return (
1282
+ "WorkerPreparedRootReceipt("
1283
+ f"transaction_id={self.transaction_id!r}, "
1284
+ f"generation={self.generation}, "
1285
+ f"manifest_sha256={self.manifest_sha256!r}, "
1286
+ "candidate_root_key=<redacted>, previous_root_key=<redacted>, "
1287
+ "generation_create_wire_probe_ms=<redacted>)"
1288
+ )
1289
+
1290
+
1291
+ @dataclass(frozen=True, slots=True, repr=False)
1292
+ class WorkerPromotionReceipt:
1293
+ transaction_id: UUID
1294
+ generation: int
1295
+ manifest_sha256: str
1296
+ root_key: str
1297
+ previous_root_key: str
1298
+ acknowledged: bool
1299
+ generation_create_wire_probe_ms: int
1300
+ root_swap_ms: int
1301
+
1302
+ def __post_init__(self) -> None:
1303
+ if (
1304
+ type(self.transaction_id) is not UUID
1305
+ or type(self.generation) is not int
1306
+ or self.generation <= 0
1307
+ or not _sha256_value(self.manifest_sha256)
1308
+ or not _safe_identity(self.root_key)
1309
+ or (
1310
+ self.previous_root_key != ""
1311
+ and not _safe_identity(self.previous_root_key)
1312
+ )
1313
+ or self.previous_root_key == self.root_key
1314
+ or type(self.acknowledged) is not bool
1315
+ or type(self.generation_create_wire_probe_ms) is not int
1316
+ or self.generation_create_wire_probe_ms < 0
1317
+ or type(self.root_swap_ms) is not int
1318
+ or self.root_swap_ms < 0
1319
+ ):
1320
+ raise ValueError("worker promotion receipt is invalid")
1321
+
1322
+ def __repr__(self) -> str:
1323
+ return (
1324
+ "WorkerPromotionReceipt("
1325
+ f"transaction_id={self.transaction_id!r}, "
1326
+ f"generation={self.generation}, "
1327
+ f"manifest_sha256={self.manifest_sha256!r}, "
1328
+ "root_key=<redacted>, previous_root_key=<redacted>, "
1329
+ f"acknowledged={self.acknowledged!r}, "
1330
+ "generation_create_wire_probe_ms=<redacted>, "
1331
+ "root_swap_ms=<redacted>)"
1332
+ )
1333
+
1334
+
1335
+ @dataclass(slots=True)
1336
+ class _GenerationRecord:
1337
+ handle: WorkerGenerationHandle
1338
+ manifest: WorkerUniverseManifest
1339
+ export_catalog: tuple[WorkerExport, ...]
1340
+ root_key: str
1341
+ active: bool
1342
+ explicitly_retained: bool
1343
+ descriptor_cache_identities: frozenset[tuple[str, str, int, str, str]]
1344
+ binary_keys: frozenset[WorkerModuleBinaryKey]
1345
+ debug_view: WorkerGenerationDebugView | None
1346
+ operation_pins: int = 0
1347
+
1348
+
1349
+ @dataclass(slots=True)
1350
+ class _OperationLease:
1351
+ pin: OperationGenerationPin
1352
+ outcome_unknown: bool = False
1353
+
1354
+
1355
+ GenerationFence = int | Callable[[], int]
1356
+
1357
+
1358
+ @dataclass(frozen=True, slots=True)
1359
+ class _WorkerUniverseLiveInventory:
1360
+ manifest_sha256s: frozenset[str]
1361
+ artifact_identities: frozenset[tuple[str, int, str]]
1362
+ descriptor_cache_identities: frozenset[tuple[str, str, int, str, str]]
1363
+ binary_keys: frozenset[WorkerModuleBinaryKey]
1364
+
1365
+
1366
+ @dataclass(frozen=True, slots=True, repr=False)
1367
+ class WorkerLifecycleReleasePlan:
1368
+ owner: object
1369
+ expected_fence: tuple[int, int]
1370
+ release: WorkerGenerationHandle | OperationGenerationPin
1371
+ remaining_views: tuple[WorkerGenerationDebugView, ...]
1372
+ released_names: tuple[str, ...]
1373
+ next_registration_refcounts: tuple[tuple[str, int], ...]
1374
+ next_registration_artifacts: tuple[tuple[str, tuple[str, str]], ...]
1375
+
1376
+ def __repr__(self) -> str:
1377
+ return (
1378
+ "WorkerLifecycleReleasePlan(owner=<redacted>, "
1379
+ f"expected_fence={self.expected_fence!r}, "
1380
+ f"release={type(self.release).__name__}, "
1381
+ f"remaining_views={len(self.remaining_views)}, "
1382
+ f"released_names={len(self.released_names)})"
1383
+ )
1384
+
1385
+
1386
+ class WorkerUniverseRegistry:
1387
+ """Atomic host registry for immutable Worker module generations."""
1388
+
1389
+ def __init__(
1390
+ self,
1391
+ *,
1392
+ runtime_generation: GenerationFence,
1393
+ context_generation: GenerationFence,
1394
+ ) -> None:
1395
+ self._lock = RLock()
1396
+ self._runtime_generation_source = _fence_source(runtime_generation)
1397
+ self._context_generation_source = _fence_source(context_generation)
1398
+ self._runtime_generation = self._runtime_generation_source()
1399
+ self._context_generation = self._context_generation_source()
1400
+ _validate_positive_generation(self._runtime_generation)
1401
+ _validate_positive_generation(self._context_generation)
1402
+ self._state = WorkerUniverseState.EMPTY
1403
+ self._next_generation = 1
1404
+ self._pending: WorkerUniverseCandidate | None = None
1405
+ self._sealed_activations: dict[int, _SealedWorkerActivation] = {}
1406
+ self._active_generation: int | None = None
1407
+ self._handles: dict[int, WorkerGenerationHandle] = {}
1408
+ self._generations: dict[int, _GenerationRecord] = {}
1409
+ self._leases: dict[str, _OperationLease] = {}
1410
+ self._registration_refcounts: dict[str, int] = {}
1411
+ self._registration_artifacts: dict[str, tuple[str, str]] = {}
1412
+ self._quarantine_holds: set[str] = set()
1413
+ self._release_owner = object()
1414
+
1415
+ @property
1416
+ def state(self) -> WorkerUniverseState:
1417
+ with self._lock:
1418
+ return self._state
1419
+
1420
+ @property
1421
+ def active_handle(self) -> WorkerGenerationHandle | None:
1422
+ with self._lock:
1423
+ self._require_active_read()
1424
+ record = self._active_record()
1425
+ return None if record is None else record.handle
1426
+
1427
+ @property
1428
+ def active_manifest(self) -> WorkerUniverseManifest | None:
1429
+ with self._lock:
1430
+ self._require_active_read()
1431
+ record = self._active_record()
1432
+ return None if record is None else record.manifest
1433
+
1434
+ @property
1435
+ def active_root_key(self) -> str | None:
1436
+ with self._lock:
1437
+ self._require_active_read()
1438
+ record = self._active_record()
1439
+ return None if record is None else record.root_key
1440
+
1441
+ def _confirmed_live_inventory(self) -> _WorkerUniverseLiveInventory | None:
1442
+ """Return exact cache roots only while host lifecycle state is certain."""
1443
+ with self._lock:
1444
+ if self._state not in (
1445
+ WorkerUniverseState.EMPTY,
1446
+ WorkerUniverseState.READY,
1447
+ ):
1448
+ return None
1449
+ try:
1450
+ self._require_current_fence()
1451
+ except ProtocolError:
1452
+ return None
1453
+ manifests = tuple(
1454
+ record.manifest
1455
+ for record in self._generations.values()
1456
+ if (
1457
+ record.active
1458
+ or record.explicitly_retained
1459
+ or record.operation_pins > 0
1460
+ )
1461
+ )
1462
+ return _WorkerUniverseLiveInventory(
1463
+ frozenset(manifest.sha256 for manifest in manifests),
1464
+ frozenset(
1465
+ (
1466
+ module.logical_name.casefold(),
1467
+ module.revision,
1468
+ module.artifact_sha256,
1469
+ )
1470
+ for manifest in manifests
1471
+ for module in manifest.modules
1472
+ ),
1473
+ frozenset(
1474
+ identity
1475
+ for record in self._generations.values()
1476
+ if (
1477
+ record.active
1478
+ or record.explicitly_retained
1479
+ or record.operation_pins > 0
1480
+ )
1481
+ for identity in record.descriptor_cache_identities
1482
+ ),
1483
+ frozenset(
1484
+ binary_key
1485
+ for record in self._generations.values()
1486
+ if (
1487
+ record.active
1488
+ or record.explicitly_retained
1489
+ or record.operation_pins > 0
1490
+ )
1491
+ for binary_key in record.binary_keys
1492
+ ),
1493
+ )
1494
+
1495
+ def preview_handle(
1496
+ self,
1497
+ artifacts: tuple[WorkerModuleArtifact, ...],
1498
+ *,
1499
+ export_catalog: tuple[WorkerExport, ...] | None = None,
1500
+ ) -> WorkerGenerationHandle:
1501
+ """Preview the next exact identity without reserving or staging it.
1502
+
1503
+ A later preparation (including an aborted one) can invalidate this
1504
+ preview. Callers must fence the actual candidate before target mutation.
1505
+ """
1506
+ with self._lock:
1507
+ self._require_available("preview")
1508
+ if self._state is WorkerUniverseState.PREPARING:
1509
+ raise ProtocolError("Worker universe already has a pending candidate")
1510
+ self._require_current_fence()
1511
+ _, manifest = _build_worker_manifest(self._next_generation, artifacts)
1512
+ catalog = manifest.exports if export_catalog is None else export_catalog
1513
+ if not _effective_export_catalog_matches(manifest, catalog):
1514
+ raise ProtocolError("Worker generation export catalog does not match its manifest")
1515
+ _require_registration_ownership(manifest, self._registration_artifacts)
1516
+ return WorkerGenerationHandle(
1517
+ self._runtime_generation, self._context_generation,
1518
+ self._next_generation, manifest.sha256,
1519
+ )
1520
+
1521
+ def prepare(
1522
+ self,
1523
+ artifacts: tuple[WorkerModuleArtifact, ...],
1524
+ *,
1525
+ export_catalog: tuple[WorkerExport, ...] | None = None,
1526
+ ) -> WorkerUniverseCandidate:
1527
+ with self._lock:
1528
+ self._require_available("prepare")
1529
+ if self._state is WorkerUniverseState.PREPARING:
1530
+ raise ProtocolError("Worker universe already has a pending candidate")
1531
+ self._require_current_fence()
1532
+ generation = self._next_generation
1533
+ ordered_views, manifest = _build_worker_manifest(generation, artifacts)
1534
+ ordered = tuple(view.descriptor for view in ordered_views)
1535
+ effective_catalog = (
1536
+ manifest.exports if export_catalog is None else export_catalog
1537
+ )
1538
+ if not _effective_export_catalog_matches(manifest, effective_catalog):
1539
+ raise ProtocolError(
1540
+ "Worker generation export catalog does not match its manifest"
1541
+ )
1542
+ _require_registration_ownership(
1543
+ manifest,
1544
+ self._registration_artifacts,
1545
+ )
1546
+ handle = WorkerGenerationHandle(
1547
+ self._runtime_generation,
1548
+ self._context_generation,
1549
+ generation,
1550
+ manifest.sha256,
1551
+ )
1552
+ new_artifacts = tuple(
1553
+ artifact
1554
+ for artifact, module in zip(ordered, manifest.modules, strict=True)
1555
+ if module.registration_name not in self._registration_refcounts
1556
+ )
1557
+ previous = self._active_record()
1558
+ candidate = WorkerUniverseCandidate(
1559
+ handle,
1560
+ manifest,
1561
+ effective_catalog,
1562
+ ordered,
1563
+ new_artifacts,
1564
+ None if previous is None else previous.handle,
1565
+ )
1566
+ activation = _seal_worker_activation(candidate, ordered_views)
1567
+ self._next_generation += 1
1568
+ self._handles[generation] = handle
1569
+ self._pending = candidate
1570
+ self._sealed_activations[generation] = activation
1571
+ self._state = WorkerUniverseState.PREPARING
1572
+ return candidate
1573
+
1574
+ def discard(self, candidate: WorkerUniverseCandidate) -> None:
1575
+ with self._lock:
1576
+ self._require_available("discard")
1577
+ self._require_pending(candidate)
1578
+ try:
1579
+ self._require_current_fence()
1580
+ except ProtocolError:
1581
+ self._quarantine_candidate(candidate)
1582
+ self._pending = None
1583
+ self._state = WorkerUniverseState.BROKEN
1584
+ raise
1585
+ self._handles.pop(candidate.handle.generation, None)
1586
+ self._sealed_activations.pop(candidate.handle.generation, None)
1587
+ self._pending = None
1588
+ self._state = (
1589
+ WorkerUniverseState.READY
1590
+ if self._active_generation is not None
1591
+ else WorkerUniverseState.EMPTY
1592
+ )
1593
+
1594
+ def _discard_exact_pending_without_fence(
1595
+ self,
1596
+ candidate: WorkerUniverseCandidate,
1597
+ ) -> None:
1598
+ """Undo an internally returned exact candidate before target access.
1599
+
1600
+ Server preparation alone may use this after its post-prepare fence
1601
+ check fails. Caller-supplied promotion still uses the public,
1602
+ fence-authenticated lifecycle paths.
1603
+ """
1604
+
1605
+ with self._lock:
1606
+ self._require_available("internal candidate cleanup")
1607
+ self._require_pending(candidate)
1608
+ activation = self._sealed_activations.get(
1609
+ candidate.handle.generation
1610
+ )
1611
+ if activation is None or activation.candidate is not candidate:
1612
+ raise ProtocolError(
1613
+ "Worker internal candidate cleanup is uncertain"
1614
+ )
1615
+ self._handles.pop(candidate.handle.generation, None)
1616
+ self._sealed_activations.pop(candidate.handle.generation, None)
1617
+ self._pending = None
1618
+ self._state = (
1619
+ WorkerUniverseState.READY
1620
+ if self._active_generation is not None
1621
+ else WorkerUniverseState.EMPTY
1622
+ )
1623
+
1624
+ def confirm(
1625
+ self,
1626
+ candidate: WorkerUniverseCandidate,
1627
+ receipt: WorkerPromotionReceipt,
1628
+ ) -> WorkerGenerationConfirmation:
1629
+ with self._lock:
1630
+ self._require_available("confirm")
1631
+ self._require_current_fence()
1632
+ self._require_pending(candidate)
1633
+ current = self._active_record()
1634
+ expected_previous_root = "" if current is None else current.root_key
1635
+ if (
1636
+ not isinstance(receipt, WorkerPromotionReceipt)
1637
+ or not receipt.acknowledged
1638
+ or receipt.generation != candidate.handle.generation
1639
+ or receipt.manifest_sha256 != candidate.manifest.sha256
1640
+ or receipt.root_key != _generation_root_key(candidate.handle.generation)
1641
+ or receipt.previous_root_key != expected_previous_root
1642
+ ):
1643
+ raise ProtocolError("Worker promotion receipt is stale or unacknowledged")
1644
+
1645
+ registration_refcounts = dict(self._registration_refcounts)
1646
+ registration_artifacts = dict(self._registration_artifacts)
1647
+ old_record = current
1648
+ if old_record is not None and not old_record.explicitly_retained:
1649
+ _adjust_manifest_registrations(
1650
+ registration_refcounts,
1651
+ old_record.manifest,
1652
+ -1,
1653
+ )
1654
+ _adjust_manifest_registrations(
1655
+ registration_refcounts,
1656
+ candidate.manifest,
1657
+ 1,
1658
+ )
1659
+ _require_registration_ownership(
1660
+ candidate.manifest,
1661
+ registration_artifacts,
1662
+ )
1663
+ for module in candidate.manifest.modules:
1664
+ registration_artifacts[module.registration_name] = (
1665
+ module.logical_name.casefold(),
1666
+ module.artifact_sha256,
1667
+ )
1668
+ released = _remove_zero_registration_refcounts(
1669
+ registration_refcounts,
1670
+ registration_artifacts,
1671
+ self._quarantine_holds,
1672
+ )
1673
+ confirmation = WorkerGenerationConfirmation(
1674
+ candidate.handle,
1675
+ released,
1676
+ )
1677
+ record = _GenerationRecord(
1678
+ candidate.handle,
1679
+ candidate.manifest,
1680
+ candidate.export_catalog,
1681
+ receipt.root_key,
1682
+ active=True,
1683
+ explicitly_retained=True,
1684
+ descriptor_cache_identities=frozenset(
1685
+ identity
1686
+ for artifact in candidate.artifacts
1687
+ if (
1688
+ identity := _worker_module_descriptor_cache_identity(
1689
+ artifact
1690
+ )
1691
+ )
1692
+ is not None
1693
+ ),
1694
+ binary_keys=frozenset(
1695
+ artifact.binary_key for artifact in candidate.artifacts
1696
+ ),
1697
+ debug_view=self._sealed_activations[
1698
+ candidate.handle.generation
1699
+ ].debug_view,
1700
+ )
1701
+
1702
+ if old_record is not None:
1703
+ old_record.active = False
1704
+ self._generations[candidate.handle.generation] = record
1705
+ self._active_generation = candidate.handle.generation
1706
+ self._registration_refcounts = registration_refcounts
1707
+ self._registration_artifacts = registration_artifacts
1708
+ self._sealed_activations.pop(candidate.handle.generation, None)
1709
+ self._pending = None
1710
+ self._state = WorkerUniverseState.READY
1711
+ self._collect_unretained_generations()
1712
+ return confirmation
1713
+
1714
+ def pin_active(self) -> OperationGenerationPin:
1715
+ with self._lock:
1716
+ self._require_available("pin")
1717
+ self._require_current_fence()
1718
+ record = self._active_record()
1719
+ if record is None:
1720
+ raise ProtocolError("Worker universe has no active generation")
1721
+ lease_id = _new_lease_id(self._leases)
1722
+ pin = OperationGenerationPin(
1723
+ record.handle,
1724
+ record.manifest,
1725
+ record.root_key,
1726
+ record.export_catalog,
1727
+ lease_id,
1728
+ )
1729
+ registration_refcounts = dict(self._registration_refcounts)
1730
+ _adjust_manifest_registrations(
1731
+ registration_refcounts,
1732
+ record.manifest,
1733
+ 1,
1734
+ )
1735
+ record.operation_pins += 1
1736
+ self._registration_refcounts = registration_refcounts
1737
+ self._leases[lease_id] = _OperationLease(pin)
1738
+ return pin
1739
+
1740
+ def release_generation(
1741
+ self,
1742
+ handle: WorkerGenerationHandle,
1743
+ ) -> tuple[str, ...]:
1744
+ return self._commit_release(self._preview_release(handle))
1745
+
1746
+ def release_pin(self, pin: OperationGenerationPin) -> tuple[str, ...]:
1747
+ return self._commit_release(self._preview_release(pin))
1748
+
1749
+ def _preview_release(
1750
+ self,
1751
+ release: WorkerGenerationHandle | OperationGenerationPin,
1752
+ ) -> WorkerLifecycleReleasePlan:
1753
+ with self._lock:
1754
+ self._require_available("preview release")
1755
+ self._require_current_fence()
1756
+ refcounts = dict(self._registration_refcounts)
1757
+ artifacts = dict(self._registration_artifacts)
1758
+ if type(release) is WorkerGenerationHandle:
1759
+ record = self._require_generation(release)
1760
+ if not record.explicitly_retained:
1761
+ raise ProtocolError("Worker generation handle is stale or released")
1762
+ if not record.active:
1763
+ _adjust_manifest_registrations(refcounts, record.manifest, -1)
1764
+ elif type(release) is OperationGenerationPin:
1765
+ lease = self._require_lease(release)
1766
+ if lease.outcome_unknown:
1767
+ raise ProtocolError(
1768
+ "Outcome-unknown Worker pin is retained until teardown"
1769
+ )
1770
+ record = self._require_generation(release.handle)
1771
+ if record.operation_pins <= 0:
1772
+ raise ProtocolError("Worker generation pin refcount is invalid")
1773
+ _adjust_manifest_registrations(refcounts, record.manifest, -1)
1774
+ else:
1775
+ raise TypeError("Worker lifecycle release capability is required")
1776
+ released = _remove_zero_registration_refcounts(
1777
+ refcounts,
1778
+ artifacts,
1779
+ self._quarantine_holds,
1780
+ )
1781
+ remaining = tuple(
1782
+ item.debug_view
1783
+ for _generation, item in sorted(self._generations.items())
1784
+ if item.debug_view is not None
1785
+ and self._record_live_after_release(item, record, release)
1786
+ )
1787
+ return WorkerLifecycleReleasePlan(
1788
+ self._release_owner,
1789
+ (self._runtime_generation, self._context_generation),
1790
+ release,
1791
+ remaining,
1792
+ released,
1793
+ tuple(sorted(refcounts.items(), key=lambda item: item[0].casefold())),
1794
+ tuple(sorted(artifacts.items(), key=lambda item: item[0].casefold())),
1795
+ )
1796
+
1797
+ def _commit_release(
1798
+ self,
1799
+ plan: WorkerLifecycleReleasePlan,
1800
+ ) -> tuple[str, ...]:
1801
+ if type(plan) is not WorkerLifecycleReleasePlan:
1802
+ raise TypeError("Worker lifecycle release plan is required")
1803
+ with self._lock:
1804
+ self._require_available("commit release")
1805
+ self._require_current_fence()
1806
+ if (
1807
+ plan.owner is not self._release_owner
1808
+ or plan.expected_fence
1809
+ != (self._runtime_generation, self._context_generation)
1810
+ ):
1811
+ raise ProtocolError("Worker lifecycle release plan is stale")
1812
+ current = self._preview_release(plan.release)
1813
+ if (
1814
+ current.remaining_views != plan.remaining_views
1815
+ or current.released_names != plan.released_names
1816
+ or current.next_registration_refcounts
1817
+ != plan.next_registration_refcounts
1818
+ or current.next_registration_artifacts
1819
+ != plan.next_registration_artifacts
1820
+ ):
1821
+ raise ProtocolError("Worker lifecycle release plan is stale")
1822
+ release = plan.release
1823
+ if type(release) is WorkerGenerationHandle:
1824
+ record = self._require_generation(release)
1825
+ record.explicitly_retained = False
1826
+ else:
1827
+ lease = self._require_lease(release)
1828
+ record = self._require_generation(release.handle)
1829
+ del self._leases[release.lease_id]
1830
+ record.operation_pins -= 1
1831
+ self._registration_refcounts = dict(plan.next_registration_refcounts)
1832
+ self._registration_artifacts = dict(plan.next_registration_artifacts)
1833
+ self._collect_unretained_generations()
1834
+ return plan.released_names
1835
+
1836
+ @staticmethod
1837
+ def _record_live_after_release(
1838
+ item: _GenerationRecord,
1839
+ released_record: _GenerationRecord,
1840
+ release: WorkerGenerationHandle | OperationGenerationPin,
1841
+ ) -> bool:
1842
+ explicitly_retained = item.explicitly_retained
1843
+ operation_pins = item.operation_pins
1844
+ if item is released_record:
1845
+ if type(release) is WorkerGenerationHandle:
1846
+ explicitly_retained = False
1847
+ else:
1848
+ operation_pins -= 1
1849
+ return item.active or explicitly_retained or operation_pins > 0
1850
+
1851
+ def retain_outcome_unknown(self, pin: OperationGenerationPin) -> None:
1852
+ with self._lock:
1853
+ self._require_available("retain outcome-unknown pin")
1854
+ lease = self._require_lease(pin)
1855
+ lease.outcome_unknown = True
1856
+ if self._pending is not None:
1857
+ self._quarantine_candidate(self._pending)
1858
+ self._state = WorkerUniverseState.BROKEN
1859
+ self._pending = None
1860
+ self._require_current_fence()
1861
+
1862
+ def mark_broken(
1863
+ self,
1864
+ candidate: WorkerUniverseCandidate | None = None,
1865
+ ) -> None:
1866
+ with self._lock:
1867
+ self._require_available("mark broken")
1868
+ if candidate is not None:
1869
+ self._require_pending(candidate)
1870
+ elif self._pending is not None:
1871
+ candidate = self._pending
1872
+ if candidate is not None:
1873
+ self._quarantine_candidate(candidate)
1874
+ self._pending = None
1875
+ self._state = WorkerUniverseState.BROKEN
1876
+
1877
+ def registration_refcount(self, name: str) -> int:
1878
+ if not isinstance(name, str):
1879
+ raise TypeError("worker registration name must be a string")
1880
+ with self._lock:
1881
+ return self._registration_refcounts.get(name, 0) + (
1882
+ 1 if name in self._quarantine_holds else 0
1883
+ )
1884
+
1885
+ def teardown(self) -> tuple[str, ...]:
1886
+ with self._lock:
1887
+ if self._pending is not None:
1888
+ self._quarantine_candidate(self._pending)
1889
+ registrations = tuple(
1890
+ sorted(
1891
+ set(self._registration_refcounts) | self._quarantine_holds,
1892
+ key=str.casefold,
1893
+ )
1894
+ )
1895
+ self._pending = None
1896
+ self._active_generation = None
1897
+ self._sealed_activations.clear()
1898
+ self._handles.clear()
1899
+ self._generations.clear()
1900
+ self._leases.clear()
1901
+ self._registration_refcounts.clear()
1902
+ self._registration_artifacts.clear()
1903
+ self._quarantine_holds.clear()
1904
+ self._state = WorkerUniverseState.CLOSED
1905
+ return registrations
1906
+
1907
+ close = teardown
1908
+
1909
+ def _active_record(self) -> _GenerationRecord | None:
1910
+ if self._active_generation is None:
1911
+ return None
1912
+ return self._generations.get(self._active_generation)
1913
+
1914
+ def _require_available(self, operation: str) -> None:
1915
+ if self._state in (WorkerUniverseState.BROKEN, WorkerUniverseState.CLOSED):
1916
+ raise ProtocolError(
1917
+ f"Worker universe {operation} is unavailable in {self._state.value} state"
1918
+ )
1919
+
1920
+ def _require_current_fence(self) -> None:
1921
+ if (
1922
+ self._runtime_generation_source() != self._runtime_generation
1923
+ or self._context_generation_source() != self._context_generation
1924
+ ):
1925
+ raise ProtocolError("Worker universe runtime/context fence is stale")
1926
+
1927
+ def _require_active_read(self) -> None:
1928
+ if self._state is WorkerUniverseState.BROKEN:
1929
+ raise ProtocolError("Worker universe active generation is unavailable in broken state")
1930
+ if self._state is not WorkerUniverseState.CLOSED:
1931
+ self._require_current_fence()
1932
+
1933
+ def _require_pending(self, candidate: WorkerUniverseCandidate) -> None:
1934
+ if (
1935
+ not isinstance(candidate, WorkerUniverseCandidate)
1936
+ or self._state is not WorkerUniverseState.PREPARING
1937
+ or self._pending is not candidate
1938
+ or self._handles.get(candidate.handle.generation) is not candidate.handle
1939
+ ):
1940
+ raise ProtocolError("Worker universe candidate is stale or forged")
1941
+
1942
+ def _require_sealed_activation(
1943
+ self,
1944
+ candidate: WorkerUniverseCandidate,
1945
+ ) -> _SealedWorkerActivation:
1946
+ """Return the O(1), exact-candidate capability for pending staging."""
1947
+ with self._lock:
1948
+ self._require_available("sealed activation")
1949
+ self._require_current_fence()
1950
+ self._require_pending(candidate)
1951
+ activation = self._sealed_activations.get(candidate.handle.generation)
1952
+ if activation is None or activation.candidate is not candidate:
1953
+ raise ProtocolError("Worker sealed activation is stale or forged")
1954
+ return activation
1955
+
1956
+ def _candidate_diagnostics(
1957
+ self,
1958
+ candidate: WorkerUniverseCandidate,
1959
+ ) -> tuple[WorkerDiagnosticArtifact, ...]:
1960
+ """Return the exact diagnostics sealed for one pending candidate."""
1961
+ with self._lock:
1962
+ return self._require_sealed_activation(candidate).diagnostics
1963
+
1964
+ def _bind_candidate_debug_view(
1965
+ self,
1966
+ candidate: WorkerUniverseCandidate,
1967
+ debug_view: WorkerGenerationDebugView,
1968
+ ) -> None:
1969
+ with self._lock:
1970
+ activation = self._require_sealed_activation(candidate)
1971
+ if (
1972
+ type(debug_view) is not WorkerGenerationDebugView
1973
+ or debug_view.handle is not candidate.handle
1974
+ or debug_view.manifest is not candidate.manifest
1975
+ or activation.debug_view is not None
1976
+ ):
1977
+ raise ProtocolError("Worker candidate debug view is invalid")
1978
+ self._sealed_activations[candidate.handle.generation] = (
1979
+ _SealedWorkerActivation(
1980
+ activation.candidate,
1981
+ activation.artifact_views,
1982
+ activation.stage_entries,
1983
+ activation.diagnostics,
1984
+ debug_view,
1985
+ )
1986
+ )
1987
+
1988
+ def _candidate_debug_view(
1989
+ self,
1990
+ candidate: WorkerUniverseCandidate,
1991
+ ) -> WorkerGenerationDebugView:
1992
+ with self._lock:
1993
+ view = self._require_sealed_activation(candidate).debug_view
1994
+ if view is None:
1995
+ raise ProtocolError("Worker candidate debug view is unavailable")
1996
+ return view
1997
+
1998
+ def _operation_debug_view(
1999
+ self,
2000
+ pin: OperationGenerationPin,
2001
+ ) -> WorkerGenerationDebugView:
2002
+ with self._lock:
2003
+ self._require_current_fence()
2004
+ lease = self._require_lease(pin)
2005
+ record = self._require_generation(lease.pin.handle)
2006
+ if record.debug_view is None:
2007
+ raise ProtocolError("Worker generation debug view is unavailable")
2008
+ return record.debug_view
2009
+
2010
+ def _retained_debug_views(self) -> tuple[WorkerGenerationDebugView, ...]:
2011
+ with self._lock:
2012
+ if self._state is WorkerUniverseState.CLOSED:
2013
+ raise ProtocolError("Worker generation debug views are unavailable")
2014
+ self._require_current_fence()
2015
+ return tuple(
2016
+ record.debug_view
2017
+ for _generation, record in sorted(self._generations.items())
2018
+ if record.debug_view is not None
2019
+ and (
2020
+ record.active
2021
+ or record.explicitly_retained
2022
+ or record.operation_pins > 0
2023
+ )
2024
+ )
2025
+
2026
+ def _require_generation(
2027
+ self,
2028
+ handle: WorkerGenerationHandle,
2029
+ ) -> _GenerationRecord:
2030
+ if (
2031
+ not isinstance(handle, WorkerGenerationHandle)
2032
+ or handle.runtime_generation != self._runtime_generation
2033
+ or handle.context_generation != self._context_generation
2034
+ or self._handles.get(handle.generation) is not handle
2035
+ ):
2036
+ raise ProtocolError("Worker generation handle is stale or forged")
2037
+ record = self._generations.get(handle.generation)
2038
+ if (
2039
+ record is None
2040
+ or record.handle is not handle
2041
+ or handle.manifest_sha256 != record.manifest.sha256
2042
+ ):
2043
+ raise ProtocolError("Worker generation handle is stale or released")
2044
+ return record
2045
+
2046
+ def _require_lease(self, pin: OperationGenerationPin) -> _OperationLease:
2047
+ if not isinstance(pin, OperationGenerationPin):
2048
+ raise ProtocolError("Worker generation pin is stale or forged")
2049
+ lease = self._leases.get(pin.lease_id)
2050
+ if lease is None or lease.pin is not pin:
2051
+ raise ProtocolError("Worker generation pin is stale or forged")
2052
+ return lease
2053
+
2054
+ def _install_registration_refcounts(
2055
+ self,
2056
+ refcounts: dict[str, int],
2057
+ ) -> tuple[str, ...]:
2058
+ released = _remove_zero_registration_refcounts(
2059
+ refcounts,
2060
+ self._registration_artifacts,
2061
+ self._quarantine_holds,
2062
+ )
2063
+ self._registration_refcounts = refcounts
2064
+ return released
2065
+
2066
+ def _collect_unretained_generations(self) -> None:
2067
+ for generation, record in tuple(self._generations.items()):
2068
+ if not record.active and not record.explicitly_retained and record.operation_pins == 0:
2069
+ del self._generations[generation]
2070
+ self._handles.pop(generation, None)
2071
+
2072
+ def _quarantine_candidate(self, candidate: WorkerUniverseCandidate) -> None:
2073
+ for artifact in candidate.new_artifacts:
2074
+ self._quarantine_holds.add(
2075
+ registration_name(artifact.logical_name, artifact.artifact_sha256)
2076
+ )
2077
+
2078
+
2079
+ def prepare_worker_root_instruction(
2080
+ candidate: WorkerUniverseCandidate,
2081
+ transaction_id: UUID,
2082
+ previous_root_key: str,
2083
+ ) -> str:
2084
+ """Create and seal a candidate root without changing the active pointer."""
2085
+ if not isinstance(candidate, WorkerUniverseCandidate):
2086
+ raise TypeError("worker universe candidate is required")
2087
+ if type(transaction_id) is not UUID:
2088
+ raise TypeError("worker root transaction id must be a UUID")
2089
+ if previous_root_key != "" and not _safe_identity(previous_root_key):
2090
+ raise ValueError("previous Worker root key is invalid")
2091
+ manifest = candidate.manifest
2092
+ reserved_names = {
2093
+ binding.target_module.casefold()
2094
+ for binding in manifest.wiring
2095
+ if binding.target_kind == "original"
2096
+ }
2097
+ reserved_names.update(
2098
+ module.logical_name.casefold() for module in manifest.modules
2099
+ )
2100
+
2101
+ def allocate_local(preferred: str) -> str:
2102
+ candidate_name = preferred
2103
+ suffix = 0
2104
+ while candidate_name.casefold() in reserved_names:
2105
+ suffix += 1
2106
+ candidate_name = f"{preferred}_{suffix}"
2107
+ reserved_names.add(candidate_name.casefold())
2108
+ return candidate_name
2109
+
2110
+ phase_variable = allocate_local("ЭтапПубликацииWorker")
2111
+ objects_variable = allocate_local("ОбъектыКандидатаWorker")
2112
+ dependency_variables = tuple(
2113
+ (
2114
+ allocate_local(f"ИсточникЗависимостиWorker{index}"),
2115
+ allocate_local(f"ЦельЗависимостиWorker{index}"),
2116
+ )
2117
+ for index, _ in enumerate(manifest.wiring)
2118
+ )
2119
+ module_variables = tuple(
2120
+ allocate_local(f"ОбъектМодуляWorker{index}")
2121
+ for index, _ in enumerate(manifest.modules)
2122
+ )
2123
+ exports_variable = allocate_local("ЭкспортыКандидатаWorker")
2124
+ exports_data_variable = allocate_local("ДанныеЭкспортовКандидатаWorker")
2125
+ modules_variable = allocate_local("МодулиКандидатаWorker")
2126
+ root_data_variable = allocate_local("ДанныеКорняКандидатаWorker")
2127
+ root_variable = allocate_local("КореньКандидатаWorker")
2128
+ prepared_data_variable = allocate_local("ДанныеПодготовленногоКорняWorker")
2129
+ prepared_variable = allocate_local("ПодготовленныйКореньWorker")
2130
+ error_variable = allocate_local("ОшибкаПубликацииWorker")
2131
+ artifact_error_variable = allocate_local("МаркерОшибкиАртефактаWorker")
2132
+ prepare_started_variable = allocate_local("НачалоПодготовкиКандидатаWorker")
2133
+ prepare_finished_variable = allocate_local("КонецПодготовкиКандидатаWorker")
2134
+ lines = [
2135
+ f'{phase_variable} = "create";',
2136
+ f'{artifact_error_variable} = "";',
2137
+ "Попытка",
2138
+ f" {prepare_started_variable} = "
2139
+ "ТекущаяУниверсальнаяДатаВМиллисекундах();",
2140
+ f" {objects_variable} = Новый Соответствие;",
2141
+ ]
2142
+ for module in manifest.modules:
2143
+ artifact_error_header = (
2144
+ "onec-worker-artifact-stage="
2145
+ f"artifact_sha256={module.artifact_sha256};"
2146
+ f"logical_name_sha256={sha256(module.logical_name.casefold().encode('utf-8')).hexdigest()};"
2147
+ "phase=create;boundary=create"
2148
+ )
2149
+ lines.append(f" {artifact_error_variable} = {_bsl_string(artifact_error_header)};")
2150
+ lines.append(
2151
+ f" {objects_variable}.Вставить("
2152
+ f"{_bsl_string(module.logical_name)}, ВнешниеОбработки.Создать("
2153
+ f"{_bsl_string(module.registration_name)}, Ложь));"
2154
+ )
2155
+ lines.append(f' {artifact_error_variable} = "";')
2156
+ lines.append(f' {phase_variable} = "wire";')
2157
+ for index, binding in enumerate(manifest.wiring):
2158
+ source_variable, target_variable = dependency_variables[index]
2159
+ lines.append(
2160
+ f" {source_variable} = {objects_variable}.Получить("
2161
+ f"{_bsl_string(binding.source_module)});"
2162
+ )
2163
+ if binding.target_kind == "overloaded":
2164
+ lines.append(
2165
+ f" {target_variable} = {objects_variable}.Получить("
2166
+ f"{_bsl_string(binding.target_module)});"
2167
+ )
2168
+ else:
2169
+ lines.append(f" {target_variable} = {binding.target_module};")
2170
+ lines.extend(
2171
+ (
2172
+ f" {source_variable}.{binding.export_variable} = {target_variable};",
2173
+ f" Если ТипЗнч({source_variable}.{binding.export_variable}) "
2174
+ f"<> ТипЗнч({target_variable}) Тогда",
2175
+ ' ВызватьИсключение "worker dependency field type mismatch";',
2176
+ " КонецЕсли;",
2177
+ )
2178
+ )
2179
+ lines.append(f' {phase_variable} = "probe";')
2180
+ for index, module in enumerate(manifest.modules):
2181
+ object_variable = module_variables[index]
2182
+ lines.extend(
2183
+ (
2184
+ f" {object_variable} = {objects_variable}.Получить("
2185
+ f"{_bsl_string(module.logical_name)});",
2186
+ f" Если ТипЗнч({object_variable}) = Тип(\"Неопределено\") Тогда",
2187
+ ' ВызватьИсключение "worker module object is unavailable";',
2188
+ " КонецЕсли;",
2189
+ )
2190
+ )
2191
+ lines.append(f" {exports_variable} = Новый Массив;")
2192
+ for export in manifest.exports:
2193
+ lines.append(
2194
+ f" {exports_variable}.Добавить("
2195
+ f"{_bsl_string(export.public_path)});"
2196
+ )
2197
+ root_key = _generation_root_key(manifest.generation)
2198
+ lines.extend(
2199
+ (
2200
+ f" {modules_variable} = Новый ФиксированноеСоответствие("
2201
+ f"{objects_variable});",
2202
+ f" {exports_variable} = Новый ФиксированныйМассив("
2203
+ f"{exports_variable});",
2204
+ f" {exports_data_variable} = Новый Структура;",
2205
+ f' {exports_data_variable}.Вставить("Kind", '
2206
+ '"OnecWorkerExportsV1");',
2207
+ f' {exports_data_variable}.Вставить("Items", '
2208
+ f"{exports_variable});",
2209
+ f" {exports_variable} = Новый ФиксированнаяСтруктура("
2210
+ f"{exports_data_variable});",
2211
+ f" {root_data_variable} = Новый Структура;",
2212
+ f' {root_data_variable}.Вставить("ManifestSha256", '
2213
+ f"{_bsl_string(manifest.sha256)});",
2214
+ f' {root_data_variable}.Вставить("RootKey", '
2215
+ f"{_bsl_string(root_key)});",
2216
+ f' {root_data_variable}.Вставить("Modules", '
2217
+ f"{modules_variable});",
2218
+ f' {root_data_variable}.Вставить("Exports", '
2219
+ f"{exports_variable});",
2220
+ )
2221
+ )
2222
+ lines.extend(
2223
+ (
2224
+ f" {root_variable} = Новый ФиксированнаяСтруктура("
2225
+ f"{root_data_variable});",
2226
+ f" {prepare_finished_variable} = "
2227
+ "ТекущаяУниверсальнаяДатаВМиллисекундах();",
2228
+ "Исключение",
2229
+ f" {error_variable} = ПодробноеПредставлениеОшибки(ИнформацияОбОшибке());",
2230
+ f' Если {artifact_error_variable} <> "" Тогда',
2231
+ f' {error_variable} = {artifact_error_variable} + ";diagnostic_utf16_length=" '
2232
+ f'+ Формат(СтрДлина({error_variable}), "ЧГ=0; ЧДЦ=0; ЧН=0") '
2233
+ f"+ Символы.ПС + {error_variable};",
2234
+ " КонецЕсли;",
2235
+ ' ВызватьИсключение "onec-worker-root-prepare-stage=" '
2236
+ f"+ {phase_variable} + Символы.ПС + {error_variable};",
2237
+ "КонецПопытки;",
2238
+ f"{prepared_data_variable} = Новый Структура;",
2239
+ f'{prepared_data_variable}.Вставить("TransactionId", '
2240
+ f"{_bsl_string(str(transaction_id))});",
2241
+ f'{prepared_data_variable}.Вставить("Generation", '
2242
+ f"{manifest.generation});",
2243
+ f'{prepared_data_variable}.Вставить("ManifestSha256", '
2244
+ f"{_bsl_string(manifest.sha256)});",
2245
+ f'{prepared_data_variable}.Вставить("CandidateRootKey", '
2246
+ f"{_bsl_string(root_key)});",
2247
+ f'{prepared_data_variable}.Вставить("PreviousRootKey", '
2248
+ f"{_bsl_string(previous_root_key)});",
2249
+ f'{prepared_data_variable}.Вставить("Root", {root_variable});',
2250
+ f'{prepared_data_variable}.Вставить("PrepareMs", '
2251
+ f"{prepare_finished_variable} - {prepare_started_variable});",
2252
+ f"{prepared_variable} = Новый ФиксированнаяСтруктура("
2253
+ f"{prepared_data_variable});",
2254
+ f"Контекст.Вставить({_bsl_string(_prepared_root_context_key(transaction_id))}, "
2255
+ f"{prepared_variable});",
2256
+ "Результат = "
2257
+ f"{_bsl_string(_PREPARED_ROOT_RECEIPT_PREFIX + '|' + str(transaction_id) + '|')} + "
2258
+ f'Формат({manifest.generation}, "ЧГ=0; ЧДЦ=0; ЧН=0") + "|" + '
2259
+ f"{_bsl_string(manifest.sha256 + '|' + root_key + '|' + _wire_root_key(previous_root_key) + '|')} + "
2260
+ f"Формат({prepare_finished_variable} - {prepare_started_variable}, "
2261
+ '"ЧГ=0; ЧДЦ=0; ЧН=0");',
2262
+ )
2263
+ )
2264
+ return "\n".join(lines)
2265
+
2266
+
2267
+ def swap_worker_root_instruction(prepared: WorkerPreparedRootReceipt) -> str:
2268
+ """Guard and publish one exact prepared root as the active root."""
2269
+ if type(prepared) is not WorkerPreparedRootReceipt:
2270
+ raise TypeError("worker prepared root receipt is required")
2271
+ prepared_key = _prepared_root_context_key(prepared.transaction_id)
2272
+ outcome_key = _root_swap_outcome_context_key(prepared.transaction_id)
2273
+ previous = prepared.previous_root_key
2274
+ lines = [
2275
+ "Результат = Неопределено;",
2276
+ f"Если Не Контекст.Свойство({_bsl_string(outcome_key)}, Результат) Тогда",
2277
+ " ПодготовленныйКореньWorker = Неопределено;",
2278
+ " ТекущийКореньWorker = Неопределено;",
2279
+ " Попытка",
2280
+ f" Если Не Контекст.Свойство({_bsl_string(prepared_key)}, "
2281
+ "ПодготовленныйКореньWorker) Тогда",
2282
+ ' ВызватьИсключение "prepared root is unavailable";',
2283
+ " КонецЕсли;",
2284
+ " Если ПодготовленныйКореньWorker.TransactionId <> "
2285
+ f"{_bsl_string(str(prepared.transaction_id))} Или "
2286
+ "ПодготовленныйКореньWorker.Generation <> "
2287
+ f"{prepared.generation} Или "
2288
+ "ПодготовленныйКореньWorker.ManifestSha256 <> "
2289
+ f"{_bsl_string(prepared.manifest_sha256)} Или "
2290
+ "ПодготовленныйКореньWorker.CandidateRootKey <> "
2291
+ f"{_bsl_string(prepared.candidate_root_key)} Или "
2292
+ "ПодготовленныйКореньWorker.PreviousRootKey <> "
2293
+ f"{_bsl_string(previous)} Тогда",
2294
+ ' ВызватьИсключение "prepared root identity mismatch";',
2295
+ " КонецЕсли;",
2296
+ ]
2297
+ if previous:
2298
+ lines.extend(
2299
+ (
2300
+ ' Если Не Контекст.Свойство("RuntimeWorkerActiveGeneration", '
2301
+ "ТекущийКореньWorker) Или ТекущийКореньWorker.RootKey <> "
2302
+ f"{_bsl_string(previous)} Тогда",
2303
+ ' ВызватьИсключение "previous root identity mismatch";',
2304
+ " КонецЕсли;",
2305
+ )
2306
+ )
2307
+ else:
2308
+ lines.extend(
2309
+ (
2310
+ ' Если Контекст.Свойство("RuntimeWorkerActiveGeneration", '
2311
+ "ТекущийКореньWorker) Тогда",
2312
+ ' ВызватьИсключение "unexpected previous root";',
2313
+ " КонецЕсли;",
2314
+ )
2315
+ )
2316
+ lines.extend(
2317
+ (
2318
+ " Исключение",
2319
+ " ОшибкаСменыКорняWorker = ОписаниеОшибки();",
2320
+ ' ВызватьИсключение "onec-worker-root-swap-stage=guard" '
2321
+ "+ Символы.ПС + ОшибкаСменыКорняWorker;",
2322
+ " КонецПопытки;",
2323
+ " НачалоСменыКорняWorker = "
2324
+ "ТекущаяУниверсальнаяДатаВМиллисекундах();",
2325
+ ' Контекст.Вставить("RuntimeWorkerActiveGeneration", '
2326
+ "ПодготовленныйКореньWorker.Root);",
2327
+ f" Контекст.Удалить({_bsl_string(prepared_key)});",
2328
+ " КонецСменыКорняWorker = "
2329
+ "ТекущаяУниверсальнаяДатаВМиллисекундах();",
2330
+ " Результат = "
2331
+ f"{_bsl_string(_ROOT_SWAP_RECEIPT_PREFIX + '|' + str(prepared.transaction_id) + '|')} + "
2332
+ f'Формат({prepared.generation}, "ЧГ=0; ЧДЦ=0; ЧН=0") + "|" + '
2333
+ f"{_bsl_string(prepared.manifest_sha256 + '|' + prepared.candidate_root_key + '|' + _wire_root_key(previous) + '|1|')} + "
2334
+ "Формат(ПодготовленныйКореньWorker.PrepareMs, "
2335
+ '"ЧГ=0; ЧДЦ=0; ЧН=0") + "|" + '
2336
+ "Формат(КонецСменыКорняWorker - НачалоСменыКорняWorker, "
2337
+ '"ЧГ=0; ЧДЦ=0; ЧН=0");',
2338
+ f" Контекст.Вставить({_bsl_string(outcome_key)}, Результат);",
2339
+ "КонецЕсли;",
2340
+ )
2341
+ )
2342
+ return "\n".join(lines)
2343
+
2344
+
2345
+ def discard_worker_root_instruction(prepared: WorkerPreparedRootReceipt) -> str:
2346
+ """Discard an exact prepared root without touching the active pointer."""
2347
+ if type(prepared) is not WorkerPreparedRootReceipt:
2348
+ raise TypeError("worker prepared root receipt is required")
2349
+ prepared_key = _prepared_root_context_key(prepared.transaction_id)
2350
+ outcome_key = _root_discard_outcome_context_key(prepared.transaction_id)
2351
+ return "\n".join(
2352
+ (
2353
+ "Результат = Неопределено;",
2354
+ f"Если Не Контекст.Свойство({_bsl_string(outcome_key)}, Результат) Тогда",
2355
+ " ПодготовленныйКореньWorker = Неопределено;",
2356
+ " Попытка",
2357
+ f" Если Не Контекст.Свойство({_bsl_string(prepared_key)}, "
2358
+ "ПодготовленныйКореньWorker) Тогда",
2359
+ ' ВызватьИсключение "prepared root is unavailable";',
2360
+ " КонецЕсли;",
2361
+ " Если ПодготовленныйКореньWorker.TransactionId <> "
2362
+ f"{_bsl_string(str(prepared.transaction_id))} Или "
2363
+ "ПодготовленныйКореньWorker.Generation <> "
2364
+ f"{prepared.generation} Или "
2365
+ "ПодготовленныйКореньWorker.ManifestSha256 <> "
2366
+ f"{_bsl_string(prepared.manifest_sha256)} Тогда",
2367
+ ' ВызватьИсключение "prepared root identity mismatch";',
2368
+ " КонецЕсли;",
2369
+ " Исключение",
2370
+ " ОшибкаУдаленияКорняWorker = ОписаниеОшибки();",
2371
+ ' ВызватьИсключение "onec-worker-root-discard-stage=guard" '
2372
+ "+ Символы.ПС + ОшибкаУдаленияКорняWorker;",
2373
+ " КонецПопытки;",
2374
+ f" Контекст.Удалить({_bsl_string(prepared_key)});",
2375
+ " Результат = "
2376
+ f"{_bsl_string(_ROOT_DISCARD_RECEIPT_PREFIX + '|' + str(prepared.transaction_id) + '|' + str(prepared.generation) + '|' + prepared.manifest_sha256 + '|' + prepared.candidate_root_key)};",
2377
+ f" Контекст.Вставить({_bsl_string(outcome_key)}, Результат);",
2378
+ "КонецЕсли;",
2379
+ )
2380
+ )
2381
+
2382
+
2383
+ def _wire_root_key(value: str) -> str:
2384
+ return "-" if value == "" else value
2385
+
2386
+
2387
+ def _unwire_root_key(value: str) -> str:
2388
+ return "" if value == "-" else value
2389
+
2390
+
2391
+ def _prepared_root_context_key(transaction_id: UUID) -> str:
2392
+ return f"__OnecWorkerPrepared_{transaction_id.hex}"
2393
+
2394
+
2395
+ def _root_swap_outcome_context_key(transaction_id: UUID) -> str:
2396
+ return f"__OnecWorkerSwapOutcome_{transaction_id.hex}"
2397
+
2398
+
2399
+ def _root_discard_outcome_context_key(transaction_id: UUID) -> str:
2400
+ return f"__OnecWorkerDiscardOutcome_{transaction_id.hex}"
2401
+
2402
+
2403
+ def _bsl_string(value: str) -> str:
2404
+ return '"' + value.replace('"', '""') + '"'
2405
+
2406
+
2407
+ @dataclass(frozen=True, slots=True, repr=False)
2408
+ class WorkerRegistrationRecord:
2409
+ target_incarnation_id: UUID
2410
+ canonical_module: str
2411
+ artifact_sha256: str
2412
+ registration_name: str
2413
+ exact_temp_storage_url: str
2414
+ locator_profile_id: str
2415
+ stage_transaction_id: UUID
2416
+ batch_digest: str
2417
+ platform_build: str
2418
+ receipt_admission: object
2419
+
2420
+ def __post_init__(self) -> None:
2421
+ if (
2422
+ type(self.target_incarnation_id) is not UUID
2423
+ or not _safe_identity(self.canonical_module)
2424
+ or self.canonical_module != self.canonical_module.casefold()
2425
+ or not _sha256_value(self.artifact_sha256)
2426
+ or not _bsl_identifier(self.registration_name)
2427
+ or _temp_storage_session_id(self.exact_temp_storage_url) is None
2428
+ or self.locator_profile_id != _WORKER_LOCATOR_PROFILE_ID
2429
+ or type(self.stage_transaction_id) is not UUID
2430
+ or not _sha256_value(self.batch_digest)
2431
+ or type(self.platform_build) is not str
2432
+ or not self.platform_build
2433
+ or self.receipt_admission is not _REGISTRATION_RECEIPT_ADMISSION
2434
+ ):
2435
+ raise ValueError("worker registration record is invalid")
2436
+
2437
+ def module_location(self, generated_line: int) -> ModuleLocation:
2438
+ if type(generated_line) is not int or generated_line < 1:
2439
+ raise ValueError("generated line must be positive")
2440
+ if (
2441
+ self.receipt_admission is not _REGISTRATION_RECEIPT_ADMISSION
2442
+ or self.platform_build != _WORKER_LOCATOR_PLATFORM_BUILD
2443
+ ):
2444
+ raise ProtocolError("Worker breakpoint locator is unsupported")
2445
+ return ModuleLocation(
2446
+ "ExtMDModule",
2447
+ self.exact_temp_storage_url,
2448
+ _WORKER_MODULE_OBJECT_ID,
2449
+ _WORKER_MODULE_PROPERTY_ID,
2450
+ generated_line,
2451
+ "",
2452
+ 0,
2453
+ )
2454
+
2455
+ def __repr__(self) -> str:
2456
+ return (
2457
+ "WorkerRegistrationRecord("
2458
+ f"target_incarnation_id={self.target_incarnation_id!r}, "
2459
+ f"canonical_module={self.canonical_module!r}, "
2460
+ f"artifact_sha256={self.artifact_sha256!r}, "
2461
+ f"registration_name={self.registration_name!r}, "
2462
+ "exact_temp_storage_url=<redacted>, "
2463
+ f"locator_profile_id={self.locator_profile_id!r}, "
2464
+ f"stage_transaction_id={self.stage_transaction_id!r}, "
2465
+ f"batch_digest={self.batch_digest!r}, "
2466
+ f"platform_build={self.platform_build!r}, "
2467
+ "receipt_admission=<redacted>)"
2468
+ )
2469
+
2470
+
2471
+ @dataclass(frozen=True, slots=True, repr=False)
2472
+ class WorkerModuleDebugView:
2473
+ source_units: tuple[SourceUnitRef, ...]
2474
+ canonical_module: str
2475
+ mapped_source: MappedSource
2476
+ visible_context: VisibleSourceContext | None
2477
+ artifact_sha256: str
2478
+ source_map_sha256: str
2479
+ registration: WorkerRegistrationRecord
2480
+ generated_line_index: LineIndex = field(init=False, repr=False, compare=False)
2481
+
2482
+ def __post_init__(self) -> None:
2483
+ if (
2484
+ type(self.source_units) is not tuple
2485
+ or not self.source_units
2486
+ or any(not isinstance(unit, SourceUnitRef) for unit in self.source_units)
2487
+ or not _safe_identity(self.canonical_module)
2488
+ or self.canonical_module != self.canonical_module.casefold()
2489
+ or not isinstance(self.mapped_source, MappedSource)
2490
+ or (
2491
+ self.visible_context is not None
2492
+ and not isinstance(self.visible_context, VisibleSourceContext)
2493
+ )
2494
+ or not _sha256_value(self.artifact_sha256)
2495
+ or not _sha256_value(self.source_map_sha256)
2496
+ or self.mapped_source.source_map_sha256 != self.source_map_sha256
2497
+ or type(self.registration) is not WorkerRegistrationRecord
2498
+ or self.registration.canonical_module != self.canonical_module
2499
+ or self.registration.artifact_sha256 != self.artifact_sha256
2500
+ ):
2501
+ raise ValueError("worker module debug view is invalid")
2502
+ if self.source_units != _worker_debug_source_units(self.mapped_source):
2503
+ raise ValueError("worker debug sources do not match the mapped source")
2504
+ object.__setattr__(
2505
+ self,
2506
+ "generated_line_index",
2507
+ LineIndex(self.mapped_source.text),
2508
+ )
2509
+
2510
+ @property
2511
+ def source_unit(self) -> SourceUnitRef:
2512
+ """Compatibility accessor for a module with one proven visible source."""
2513
+ if len(self.source_units) != 1:
2514
+ raise ProtocolError("Worker debug source identity is ambiguous")
2515
+ return self.source_units[0]
2516
+
2517
+ def __repr__(self) -> str:
2518
+ return (
2519
+ "WorkerModuleDebugView("
2520
+ f"source_units={self.source_units!r}, "
2521
+ f"canonical_module={self.canonical_module!r}, "
2522
+ f"artifact_sha256={self.artifact_sha256!r}, "
2523
+ f"source_map_sha256={self.source_map_sha256!r}, "
2524
+ "mapped_source=<redacted>, visible_context=<redacted>, "
2525
+ "registration=<redacted>)"
2526
+ )
2527
+
2528
+
2529
+ @dataclass(frozen=True, slots=True, repr=False)
2530
+ class WorkerGenerationDebugView:
2531
+ handle: WorkerGenerationHandle
2532
+ manifest: WorkerUniverseManifest
2533
+ modules: tuple[WorkerModuleDebugView, ...]
2534
+
2535
+ def __post_init__(self) -> None:
2536
+ if (
2537
+ not isinstance(self.handle, WorkerGenerationHandle)
2538
+ or not isinstance(self.manifest, WorkerUniverseManifest)
2539
+ or self.handle.generation != self.manifest.generation
2540
+ or self.handle.manifest_sha256 != self.manifest.sha256
2541
+ or type(self.modules) is not tuple
2542
+ or len(self.modules) != len(self.manifest.modules)
2543
+ or any(type(module) is not WorkerModuleDebugView for module in self.modules)
2544
+ or any(
2545
+ module.canonical_module != descriptor.logical_name.casefold()
2546
+ or module.artifact_sha256 != descriptor.artifact_sha256
2547
+ or module.registration.registration_name
2548
+ != descriptor.registration_name
2549
+ for module, descriptor in zip(
2550
+ self.modules,
2551
+ self.manifest.modules,
2552
+ strict=True,
2553
+ )
2554
+ )
2555
+ ):
2556
+ raise ValueError("worker generation debug view is invalid")
2557
+
2558
+ def __repr__(self) -> str:
2559
+ return (
2560
+ "WorkerGenerationDebugView("
2561
+ f"handle={self.handle!r}, manifest_sha256={self.manifest.sha256!r}, "
2562
+ f"modules={len(self.modules)}, provenance=<redacted>)"
2563
+ )
2564
+
2565
+
2566
+ def _worker_debug_source_units(
2567
+ mapped_source: MappedSource,
2568
+ ) -> tuple[SourceUnitRef, ...]:
2569
+ """Enumerate exact identities from an admitted flattened map, without aliases."""
2570
+ compact_spans = getattr(mapped_source.source_map, "compact_visible_spans", None)
2571
+ references = (
2572
+ tuple(reference for _role, reference, _span in compact_spans())
2573
+ if callable(compact_spans)
2574
+ else tuple(
2575
+ reference
2576
+ for segment in mapped_source.source_map.segments
2577
+ for reference in (segment.origin_ref, segment.anchor_ref)
2578
+ )
2579
+ )
2580
+ units: dict[tuple[object, str, int], SourceUnitRef] = {}
2581
+ for reference in references:
2582
+ if reference is None:
2583
+ continue
2584
+ if not isinstance(reference, SourceUnitRef):
2585
+ raise ProtocolError("Worker debug source map is not flattened")
2586
+ key = (reference.kind, reference.unit_id, reference.revision)
2587
+ existing = units.get(key)
2588
+ if existing is not None and existing.source_sha256 != reference.source_sha256:
2589
+ raise ProtocolError("Worker debug sources have conflicting source identities")
2590
+ units[key] = reference
2591
+ return tuple(units.values())
2592
+
2593
+
2594
+ class ServerWorkerUniverseRegistry:
2595
+ """Target-side counterpart for a host Worker universe registry."""
2596
+
2597
+ def __init__(
2598
+ self,
2599
+ host: WorkerUniverseRegistry,
2600
+ instruction_executor: Callable[[str], object],
2601
+ *,
2602
+ target_incarnation_id: UUID | None = None,
2603
+ platform_build: str = _WORKER_LOCATOR_PLATFORM_BUILD,
2604
+ ) -> None:
2605
+ if not isinstance(host, WorkerUniverseRegistry):
2606
+ raise TypeError("host Worker universe registry is required")
2607
+ if not callable(instruction_executor):
2608
+ raise TypeError("worker universe instruction executor must be callable")
2609
+ if target_incarnation_id is not None and type(target_incarnation_id) is not UUID:
2610
+ raise TypeError("target incarnation ID must be a UUID")
2611
+ if type(platform_build) is not str or not platform_build:
2612
+ raise TypeError("platform build must be a non-empty string")
2613
+ self._host = host
2614
+ self._instruction_executor = instruction_executor
2615
+ self._target_incarnation_id = target_incarnation_id or uuid4()
2616
+ self._platform_build = platform_build
2617
+ self._lock = RLock()
2618
+ # Exact 8.3.27 has no supported external-processing Disconnect API.
2619
+ # Keep every successful content identity for this target session,
2620
+ # including identities whose host generation refs reached zero.
2621
+ self._registrations: dict[str, WorkerRegistrationRecord] = {}
2622
+ self._candidate_registrations: dict[int, set[str]] = {}
2623
+ self._instantiated_registrations: set[str] = set()
2624
+ self._compile_failed_registrations: set[str] = set()
2625
+ self._prepared_roots: dict[
2626
+ UUID,
2627
+ tuple[WorkerUniverseCandidate, WorkerPreparedRootReceipt],
2628
+ ] = {}
2629
+ self._storage_session_id: str | None = None
2630
+ self._orphan_urls: set[str] = set()
2631
+ self._broken = False
2632
+
2633
+ def prepare(
2634
+ self,
2635
+ artifacts: tuple[WorkerModuleArtifact, ...],
2636
+ ) -> WorkerUniverseCandidate:
2637
+ with self._lock:
2638
+ if self._broken:
2639
+ raise ProtocolError("Worker target registry is broken")
2640
+ candidate = self._host.prepare(artifacts)
2641
+ try:
2642
+ activation = self._sealed_activation(candidate)
2643
+ except BaseException:
2644
+ try:
2645
+ self._host._discard_exact_pending_without_fence(candidate)
2646
+ except BaseException:
2647
+ self._break_pending(candidate)
2648
+ raise _promotion_unknown(candidate) from None
2649
+ self._candidate_registrations.pop(
2650
+ candidate.handle.generation,
2651
+ None,
2652
+ )
2653
+ raise
2654
+ try:
2655
+ self._stage_candidate(
2656
+ candidate,
2657
+ batches=self._stage_batches_from_activation(activation),
2658
+ )
2659
+ debug_view = self._build_candidate_debug_view(activation)
2660
+ if debug_view is not None:
2661
+ self._host._bind_candidate_debug_view(candidate, debug_view)
2662
+ except BaseException:
2663
+ if self._host.state is WorkerUniverseState.PREPARING:
2664
+ owned = self._candidate_registrations.get(
2665
+ candidate.handle.generation,
2666
+ set(),
2667
+ )
2668
+ if owned:
2669
+ self._break_pending(candidate)
2670
+ raise _promotion_unknown(candidate) from None
2671
+ try:
2672
+ self._host.discard(candidate)
2673
+ except BaseException:
2674
+ self._break_pending(candidate)
2675
+ raise _promotion_unknown(candidate) from None
2676
+ self._candidate_registrations.pop(
2677
+ candidate.handle.generation,
2678
+ None,
2679
+ )
2680
+ raise
2681
+ return candidate
2682
+
2683
+ def stages(
2684
+ self,
2685
+ candidate: WorkerUniverseCandidate,
2686
+ ) -> tuple[WorkerArtifactStage, ...]:
2687
+ with self._lock:
2688
+ return self._stages(candidate)
2689
+
2690
+ def _stages(
2691
+ self,
2692
+ candidate: WorkerUniverseCandidate,
2693
+ ) -> tuple[WorkerArtifactStage, ...]:
2694
+ return tuple(
2695
+ WorkerArtifactStage(
2696
+ entry.logical_name,
2697
+ entry.artifact_sha256,
2698
+ entry.registration_name,
2699
+ "connect",
2700
+ stage_worker_module_instruction(
2701
+ entry.artifact_bytes,
2702
+ logical_name=entry.logical_name,
2703
+ artifact_sha256=entry.artifact_sha256,
2704
+ registration_name=entry.registration_name,
2705
+ ),
2706
+ )
2707
+ for entry in self._unstaged_sealed_entries(candidate)
2708
+ )
2709
+
2710
+ def _unstaged_sealed_entries(
2711
+ self,
2712
+ candidate: WorkerUniverseCandidate,
2713
+ *,
2714
+ profiler: PhaseRecorder | None = None,
2715
+ ) -> tuple[_SealedWorkerStageEntry, ...]:
2716
+ return self._unstaged_sealed_entries_from_activation(
2717
+ self._sealed_activation(candidate, profiler=profiler)
2718
+ )
2719
+
2720
+ def _sealed_activation(
2721
+ self,
2722
+ candidate: WorkerUniverseCandidate,
2723
+ *,
2724
+ profiler: PhaseRecorder | None = None,
2725
+ ) -> _SealedWorkerActivation:
2726
+ if not isinstance(candidate, WorkerUniverseCandidate):
2727
+ raise TypeError("worker universe candidate is required")
2728
+ if self._broken:
2729
+ raise ProtocolError("Worker target registry is broken")
2730
+ return (
2731
+ self._host._require_sealed_activation(candidate)
2732
+ if profiler is None
2733
+ else profiler.measure(
2734
+ "artifact_stage_sealed_validation",
2735
+ lambda: self._host._require_sealed_activation(candidate),
2736
+ item_count=lambda result: len(result.stage_entries),
2737
+ )
2738
+ )
2739
+
2740
+ def _unstaged_sealed_entries_from_activation(
2741
+ self,
2742
+ activation: _SealedWorkerActivation,
2743
+ ) -> tuple[_SealedWorkerStageEntry, ...]:
2744
+ new_artifact_ids = {
2745
+ id(artifact) for artifact in activation.candidate.new_artifacts
2746
+ }
2747
+ unstaged: list[_SealedWorkerStageEntry] = []
2748
+ for entry in activation.stage_entries:
2749
+ expected_owner = (
2750
+ entry.logical_name.casefold(),
2751
+ entry.artifact_sha256,
2752
+ )
2753
+ owner = self._registrations.get(entry.registration_name)
2754
+ if owner is not None:
2755
+ if (
2756
+ type(owner) is not WorkerRegistrationRecord
2757
+ or owner.target_incarnation_id != self._target_incarnation_id
2758
+ or (owner.canonical_module, owner.artifact_sha256)
2759
+ != expected_owner
2760
+ ):
2761
+ raise ProtocolError(
2762
+ "Worker target registration ownership is invalid"
2763
+ )
2764
+ continue
2765
+ if id(entry.descriptor) not in new_artifact_ids:
2766
+ raise ProtocolError("Worker target registration ownership is missing")
2767
+ unstaged.append(entry)
2768
+ return tuple(unstaged)
2769
+
2770
+ def _stage_batches(
2771
+ self,
2772
+ candidate: WorkerUniverseCandidate,
2773
+ *,
2774
+ profiler: PhaseRecorder | None = None,
2775
+ ) -> tuple[WorkerStageBatch, ...]:
2776
+ return self._stage_batches_from_activation(
2777
+ self._sealed_activation(candidate, profiler=profiler)
2778
+ )
2779
+
2780
+ def _stage_batches_from_activation(
2781
+ self,
2782
+ activation: _SealedWorkerActivation,
2783
+ ) -> tuple[WorkerStageBatch, ...]:
2784
+ return build_worker_stage_batches(
2785
+ tuple(
2786
+ WorkerStageEntry(
2787
+ entry.logical_name,
2788
+ entry.artifact_sha256,
2789
+ entry.registration_name,
2790
+ entry.artifact_bytes,
2791
+ )
2792
+ for entry in self._unstaged_sealed_entries_from_activation(
2793
+ activation,
2794
+ )
2795
+ )
2796
+ )
2797
+
2798
+ def _build_candidate_debug_view(
2799
+ self,
2800
+ activation: _SealedWorkerActivation,
2801
+ ) -> WorkerGenerationDebugView | None:
2802
+ modules: list[WorkerModuleDebugView] = []
2803
+ for entry, descriptor in zip(
2804
+ activation.stage_entries,
2805
+ activation.candidate.manifest.modules,
2806
+ strict=True,
2807
+ ):
2808
+ registration = self._registration_view(entry.registration_name)
2809
+ snapshot = entry.admitted_snapshot
2810
+ visible_snapshot = snapshot.visible_context_snapshot
2811
+ source_units = _worker_debug_source_units(snapshot.mapped_source)
2812
+ if not source_units:
2813
+ return None
2814
+ modules.append(
2815
+ WorkerModuleDebugView(
2816
+ source_units,
2817
+ descriptor.logical_name.casefold(),
2818
+ snapshot.mapped_source,
2819
+ None if visible_snapshot is None else visible_snapshot.context,
2820
+ descriptor.artifact_sha256,
2821
+ entry.descriptor.source_map_sha256,
2822
+ registration,
2823
+ )
2824
+ )
2825
+ return WorkerGenerationDebugView(
2826
+ activation.candidate.handle,
2827
+ activation.candidate.manifest,
2828
+ tuple(modules),
2829
+ )
2830
+
2831
+ def prepare_root(
2832
+ self,
2833
+ candidate: WorkerUniverseCandidate,
2834
+ *,
2835
+ transaction_id: UUID,
2836
+ profiler: PhaseRecorder | None = None,
2837
+ ) -> WorkerPreparedRootReceipt:
2838
+ if type(transaction_id) is not UUID:
2839
+ raise TypeError("worker root transaction id must be a UUID")
2840
+ with self._lock:
2841
+ if self._broken:
2842
+ raise ProtocolError("Worker target registry is broken")
2843
+ if transaction_id in self._prepared_roots:
2844
+ raise ProtocolError("Worker root transaction was already used")
2845
+ activation = self._sealed_activation(candidate, profiler=profiler)
2846
+ try:
2847
+ batches = self._stage_batches_from_activation(activation)
2848
+ except BaseException as error:
2849
+ self._abort_pre_swap(candidate, error)
2850
+ staged_item_count = sum(len(batch.entries) for batch in batches)
2851
+ if profiler is not None and staged_item_count:
2852
+ profiler.measure(
2853
+ "artifact_staging",
2854
+ lambda: self._stage_candidate(
2855
+ candidate,
2856
+ batches=batches,
2857
+ profiler=profiler,
2858
+ ),
2859
+ item_count=lambda _result: staged_item_count,
2860
+ )
2861
+ else:
2862
+ self._stage_candidate(
2863
+ candidate,
2864
+ batches=batches,
2865
+ profiler=profiler,
2866
+ )
2867
+ if activation.debug_view is None:
2868
+ try:
2869
+ debug_view = self._build_candidate_debug_view(activation)
2870
+ if debug_view is not None:
2871
+ self._host._bind_candidate_debug_view(candidate, debug_view)
2872
+ except BaseException as error:
2873
+ self._abort_pre_swap(candidate, error)
2874
+ previous_root_key = self._host.active_root_key or ""
2875
+ try:
2876
+ result = self._instruction_executor(
2877
+ prepare_worker_root_instruction(
2878
+ candidate,
2879
+ transaction_id,
2880
+ previous_root_key,
2881
+ )
2882
+ )
2883
+ except BaseException as error:
2884
+ if _worker_promotion_failure_phase(error) is not None:
2885
+ self._record_uninstantiable_candidate_module(candidate, error)
2886
+ self._abort_pre_swap(candidate, error)
2887
+ self._break_pending(candidate)
2888
+ raise _promotion_unknown(candidate) from None
2889
+ try:
2890
+ receipt = _worker_prepared_root_receipt(result)
2891
+ if (
2892
+ receipt.transaction_id != transaction_id
2893
+ or receipt.generation != candidate.handle.generation
2894
+ or receipt.manifest_sha256 != candidate.manifest.sha256
2895
+ or receipt.candidate_root_key
2896
+ != _generation_root_key(candidate.handle.generation)
2897
+ or receipt.previous_root_key != previous_root_key
2898
+ ):
2899
+ raise ProtocolError("Worker prepared root receipt is stale")
2900
+ except BaseException:
2901
+ self._break_pending(candidate)
2902
+ raise _promotion_unknown(candidate) from None
2903
+ self._prepared_roots[transaction_id] = (candidate, receipt)
2904
+ instantiated = {
2905
+ module.registration_name for module in candidate.manifest.modules
2906
+ }
2907
+ self._instantiated_registrations.update(instantiated)
2908
+ self._compile_failed_registrations.difference_update(instantiated)
2909
+ if profiler is not None:
2910
+ profiler.record_duration(
2911
+ "generation_create_wire_probe",
2912
+ wall_ns=receipt.generation_create_wire_probe_ms * 1_000_000,
2913
+ item_count=1,
2914
+ )
2915
+ return receipt
2916
+
2917
+ def swap_root(
2918
+ self,
2919
+ prepared: WorkerPreparedRootReceipt,
2920
+ *,
2921
+ profiler: PhaseRecorder | None = None,
2922
+ ) -> WorkerGenerationHandle:
2923
+ with self._lock:
2924
+ if self._broken:
2925
+ raise ProtocolError("Worker target registry is broken")
2926
+ entry = self._prepared_roots.get(
2927
+ getattr(prepared, "transaction_id", None)
2928
+ )
2929
+ if entry is None or entry[1] is not prepared:
2930
+ raise ProtocolError("Worker prepared root is stale or forged")
2931
+ candidate, _exact_receipt = entry
2932
+ try:
2933
+ result = self._instruction_executor(
2934
+ swap_worker_root_instruction(prepared)
2935
+ )
2936
+ except BaseException as error:
2937
+ if _worker_root_swap_failure_phase(error) is not None:
2938
+ raise
2939
+ self._break_pending(candidate)
2940
+ raise _promotion_unknown(candidate) from None
2941
+ try:
2942
+ receipt = _worker_promotion_receipt(result)
2943
+ if (
2944
+ receipt.transaction_id != prepared.transaction_id
2945
+ or receipt.generation != prepared.generation
2946
+ or receipt.manifest_sha256 != prepared.manifest_sha256
2947
+ or receipt.root_key != prepared.candidate_root_key
2948
+ or receipt.previous_root_key != prepared.previous_root_key
2949
+ or receipt.generation_create_wire_probe_ms
2950
+ != prepared.generation_create_wire_probe_ms
2951
+ ):
2952
+ raise ProtocolError("Worker root swap receipt is stale")
2953
+ confirmation = self._host.confirm(candidate, receipt)
2954
+ except BaseException:
2955
+ self._break_pending(candidate)
2956
+ raise _promotion_unknown(candidate) from None
2957
+ if profiler is not None:
2958
+ profiler.record_duration(
2959
+ "root_swap",
2960
+ wall_ns=receipt.root_swap_ms * 1_000_000,
2961
+ item_count=1,
2962
+ )
2963
+ del self._prepared_roots[prepared.transaction_id]
2964
+ self._candidate_registrations.pop(candidate.handle.generation, None)
2965
+ return confirmation.handle
2966
+
2967
+ def discard_root(self, prepared: WorkerPreparedRootReceipt) -> None:
2968
+ with self._lock:
2969
+ if self._broken:
2970
+ raise ProtocolError("Worker target registry is broken")
2971
+ entry = self._prepared_roots.get(
2972
+ getattr(prepared, "transaction_id", None)
2973
+ )
2974
+ if entry is None or entry[1] is not prepared:
2975
+ raise ProtocolError("Worker prepared root is stale or forged")
2976
+ candidate, _exact_receipt = entry
2977
+ try:
2978
+ result = self._instruction_executor(
2979
+ discard_worker_root_instruction(prepared)
2980
+ )
2981
+ _worker_root_discard_receipt(result, prepared)
2982
+ self._host.discard(candidate)
2983
+ except BaseException:
2984
+ self._break_pending(candidate)
2985
+ raise _promotion_unknown(candidate) from None
2986
+ del self._prepared_roots[prepared.transaction_id]
2987
+ self._candidate_registrations.pop(candidate.handle.generation, None)
2988
+
2989
+ def quarantine_root(self, prepared: WorkerPreparedRootReceipt) -> None:
2990
+ """Retain both possible roots after another participant becomes unknown."""
2991
+ with self._lock:
2992
+ if self._broken:
2993
+ return
2994
+ entry = self._prepared_roots.get(
2995
+ getattr(prepared, "transaction_id", None)
2996
+ )
2997
+ if entry is None or entry[1] is not prepared:
2998
+ raise ProtocolError("Worker prepared root is stale or forged")
2999
+ candidate, _exact_receipt = entry
3000
+ self._break_pending(candidate)
3001
+
3002
+ def promote(
3003
+ self,
3004
+ candidate: WorkerUniverseCandidate,
3005
+ *,
3006
+ profiler: PhaseRecorder | None = None,
3007
+ ) -> WorkerGenerationHandle:
3008
+ prepared = self.prepare_root(
3009
+ candidate,
3010
+ transaction_id=uuid4(),
3011
+ profiler=profiler,
3012
+ )
3013
+ try:
3014
+ return self.swap_root(prepared, profiler=profiler)
3015
+ except BaseException as error:
3016
+ if _worker_root_swap_failure_phase(error) is not None:
3017
+ self.discard_root(prepared)
3018
+ raise
3019
+
3020
+ def privacy_registration_snapshot(self) -> tuple[str, ...]:
3021
+ """Return every exact registration that can still own a Worker object."""
3022
+
3023
+ with self._lock:
3024
+ registrations = tuple(
3025
+ sorted(
3026
+ self._registrations.keys() - self._compile_failed_registrations,
3027
+ key=str.casefold,
3028
+ )
3029
+ )
3030
+ if (
3031
+ self._broken
3032
+ or self._host.state is not WorkerUniverseState.READY
3033
+ or self._host.active_handle is None
3034
+ or not registrations
3035
+ or len({name.casefold() for name in registrations})
3036
+ != len(registrations)
3037
+ ):
3038
+ raise ProtocolError(
3039
+ "Worker privacy registration snapshot is unavailable"
3040
+ )
3041
+ for name in registrations:
3042
+ owner = self._registrations.get(name)
3043
+ if (
3044
+ not _bsl_identifier(name)
3045
+ or type(owner) is not WorkerRegistrationRecord
3046
+ or owner.registration_name != name
3047
+ or owner.target_incarnation_id != self._target_incarnation_id
3048
+ ):
3049
+ raise ProtocolError(
3050
+ "Worker privacy registration snapshot is invalid"
3051
+ )
3052
+ return registrations
3053
+
3054
+ def _record_uninstantiable_candidate_module(
3055
+ self,
3056
+ candidate: WorkerUniverseCandidate,
3057
+ error: BaseException,
3058
+ ) -> None:
3059
+ """Exclude a proven compile-invalid registration from type probes."""
3060
+ failure = worker_artifact_stage_failure(error)
3061
+ if (
3062
+ failure is None
3063
+ or failure.phase != "create"
3064
+ or not parse_platform_diagnostic(
3065
+ _worker_reload_platform_message(error)
3066
+ ).has_compilation_marker
3067
+ ):
3068
+ return
3069
+ matches = tuple(
3070
+ module.registration_name for module in candidate.manifest.modules
3071
+ if module.artifact_sha256 == failure.artifact_sha256
3072
+ )
3073
+ if len(matches) == 1 and matches[0] not in self._instantiated_registrations:
3074
+ self._compile_failed_registrations.add(matches[0])
3075
+
3076
+ def _registration_view(self, registration_name: str) -> WorkerRegistrationRecord:
3077
+ with self._lock:
3078
+ if not _bsl_identifier(registration_name):
3079
+ raise ProtocolError("Worker registration identity is invalid")
3080
+ record = self._registrations.get(registration_name)
3081
+ if (
3082
+ type(record) is not WorkerRegistrationRecord
3083
+ or record.target_incarnation_id != self._target_incarnation_id
3084
+ or record.registration_name != registration_name
3085
+ or record.receipt_admission is not _REGISTRATION_RECEIPT_ADMISSION
3086
+ ):
3087
+ raise ProtocolError("Worker registration identity is unavailable")
3088
+ return record
3089
+
3090
+ def release(self, handle: WorkerGenerationHandle) -> None:
3091
+ with self._lock:
3092
+ if self._broken:
3093
+ raise ProtocolError("Worker target registry is broken")
3094
+ self._host.release_generation(handle)
3095
+
3096
+ def release_pin(self, pin: OperationGenerationPin) -> None:
3097
+ with self._lock:
3098
+ if self._broken:
3099
+ raise ProtocolError("Worker target registry is broken")
3100
+ self._host.release_pin(pin)
3101
+
3102
+ def preview_release(
3103
+ self,
3104
+ release: WorkerGenerationHandle | OperationGenerationPin,
3105
+ ) -> WorkerLifecycleReleasePlan:
3106
+ with self._lock:
3107
+ if self._broken:
3108
+ raise ProtocolError("Worker target registry is broken")
3109
+ return self._host._preview_release(release)
3110
+
3111
+ def commit_release(self, plan: WorkerLifecycleReleasePlan) -> tuple[str, ...]:
3112
+ with self._lock:
3113
+ if self._broken:
3114
+ raise ProtocolError("Worker target registry is broken")
3115
+ return self._host._commit_release(plan)
3116
+
3117
+ def teardown(self) -> None:
3118
+ with self._lock:
3119
+ self._host.teardown()
3120
+ # RuntimeSession owns target-process termination; never dispatch an
3121
+ # unsupported cleanup instruction while forgetting the host ledger.
3122
+ self._registrations.clear()
3123
+ self._candidate_registrations.clear()
3124
+ self._instantiated_registrations.clear()
3125
+ self._compile_failed_registrations.clear()
3126
+ self._prepared_roots.clear()
3127
+ self._orphan_urls.clear()
3128
+ self._storage_session_id = None
3129
+
3130
+ def abandon_target(self) -> None:
3131
+ """Forget target registrations when the target cannot execute cleanup.
3132
+
3133
+ The caller owns terminating the target transport/process. No target
3134
+ instruction is attempted here: host leases and handles are closed, all
3135
+ target identities are forgotten, and this registry is permanently
3136
+ unusable.
3137
+ """
3138
+ with self._lock:
3139
+ self._host.teardown()
3140
+ self._registrations.clear()
3141
+ self._candidate_registrations.clear()
3142
+ self._instantiated_registrations.clear()
3143
+ self._compile_failed_registrations.clear()
3144
+ self._prepared_roots.clear()
3145
+ self._orphan_urls.clear()
3146
+ self._storage_session_id = None
3147
+ self._broken = True
3148
+
3149
+ def _stage_candidate(
3150
+ self,
3151
+ candidate: WorkerUniverseCandidate,
3152
+ *,
3153
+ batches: tuple[WorkerStageBatch, ...],
3154
+ profiler: PhaseRecorder | None = None,
3155
+ ) -> None:
3156
+ if not batches:
3157
+ return
3158
+ generation = candidate.handle.generation
3159
+ possible_registrations = {
3160
+ entry.registration_name
3161
+ for batch in batches
3162
+ for entry in batch.entries
3163
+ }
3164
+ batch_count = len(batches)
3165
+ transaction_id = uuid4()
3166
+ for batch in batches:
3167
+ def stage_batch() -> None:
3168
+ try:
3169
+ instruction = (
3170
+ stage_worker_batch_instruction(
3171
+ batch,
3172
+ batch_count=batch_count,
3173
+ transaction_id=transaction_id,
3174
+ )
3175
+ if profiler is None
3176
+ else profiler.measure(
3177
+ "artifact_stage_base64",
3178
+ lambda: stage_worker_batch_instruction(
3179
+ batch,
3180
+ batch_count=batch_count,
3181
+ transaction_id=transaction_id,
3182
+ ),
3183
+ item_count=lambda _result: len(batch.entries),
3184
+ )
3185
+ )
3186
+ except BaseException as error:
3187
+ self._abort_pre_swap(candidate, error)
3188
+ try:
3189
+ result = (
3190
+ self._instruction_executor(instruction)
3191
+ if profiler is None
3192
+ else profiler.measure(
3193
+ "artifact_stage_executor",
3194
+ lambda: self._instruction_executor(instruction),
3195
+ item_count=lambda _result: len(batch.entries),
3196
+ )
3197
+ )
3198
+ except BaseException:
3199
+ owned = self._candidate_registrations.setdefault(
3200
+ generation,
3201
+ set(),
3202
+ )
3203
+ owned.update(possible_registrations)
3204
+ self._break_pending(candidate)
3205
+ raise _promotion_unknown(candidate) from None
3206
+ try:
3207
+ outcome = parse_worker_stage_batch_outcome(
3208
+ result,
3209
+ batch,
3210
+ batch_count=batch_count,
3211
+ transaction_id=transaction_id,
3212
+ )
3213
+ except BaseException:
3214
+ owned = self._candidate_registrations.setdefault(
3215
+ generation,
3216
+ set(),
3217
+ )
3218
+ owned.update(possible_registrations)
3219
+ self._break_pending(candidate)
3220
+ raise _promotion_unknown(candidate) from None
3221
+ try:
3222
+ self._record_stage_entries(
3223
+ batch,
3224
+ outcome,
3225
+ self._candidate_registrations.setdefault(
3226
+ generation,
3227
+ set(),
3228
+ ),
3229
+ )
3230
+ except BaseException:
3231
+ owned = self._candidate_registrations.setdefault(
3232
+ generation,
3233
+ set(),
3234
+ )
3235
+ owned.update(possible_registrations)
3236
+ self._break_pending(candidate)
3237
+ raise _promotion_unknown(candidate) from None
3238
+ if (
3239
+ outcome.failure is not None
3240
+ and outcome.failure.orphan_url is not None
3241
+ ):
3242
+ self._orphan_urls.add(outcome.failure.orphan_url)
3243
+ if outcome.failure is None:
3244
+ return
3245
+ if outcome.failure.outcome == "known_pre_swap":
3246
+ self._abort_pre_swap(
3247
+ candidate,
3248
+ self._stage_failure_error(candidate, batch, outcome),
3249
+ )
3250
+ owned = self._candidate_registrations.setdefault(
3251
+ generation,
3252
+ set(),
3253
+ )
3254
+ owned.update(possible_registrations)
3255
+ self._break_pending(candidate)
3256
+ raise _promotion_unknown(candidate) from None
3257
+
3258
+ if profiler is None:
3259
+ stage_batch()
3260
+ else:
3261
+ profiler.measure(
3262
+ "artifact_stage_batch",
3263
+ stage_batch,
3264
+ item_count=lambda _result: len(batch.entries),
3265
+ )
3266
+
3267
+ def _stage_failure_error(
3268
+ self,
3269
+ candidate: WorkerUniverseCandidate,
3270
+ batch: WorkerStageBatch,
3271
+ outcome: WorkerStageBatchOutcome,
3272
+ ) -> BslExecutionError:
3273
+ failure = outcome.failure
3274
+ if failure is None:
3275
+ raise ProtocolError("Worker stage failure is missing")
3276
+ entry = batch.entries[failure.item_index]
3277
+ artifacts = tuple(
3278
+ artifact
3279
+ for artifact in self._host._candidate_diagnostics(candidate)
3280
+ if artifact.registration_name == entry.registration_name
3281
+ and artifact.artifact_sha256 == entry.artifact_sha256
3282
+ )
3283
+ diagnostic = remap_worker_stage_diagnostic(
3284
+ parse_platform_diagnostic(failure.diagnostic),
3285
+ artifact_sha256=entry.artifact_sha256,
3286
+ phase=failure.phase,
3287
+ candidate_manifest_sha256=candidate.manifest.sha256,
3288
+ candidate_artifacts=artifacts,
3289
+ )
3290
+ return BslExecutionError(failure.diagnostic, diagnostic=diagnostic)
3291
+
3292
+ def _record_stage_entries(
3293
+ self,
3294
+ batch: WorkerStageBatch,
3295
+ outcome: WorkerStageBatchOutcome,
3296
+ owned: set[str],
3297
+ ) -> None:
3298
+ for entry, receipt in zip(
3299
+ batch.entries,
3300
+ outcome.connected,
3301
+ strict=False,
3302
+ ):
3303
+ self._record_stage_entry(
3304
+ entry,
3305
+ receipt,
3306
+ transaction_id=outcome.transaction_id,
3307
+ batch_digest=outcome.batch_digest,
3308
+ )
3309
+ owned.add(entry.registration_name)
3310
+
3311
+ def _record_stage_entry(
3312
+ self,
3313
+ entry: WorkerStageEntry,
3314
+ receipt: WorkerStageRegistrationReceipt,
3315
+ *,
3316
+ transaction_id: UUID,
3317
+ batch_digest: str,
3318
+ ) -> None:
3319
+ storage_session_id = _temp_storage_session_id(receipt.temp_storage_url)
3320
+ if storage_session_id is None:
3321
+ raise ProtocolError("Worker registration URL is invalid")
3322
+ if self._storage_session_id is None:
3323
+ self._storage_session_id = storage_session_id
3324
+ elif self._storage_session_id != storage_session_id:
3325
+ raise ProtocolError("Worker registration storage session is inconsistent")
3326
+ record = WorkerRegistrationRecord(
3327
+ self._target_incarnation_id,
3328
+ entry.logical_name.casefold(),
3329
+ entry.artifact_sha256,
3330
+ entry.registration_name,
3331
+ receipt.temp_storage_url,
3332
+ _WORKER_LOCATOR_PROFILE_ID,
3333
+ transaction_id,
3334
+ batch_digest,
3335
+ self._platform_build,
3336
+ _REGISTRATION_RECEIPT_ADMISSION,
3337
+ )
3338
+ previous = self._registrations.get(entry.registration_name)
3339
+ if previous is not None and previous != record:
3340
+ raise ProtocolError("Worker target registration ownership is invalid")
3341
+ self._registrations[entry.registration_name] = record
3342
+
3343
+ def _abort_pre_swap(
3344
+ self,
3345
+ candidate: WorkerUniverseCandidate,
3346
+ error: BaseException,
3347
+ ) -> None:
3348
+ # Successful staging remains reusable and privacy-visible for the
3349
+ # target session even though this generation candidate is discarded.
3350
+ self._candidate_registrations.pop(candidate.handle.generation, None)
3351
+ try:
3352
+ self._host.discard(candidate)
3353
+ except BaseException:
3354
+ self._break_pending(candidate)
3355
+ raise _promotion_unknown(candidate) from None
3356
+ raise error
3357
+
3358
+ def _break_pending(self, candidate: WorkerUniverseCandidate) -> None:
3359
+ self._broken = True
3360
+ try:
3361
+ self._host.mark_broken(candidate)
3362
+ except ProtocolError:
3363
+ if self._host.state is not WorkerUniverseState.BROKEN:
3364
+ self._host.mark_broken()
3365
+
3366
+ def _worker_prepared_root_receipt(value: object) -> WorkerPreparedRootReceipt:
3367
+ if type(value) is WorkerPreparedRootReceipt:
3368
+ return value
3369
+ if type(value) is not str:
3370
+ raise ProtocolError("Worker prepared root receipt is invalid")
3371
+ fields = value.split("|")
3372
+ if (
3373
+ len(fields) != 7
3374
+ or any(not field for field in fields)
3375
+ or fields[0] != _PREPARED_ROOT_RECEIPT_PREFIX
3376
+ or fullmatch(r"(?:0|[1-9][0-9]*)", fields[2]) is None
3377
+ or fullmatch(r"(?:0|[1-9][0-9]*)", fields[6]) is None
3378
+ ):
3379
+ raise ProtocolError("Worker prepared root receipt is invalid")
3380
+ try:
3381
+ transaction_id = UUID(fields[1])
3382
+ if str(transaction_id) != fields[1]:
3383
+ raise ValueError
3384
+ return WorkerPreparedRootReceipt(
3385
+ transaction_id,
3386
+ int(fields[2]),
3387
+ fields[3],
3388
+ fields[4],
3389
+ _unwire_root_key(fields[5]),
3390
+ int(fields[6]),
3391
+ )
3392
+ except (TypeError, ValueError):
3393
+ raise ProtocolError("Worker prepared root receipt is invalid") from None
3394
+
3395
+
3396
+ def _worker_promotion_receipt(value: object) -> WorkerPromotionReceipt:
3397
+ if isinstance(value, WorkerPromotionReceipt):
3398
+ return value
3399
+ if type(value) is not str:
3400
+ raise ProtocolError("Worker promotion receipt is invalid")
3401
+ fields = value.split("|")
3402
+ if (
3403
+ len(fields) != 9
3404
+ or any(not field for field in fields)
3405
+ or fields[0] != _ROOT_SWAP_RECEIPT_PREFIX
3406
+ or fields[6] != "1"
3407
+ or fullmatch(r"(?:0|[1-9][0-9]*)", fields[2]) is None
3408
+ or fullmatch(r"(?:0|[1-9][0-9]*)", fields[7]) is None
3409
+ or fullmatch(r"(?:0|[1-9][0-9]*)", fields[8]) is None
3410
+ ):
3411
+ raise ProtocolError("Worker promotion receipt is invalid")
3412
+ try:
3413
+ transaction_id = UUID(fields[1])
3414
+ if str(transaction_id) != fields[1]:
3415
+ raise ValueError
3416
+ return WorkerPromotionReceipt(
3417
+ transaction_id,
3418
+ int(fields[2]),
3419
+ fields[3],
3420
+ fields[4],
3421
+ _unwire_root_key(fields[5]),
3422
+ True,
3423
+ int(fields[7]),
3424
+ int(fields[8]),
3425
+ )
3426
+ except (TypeError, ValueError):
3427
+ raise ProtocolError("Worker promotion receipt is invalid") from None
3428
+
3429
+
3430
+ def _worker_root_discard_receipt(
3431
+ value: object,
3432
+ prepared: WorkerPreparedRootReceipt,
3433
+ ) -> None:
3434
+ expected = "|".join(
3435
+ (
3436
+ _ROOT_DISCARD_RECEIPT_PREFIX,
3437
+ str(prepared.transaction_id),
3438
+ str(prepared.generation),
3439
+ prepared.manifest_sha256,
3440
+ prepared.candidate_root_key,
3441
+ )
3442
+ )
3443
+ if type(value) is not str or not compare_digest(value, expected):
3444
+ raise ProtocolError("Worker root discard receipt is invalid")
3445
+
3446
+
3447
+ def _worker_promotion_failure_phase(error: BaseException) -> str | None:
3448
+ prefix = "onec-worker-root-prepare-stage="
3449
+ message = str(error)
3450
+ marker = message.find(prefix)
3451
+ if marker < 0:
3452
+ return None
3453
+ phase = message[marker + len(prefix) :].splitlines()[0].rstrip("\r").strip()
3454
+ return phase if phase in {"create", "wire", "probe"} else None
3455
+
3456
+
3457
+ def _worker_root_swap_failure_phase(error: BaseException) -> str | None:
3458
+ prefix = "onec-worker-root-swap-stage="
3459
+ message = str(error)
3460
+ marker = message.find(prefix)
3461
+ if marker < 0:
3462
+ return None
3463
+ phase = message[marker + len(prefix) :].splitlines()[0].rstrip("\r").strip()
3464
+ return phase if phase == "guard" else None
3465
+
3466
+
3467
+ def _promotion_unknown(
3468
+ candidate: WorkerUniverseCandidate,
3469
+ ) -> WorkerPromotionOutcomeUnknown:
3470
+ return WorkerPromotionOutcomeUnknown(
3471
+ candidate.handle.generation,
3472
+ candidate.manifest.sha256,
3473
+ )
3474
+
3475
+
3476
+ def registration_name(logical_name: str, artifact_sha256: str) -> str:
3477
+ if not _bsl_identifier(logical_name) or not _sha256_value(artifact_sha256):
3478
+ raise ValueError("worker registration identity is invalid")
3479
+ logical_digest = sha256(logical_name.casefold().encode("utf-8")).hexdigest()[:8]
3480
+ value = f"OnecRuntime_{logical_digest}_{artifact_sha256[:16]}"
3481
+ if not _bsl_identifier(value) or len(value) > 80:
3482
+ raise ValueError("worker registration identity is invalid")
3483
+ return value
3484
+
3485
+
3486
+ def _build_worker_manifest(
3487
+ generation: int,
3488
+ artifacts: tuple[WorkerModuleArtifact, ...],
3489
+ ) -> tuple[tuple[_ValidatedWorkerModuleArtifact, ...], WorkerUniverseManifest]:
3490
+ if type(artifacts) is not tuple or not artifacts:
3491
+ raise ProtocolError("Worker universe requires a nonempty artifact tuple")
3492
+ try:
3493
+ views = tuple(_validated_worker_module_artifact(artifact) for artifact in artifacts)
3494
+ ordered_views = tuple(
3495
+ sorted(
3496
+ views,
3497
+ key=lambda view: (
3498
+ view.descriptor.logical_name.casefold(),
3499
+ view.descriptor.artifact_sha256,
3500
+ view.descriptor.revision,
3501
+ ),
3502
+ )
3503
+ )
3504
+ ordered = tuple(view.descriptor for view in ordered_views)
3505
+ names = tuple(item.logical_name.casefold() for item in ordered)
3506
+ if len(names) != len(set(names)):
3507
+ raise ProtocolError("Worker universe has duplicate logical modules")
3508
+ modules = tuple(
3509
+ ManifestModule(
3510
+ artifact.logical_name,
3511
+ artifact.revision,
3512
+ artifact.artifact_sha256,
3513
+ registration_name(artifact.logical_name, artifact.artifact_sha256),
3514
+ )
3515
+ for artifact in ordered
3516
+ )
3517
+ artifact_by_name = {
3518
+ artifact.logical_name.casefold(): artifact for artifact in ordered
3519
+ }
3520
+ wiring: list[DependencyTarget] = []
3521
+ seen_bindings: set[tuple[str, str]] = set()
3522
+ for artifact in ordered:
3523
+ for binding in artifact.dependency_bindings:
3524
+ binding_key = (
3525
+ artifact.logical_name.casefold(),
3526
+ binding.export_variable.casefold(),
3527
+ )
3528
+ if binding_key in seen_bindings:
3529
+ raise ProtocolError("Worker universe dependency binding is ambiguous")
3530
+ seen_bindings.add(binding_key)
3531
+ overloaded = artifact_by_name.get(binding.target_module.casefold())
3532
+ target_kind: Literal["original", "overloaded"]
3533
+ if overloaded is None:
3534
+ target_kind = "original"
3535
+ target_name = binding.target_module
3536
+ else:
3537
+ target_kind = "overloaded"
3538
+ target_name = overloaded.logical_name
3539
+ wiring.append(
3540
+ DependencyTarget(
3541
+ artifact.logical_name,
3542
+ binding.export_variable,
3543
+ target_kind,
3544
+ target_name,
3545
+ )
3546
+ )
3547
+ wiring_tuple = tuple(sorted(wiring, key=_dependency_target_sort_key))
3548
+ exports = tuple(
3549
+ sorted(
3550
+ (item for artifact in ordered for item in artifact.exports),
3551
+ key=_worker_export_sort_key,
3552
+ )
3553
+ )
3554
+ validate_worker_export_catalog(exports, require_nonempty=True)
3555
+ digest_value = _worker_manifest_sha256(
3556
+ generation,
3557
+ modules,
3558
+ wiring_tuple,
3559
+ exports,
3560
+ )
3561
+ return ordered_views, WorkerUniverseManifest(
3562
+ generation,
3563
+ modules,
3564
+ wiring_tuple,
3565
+ exports,
3566
+ digest_value,
3567
+ )
3568
+ except ProtocolError:
3569
+ raise
3570
+ except Exception:
3571
+ raise ProtocolError("Worker universe manifest admission is invalid") from None
3572
+
3573
+
3574
+ def _worker_manifest_sha256(
3575
+ generation: int,
3576
+ modules: tuple[ManifestModule, ...],
3577
+ wiring: tuple[DependencyTarget, ...],
3578
+ exports: tuple[WorkerExport, ...],
3579
+ ) -> str:
3580
+ manifest = {
3581
+ "schema": "onec-worker-universe-manifest-v1",
3582
+ "generation": generation,
3583
+ "modules": [
3584
+ {
3585
+ "logical_name": item.logical_name.casefold(),
3586
+ "revision": item.revision,
3587
+ "artifact_sha256": item.artifact_sha256,
3588
+ "registration_name": item.registration_name,
3589
+ }
3590
+ for item in modules
3591
+ ],
3592
+ "wiring": [
3593
+ {
3594
+ "source_module": item.source_module.casefold(),
3595
+ "export_variable": item.export_variable.casefold(),
3596
+ "target_kind": item.target_kind,
3597
+ "target_module": item.target_module.casefold(),
3598
+ }
3599
+ for item in wiring
3600
+ ],
3601
+ "exports": [
3602
+ {
3603
+ "public_path": item.public_path.casefold(),
3604
+ "method": item.method.casefold(),
3605
+ "receiver_module": (
3606
+ None
3607
+ if item.receiver_module is None
3608
+ else item.receiver_module.casefold()
3609
+ ),
3610
+ }
3611
+ for item in exports
3612
+ ],
3613
+ }
3614
+ payload = json.dumps(
3615
+ manifest,
3616
+ ensure_ascii=False,
3617
+ sort_keys=True,
3618
+ separators=(",", ":"),
3619
+ ).encode("utf-8")
3620
+ return sha256(payload).hexdigest()
3621
+
3622
+
3623
+ def _manifest_module_sort_key(item: ManifestModule) -> tuple[str, str, int]:
3624
+ return item.logical_name.casefold(), item.artifact_sha256, item.revision
3625
+
3626
+
3627
+ def _dependency_target_sort_key(
3628
+ item: DependencyTarget,
3629
+ ) -> tuple[str, str, str, str]:
3630
+ return (
3631
+ item.source_module.casefold(),
3632
+ item.export_variable.casefold(),
3633
+ item.target_kind,
3634
+ item.target_module.casefold(),
3635
+ )
3636
+
3637
+
3638
+ def _worker_export_sort_key(item: WorkerExport) -> tuple[str, str, str]:
3639
+ return (
3640
+ item.public_path.casefold(),
3641
+ item.method.casefold(),
3642
+ "" if item.receiver_module is None else item.receiver_module.casefold(),
3643
+ )
3644
+
3645
+
3646
+ def _effective_export_catalog_matches(
3647
+ manifest: WorkerUniverseManifest,
3648
+ catalog: tuple[WorkerExport, ...],
3649
+ ) -> bool:
3650
+ if type(catalog) is not tuple:
3651
+ return False
3652
+ try:
3653
+ admitted = validate_worker_export_catalog(catalog, require_nonempty=True)
3654
+ except (ProtocolError, ValueError):
3655
+ return False
3656
+ if admitted != catalog:
3657
+ return False
3658
+ manifest_receivers = {
3659
+ (export.receiver_module.casefold(), export.method.casefold())
3660
+ for export in manifest.exports
3661
+ if export.receiver_module is not None
3662
+ }
3663
+ catalog_receivers = {
3664
+ (export.receiver_module.casefold(), export.method.casefold())
3665
+ for export in catalog
3666
+ if export.receiver_module is not None
3667
+ }
3668
+ if catalog_receivers != manifest_receivers:
3669
+ return False
3670
+ return all(
3671
+ export.receiver_module is not None
3672
+ and _bsl_identifier(export.receiver_module)
3673
+ and _bsl_identifier(export.method)
3674
+ and (
3675
+ export.receiver_module.casefold(),
3676
+ export.method.casefold(),
3677
+ )
3678
+ in manifest_receivers
3679
+ for export in catalog
3680
+ )
3681
+
3682
+
3683
+ def _adjust_manifest_registrations(
3684
+ refcounts: dict[str, int],
3685
+ manifest: WorkerUniverseManifest,
3686
+ delta: Literal[-1, 1],
3687
+ ) -> None:
3688
+ for module in manifest.modules:
3689
+ current = refcounts.get(module.registration_name, 0)
3690
+ updated = current + delta
3691
+ if updated < 0:
3692
+ raise ProtocolError("Worker registration refcount would become negative")
3693
+ refcounts[module.registration_name] = updated
3694
+
3695
+
3696
+ def _remove_zero_registration_refcounts(
3697
+ refcounts: dict[str, int],
3698
+ registration_artifacts: dict[str, tuple[str, str]],
3699
+ quarantine_holds: set[str],
3700
+ ) -> tuple[str, ...]:
3701
+ released = tuple(
3702
+ sorted(
3703
+ (
3704
+ name
3705
+ for name, count in refcounts.items()
3706
+ if count == 0 and name not in quarantine_holds
3707
+ ),
3708
+ key=str.casefold,
3709
+ )
3710
+ )
3711
+ for name in released:
3712
+ del refcounts[name]
3713
+ registration_artifacts.pop(name, None)
3714
+ return released
3715
+
3716
+
3717
+ def _require_registration_ownership(
3718
+ manifest: WorkerUniverseManifest,
3719
+ registration_artifacts: dict[str, tuple[str, str]],
3720
+ ) -> None:
3721
+ for module in manifest.modules:
3722
+ owner = registration_artifacts.get(module.registration_name)
3723
+ expected = (module.logical_name.casefold(), module.artifact_sha256)
3724
+ if owner is not None and owner != expected:
3725
+ raise ProtocolError("Worker registration identity collision")
3726
+
3727
+
3728
+ def _fence_source(value: GenerationFence) -> Callable[[], int]:
3729
+ if callable(value):
3730
+ return value
3731
+ _validate_positive_generation(value)
3732
+ return lambda: value
3733
+
3734
+
3735
+ def _validate_positive_generation(value: object) -> None:
3736
+ if type(value) is not int or value <= 0:
3737
+ raise ValueError("worker generation fence must be positive")
3738
+
3739
+
3740
+ def _new_lease_id(leases: dict[str, _OperationLease]) -> str:
3741
+ while True:
3742
+ value = token_urlsafe(32)
3743
+ if value not in leases:
3744
+ return value
3745
+
3746
+
3747
+ def _generation_root_key(generation: int) -> str:
3748
+ _validate_positive_generation(generation)
3749
+ return f"generation-{generation}"
3750
+
3751
+
3752
+ def _bsl_identifier(value: object) -> bool:
3753
+ return (
3754
+ isinstance(value, str)
3755
+ and fullmatch(r"[A-Za-zА-Яа-яЁё_][A-Za-zА-Яа-яЁё_0-9]*", value) is not None
3756
+ and value.upper() not in _BSL_KEYWORDS
3757
+ )
3758
+
3759
+
3760
+ def _valid_dependency_target(value: object) -> bool:
3761
+ return (
3762
+ isinstance(value, DependencyTarget)
3763
+ and _bsl_identifier(value.source_module)
3764
+ and _bsl_identifier(value.export_variable)
3765
+ and value.target_kind in ("original", "overloaded")
3766
+ and _bsl_identifier(value.target_module)
3767
+ and not (
3768
+ value.target_kind == "original"
3769
+ and value.target_module.casefold()
3770
+ in _FIXED_UNQUALIFIED_WORKER_IDENTIFIERS
3771
+ )
3772
+ )