tigrbl-runtime 0.4.5.dev4__tar.gz → 0.4.5.dev8__tar.gz

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 (60) hide show
  1. {tigrbl_runtime-0.4.5.dev4 → tigrbl_runtime-0.4.5.dev8}/PKG-INFO +15 -11
  2. {tigrbl_runtime-0.4.5.dev4 → tigrbl_runtime-0.4.5.dev8}/README.md +9 -5
  3. {tigrbl_runtime-0.4.5.dev4 → tigrbl_runtime-0.4.5.dev8}/pyproject.toml +6 -6
  4. {tigrbl_runtime-0.4.5.dev4 → tigrbl_runtime-0.4.5.dev8}/tigrbl_runtime/channel/_asgi_jsonrpc.py +11 -0
  5. {tigrbl_runtime-0.4.5.dev4 → tigrbl_runtime-0.4.5.dev8}/tigrbl_runtime/channel/_asgi_send.py +79 -5
  6. {tigrbl_runtime-0.4.5.dev4 → tigrbl_runtime-0.4.5.dev8}/tigrbl_runtime/channel/_asgi_webtransport.py +31 -4
  7. {tigrbl_runtime-0.4.5.dev4 → tigrbl_runtime-0.4.5.dev8}/tigrbl_runtime/executors/packed/_shared.py +2 -3
  8. {tigrbl_runtime-0.4.5.dev4 → tigrbl_runtime-0.4.5.dev8}/tigrbl_runtime/executors/packed/batch.py +7 -46
  9. {tigrbl_runtime-0.4.5.dev4 → tigrbl_runtime-0.4.5.dev8}/tigrbl_runtime/executors/packed/execute.py +43 -18
  10. {tigrbl_runtime-0.4.5.dev4 → tigrbl_runtime-0.4.5.dev8}/tigrbl_runtime/executors/packed/input_compile.py +21 -12
  11. {tigrbl_runtime-0.4.5.dev4 → tigrbl_runtime-0.4.5.dev8}/tigrbl_runtime/executors/packed/input_extract.py +1 -1
  12. {tigrbl_runtime-0.4.5.dev4 → tigrbl_runtime-0.4.5.dev8}/tigrbl_runtime/executors/packed/route_select.py +98 -20
  13. tigrbl_runtime-0.4.5.dev8/tigrbl_runtime/executors/packed/route_select_websocket.py +34 -0
  14. tigrbl_runtime-0.4.5.dev8/tigrbl_runtime/lowering_services.py +22 -0
  15. {tigrbl_runtime-0.4.5.dev4 → tigrbl_runtime-0.4.5.dev8}/LICENSE +0 -0
  16. {tigrbl_runtime-0.4.5.dev4 → tigrbl_runtime-0.4.5.dev8}/NOTICE +0 -0
  17. {tigrbl_runtime-0.4.5.dev4 → tigrbl_runtime-0.4.5.dev8}/tigrbl_runtime/__init__.py +0 -0
  18. {tigrbl_runtime-0.4.5.dev4 → tigrbl_runtime-0.4.5.dev8}/tigrbl_runtime/callbacks.py +0 -0
  19. {tigrbl_runtime-0.4.5.dev4 → tigrbl_runtime-0.4.5.dev8}/tigrbl_runtime/channel/__init__.py +0 -0
  20. {tigrbl_runtime-0.4.5.dev4 → tigrbl_runtime-0.4.5.dev8}/tigrbl_runtime/channel/_asgi_completion.py +0 -0
  21. {tigrbl_runtime-0.4.5.dev4 → tigrbl_runtime-0.4.5.dev8}/tigrbl_runtime/channel/_asgi_context.py +0 -0
  22. {tigrbl_runtime-0.4.5.dev4 → tigrbl_runtime-0.4.5.dev8}/tigrbl_runtime/channel/_asgi_receive.py +0 -0
  23. {tigrbl_runtime-0.4.5.dev4 → tigrbl_runtime-0.4.5.dev8}/tigrbl_runtime/channel/_asgi_scope.py +0 -0
  24. {tigrbl_runtime-0.4.5.dev4 → tigrbl_runtime-0.4.5.dev8}/tigrbl_runtime/channel/asgi.py +0 -0
  25. {tigrbl_runtime-0.4.5.dev4 → tigrbl_runtime-0.4.5.dev8}/tigrbl_runtime/channel/capabilities.py +0 -0
  26. {tigrbl_runtime-0.4.5.dev4 → tigrbl_runtime-0.4.5.dev8}/tigrbl_runtime/channel/websocket.py +0 -0
  27. {tigrbl_runtime-0.4.5.dev4 → tigrbl_runtime-0.4.5.dev8}/tigrbl_runtime/config/__init__.py +0 -0
  28. {tigrbl_runtime-0.4.5.dev4 → tigrbl_runtime-0.4.5.dev8}/tigrbl_runtime/config/constants.py +0 -0
  29. {tigrbl_runtime-0.4.5.dev4 → tigrbl_runtime-0.4.5.dev8}/tigrbl_runtime/executors/__init__.py +0 -0
  30. {tigrbl_runtime-0.4.5.dev4 → tigrbl_runtime-0.4.5.dev8}/tigrbl_runtime/executors/_invoke_support.py +0 -0
  31. {tigrbl_runtime-0.4.5.dev4 → tigrbl_runtime-0.4.5.dev8}/tigrbl_runtime/executors/base.py +0 -0
  32. {tigrbl_runtime-0.4.5.dev4 → tigrbl_runtime-0.4.5.dev8}/tigrbl_runtime/executors/ctx/__init__.py +0 -0
  33. {tigrbl_runtime-0.4.5.dev4 → tigrbl_runtime-0.4.5.dev8}/tigrbl_runtime/executors/ctx/context.py +0 -0
  34. {tigrbl_runtime-0.4.5.dev4 → tigrbl_runtime-0.4.5.dev8}/tigrbl_runtime/executors/ctx/hot.py +0 -0
  35. {tigrbl_runtime-0.4.5.dev4 → tigrbl_runtime-0.4.5.dev8}/tigrbl_runtime/executors/ctx/hot_namespace_values.py +0 -0
  36. {tigrbl_runtime-0.4.5.dev4 → tigrbl_runtime-0.4.5.dev8}/tigrbl_runtime/executors/ctx/hot_namespaces.py +0 -0
  37. {tigrbl_runtime-0.4.5.dev4 → tigrbl_runtime-0.4.5.dev8}/tigrbl_runtime/executors/ctx/hot_state.py +0 -0
  38. {tigrbl_runtime-0.4.5.dev4 → tigrbl_runtime-0.4.5.dev8}/tigrbl_runtime/executors/kernel_executor.py +0 -0
  39. {tigrbl_runtime-0.4.5.dev4 → tigrbl_runtime-0.4.5.dev8}/tigrbl_runtime/executors/numba_packed.py +0 -0
  40. {tigrbl_runtime-0.4.5.dev4 → tigrbl_runtime-0.4.5.dev8}/tigrbl_runtime/executors/packed/__init__.py +0 -0
  41. {tigrbl_runtime-0.4.5.dev4 → tigrbl_runtime-0.4.5.dev8}/tigrbl_runtime/executors/packed/error_edges.py +0 -0
  42. {tigrbl_runtime-0.4.5.dev4 → tigrbl_runtime-0.4.5.dev8}/tigrbl_runtime/executors/packed/executor.py +0 -0
  43. {tigrbl_runtime-0.4.5.dev4 → tigrbl_runtime-0.4.5.dev8}/tigrbl_runtime/executors/packed/hot_access.py +0 -0
  44. {tigrbl_runtime-0.4.5.dev4 → tigrbl_runtime-0.4.5.dev8}/tigrbl_runtime/executors/packed/hot_context.py +0 -0
  45. {tigrbl_runtime-0.4.5.dev4 → tigrbl_runtime-0.4.5.dev8}/tigrbl_runtime/executors/packed/request_select.py +0 -0
  46. {tigrbl_runtime-0.4.5.dev4 → tigrbl_runtime-0.4.5.dev8}/tigrbl_runtime/executors/packed/runner_resolve.py +0 -0
  47. {tigrbl_runtime-0.4.5.dev4 → tigrbl_runtime-0.4.5.dev8}/tigrbl_runtime/executors/packed/segments.py +0 -0
  48. {tigrbl_runtime-0.4.5.dev4 → tigrbl_runtime-0.4.5.dev8}/tigrbl_runtime/executors/phase.py +0 -0
  49. {tigrbl_runtime-0.4.5.dev4 → tigrbl_runtime-0.4.5.dev8}/tigrbl_runtime/executors/phase_runner.py +0 -0
  50. {tigrbl_runtime-0.4.5.dev4 → tigrbl_runtime-0.4.5.dev8}/tigrbl_runtime/executors/types.py +0 -0
  51. {tigrbl_runtime-0.4.5.dev4 → tigrbl_runtime-0.4.5.dev8}/tigrbl_runtime/runtime/__init__.py +0 -0
  52. {tigrbl_runtime-0.4.5.dev4 → tigrbl_runtime-0.4.5.dev8}/tigrbl_runtime/runtime/_typing_aliases.py +0 -0
  53. {tigrbl_runtime-0.4.5.dev4 → tigrbl_runtime-0.4.5.dev8}/tigrbl_runtime/runtime/base.py +0 -0
  54. {tigrbl_runtime-0.4.5.dev4 → tigrbl_runtime-0.4.5.dev8}/tigrbl_runtime/runtime/channel.py +0 -0
  55. {tigrbl_runtime-0.4.5.dev4 → tigrbl_runtime-0.4.5.dev8}/tigrbl_runtime/runtime/exceptions.py +0 -0
  56. {tigrbl_runtime-0.4.5.dev4 → tigrbl_runtime-0.4.5.dev8}/tigrbl_runtime/runtime/hook_types.py +0 -0
  57. {tigrbl_runtime-0.4.5.dev4 → tigrbl_runtime-0.4.5.dev8}/tigrbl_runtime/runtime/response.py +0 -0
  58. {tigrbl_runtime-0.4.5.dev4 → tigrbl_runtime-0.4.5.dev8}/tigrbl_runtime/runtime/runtime.py +0 -0
  59. {tigrbl_runtime-0.4.5.dev4 → tigrbl_runtime-0.4.5.dev8}/tigrbl_runtime/runtime/system.py +0 -0
  60. {tigrbl_runtime-0.4.5.dev4 → tigrbl_runtime-0.4.5.dev8}/tigrbl_runtime/semantics.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tigrbl-runtime
