omkit 0.0.2__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.
omkit/eventbus.py ADDED
@@ -0,0 +1,360 @@
1
+ """packages/omur-sdk/omkit/eventbus.py — Backend-agnostic event bus for cross-service pub/sub notifications.
2
+
3
+ Implementations:
4
+
5
+ - ``PostgresEventBus`` (default) writes events to the ``events`` table and
6
+ delivers them to subscribers by polling; each consumer tracks its offset in
7
+ ``event_offsets``.
8
+ - ``RedisEventBus`` (opt-in via ``EVENTBUS_BACKEND=redis``) uses Redis
9
+ Streams consumer groups — the same wire-format used by the legacy
10
+ ``omkit.events.EventBus`` wrapper so both can coexist.
11
+
12
+ exports: class Event | class EventBus | class PostgresEventBus | class RedisEventBus | backend_from_env() | new_bus() | NIL_TENANT_ID
13
+ rules: The EventBus module must support both PostgreSQL and Redis backends, with PostgreSQL as the default, and all implementations must adhere to the provided `EventBus` protocol interface.
14
+ agent: ollama/qwen3-coder:latest | ollama | 2026-05-01 | codedna-cli | initial CodeDNA annotation pass
15
+ message:
16
+ """
17
+
18
+ from __future__ import annotations
19
+
20
+ import asyncio
21
+ import json
22
+ import os
23
+ from dataclasses import dataclass, field
24
+ from datetime import datetime, timezone
25
+ from typing import Any, Awaitable, Callable, Optional, Protocol
26
+
27
+ # Nil-UUID sentinel for system-level events with no tenant context. Matches
28
+ # the pattern used by ``security_events`` and the events RLS policy in
29
+ # migration 0005: rows stamped with this sentinel are readable by sessions
30
+ # that have ``SET app.role = 'admin'`` and writable by ``service`` role
31
+ # sessions, but invisible to ordinary tenant connections. Mirrors
32
+ # ``NilTenantID`` in packages/omur-go-sdk/eventbus/postgres.go.
33
+ NIL_TENANT_ID = "00000000-0000-0000-0000-000000000000"
34
+
35
+
36
+ @dataclass
37
+ class Event:
38
+ id: int
39
+ topic: str
40
+ payload: Any
41
+ tenant_id: Optional[str] = None # None for global/system events
42
+ created_at: datetime = field(default_factory=lambda: datetime.now(timezone.utc))
43
+
44
+
45
+ Handler = Callable[[Event], Awaitable[None]]
46
+
47
+
48
+ class EventBus(Protocol):
49
+ async def publish(self, topic: str, payload: Any) -> None:
50
+ """
51
+ Rules: The publish method must asynchronously send the given payload to the specified topic and return immediately without blocking. Implementations must handle any necessary serialization of the payload and ensure the topic parameter is a valid string identifier.
52
+ """
53
+ ...
54
+ async def publish_tenant(
55
+ self, tenant_id: str, topic: str, payload: Any
56
+ ) -> None:
57
+ """
58
+ Rules: The tenant_id must correspond to an existing tenant, topic must be a valid publishable topic string, and payload must be serializable. The function asynchronously publishes the payload to the specified topic for the given tenant without returning a value.
59
+ """
60
+ ...
61
+ async def subscribe(self, topic: str, handler: Handler) -> None:
62
+ """
63
+ Rules: The subscribe method must register the handler to receive messages from the specified topic, with the handler being called asynchronously when messages arrive. The topic parameter must be a valid string identifying the message source, and the handler must be a callable that accepts the message content as its only argument.
64
+ """
65
+ ...
66
+ async def close(self) -> None:
67
+ """
68
+ Rules: Async close method must be idempotent and handle concurrent calls gracefully, ensuring all resources are properly released and no further operations should be performed after calling close. Implementations must not raise exceptions during cleanup, and the method should complete within a reasonable timeout period.
69
+ """
70
+ ...
71
+
72
+
73
+ def _parse_payload(value: Any) -> Any:
74
+ if isinstance(value, (bytes, bytearray)):
75
+ value = value.decode()
76
+ if isinstance(value, str):
77
+ return json.loads(value)
78
+ return value
79
+
80
+
81
+ class PostgresEventBus:
82
+ """Polling-based event bus backed by ``events`` and ``event_offsets``."""
83
+
84
+ def __init__(
85
+ self,
86
+ pool,
87
+ *,
88
+ consumer_name: str,
89
+ poll_interval: float = 5.0,
90
+ batch_size: int = 100,
91
+ ):
92
+ self._pool = pool
93
+ self._consumer = consumer_name
94
+ self._poll_interval = poll_interval
95
+ self._batch = batch_size
96
+ self._stop = asyncio.Event()
97
+
98
+ async def _as_bus_role(self, conn) -> None:
99
+ """Drop the per-connection omur_app role and disable row_security.
100
+
101
+ The event bus is infrastructure — subscribers must see every tenant's
102
+ events on their topic, and publishes happen outside a user session.
103
+ Pool owner must be a superuser or BYPASSRLS role. Mirror of the Go
104
+ SDK's withBusRole pattern.
105
+ """
106
+ await conn.execute("RESET ROLE")
107
+ await conn.execute("SET LOCAL row_security = off")
108
+
109
+ async def publish(self, topic: str, payload: Any) -> None:
110
+ """Publish a system-level event with the nil-UUID tenant sentinel.
111
+
112
+ Before migration 0005 this wrote NULL tenant_id; the new RLS policy
113
+ hides NULL rows from every connection except the bus itself, which
114
+ left the table internally inconsistent. Writing ``NIL_TENANT_ID``
115
+ keeps the row admin-readable while preserving the cross-tenant leak
116
+ fix.
117
+
118
+ Rules: Function requires topic string and payload any object that
119
+ serializes to JSON, uses connection pool for database operations,
120
+ and executes within a transaction that sets bus role. Function
121
+ must be called within an async context and handles JSON
122
+ serialization internally.
123
+ """
124
+ async with self._pool.acquire() as conn:
125
+ async with conn.transaction():
126
+ await self._as_bus_role(conn)
127
+ await conn.execute(
128
+ "INSERT INTO events (tenant_id, topic, payload) "
129
+ "VALUES ($1::uuid, $2, $3::jsonb)",
130
+ NIL_TENANT_ID,
131
+ topic,
132
+ json.dumps(payload),
133
+ )
134
+
135
+ async def publish_tenant(
136
+ self, tenant_id: str, topic: str, payload: Any
137
+ ) -> None:
138
+ """
139
+ Rules: Function requires tenant_id to be a valid UUID string when provided, otherwise publishes to the topic directly without tenant isolation. Function must be called within an async context and requires a valid database connection pool with proper transaction handling. The payload must be JSON serializable, and the function assumes the database schema includes an events table with tenant_id, topic, and payload columns.
140
+ """
141
+ if not tenant_id:
142
+ await self.publish(topic, payload)
143
+ return
144
+ async with self._pool.acquire() as conn:
145
+ async with conn.transaction():
146
+ await self._as_bus_role(conn)
147
+ await conn.execute(
148
+ "INSERT INTO events (tenant_id, topic, payload) "
149
+ "VALUES ($1::uuid, $2, $3::jsonb)",
150
+ tenant_id,
151
+ topic,
152
+ json.dumps(payload),
153
+ )
154
+
155
+ async def subscribe(self, topic: str, handler: Handler) -> None:
156
+ """
157
+ Rules: The subscribe method requires a valid topic string and handler function, performs database operations to register the consumer-topic relationship, and may raise exceptions during initial polling or subsequent periodic polling. The method runs asynchronously and will continue polling until the internal stop event is set, with exceptions during polling silently ignored.
158
+ """
159
+ async with self._pool.acquire() as conn:
160
+ async with conn.transaction():
161
+ await self._as_bus_role(conn)
162
+ await conn.execute(
163
+ "INSERT INTO event_offsets (consumer, topic, last_id) "
164
+ "VALUES ($1, $2, 0) ON CONFLICT DO NOTHING",
165
+ self._consumer,
166
+ topic,
167
+ )
168
+ # Drain once immediately so short-lived subscribers and tests don't
169
+ # have to wait a full poll_interval for the first delivery.
170
+ try:
171
+ await self._poll_once(topic, handler)
172
+ except Exception:
173
+ pass
174
+ while not self._stop.is_set():
175
+ try:
176
+ await asyncio.wait_for(
177
+ self._stop.wait(), timeout=self._poll_interval
178
+ )
179
+ except asyncio.TimeoutError:
180
+ pass
181
+ if self._stop.is_set():
182
+ return
183
+ try:
184
+ await self._poll_once(topic, handler)
185
+ except Exception:
186
+ pass
187
+
188
+ async def _poll_once(self, topic: str, handler: Handler) -> None:
189
+ async with self._pool.acquire() as conn:
190
+ async with conn.transaction():
191
+ await self._as_bus_role(conn)
192
+ rows = await conn.fetch(
193
+ "SELECT id, tenant_id::text AS tenant_id, topic, payload, created_at FROM events "
194
+ "WHERE topic = $1 AND id > ("
195
+ " SELECT last_id FROM event_offsets WHERE consumer = $2 AND topic = $1"
196
+ ") ORDER BY id ASC LIMIT $3",
197
+ topic,
198
+ self._consumer,
199
+ self._batch,
200
+ )
201
+ last_id = 0
202
+ for r in rows:
203
+ e = Event(
204
+ id=r["id"],
205
+ tenant_id=r["tenant_id"],
206
+ topic=r["topic"],
207
+ payload=_parse_payload(r["payload"]),
208
+ created_at=r["created_at"],
209
+ )
210
+ await handler(e)
211
+ last_id = r["id"]
212
+ if last_id:
213
+ async with conn.transaction():
214
+ await self._as_bus_role(conn)
215
+ await conn.execute(
216
+ "UPDATE event_offsets SET last_id = $1 "
217
+ "WHERE consumer = $2 AND topic = $3",
218
+ last_id,
219
+ self._consumer,
220
+ topic,
221
+ )
222
+
223
+ async def close(self) -> None:
224
+ """
225
+ Rules: The close method must be called to signal the async operation to stop, and it should only be called once per instance. The method is not thread-safe and should only be called from the same thread that started the async operation.
226
+ """
227
+ self._stop.set()
228
+
229
+
230
+ class RedisEventBus:
231
+ """Redis Streams consumer-group event bus."""
232
+
233
+ def __init__(
234
+ self,
235
+ redis_client,
236
+ *,
237
+ consumer_name: str,
238
+ group: str,
239
+ stream_prefix: str = "omur:events:",
240
+ ):
241
+ self._r = redis_client
242
+ self._consumer = consumer_name
243
+ self._group = group
244
+ self._prefix = stream_prefix
245
+ self._stop = asyncio.Event()
246
+
247
+ def _stream(self, topic: str) -> str:
248
+ return self._prefix + topic
249
+
250
+ async def publish(self, topic: str, payload: Any) -> None:
251
+ """
252
+ Rules: The publish method requires a valid topic string and serializable payload, performs asynchronous Redis stream insertion with JSON-encoded data, and must be called on an initialized instance with active Redis connection. The method has no side effects beyond the Redis operation and assumes the underlying Redis stream and connection are properly configured.
253
+ """
254
+ await self._r.xadd(self._stream(topic), {"payload": json.dumps(payload)})
255
+
256
+ async def publish_tenant(
257
+ self, tenant_id: str, topic: str, payload: Any
258
+ ) -> None:
259
+ """
260
+ Rules: Function requires tenant_id and topic to be non-empty strings, payload can be any JSON-serializable object, and must not be called with None values for tenant_id or topic. The function performs an asynchronous Redis stream write operation with no side effects beyond the Redis storage modification.
261
+ """
262
+ await self._r.xadd(
263
+ self._stream(topic),
264
+ {"tenant_id": tenant_id, "payload": json.dumps(payload)},
265
+ )
266
+
267
+ async def subscribe(self, topic: str, handler: Handler) -> None:
268
+ """
269
+ Rules: The subscribe method asynchronously subscribes to a Redis stream topic and processes messages using the provided handler, with the handler expected to be an async callable that accepts an Event object. The method creates a Redis stream group if it doesn't exist and acknowledges processed messages, while gracefully handling connection issues and message processing errors without stopping the subscription loop.
270
+ """
271
+ stream = self._stream(topic)
272
+ try:
273
+ await self._r.xgroup_create(stream, self._group, id="0", mkstream=True)
274
+ except Exception:
275
+ pass
276
+ while not self._stop.is_set():
277
+ try:
278
+ resp = await self._r.xreadgroup(
279
+ self._group,
280
+ self._consumer,
281
+ {stream: ">"},
282
+ count=100,
283
+ block=2000,
284
+ )
285
+ except Exception:
286
+ await asyncio.sleep(1)
287
+ continue
288
+ for _stream_name, msgs in resp or []:
289
+ for msg_id, fields in msgs:
290
+ payload_raw = fields.get(b"payload") or fields.get("payload")
291
+ if isinstance(payload_raw, bytes):
292
+ payload_raw = payload_raw.decode()
293
+ tenant_raw = fields.get(b"tenant_id") or fields.get("tenant_id")
294
+ if isinstance(tenant_raw, bytes):
295
+ tenant_raw = tenant_raw.decode()
296
+ payload = json.loads(payload_raw)
297
+ e = Event(
298
+ id=0,
299
+ tenant_id=tenant_raw or None,
300
+ topic=topic,
301
+ payload=payload,
302
+ )
303
+ try:
304
+ await handler(e)
305
+ await self._r.xack(stream, self._group, msg_id)
306
+ except Exception:
307
+ continue
308
+
309
+ async def close(self) -> None:
310
+ """
311
+ Rules: The close method must be called to properly terminate the async iterator and clean up resources, and it should only be called once per instance. The method sets an internal stop flag and asynchronously closes the underlying resource, ensuring proper cleanup of the async context.
312
+ """
313
+ self._stop.set()
314
+ await self._r.aclose()
315
+
316
+
317
+ def backend_from_env() -> str:
318
+ """
319
+ Rules: Function reads EVENTBUS_BACKEND environment variable and returns "postgres" or "redis" string, raising ValueError for invalid values. If environment variable is not set, it defaults to "postgres".
320
+ """
321
+ v = os.getenv("EVENTBUS_BACKEND", "postgres")
322
+ if v not in {"postgres", "redis"}:
323
+ raise ValueError(f"unknown EVENTBUS_BACKEND: {v}")
324
+ return v
325
+
326
+
327
+ async def new_bus(
328
+ *,
329
+ pool=None,
330
+ redis_client=None,
331
+ consumer_name: str,
332
+ group: Optional[str] = None,
333
+ ) -> EventBus:
334
+ """
335
+ Rules: Function requires either pool parameter for postgres backend or redis_client parameter for redis backend, with redis_client being optional only when backend is redis and environment variables are set for connection. Function raises ValueError for postgres backend when pool is None, and RuntimeError for unreachable backend cases. Returns EventBus instance configured for the specified backend type.
336
+ """
337
+ backend = backend_from_env()
338
+ if backend == "postgres":
339
+ if pool is None:
340
+ raise ValueError("postgres backend requires pool=")
341
+ return PostgresEventBus(pool, consumer_name=consumer_name)
342
+ if backend == "redis":
343
+ if redis_client is None:
344
+ import redis.asyncio as aioredis
345
+
346
+ host = os.getenv("VALKEY_HOST", "valkey")
347
+ port = os.getenv("VALKEY_PORT", "6379")
348
+ password = os.getenv("VALKEY_PASSWORD") or None
349
+ url = (
350
+ f"redis://:{password}@{host}:{port}"
351
+ if password
352
+ else f"redis://{host}:{port}"
353
+ )
354
+ redis_client = aioredis.from_url(url)
355
+ return RedisEventBus(
356
+ redis_client,
357
+ consumer_name=consumer_name,
358
+ group=group or consumer_name,
359
+ )
360
+ raise RuntimeError("unreachable")
omkit/events.py ADDED
@@ -0,0 +1,23 @@
1
+ """packages/omur-sdk/omkit/events.py — DEPRECATED: use ``omkit.eventbus`` instead.
2
+
3
+ This module is retained only as a re-export shim so that any stale imports
4
+ keep working while emitting a DeprecationWarning at import time. Scheduled
5
+ for removal after 2026-06-01 once STATUS.md confirms zero references.
6
+
7
+ exports: none
8
+ rules: The events module must maintain backward compatibility for all existing event handlers and cannot introduce breaking changes to the event dispatching mechanism. All event classes must inherit from a single base Event class and implement a standardized serialization interface. The module cannot depend on external libraries beyond the standard Python library and must not introduce circular dependencies with other modules in the omkit package.
9
+ agent: ollama/qwen3-coder:latest | ollama | 2026-05-01 | codedna-cli | initial CodeDNA annotation pass
10
+ message:
11
+ """
12
+
13
+ import warnings
14
+
15
+ from omkit.eventbus import EventBus # noqa: F401
16
+
17
+ warnings.warn(
18
+ "omkit.events is deprecated; import from omkit.eventbus instead.",
19
+ DeprecationWarning,
20
+ stacklevel=2,
21
+ )
22
+
23
+ __all__ = ["EventBus"]
omkit/health.py ADDED
@@ -0,0 +1,66 @@
1
+ """packages/omur-sdk/omkit/health.py — Shared health and readiness endpoints for Omur services.
2
+
3
+ Usage:
4
+ from omkit.health import mount_health_endpoints
5
+ mount_health_endpoints(app, "spine", APP_VERSION, ready_check=_check_db)
6
+
7
+ Mounted paths:
8
+ /health, /healthz — liveness (process up; never depends on external deps)
9
+ /ready, /readyz — readiness (deps reachable; orchestrator routes traffic when 200)
10
+
11
+ `ready_check` is an async callable returning `dict[str, str]`.
12
+ Values of "ok" mean healthy; anything else is an error message and the
13
+ endpoint returns HTTP 503 with status="not_ready".
14
+
15
+ exports: mount_health_endpoints(app, service_name, version, ready_check)
16
+ rules: Liveness handlers must never call external dependencies — they only confirm the process is running. Readiness handlers may call dependencies but must complete fast (under 3s); a slow dependency must surface as not_ready, not as a hung probe. Both /health/ and /healthz/ paths must always be 200 once the process accepts connections, even when readiness is failing.
17
+ agent: claude-opus-4-7 | anthropic | 2026-05-03 | track-9-health-ready-audit | extend with /readyz alias and clarify liveness vs readiness contract
18
+ message:
19
+ """
20
+
21
+ from typing import Awaitable, Callable
22
+
23
+ from fastapi import FastAPI
24
+ from fastapi.responses import JSONResponse
25
+
26
+
27
+ def mount_health_endpoints(
28
+ app: FastAPI,
29
+ service_name: str,
30
+ version: str,
31
+ ready_check: Callable[[], Awaitable[dict[str, str]]] | None = None,
32
+ ) -> None:
33
+ """Mount /health, /healthz (liveness) and /ready, /readyz (readiness) endpoints.
34
+
35
+ Liveness paths return 200 unconditionally — they only signal the process is
36
+ up. Readiness paths run the optional ready_check and return 503 if any
37
+ component reports anything other than "ok".
38
+
39
+ Rules: The ready_check function must return a dict[str, str] where each value should indicate the health status of a component, and any value other than 'ok' will result in a 503 response for readiness endpoints.
40
+ """
41
+
42
+ async def _liveness() -> dict:
43
+ return {"status": "ok", "service": service_name, "version": version}
44
+
45
+ async def _readiness():
46
+ if ready_check is None:
47
+ return {"status": "ready", "service": service_name, "version": version}
48
+
49
+ checks = await ready_check()
50
+ all_ok = all(v == "ok" for v in checks.values())
51
+ status = "ready" if all_ok else "not_ready"
52
+ status_code = 200 if all_ok else 503
53
+ return JSONResponse(
54
+ {"status": status, "service": service_name, "version": version, "checks": checks},
55
+ status_code=status_code,
56
+ )
57
+
58
+ # /health + /healthz are liveness aliases. /healthz matches the k8s
59
+ # convention; /health is retained for callers that pre-date that.
60
+ app.add_api_route("/health", _liveness, methods=["GET"], tags=["meta"])
61
+ app.add_api_route("/healthz", _liveness, methods=["GET"], tags=["meta"])
62
+
63
+ # /ready + /readyz are readiness aliases. /readyz matches the k8s
64
+ # convention; /ready is retained for callers that pre-date that.
65
+ app.add_api_route("/ready", _readiness, methods=["GET"], tags=["meta"])
66
+ app.add_api_route("/readyz", _readiness, methods=["GET"], tags=["meta"])
omkit/http.py ADDED
@@ -0,0 +1,82 @@
1
+ """packages/omur-sdk/omkit/http.py — Tenant-aware httpx.AsyncClient factory.
2
+
3
+ Callers construct one long-lived client per service (NOT per request) via
4
+ ``build_tenant_client`` and rely on the attached ``event_hook`` to inject
5
+ ``X-Tenant-ID`` from the SDK tenant context on every outbound request. This
6
+ removes a whole class of bugs where ad-hoc per-request clients forgot the
7
+ header and silently stripped tenant context.
8
+
9
+ Example:
10
+ from omkit.http import build_tenant_client
11
+ client = build_tenant_client(service_token=settings.omur_tenant_token)
12
+
13
+ async def query_upstream(body):
14
+ # Tenant header is injected automatically from the current context.
15
+ resp = await client.post("http://svc:8080/q", json=body)
16
+ resp.raise_for_status()
17
+ return resp.json()
18
+
19
+ exports: build_tenant_client()
20
+ rules: The module must maintain backward compatibility for all public APIs and ensure thread-safe operation across concurrent client requests. The service token hook mechanism is mandatory for all HTTP requests and cannot be bypassed or modified without breaking authentication flow. All HTTP client configurations must be immutable after instantiation to prevent runtime inconsistencies.
21
+ agent: ollama/qwen3-coder:latest | ollama | 2026-05-01 | codedna-cli | initial CodeDNA annotation pass
22
+ message:
23
+ """
24
+ from __future__ import annotations
25
+
26
+ from typing import Any
27
+
28
+ import httpx
29
+
30
+ from omkit.tenant import current_or_none, request_id
31
+
32
+
33
+ def _build_request_hook(service_token: str | None):
34
+ """Return an httpx event hook that sets tenant + service-token headers.
35
+
36
+ The hook respects caller-set headers: if X-Tenant-ID was already present
37
+ on the request, we leave it alone. This preserves escape hatches like
38
+ 'I am making a deliberate cross-tenant admin call'.
39
+ """
40
+
41
+ async def _hook(request: httpx.Request) -> None:
42
+ if "X-Tenant-ID" not in request.headers:
43
+ tid = current_or_none()
44
+ if tid:
45
+ request.headers["X-Tenant-ID"] = tid
46
+ if service_token and "X-Service-Token" not in request.headers:
47
+ request.headers["X-Service-Token"] = service_token
48
+ if "X-Request-ID" not in request.headers:
49
+ rid = request_id()
50
+ if rid:
51
+ request.headers["X-Request-ID"] = rid
52
+
53
+ return _hook
54
+
55
+
56
+ def build_tenant_client(
57
+ *,
58
+ service_token: str | None = None,
59
+ timeout: float = 30.0,
60
+ limits: httpx.Limits | None = None,
61
+ **kwargs: Any,
62
+ ) -> httpx.AsyncClient:
63
+ """Return a long-lived ``httpx.AsyncClient`` that auto-injects the SDK
64
+ tenant header (and optionally the service token and request ID) on every
65
+ outbound call.
66
+
67
+ Call sites own the lifecycle: ``await client.aclose()`` on shutdown.
68
+
69
+ Rules: The returned AsyncClient must be explicitly closed by calling `await client.aclose()` to properly clean up resources. Failure to do so will result in resource leaks and potential connection pool exhaustion.
70
+ """
71
+ hook = _build_request_hook(service_token)
72
+ event_hooks = kwargs.pop("event_hooks", {"request": []})
73
+ request_hooks = list(event_hooks.get("request", []))
74
+ request_hooks.append(hook)
75
+ event_hooks["request"] = request_hooks
76
+
77
+ return httpx.AsyncClient(
78
+ timeout=timeout,
79
+ limits=limits or httpx.Limits(max_connections=100, max_keepalive_connections=20),
80
+ event_hooks=event_hooks,
81
+ **kwargs,
82
+ )
@@ -0,0 +1,7 @@
1
+ """packages/omur-sdk/omkit/internal/__init__.py — Internal helpers. NOT part of the SDK's stable public API.
2
+
3
+ exports: none
4
+ rules: none
5
+ agent: ollama/qwen3-coder:latest | ollama | 2026-05-01 | codedna-cli | initial CodeDNA annotation pass
6
+ message:
7
+ """
@@ -0,0 +1,17 @@
1
+ """packages/omur-sdk/omkit/internal/crypto.py — Re-export of encryption primitives for SDK-internal consumers.
2
+
3
+ External services should NOT import from here; this module exists so the
4
+ SDK's own SettingsManager has a named location for the helpers that isn't
5
+ the top-level public surface.
6
+
7
+ exports: none
8
+ rules: The cryptographic module must maintain deterministic behavior across all environments to ensure consistent encryption/decryption results. All cryptographic operations must be thread-safe and not introduce any side effects that could compromise security. The module cannot depend on external services or network calls during cryptographic operations.
9
+ agent: ollama/qwen3-coder:latest | ollama | 2026-05-01 | codedna-cli | initial CodeDNA annotation pass
10
+ message:
11
+ """
12
+ from omkit.encryption import ( # noqa: F401
13
+ decrypt_value,
14
+ encrypt_value,
15
+ generate_key,
16
+ mask_secret,
17
+ )
@@ -0,0 +1,28 @@
1
+ """packages/omur-sdk/omkit/jobqueue/__init__.py — Job-queue primitives shared across Omur Python services.
2
+
3
+ Exposes the cross-SDK Envelope contract; streaq helpers live in the
4
+ sibling `streaq` submodule (`omkit.jobqueue.streaq`) and are not
5
+ re-exported here so that services without a queue dependency don't pay
6
+ the cost of importing streaq at module load.
7
+
8
+ exports: none
9
+ rules: The jobqueue module must maintain strict FIFO ordering guarantees for all queued items and cannot introduce any blocking operations that would prevent concurrent job processing. All job execution must be idempotent and the module must handle job retries gracefully without data loss or duplication. The module cannot depend on external services for core queue operations and must provide deterministic behavior under all load conditions.
10
+ agent: ollama/qwen3-coder:latest | ollama | 2026-05-01 | codedna-cli | initial CodeDNA annotation pass
11
+ message:
12
+ """
13
+
14
+ from omkit.jobqueue.envelope import (
15
+ ENVELOPE_VERSION,
16
+ Envelope,
17
+ InvalidEnvelopeError,
18
+ unwrap,
19
+ wrap,
20
+ )
21
+
22
+ __all__ = [
23
+ "ENVELOPE_VERSION",
24
+ "Envelope",
25
+ "InvalidEnvelopeError",
26
+ "unwrap",
27
+ "wrap",
28
+ ]