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
@@ -121,14 +121,18 @@ from omnibase_infra.errors import ModelInfraErrorContext, ProtocolConfigurationE
121
121
  from omnibase_infra.event_bus.event_bus_inmemory import EventBusInmemory
122
122
  from omnibase_infra.handlers.handler_consul import HandlerConsul
123
123
  from omnibase_infra.handlers.handler_db import HandlerDb
124
+ from omnibase_infra.handlers.handler_graph import HandlerGraph
124
125
  from omnibase_infra.handlers.handler_http import HandlerHttpRest
126
+ from omnibase_infra.handlers.handler_intent import HandlerIntent
125
127
  from omnibase_infra.handlers.handler_mcp import HandlerMCP
126
128
  from omnibase_infra.handlers.handler_vault import HandlerVault
127
129
  from omnibase_infra.runtime.handler_registry import (
128
130
  EVENT_BUS_INMEMORY,
129
131
  HANDLER_TYPE_CONSUL,
130
132
  HANDLER_TYPE_DATABASE,
133
+ HANDLER_TYPE_GRAPH,
131
134
  HANDLER_TYPE_HTTP,
135
+ HANDLER_TYPE_INTENT,
132
136
  HANDLER_TYPE_MCP,
133
137
  HANDLER_TYPE_VAULT,
134
138
  RegistryEventBusBinding,
@@ -139,7 +143,7 @@ from omnibase_infra.runtime.handler_registry import (
139
143
 
140
144
  if TYPE_CHECKING:
141
145
  from omnibase_core.protocol.protocol_event_bus import ProtocolEventBus
142
- from omnibase_spi.protocols.handlers.protocol_handler import ProtocolHandler
146
+ from omnibase_infra.protocols import ProtocolContainerAware
143
147
 
144
148
  logger = logging.getLogger(__name__)
145
149
 
@@ -166,11 +170,14 @@ logger = logging.getLogger(__name__)
166
170
  # NOTE: HandlerHttpRest and HandlerDb use legacy execute(envelope: dict) signature.
167
171
  # They will be migrated to ProtocolHandler.execute(request, operation_config) in future.
168
172
  # Type ignore comments suppress MyPy errors during MVP phase.
169
- _KNOWN_HANDLERS: dict[str, tuple[type[ProtocolHandler], str]] = {
173
+ _KNOWN_HANDLERS: dict[str, tuple[type[ProtocolContainerAware], str]] = {
170
174
  # NOTE: Handlers implement ProtocolHandler structurally but concrete types differ from protocol.
171
175
  HANDLER_TYPE_CONSUL: (HandlerConsul, "HashiCorp Consul service discovery handler"), # type: ignore[dict-item] # NOTE: structural subtyping
172
176
  HANDLER_TYPE_DATABASE: (HandlerDb, "PostgreSQL database handler"), # type: ignore[dict-item] # NOTE: structural subtyping
177
+ HANDLER_TYPE_GRAPH: (HandlerGraph, "Graph database (Memgraph/Neo4j) handler"), # type: ignore[dict-item] # NOTE: structural subtyping
173
178
  HANDLER_TYPE_HTTP: (HandlerHttpRest, "HTTP REST protocol handler"), # type: ignore[dict-item] # NOTE: structural subtyping
179
+ # DEMO: Temporary registration - remove when contract-driven (OMN-1515)
180
+ HANDLER_TYPE_INTENT: (HandlerIntent, "Intent storage and query handler for demo"), # type: ignore[dict-item] # NOTE: structural subtyping
174
181
  HANDLER_TYPE_MCP: (HandlerMCP, "Model Context Protocol handler for AI agents"), # type: ignore[dict-item] # NOTE: structural subtyping
175
182
  HANDLER_TYPE_VAULT: (HandlerVault, "HashiCorp Vault secret management handler"), # type: ignore[dict-item] # NOTE: structural subtyping
176
183
  }
@@ -197,7 +204,9 @@ def wire_default_handlers() -> dict[str, list[str]]:
197
204
  Registered Handlers:
198
205
  - CONSUL: HandlerConsul for HashiCorp Consul service discovery
199
206
  - DB: HandlerDb for PostgreSQL database operations
207
+ - GRAPH: HandlerGraph for graph database (Memgraph/Neo4j) operations
200
208
  - HTTP: HandlerHttpRest for HTTP/REST protocol operations
209
+ - INTENT: HandlerIntent for intent storage and query (demo)
201
210
  - MCP: HandlerMCP for Model Context Protocol AI agent integration
202
211
  - VAULT: HandlerVault for HashiCorp Vault secret management
203
212
 
@@ -487,7 +496,7 @@ def get_known_event_bus_kinds() -> list[str]:
487
496
 
488
497
  def wire_custom_handler(
489
498
  handler_type: str,
490
- handler_cls: type[ProtocolHandler],
499
+ handler_cls: type[ProtocolContainerAware],
491
500
  registry: RegistryProtocolBinding | None = None,
492
501
  ) -> None:
493
502
  """Register a custom handler class with the registry.
@@ -39,6 +39,18 @@ from omnibase_infra.services.service_timeout_scanner import (
39
39
  ModelTimeoutQueryResult,
40
40
  ServiceTimeoutScanner,
41
41
  )
42
+
43
+ # Session services (moved from omniclaude in OMN-1526)
44
+ from omnibase_infra.services.session import (
45
+ ConfigSessionConsumer,
46
+ ConfigSessionStorage,
47
+ ConsumerMetrics,
48
+ EnumCircuitState,
49
+ ProtocolSessionAggregator,
50
+ SessionEventConsumer,
51
+ SessionSnapshotStore,
52
+ SessionStoreNotInitializedError,
53
+ )
42
54
  from omnibase_infra.services.snapshot import (
43
55
  ServiceSnapshot,
44
56
  StoreSnapshotInMemory,
@@ -65,4 +77,13 @@ __all__ = [
65
77
  "StoreSnapshotPostgres",
66
78
  "TimeoutEmitter",
67
79
  "TimeoutScanner",
80
+ # Session services (OMN-1526)
81
+ "ConfigSessionConsumer",
82
+ "ConfigSessionStorage",
83
+ "ConsumerMetrics",
84
+ "EnumCircuitState",
85
+ "ProtocolSessionAggregator",
86
+ "SessionEventConsumer",
87
+ "SessionSnapshotStore",
88
+ "SessionStoreNotInitializedError",
68
89
  ]
@@ -22,6 +22,7 @@ from datetime import UTC, datetime
22
22
  from typing import Protocol
23
23
  from uuid import UUID
24
24
 
25
+ from omnibase_core.container import ModelONEXContainer
25
26
  from omnibase_core.enums import EnumCoreErrorCode
26
27
  from omnibase_core.errors import OnexError
27
28
  from omnibase_core.models.manifest.model_execution_manifest import (
@@ -226,13 +227,15 @@ class CorpusCapture:
226
227
  - Max executions enforcement with automatic state transitions
227
228
 
228
229
  Example:
230
+ >>> from omnibase_core.container import ModelONEXContainer
231
+ >>> container = ModelONEXContainer(...) # Configure as needed
229
232
  >>> config = ModelCaptureConfig(
230
233
  ... corpus_display_name="regression-suite-v1",
231
234
  ... max_executions=50,
232
235
  ... sample_rate=0.5,
233
236
  ... handler_filter=("compute-handler",),
234
237
  ... )
235
- >>> service = CorpusCapture()
238
+ >>> service = CorpusCapture(container)
236
239
  >>> service.create_corpus(config)
237
240
  >>> service.start_capture()
238
241
  >>>
@@ -249,17 +252,20 @@ class CorpusCapture:
249
252
 
250
253
  def __init__(
251
254
  self,
255
+ container: ModelONEXContainer,
252
256
  persistence: ProtocolManifestPersistence | None = None,
253
257
  ) -> None:
254
258
  """
255
259
  Initialize the corpus capture service.
256
260
 
257
261
  Args:
262
+ container: ONEX container for dependency injection.
258
263
  persistence: Optional persistence handler for flushing manifests.
259
264
  If provided, manifests can be persisted via flush_to_persistence()
260
265
  or by calling close_corpus_async(flush=True). The synchronous
261
266
  close_corpus() does NOT automatically flush.
262
267
  """
268
+ self._container = container
263
269
  self._state_machine = CaptureLifecycleFSM()
264
270
  self._config: ModelCaptureConfig | None = None
265
271
  self._corpus: ModelExecutionCorpus | None = None
@@ -15,7 +15,7 @@ Architecture:
15
15
 
16
16
  Usage:
17
17
  ```python
18
- lifecycle = MCPServerLifecycle(config)
18
+ lifecycle = MCPServerLifecycle(container=container, config=config)
19
19
  await lifecycle.start()
20
20
  # ... server is running ...
21
21
  await lifecycle.shutdown()
@@ -28,6 +28,7 @@ import logging
28
28
  from typing import TYPE_CHECKING
29
29
  from uuid import UUID, uuid4
30
30
 
31
+ from omnibase_core.container import ModelONEXContainer
31
32
  from omnibase_infra.adapters.adapter_onex_tool_execution import (
32
33
  AdapterONEXToolExecution,
33
34
  )
@@ -68,6 +69,7 @@ class MCPServerLifecycle:
68
69
  3. shutdown(): Clean up all resources
69
70
 
70
71
  Attributes:
72
+ _container: ONEX container for dependency injection.
71
73
  _config: Server configuration.
72
74
  _registry: Tool registry instance.
73
75
  _discovery: Consul discovery service.
@@ -80,25 +82,28 @@ class MCPServerLifecycle:
80
82
  ... consul_host="consul.local",
81
83
  ... http_port=8090,
82
84
  ... )
83
- >>> lifecycle = MCPServerLifecycle(config)
85
+ >>> lifecycle = MCPServerLifecycle(container=container, config=config)
84
86
  >>> await lifecycle.start()
85
- >>> handler = lifecycle.get_handler(container)
87
+ >>> handler = lifecycle.get_handler()
86
88
  >>> # Use handler with uvicorn/transport
87
89
  >>> await lifecycle.shutdown()
88
90
  """
89
91
 
90
92
  def __init__(
91
93
  self,
94
+ container: ModelONEXContainer,
92
95
  config: ModelMCPServerConfig,
93
96
  bus: EventBusKafka | None = None,
94
97
  ) -> None:
95
98
  """Initialize the lifecycle manager.
96
99
 
97
100
  Args:
101
+ container: ONEX container for dependency injection.
98
102
  config: Server configuration.
99
103
  bus: Optional Kafka event bus for hot reload. If not provided,
100
104
  Kafka subscription is skipped even if kafka_enabled=True.
101
105
  """
106
+ self._container = container
102
107
  self._config = config
103
108
  self._bus = bus
104
109
 
@@ -162,6 +167,7 @@ class MCPServerLifecycle:
162
167
  # Create services
163
168
  self._registry = ServiceMCPToolRegistry()
164
169
  self._executor = AdapterONEXToolExecution(
170
+ container=self._container,
165
171
  default_timeout=self._config.default_timeout,
166
172
  )
167
173
 
@@ -7,13 +7,18 @@ Provides factory function for flexible instantiation with different
7
7
  backend configurations.
8
8
 
9
9
  Usage:
10
- # Create app with default settings (no backends)
11
- app = create_app()
10
+ # Create app with container (required)
11
+ from omnibase_core.container import ModelONEXContainer
12
+
13
+ container = ModelONEXContainer()
14
+ app = create_app(container=container, cors_origins=["http://localhost:3000"])
12
15
 
13
16
  # Create app with full backends
14
17
  app = create_app(
18
+ container=container,
15
19
  projection_reader=reader,
16
20
  consul_handler=handler,
21
+ cors_origins=["http://localhost:3000"],
17
22
  )
18
23
 
19
24
  # Run with uvicorn
@@ -35,6 +40,7 @@ from typing import TYPE_CHECKING
35
40
  from fastapi import FastAPI
36
41
  from fastapi.middleware.cors import CORSMiddleware
37
42
 
43
+ from omnibase_core.container import ModelONEXContainer
38
44
  from omnibase_infra.enums import EnumInfraTransportType
39
45
  from omnibase_infra.errors import ModelInfraErrorContext, ProtocolConfigurationError
40
46
  from omnibase_infra.services.registry_api.routes import router
@@ -117,6 +123,7 @@ async def lifespan(app: FastAPI) -> AsyncIterator[None]:
117
123
 
118
124
 
119
125
  def create_app(
126
+ container: ModelONEXContainer,
120
127
  projection_reader: ProjectionReaderRegistration | None = None,
121
128
  consul_handler: HandlerServiceDiscoveryConsul | None = None,
122
129
  widget_mapping_path: Path | None = None,
@@ -129,6 +136,8 @@ def create_app(
129
136
  return partial data with warnings when backends are unavailable.
130
137
 
131
138
  Args:
139
+ container: ONEX container for dependency injection. Required for
140
+ ONEX DI pattern compliance.
132
141
  projection_reader: Optional projection reader for node registrations.
133
142
  consul_handler: Optional Consul handler for live instances.
134
143
  widget_mapping_path: Optional path to widget mapping YAML.
@@ -145,7 +154,9 @@ def create_app(
145
154
 
146
155
  Example:
147
156
  >>> from omnibase_infra.services.registry_api import create_app
148
- >>> app = create_app()
157
+ >>> from omnibase_core.container import ModelONEXContainer
158
+ >>> container = ModelONEXContainer()
159
+ >>> app = create_app(container=container)
149
160
  >>> # Run with: uvicorn module:app --host 0.0.0.0 --port 8000
150
161
  """
151
162
  app = FastAPI(
@@ -195,6 +206,7 @@ def create_app(
195
206
 
196
207
  # Create and attach service
197
208
  service = ServiceRegistryDiscovery(
209
+ container=container,
198
210
  projection_reader=projection_reader,
199
211
  consul_handler=consul_handler,
200
212
  widget_mapping_path=widget_mapping_path,
@@ -226,18 +238,24 @@ def create_app(
226
238
  return app
227
239
 
228
240
 
229
- # Default app instance for direct uvicorn usage
230
- # Example: uvicorn omnibase_infra.services.registry_api.main:app --host 0.0.0.0 --port 8000
231
- # Note: This creates an app with no backends configured. For production,
232
- # use create_app() directly with proper backend configuration (projection_reader,
233
- # consul_handler). Tests should also use create_app() for controlled app creation.
241
+ # Module-level app instance is not supported since container is required.
242
+ # For production usage, use create_app() with proper configuration:
243
+ #
244
+ # Example:
245
+ # from omnibase_core.container import ModelONEXContainer
246
+ # from omnibase_infra.services.registry_api import create_app
247
+ #
248
+ # container = ModelONEXContainer()
249
+ # app = create_app(
250
+ # container=container,
251
+ # projection_reader=reader,
252
+ # consul_handler=handler,
253
+ # cors_origins=["http://localhost:3000"],
254
+ # )
255
+ # uvicorn.run(app, host="0.0.0.0", port=8000)
234
256
  #
235
- # IMPORTANT: CORS_ORIGINS environment variable MUST be set for module-level app creation.
236
- # If CORS_ORIGINS is not set, the module can still be imported but `app` will be None.
237
- # Use create_app(cors_origins=["..."]) directly for programmatic usage.
257
+ # For uvicorn CLI usage, create a launcher module that instantiates the container.
238
258
  app: FastAPI | None = None
239
- if os.environ.get("CORS_ORIGINS") is not None:
240
- app = create_app()
241
259
 
242
260
 
243
261
  __all__ = ["app", "create_app"]
@@ -77,11 +77,9 @@ class ServiceRegistryDiscovery:
77
77
  showing complete errors.
78
78
 
79
79
  Dependency Injection:
80
- This service accepts a ModelONEXContainer for ONEX-style dependency
80
+ This service requires a ModelONEXContainer for ONEX-style dependency
81
81
  injection. Dependencies can also be provided directly via constructor
82
- parameters for testing or when the container is not available.
83
-
84
- Priority: Direct parameters > Container resolution > None (with warnings)
82
+ parameters for testing flexibility.
85
83
 
86
84
  Thread Safety:
87
85
  This service is coroutine-safe. All methods are async and
@@ -89,12 +87,13 @@ class ServiceRegistryDiscovery:
89
87
  concurrency requirements.
90
88
 
91
89
  Example:
92
- >>> # Using container for DI
90
+ >>> # Using container for DI (container is required)
93
91
  >>> service = ServiceRegistryDiscovery(container=container)
94
92
  >>> response = await service.get_discovery()
95
93
  >>>
96
- >>> # Using direct dependencies (for testing)
94
+ >>> # With explicit dependencies (for testing)
97
95
  >>> service = ServiceRegistryDiscovery(
96
+ ... container=container,
98
97
  ... projection_reader=reader,
99
98
  ... consul_handler=handler,
100
99
  ... )
@@ -110,7 +109,7 @@ class ServiceRegistryDiscovery:
110
109
 
111
110
  def __init__(
112
111
  self,
113
- container: ModelONEXContainer | None = None,
112
+ container: ModelONEXContainer,
114
113
  projection_reader: ProjectionReaderRegistration | None = None,
115
114
  consul_handler: HandlerServiceDiscoveryConsul | None = None,
116
115
  widget_mapping_path: Path | None = None,
@@ -118,21 +117,14 @@ class ServiceRegistryDiscovery:
118
117
  """Initialize the registry discovery service.
119
118
 
120
119
  Args:
121
- container: Optional ONEX container for dependency injection.
122
- When provided, dependencies will be resolved from the container
123
- if not explicitly passed via other parameters.
120
+ container: ONEX container for dependency injection. Required for
121
+ ONEX DI pattern compliance.
124
122
  projection_reader: Optional projection reader for node registrations.
125
- If not provided, will attempt to resolve from container.
126
- If still None, node queries will return empty results with warnings.
123
+ If not provided, node queries will return empty results with warnings.
127
124
  consul_handler: Optional Consul handler for live instances.
128
- If not provided, will attempt to resolve from container.
129
- If still None, instance queries will return empty results with warnings.
125
+ If not provided, instance queries will return empty results with warnings.
130
126
  widget_mapping_path: Path to widget mapping YAML file.
131
127
  Defaults to configs/widget_mapping.yaml relative to package.
132
-
133
- Note:
134
- Direct dependency parameters take precedence over container resolution.
135
- This allows easy mocking in tests while supporting full DI in production.
136
128
  """
137
129
  self._container = container
138
130
 
@@ -157,7 +149,6 @@ class ServiceRegistryDiscovery:
157
149
  logger.info(
158
150
  "ServiceRegistryDiscovery initialized",
159
151
  extra={
160
- "has_container": container is not None,
161
152
  "has_projection_reader": self._projection_reader is not None,
162
153
  "has_consul_handler": self._consul_handler is not None,
163
154
  "widget_mapping_path": str(self._widget_mapping_path),
@@ -36,6 +36,7 @@ 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
39
40
  from omnibase_core.models.events.model_event_envelope import ModelEventEnvelope
40
41
  from omnibase_infra.enums import EnumInfraTransportType
41
42
  from omnibase_infra.errors import ModelInfraErrorContext, ProtocolConfigurationError
@@ -217,6 +218,7 @@ class ServiceTimeoutEmitter:
217
218
 
218
219
  Usage:
219
220
  >>> emitter = ServiceTimeoutEmitter(
221
+ ... container=container,
220
222
  ... timeout_query=timeout_scanner,
221
223
  ... event_bus=event_bus,
222
224
  ... projector=projector,
@@ -262,6 +264,7 @@ class ServiceTimeoutEmitter:
262
264
 
263
265
  def __init__(
264
266
  self,
267
+ container: ModelONEXContainer,
265
268
  timeout_query: ServiceTimeoutScanner,
266
269
  event_bus: ProtocolEventBus,
267
270
  projector: ProjectorShell,
@@ -270,6 +273,7 @@ class ServiceTimeoutEmitter:
270
273
  """Initialize with required dependencies.
271
274
 
272
275
  Args:
276
+ container: ONEX container for dependency injection.
273
277
  timeout_query: Scanner for querying overdue entities.
274
278
  Must be initialized with a ProjectionReaderRegistration.
275
279
  event_bus: Event bus for publishing timeout events.
@@ -281,16 +285,18 @@ class ServiceTimeoutEmitter:
281
285
 
282
286
  Example:
283
287
  >>> reader = ProjectionReaderRegistration(pool)
284
- >>> timeout_query = ServiceTimeoutScanner(reader)
288
+ >>> timeout_query = ServiceTimeoutScanner(container, reader)
285
289
  >>> bus = EventBusKafka.default()
286
290
  >>> projector = projector_loader.load("registration_projector")
287
291
  >>> emitter = ServiceTimeoutEmitter(
292
+ ... container=container,
288
293
  ... timeout_query=timeout_query,
289
294
  ... event_bus=bus,
290
295
  ... projector=projector,
291
296
  ... config=ModelTimeoutEmissionConfig(environment="dev"),
292
297
  ... )
293
298
  """
299
+ self._container = container
294
300
  self._timeout_query = timeout_query
295
301
  self._event_bus = event_bus
296
302
  self._projector = projector
@@ -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