3
- Version: 0.4.5.dev4
3
+ Version: 0.4.5.dev8
4
4
  Summary: Execution bridge for compiled Tigrbl kernel plans and narrow runtime context/carrier compatibility.
5
5
  License: Apache License
6
6
  Version 2.0, January 2004
@@ -226,16 +226,16 @@ Classifier: Topic :: Internet :: WWW/HTTP
226
226
  Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
227
227
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
228
228
  Requires-Dist: numba (>=0.61.2)
229
- Requires-Dist: tigrbl-atoms
230
- Requires-Dist: tigrbl-base
231
- Requires-Dist: tigrbl-core
232
- Requires-Dist: tigrbl-kernel
229
+ Requires-Dist: tigrbl-atoms (>=0.4.5.dev5)
230
+ Requires-Dist: tigrbl-base (>=0.4.5.dev6)
231
+ Requires-Dist: tigrbl-core (>=0.4.5.dev6)
232
+ Requires-Dist: tigrbl-kernel (>=0.4.5.dev5)
233
233
  Requires-Dist: tigrbl-typing
234
234
  Project-URL: Discord, https://discord.gg/K4YTAPapjR
235
235
  Project-URL: Homepage, https://github.com/tigrbl/tigrbl
236
236
  Project-URL: Issues, https://github.com/tigrbl/tigrbl/issues
237
237
  Project-URL: Organization, https://github.com/tigrbl
238
- Project-URL: Repository, https://github.com/tigrbl/tigrbl/tree/master/pkgs/core/tigrbl_runtime
238
+ Project-URL: Repository, https://github.com/tigrbl/tigrbl/tree/master/pkgs/50_runtime/tigrbl_runtime
239
239
  Description-Content-Type: text/markdown
240
240
 
241
241
  <div align="center">
@@ -245,7 +245,7 @@ Description-Content-Type: text/markdown
245
245
  <a href="https://pypi.org/project/tigrbl-runtime/"><img src="https://img.shields.io/pypi/v/tigrbl-runtime?label=PyPI" alt="PyPI version for tigrbl-runtime"/></a>
246
246
  <a href="https://pypi.org/project/tigrbl-runtime/"><img src="https://static.pepy.tech/badge/tigrbl-runtime" alt="Downloads for tigrbl-runtime"/></a>
247
247
  <a href="https://discord.gg/K4YTAPapjR"><img src="https://img.shields.io/badge/Discord-Join%20chat-5865F2?logo=discord&logoColor=white" alt="Discord community for tigrbl-runtime"/></a>
248
- <a href="https://github.com/tigrbl/tigrbl/blob/master/pkgs/core/tigrbl_runtime/README.md"><img src="https://hits.sh/github.com/tigrbl/tigrbl/blob/master/pkgs/core/tigrbl_runtime/README.md.svg?label=hits" alt="Repository hits for tigrbl-runtime README"/></a>
248
+ <a href="https://github.com/tigrbl/tigrbl/blob/master/pkgs/50_runtime/tigrbl_runtime/README.md"><img src="https://hits.sh/github.com/tigrbl/tigrbl/blob/master/pkgs/50_runtime/tigrbl_runtime/README.md.svg?label=hits" alt="Repository hits for tigrbl-runtime README"/></a>
249
249
  <a href="LICENSE"><img src="https://img.shields.io/badge/license-Apache%202.0-525252" alt="Apache 2.0 license"/></a>
250
250
  <a href="pyproject.toml"><img src="https://img.shields.io/badge/python-3.10%20%7C%203.11%20%7C%203.12%20%7C%203.13%20%7C%203.14-3776ab" alt="Python versions 3.10 | 3.11 | 3.12 | 3.13 | 3.14 for tigrbl-runtime"/></a>
251
251
  <a href="https://github.com/tigrbl/tigrbl/blob/master/docs/README.md"><img src="https://img.shields.io/badge/workspace-core-1f6feb" alt="Workspace group for tigrbl-runtime"/></a>
