omnibase_infra 0.2.1__py3-none-any.whl → 0.2.3__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 (161) hide show
  1. omnibase_infra/__init__.py +1 -1
  2. omnibase_infra/adapters/adapter_onex_tool_execution.py +451 -0
  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/cli/commands.py +1 -1
  8. omnibase_infra/configs/widget_mapping.yaml +176 -0
  9. omnibase_infra/contracts/handlers/filesystem/handler_contract.yaml +5 -2
  10. omnibase_infra/contracts/handlers/mcp/handler_contract.yaml +5 -2
  11. omnibase_infra/enums/__init__.py +6 -0
  12. omnibase_infra/enums/enum_handler_error_type.py +10 -0
  13. omnibase_infra/enums/enum_handler_source_mode.py +72 -0
  14. omnibase_infra/enums/enum_kafka_acks.py +99 -0
  15. omnibase_infra/errors/error_compute_registry.py +4 -1
  16. omnibase_infra/errors/error_event_bus_registry.py +4 -1
  17. omnibase_infra/errors/error_infra.py +3 -1
  18. omnibase_infra/errors/error_policy_registry.py +4 -1
  19. omnibase_infra/event_bus/event_bus_kafka.py +1 -1
  20. omnibase_infra/event_bus/models/config/model_kafka_event_bus_config.py +59 -10
  21. omnibase_infra/handlers/__init__.py +8 -1
  22. omnibase_infra/handlers/handler_consul.py +7 -1
  23. omnibase_infra/handlers/handler_db.py +10 -3
  24. omnibase_infra/handlers/handler_graph.py +10 -5
  25. omnibase_infra/handlers/handler_http.py +8 -2
  26. omnibase_infra/handlers/handler_intent.py +387 -0
  27. omnibase_infra/handlers/handler_mcp.py +745 -63
  28. omnibase_infra/handlers/handler_vault.py +11 -5
  29. omnibase_infra/handlers/mixins/mixin_consul_kv.py +4 -3
  30. omnibase_infra/handlers/mixins/mixin_consul_service.py +2 -1
  31. omnibase_infra/handlers/registration_storage/handler_registration_storage_postgres.py +7 -0
  32. omnibase_infra/handlers/service_discovery/handler_service_discovery_consul.py +308 -4
  33. omnibase_infra/handlers/service_discovery/models/model_service_info.py +10 -0
  34. omnibase_infra/mixins/mixin_async_circuit_breaker.py +3 -2
  35. omnibase_infra/mixins/mixin_node_introspection.py +42 -7
  36. omnibase_infra/mixins/mixin_retry_execution.py +1 -1
  37. omnibase_infra/models/discovery/model_introspection_config.py +11 -0
  38. omnibase_infra/models/handlers/__init__.py +48 -5
  39. omnibase_infra/models/handlers/model_bootstrap_handler_descriptor.py +162 -0
  40. omnibase_infra/models/handlers/model_contract_discovery_result.py +6 -4
  41. omnibase_infra/models/handlers/model_handler_descriptor.py +15 -0
  42. omnibase_infra/models/handlers/model_handler_source_config.py +220 -0
  43. omnibase_infra/models/mcp/__init__.py +15 -0
  44. omnibase_infra/models/mcp/model_mcp_contract_config.py +80 -0
  45. omnibase_infra/models/mcp/model_mcp_server_config.py +67 -0
  46. omnibase_infra/models/mcp/model_mcp_tool_definition.py +73 -0
  47. omnibase_infra/models/mcp/model_mcp_tool_parameter.py +35 -0
  48. omnibase_infra/models/registration/model_node_capabilities.py +11 -0
  49. omnibase_infra/models/registration/model_node_introspection_event.py +9 -0
  50. omnibase_infra/models/runtime/model_handler_contract.py +25 -9
  51. omnibase_infra/models/runtime/model_loaded_handler.py +9 -0
  52. omnibase_infra/nodes/architecture_validator/contract_architecture_validator.yaml +0 -5
  53. omnibase_infra/nodes/architecture_validator/registry/registry_infra_architecture_validator.py +17 -10
  54. omnibase_infra/nodes/effects/contract.yaml +0 -5
  55. omnibase_infra/nodes/node_registration_orchestrator/contract.yaml +7 -0
  56. omnibase_infra/nodes/node_registration_orchestrator/handlers/handler_node_introspected.py +86 -1
  57. omnibase_infra/nodes/node_registration_orchestrator/introspection_event_router.py +3 -3
  58. omnibase_infra/nodes/node_registration_orchestrator/plugin.py +1 -1
  59. omnibase_infra/nodes/node_registration_orchestrator/registry/registry_infra_node_registration_orchestrator.py +9 -8
  60. omnibase_infra/nodes/node_registration_orchestrator/timeout_coordinator.py +4 -3
  61. omnibase_infra/nodes/node_registration_orchestrator/wiring.py +14 -13
  62. omnibase_infra/nodes/node_registration_storage_effect/contract.yaml +0 -5
  63. omnibase_infra/nodes/node_registration_storage_effect/node.py +4 -1
  64. omnibase_infra/nodes/node_registration_storage_effect/registry/registry_infra_registration_storage.py +47 -26
  65. omnibase_infra/nodes/node_registry_effect/contract.yaml +0 -5
  66. omnibase_infra/nodes/node_registry_effect/handlers/handler_partial_retry.py +2 -1
  67. omnibase_infra/nodes/node_service_discovery_effect/registry/registry_infra_service_discovery.py +28 -20
  68. omnibase_infra/plugins/examples/plugin_json_normalizer.py +2 -2
  69. omnibase_infra/plugins/examples/plugin_json_normalizer_error_handling.py +2 -2
  70. omnibase_infra/plugins/plugin_compute_base.py +16 -2
  71. omnibase_infra/protocols/__init__.py +2 -0
  72. omnibase_infra/protocols/protocol_container_aware.py +200 -0
  73. omnibase_infra/protocols/protocol_event_projector.py +1 -1
  74. omnibase_infra/runtime/__init__.py +90 -1
  75. omnibase_infra/runtime/binding_config_resolver.py +102 -37
  76. omnibase_infra/runtime/constants_notification.py +75 -0
  77. omnibase_infra/runtime/contract_handler_discovery.py +6 -1
  78. omnibase_infra/runtime/handler_bootstrap_source.py +507 -0
  79. omnibase_infra/runtime/handler_contract_config_loader.py +603 -0
  80. omnibase_infra/runtime/handler_contract_source.py +267 -186
  81. omnibase_infra/runtime/handler_identity.py +81 -0
  82. omnibase_infra/runtime/handler_plugin_loader.py +19 -2
  83. omnibase_infra/runtime/handler_registry.py +11 -3
  84. omnibase_infra/runtime/handler_source_resolver.py +326 -0
  85. omnibase_infra/runtime/mixin_semver_cache.py +25 -1
  86. omnibase_infra/runtime/mixins/__init__.py +7 -0
  87. omnibase_infra/runtime/mixins/mixin_projector_notification_publishing.py +566 -0
  88. omnibase_infra/runtime/mixins/mixin_projector_sql_operations.py +31 -10
  89. omnibase_infra/runtime/models/__init__.py +24 -0
  90. omnibase_infra/runtime/models/model_health_check_result.py +2 -1
  91. omnibase_infra/runtime/models/model_projector_notification_config.py +171 -0
  92. omnibase_infra/runtime/models/model_transition_notification_outbox_config.py +112 -0
  93. omnibase_infra/runtime/models/model_transition_notification_outbox_metrics.py +140 -0
  94. omnibase_infra/runtime/models/model_transition_notification_publisher_metrics.py +357 -0
  95. omnibase_infra/runtime/projector_plugin_loader.py +1 -1
  96. omnibase_infra/runtime/projector_shell.py +229 -1
  97. omnibase_infra/runtime/protocol_lifecycle_executor.py +6 -6
  98. omnibase_infra/runtime/protocols/__init__.py +10 -0
  99. omnibase_infra/runtime/registry/registry_protocol_binding.py +16 -15
  100. omnibase_infra/runtime/registry_contract_source.py +693 -0
  101. omnibase_infra/runtime/registry_policy.py +9 -326
  102. omnibase_infra/runtime/secret_resolver.py +4 -2
  103. omnibase_infra/runtime/service_kernel.py +11 -3
  104. omnibase_infra/runtime/service_message_dispatch_engine.py +4 -2
  105. omnibase_infra/runtime/service_runtime_host_process.py +589 -106
  106. omnibase_infra/runtime/transition_notification_outbox.py +1190 -0
  107. omnibase_infra/runtime/transition_notification_publisher.py +764 -0
  108. omnibase_infra/runtime/util_container_wiring.py +6 -5
  109. omnibase_infra/runtime/util_wiring.py +17 -4
  110. omnibase_infra/schemas/schema_transition_notification_outbox.sql +245 -0
  111. omnibase_infra/services/__init__.py +21 -0
  112. omnibase_infra/services/corpus_capture.py +7 -1
  113. omnibase_infra/services/mcp/__init__.py +31 -0
  114. omnibase_infra/services/mcp/mcp_server_lifecycle.py +449 -0
  115. omnibase_infra/services/mcp/service_mcp_tool_discovery.py +411 -0
  116. omnibase_infra/services/mcp/service_mcp_tool_registry.py +329 -0
  117. omnibase_infra/services/mcp/service_mcp_tool_sync.py +547 -0
  118. omnibase_infra/services/registry_api/__init__.py +40 -0
  119. omnibase_infra/services/registry_api/main.py +261 -0
  120. omnibase_infra/services/registry_api/models/__init__.py +66 -0
  121. omnibase_infra/services/registry_api/models/model_capability_widget_mapping.py +38 -0
  122. omnibase_infra/services/registry_api/models/model_pagination_info.py +48 -0
  123. omnibase_infra/services/registry_api/models/model_registry_discovery_response.py +73 -0
  124. omnibase_infra/services/registry_api/models/model_registry_health_response.py +49 -0
  125. omnibase_infra/services/registry_api/models/model_registry_instance_view.py +88 -0
  126. omnibase_infra/services/registry_api/models/model_registry_node_view.py +88 -0
  127. omnibase_infra/services/registry_api/models/model_registry_summary.py +60 -0
  128. omnibase_infra/services/registry_api/models/model_response_list_instances.py +43 -0
  129. omnibase_infra/services/registry_api/models/model_response_list_nodes.py +51 -0
  130. omnibase_infra/services/registry_api/models/model_warning.py +49 -0
  131. omnibase_infra/services/registry_api/models/model_widget_defaults.py +28 -0
  132. omnibase_infra/services/registry_api/models/model_widget_mapping.py +51 -0
  133. omnibase_infra/services/registry_api/routes.py +371 -0
  134. omnibase_infra/services/registry_api/service.py +837 -0
  135. omnibase_infra/services/service_capability_query.py +4 -4
  136. omnibase_infra/services/service_health.py +3 -2
  137. omnibase_infra/services/service_timeout_emitter.py +20 -3
  138. omnibase_infra/services/service_timeout_scanner.py +7 -3
  139. omnibase_infra/services/session/__init__.py +56 -0
  140. omnibase_infra/services/session/config_consumer.py +120 -0
  141. omnibase_infra/services/session/config_store.py +139 -0
  142. omnibase_infra/services/session/consumer.py +1007 -0
  143. omnibase_infra/services/session/protocol_session_aggregator.py +117 -0
  144. omnibase_infra/services/session/store.py +997 -0
  145. omnibase_infra/utils/__init__.py +19 -0
  146. omnibase_infra/utils/util_atomic_file.py +261 -0
  147. omnibase_infra/utils/util_db_transaction.py +239 -0
  148. omnibase_infra/utils/util_dsn_validation.py +1 -1
  149. omnibase_infra/utils/util_retry_optimistic.py +281 -0
  150. omnibase_infra/validation/__init__.py +3 -19
  151. omnibase_infra/validation/contracts/security.validation.yaml +114 -0
  152. omnibase_infra/validation/infra_validators.py +35 -24
  153. omnibase_infra/validation/validation_exemptions.yaml +140 -9
  154. omnibase_infra/validation/validator_chain_propagation.py +2 -2
  155. omnibase_infra/validation/validator_runtime_shape.py +1 -1
  156. omnibase_infra/validation/validator_security.py +473 -370
  157. {omnibase_infra-0.2.1.dist-info → omnibase_infra-0.2.3.dist-info}/METADATA +3 -3
  158. {omnibase_infra-0.2.1.dist-info → omnibase_infra-0.2.3.dist-info}/RECORD +161 -98
  159. {omnibase_infra-0.2.1.dist-info → omnibase_infra-0.2.3.dist-info}/WHEEL +0 -0
  160. {omnibase_infra-0.2.1.dist-info → omnibase_infra-0.2.3.dist-info}/entry_points.txt +0 -0
  161. {omnibase_infra-0.2.1.dist-info → omnibase_infra-0.2.3.dist-info}/licenses/LICENSE +0 -0
