tigrbl-kernel 0.4.2.dev4__py3-none-any.whl → 0.4.3.dev4__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.
- tigrbl_kernel/__init__.py +1 -19
- tigrbl_kernel/_build.py +1 -1
- tigrbl_kernel/_compile.py +100 -1
- tigrbl_kernel/channel_taxonomy.py +142 -0
- tigrbl_kernel/cross_transport.py +167 -0
- tigrbl_kernel/dispatch_taxonomy.py +47 -0
- tigrbl_kernel/events.py +10 -0
- tigrbl_kernel/helpers.py +2 -2
- tigrbl_kernel/loop_modes.py +19 -1
- tigrbl_kernel/measure.py +1 -1
- tigrbl_kernel/ordering.py +1 -0
- tigrbl_kernel/packed_access.py +131 -0
- tigrbl_kernel/packed_selectors.py +239 -0
- tigrbl_kernel/protocol_anchors.py +49 -0
- tigrbl_kernel/protocol_bindings.py +26 -7
- tigrbl_kernel/protocol_chains/http_stream.py +35 -2
- tigrbl_kernel/protocol_streams.py +470 -0
- tigrbl_kernel/resume_policy.py +78 -0
- tigrbl_kernel/runtime_contracts.py +254 -0
- tigrbl_kernel/rust_compile.py +11 -27
- tigrbl_kernel/rust_plan.py +9 -3
- tigrbl_kernel/rust_spec.py +15 -398
- tigrbl_kernel/transport_events.py +5 -2
- tigrbl_kernel/types.py +1 -0
- tigrbl_kernel/webtransport_events.py +65 -3
- {tigrbl_kernel-0.4.2.dev4.dist-info → tigrbl_kernel-0.4.3.dev4.dist-info}/METADATA +74 -5
- {tigrbl_kernel-0.4.2.dev4.dist-info → tigrbl_kernel-0.4.3.dev4.dist-info}/RECORD +30 -21
- {tigrbl_kernel-0.4.2.dev4.dist-info → tigrbl_kernel-0.4.3.dev4.dist-info}/WHEEL +0 -0
- {tigrbl_kernel-0.4.2.dev4.dist-info → tigrbl_kernel-0.4.3.dev4.dist-info}/licenses/LICENSE +0 -0
- {tigrbl_kernel-0.4.2.dev4.dist-info → tigrbl_kernel-0.4.3.dev4.dist-info}/licenses/NOTICE +0 -0
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections.abc import Iterable, Mapping, Sequence
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from hashlib import sha256
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
REQUIRED_ATOM_REQUIREMENTS = frozenset(
|
|
10
|
+
{
|
|
11
|
+
"semantic_identity",
|
|
12
|
+
"concrete_identity",
|
|
13
|
+
"engine",
|
|
14
|
+
"transport_delivery",
|
|
15
|
+
"framing",
|
|
16
|
+
"idempotency",
|
|
17
|
+
"retry",
|
|
18
|
+
"replay",
|
|
19
|
+
"session_isolation",
|
|
20
|
+
"engine_session_isolation",
|
|
21
|
+
}
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@dataclass(frozen=True, slots=True)
|
|
26
|
+
class RetryPolicy:
|
|
27
|
+
max_attempts: int
|
|
28
|
+
retryable_errors: tuple[str, ...]
|
|
29
|
+
backoff_ms: tuple[int, ...]
|
|
30
|
+
requires_idempotency_or_replay: bool = True
|
|
31
|
+
owner: str = "runtime"
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def stable_semantic_id(*parts: str) -> str:
|
|
35
|
+
if not parts or any(not str(part).strip() for part in parts):
|
|
36
|
+
raise ValueError("semantic id parts must be non-empty")
|
|
37
|
+
return sha256("::".join(str(part) for part in parts).encode("utf-8")).hexdigest()
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def concrete_id(kind: str, parent_scope: str, local_id: str) -> str:
|
|
41
|
+
if not kind or not parent_scope or not local_id:
|
|
42
|
+
raise ValueError("concrete id requires kind, parent scope, and local id")
|
|
43
|
+
return f"{kind}:{stable_semantic_id(parent_scope, local_id)[:16]}"
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def ensure_unique_concrete_ids(ids: Iterable[str]) -> tuple[str, ...]:
|
|
47
|
+
values = tuple(ids)
|
|
48
|
+
if len(set(values)) != len(values):
|
|
49
|
+
raise ValueError("concrete ids must be unique within parent scope")
|
|
50
|
+
return values
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def attempt_record(
|
|
54
|
+
*,
|
|
55
|
+
app_id: str,
|
|
56
|
+
router_id: str,
|
|
57
|
+
table_id: str,
|
|
58
|
+
op_id: str,
|
|
59
|
+
attempt_id: str,
|
|
60
|
+
) -> dict[str, str]:
|
|
61
|
+
return {
|
|
62
|
+
"app_id": app_id,
|
|
63
|
+
"router_id": router_id,
|
|
64
|
+
"table_id": table_id,
|
|
65
|
+
"op_id": op_id,
|
|
66
|
+
"attempt_id": attempt_id,
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def atom_execution_record(
|
|
71
|
+
*,
|
|
72
|
+
atom_id: str,
|
|
73
|
+
attempt_id: str,
|
|
74
|
+
runtime_plan_id: str,
|
|
75
|
+
) -> dict[str, str]:
|
|
76
|
+
return {
|
|
77
|
+
"atom_id": atom_id,
|
|
78
|
+
"attempt_id": attempt_id,
|
|
79
|
+
"runtime_plan_id": runtime_plan_id,
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def replay_record(*, replay_id: str, original_attempt_id: str) -> dict[str, str]:
|
|
84
|
+
return {"replay_id": replay_id, "original_attempt_id": original_attempt_id}
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def project_atom_chain_requirements(
|
|
88
|
+
chain: Sequence[Mapping[str, Any]],
|
|
89
|
+
) -> dict[str, Any]:
|
|
90
|
+
if not chain:
|
|
91
|
+
raise ValueError("atom chain must contain at least one atom")
|
|
92
|
+
atom_order: list[str] = []
|
|
93
|
+
hook_order: list[str] = []
|
|
94
|
+
capability_vector: set[str] = set()
|
|
95
|
+
merged: dict[str, Any] = {}
|
|
96
|
+
diagnostics: list[dict[str, str]] = []
|
|
97
|
+
for index, atom in enumerate(chain):
|
|
98
|
+
atom_id = str(atom.get("atom_id") or "")
|
|
99
|
+
hook_id = str(atom.get("hook_id") or "")
|
|
100
|
+
requirements = atom.get("requirements")
|
|
101
|
+
if not atom_id or not hook_id or not isinstance(requirements, Mapping):
|
|
102
|
+
raise ValueError("atom requirement metadata is required")
|
|
103
|
+
missing = REQUIRED_ATOM_REQUIREMENTS - set(requirements)
|
|
104
|
+
if missing:
|
|
105
|
+
raise ValueError(f"missing atom requirement metadata: {sorted(missing)[0]}")
|
|
106
|
+
atom_order.append(atom_id)
|
|
107
|
+
hook_order.append(hook_id)
|
|
108
|
+
diagnostics.append(
|
|
109
|
+
{
|
|
110
|
+
"atom_id": atom_id,
|
|
111
|
+
"hook_id": hook_id,
|
|
112
|
+
"requirement_source": str(atom.get("source") or f"chain[{index}]"),
|
|
113
|
+
}
|
|
114
|
+
)
|
|
115
|
+
for key, value in requirements.items():
|
|
116
|
+
merged[key] = _merge_requirement(merged.get(key), value)
|
|
117
|
+
capability_vector.add(f"{key}:{_freeze(value)!r}")
|
|
118
|
+
return {
|
|
119
|
+
"requirements": dict(sorted(merged.items())),
|
|
120
|
+
"capability_vector": tuple(sorted(capability_vector)),
|
|
121
|
+
"atom_order": tuple(atom_order),
|
|
122
|
+
"hook_order": tuple(hook_order),
|
|
123
|
+
"diagnostics": tuple(diagnostics),
|
|
124
|
+
"rollup": {
|
|
125
|
+
"atom_count": len(atom_order),
|
|
126
|
+
"capability_count": len(capability_vector),
|
|
127
|
+
},
|
|
128
|
+
"compaction": {
|
|
129
|
+
"atom_order_hash": stable_semantic_id(*atom_order),
|
|
130
|
+
"capability_hash": stable_semantic_id(*tuple(sorted(capability_vector))),
|
|
131
|
+
},
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def compile_retry_policy(
|
|
136
|
+
*,
|
|
137
|
+
max_attempts: int,
|
|
138
|
+
retryable_errors: Iterable[str],
|
|
139
|
+
backoff_ms: Iterable[int],
|
|
140
|
+
requires_idempotency_or_replay: bool = True,
|
|
141
|
+
owner: str = "runtime",
|
|
142
|
+
) -> RetryPolicy:
|
|
143
|
+
errors = tuple(str(item) for item in retryable_errors)
|
|
144
|
+
backoff = tuple(int(item) for item in backoff_ms)
|
|
145
|
+
if max_attempts < 1:
|
|
146
|
+
raise ValueError("retry max_attempts must be at least one")
|
|
147
|
+
if owner != "runtime":
|
|
148
|
+
raise ValueError("retry owner must be runtime")
|
|
149
|
+
if len(backoff) > max_attempts - 1:
|
|
150
|
+
raise ValueError("retry backoff cannot exceed retry budget")
|
|
151
|
+
return RetryPolicy(max_attempts, errors, backoff, requires_idempotency_or_replay, owner)
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def evaluate_retry_attempts(
|
|
155
|
+
*,
|
|
156
|
+
policy: RetryPolicy | None,
|
|
157
|
+
failures: Sequence[str],
|
|
158
|
+
idempotency_key: str | None = None,
|
|
159
|
+
replay_policy: str | None = None,
|
|
160
|
+
original_attempt_id: str = "attempt-1",
|
|
161
|
+
session_scope: str = "session-1",
|
|
162
|
+
retry_session_scope: str = "session-1",
|
|
163
|
+
transaction_open: bool = False,
|
|
164
|
+
) -> tuple[dict[str, Any], ...]:
|
|
165
|
+
if policy is None:
|
|
166
|
+
raise ValueError("retry policy is required before attempt execution")
|
|
167
|
+
if policy.requires_idempotency_or_replay and not (idempotency_key or replay_policy):
|
|
168
|
+
raise ValueError("retry requires idempotency key or replay policy")
|
|
169
|
+
if session_scope != retry_session_scope:
|
|
170
|
+
raise ValueError("retry cannot cross session stream datagram or engine session scope")
|
|
171
|
+
if transaction_open:
|
|
172
|
+
raise ValueError("retry cannot reuse open transaction from failed attempt")
|
|
173
|
+
attempts: list[dict[str, Any]] = [
|
|
174
|
+
{
|
|
175
|
+
"attempt_id": original_attempt_id,
|
|
176
|
+
"parent_attempt_id": None,
|
|
177
|
+
"decision": "original",
|
|
178
|
+
"backoff_ms": 0,
|
|
179
|
+
}
|
|
180
|
+
]
|
|
181
|
+
for index, failure in enumerate(failures):
|
|
182
|
+
if failure not in policy.retryable_errors:
|
|
183
|
+
attempts.append(
|
|
184
|
+
{
|
|
185
|
+
"attempt_id": f"attempt-{index + 2}",
|
|
186
|
+
"parent_attempt_id": original_attempt_id,
|
|
187
|
+
"decision": "stop",
|
|
188
|
+
"error": failure,
|
|
189
|
+
"backoff_ms": 0,
|
|
190
|
+
}
|
|
191
|
+
)
|
|
192
|
+
break
|
|
193
|
+
if len(attempts) >= policy.max_attempts:
|
|
194
|
+
attempts.append(
|
|
195
|
+
{
|
|
196
|
+
"attempt_id": f"attempt-{index + 2}",
|
|
197
|
+
"parent_attempt_id": original_attempt_id,
|
|
198
|
+
"decision": "budget-exhausted",
|
|
199
|
+
"error": failure,
|
|
200
|
+
"backoff_ms": 0,
|
|
201
|
+
}
|
|
202
|
+
)
|
|
203
|
+
break
|
|
204
|
+
attempts.append(
|
|
205
|
+
{
|
|
206
|
+
"attempt_id": f"attempt-{index + 2}",
|
|
207
|
+
"parent_attempt_id": original_attempt_id,
|
|
208
|
+
"decision": "retry",
|
|
209
|
+
"error": failure,
|
|
210
|
+
"backoff_ms": policy.backoff_ms[min(index, len(policy.backoff_ms) - 1)]
|
|
211
|
+
if policy.backoff_ms
|
|
212
|
+
else 0,
|
|
213
|
+
}
|
|
214
|
+
)
|
|
215
|
+
return tuple(attempts)
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
def _merge_requirement(existing: Any, incoming: Any) -> Any:
|
|
219
|
+
if existing is None:
|
|
220
|
+
return _freeze(incoming)
|
|
221
|
+
if existing == incoming:
|
|
222
|
+
return existing
|
|
223
|
+
if isinstance(existing, tuple):
|
|
224
|
+
values = set(existing)
|
|
225
|
+
else:
|
|
226
|
+
values = {existing}
|
|
227
|
+
if isinstance(incoming, (tuple, list, set)):
|
|
228
|
+
values.update(_freeze(item) for item in incoming)
|
|
229
|
+
else:
|
|
230
|
+
values.add(_freeze(incoming))
|
|
231
|
+
return tuple(sorted(values, key=repr))
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
def _freeze(value: Any) -> Any:
|
|
235
|
+
if isinstance(value, Mapping):
|
|
236
|
+
return tuple((key, _freeze(value[key])) for key in sorted(value))
|
|
237
|
+
if isinstance(value, (list, tuple, set, frozenset)):
|
|
238
|
+
return tuple(_freeze(item) for item in value)
|
|
239
|
+
return value
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
__all__ = [
|
|
243
|
+
"REQUIRED_ATOM_REQUIREMENTS",
|
|
244
|
+
"RetryPolicy",
|
|
245
|
+
"attempt_record",
|
|
246
|
+
"atom_execution_record",
|
|
247
|
+
"compile_retry_policy",
|
|
248
|
+
"concrete_id",
|
|
249
|
+
"ensure_unique_concrete_ids",
|
|
250
|
+
"evaluate_retry_attempts",
|
|
251
|
+
"project_atom_chain_requirements",
|
|
252
|
+
"replay_record",
|
|
253
|
+
"stable_semantic_id",
|
|
254
|
+
]
|
tigrbl_kernel/rust_compile.py
CHANGED
|
@@ -1,40 +1,24 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
|
-
from importlib import import_module
|
|
3
2
|
from typing import Any
|
|
4
3
|
|
|
5
4
|
from .rust_plan import RustPlan
|
|
6
|
-
|
|
5
|
+
import warnings
|
|
7
6
|
|
|
8
7
|
|
|
9
|
-
def
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
rust.normalize_spec,
|
|
8
|
+
def _raise_deprecated(action: str) -> None:
|
|
9
|
+
warnings.warn(
|
|
10
|
+
"tigrbl_kernel Rust planning is deprecated; kernel planning is Python-only.",
|
|
11
|
+
DeprecationWarning,
|
|
12
|
+
stacklevel=3,
|
|
15
13
|
)
|
|
14
|
+
raise RuntimeError(f"{action} is unavailable; Tigrbl kernel planning is Python-only.")
|
|
16
15
|
|
|
17
16
|
|
|
18
17
|
def build_rust_kernel(app: Any) -> RustPlan:
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
normalized = normalize_spec(payload)
|
|
22
|
-
compiled = compile_app(payload)
|
|
23
|
-
return RustPlan(
|
|
24
|
-
description=f"compiled rust KernelPlan for {compiled.get('app_name', payload['name'])}",
|
|
25
|
-
compiled_plan=compiled,
|
|
26
|
-
backend="rust",
|
|
27
|
-
normalized_spec=normalized,
|
|
28
|
-
parity_snapshot=rust_parity_snapshot(payload),
|
|
29
|
-
claimable=False,
|
|
30
|
-
)
|
|
18
|
+
del app
|
|
19
|
+
_raise_deprecated("build_rust_kernel")
|
|
31
20
|
|
|
32
21
|
|
|
33
22
|
def normalize_rust_spec(app: Any) -> str:
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
def build_rust_parity_snapshot(app: Any) -> dict[str, object]:
|
|
39
|
-
_, rust_parity_snapshot, _ = _runtime_rust_helpers()
|
|
40
|
-
return rust_parity_snapshot(build_rust_app_spec(app))
|
|
23
|
+
del app
|
|
24
|
+
_raise_deprecated("normalize_rust_spec")
|
tigrbl_kernel/rust_plan.py
CHANGED
|
@@ -2,13 +2,19 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
from dataclasses import dataclass
|
|
4
4
|
from typing import Any
|
|
5
|
+
import warnings
|
|
5
6
|
|
|
6
7
|
|
|
7
8
|
@dataclass(slots=True)
|
|
8
9
|
class RustPlan:
|
|
9
10
|
description: str
|
|
10
11
|
compiled_plan: dict[str, Any] | None = None
|
|
11
|
-
backend: str = "rust"
|
|
12
|
+
backend: str = "deprecated-rust"
|
|
12
13
|
normalized_spec: str | None = None
|
|
13
|
-
|
|
14
|
-
|
|
14
|
+
|
|
15
|
+
def __post_init__(self) -> None:
|
|
16
|
+
warnings.warn(
|
|
17
|
+
"RustPlan is deprecated; Tigrbl kernel planning is Python-only.",
|
|
18
|
+
DeprecationWarning,
|
|
19
|
+
stacklevel=2,
|
|
20
|
+
)
|