@@ -269,7 +269,7 @@ Framework maintainers, extension authors, and advanced users composing Tigrbl fr
269
269
 
270
270
  ## Where does tigrbl-runtime fit?
271
271
 
272
- `tigrbl-runtime` lives at `pkgs/core/tigrbl_runtime` and serves a focused layer in the split Tigrbl framework.
272
+ `tigrbl-runtime` lives at `pkgs/50_runtime/tigrbl_runtime` and serves a focused layer in the split Tigrbl framework.
273
273
 
274
274
  ## How does tigrbl-runtime work?
275
275
 
@@ -291,7 +291,7 @@ pip install tigrbl-runtime
291
291
  | Surface | Value |
292
292
  |---|---|
293
293
  | PyPI package | [`tigrbl-runtime`](https://pypi.org/project/tigrbl-runtime/) |
294
- | Repository path | [`pkgs/core/tigrbl_runtime`](https://github.com/tigrbl/tigrbl/tree/master/pkgs/core/tigrbl_runtime) |
294
+ | Repository path | [`pkgs/50_runtime/tigrbl_runtime`](https://github.com/tigrbl/tigrbl/tree/master/pkgs/50_runtime/tigrbl_runtime) |
295
295
  | Python import root | `tigrbl_runtime` |
296
296
  | Console scripts | none declared |
297
297
  | Entry points | none declared |
@@ -387,7 +387,7 @@ Choose `tigrbl-runtime` when the quick-answer table matches your use case. Choos
387
387
 
388
388
  - Community: [Discord](https://discord.gg/K4YTAPapjR).
389
389
  - Issues: [GitHub Issues](https://github.com/tigrbl/tigrbl/issues).
390
- - Repository: [pkgs/core/tigrbl_runtime](https://github.com/tigrbl/tigrbl/tree/master/pkgs/core/tigrbl_runtime).
390
+ - Repository: [pkgs/50_runtime/tigrbl_runtime](https://github.com/tigrbl/tigrbl/tree/master/pkgs/50_runtime/tigrbl_runtime).
391
391
 
392
392
  ## Package-local Boundary
393
393
 
@@ -398,10 +398,14 @@ This README is the package-local distribution entry point for `tigrbl-runtime`.
398
398
  - Package status: governed package in the `tigrbl/tigrbl` workspace.
399
399
  - Governance source: [SSOT registry](https://github.com/tigrbl/tigrbl/blob/master/.ssot/registry.json).
400
400
  - Release evidence: [publish workflow](https://github.com/tigrbl/tigrbl/actions/workflows/publish.yml) validates package builds, tests, GitHub release assets, and PyPI publication for managed packages.
401
- - Local certification guard: `pkgs/core/tigrbl_tests/tests/unit/test_package_badges_and_notices.py` verifies every package README keeps the Discord badge, Apache 2.0 badge, explicit Python-version badge, `LICENSE`, and `NOTICE`.
401
+ - Local certification guard: `pkgs/97_tests/tigrbl_tests/tests/unit/test_package_badges_and_notices.py` verifies every package README keeps the Discord badge, Apache 2.0 badge, explicit Python-version badge, `LICENSE`, and `NOTICE`.
402
402
  - Scope note: this README documents the package boundary. Runtime feature support remains governed by `.ssot/` entities and the conformance docs linked below.
403
403
 
404
404
  ## License
405
405
 
406
406
  Licensed under the Apache License, Version 2.0. See `LICENSE`, `NOTICE`, and the official [Apache 2.0 license text](https://www.apache.org/licenses/LICENSE-2.0).
407
407
 
408
+ ## Lowering integration
409
+
410
+ `lowering_services` lazily exposes canonical kernel operation-view compilation and WebSocket JSON-RPC session construction to concrete integration code. It preserves the kernel function objects and keeps kernel imports out of concrete authoring modules.
411
+
@@ -5,7 +5,7 @@
5
5
  <a href="https://pypi.org/project/tigrbl-runtime/"><img src="https://img.shields.io/pypi/v/tigrbl-runtime?label=PyPI" alt="PyPI version for tigrbl-runtime"/></a>
6
6
  <a href="https://pypi.org/project/tigrbl-runtime/"><img src="https://static.pepy.tech/badge/tigrbl-runtime" alt="Downloads for tigrbl-runtime"/></a>
7
7
  <a href="https://discord.gg/K4YTAPapjR"><img src="https://img.shields.io/badge/Discord-Join%20chat-5865F2?logo=discord&logoColor=white" alt="Discord community for tigrbl-runtime"/></a>
8
- <a href="https://github.com/tigrbl/tigrbl/blob/master/pkgs/core/tigrbl_runtime/README.md"><img src="https://hits.sh/github.com/tigrbl/tigrbl/blob/master/pkgs/core/tigrbl_runtime/README.md.svg?label=hits" alt="Repository hits for tigrbl-runtime README"/></a>
8
+ <a href="https://github.com/tigrbl/tigrbl/blob/master/pkgs/50_runtime/tigrbl_runtime/README.md"><img src="https://hits.sh/github.com/tigrbl/tigrbl/blob/master/pkgs/50_runtime/tigrbl_runtime/README.md.svg?label=hits" alt="Repository hits for tigrbl-runtime README"/></a>
9
9
  <a href="LICENSE"><img src="https://img.shields.io/badge/license-Apache%202.0-525252" alt="Apache 2.0 license"/></a>
10
10
  <a href="pyproject.toml"><img src="https://img.shields.io/badge/python-3.10%20%7C%203.11%20%7C%203.12%20%7C%203.13%20%7C%203.14-3776ab" alt="Python versions 3.10 | 3.11 | 3.12 | 3.13 | 3.14 for tigrbl-runtime"/></a>
11
11
  <a href="https://github.com/tigrbl/tigrbl/blob/master/docs/README.md"><img src="https://img.shields.io/badge/workspace-core-1f6feb" alt="Workspace group for tigrbl-runtime"/></a>
@@ -29,7 +29,7 @@ Framework maintainers, extension authors, and advanced users composing Tigrbl fr
29
29
 
30
30
  ## Where does tigrbl-runtime fit?
31
31
 
32
- `tigrbl-runtime` lives at `pkgs/core/tigrbl_runtime` and serves a focused layer in the split Tigrbl framework.
32
+ `tigrbl-runtime` lives at `pkgs/50_runtime/tigrbl_runtime` and serves a focused layer in the split Tigrbl framework.
33
33
 
34
34
  ## How does tigrbl-runtime work?
35
35
 
@@ -51,7 +51,7 @@ pip install tigrbl-runtime
51
51
  | Surface | Value |
52
52
  |---|---|
53
53
  | PyPI package | [`tigrbl-runtime`](https://pypi.org/project/tigrbl-runtime/) |
54
- | Repository path | [`pkgs/core/tigrbl_runtime`](https://github.com/tigrbl/tigrbl/tree/master/pkgs/core/tigrbl_runtime) |
54
+ | Repository path | [`pkgs/50_runtime/tigrbl_runtime`](https://github.com/tigrbl/tigrbl/tree/master/pkgs/50_runtime/tigrbl_runtime) |
55
55
  | Python import root | `tigrbl_runtime` |
56
56
  | Console scripts | none declared |
57
57
  | Entry points | none declared |
@@ -147,7 +147,7 @@ Choose `tigrbl-runtime` when the quick-answer table matches your use case. Choos
147
147
 
148
148
  - Community: [Discord](https://discord.gg/K4YTAPapjR).
149
149
  - Issues: [GitHub Issues](https://github.com/tigrbl/tigrbl/issues).
150
- - Repository: [pkgs/core/tigrbl_runtime](https://github.com/tigrbl/tigrbl/tree/master/pkgs/core/tigrbl_runtime).
150
+ - Repository: [pkgs/50_runtime/tigrbl_runtime](https://github.com/tigrbl/tigrbl/tree/master/pkgs/50_runtime/tigrbl_runtime).
151
151
 
152
152
  ## Package-local Boundary
153
153
 
@@ -158,9 +158,13 @@ This README is the package-local distribution entry point for `tigrbl-runtime`.
158
158
  - Package status: governed package in the `tigrbl/tigrbl` workspace.
159
159
  - Governance source: [SSOT registry](https://github.com/tigrbl/tigrbl/blob/master/.ssot/registry.json).
160
160
  - Release evidence: [publish workflow](https://github.com/tigrbl/tigrbl/actions/workflows/publish.yml) validates package builds, tests, GitHub release assets, and PyPI publication for managed packages.
161
- - Local certification guard: `pkgs/core/tigrbl_tests/tests/unit/test_package_badges_and_notices.py` verifies every package README keeps the Discord badge, Apache 2.0 badge, explicit Python-version badge, `LICENSE`, and `NOTICE`.
161
+ - Local certification guard: `pkgs/97_tests/tigrbl_tests/tests/unit/test_package_badges_and_notices.py` verifies every package README keeps the Discord badge, Apache 2.0 badge, explicit Python-version badge, `LICENSE`, and `NOTICE`.
162
162
  - Scope note: this README documents the package boundary. Runtime feature support remains governed by `.ssot/` entities and the conformance docs linked below.
163
163
 
164
164
  ## License
165
165
 
166
166
  Licensed under the Apache License, Version 2.0. See `LICENSE`, `NOTICE`, and the official [Apache 2.0 license text](https://www.apache.org/licenses/LICENSE-2.0).
167
+
168
+ ## Lowering integration
169
+
170
+ `lowering_services` lazily exposes canonical kernel operation-view compilation and WebSocket JSON-RPC session construction to concrete integration code. It preserves the kernel function objects and keeps kernel imports out of concrete authoring modules.
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "tigrbl-runtime"
3
- version = "0.4.5.dev4"
3
+ version = "0.4.5.dev8"
4
4
  description = "Execution bridge for compiled Tigrbl kernel plans and narrow runtime context/carrier compatibility."
5
5
  readme = "README.md"
6
6
  license = { file = "LICENSE" }
@@ -38,10 +38,10 @@ authors = [{ name = "Jacob Stewart", email = "jacob@swarmauri.com" }]
38
38
 
39
39
  dependencies = [
40
40
  "tigrbl-typing",
41
- "tigrbl-kernel",
42
- "tigrbl-atoms",
43
- "tigrbl-base",
44
- "tigrbl-core",
41
+ "tigrbl-kernel>=0.4.5.dev5",
42
+ "tigrbl-atoms>=0.4.5.dev5",
43
+ "tigrbl-base>=0.4.5.dev6",
44
+ "tigrbl-core>=0.4.5.dev6",
45
45
  "numba>=0.61.2",
46
46
  ]
47
47
 
@@ -49,7 +49,7 @@ dependencies = [
49
49
  Organization = "https://github.com/tigrbl"
50
50
  Discord = "https://discord.gg/K4YTAPapjR"
51
51
  Homepage = "https://github.com/tigrbl/tigrbl"
52
- Repository = "https://github.com/tigrbl/tigrbl/tree/master/pkgs/core/tigrbl_runtime"
52
+ Repository = "https://github.com/tigrbl/tigrbl/tree/master/pkgs/50_runtime/tigrbl_runtime"
53
53
  Issues = "https://github.com/tigrbl/tigrbl/issues"
54
54
 
55
55
  [tool.uv.sources]
@@ -36,3 +36,14 @@ def _resolve_jsonrpc_endpoint(ctx: Any, channel: OpChannel) -> str | None:
36
36
  if path == _normalize_path(mapped_path):
37
37
  return endpoint
38
38
  return None
39
+
40
+
41
+ _NO_ERROR_DATA = object()
42
+
43
+
44
+ def _jsonrpc_error_payload(code: int, message: str, request_id: Any, *, data: Any = _NO_ERROR_DATA) -> dict[str, Any]:
45
+ """Construct the existing compatibility error envelope without choosing policy."""
46
+ error: dict[str, Any] = {"code": code, "message": message}
47
+ if data is not _NO_ERROR_DATA:
48
+ error["data"] = data
49
+ return {"jsonrpc": "2.0", "error": error, "id": request_id}
@@ -19,6 +19,52 @@ from ._asgi_webtransport import (
19
19
  )
20
20
 
21
21
 
22
+ def _webtransport_event_projection(ctx: Any) -> Mapping[str, Any]:
23
+ projection = ctx.get("channel_event_projection")
24
+ if isinstance(projection, Mapping):
25
+ return projection
26
+ channel = ctx.get("channel")
27
+ state = getattr(channel, "state", None) if channel is not None else None
28
+ if isinstance(state, dict):
29
+ projection = state.get("event_projection")
30
+ if isinstance(projection, Mapping):
31
+ return projection
32
+ return {}
33
+
34
+
35
+ def _is_receive_only_webtransport_stream(ctx: Any) -> bool:
36
+ projection = _webtransport_event_projection(ctx)
37
+ if (
38
+ projection.get("lane") == "unidi_client_stream"
39
+ or projection.get("exchange") == "client_stream"
40
+ ):
41
+ return True
42
+ message = ctx.get("channel_message")
43
+ return bool(
44
+ isinstance(message, Mapping)
45
+ and message.get("type") == "webtransport.stream.receive"
46
+ and message.get("stream_direction") == "client_to_server"
47
+ )
48
+
49
+
50
+ def _validate_receive_only_webtransport_output(payload: Any) -> None:
51
+ if payload is None:
52
+ return
53
+ if not isinstance(payload, Mapping) or not any(
54
+ key in payload
55
+ for key in ("bidirectional_streams", "unidirectional_streams", "datagrams")
56
+ ):
57
+ raise ValueError(
58
+ "cannot automatically reply on a client_to_server WebTransport stream; "
59
+ "return None or emit an explicit server_to_client stream or datagram"
60
+ )
61
+ if payload.get("bidirectional_streams"):
62
+ raise ValueError(
63
+ "bidirectional_streams output is invalid for a client_to_server "
64
+ "WebTransport stream; use unidirectional_streams or datagrams"
65
+ )
66
+
67
+
22
68
  async def _send_session_payload(
23
69
  env: Any,
24
70
  payload: Any,
@@ -87,11 +133,15 @@ async def _send_webtransport_payload(env: Any, ctx: Any, payload: Any) -> None:
87
133
  for key in ("bidirectional_streams", "unidirectional_streams", "datagrams")
88
134
  )
89
135
  if structured_payload:
90
- if not queue and message_type in {
91
- "webtransport.stream.receive",
92
- "webtransport.datagram.receive",
93
- }:
94
- queue = [message]
136
+ if (
137
+ message_type
138
+ in {
139
+ "webtransport.stream.receive",
140
+ "webtransport.datagram.receive",
141
+ }
142
+ and message not in queue
143
+ ):
144
+ queue.append(message)
95
145
  if not queue:
96
146
  raise ValueError(
97
147
  "structured WebTransport payloads require inbound stream.receive or datagram.receive events"
@@ -125,6 +175,12 @@ async def _send_webtransport_payload(env: Any, ctx: Any, payload: Any) -> None:
125
175
  )
126
176
  if payload is not None:
127
177
  base = {**message, "session_id": session_id}
178
+ temp = getattr(ctx, "temp", None)
179
+ dispatch = temp.get("dispatch") if isinstance(temp, Mapping) else None
180
+ if isinstance(dispatch, Mapping):
181
+ request = dispatch.get("rpc") or dispatch.get("rpc_envelope")
182
+ if isinstance(request, Mapping) and request.get("id") is not None:
183
+ base["jsonrpc_request_id"] = request.get("id")
128
184
  if structured_payload:
129
185
  for event in _webtransport_structured_payload_events(
130
186
  session_id=session_id,
@@ -151,6 +207,14 @@ async def _send_webtransport_payload(env: Any, ctx: Any, payload: Any) -> None:
151
207
  )
152
208
  await _run_webtransport_hooks(env, ctx, direction="send", message=event)
153
209
  await send(event)
210
+ if isinstance(state, dict):
211
+ raw_queue = state.get("receive_queue")
212
+ consumed = min(len(queue), len(raw_queue or ()))
213
+ if isinstance(raw_queue, deque):
214
+ for _ in range(consumed):
215
+ raw_queue.popleft()
216
+ elif isinstance(raw_queue, list):
217
+ del raw_queue[:consumed]
154
218
  else:
155
219
  event = _webtransport_payload_event(base=base, payload=payload)
156
220
  if isinstance(session, WebTransportSessionState):
@@ -222,6 +286,16 @@ async def send_transport_via_channel(env: Any, ctx: Any) -> None:
222
286
  if payload is None:
223
287
  payload = getattr(ctx, "result", None)
224
288
  if scope_type == "webtransport":
289
+ if _is_receive_only_webtransport_stream(ctx):
290
+ _validate_receive_only_webtransport_output(payload)
291
+ if payload is None:
292
+ if isinstance(state, dict):
293
+ state["egress_disposition"] = "suppressed_receive_only"
294
+ state["emitted"] = False
295
+ if isinstance(egress, dict):
296
+ egress["response_suppressed"] = True
297
+ egress["suppression_reason"] = "receive_only_stream"
298
+ return
225
299
  await _send_webtransport_payload(env, ctx, payload)
226
300
  else:
227
301
  await _send_session_payload(
@@ -15,9 +15,12 @@ from tigrbl_kernel.channel_taxonomy import (
15
15
  webtransport_event_metadata as _webtransport_event_metadata,
16
16
  )
17
17
  from tigrbl_kernel.webtransport_events import validate_webtransport_event_payload
18
+ from tigrbl_kernel.webtransport_session_plan import WebTransportSessionPlan
18
19
  from tigrbl_atoms.runtime_channel import WebTransportSessionState
19
20
  from tigrbl_typing.channel import OpChannel
20
21
 
22
+ MAX_EAGER_DRAIN_EVENTS = 16
23
+
21
24
 
22
25
  def _webtransport_scope_state(env: Any) -> dict[str, Any]:
23
26
  scope = getattr(env, "scope", {}) or {}
@@ -242,6 +245,20 @@ async def _receive_webtransport_session_messages(
242
245
  queue: deque[Mapping[str, Any]] = deque()
243
246
  session: WebTransportSessionState | None = None
244
247
 
248
+ session_plan = shared_state.get("session_plan")
249
+ if not isinstance(session_plan, WebTransportSessionPlan):
250
+ session_plan = WebTransportSessionPlan()
251
+ shared_state["session_plan"] = session_plan
252
+
253
+ async def _receive_record_or_event() -> Mapping[str, Any]:
254
+ if session_plan.has_pending():
255
+ return session_plan.pop_pending()
256
+ while True:
257
+ raw = _enrich_webtransport_message(env, await receive())
258
+ session_plan.project_receive(raw)
259
+ if session_plan.has_pending():
260
+ return session_plan.pop_pending()
261
+
245
262
  def _ensure_session(message: Mapping[str, Any]) -> WebTransportSessionState | None:
246
263
  nonlocal session
247
264
  session_id = message.get("session_id") or _webtransport_scope_session_id(env)
@@ -292,6 +309,8 @@ async def _receive_webtransport_session_messages(
292
309
  channel="receive",
293
310
  payload={**message, "session_id": state.get("session_id")},
294
311
  )
312
+ state["event_projection"] = dict(projection)
313
+ ctx["channel_event_projection"] = dict(projection)
295
314
  if current_session is not None:
296
315
  current_session.apply_event(
297
316
  event=message_type,
@@ -334,18 +353,26 @@ async def _receive_webtransport_session_messages(
334
353
  return
335
354
  ctx["channel_message"] = message
336
355
 
337
- message = await receive()
356
+ message = await _receive_record_or_event()
338
357
  await _record_payload_message(message)
339
358
  if message.get("type") == "webtransport.connect":
340
359
  if "channel_message" not in ctx:
341
- message = await receive()
360
+ message = await _receive_record_or_event()
342
361
  await _record_payload_message(message)
343
362
 
344
- while shared_state.get("eager_drain") is True and not state.get("disconnected"):
363
+ drained_events = 0
364
+ while (
365
+ shared_state.get("eager_drain") is True
366
+ and not state.get("disconnected")
367
+ and drained_events < MAX_EAGER_DRAIN_EVENTS
368
+ ):
345
369
  try:
346
- message = await asyncio.wait_for(receive(), timeout=0.001)
370
+ message = await asyncio.wait_for(
371
+ _receive_record_or_event(), timeout=0.001
372
+ )
347
373
  except TimeoutError:
348
374
  break
349
375
  await _record_payload_message(message)
376
+ drained_events += 1
350
377
 
351
378
  state["receive_queue"] = queue
@@ -13,7 +13,7 @@ from types import SimpleNamespace
13
13
  from tigrbl_atoms._ctx import _ctx_view
14
14
  from tigrbl_core.config.constants import __JSONRPC_DEFAULT_ENDPOINT_MAPPINGS__
15
15
  from tigrbl_atoms.types import build_error_ctx, error_phase_for, select_error_edge
16
- from tigrbl_atoms.atoms.wire.validate_in import _coerce_if_needed
16
+ from tigrbl_core.schema.materialize import _materialize_field_value
17
17
  from tigrbl_atoms.atoms.transport.websocket_unary import (
18
18
  DirectWebSocketUnary as _DirectWebSocketUnary,
19
19
  )
@@ -91,6 +91,7 @@ from tigrbl_kernel.packed_selectors import (
91
91
  resolve_hot_exact_websocket_routes as _kernel_resolve_hot_exact_websocket_routes,
92
92
  resolve_program_id_from_exact_route as _kernel_resolve_program_id_from_exact_route,
93
93
  resolve_program_id_from_exact_websocket as _kernel_resolve_program_id_from_exact_websocket,
94
+ resolve_webtransport_program as _kernel_resolve_webtransport_program,
94
95
  )
95
96
  from tigrbl_typing.status.exceptions import HTTPException
96
97
  from tigrbl_typing.phases import canonicalize_phase_input as normalize_phase
@@ -165,5 +166,3 @@ __all__ = [
165
166
  for name in globals()
166
167
  if not name.startswith("__") or name == "__JSONRPC_DEFAULT_ENDPOINT_MAPPINGS__"
167
168
  ]
168
-
169
-
@@ -1,6 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from ._shared import *
4
+ from ...channel._asgi_jsonrpc import _jsonrpc_error_payload
4
5
 
5
6
 
6
7
  class _PackedBatchMixin:
@@ -210,15 +211,7 @@ class _PackedBatchMixin:
210
211
 
211
212
  rpc_code = _HTTP_TO_RPC.get(int(status_code), -32603)
212
213
  data = dict(detail) if isinstance(detail, Mapping) else {"detail": detail}
213
- return {
214
- "jsonrpc": "2.0",
215
- "error": {
216
- "code": rpc_code,
217
- "message": ERROR_MESSAGES.get(rpc_code, "Internal error"),
218
- "data": data,
219
- },
220
- "id": rpc_id,
221
- }
214
+ return _jsonrpc_error_payload(rpc_code, ERROR_MESSAGES.get(rpc_code, 'Internal error'), rpc_id, data=data)
222
215
 
223
216
  @staticmethod
224
217
  def _reject_jsonrpc_wrapper_keys(
@@ -273,11 +266,7 @@ class _PackedBatchMixin:
273
266
  for item in batch_payload:
274
267
  if not isinstance(item, Mapping):
275
268
  responses.append(
276
- {
277
- "jsonrpc": "2.0",
278
- "error": {"code": -32600, "message": "Invalid Request"},
279
- "id": None,
280
- }
269
+ _jsonrpc_error_payload(-32600, 'Invalid Request', None)
281
270
  )
282
271
  continue
283
272
 
@@ -285,25 +274,13 @@ class _PackedBatchMixin:
285
274
  method = item.get("method")
286
275
  if not isinstance(method, str):
287
276
  responses.append(
288
- {
289
- "jsonrpc": "2.0",
290
- "error": {"code": -32601, "message": "Method not found"},
291
- "id": rpc_id,
292
- }
277
+ _jsonrpc_error_payload(-32601, 'Method not found', rpc_id)
293
278
  )
294
279
  continue
295
280
 
296
281
  if not callable(router_or_app) or not scope:
297
282
  responses.append(
298
- {
299
- "jsonrpc": "2.0",
300
- "error": {
301
- "code": -32603,
302
- "message": "Internal error",
303
- "data": {"detail": "RPC invoker unavailable."},
304
- },
305
- "id": rpc_id,
306
- }
283
+ _jsonrpc_error_payload(-32603, 'Internal error', rpc_id, data={'detail': 'RPC invoker unavailable.'})
307
284
  )
308
285
  continue
309
286
 
@@ -323,15 +300,7 @@ class _PackedBatchMixin:
323
300
  await router_or_app(dict(scope), _receive, _send)
324
301
  except Exception as exc:
325
302
  responses.append(
326
- {
327
- "jsonrpc": "2.0",
328
- "error": {
329
- "code": -32603,
330
- "message": "Internal error",
331
- "data": {"detail": str(exc)},
332
- },
333
- "id": rpc_id,
334
- }
303
+ _jsonrpc_error_payload(-32603, 'Internal error', rpc_id, data={'detail': str(exc)})
335
304
  )
336
305
  continue
337
306
 
@@ -349,15 +318,7 @@ class _PackedBatchMixin:
349
318
  try:
350
319
  decoded = json.loads(response_body.decode("utf-8"))
351
320
  except Exception:
352
- decoded = {
353
- "jsonrpc": "2.0",
354
- "error": {
355
- "code": -32603,
356
- "message": "Internal error",
357
- "data": {"detail": response_body.decode("utf-8", errors="replace")},
358
- },
359
- "id": rpc_id,
360
- }
321
+ decoded = _jsonrpc_error_payload(-32603, 'Internal error', rpc_id, data={'detail': response_body.decode('utf-8', errors='replace')})
361
322
  if isinstance(decoded, Mapping):
362
323
  response = dict(decoded)
363
324
  if response.get("id") is None:
@@ -1,6 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from ._shared import *
4
+ from ...channel._asgi_jsonrpc import _jsonrpc_error_payload
4
5
 
5
6
 
6
7
  class _PackedExecuteMixin:
@@ -29,7 +30,11 @@ class _PackedExecuteMixin:
29
30
  ):
30
31
  request = self._ensure_hot_request(ctx, hot)
31
32
  body_bytes = await self._ensure_body_bytes(ctx, hot)
32
- if request is not None and body_bytes is not None and hasattr(request, "body"):
33
+ if (
34
+ request is not None
35
+ and body_bytes is not None
36
+ and hasattr(request, "body")
37
+ ):
33
38
  request.body = body_bytes
34
39
  if not hot.parsed_json_loaded:
35
40
  parsed = None
@@ -40,7 +45,10 @@ class _PackedExecuteMixin:
40
45
  parsed = None
41
46
  hot.parsed_json = parsed
42
47
  hot.parsed_json_loaded = True
43
- if isinstance(hot.parsed_json, Mapping) and hot.parsed_json.get("jsonrpc") == "2.0":
48
+ if (
49
+ isinstance(hot.parsed_json, Mapping)
50
+ and hot.parsed_json.get("jsonrpc") == "2.0"
51
+ ):
44
52
  params = hot.parsed_json.get("params", {})
45
53
  if isinstance(params, Mapping) and set(params) == {"params"}:
46
54
  egress = temp.setdefault("egress", {})
@@ -62,21 +70,30 @@ class _PackedExecuteMixin:
62
70
  )
63
71
  return
64
72
 
65
- program_id = self._require_program_id_from_ctx(ctx)
66
- if program_id < 0:
73
+ if hot.scope_type == "webtransport":
74
+ program_id = self._prime_webtransport_program(ctx, env, plan, packed)
75
+ else:
76
+ program_id = self._require_program_id_from_ctx(ctx)
77
+ if program_id < 0 and hot.scope_type != "webtransport":
67
78
  program_id = self._prime_exact_route_program(ctx, env, packed)
68
- if program_id < 0:
69
- program_id = await self._prime_exact_jsonrpc_program(
70
- ctx, env, plan, packed
71
- )
72
- if program_id < 0:
79
+ if program_id < 0 and hot.scope_type != "webtransport":
80
+ program_id = await self._prime_exact_jsonrpc_program(ctx, env, plan, packed)
81
+ if program_id < 0 and hot.scope_type != "webtransport":
73
82
  program_id = self._prime_exact_channel_program(ctx, env, plan, packed)
74
83
  if program_id < 0:
75
84
  program_id = await self._probe_ingress_for_program(ctx, plan, packed)
76
85
  if program_id < 0:
77
86
  scope = getattr(env, "scope", {}) or {}
78
- transport = hot.egress_transport_response
79
- if isinstance(transport, dict):
87
+ wt_selection = temp.get("webtransport_selection")
88
+ disposition = getattr(wt_selection, "disposition", None)
89
+ rpc_envelope = getattr(wt_selection, "rpc_envelope", None)
90
+ if disposition in {"method_not_found", "invalid_jsonrpc"}:
91
+ ctx.result = _jsonrpc_error_payload(
92
+ -32601 if disposition == "method_not_found" else -32600,
93
+ "Method not found" if disposition == "method_not_found" else "Invalid Request",
94
+ rpc_envelope.get("id") if isinstance(rpc_envelope, Mapping) else None,
95
+ )
96
+ if disposition in {"lifecycle", "method_not_found", "invalid_jsonrpc"} or isinstance(hot.egress_transport_response, dict):
80
97
  await _send_transport_response(env, ctx)
81
98
  return
82
99
 
@@ -176,7 +193,7 @@ class _PackedExecuteMixin:
176
193
  error_phase_segments = self._resolve_error_segments(
177
194
  packed,
178
195
  program_id,
179
- )
196
+ )
180
197
 
181
198
  try:
182
199
  await self._run_hot_jsonrpc_security_dependencies(
@@ -190,10 +207,11 @@ class _PackedExecuteMixin:
190
207
  opview = getattr(ctx, "opview", None)
191
208
  schema_in = getattr(opview, "schema_in", None)
192
209
  field_names = tuple(getattr(schema_in, "fields", ()) or ())
193
- self._reject_jsonrpc_wrapper_keys(
194
- hot.parsed_json.get("params", {}),
195
- field_names=field_names,
196
- )
210
+ if field_names:
211
+ self._reject_jsonrpc_wrapper_keys(
212
+ hot.parsed_json.get("params", {}),
213
+ field_names=field_names,
214
+ )
197
215
  await self._resolve_program_runner(
198
216
  packed,
199
217
  program_id,
@@ -252,8 +270,15 @@ class _PackedExecuteMixin:
252
270
  status_code = int(getattr(std, "status_code", 500) or 500)
253
271
  persistence_error = self._is_persistence_exception(exc)
254
272
  if persistence_error:
255
- status_code = 500
256
- detail = "Internal error"
273
+ if status_code >= 500:
274
+ status_code = 500
275
+ detail = "Internal error"
276
+ else:
277
+ detail = (
278
+ "Conflict"
279
+ if status_code == 409
280
+ else "Persistence constraint failed"
281
+ )
257
282
  payload = self._jsonrpc_error_payload(
258
283
  ctx,
259
284
  status_code,
@@ -102,7 +102,9 @@ class _PackedInputCompileMixin:
102
102
  if not (0 <= slot_id < len(field_names)):
103
103
  continue
104
104
  field_name = field_names[slot_id]
105
- field_meta = by_field.get(field_name, {}) if isinstance(by_field, Mapping) else {}
105
+ field_meta = (
106
+ by_field.get(field_name, {}) if isinstance(by_field, Mapping) else {}
107
+ )
106
108
  source_mask = self._hot_int_at(
107
109
  packed,
108
110
  "param_shape_source_masks",
@@ -156,7 +158,11 @@ class _PackedInputCompileMixin:
156
158
  )
157
159
  paired_index = getattr(opview, "paired_index", {}) or {}
158
160
  for field_name, desc in paired_index.items():
159
- if isinstance(field_name, str) and field_name and field_name not in field_index:
161
+ if (
162
+ isinstance(field_name, str)
163
+ and field_name
164
+ and field_name not in field_index
165
+ ):
160
166
  reserved_input_keys.add(field_name)
161
167
  alias = desc.get("alias") if isinstance(desc, Mapping) else None
162
168
  if isinstance(alias, str) and alias:
@@ -217,18 +223,15 @@ class _PackedInputCompileMixin:
217
223
  continue
218
224
 
219
225
  target_type = field_plan.py_type
220
- if value is not None and isinstance(target_type, type):
221
- ok, new_val, msg = _coerce_if_needed(
222
- value,
223
- target_type,
224
- allow=field_plan.coerce,
225
- )
226
- if not ok:
226
+ if value is not None and target_type is not None:
227
+ try:
228
+ new_val = _materialize_field_value(value, target_type)
229
+ except Exception:
227
230
  errors.append(
228
231
  {
229
232
  "field": field_plan.field_name,
230
233
  "code": "type_mismatch",
231
- "message": msg or f"Expected {target_type.__name__}.",
234
+ "message": f"Could not materialize {target_type!s}.",
232
235
  }
233
236
  )
234
237
  continue
@@ -267,7 +270,9 @@ class _PackedInputCompileMixin:
267
270
  )
268
271
 
269
272
  hot.compiled_in_invalid = bool(errors)
270
- hot.compiled_in_errors = tuple(dict(error) for error in errors) if errors else None
273
+ hot.compiled_in_errors = (
274
+ tuple(dict(error) for error in errors) if errors else None
275
+ )
271
276
  hot.compiled_in_coerced = tuple(coerced)
272
277
  hot.in_present_names = tuple(
273
278
  field_names[idx]
@@ -306,7 +311,11 @@ class _PackedInputCompileMixin:
306
311
 
307
312
  absent.append(field_plan.field_name)
308
313
  default_fn = field_plan.default_factory
309
- if callable(default_fn) and field_plan.in_enabled and not field_plan.is_virtual:
314
+ if (
315
+ callable(default_fn)
316
+ and field_plan.in_enabled
317
+ and not field_plan.is_virtual
318
+ ):
310
319
  if view is None:
311
320
  view = _ctx_view(ctx)
312
321
  try:
@@ -185,7 +185,7 @@ class _PackedInputExtractMixin:
185
185
  body_payload = hot.parsed_json
186
186
  if hot.transport_kind_id == _TRANSPORT_KIND_JSONRPC and isinstance(body_payload, dict):
187
187
  body_payload = body_payload.get("params", {})
188
- if hot.transport_kind_id == _TRANSPORT_KIND_JSONRPC:
188
+ if hot.transport_kind_id == _TRANSPORT_KIND_JSONRPC and plan.field_names:
189
189
  self._reject_jsonrpc_wrapper_keys(
190
190
  body_payload,
191
191
  field_names=plan.field_names,
@@ -1,34 +1,110 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from ._shared import *
4
+ from .route_select_websocket import _PackedWebSocketRouteSelectMixin
4
5
 
5
6
 
6
- class _PackedRouteSelectMixin:
7
- def _resolve_hot_exact_websocket_routes(
8
- self,
9
- plan: KernelPlan,
10
- packed: PackedKernel,
11
- ) -> Mapping[tuple[str, str], int]:
12
- return _kernel_resolve_hot_exact_websocket_routes(
13
- plan,
14
- packed,
15
- self._hot_exact_websocket_route_cache,
16
- )
7
+ class _PackedRouteSelectMixin(_PackedWebSocketRouteSelectMixin):
8
+
17
9
 
18
- def _resolve_program_id_from_exact_websocket(
10
+ def _prime_webtransport_program(
19
11
  self,
12
+ ctx: _Ctx,
13
+ env: Any,
20
14
  plan: KernelPlan,
21
15
  packed: PackedKernel,
22
- protocol: str,
23
- path: str,
24
16
  ) -> int:
25
- return _kernel_resolve_program_id_from_exact_websocket(
17
+ del packed
18
+ hot = self._ensure_hot_ctx(ctx, env)
19
+ if hot.scope_type != "webtransport" or not hot.path:
20
+ return -1
21
+ message = ctx.get("channel_message")
22
+ if not isinstance(message, Mapping):
23
+ return -1
24
+
25
+ selection = _kernel_resolve_webtransport_program(
26
26
  plan,
27
- packed,
28
- protocol,
29
- path,
30
- self._hot_exact_websocket_route_cache,
27
+ path=hot.path,
28
+ message=message,
29
+ )
30
+ temp = getattr(ctx, "temp", None)
31
+ if not isinstance(temp, dict):
32
+ return -1
33
+ temp["webtransport_selection"] = selection
34
+ if selection.path_params is not None:
35
+ path_params = dict(selection.path_params)
36
+ hot.path_params = path_params
37
+ hot.route_path_params = path_params
38
+ temp.setdefault("route", {})["path_params"] = path_params
39
+ temp.setdefault("dispatch", {})["path_params"] = path_params
40
+ channel = ctx.get("channel")
41
+ if channel is not None:
42
+ channel.path_params = path_params
43
+
44
+ if selection.program_id < 0:
45
+ return -1
46
+
47
+ protocol = (
48
+ "webtransport.jsonrpc"
49
+ if selection.rpc_envelope is not None
50
+ else "webtransport"
31
51
  )
52
+ selector = hot.path
53
+ if selection.rpc_envelope is not None:
54
+ rpc_method = selection.rpc_envelope.get("method")
55
+ selector = f"{hot.path}:{rpc_method}"
56
+ params = selection.rpc_envelope.get("params")
57
+ hot.parsed_json = dict(selection.rpc_envelope)
58
+ hot.parsed_json_loaded = True
59
+ hot.route_rpc_envelope = dict(selection.rpc_envelope)
60
+ hot.dispatch_rpc_envelope = dict(selection.rpc_envelope)
61
+ hot.dispatch_jsonrpc_request_id = selection.rpc_envelope.get("id")
62
+ hot.dispatch_rpc_method = (
63
+ rpc_method if isinstance(rpc_method, str) else None
64
+ )
65
+ hot.route_payload = dict(params) if isinstance(params, Mapping) else {}
66
+ route = temp.setdefault("route", {})
67
+ dispatch = temp.setdefault("dispatch", {})
68
+ route["rpc_envelope"] = dict(selection.rpc_envelope)
69
+ route["payload"] = hot.route_payload
70
+ dispatch["rpc"] = dict(selection.rpc_envelope)
71
+ dispatch["rpc_method"] = rpc_method
72
+ dispatch["parsed_payload"] = hot.route_payload
73
+
74
+ program_id = int(selection.program_id)
75
+ hot.program_id = program_id
76
+ hot.route_program_id = program_id
77
+ hot.route_opmeta_index = program_id
78
+ hot.route_protocol = protocol
79
+ hot.route_selector = selector
80
+ hot.dispatch_binding_protocol = protocol
81
+ hot.dispatch_binding_selector = selector
82
+ hot.dispatch_channel_protocol = "webtransport"
83
+ hot.dispatch_channel_selector = hot.path
84
+ hot.transport_kind_id = (
85
+ _TRANSPORT_KIND_JSONRPC
86
+ if selection.rpc_envelope is not None
87
+ else _TRANSPORT_KIND_CHANNEL
88
+ )
89
+ temp["program_id"] = program_id
90
+ temp.setdefault("route", {}).update(
91
+ {
92
+ "program_id": program_id,
93
+ "opmeta_index": program_id,
94
+ "protocol": protocol,
95
+ "selector": selector,
96
+ }
97
+ )
98
+ temp.setdefault("dispatch", {}).update(
99
+ {
100
+ "binding_protocol": protocol,
101
+ "binding_selector": selector,
102
+ "channel_protocol": "webtransport",
103
+ "channel_selector": hot.path,
104
+ }
105
+ )
106
+ ctx.path = hot.path
107
+ return program_id
32
108
 
33
109
  def _prime_exact_channel_program(
34
110
  self,
@@ -140,7 +216,9 @@ class _PackedRouteSelectMixin:
140
216
  owner = ctx.get(owner_key)
141
217
  mounts = getattr(owner, "_jsonrpc_endpoint_mounts", None)
142
218
  if isinstance(mounts, Mapping):
143
- endpoint = mounts.get(path) or mounts.get(hot.path) or mounts.get(f"{path}/")
219
+ endpoint = (
220
+ mounts.get(path) or mounts.get(hot.path) or mounts.get(f"{path}/")
221
+ )
144
222
  if isinstance(endpoint, str) and endpoint:
145
223
  return endpoint
146
224
  for endpoint, mapped_path in __JSONRPC_DEFAULT_ENDPOINT_MAPPINGS__.items():
@@ -0,0 +1,34 @@
1
+ from __future__ import annotations
2
+
3
+ from ._shared import *
4
+
5
+
6
+ class _PackedWebSocketRouteSelectMixin:
7
+ def _resolve_hot_exact_websocket_routes(
8
+ self,
9
+ plan: KernelPlan,
10
+ packed: PackedKernel,
11
+ ) -> Mapping[tuple[str, str], int]:
12
+ return _kernel_resolve_hot_exact_websocket_routes(
13
+ plan,
14
+ packed,
15
+ self._hot_exact_websocket_route_cache,
16
+ )
17
+
18
+
19
+ def _resolve_program_id_from_exact_websocket(
20
+ self,
21
+ plan: KernelPlan,
22
+ packed: PackedKernel,
23
+ protocol: str,
24
+ path: str,
25
+ ) -> int:
26
+ return _kernel_resolve_program_id_from_exact_websocket(
27
+ plan,
28
+ packed,
29
+ protocol,
30
+ path,
31
+ self._hot_exact_websocket_route_cache,
32
+ )
33
+
34
+
@@ -0,0 +1,22 @@
1
+ """Lazy runtime bridge to canonical kernel lowering services.
2
+
3
+ Functions retain their kernel identity. Loading one service does not import
4
+ unrelated protocol chains during concrete model initialization.
5
+ """
6
+ from __future__ import annotations
7
+
8
+ from typing import Any
9
+
10
+ __all__ = ["compile_opview_from_specs", "build_websocket_jsonrpc_session_handler"]
11
+
12
+
13
+ def __getattr__(name: str) -> Any:
14
+ if name == "compile_opview_from_specs":
15
+ from tigrbl_kernel.opview_compiler import compile_opview_from_specs
16
+
17
+ return compile_opview_from_specs
18
+ if name == "build_websocket_jsonrpc_session_handler":
19
+ from tigrbl_kernel.protocol_chains.websocket import build_websocket_jsonrpc_session_handler
20
+
21
+ return build_websocket_jsonrpc_session_handler
22
+ raise AttributeError(name)