@@ -131,10 +131,10 @@ class ServiceCapabilityQuery(MixinAsyncCircuitBreaker):
131
131
  Dependency Injection Pattern: This service is a leaf infrastructure
132
132
  service that receives its dependencies directly via constructor
133
133
  parameters rather than resolving them from a container. Unlike
134
- orchestrators that use ``container.resolve()`` to obtain services
135
- dynamically, leaf services like this one are instantiated with
136
- concrete dependencies (projection_reader, node_selector) and are
137
- themselves resolved by higher-level components.
134
+ orchestrators that use ``container.service_registry.resolve_service()``
135
+ to obtain services dynamically, leaf services like this one are
136
+ instantiated with concrete dependencies (projection_reader, node_selector)
137
+ and are themselves resolved by higher-level components.
138
138
  """
139
139
 
140
140
  def __init__(
@@ -57,10 +57,11 @@ See Also:
57
57
  from __future__ import annotations
58
58
 
59
59
  import logging
60
- from typing import TYPE_CHECKING, Literal
60
+ from typing import TYPE_CHECKING, Literal, cast
61
61
 
62
62
  from aiohttp import web
63
63
 
64
+ from omnibase_core.types import JsonType
64
65
  from omnibase_infra.enums import EnumInfraTransportType
65
66
  from omnibase_infra.errors import (
66
67
  ModelInfraErrorContext,
@@ -859,7 +860,7 @@ class ServiceHealth:
859
860
  response = ModelHealthCheckResponse.success(
860
861
  status=status,
861
862
  version=self._version,
862
- details=health_details,
863
+ details=cast("dict[str, JsonType]", health_details),
863
864
  )
864
865
 
865
866
  return web.Response(
@@ -36,6 +36,8 @@ from uuid import UUID
36
36
 
37
37
  from pydantic import BaseModel, ConfigDict, Field, field_validator
38
38
 
39
+ from omnibase_core.container import ModelONEXContainer
40
+ from omnibase_core.models.events.model_event_envelope import ModelEventEnvelope
39
41
  from omnibase_infra.enums import EnumInfraTransportType
40
42
  from omnibase_infra.errors import ModelInfraErrorContext, ProtocolConfigurationError
41
43
  from omnibase_infra.models.projection import ModelRegistrationProjection
@@ -216,6 +218,7 @@ class ServiceTimeoutEmitter:
216
218
 
217
219
  Usage:
218
220
  >>> emitter = ServiceTimeoutEmitter(
221
+ ... container=container,
219
222
  ... timeout_query=timeout_scanner,
220
223
  ... event_bus=event_bus,
221
224
  ... projector=projector,
@@ -261,6 +264,7 @@ class ServiceTimeoutEmitter:
261
264
 
262
265
  def __init__(
263
266
  self,
267
+ container: ModelONEXContainer,
264
268
  timeout_query: ServiceTimeoutScanner,
265
269
  event_bus: ProtocolEventBus,
266
270
  projector: ProjectorShell,
@@ -269,6 +273,7 @@ class ServiceTimeoutEmitter:
269
273
  """Initialize with required dependencies.
