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.
@@ -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
+ ]
@@ -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
- from .rust_spec import build_rust_app_spec
5
+ import warnings
7
6
 
8
7
 
9
- def _runtime_rust_helpers():
10
- rust = import_module("tigrbl_runtime.rust")
11
- return (
12
- rust.compile_app,
13
- rust.rust_parity_snapshot,
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
- compile_app, rust_parity_snapshot, normalize_spec = _runtime_rust_helpers()
20
- payload = build_rust_app_spec(app)
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
- _, _, normalize_spec = _runtime_rust_helpers()
35
- return normalize_spec(build_rust_app_spec(app))
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")
@@ -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
- parity_snapshot: dict[str, Any] | None = None
14
- claimable: bool = False
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
+ )