tigrbl-kernel 0.1.0.dev1__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,49 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any, Dict, List, Mapping, Optional
4
+
5
+ from tigrbl_runtime.hook_types import StepFn
6
+ from tigrbl_runtime.executor import _Ctx, _invoke
7
+ from .core import Kernel
8
+ from .models import OpView, SchemaIn, SchemaOut
9
+
10
+ _default_kernel = Kernel()
11
+
12
+
13
+ def get_cached_specs(model: type) -> Mapping[str, Any]:
14
+ """Atoms can call this; zero per-request collection."""
15
+ return _default_kernel.get_specs(model)
16
+
17
+
18
+ def build_phase_chains(model: type, alias: str) -> Dict[str, List[StepFn]]:
19
+ return _default_kernel.build_op(model, alias)
20
+
21
+
22
+ def plan_labels(model: type, alias: str) -> list[str]:
23
+ return _default_kernel.plan_labels(model, alias)
24
+
25
+
26
+ async def run(
27
+ model: type,
28
+ alias: str,
29
+ *,
30
+ db: Any,
31
+ request: Any | None = None,
32
+ ctx: Optional[Mapping[str, Any]] = None,
33
+ ) -> Any:
34
+ phases = _default_kernel.build_op(model, alias)
35
+ base_ctx = _Ctx.ensure(request=request, db=db, seed=ctx)
36
+ return await _invoke(request=request, db=db, phases=phases, ctx=base_ctx)
37
+
38
+
39
+ __all__ = [
40
+ "Kernel",
41
+ "OpView",
42
+ "SchemaIn",
43
+ "SchemaOut",
44
+ "get_cached_specs",
45
+ "_default_kernel",
46
+ "build_phase_chains",
47
+ "plan_labels",
48
+ "run",
49
+ ]
@@ -0,0 +1,234 @@
1
+ from __future__ import annotations
2
+
3
+ import importlib
4
+ import inspect
5
+ import logging
6
+ import pkgutil
7
+ from types import SimpleNamespace
8
+ from typing import (
9
+ Any,
10
+ Callable,
11
+ Dict,
12
+ Iterable,
13
+ List,
14
+ Mapping,
15
+ Optional,
16
+ Sequence,
17
+ cast,
18
+ )
19
+
20
+ from tigrbl_runtime.hook_types import PHASES as HOOK_PHASES
21
+ from tigrbl_runtime.hook_types import StepFn
22
+ from tigrbl_runtime import events as _ev, ordering as _ordering, system as _sys
23
+
24
+ logger = logging.getLogger(__name__)
25
+
26
+ _AtomRun = Callable[[Optional[object], Any], Any]
27
+ _DiscoveredAtom = tuple[str, _AtomRun]
28
+
29
+
30
+ def _discover_atoms() -> list[_DiscoveredAtom]:
31
+ out: list[_DiscoveredAtom] = []
32
+ try:
33
+ import tigrbl_concrete.atoms as atoms_pkg # type: ignore
34
+ except Exception:
35
+ return out
36
+
37
+ for info in pkgutil.walk_packages(atoms_pkg.__path__, atoms_pkg.__name__ + "."): # type: ignore[attr-defined]
38
+ if info.ispkg:
39
+ continue
40
+ try:
41
+ mod = importlib.import_module(info.name)
42
+ anchor = getattr(mod, "ANCHOR", None)
43
+ run = getattr(mod, "run", None)
44
+ if isinstance(anchor, str) and callable(run):
45
+ out.append((anchor, run))
46
+ except Exception:
47
+ continue
48
+ logger.debug("kernel: discovered %d atoms", len(out))
49
+ return out
50
+
51
+
52
+ def _infer_domain_subject(run: _AtomRun) -> tuple[Optional[str], Optional[str]]:
53
+ mod = getattr(run, "__module__", "") or ""
54
+ parts = mod.split(".")
55
+ try:
56
+ i = parts.index("atoms")
57
+ return (
58
+ parts[i + 1] if i + 1 < len(parts) else None,
59
+ parts[i + 2] if i + 2 < len(parts) else None,
60
+ )
61
+ except ValueError:
62
+ return None, None
63
+
64
+
65
+ def _make_label(anchor: str, run: _AtomRun) -> Optional[str]:
66
+ domain, subject = _infer_domain_subject(run)
67
+ return f"atom:{domain}:{subject}@{anchor}" if (domain and subject) else None
68
+
69
+
70
+ def _wrap_atom(run: _AtomRun, *, anchor: str) -> StepFn:
71
+ use_two_args = True
72
+ try:
73
+ params = tuple(inspect.signature(run).parameters.values())
74
+ positional = [
75
+ p
76
+ for p in params
77
+ if p.kind
78
+ in (
79
+ inspect.Parameter.POSITIONAL_ONLY,
80
+ inspect.Parameter.POSITIONAL_OR_KEYWORD,
81
+ )
82
+ ]
83
+ # Kernel atoms default to ``run(obj, ctx)``. Support legacy
84
+ # ``run(ctx)`` atoms without masking TypeError raised inside the atom.
85
+ use_two_args = len(positional) != 1
86
+ except (TypeError, ValueError):
87
+ use_two_args = True
88
+
89
+ async def _step(ctx: Any) -> Any:
90
+ if use_two_args:
91
+ rv = run(None, ctx)
92
+ else:
93
+ rv = run(ctx) # type: ignore[misc]
94
+ if hasattr(rv, "__await__"):
95
+ return await cast(Any, rv)
96
+ return rv
97
+
98
+ label = getattr(run, "__tigrbl_label", None)
99
+ if not isinstance(label, str):
100
+ label = _make_label(anchor, run)
101
+ if label:
102
+ setattr(_step, "__tigrbl_label", label)
103
+ return _step
104
+
105
+
106
+ def _hook_phase_chains(model: type, alias: str) -> Dict[str, List[StepFn]]:
107
+ hooks_root = getattr(model, "hooks", None) or SimpleNamespace()
108
+ alias_ns = getattr(hooks_root, alias, None)
109
+ out: Dict[str, List[StepFn]] = {ph: [] for ph in HOOK_PHASES}
110
+ if alias_ns is None:
111
+ return out
112
+ for phase in HOOK_PHASES:
113
+ out[phase] = list(getattr(alias_ns, phase, []) or [])
114
+ return out
115
+
116
+
117
+ def _is_persistent(chains: Mapping[str, Sequence[StepFn]]) -> bool:
118
+ for fn in chains.get("START_TX", ()) or ():
119
+ if getattr(fn, "__name__", "") == "start_tx":
120
+ return True
121
+ for fn in chains.get("PRE_TX_BEGIN", ()) or ():
122
+ if getattr(fn, "__name__", "") == "mark_skip_persist":
123
+ return False
124
+ return False
125
+
126
+
127
+ def _label_dep_atom(*, kind: str, index: int, anchor: str) -> str:
128
+ return f"hook:dep:{kind}:{index}@{anchor}"
129
+
130
+
131
+ def _make_dep_atom_step(run_fn: _AtomRun, dep: Any, *, label: str) -> StepFn:
132
+ async def _step(ctx: Any) -> Any:
133
+ rv = run_fn(dep, ctx)
134
+ if hasattr(rv, "__await__"):
135
+ return await cast(Any, rv)
136
+ return rv
137
+
138
+ setattr(_step, "__tigrbl_label", label)
139
+ return _step
140
+
141
+
142
+ def _inject_pre_tx_dep_atoms(chains: Dict[str, List[StepFn]], sp: Any | None) -> None:
143
+ if sp is None:
144
+ return
145
+ try:
146
+ from tigrbl_concrete.atoms.dep.security import run as sec_run
147
+ from tigrbl_concrete.atoms.dep.extra import run as dep_run
148
+ except Exception:
149
+ return
150
+
151
+ pre_tx = chains.setdefault("PRE_TX_BEGIN", [])
152
+ for idx, dep in enumerate(getattr(sp, "secdeps", ()) or ()):
153
+ pre_tx.append(
154
+ _make_dep_atom_step(
155
+ sec_run,
156
+ dep,
157
+ label=_label_dep_atom(
158
+ kind="security",
159
+ index=idx,
160
+ anchor=_ev.DEP_SECURITY,
161
+ ),
162
+ )
163
+ )
164
+ for idx, dep in enumerate(getattr(sp, "deps", ()) or ()):
165
+ pre_tx.append(
166
+ _make_dep_atom_step(
167
+ dep_run,
168
+ dep,
169
+ label=_label_dep_atom(
170
+ kind="extra",
171
+ index=idx,
172
+ anchor=_ev.DEP_EXTRA,
173
+ ),
174
+ )
175
+ )
176
+
177
+
178
+ def _inject_atoms(
179
+ chains: Dict[str, List[StepFn]],
180
+ atoms: Iterable[_DiscoveredAtom],
181
+ *,
182
+ persistent: bool,
183
+ ) -> None:
184
+ order = {name: i for i, name in enumerate(_ev.all_events_ordered())}
185
+
186
+ def _sort_key(item: _DiscoveredAtom) -> tuple[int, int]:
187
+ anchor, run = item
188
+ anchor_idx = order.get(anchor, 10_000)
189
+ domain, subject = _infer_domain_subject(run)
190
+ token = f"{domain}:{subject}" if domain and subject else ""
191
+ pref = _ordering._PREF.get(anchor, ())
192
+ token_idx = pref.index(token) if token in pref else 10_000
193
+ return anchor_idx, token_idx
194
+
195
+ for anchor, run in sorted(atoms, key=_sort_key):
196
+ if _ev.is_valid_event(anchor):
197
+ info = _ev.get_anchor_info(anchor)
198
+ phase = info.phase
199
+ persist_tied = info.persist_tied
200
+ elif anchor in _ev.PHASES:
201
+ phase = anchor
202
+ persist_tied = False
203
+ else:
204
+ continue
205
+
206
+ if phase in ("START_TX", "END_TX"):
207
+ continue
208
+ if not persistent and persist_tied:
209
+ continue
210
+ if anchor == _ev.SYS_HANDLER_PERSISTENCE and chains.get("HANDLER"):
211
+ continue
212
+ domain, _subject = _infer_domain_subject(run)
213
+ if domain == "dep":
214
+ continue
215
+
216
+ chains.setdefault(phase, []).append(_wrap_atom(run, anchor=anchor))
217
+
218
+
219
+ def _inject_txn_system_steps(
220
+ chains: Dict[str, List[StepFn]], *, model: Any | None = None
221
+ ) -> None:
222
+ start_anchor, start_run = _sys.get("txn", "begin")
223
+ end_anchor, end_run = _sys.get("txn", "commit")
224
+ chains.setdefault(start_anchor, []).append(
225
+ _wrap_atom(start_run, anchor=start_anchor)
226
+ )
227
+
228
+ if not chains.get(_sys.HANDLER) and _sys.can_resolve_handler(model):
229
+ handler_anchor, handler_run = _sys.get("handler", "crud")
230
+ chains.setdefault(handler_anchor, []).append(
231
+ _wrap_atom(handler_run, anchor=handler_anchor)
232
+ )
233
+
234
+ chains.setdefault(end_anchor, []).append(_wrap_atom(end_run, anchor=end_anchor))
@@ -0,0 +1,85 @@
1
+ from __future__ import annotations
2
+
3
+ import threading
4
+ import weakref
5
+ from typing import Any, Dict, Generic, Mapping, Optional, Sequence, TypeVar
6
+
7
+ from tigrbl_canon.mapping.column_mro_collect import mro_collect_columns
8
+
9
+ K = TypeVar("K")
10
+ V = TypeVar("V")
11
+
12
+
13
+ class _WeakMaybeDict(Generic[K, V]):
14
+ """Dictionary that uses weak references when possible.
15
+
16
+ Falls back to strong references when ``key`` cannot be weakly referenced.
17
+ """
18
+
19
+ def __init__(self) -> None:
20
+ self._weak: "weakref.WeakKeyDictionary[Any, V]" = weakref.WeakKeyDictionary()
21
+ self._strong: Dict[int, tuple[Any, V]] = {}
22
+
23
+ def _use_weak(self, key: Any) -> bool:
24
+ try:
25
+ weakref.ref(key)
26
+ return True
27
+ except TypeError:
28
+ return False
29
+
30
+ def __setitem__(self, key: K, value: V) -> None:
31
+ if self._use_weak(key):
32
+ self._weak[key] = value
33
+ else:
34
+ self._strong[id(key)] = (key, value)
35
+
36
+ def __getitem__(self, key: K) -> V:
37
+ if self._use_weak(key):
38
+ return self._weak[key]
39
+ return self._strong[id(key)][1]
40
+
41
+ def get(self, key: K, default: Optional[V] = None) -> Optional[V]:
42
+ if self._use_weak(key):
43
+ return self._weak.get(key, default)
44
+ return self._strong.get(id(key), (None, default))[1]
45
+
46
+ def setdefault(self, key: K, default: V) -> V:
47
+ if self._use_weak(key):
48
+ return self._weak.setdefault(key, default)
49
+ return self._strong.setdefault(id(key), (key, default))[1]
50
+
51
+ def pop(self, key: K, default: Optional[V] = None) -> Optional[V]:
52
+ if self._use_weak(key):
53
+ return self._weak.pop(key, default)
54
+ return self._strong.pop(id(key), (None, default))[1]
55
+
56
+
57
+ class _SpecsOnceCache:
58
+ """Thread-safe, compute-once cache of per-model column specs."""
59
+
60
+ def __init__(self) -> None:
61
+ self._d: Dict[type, Mapping[str, Any]] = {}
62
+ self._lock = threading.Lock()
63
+
64
+ def get(self, model: type) -> Mapping[str, Any]:
65
+ try:
66
+ return self._d[model]
67
+ except KeyError:
68
+ pass
69
+ with self._lock:
70
+ rv = self._d.get(model)
71
+ if rv is None:
72
+ rv = mro_collect_columns(model)
73
+ self._d[model] = rv
74
+ return rv
75
+
76
+ def prime(self, models: Sequence[type]) -> None:
77
+ for model in models:
78
+ self.get(model)
79
+
80
+ def invalidate(self, model: Optional[type] = None) -> None:
81
+ with self._lock:
82
+ if model is None:
83
+ self._d.clear()
84
+ else:
85
+ self._d.pop(model, None)
@@ -0,0 +1,582 @@
1
+ from __future__ import annotations
2
+
3
+ import logging
4
+ import re
5
+ import threading
6
+ from types import SimpleNamespace
7
+ from typing import Any, ClassVar, Dict, List, Mapping, Optional, Sequence, Tuple
8
+
9
+ from tigrbl_runtime.hook_types import StepFn
10
+ from tigrbl_runtime.executor import _Ctx, _invoke
11
+ from tigrbl_runtime import events as _ev
12
+ from .atoms import (
13
+ _DiscoveredAtom,
14
+ _discover_atoms,
15
+ _hook_phase_chains,
16
+ _inject_atoms,
17
+ _inject_pre_tx_dep_atoms,
18
+ _inject_txn_system_steps,
19
+ _is_persistent,
20
+ _wrap_atom,
21
+ )
22
+ from .cache import _SpecsOnceCache, _WeakMaybeDict
23
+ from .models import KernelPlan, OpKey, OpMeta, OpView
24
+ from tigrbl_runtime.labels import label_hook
25
+ from .opview_compiler import compile_opview_from_specs
26
+
27
+ logger = logging.getLogger(__name__)
28
+
29
+
30
+ class _RestMatcher:
31
+ """REST selector matcher supporting exact and templated paths."""
32
+
33
+ def __init__(self) -> None:
34
+ self._exact: dict[tuple[str, str], int] = {}
35
+ self._templated: list[tuple[str, re.Pattern[str], tuple[str, ...], int]] = []
36
+ self._selectors: dict[str, int] = {}
37
+
38
+ @staticmethod
39
+ def _normalize_path(path: str) -> str:
40
+ p = path if path.startswith("/") else f"/{path}"
41
+ return p.rstrip("/") or "/"
42
+
43
+ def add(self, method: str, path: str, meta_index: int) -> None:
44
+ m = method.upper()
45
+ normalized = self._normalize_path(path)
46
+ self._selectors[f"{m} {normalized}"] = meta_index
47
+ if "{" not in normalized:
48
+ self._exact[(m, normalized)] = meta_index
49
+ return
50
+
51
+ names = tuple(re.findall(r"\{([^{}]+)\}", normalized))
52
+ pattern = "^" + re.sub(r"\{([^{}]+)\}", r"(?P<\1>[^/]+)", normalized) + "$"
53
+ self._templated.append((m, re.compile(pattern), names, meta_index))
54
+
55
+ def match(self, method: str, path: str) -> tuple[int, dict[str, str]]:
56
+ m = method.upper()
57
+ normalized = self._normalize_path(path)
58
+
59
+ exact = self._exact.get((m, normalized))
60
+ if exact is not None:
61
+ return exact, {}
62
+
63
+ for templ_method, pattern, names, meta_index in self._templated:
64
+ if templ_method != m:
65
+ continue
66
+ matched = pattern.match(normalized)
67
+ if matched is None:
68
+ continue
69
+ params = {
70
+ name: matched.group(name)
71
+ for name in names
72
+ if matched.group(name) is not None
73
+ }
74
+ return meta_index, params
75
+
76
+ raise KeyError((m, normalized))
77
+
78
+ def __call__(self, method: str, path: str) -> tuple[int, dict[str, str]]:
79
+ return self.match(method, path)
80
+
81
+ # Compatibility mapping surface used by older tests and adapters.
82
+ def __contains__(self, selector: object) -> bool:
83
+ return isinstance(selector, str) and selector in self._selectors
84
+
85
+ def __getitem__(self, selector: str) -> int:
86
+ return self._selectors[selector]
87
+
88
+ def get(self, selector: str, default: Any = None) -> Any:
89
+ return self._selectors.get(selector, default)
90
+
91
+ def keys(self):
92
+ return self._selectors.keys()
93
+
94
+ def items(self):
95
+ return self._selectors.items()
96
+
97
+ def values(self):
98
+ return self._selectors.values()
99
+
100
+ def __iter__(self):
101
+ return iter(self._selectors)
102
+
103
+ def __len__(self) -> int:
104
+ return len(self._selectors)
105
+
106
+
107
+ def deepmerge_phase_chains(
108
+ *phase_maps: Mapping[str, Sequence[StepFn]],
109
+ ) -> Dict[str, List[StepFn]]:
110
+ """Deterministically concatenate phase step lists into a new mapping."""
111
+ merged: Dict[str, List[StepFn]] = {}
112
+ for phase_map in phase_maps:
113
+ for phase, steps in (phase_map or {}).items():
114
+ merged.setdefault(phase, []).extend(list(steps or ()))
115
+ return {phase: list(steps) for phase, steps in merged.items()}
116
+
117
+
118
+ def _table_iter(app: Any) -> Sequence[type]:
119
+ tables = getattr(app, "tables", None)
120
+ if isinstance(tables, dict):
121
+ return tuple(v for v in tables.values() if isinstance(v, type))
122
+ return ()
123
+
124
+
125
+ def _opspecs(model: type) -> Sequence[Any]:
126
+ return getattr(getattr(model, "opspecs", SimpleNamespace()), "all", ()) or ()
127
+
128
+
129
+ def _label_callable(fn: Any) -> str:
130
+ name = getattr(fn, "__qualname__", getattr(fn, "__name__", repr(fn)))
131
+ module = getattr(fn, "__module__", None)
132
+ return f"{module}.{name}" if module else name
133
+
134
+
135
+ def _label_step(step: Any, phase: str) -> str:
136
+ label = getattr(step, "__tigrbl_label", None)
137
+ if isinstance(label, str) and "@" in label:
138
+ return label
139
+ module = getattr(step, "__module__", "") or ""
140
+ name = getattr(step, "__name__", "") or ""
141
+ if module.startswith("tigrbl_core.core.crud") and name:
142
+ return f"hook:wire:tigrbl:core:crud:ops:{name}@{phase}"
143
+ return f"hook:wire:{_label_callable(step).replace('.', ':')}@{phase}"
144
+
145
+
146
+ class Kernel:
147
+ """
148
+ SSoT for runtime scheduling. One Kernel per App (not per API).
149
+ Auto-primed under the hood. Downstream users never touch this.
150
+ """
151
+
152
+ _instance: ClassVar["Kernel | None"] = None
153
+
154
+ def __new__(cls, *args: Any, **kwargs: Any) -> "Kernel":
155
+ if cls._instance is None:
156
+ cls._instance = super().__new__(cls)
157
+ return cls._instance
158
+
159
+ def __init__(self, atoms: Optional[Sequence[_DiscoveredAtom]] = None):
160
+ if atoms is None and getattr(self, "_singleton_initialized", False):
161
+ self._reset(atoms)
162
+ return
163
+ self._reset(atoms)
164
+ if atoms is None:
165
+ self._singleton_initialized = True
166
+
167
+ def _reset(self, atoms: Optional[Sequence[_DiscoveredAtom]] = None) -> None:
168
+ self._atoms_cache = list(atoms) if atoms else None
169
+ self._specs_cache = _SpecsOnceCache()
170
+ self._opviews = _WeakMaybeDict()
171
+ self._kernel_plans = _WeakMaybeDict()
172
+ self._kernelz_payload = _WeakMaybeDict()
173
+ self._primed = _WeakMaybeDict()
174
+ self._lock = threading.Lock()
175
+
176
+ def _atoms(self) -> list[_DiscoveredAtom]:
177
+ if self._atoms_cache is None:
178
+ self._atoms_cache = _discover_atoms()
179
+ return self._atoms_cache
180
+
181
+ def get_specs(self, model: type) -> Mapping[str, Any]:
182
+ return self._specs_cache.get(model)
183
+
184
+ def _compile_opview_from_specs(self, specs: Mapping[str, Any], sp: Any) -> OpView:
185
+ """Compatibility shim for callers using legacy Kernel method dispatch."""
186
+ return compile_opview_from_specs(specs, sp)
187
+
188
+ def prime_specs(self, models: Sequence[type]) -> None:
189
+ self._specs_cache.prime(models)
190
+
191
+ def invalidate_specs(self, model: Optional[type] = None) -> None:
192
+ self._specs_cache.invalidate(model)
193
+
194
+ def build_op(self, model: type, alias: str) -> Dict[str, List[StepFn]]:
195
+ chains = _hook_phase_chains(model, alias)
196
+ specs = getattr(getattr(model, "ops", SimpleNamespace()), "by_alias", {})
197
+ sp_list = specs.get(alias) or ()
198
+ sp = sp_list[0] if sp_list else None
199
+ target = (getattr(sp, "target", alias) or "").lower()
200
+ persist_policy = getattr(sp, "persist", "default")
201
+ persistent = (
202
+ persist_policy != "skip" and target not in {"read", "list"}
203
+ ) or _is_persistent(chains)
204
+
205
+ try:
206
+ _inject_atoms(chains, self._atoms() or (), persistent=persistent)
207
+ except Exception:
208
+ logger.exception(
209
+ "kernel: atom injection failed for %s.%s",
210
+ getattr(model, "__name__", model),
211
+ alias,
212
+ )
213
+
214
+ _inject_pre_tx_dep_atoms(chains, sp)
215
+
216
+ if persistent:
217
+ try:
218
+ _inject_txn_system_steps(chains, model=model)
219
+ except Exception:
220
+ logger.exception(
221
+ "kernel: failed to inject txn system steps for %s.%s",
222
+ getattr(model, "__name__", model),
223
+ alias,
224
+ )
225
+ for phase in _ev.PHASES:
226
+ chains.setdefault(phase, [])
227
+ return chains
228
+
229
+ def build(self, model: type, alias: str) -> Dict[str, List[StepFn]]:
230
+ return self.build_op(model, alias)
231
+
232
+ def build_ingress(self, app: Any) -> Dict[str, List[StepFn]]:
233
+ del app
234
+ order = {name: idx for idx, name in enumerate(_ev.all_events_ordered())}
235
+ ingress_atoms: Dict[str, List[tuple[str, Any]]] = {}
236
+ for anchor, run in self._atoms() or ():
237
+ if not _ev.is_valid_event(anchor):
238
+ continue
239
+ phase = _ev.phase_for_event(anchor)
240
+ if phase not in {"INGRESS_BEGIN", "INGRESS_PARSE", "INGRESS_ROUTE"}:
241
+ continue
242
+ ingress_atoms.setdefault(phase, []).append((anchor, run))
243
+
244
+ out: Dict[str, List[StepFn]] = {}
245
+ for phase, atoms in ingress_atoms.items():
246
+ ordered = sorted(atoms, key=lambda item: order.get(item[0], 10_000))
247
+ out[phase] = [_wrap_atom(run, anchor=anchor) for anchor, run in ordered]
248
+ return out
249
+
250
+ def build_egress(self, app: Any) -> Dict[str, List[StepFn]]:
251
+ del app
252
+ order = {name: idx for idx, name in enumerate(_ev.all_events_ordered())}
253
+ egress_atoms: Dict[str, List[tuple[str, Any]]] = {}
254
+ for anchor, run in self._atoms() or ():
255
+ if not _ev.is_valid_event(anchor):
256
+ continue
257
+ phase = _ev.phase_for_event(anchor)
258
+ if phase not in {"EGRESS_SHAPE", "EGRESS_FINALIZE", "POST_RESPONSE"}:
259
+ continue
260
+ egress_atoms.setdefault(phase, []).append((anchor, run))
261
+
262
+ out: Dict[str, List[StepFn]] = {}
263
+ for phase, atoms in egress_atoms.items():
264
+ ordered = sorted(atoms, key=lambda item: order.get(item[0], 10_000))
265
+ out[phase] = [_wrap_atom(run, anchor=anchor) for anchor, run in ordered]
266
+ return out
267
+
268
+ def plan_labels(self, model: type, alias: str) -> list[str]:
269
+ labels: list[str] = []
270
+ chains = self.build(model, alias)
271
+ opspec = next(
272
+ (sp for sp in _opspecs(model) if getattr(sp, "alias", None) == alias),
273
+ None,
274
+ )
275
+ persist = getattr(opspec, "persist", "default") != "skip"
276
+
277
+ tx_begin = "START_TX:hook:sys:txn:begin@START_TX"
278
+ tx_end = "END_TX:hook:sys:txn:commit@END_TX"
279
+ if persist:
280
+ labels.append(tx_begin)
281
+
282
+ for phase in _ev.PHASES:
283
+ if phase in {
284
+ "INGRESS_BEGIN",
285
+ "INGRESS_PARSE",
286
+ "INGRESS_ROUTE",
287
+ "EGRESS_SHAPE",
288
+ "EGRESS_FINALIZE",
289
+ "START_TX",
290
+ "END_TX",
291
+ }:
292
+ continue
293
+ for step in chains.get(phase, ()) or ():
294
+ labels.append(f"{phase}:{label_hook(step, phase)}")
295
+
296
+ if persist:
297
+ labels.append(tx_end)
298
+
299
+ return labels
300
+
301
+ async def invoke(
302
+ self,
303
+ *,
304
+ model: type,
305
+ alias: str,
306
+ db: Any,
307
+ request: Any | None = None,
308
+ ctx: Optional[Mapping[str, Any]] = None,
309
+ ) -> Any:
310
+ """Execute an operation for ``model.alias`` using the executor."""
311
+ phases = self.build_op(model, alias)
312
+ base_ctx = _Ctx.ensure(request=request, db=db, seed=ctx)
313
+ base_ctx.model = model
314
+ base_ctx.op = alias
315
+ specs = self.get_specs(model)
316
+ base_ctx.opview = compile_opview_from_specs(specs, SimpleNamespace(alias=alias))
317
+ return await _invoke(request=request, db=db, phases=phases, ctx=base_ctx)
318
+
319
+ def ensure_primed(self, app: Any) -> None:
320
+ """Autoprime once per App: specs → OpViews → /kernelz payload."""
321
+ with self._lock:
322
+ if self._primed.get(app):
323
+ return
324
+ models = list(_table_iter(app))
325
+ for model in models:
326
+ self._specs_cache.get(model)
327
+
328
+ ov_map: Dict[Tuple[type, str], OpView] = {}
329
+ for model in models:
330
+ specs = self._specs_cache.get(model)
331
+ for sp in _opspecs(model):
332
+ ov_map[(model, sp.alias)] = compile_opview_from_specs(specs, sp)
333
+ self._opviews[app] = ov_map
334
+
335
+ self._kernelz_payload[app] = self.compile_plan(app)
336
+ self._primed[app] = True
337
+
338
+ def get_opview(self, app: Any, model: type, alias: str) -> OpView:
339
+ """Return OpView for (model, alias); compile on-demand if missing."""
340
+ ov_map = self._opviews.get(app)
341
+ if isinstance(ov_map, dict):
342
+ opview = ov_map.get((model, alias))
343
+ if opview is not None:
344
+ return opview
345
+
346
+ self.ensure_primed(app)
347
+
348
+ ov_map = self._opviews.setdefault(app, {})
349
+ opview = ov_map.get((model, alias))
350
+ if opview is not None:
351
+ return opview
352
+
353
+ try:
354
+ specs = self._specs_cache.get(model)
355
+ found = False
356
+ for sp in _opspecs(model):
357
+ ov_map.setdefault(
358
+ (model, sp.alias), compile_opview_from_specs(specs, sp)
359
+ )
360
+ if sp.alias == alias:
361
+ found = True
362
+
363
+ if not found:
364
+ temp_sp = SimpleNamespace(alias=alias)
365
+ ov_map[(model, alias)] = compile_opview_from_specs(specs, temp_sp)
366
+
367
+ return ov_map[(model, alias)]
368
+ except Exception as exc:
369
+ raise RuntimeError(
370
+ f"opview_missing: app={app!r} model={getattr(model, '__name__', model)!r} alias={alias!r}"
371
+ ) from exc
372
+
373
+ async def _run_phase_chain(self, ctx: _Ctx, phases: Any) -> None:
374
+ for _phase, steps in (phases or {}).items():
375
+ for step in steps or ():
376
+ rv = step(ctx)
377
+ if hasattr(rv, "__await__"):
378
+ await rv
379
+
380
+ @staticmethod
381
+ def _without_ingress_phases(phases: Mapping[str, Any] | None) -> dict[str, Any]:
382
+ if not phases:
383
+ return {}
384
+ ingress = {"INGRESS_BEGIN", "INGRESS_PARSE", "INGRESS_ROUTE"}
385
+ return {phase: steps for phase, steps in phases.items() if phase not in ingress}
386
+
387
+ async def handle_http(self, env: Any, app: Any) -> None:
388
+ from tigrbl_canon.mapping.runtime_routes import invoke_runtime_route_handler
389
+ from tigrbl_concrete.atoms.egress.asgi_send import (
390
+ _send_json,
391
+ _send_transport_response,
392
+ )
393
+ from tigrbl_runtime.status import StatusDetailError
394
+
395
+ plan = self.kernel_plan(app)
396
+ ctx = _Ctx.ensure(request=None, db=None)
397
+ ctx.app = app
398
+ ctx.router = app
399
+ ctx.raw = env
400
+ ctx.kernel_plan = plan
401
+
402
+ await self._run_phase_chain(ctx, plan.ingress_chain)
403
+
404
+ route = (
405
+ ctx.temp.get("route", {})
406
+ if isinstance(getattr(ctx, "temp", None), dict)
407
+ else {}
408
+ )
409
+ egress = (
410
+ ctx.temp.get("egress", {})
411
+ if isinstance(getattr(ctx, "temp", None), dict)
412
+ else {}
413
+ )
414
+ if (
415
+ isinstance(route, dict)
416
+ and route.get("short_circuit") is True
417
+ and isinstance(egress, dict)
418
+ and egress.get("transport_response")
419
+ ):
420
+ await _send_transport_response(env, ctx)
421
+ return
422
+
423
+ opmeta_index = route.get("opmeta_index") if isinstance(route, dict) else None
424
+ if not isinstance(opmeta_index, int):
425
+ if isinstance(route, dict) and route.get("method_not_allowed") is True:
426
+ await _send_json(env, 405, {"detail": "Method Not Allowed"})
427
+ return
428
+ handler = route.get("handler") if isinstance(route, dict) else None
429
+ if callable(handler):
430
+ await invoke_runtime_route_handler(ctx, handler=handler)
431
+ await _send_transport_response(env, ctx)
432
+ return
433
+ await _send_json(
434
+ env, 404, {"detail": "No runtime operation matched request."}
435
+ )
436
+ return
437
+
438
+ opmeta = plan.opmeta[opmeta_index]
439
+ ctx.model = opmeta.model
440
+ ctx.op = opmeta.alias
441
+ ctx.opview = self.get_opview(app, opmeta.model, opmeta.alias)
442
+
443
+ phases = self._without_ingress_phases(plan.phase_chains.get(opmeta_index, {}))
444
+ try:
445
+ await _invoke(request=None, db=None, phases=phases, ctx=ctx)
446
+ except StatusDetailError as exc:
447
+ detail = (
448
+ exc.detail
449
+ if getattr(exc, "detail", None) not in (None, "")
450
+ else str(exc)
451
+ )
452
+ await _send_json(
453
+ env, int(getattr(exc, "status_code", 500) or 500), {"detail": detail}
454
+ )
455
+ return
456
+ except Exception as exc: # pragma: no cover - defensive runtime fallback
457
+ from tigrbl_runtime.status import create_standardized_error
458
+
459
+ std = create_standardized_error(exc)
460
+ detail = (
461
+ std.detail
462
+ if getattr(std, "detail", None) not in (None, "")
463
+ else str(std)
464
+ )
465
+ await _send_json(
466
+ env, int(getattr(std, "status_code", 500) or 500), {"detail": detail}
467
+ )
468
+ return
469
+
470
+ await _send_transport_response(env, ctx)
471
+
472
+ def compile_plan(self, app: Any) -> KernelPlan:
473
+ from tigrbl_core._spec.binding_spec import (
474
+ HttpJsonRpcBindingSpec,
475
+ HttpRestBindingSpec,
476
+ WsBindingSpec,
477
+ )
478
+
479
+ proto_indices: dict[str, Any] = {"http.rest": {}, "https.rest": {}}
480
+ opmeta: list[OpMeta] = []
481
+ opkey_to_meta: dict[OpKey, int] = {}
482
+ phase_chains: dict[int, Mapping[str, list[StepFn]]] = {}
483
+ ingress_chain = self.build_ingress(app)
484
+ egress_chain = self.build_egress(app)
485
+
486
+ for model in _table_iter(app):
487
+ for sp in _opspecs(model):
488
+ meta_index = len(opmeta)
489
+ target = (getattr(sp, "target", sp.alias) or sp.alias).lower()
490
+ opmeta.append(OpMeta(model=model, alias=sp.alias, target=target))
491
+ phase_chains[meta_index] = deepmerge_phase_chains(
492
+ ingress_chain,
493
+ self.build_op(model, sp.alias),
494
+ egress_chain,
495
+ )
496
+
497
+ for binding in getattr(sp, "bindings", ()) or ():
498
+ if isinstance(binding, HttpRestBindingSpec):
499
+ for method in binding.methods:
500
+ selector = f"{method.upper()} {binding.path}"
501
+ opkey = OpKey(proto=binding.proto, selector=selector)
502
+ opkey_to_meta[opkey] = meta_index
503
+ proto_indices.setdefault(binding.proto, {})[selector] = (
504
+ meta_index
505
+ )
506
+ elif isinstance(binding, HttpJsonRpcBindingSpec):
507
+ opkey = OpKey(proto=binding.proto, selector=binding.rpc_method)
508
+ opkey_to_meta[opkey] = meta_index
509
+ proto_indices.setdefault(binding.proto, {})[
510
+ binding.rpc_method
511
+ ] = meta_index
512
+ elif isinstance(binding, WsBindingSpec):
513
+ selector = binding.path
514
+ if binding.subprotocols:
515
+ for subprotocol in binding.subprotocols:
516
+ full_selector = f"{selector}|{subprotocol}"
517
+ opkey = OpKey(
518
+ proto=binding.proto, selector=full_selector
519
+ )
520
+ opkey_to_meta[opkey] = meta_index
521
+ proto_indices.setdefault(binding.proto, {})[
522
+ full_selector
523
+ ] = meta_index
524
+ else:
525
+ opkey = OpKey(proto=binding.proto, selector=selector)
526
+ opkey_to_meta[opkey] = meta_index
527
+ proto_indices.setdefault(binding.proto, {})[selector] = (
528
+ meta_index
529
+ )
530
+
531
+ return KernelPlan(
532
+ proto_indices=proto_indices,
533
+ opmeta=tuple(opmeta),
534
+ opkey_to_meta=opkey_to_meta,
535
+ ingress_chain=ingress_chain,
536
+ phase_chains=phase_chains,
537
+ egress_chain=egress_chain,
538
+ )
539
+
540
+ def compile_bootstrap_plan(self, app: Any) -> Dict[str, List[StepFn]]:
541
+ """Compatibility entrypoint for ingress-only bootstrap diagnostics."""
542
+ return self.build_ingress(app)
543
+
544
+ def kernel_plan(self, app: Any) -> KernelPlan:
545
+ self.ensure_primed(app)
546
+ plan = self._kernel_plans.get(app)
547
+ if isinstance(plan, KernelPlan):
548
+ return plan
549
+
550
+ compiled = self.compile_plan(app)
551
+ self._kernel_plans[app] = compiled
552
+
553
+ payload: dict[str, dict[str, list[str]]] = {}
554
+ for model in _table_iter(app):
555
+ model_name = getattr(model, "__name__", str(model))
556
+ payload[model_name] = {}
557
+ for sp in _opspecs(model):
558
+ payload[model_name][sp.alias] = self.plan_labels(model, sp.alias)
559
+ self._kernelz_payload[app] = payload
560
+
561
+ return compiled
562
+
563
+ def kernelz_payload(self, app: Any) -> dict[str, dict[str, list[str]]]:
564
+ """Thin accessor for endpoint: guarantees primed diagnostics payload."""
565
+ self.kernel_plan(app)
566
+ payload = self._kernelz_payload.get(app)
567
+ if isinstance(payload, dict):
568
+ return payload
569
+ return {}
570
+
571
+ def invalidate_kernelz_payload(self, app: Optional[Any] = None) -> None:
572
+ with self._lock:
573
+ if app is None:
574
+ self._kernel_plans = _WeakMaybeDict()
575
+ self._kernelz_payload = _WeakMaybeDict()
576
+ self._opviews = _WeakMaybeDict()
577
+ self._primed = _WeakMaybeDict()
578
+ else:
579
+ self._kernel_plans.pop(app, None)
580
+ self._kernelz_payload.pop(app, None)
581
+ self._opviews.pop(app, None)
582
+ self._primed.pop(app, None)
@@ -0,0 +1,124 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass, field
4
+ from typing import Any, Callable, Dict, Iterator, Mapping, Tuple
5
+
6
+
7
+ def _label_callable(fn: Any) -> str:
8
+ name = getattr(fn, "__qualname__", getattr(fn, "__name__", repr(fn)))
9
+ module = getattr(fn, "__module__", None)
10
+ return f"{module}.{name}" if module else name
11
+
12
+
13
+ def _label_step(step: Any, phase: str) -> str:
14
+ label = getattr(step, "__tigrbl_label", None)
15
+ if isinstance(label, str) and "@" in label:
16
+ return label
17
+ module = getattr(step, "__module__", "") or ""
18
+ name = getattr(step, "__name__", "") or ""
19
+ if module.startswith("tigrbl_core.core.crud") and name:
20
+ return f"hook:wire:tigrbl:core:crud:ops:{name}@{phase}"
21
+ return f"hook:wire:{_label_callable(step).replace('.', ':')}@{phase}"
22
+
23
+
24
+ @dataclass(frozen=True)
25
+ class SchemaIn:
26
+ fields: Tuple[str, ...]
27
+ by_field: Dict[str, Dict[str, object]]
28
+
29
+
30
+ @dataclass(frozen=True)
31
+ class SchemaOut:
32
+ fields: Tuple[str, ...]
33
+ by_field: Dict[str, Dict[str, object]]
34
+ expose: Tuple[str, ...]
35
+
36
+
37
+ @dataclass(frozen=True)
38
+ class OpView:
39
+ schema_in: SchemaIn
40
+ schema_out: SchemaOut
41
+ paired_index: Dict[str, Dict[str, object]]
42
+ virtual_producers: Dict[str, Callable[[object, dict], object]]
43
+ to_stored_transforms: Dict[str, Callable[[object, dict], object]]
44
+ refresh_hints: Tuple[str, ...]
45
+
46
+
47
+ @dataclass(frozen=True, slots=True)
48
+ class OpKey:
49
+ proto: str
50
+ selector: str
51
+
52
+
53
+ @dataclass(frozen=True, slots=True)
54
+ class OpMeta:
55
+ model: type
56
+ alias: str
57
+ target: str
58
+
59
+
60
+ @dataclass(frozen=True, slots=True)
61
+ class KernelPlan:
62
+ proto_indices: Mapping[str, Any] = field(default_factory=dict)
63
+ opmeta: tuple[OpMeta, ...] = ()
64
+ opkey_to_meta: Mapping[OpKey, int] = field(default_factory=dict)
65
+ ingress_chain: Mapping[str, list[Callable[..., Any]]] = field(default_factory=dict)
66
+ phase_chains: Mapping[int, Mapping[str, list[Callable[..., Any]]]] = field(
67
+ default_factory=dict
68
+ )
69
+ egress_chain: Mapping[str, list[Callable[..., Any]]] = field(default_factory=dict)
70
+ _appspec_mapping: Dict[str, Dict[str, list[str]]] = field(
71
+ default_factory=dict, init=False, repr=False, compare=False
72
+ )
73
+
74
+ def _normalize_mappings(self) -> Dict[str, Dict[str, list[str]]]:
75
+ if self._appspec_mapping:
76
+ return self._appspec_mapping
77
+
78
+ from tigrbl_runtime import events as _ev
79
+
80
+ normalized: Dict[str, Dict[str, list[str]]] = {}
81
+ for meta_index, meta in enumerate(self.opmeta):
82
+ table_name = getattr(meta.model, "__name__", str(meta.model))
83
+ labels: list[str] = []
84
+ chains = self.phase_chains.get(meta_index, {})
85
+ for phase in _ev.PHASES:
86
+ phase_steps = chains.get(phase, ())
87
+ for step in phase_steps or ():
88
+ labels.append(_label_step(step, phase))
89
+
90
+ seen, deduped = set(), []
91
+ for label in labels:
92
+ if ":hook:wire:" in label:
93
+ if label in seen:
94
+ continue
95
+ seen.add(label)
96
+ deduped.append(label)
97
+
98
+ normalized.setdefault(table_name, {})[meta.alias] = deduped
99
+
100
+ self._appspec_mapping.update(normalized)
101
+ return self._appspec_mapping
102
+
103
+ def __getitem__(self, key: str) -> Dict[str, list[str]]:
104
+ return self._normalize_mappings()[key]
105
+
106
+ def __iter__(self) -> Iterator[str]:
107
+ return iter(self._normalize_mappings())
108
+
109
+ def __len__(self) -> int:
110
+ return len(self._normalize_mappings())
111
+
112
+ def get(
113
+ self, key: str, default: Dict[str, list[str]] | None = None
114
+ ) -> Dict[str, list[str]] | None:
115
+ return self._normalize_mappings().get(key, default)
116
+
117
+ def items(self):
118
+ return self._normalize_mappings().items()
119
+
120
+ def keys(self):
121
+ return self._normalize_mappings().keys()
122
+
123
+ def values(self):
124
+ return self._normalize_mappings().values()
@@ -0,0 +1,99 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any, Dict, Mapping
4
+
5
+ from .models import OpView, SchemaIn, SchemaOut
6
+
7
+
8
+ def compile_opview_from_specs(specs: Mapping[str, Any], sp: Any) -> OpView:
9
+ """Build a basic OpView from collected specs when no app/model is present."""
10
+ alias = getattr(sp, "alias", "")
11
+
12
+ in_fields: list[str] = []
13
+ out_fields: list[str] = []
14
+ by_field_in: Dict[str, Dict[str, object]] = {}
15
+ by_field_out: Dict[str, Dict[str, object]] = {}
16
+
17
+ for name, spec in specs.items():
18
+ io = getattr(spec, "io", None)
19
+ fs = getattr(spec, "field", None)
20
+ storage = getattr(spec, "storage", None)
21
+ in_verbs = set(getattr(io, "in_verbs", ()) or ())
22
+ out_verbs = set(getattr(io, "out_verbs", ()) or ())
23
+
24
+ if alias in in_verbs:
25
+ in_fields.append(name)
26
+ meta: Dict[str, object] = {"in_enabled": True}
27
+ if storage is None:
28
+ meta["virtual"] = True
29
+ default_factory = getattr(spec, "default_factory", None)
30
+ if callable(default_factory):
31
+ meta["default_factory"] = default_factory
32
+ alias_in = getattr(io, "alias_in", None)
33
+ if alias_in:
34
+ meta["alias_in"] = alias_in
35
+ header_in = getattr(io, "header_in", None)
36
+ if header_in:
37
+ meta["header_in"] = header_in
38
+ meta["header_required_in"] = bool(
39
+ getattr(io, "header_required_in", False)
40
+ )
41
+ required = bool(fs and alias in getattr(fs, "required_in", ()))
42
+ meta["required"] = required
43
+ base_nullable = (
44
+ True if storage is None else getattr(storage, "nullable", True)
45
+ )
46
+ meta["nullable"] = base_nullable
47
+ by_field_in[name] = meta
48
+
49
+ if alias in out_verbs:
50
+ out_fields.append(name)
51
+ meta_out: Dict[str, object] = {}
52
+ alias_out = getattr(io, "alias_out", None)
53
+ if alias_out:
54
+ meta_out["alias_out"] = alias_out
55
+ if storage is None:
56
+ meta_out["virtual"] = True
57
+ py_type = getattr(getattr(fs, "py_type", None), "__name__", None)
58
+ if py_type:
59
+ meta_out["py_type"] = py_type
60
+ by_field_out[name] = meta_out
61
+
62
+ schema_in = SchemaIn(
63
+ fields=tuple(sorted(in_fields)),
64
+ by_field={field: by_field_in.get(field, {}) for field in sorted(in_fields)},
65
+ )
66
+ schema_out = SchemaOut(
67
+ fields=tuple(sorted(out_fields)),
68
+ by_field={field: by_field_out.get(field, {}) for field in sorted(out_fields)},
69
+ expose=tuple(sorted(out_fields)),
70
+ )
71
+ paired_index: Dict[str, Dict[str, object]] = {}
72
+ for field, col in specs.items():
73
+ io = getattr(col, "io", None)
74
+ cfg = getattr(io, "_paired", None)
75
+ if cfg and alias in getattr(cfg, "verbs", ()): # type: ignore[attr-defined]
76
+ field_spec = getattr(col, "field", None)
77
+ max_length = None
78
+ if field_spec is not None:
79
+ max_length = getattr(
80
+ getattr(field_spec, "constraints", {}),
81
+ "get",
82
+ lambda k, d=None: None,
83
+ )("max_length")
84
+ paired_index[field] = {
85
+ "alias": cfg.alias,
86
+ "gen": cfg.gen,
87
+ "store": cfg.store,
88
+ "mask_last": cfg.mask_last,
89
+ "max_length": max_length,
90
+ }
91
+
92
+ return OpView(
93
+ schema_in=schema_in,
94
+ schema_out=schema_out,
95
+ paired_index=paired_index,
96
+ virtual_producers={},
97
+ to_stored_transforms={},
98
+ refresh_hints=(),
99
+ )
@@ -0,0 +1,64 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING, Any
4
+
5
+ from tigrbl_runtime import events as _ev
6
+ from tigrbl_runtime.labels import label_hook
7
+
8
+
9
+ def _callable_label(fn: Any) -> str:
10
+ module = getattr(fn, "__module__", "") or ""
11
+ qualname = getattr(fn, "__qualname__", getattr(fn, "__name__", repr(fn)))
12
+ return f"{module}.{qualname}" if module else qualname
13
+
14
+
15
+ if TYPE_CHECKING:
16
+ from .core import Kernel
17
+
18
+
19
+ def _table_iter(app: Any):
20
+ tables = getattr(app, "tables", None)
21
+ if isinstance(tables, dict):
22
+ return tuple(v for v in tables.values() if isinstance(v, type))
23
+ return ()
24
+
25
+
26
+ def _opspecs(model: type):
27
+ return getattr(getattr(model, "opspecs", object()), "all", ()) or ()
28
+
29
+
30
+ def build_kernelz_payload(kernel: "Kernel", app: Any):
31
+ payload: dict[str, dict[str, list[str]]] = {}
32
+ for model in _table_iter(app):
33
+ mname = getattr(model, "__name__", str(model))
34
+ payload[mname] = {}
35
+ for sp in _opspecs(model):
36
+ labels: list[str] = []
37
+ for dep in getattr(sp, "secdeps", ()) or ():
38
+ labels.append(
39
+ f"PRE_TX_BEGIN:hook:dep:security:{_callable_label(getattr(dep, 'dependency', dep))}"
40
+ )
41
+ for dep in getattr(sp, "deps", ()) or ():
42
+ labels.append(
43
+ f"PRE_TX_BEGIN:hook:dep:extra:{_callable_label(getattr(dep, 'dependency', dep))}"
44
+ )
45
+
46
+ persist = getattr(sp, "persist", "default") != "skip"
47
+ if persist:
48
+ labels.append("START_TX:hook:sys:txn:begin@START_TX")
49
+
50
+ chains = kernel.build(model, sp.alias)
51
+ for phase in _ev.PHASES:
52
+ if phase in {"START_TX", "END_TX", "PRE_TX_BEGIN", "POST_RESPONSE"}:
53
+ continue
54
+ for step in chains.get(phase, ()) or ():
55
+ labels.append(f"{phase}:{label_hook(step, phase)}")
56
+
57
+ if persist:
58
+ labels.append("END_TX:hook:sys:txn:commit@END_TX")
59
+
60
+ for step in chains.get("POST_RESPONSE", ()) or ():
61
+ labels.append(f"POST_RESPONSE:{label_hook(step, 'POST_RESPONSE')}")
62
+
63
+ payload[mname][sp.alias] = labels
64
+ return payload
@@ -0,0 +1,51 @@
1
+ Metadata-Version: 2.4
2
+ Name: tigrbl-kernel
3
+ Version: 0.1.0.dev1
4
+ Summary: Kernel orchestration for Tigrbl runtime composition.
5
+ License-Expression: Apache-2.0
6
+ Keywords: tigrbl,sdk,standards,framework
7
+ Author: Jacob Stewart
8
+ Author-email: jacob@swarmauri.com
9
+ Requires-Python: >=3.10,<3.13
10
+ Classifier: License :: OSI Approved :: Apache Software License
11
+ Classifier: Development Status :: 1 - Planning
12
+ Classifier: Programming Language :: Python :: 3.10
13
+ Classifier: Programming Language :: Python :: 3.11
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Classifier: Programming Language :: Python
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3 :: Only
18
+ Requires-Dist: tigrbl-atoms
19
+ Requires-Dist: tigrbl-canon
20
+ Description-Content-Type: text/markdown
21
+
22
+ ![Tigrbl branding](https://github.com/swarmauri/swarmauri-sdk/blob/a170683ecda8ca1c4f912c966d4499649ffb8224/assets/tigrbl.brand.theme.svg)
23
+
24
+ # tigrbl-kernel
25
+
26
+ ![PyPI - Downloads](https://img.shields.io/pypi/dm/tigrbl-kernel.svg) ![Hits](https://hits.sh/github.com/swarmauri/swarmauri-sdk.svg) ![Python Versions](https://img.shields.io/pypi/pyversions/tigrbl-kernel.svg) ![License](https://img.shields.io/pypi/l/tigrbl-kernel.svg) ![Version](https://img.shields.io/pypi/v/tigrbl-kernel.svg)
27
+
28
+ ## Features
29
+
30
+ - Modular package in the Tigrbl namespace.
31
+ - Supports Python 3.10 through 3.12.
32
+ - Distributed as part of the swarmauri-sdk workspace.
33
+
34
+ ## Installation
35
+
36
+ ### uv
37
+
38
+ ```bash
39
+ uv add tigrbl-kernel
40
+ ```
41
+
42
+ ### pip
43
+
44
+ ```bash
45
+ pip install tigrbl-kernel
46
+ ```
47
+
48
+ ## Usage
49
+
50
+ Import from the shared package-specific module namespaces after installation in your environment.
51
+
@@ -0,0 +1,10 @@
1
+ tigrbl_kernel/kernel/__init__.py,sha256=Vnd3I9czWwQAAdCCKPEuDdg3xnZafSPXruLaXCRkFOU,1215
2
+ tigrbl_kernel/kernel/atoms.py,sha256=aaTOixPocG1Hxn5JJ3Ww-vH63aWSyoKzidz0ojXwypk,7308
3
+ tigrbl_kernel/kernel/cache.py,sha256=IBhLUsHo8ASlnQXGGbw9MoQYaOpID7YBBcLNfIjA-uE,2606
4
+ tigrbl_kernel/kernel/core.py,sha256=bstWeuJYhNgucOkzH9BVgnQC7I5ZkN1L99pH2Gxx1VY,22142
5
+ tigrbl_kernel/kernel/models.py,sha256=l31lxLk4_0-pUyiHLTojYYhhlD6VuPLiC7-iIqKCfoc,3983
6
+ tigrbl_kernel/kernel/opview_compiler.py,sha256=KDBllYxk-gqyohLWMKbNSo135mKMzsQgeitYdJJJJ-o,3738
7
+ tigrbl_kernel/kernel/payload.py,sha256=ofg0ejCtO5VICsXxrfT2UiT9SaGHSrbr-G49avqCiLo,2236
8
+ tigrbl_kernel-0.1.0.dev1.dist-info/METADATA,sha256=kKOzoqErirX14gzGVLDNzhPtAR17QqAeRno_EzryjFE,1642
9
+ tigrbl_kernel-0.1.0.dev1.dist-info/WHEEL,sha256=kJCRJT_g0adfAJzTx2GUMmS80rTJIVHRCfG0DQgLq3o,88
10
+ tigrbl_kernel-0.1.0.dev1.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: poetry-core 2.3.1
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any