270
274
 
271
275
  Args:
276
+ container: ONEX container for dependency injection.
272
277
  timeout_query: Scanner for querying overdue entities.
273
278
  Must be initialized with a ProjectionReaderRegistration.
274
279
  event_bus: Event bus for publishing timeout events.
@@ -280,16 +285,18 @@ class ServiceTimeoutEmitter:
280
285
 
281
286
  Example:
282
287
  >>> reader = ProjectionReaderRegistration(pool)
283
- >>> timeout_query = ServiceTimeoutScanner(reader)
288
+ >>> timeout_query = ServiceTimeoutScanner(container, reader)
284
289
  >>> bus = EventBusKafka.default()
285
290
  >>> projector = projector_loader.load("registration_projector")
286
291
  >>> emitter = ServiceTimeoutEmitter(
292
+ ... container=container,
287
293
  ... timeout_query=timeout_query,
288
294
  ... event_bus=bus,
289
295
  ... projector=projector,
290
296
  ... config=ModelTimeoutEmissionConfig(environment="dev"),
291
297
  ... )
292
298
  """
299
+ self._container = container
293
300
  self._timeout_query = timeout_query
294
301
  self._event_bus = event_bus
295
302
  self._projector = projector
@@ -568,8 +575,13 @@ class ServiceTimeoutEmitter:
568
575
  },
569
576
  )
570
577
 
578
+ # Wrap event in ModelEventEnvelope for protocol compliance
579
+ envelope: ModelEventEnvelope[object] = ModelEventEnvelope(
580
+ payload=event,
581
+ correlation_id=correlation_id,
582
+ )
571
583
  await self._event_bus.publish_envelope(
572
- envelope=event,
584
+ envelope=envelope, # type: ignore[arg-type]
573
585
  topic=topic,
574
586
  )
575
587
 
@@ -657,8 +669,13 @@ class ServiceTimeoutEmitter:
657
669
  },
658
670
  )
659
671
 
672
+ # Wrap event in ModelEventEnvelope for protocol compliance
673
+ envelope: ModelEventEnvelope[object] = ModelEventEnvelope(
674
+ payload=event,
675
+ correlation_id=correlation_id,
676
+ )
660
677
  await self._event_bus.publish_envelope(
661
- envelope=event,
678
+ envelope=envelope, # type: ignore[arg-type]
662
679
  topic=topic,
663
680
  )
664
681
 
@@ -31,6 +31,7 @@ from uuid import UUID, uuid4
31
31
 
32
32
  from pydantic import BaseModel, ConfigDict, Field
33
33
 
34
+ from omnibase_core.container import ModelONEXContainer
34
35
  from omnibase_infra.models.projection import ModelRegistrationProjection
35
36
  from omnibase_infra.projectors.projection_reader_registration import (
36
37
  ProjectionReaderRegistration,
@@ -126,7 +127,7 @@ class ServiceTimeoutScanner:
126
127
 
127
128
  Usage:
128
129
  >>> reader = ProjectionReaderRegistration(pool)
129
- >>> scanner = ServiceTimeoutScanner(reader)
130
+ >>> scanner = ServiceTimeoutScanner(container, reader)
130
131
  >>> result = await scanner.find_overdue_entities(now=tick.now)
131
132
  >>>
132
133
  >>> for projection in result.ack_timeouts:
@@ -152,12 +153,14 @@ class ServiceTimeoutScanner:
152
153
 
153
154
  def __init__(
154
155
  self,
156
+ container: ModelONEXContainer,
155
157
  projection_reader: ProjectionReaderRegistration,
156
158
  batch_size: int | None = None,
157
159
  ) -> None:
158
- """Initialize with projection reader dependency.
160
+ """Initialize the timeout scanner service.
159
161
 
160
162
  Args:
163
+ container: ONEX container for dependency injection.
161
164
  projection_reader: The projection reader for database queries.
162
165
  Must be initialized with an asyncpg connection pool.
163
166
  batch_size: Maximum entities to return per query type.
@@ -166,8 +169,9 @@ class ServiceTimeoutScanner:
166
169
  Example:
167
170
  >>> pool = await asyncpg.create_pool(dsn)
168
171
  >>> reader = ProjectionReaderRegistration(pool)
169
- >>> scanner = ServiceTimeoutScanner(reader)
172
+ >>> scanner = ServiceTimeoutScanner(container, reader)
170
173
  """
