omnibase_infra 0.2.2__py3-none-any.whl → 0.2.4__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.
Files changed (79) hide show
  1. omnibase_infra/__init__.py +1 -1
  2. omnibase_infra/adapters/adapter_onex_tool_execution.py +6 -1
  3. omnibase_infra/capabilities/__init__.py +15 -0
  4. omnibase_infra/capabilities/capability_inference_rules.py +211 -0
  5. omnibase_infra/capabilities/contract_capability_extractor.py +221 -0
  6. omnibase_infra/capabilities/intent_type_extractor.py +160 -0
  7. omnibase_infra/contracts/handlers/filesystem/handler_contract.yaml +1 -1
  8. omnibase_infra/contracts/handlers/mcp/handler_contract.yaml +1 -1
  9. omnibase_infra/enums/__init__.py +6 -0
  10. omnibase_infra/enums/enum_handler_error_type.py +10 -0
  11. omnibase_infra/enums/enum_handler_source_mode.py +72 -0
  12. omnibase_infra/enums/enum_kafka_acks.py +99 -0
  13. omnibase_infra/event_bus/event_bus_kafka.py +1 -1
  14. omnibase_infra/event_bus/models/config/model_kafka_event_bus_config.py +59 -10
  15. omnibase_infra/handlers/__init__.py +8 -1
  16. omnibase_infra/handlers/handler_consul.py +7 -1
  17. omnibase_infra/handlers/handler_db.py +8 -2
  18. omnibase_infra/handlers/handler_graph.py +860 -4
  19. omnibase_infra/handlers/handler_http.py +8 -2
  20. omnibase_infra/handlers/handler_intent.py +387 -0
  21. omnibase_infra/handlers/handler_mcp.py +10 -1
  22. omnibase_infra/handlers/handler_vault.py +11 -5
  23. omnibase_infra/handlers/registration_storage/handler_registration_storage_postgres.py +7 -0
  24. omnibase_infra/handlers/service_discovery/handler_service_discovery_consul.py +7 -0
  25. omnibase_infra/mixins/mixin_node_introspection.py +18 -0
  26. omnibase_infra/models/discovery/model_introspection_config.py +11 -0
  27. omnibase_infra/models/handlers/__init__.py +38 -5
  28. omnibase_infra/models/handlers/model_bootstrap_handler_descriptor.py +4 -4
  29. omnibase_infra/models/handlers/model_contract_discovery_result.py +6 -4
  30. omnibase_infra/models/handlers/model_handler_source_config.py +220 -0
  31. omnibase_infra/models/registration/model_node_introspection_event.py +9 -0
  32. omnibase_infra/models/runtime/model_handler_contract.py +25 -9
  33. omnibase_infra/models/runtime/model_loaded_handler.py +9 -0
  34. omnibase_infra/nodes/node_registration_orchestrator/plugin.py +1 -1
  35. omnibase_infra/nodes/node_registration_orchestrator/registry/registry_infra_node_registration_orchestrator.py +7 -7
  36. omnibase_infra/nodes/node_registration_orchestrator/timeout_coordinator.py +4 -3
  37. omnibase_infra/nodes/node_registration_storage_effect/node.py +4 -1
  38. omnibase_infra/nodes/node_registration_storage_effect/registry/registry_infra_registration_storage.py +1 -1
  39. omnibase_infra/nodes/node_service_discovery_effect/registry/registry_infra_service_discovery.py +4 -1
  40. omnibase_infra/protocols/__init__.py +2 -0
  41. omnibase_infra/protocols/protocol_container_aware.py +200 -0
  42. omnibase_infra/runtime/__init__.py +39 -0
  43. omnibase_infra/runtime/handler_bootstrap_source.py +26 -33
  44. omnibase_infra/runtime/handler_contract_config_loader.py +1 -1
  45. omnibase_infra/runtime/handler_contract_source.py +10 -51
  46. omnibase_infra/runtime/handler_identity.py +81 -0
  47. omnibase_infra/runtime/handler_plugin_loader.py +15 -0
  48. omnibase_infra/runtime/handler_registry.py +11 -3
  49. omnibase_infra/runtime/handler_source_resolver.py +326 -0
  50. omnibase_infra/runtime/protocol_lifecycle_executor.py +6 -6
  51. omnibase_infra/runtime/registry/registry_protocol_binding.py +13 -13
  52. omnibase_infra/runtime/registry_contract_source.py +693 -0
  53. omnibase_infra/runtime/service_kernel.py +1 -1
  54. omnibase_infra/runtime/service_runtime_host_process.py +463 -190
  55. omnibase_infra/runtime/util_wiring.py +12 -3
  56. omnibase_infra/services/__init__.py +21 -0
  57. omnibase_infra/services/corpus_capture.py +7 -1
  58. omnibase_infra/services/mcp/mcp_server_lifecycle.py +9 -3
  59. omnibase_infra/services/registry_api/main.py +31 -13
  60. omnibase_infra/services/registry_api/service.py +10 -19
  61. omnibase_infra/services/service_timeout_emitter.py +7 -1
  62. omnibase_infra/services/service_timeout_scanner.py +7 -3
  63. omnibase_infra/services/session/__init__.py +56 -0
  64. omnibase_infra/services/session/config_consumer.py +120 -0
  65. omnibase_infra/services/session/config_store.py +139 -0
  66. omnibase_infra/services/session/consumer.py +1007 -0
  67. omnibase_infra/services/session/protocol_session_aggregator.py +117 -0
  68. omnibase_infra/services/session/store.py +997 -0
  69. omnibase_infra/utils/__init__.py +19 -0
  70. omnibase_infra/utils/util_atomic_file.py +261 -0
  71. omnibase_infra/utils/util_db_transaction.py +239 -0
  72. omnibase_infra/utils/util_retry_optimistic.py +281 -0
  73. omnibase_infra/validation/__init__.py +16 -0
  74. omnibase_infra/validation/validation_exemptions.yaml +27 -0
  75. {omnibase_infra-0.2.2.dist-info → omnibase_infra-0.2.4.dist-info}/METADATA +3 -3
  76. {omnibase_infra-0.2.2.dist-info → omnibase_infra-0.2.4.dist-info}/RECORD +79 -58
  77. {omnibase_infra-0.2.2.dist-info → omnibase_infra-0.2.4.dist-info}/WHEEL +0 -0
  78. {omnibase_infra-0.2.2.dist-info → omnibase_infra-0.2.4.dist-info}/entry_points.txt +0 -0
  79. {omnibase_infra-0.2.2.dist-info → omnibase_infra-0.2.4.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,281 @@
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright (c) 2025 OmniNode Team
3
+ """Optimistic locking retry helper for concurrent data operations.
4
+
5
+ This module provides utilities for retrying operations that may fail due to
6
+ optimistic locking conflicts. Optimistic locking is a concurrency control
7
+ strategy where conflicts are detected at write time rather than using locks.
8
+
9
+ Use Cases:
10
+ - Database UPDATE with version checks (row_count=0 indicates conflict)
11
+ - CAS (Compare-And-Swap) operations in distributed systems
12
+ - Consul KV ModifyIndex-based updates
13
+ - Any operation where conflict detection is based on return value
14
+
15
+ Design Decisions:
16
+ - No circuit breaker integration: Optimistic conflicts are application logic,
17
+ not infrastructure failures. They indicate contention, not service degradation.
18
+ - Lower initial backoff: 0.1s vs 1.0s for transient errors because conflicts
19
+ typically resolve faster as other transactions complete.
20
+ - Jitter by default: Critical for preventing thundering herd on high-contention
21
+ resources where multiple retries might synchronize.
22
+ - Caller-provided conflict check: Flexible for different conflict indicators
23
+ (row_count=0, version mismatch, boolean flags, etc.)
24
+
25
+ Example:
26
+ >>> import asyncio
27
+ >>> from omnibase_infra.utils.util_retry_optimistic import (
28
+ ... retry_on_optimistic_conflict,
29
+ ... OptimisticConflictError,
30
+ ... )
31
+ >>>
32
+ >>> attempt_count = 0
33
+ >>> async def update_with_version_check():
34
+ ... global attempt_count
35
+ ... attempt_count += 1
36
+ ... # Simulate success after 2 conflicts
37
+ ... if attempt_count < 3:
38
+ ... return {"row_count": 0} # Conflict
39
+ ... return {"row_count": 1} # Success
40
+ >>>
41
+ >>> async def main():
42
+ ... result = await retry_on_optimistic_conflict(
43
+ ... update_with_version_check,
44
+ ... check_conflict=lambda r: r["row_count"] == 0,
45
+ ... max_retries=5,
46
+ ... )
47
+ ... print(f"Success after {attempt_count} attempts")
48
+ >>>
49
+ >>> asyncio.run(main()) # doctest: +SKIP
50
+ Success after 3 attempts
51
+
52
+ See Also:
53
+ - ONEX infrastructure patterns documentation
54
+ - PostgreSQL advisory locks vs optimistic locking
55
+ - Consul KV ModifyIndex documentation
56
+
57
+ .. versionadded:: 0.10.0
58
+ Created for database operations requiring optimistic concurrency control.
59
+ """
60
+
61
+ from __future__ import annotations
62
+
63
+ import asyncio
64
+ import logging
65
+ import random
66
+ from collections.abc import Awaitable, Callable
67
+ from typing import TYPE_CHECKING, TypeVar
68
+
69
+ if TYPE_CHECKING:
70
+ from uuid import UUID
71
+
72
+ logger = logging.getLogger(__name__)
73
+
74
+ # Generic type for the return value of the retried function
75
+ T = TypeVar("T")
76
+
77
+
78
+ class OptimisticConflictError(Exception):
79
+ """Exception raised when optimistic locking retries are exhausted.
80
+
81
+ This exception indicates that an operation failed due to repeated optimistic
82
+ locking conflicts after all retry attempts. The caller should handle this
83
+ by either:
84
+ - Reporting the conflict to the user
85
+ - Using a different conflict resolution strategy
86
+ - Applying exponential backoff at a higher level
87
+
88
+ Note:
89
+ This is a standard Python exception, not an ONEX error. Optimistic
90
+ conflicts are expected application behavior, not infrastructure failures.
91
+
92
+ Attributes:
93
+ attempts: The total number of attempts made (including initial attempt).
94
+ last_result: The result from the final failed attempt, useful for
95
+ debugging or logging the conflict state.
96
+
97
+ Example:
98
+ >>> from omnibase_infra.utils.util_retry_optimistic import OptimisticConflictError
99
+ >>>
100
+ >>> try:
101
+ ... # ... retry logic that exhausted retries
102
+ ... raise OptimisticConflictError(
103
+ ... attempts=4,
104
+ ... last_result={"row_count": 0, "current_version": 5}
105
+ ... )
106
+ ... except OptimisticConflictError as e:
107
+ ... print(f"Failed after {e.attempts} attempts")
108
+ ... print(f"Last conflict state: {e.last_result}")
109
+ Failed after 4 attempts
110
+ Last conflict state: {'row_count': 0, 'current_version': 5}
111
+ """
112
+
113
+ def __init__(self, *, attempts: int, last_result: object) -> None:
114
+ """Initialize OptimisticConflictError.
115
+
116
+ Args:
117
+ attempts: Total number of attempts made.
118
+ last_result: Result from the final attempt.
119
+ """
120
+ self.attempts = attempts
121
+ self.last_result = last_result
122
+ super().__init__(
123
+ f"Optimistic locking conflict persisted after {attempts} attempts. "
124
+ f"Last result: {last_result}"
125
+ )
126
+
127
+
128
+ async def retry_on_optimistic_conflict(
129
+ fn: Callable[[], Awaitable[T]],
130
+ *,
131
+ check_conflict: Callable[[T], bool],
132
+ max_retries: int = 3,
133
+ initial_backoff: float = 0.1,
134
+ max_backoff: float = 5.0,
135
+ backoff_multiplier: float = 2.0,
136
+ jitter: bool = True,
137
+ correlation_id: UUID | None = None,
138
+ ) -> T:
139
+ """Execute async function with retry on optimistic locking conflict.
140
+
141
+ This function implements exponential backoff retry logic for operations
142
+ that may fail due to optimistic locking conflicts. Unlike transient error
143
+ retries, this does NOT integrate with circuit breakers since conflicts
144
+ indicate contention, not service degradation.
145
+
146
+ Args:
147
+ fn: Async function to execute. Should take no arguments; use closures
148
+ or functools.partial to bind arguments. Example:
149
+ ``lambda: update_record(id=123, data=data)``
150
+ check_conflict: Callable that inspects the result and returns True if
151
+ the result indicates a conflict. Examples:
152
+ - ``lambda r: r.row_count == 0`` (database update)
153
+ - ``lambda r: r.success is False`` (boolean result)
154
+ - ``lambda r: r.version != expected_version`` (version mismatch)
155
+ max_retries: Maximum number of retry attempts after the initial attempt.
156
+ Total attempts = max_retries + 1. Defaults to 3.
157
+ initial_backoff: Initial backoff delay in seconds before first retry.
158
+ Lower than transient error defaults (0.1s vs 1.0s) because conflicts
159
+ typically resolve quickly. Defaults to 0.1.
160
+ max_backoff: Maximum backoff delay cap in seconds. Prevents excessive
161
+ wait times. Defaults to 5.0.
162
+ backoff_multiplier: Multiplier for exponential backoff between retries.
163
+ Delay doubles by default: 0.1s -> 0.2s -> 0.4s -> 0.8s. Defaults to 2.0.
164
+ jitter: If True, adds random jitter (50-150% of delay) to prevent
165
+ thundering herd when multiple clients retry simultaneously.
166
+ Strongly recommended for high-contention scenarios. Defaults to True.
167
+ correlation_id: Optional correlation ID for structured logging. When
168
+ provided, retry attempts are logged with this ID for distributed
169
+ tracing. Defaults to None.
170
+
171
+ Returns:
172
+ The result of ``fn()`` when ``check_conflict(result)`` returns False.
173
+
174
+ Raises:
175
+ OptimisticConflictError: If all retry attempts are exhausted and the
176
+ operation still indicates a conflict. Contains ``attempts`` count
177
+ and ``last_result`` for debugging.
178
+
179
+ Example:
180
+ Basic usage with row count check::
181
+
182
+ from functools import partial
183
+ from omnibase_infra.utils.util_retry_optimistic import (
184
+ retry_on_optimistic_conflict,
185
+ OptimisticConflictError,
186
+ )
187
+
188
+ async def update_with_version(id: str, data: dict, version: int):
189
+ return await db.execute(
190
+ "UPDATE t SET data=$1, version=version+1 "
191
+ "WHERE id=$2 AND version=$3",
192
+ data, id, version
193
+ )
194
+
195
+ try:
196
+ result = await retry_on_optimistic_conflict(
197
+ partial(update_with_version, "abc", {"name": "new"}, 5),
198
+ check_conflict=lambda r: r.row_count == 0,
199
+ max_retries=5,
200
+ correlation_id=correlation_id,
201
+ )
202
+ except OptimisticConflictError as e:
203
+ logger.error(f"Update failed after {e.attempts} attempts")
204
+ raise
205
+
206
+ Warning:
207
+ The ``fn`` callable should be idempotent or at least safe to retry.
208
+ This function will call ``fn`` multiple times on conflicts.
209
+
210
+ Note:
211
+ Backoff timing with default parameters (jitter disabled for clarity):
212
+ - Attempt 1: immediate
213
+ - Attempt 2: wait 0.1s
214
+ - Attempt 3: wait 0.2s
215
+ - Attempt 4: wait 0.4s
216
+ - Total max wait: ~0.7s (with max_retries=3)
217
+
218
+ .. versionadded:: 0.10.0
219
+ """
220
+ backoff = initial_backoff
221
+ last_result: T | None = None
222
+
223
+ for attempt in range(max_retries + 1): # +1 for initial attempt
224
+ result = await fn()
225
+
226
+ if not check_conflict(result):
227
+ # Success - no conflict detected
228
+ if attempt > 0 and correlation_id is not None:
229
+ logger.info(
230
+ "Optimistic conflict resolved after %d retries",
231
+ attempt,
232
+ extra={
233
+ "correlation_id": str(correlation_id),
234
+ "total_attempts": attempt + 1,
235
+ "action": "optimistic_conflict_resolved",
236
+ },
237
+ )
238
+ return result
239
+
240
+ # Conflict detected - store result for potential error reporting
241
+ last_result = result
242
+
243
+ if attempt == max_retries:
244
+ # All retries exhausted
245
+ break
246
+
247
+ # Log retry attempt if correlation_id provided
248
+ if correlation_id is not None:
249
+ logger.debug(
250
+ "Optimistic conflict detected, retrying (attempt %d/%d)",
251
+ attempt + 1,
252
+ max_retries + 1,
253
+ extra={
254
+ "correlation_id": str(correlation_id),
255
+ "attempt": attempt + 1,
256
+ "max_attempts": max_retries + 1,
257
+ "backoff_seconds": backoff,
258
+ "action": "optimistic_conflict_retry",
259
+ },
260
+ )
261
+
262
+ # Calculate delay with optional jitter
263
+ delay = min(backoff, max_backoff)
264
+ if jitter:
265
+ # Apply 50-150% jitter to prevent thundering herd
266
+ delay *= 0.5 + random.random()
267
+
268
+ await asyncio.sleep(delay)
269
+ backoff *= backoff_multiplier
270
+
271
+ # All retries exhausted - raise conflict error
272
+ # Defensive check - last_result is guaranteed to be set after at least one attempt
273
+ if last_result is None:
274
+ raise AssertionError("Unreachable: last_result must be set after retries")
275
+ raise OptimisticConflictError(attempts=max_retries + 1, last_result=last_result)
276
+
277
+
278
+ __all__: list[str] = [
279
+ "OptimisticConflictError",
280
+ "retry_on_optimistic_conflict",
281
+ ]
@@ -91,13 +91,22 @@ Security Design (Intentional Fail-Open Architecture):
91
91
  - validator_routing_coverage.py: Routing gap detection (module docstring)
92
92
  """
93
93
 
94
+ # Topic suffix validation models (OMN-1537)
95
+ from omnibase_core.models.validation import (
96
+ ModelTopicSuffixParts,
97
+ ModelTopicValidationResult,
98
+ )
94
99
  from omnibase_core.validation import (
95
100
  CircularImportValidator,
96
101
  ModelModuleImportResult,
102
+ compose_full_topic,
103
+ is_valid_topic_suffix,
104
+ parse_topic_suffix,
97
105
  validate_all,
98
106
  validate_architecture,
99
107
  validate_contracts,
100
108
  validate_patterns,
109
+ validate_topic_suffix,
101
110
  validate_union_usage,
102
111
  )
103
112
 
@@ -222,6 +231,13 @@ __all__: list[str] = [
222
231
  "NODE_ARCHETYPE_EXPECTED_CATEGORIES", # Node archetype categories
223
232
  "TOPIC_CATEGORY_PATTERNS", # Topic category patterns
224
233
  "TOPIC_SUFFIXES", # Topic suffix constants
234
+ # Topic suffix validation (OMN-1537)
235
+ "ModelTopicSuffixParts", # Topic suffix parsed parts
236
+ "ModelTopicValidationResult", # Topic validation result
237
+ "compose_full_topic", # Compose full topic from parts
238
+ "is_valid_topic_suffix", # Check if topic suffix is valid
239
+ "parse_topic_suffix", # Parse topic suffix into parts
240
+ "validate_topic_suffix", # Validate topic suffix format
225
241
  # Errors
226
242
  "ChainPropagationError", # Chain propagation error (OMN-951)
227
243
  "ExecutionShapeViolationError", # Execution shape violation
@@ -1094,6 +1094,33 @@ pattern_exemptions:
1094
1094
  - CLAUDE.md (ONEX Architecture - Handler Types)
1095
1095
  ticket: OMN-1087
1096
1096
  # ==========================================================================
1097
+ # Handler Source Mode Exemptions (OMN-1095)
1098
+ # ==========================================================================
1099
+ # EnumHandlerSourceMode and HandlerSourceResolver are part of the handler
1100
+ # source mode feature flag implementation. The "Handler" in the names refers
1101
+ # to ONEX handler concepts (how handlers are discovered/loaded), consistent
1102
+ # with HandlerBootstrapSource and HandlerContractSource.
1103
+ - file_pattern: 'enum_handler_source_mode\.py'
1104
+ class_pattern: "Class name 'EnumHandlerSourceMode'"
1105
+ violation_pattern: "contains anti-pattern 'Handler'"
1106
+ reason: >
1107
+ EnumHandlerSourceMode defines handler loading modes (BOOTSTRAP, CONTRACT, HYBRID). The "Handler" refers to ONEX handler concepts - how handlers are discovered and loaded at runtime. Consistent with HandlerBootstrapSource and HandlerContractSource naming.
1108
+
1109
+ documentation:
1110
+ - docs/architecture/HANDLER_PROTOCOL_DRIVEN_ARCHITECTURE.md
1111
+ - CLAUDE.md (ONEX Architecture - Handler Types)
1112
+ ticket: OMN-1095
1113
+ - file_pattern: 'handler_source_resolver\.py'
1114
+ class_pattern: "Class name 'HandlerSourceResolver'"
1115
+ violation_pattern: "contains anti-pattern 'Handler'"
1116
+ reason: >
1117
+ HandlerSourceResolver implements per-handler identity resolution between bootstrap and contract sources. The "Handler" refers to ONEX handler concepts - resolving which handler source takes precedence. Consistent with HandlerBootstrapSource and HandlerContractSource naming.
1118
+
1119
+ documentation:
1120
+ - docs/architecture/HANDLER_PROTOCOL_DRIVEN_ARCHITECTURE.md
1121
+ - CLAUDE.md (ONEX Architecture - Handler Types)
1122
+ ticket: OMN-1095
1123
+ # ==========================================================================
1097
1124
  # Handler Plugin Loader Exemptions (OMN-1132)
1098
1125
  # ==========================================================================
1099
1126
  # HandlerPluginLoader discovers and loads ONEX handlers from contract YAML files.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: omnibase_infra
3
- Version: 0.2.2
3
+ Version: 0.2.4
4
4
  Summary: ONEX Infrastructure - Service integration and database infrastructure tools
5
5
  License: MIT
6
6
  License-File: LICENSE
@@ -28,8 +28,8 @@ Requires-Dist: jinja2 (>=3.1.6,<4.0.0)
28
28
  Requires-Dist: jsonschema (>=4.20.0,<5.0.0)
29
29
  Requires-Dist: mcp (>=1.25.0,<2.0.0)
30
30
  Requires-Dist: neo4j (>=5.15.0,<6.0.0)
31
- Requires-Dist: omnibase-core (>=0.9.1,<0.10.0)
32
- Requires-Dist: omnibase-spi (>=0.5.0,<0.6.0)
31
+ Requires-Dist: omnibase-core (>=0.9.5,<0.10.0)
32
+ Requires-Dist: omnibase-spi (>=0.6.0,<0.7.0)
33
33
  Requires-Dist: opentelemetry-api (>=1.27.0,<2.0.0)
34
34
  Requires-Dist: opentelemetry-exporter-otlp (>=1.27.0,<2.0.0)
35
35
  Requires-Dist: opentelemetry-instrumentation (>=0.48b0,<0.49)