174
+ self._container = container
171
175
  self._reader = projection_reader
172
176
  self._batch_size = batch_size or self.DEFAULT_BATCH_SIZE
173
177
 
@@ -0,0 +1,56 @@
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright (c) 2025 OmniNode Team
3
+ """Session storage and event consumer services.
4
+
5
+ This module provides infrastructure for persisting Claude Code session
6
+ snapshots and consuming session events from Kafka.
7
+
8
+ Moved from omniclaude as part of OMN-1526 architectural cleanup.
9
+
10
+ Components:
11
+ - SessionSnapshotStore: PostgreSQL storage for session snapshots
12
+ - SessionEventConsumer: Kafka consumer for session events
13
+ - ConfigSessionStorage: Storage configuration
14
+ - ConfigSessionConsumer: Consumer configuration
15
+ - ConsumerMetrics: Metrics for consumer observability
16
+ - EnumCircuitState: Circuit breaker states
17
+
18
+ Example:
19
+ >>> from omnibase_infra.services.session import (
20
+ ... SessionSnapshotStore,
21
+ ... ConfigSessionStorage,
22
+ ... )
23
+ >>> from pydantic import SecretStr
24
+ >>>
25
+ >>> config = ConfigSessionStorage(postgres_password=SecretStr("secret"))
26
+ >>> store = SessionSnapshotStore(config)
27
+ >>> await store.initialize()
28
+ """
29
+
30
+ from omnibase_infra.services.session.config_consumer import ConfigSessionConsumer
31
+ from omnibase_infra.services.session.config_store import ConfigSessionStorage
32
+ from omnibase_infra.services.session.consumer import (
33
+ ConsumerMetrics,
34
+ EnumCircuitState,
35
+ SessionEventConsumer,
36
+ )
37
+ from omnibase_infra.services.session.protocol_session_aggregator import (
38
+ ProtocolSessionAggregator,
39
+ )
40
+ from omnibase_infra.services.session.store import (
41
+ SessionSnapshotStore,
42
+ SessionStoreNotInitializedError,
43
+ )
44
+
45
+ __all__ = [
46
+ # Storage
47
+ "SessionSnapshotStore",
48
+ "SessionStoreNotInitializedError",
49
+ "ConfigSessionStorage",
50
+ # Consumer
51
+ "SessionEventConsumer",
52
+ "ConfigSessionConsumer",
53
+ "ConsumerMetrics",
54
+ "EnumCircuitState",
55
+ "ProtocolSessionAggregator",
56
+ ]
@@ -0,0 +1,120 @@
1
+ """Configuration for session event consumers.
2
+
3
+ Loads from environment variables with OMNIBASE_INFRA_SESSION_CONSUMER_ prefix.
4
+
5
+ Moved from omniclaude as part of OMN-1526 architectural cleanup.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import logging
11
+
12
+ from pydantic import Field, model_validator
13
+ from pydantic_settings import BaseSettings, SettingsConfigDict
14
+
15
+ logger = logging.getLogger(__name__)
16
+
17
+
18
+ class ConfigSessionConsumer(BaseSettings):
19
+ """Configuration for the Claude session event Kafka consumer.
20
+
21
+ Environment variables use the OMNIBASE_INFRA_SESSION_CONSUMER_ prefix.
22
+ Example: OMNIBASE_INFRA_SESSION_CONSUMER_BOOTSTRAP_SERVERS=kafka.example.com:9092
23
+ """
24
+
25
+ model_config = SettingsConfigDict(
26
+ env_prefix="OMNIBASE_INFRA_SESSION_CONSUMER_",
27
+ env_file=".env",
28
+ env_file_encoding="utf-8",
29
+ case_sensitive=False,
30
+ extra="ignore",
31
+ )
32
+
33
+ # Kafka connection
34
+ bootstrap_servers: str = Field(
35
+ default="localhost:9092",
36
+ description="Kafka bootstrap servers. Set via OMNIBASE_INFRA_SESSION_CONSUMER_BOOTSTRAP_SERVERS env var for production.",
37
+ )
38
+ group_id: str = Field(
39
+ default="omnibase-infra-session-consumer",
40
+ description="Consumer group ID",
41
+ )
42
+
43
+ # Topics to subscribe
44
+ topics: list[str] = Field(
45
+ default=[
46
+ "dev.omniclaude.session.started.v1",
47
+ "dev.omniclaude.session.ended.v1",
48
+ "dev.omniclaude.prompt.submitted.v1",
49
+ "dev.omniclaude.tool.executed.v1",
50
+ ],
51
+ description="Kafka topics to consume",
52
+ )
53
+
54
+ # Consumer behavior
55
+ auto_offset_reset: str = Field(
56
+ default="earliest",
57
+ description="Where to start consuming if no offset exists",
58
+ )
59
+ enable_auto_commit: bool = Field(
60
+ default=False,
61
+ description="Disable auto-commit for at-least-once delivery",
62
+ )
63
+ max_poll_records: int = Field(
64
+ default=100,
65
+ ge=1,
66
+ le=10000,
67
+ description="Maximum records per poll",
68
+ )
69
+
70
+ # Processing
71
+ batch_timeout_ms: int = Field(
72
+ default=5000,
73
+ ge=100,
74
+ le=60000,
75
+ description="Timeout for batch processing in milliseconds",
76
+ )
77
+
78
+ # Circuit breaker
79
+ circuit_breaker_threshold: int = Field(
80
+ default=5,
81
+ ge=1,
82
+ le=100,
83
+ description="Failures before circuit opens",
84
+ )
85
+ circuit_breaker_timeout_seconds: int = Field(
86
+ default=60,
87
+ ge=1,
88
+ le=3600,
89
+ description="Time before circuit half-opens",
90
+ )
91
+ circuit_breaker_half_open_successes: int = Field(
92
+ default=1,
93
+ ge=1,
94
+ le=10,
95
+ description="Number of successful requests required to close circuit from half-open state",
96
+ )
97
+
98
+ @model_validator(mode="after")
99
+ def validate_timing_relationships(self) -> ConfigSessionConsumer:
100
+ """Validate timing relationships between configuration values.
101
+
102
+ Warns if circuit breaker timeout is very short relative to batch processing,
103
+ which could cause premature circuit opens during normal batch operations.
104
+
105
+ Returns:
106
+ Self if validation passes.
107
+ """
108
+ batch_timeout_seconds = self.batch_timeout_ms / 1000
109
+ min_recommended_circuit_timeout = batch_timeout_seconds * 2
110
+
111
+ if self.circuit_breaker_timeout_seconds < min_recommended_circuit_timeout:
112
+ logger.warning(
113
+ "Circuit breaker timeout (%ds) is less than 2x batch timeout (%.1fs). "
114
+ "This may cause premature circuit opens during normal batch processing. "
115
+ "Recommended minimum: %ds",
116
+ self.circuit_breaker_timeout_seconds,
117
+ batch_timeout_seconds,
118
+ int(min_recommended_circuit_timeout),
119
+ )
120
+ return self
@@ -0,0 +1,139 @@
1
+ """Configuration for session snapshot storage.
2
+
3
+ Loads from environment variables with OMNIBASE_INFRA_SESSION_STORAGE_ prefix.
4
+
5
+ Moved from omniclaude as part of OMN-1526 architectural cleanup.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from pydantic import Field, SecretStr, model_validator
11
+ from pydantic_settings import BaseSettings, SettingsConfigDict
12
+
13
+
14
+ class ConfigSessionStorage(BaseSettings):
15
+ """Configuration for session snapshot PostgreSQL storage.
16
+
17
+ Environment variables use the OMNIBASE_INFRA_SESSION_STORAGE_ prefix.
18
+ Example: OMNIBASE_INFRA_SESSION_STORAGE_POSTGRES_HOST=192.168.86.200
19
+ """
20
+
21
+ model_config = SettingsConfigDict(
22
+ env_prefix="OMNIBASE_INFRA_SESSION_STORAGE_",
23
+ env_file=".env",
24
+ env_file_encoding="utf-8",
25
+ case_sensitive=False,
26
+ extra="ignore",
27
+ )
28
+
29
+ # PostgreSQL connection
30
+ postgres_host: str = Field(
31
+ default="192.168.86.200",
32
+ description="PostgreSQL host",
33
+ )
34
+ postgres_port: int = Field(
35
+ default=5436,
36
+ ge=1,
37
+ le=65535,
38
+ description="PostgreSQL port",
39
+ )
40
+ postgres_database: str = Field(
41
+ default="omninode_bridge",
42
+ description="PostgreSQL database name",
43
+ )
44
+ postgres_user: str = Field(
45
+ default="postgres",
46
+ description="PostgreSQL user",
47
+ )
48
+ postgres_password: SecretStr = Field(
49
+ ..., # Required
50
+ description="PostgreSQL password - set via OMNIBASE_INFRA_SESSION_STORAGE_POSTGRES_PASSWORD env var",
51
+ )
52
+
53
+ # Connection pool
54
+ pool_min_size: int = Field(
55
+ default=2,
56
+ ge=1,
57
+ le=100,
58
+ description="Minimum connection pool size",
59
+ )
60
+ pool_max_size: int = Field(
61
+ default=10,
62
+ ge=1,
63
+ le=100,
64
+ description="Maximum connection pool size",
65
+ )
66
+
67
+ # Query timeouts
68
+ query_timeout_seconds: int = Field(
69
+ default=30,
70
+ ge=1,
71
+ le=300,
72
+ description="Query timeout in seconds",
73
+ )
74
+
75
+ @model_validator(mode="after")
76
+ def validate_pool_sizes(self) -> ConfigSessionStorage:
77
+ """Validate that pool_min_size <= pool_max_size.
78
+
79
+ Returns:
80
+ Self if validation passes.
81
+
82
+ Raises:
83
+ ValueError: If pool_min_size > pool_max_size.
84
+ """
85
+ if self.pool_min_size > self.pool_max_size:
86
+ raise ValueError(
87
+ f"pool_min_size ({self.pool_min_size}) must be <= "
88
+ f"pool_max_size ({self.pool_max_size})"
89
+ )
90
+ return self
91
+
92
+ @property
93
+ def dsn(self) -> str:
94
+ """Build PostgreSQL DSN from components.
95
+
96
+ Returns:
97
+ PostgreSQL connection string.
98
+ """
99
+ password = self.postgres_password.get_secret_value()
100
+ return (
101
+ f"postgresql://{self.postgres_user}:{password}"
102
+ f"@{self.postgres_host}:{self.postgres_port}"
103
+ f"/{self.postgres_database}"
104
+ )
105
+
106
+ @property
107
+ def dsn_async(self) -> str:
108
+ """Build async PostgreSQL DSN for asyncpg.
109
+
110
+ Returns:
111
+ PostgreSQL connection string with postgresql+asyncpg scheme.
112
+ """
113
+ password = self.postgres_password.get_secret_value()
114
+ return (
115
+ f"postgresql+asyncpg://{self.postgres_user}:{password}"
116
+ f"@{self.postgres_host}:{self.postgres_port}"
117
+ f"/{self.postgres_database}"
118
+ )
119
+
120
+ @property
121
+ def dsn_safe(self) -> str:
122
+ """Build PostgreSQL DSN with password masked (safe for logging).
123
+
124
+ Returns:
125
+ PostgreSQL connection string with password replaced by ***.
126
+ """
127
+ return (
128
+ f"postgresql://{self.postgres_user}:***"
129
+ f"@{self.postgres_host}:{self.postgres_port}"
130
+ f"/{self.postgres_database}"
131
+ )
132
+
133
+ def __repr__(self) -> str:
134
+ """Safe string representation that doesn't expose credentials.
135
+
136
+ Returns:
137
+ String representation with masked password.
138
+ """
139
+ return f"ConfigSessionStorage(dsn={self.dsn_safe!r})"