omnibase_infra 0.3.2__py3-none-any.whl → 0.4.0__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 (57) hide show
  1. omnibase_infra/__init__.py +1 -1
  2. omnibase_infra/errors/__init__.py +4 -0
  3. omnibase_infra/errors/error_infra.py +60 -0
  4. omnibase_infra/handlers/__init__.py +3 -0
  5. omnibase_infra/handlers/handler_slack_webhook.py +426 -0
  6. omnibase_infra/handlers/models/__init__.py +14 -0
  7. omnibase_infra/handlers/models/enum_alert_severity.py +36 -0
  8. omnibase_infra/handlers/models/model_slack_alert.py +24 -0
  9. omnibase_infra/handlers/models/model_slack_alert_payload.py +77 -0
  10. omnibase_infra/handlers/models/model_slack_alert_result.py +73 -0
  11. omnibase_infra/mixins/mixin_node_introspection.py +42 -20
  12. omnibase_infra/models/discovery/model_dependency_spec.py +1 -0
  13. omnibase_infra/models/discovery/model_discovered_capabilities.py +1 -1
  14. omnibase_infra/models/discovery/model_introspection_config.py +28 -1
  15. omnibase_infra/models/discovery/model_introspection_performance_metrics.py +1 -0
  16. omnibase_infra/models/discovery/model_introspection_task_config.py +1 -0
  17. omnibase_infra/models/runtime/__init__.py +4 -0
  18. omnibase_infra/models/runtime/model_resolved_dependencies.py +116 -0
  19. omnibase_infra/nodes/contract_registry_reducer/contract.yaml +6 -5
  20. omnibase_infra/nodes/contract_registry_reducer/reducer.py +9 -26
  21. omnibase_infra/nodes/node_contract_persistence_effect/node.py +18 -1
  22. omnibase_infra/nodes/node_contract_persistence_effect/registry/registry_infra_contract_persistence_effect.py +33 -2
  23. omnibase_infra/nodes/node_registration_orchestrator/models/model_postgres_intent_payload.py +8 -12
  24. omnibase_infra/nodes/node_slack_alerter_effect/__init__.py +33 -0
  25. omnibase_infra/nodes/node_slack_alerter_effect/contract.yaml +291 -0
  26. omnibase_infra/nodes/node_slack_alerter_effect/node.py +106 -0
  27. omnibase_infra/runtime/__init__.py +7 -0
  28. omnibase_infra/runtime/baseline_subscriptions.py +13 -6
  29. omnibase_infra/runtime/contract_dependency_resolver.py +455 -0
  30. omnibase_infra/runtime/contract_registration_event_router.py +5 -5
  31. omnibase_infra/runtime/emit_daemon/event_registry.py +34 -22
  32. omnibase_infra/runtime/event_bus_subcontract_wiring.py +63 -23
  33. omnibase_infra/runtime/publisher_topic_scoped.py +16 -11
  34. omnibase_infra/runtime/registry_policy.py +29 -15
  35. omnibase_infra/runtime/request_response_wiring.py +15 -7
  36. omnibase_infra/runtime/service_runtime_host_process.py +149 -5
  37. omnibase_infra/runtime/util_version.py +5 -1
  38. omnibase_infra/schemas/schema_latency_baseline.sql +135 -0
  39. omnibase_infra/services/contract_publisher/config.py +4 -4
  40. omnibase_infra/services/contract_publisher/service.py +8 -5
  41. omnibase_infra/services/observability/injection_effectiveness/__init__.py +67 -0
  42. omnibase_infra/services/observability/injection_effectiveness/config.py +295 -0
  43. omnibase_infra/services/observability/injection_effectiveness/consumer.py +1461 -0
  44. omnibase_infra/services/observability/injection_effectiveness/models/__init__.py +32 -0
  45. omnibase_infra/services/observability/injection_effectiveness/models/model_agent_match.py +79 -0
  46. omnibase_infra/services/observability/injection_effectiveness/models/model_context_utilization.py +118 -0
  47. omnibase_infra/services/observability/injection_effectiveness/models/model_latency_breakdown.py +107 -0
  48. omnibase_infra/services/observability/injection_effectiveness/models/model_pattern_utilization.py +46 -0
  49. omnibase_infra/services/observability/injection_effectiveness/writer_postgres.py +596 -0
  50. omnibase_infra/utils/__init__.py +7 -0
  51. omnibase_infra/utils/util_db_error_context.py +292 -0
  52. omnibase_infra/validation/validation_exemptions.yaml +11 -0
  53. {omnibase_infra-0.3.2.dist-info → omnibase_infra-0.4.0.dist-info}/METADATA +2 -2
  54. {omnibase_infra-0.3.2.dist-info → omnibase_infra-0.4.0.dist-info}/RECORD +57 -36
  55. {omnibase_infra-0.3.2.dist-info → omnibase_infra-0.4.0.dist-info}/WHEEL +0 -0
  56. {omnibase_infra-0.3.2.dist-info → omnibase_infra-0.4.0.dist-info}/entry_points.txt +0 -0
  57. {omnibase_infra-0.3.2.dist-info → omnibase_infra-0.4.0.dist-info}/licenses/LICENSE +0 -0
@@ -56,8 +56,14 @@ from omnibase_core.nodes.node_effect import NodeEffect
56
56
 
57
57
  if TYPE_CHECKING:
58
58
  from omnibase_core.models.container.model_onex_container import ModelONEXContainer
59
+ from omnibase_infra.models.runtime.model_resolved_dependencies import (
60
+ ModelResolvedDependencies,
61
+ )
59
62
 
60
63
 
64
+ # ONEX_EXCLUDE: declarative_node - OMN-1732 DEC-003 requires constructor injection
65
+ # for protocol dependencies. The _resolved_dependencies instance variable stores
66
+ # pre-resolved protocols from ContractDependencyResolver.
61
67
  class NodeContractPersistenceEffect(NodeEffect):
62
68
  """Declarative effect node for contract registry persistence.
63
69
 
@@ -76,6 +82,9 @@ class NodeContractPersistenceEffect(NodeEffect):
76
82
 
77
83
  Args:
78
84
  container: ONEX dependency injection container.
85
+ dependencies: Optional pre-resolved protocol dependencies. If provided,
86
+ the node will use these instead of resolving from container.
87
+ Part of OMN-1732 runtime dependency injection.
79
88
 
80
89
  Dependency Injection:
81
90
  Backend adapters (PostgreSQL) are resolved via container.
@@ -102,13 +111,21 @@ class NodeContractPersistenceEffect(NodeEffect):
102
111
  ```
103
112
  """
104
113
 
105
- def __init__(self, container: ModelONEXContainer) -> None:
114
+ def __init__(
115
+ self,
116
+ container: ModelONEXContainer,
117
+ dependencies: ModelResolvedDependencies | None = None,
118
+ ) -> None:
106
119
  """Initialize effect node with container dependency injection.
107
120
 
108
121
  Args:
109
122
  container: ONEX dependency injection container.
123
+ dependencies: Optional pre-resolved protocol dependencies from
124
+ ContractDependencyResolver. If provided, the node uses these
125
+ instead of resolving from container. Part of OMN-1732.
110
126
  """
111
127
  super().__init__(container)
128
+ self._resolved_dependencies = dependencies
112
129
 
113
130
 
114
131
  __all__ = ["NodeContractPersistenceEffect"]
@@ -24,10 +24,14 @@ Related:
24
24
 
25
25
  from __future__ import annotations
26
26
 
27
+ import warnings
27
28
  from typing import TYPE_CHECKING
28
29
 
29
30
  if TYPE_CHECKING:
30
31
  from omnibase_core.models.container.model_onex_container import ModelONEXContainer
32
+ from omnibase_infra.models.runtime.model_resolved_dependencies import (
33
+ ModelResolvedDependencies,
34
+ )
31
35
  from omnibase_infra.nodes.node_contract_persistence_effect.node import (
32
36
  NodeContractPersistenceEffect,
33
37
  )
@@ -62,7 +66,10 @@ class RegistryInfraContractPersistenceEffect:
62
66
  """
63
67
 
64
68
  @staticmethod
65
- def create(container: ModelONEXContainer) -> NodeContractPersistenceEffect:
69
+ def create(
70
+ container: ModelONEXContainer,
71
+ dependencies: ModelResolvedDependencies | None = None,
72
+ ) -> NodeContractPersistenceEffect:
66
73
  """Create a NodeContractPersistenceEffect instance with resolved dependencies.
67
74
 
68
75
  Factory method that creates a fully configured NodeContractPersistenceEffect
@@ -73,6 +80,10 @@ class RegistryInfraContractPersistenceEffect:
73
80
  following protocols registered:
74
81
  - ProtocolPostgresAdapter: PostgreSQL database operations
75
82
  - ProtocolCircuitBreakerAware: Backend circuit breaker protection
83
+ dependencies: Optional pre-resolved protocol dependencies from
84
+ ContractDependencyResolver. If provided, the node uses these
85
+ instead of resolving from container. Part of OMN-1732 runtime
86
+ dependency injection.
76
87
 
77
88
  Returns:
78
89
  Configured NodeContractPersistenceEffect instance ready for operation.
@@ -84,14 +95,22 @@ class RegistryInfraContractPersistenceEffect:
84
95
  >>> container = ModelONEXContainer()
85
96
  >>> container.register(ProtocolPostgresAdapter, postgres_adapter)
86
97
  >>> effect = RegistryInfraContractPersistenceEffect.create(container)
98
+ >>>
99
+ >>> # With pre-resolved dependencies (OMN-1732)
100
+ >>> resolved = resolver.resolve(contract)
101
+ >>> effect = RegistryInfraContractPersistenceEffect.create(
102
+ ... container, dependencies=resolved
103
+ ... )
87
104
 
88
105
  .. versionadded:: 0.5.0
106
+ .. versionchanged:: 0.6.0
107
+ Added optional ``dependencies`` parameter for constructor injection (OMN-1732).
89
108
  """
90
109
  from omnibase_infra.nodes.node_contract_persistence_effect.node import (
91
110
  NodeContractPersistenceEffect,
92
111
  )
93
112
 
94
- return NodeContractPersistenceEffect(container)
113
+ return NodeContractPersistenceEffect(container, dependencies=dependencies)
95
114
 
96
115
  @staticmethod
97
116
  def get_required_protocols() -> list[str]:
@@ -100,6 +119,11 @@ class RegistryInfraContractPersistenceEffect:
100
119
  Returns the protocol class names that must be registered in the
101
120
  container before creating a NodeContractPersistenceEffect instance.
102
121
 
122
+ .. deprecated:: 0.6.0
123
+ Use contract.yaml dependencies field instead. This method will be
124
+ removed in a future version. The contract is now the single source
125
+ of truth for protocol requirements (OMN-1732).
126
+
103
127
  Returns:
104
128
  List of protocol class names required for node operation.
105
129
 
@@ -111,6 +135,13 @@ class RegistryInfraContractPersistenceEffect:
111
135
 
112
136
  .. versionadded:: 0.5.0
113
137
  """
138
+ warnings.warn(
139
+ "get_required_protocols() is deprecated. Use contract.yaml dependencies "
140
+ "field instead. The contract is the single source of truth for protocol "
141
+ "requirements (OMN-1732).",
142
+ DeprecationWarning,
143
+ stacklevel=2,
144
+ )
114
145
  return [
115
146
  "ProtocolPostgresAdapter",
116
147
  "ProtocolCircuitBreakerAware",
@@ -26,7 +26,7 @@ Thread Safety:
26
26
  Edge Case Behavior:
27
27
  The ``endpoints`` field validator explicitly handles the following cases:
28
28
  - ``None``: Raises ValueError (invalid input, not silently ignored)
29
- - Empty Mapping ``{}``: Logs warning and converts to empty tuple
29
+ - Empty Mapping ``{}``: Raises ValueError (use default=() for no endpoints)
30
30
  - Invalid types (int, str, list, etc.): Raises ValueError
31
31
  - Tuple: Passed through as-is
32
32
  - Non-empty Mapping: Converted to tuple of (key, value) pairs
@@ -36,7 +36,6 @@ Edge Case Behavior:
36
36
  from __future__ import annotations
37
37
 
38
38
  import logging
39
- import warnings
40
39
  from collections.abc import Mapping
41
40
  from types import MappingProxyType
42
41
  from uuid import UUID
@@ -153,7 +152,7 @@ class ModelPostgresIntentPayload(BaseModel):
153
152
 
154
153
  Edge Cases:
155
154
  - ``None``: Raises ValueError (explicit rejection)
156
- - Empty Mapping ``{}``: Logs warning, returns empty tuple
155
+ - Empty Mapping ``{}``: Raises ValueError (use default=() for no endpoints)
157
156
  - Empty tuple ``()``: Passed through (same as default)
158
157
  - Invalid types (list, int, str): Raises ValueError
159
158
  - Non-string keys/values: Raises ValueError (strict mode)
@@ -192,16 +191,13 @@ class ModelPostgresIntentPayload(BaseModel):
192
191
  return v # type: ignore[return-value] # NOTE: runtime type validated by Pydantic
193
192
  if isinstance(v, Mapping):
194
193
  if len(v) == 0:
195
- # Log warning for empty Mapping to help detect potentially missing data.
196
- # This is different from the default empty tuple - it's an explicit
197
- # empty Mapping input that gets coerced.
198
- warning_msg = (
199
- "Empty Mapping provided for endpoints, coercing to empty tuple. "
200
- "If this is intentional, consider using default=() instead."
194
+ # Explicit empty Mapping is rejected - use default=() instead.
195
+ # This prevents silent coercion that could mask invalid input.
196
+ raise ValueError(
197
+ "Empty Mapping provided for endpoints. "
198
+ "If no endpoints are needed, omit the field to use default=() "
199
+ "rather than passing an explicit empty Mapping."
201
200
  )
202
- logger.warning(warning_msg)
203
- warnings.warn(warning_msg, UserWarning, stacklevel=2)
204
- return ()
205
201
  # Validate and convert to tuple - strict mode requires string keys/values
206
202
  result: list[tuple[str, str]] = []
207
203
  for key, val in v.items():
@@ -0,0 +1,33 @@
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright (c) 2025 OmniNode Team
3
+ """Slack Alerter Effect Node - Declarative Slack webhook alerting.
4
+
5
+ This module exports the declarative NodeSlackAlerterEffect for sending
6
+ infrastructure alerts to Slack via webhooks.
7
+
8
+ Architecture:
9
+ This node follows the ONEX declarative pattern:
10
+ - DECLARATIVE effect driven by contract.yaml
11
+ - Zero custom routing logic - all behavior from handler_routing
12
+ - Lightweight shell that delegates to HandlerSlackWebhook
13
+ - Pattern: "Contract-driven, handlers wired externally"
14
+
15
+ Example:
16
+ >>> from omnibase_core.models.container import ModelONEXContainer
17
+ >>> from omnibase_infra.nodes.node_slack_alerter_effect import NodeSlackAlerterEffect
18
+ >>> from omnibase_infra.handlers import HandlerSlackWebhook
19
+ >>>
20
+ >>> container = ModelONEXContainer()
21
+ >>> node = NodeSlackAlerterEffect(container)
22
+ >>>
23
+ >>> # Handler receives dependencies via constructor
24
+ >>> handler = HandlerSlackWebhook()
25
+ >>> # result = await handler.handle(alert)
26
+
27
+ Related Tickets:
28
+ - OMN-1905: Add declarative Slack webhook handler to omnibase_infra
29
+ """
30
+
31
+ from omnibase_infra.nodes.node_slack_alerter_effect.node import NodeSlackAlerterEffect
32
+
33
+ __all__ = ["NodeSlackAlerterEffect"]
@@ -0,0 +1,291 @@
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright (c) 2025 OmniNode Team
3
+ #
4
+ # ONEX Node Contract
5
+ # Node: NodeSlackAlerterEffect
6
+ #
7
+ # This contract defines the interface for the Slack Alerter Effect node,
8
+ # which sends infrastructure alerts to Slack via webhooks. The node uses
9
+ # declarative operation routing to dispatch operations to the handler.
10
+ #
11
+ # Related Tickets:
12
+ # - OMN-1905: Add declarative Slack webhook handler to omnibase_infra
13
+ # Contract identifiers
14
+ name: "node_slack_alerter_effect"
15
+ contract_name: "node_slack_alerter_effect"
16
+ node_name: "node_slack_alerter_effect"
17
+ contract_version:
18
+ major: 1
19
+ minor: 0
20
+ patch: 0
21
+ node_version:
22
+ major: 1
23
+ minor: 0
24
+ patch: 0
25
+ # Node type
26
+ node_type: "EFFECT_GENERIC"
27
+ # Description
28
+ description: >
29
+ Effect node for Slack webhook alerting. Sends infrastructure alerts to Slack channels using incoming webhooks with Block Kit formatting, retry with exponential backoff, and rate limit handling. Uses declarative operation routing to dispatch to the HandlerSlackWebhook.
30
+
31
+ # Strongly typed I/O models
32
+ input_model:
33
+ name: "ModelSlackAlert"
34
+ module: "omnibase_infra.handlers.models.model_slack_alert"
35
+ description: "Input model containing alert severity, message, and optional details."
36
+ output_model:
37
+ name: "ModelSlackAlertResult"
38
+ module: "omnibase_infra.handlers.models.model_slack_alert"
39
+ description: "Output model containing delivery status, timing, and error information."
40
+ # =============================================================================
41
+ # HANDLER ROUTING (Declarative Handler Dispatch)
42
+ # =============================================================================
43
+ # This section defines how operations are routed to the Slack webhook handler.
44
+ # The routing strategy determines handler selection based on operation type.
45
+ #
46
+ # Design Rationale:
47
+ # - Single handler for all Slack operations
48
+ # - Block Kit formatting for rich messages
49
+ # - Retry with exponential backoff for resilience
50
+ # - Rate limit handling for graceful degradation
51
+ #
52
+ # Execution Flow:
53
+ # 1. Receive ModelSlackAlert with operation type
54
+ # 2. Route to HandlerSlackWebhook based on operation
55
+ # 3. Execute webhook delivery with retry logic
56
+ # 4. Return ModelSlackAlertResult with delivery status
57
+ # =============================================================================
58
+ handler_routing:
59
+ routing_strategy: "operation_match"
60
+ handlers:
61
+ # Send Alert - Formatted Block Kit message
62
+ # Sends a rich formatted alert with severity, message, and details.
63
+ - operation: "send_alert"
64
+ handler:
65
+ name: "HandlerSlackWebhook"
66
+ module: "omnibase_infra.handlers.handler_slack_webhook"
67
+ description: "Send formatted alert to Slack with Block Kit formatting"
68
+ output_fields:
69
+ - success
70
+ - duration_ms
71
+ - retry_count
72
+ # Send Message - Plain text message
73
+ # Sends a simple text message (uses same handler, alert formatting applied)
74
+ - operation: "send_message"
75
+ handler:
76
+ name: "HandlerSlackWebhook"
77
+ module: "omnibase_infra.handlers.handler_slack_webhook"
78
+ description: "Send message to Slack channel"
79
+ output_fields:
80
+ - success
81
+ - duration_ms
82
+ - retry_count
83
+ # =============================================================================
84
+ # ERROR HANDLING (Retry + Rate Limiting)
85
+ # =============================================================================
86
+ # Error handling configuration for Slack webhook operations.
87
+ # Uses retry with exponential backoff and handles rate limiting gracefully.
88
+ #
89
+ # Note: The handler implements its own retry logic internally rather than
90
+ # relying on external retry orchestration. This ensures consistent behavior
91
+ # and proper rate limit handling.
92
+ # =============================================================================
93
+ error_handling:
94
+ # Retry policy with exponential backoff
95
+ retry_policy:
96
+ max_retries: 3
97
+ initial_delay_ms: 1000
98
+ max_delay_ms: 4000
99
+ exponential_base: 2
100
+ retry_on:
101
+ - "SLACK_RATE_LIMITED"
102
+ - "SLACK_TIMEOUT"
103
+ - "SLACK_CONNECTION_ERROR"
104
+ - "SLACK_CLIENT_ERROR"
105
+ # Error type definitions
106
+ error_types:
107
+ - name: "SLACK_NOT_CONFIGURED"
108
+ description: "SLACK_WEBHOOK_URL environment variable not set"
109
+ recoverable: false
110
+ retry_strategy: "none"
111
+ - name: "SLACK_RATE_LIMITED"
112
+ description: "Slack returned HTTP 429 rate limit"
113
+ recoverable: true
114
+ retry_strategy: "exponential_backoff"
115
+ - name: "SLACK_TIMEOUT"
116
+ description: "Webhook request timed out"
117
+ recoverable: true
118
+ retry_strategy: "exponential_backoff"
119
+ - name: "SLACK_CONNECTION_ERROR"
120
+ description: "Failed to connect to Slack webhook"
121
+ recoverable: true
122
+ retry_strategy: "exponential_backoff"
123
+ - name: "SLACK_CLIENT_ERROR"
124
+ description: "HTTP client error during request"
125
+ recoverable: true
126
+ retry_strategy: "exponential_backoff"
127
+ - name: "SLACK_HTTP_4XX"
128
+ description: "Slack returned HTTP 4xx error (non-429)"
129
+ recoverable: false
130
+ retry_strategy: "none"
131
+ - name: "SLACK_HTTP_5XX"
132
+ description: "Slack returned HTTP 5xx error"
133
+ recoverable: true
134
+ retry_strategy: "exponential_backoff"
135
+ # IO operations (EFFECT node specific)
136
+ io_operations:
137
+ - operation: "send_alert"
138
+ description: "Send a formatted alert to Slack with Block Kit formatting"
139
+ input_fields:
140
+ - severity
141
+ - message
142
+ - title
143
+ - details
144
+ - channel
145
+ - correlation_id
146
+ output_fields:
147
+ - success
148
+ - duration_ms
149
+ - correlation_id
150
+ - error
151
+ - error_code
152
+ - retry_count
153
+ - operation: "send_message"
154
+ description: "Send a plain text message to Slack"
155
+ input_fields:
156
+ - message
157
+ - channel
158
+ - correlation_id
159
+ output_fields:
160
+ - success
161
+ - duration_ms
162
+ - correlation_id
163
+ - error
164
+ - error_code
165
+ - retry_count
166
+ # Dependencies (protocols this node requires)
167
+ dependencies:
168
+ - name: "http_client"
169
+ type: "library"
170
+ library: "aiohttp"
171
+ description: "Async HTTP client for webhook requests"
172
+ - name: "slack_webhook_url"
173
+ type: "environment"
174
+ env_var: "SLACK_WEBHOOK_URL"
175
+ description: "Slack incoming webhook URL"
176
+ required: true
177
+ # Capabilities provided by this node
178
+ capabilities:
179
+ - name: "slack_alerting"
180
+ description: "Send infrastructure alerts to Slack channels"
181
+ - name: "block_kit_formatting"
182
+ description: "Format messages using Slack Block Kit for rich display"
183
+ - name: "retry_with_backoff"
184
+ description: "Retry failed requests with exponential backoff"
185
+ - name: "rate_limit_handling"
186
+ description: "Handle Slack rate limiting gracefully"
187
+ # Model definitions
188
+ definitions:
189
+ ModelSlackAlert:
190
+ type: object
191
+ description: "Input payload for Slack alert operations"
192
+ frozen: true
193
+ extra: "forbid"
194
+ properties:
195
+ severity:
196
+ type: enum
197
+ enum_class: "EnumAlertSeverity"
198
+ module: "omnibase_infra.handlers.models.model_slack_alert"
199
+ description: "Alert severity level for visual formatting"
200
+ default: "info"
201
+ enum:
202
+ - "critical"
203
+ - "error"
204
+ - "warning"
205
+ - "info"
206
+ message:
207
+ type: string
208
+ description: "Main alert message content"
209
+ min_length: 1
210
+ max_length: 3000
211
+ title:
212
+ type: string
213
+ nullable: true
214
+ default: null
215
+ max_length: 150
216
+ description: "Optional alert title"
217
+ details:
218
+ type: object
219
+ key_type: string
220
+ value_type: any
221
+ description: "Additional key-value details"
222
+ default_factory: "dict"
223
+ channel:
224
+ type: string
225
+ nullable: true
226
+ default: null
227
+ description: "Optional channel override"
228
+ correlation_id:
229
+ type: uuid
230
+ description: "UUID for distributed tracing"
231
+ default_factory: "uuid4"
232
+ required:
233
+ - message
234
+ ModelSlackAlertResult:
235
+ type: object
236
+ description: "Response from Slack webhook operations"
237
+ frozen: true
238
+ extra: "forbid"
239
+ properties:
240
+ success:
241
+ type: boolean
242
+ description: "Whether the alert was delivered successfully"
243
+ duration_ms:
244
+ type: float
245
+ description: "Time taken for the operation in milliseconds"
246
+ default: 0.0
247
+ ge: 0.0
248
+ correlation_id:
249
+ type: uuid
250
+ description: "UUID from the original request"
251
+ error:
252
+ type: string
253
+ nullable: true
254
+ default: null
255
+ description: "Sanitized error message if success is False"
256
+ error_code:
257
+ type: string
258
+ nullable: true
259
+ default: null
260
+ description: "Error code for programmatic handling"
261
+ retry_count:
262
+ type: integer
263
+ description: "Number of retry attempts made"
264
+ default: 0
265
+ ge: 0
266
+ required:
267
+ - success
268
+ - correlation_id
269
+ # Health check configuration
270
+ health_check:
271
+ enabled: true
272
+ endpoint: "/health"
273
+ interval_seconds: 30
274
+ checks:
275
+ - name: "webhook_configured"
276
+ check_type: "environment"
277
+ env_var: "SLACK_WEBHOOK_URL"
278
+ description: "Verify SLACK_WEBHOOK_URL is configured"
279
+ # Metadata
280
+ metadata:
281
+ author: "OmniNode Team"
282
+ license: "MIT"
283
+ created: "2026-02-04"
284
+ updated: "2026-02-04"
285
+ tags:
286
+ - effect
287
+ - slack
288
+ - alerting
289
+ - webhook
290
+ - infrastructure
291
+ - block-kit
@@ -0,0 +1,106 @@
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright (c) 2025 OmniNode Team
3
+ """Node Slack Alerter Effect - Declarative effect node for Slack alerting.
4
+
5
+ This node follows the ONEX declarative pattern:
6
+ - DECLARATIVE effect driven by contract.yaml
7
+ - Zero custom routing logic - all behavior from handler_routing
8
+ - Lightweight shell that delegates to handlers via container resolution
9
+ - Used for ONEX-compliant runtime execution via RuntimeHostProcess
10
+ - Pattern: "Contract-driven, handlers wired externally"
11
+
12
+ Extends NodeEffect from omnibase_core for infrastructure I/O operations.
13
+ All handler routing is 100% driven by contract.yaml, not Python code.
14
+
15
+ Handler Routing Pattern:
16
+ 1. Receive alert request (input_model in contract)
17
+ 2. Route to appropriate handler based on operation (handler_routing)
18
+ 3. Execute infrastructure I/O via handler (Slack webhook)
19
+ 4. Return structured response (output_model in contract)
20
+
21
+ Design Decisions:
22
+ - 100% Contract-Driven: All routing logic in YAML, not Python
23
+ - Zero Custom Routing: Base class handles handler dispatch via contract
24
+ - Declarative Handlers: handler_routing section defines dispatch rules
25
+ - Container DI: Handler dependencies resolved via container
26
+
27
+ Node Responsibilities:
28
+ - Define I/O model contract (ModelSlackAlert -> ModelSlackAlertResult)
29
+ - Delegate all execution to handlers via base class
30
+ - NO custom logic - pure declarative shell
31
+
32
+ The actual handler execution and routing is performed by:
33
+ - Direct handler invocation by callers
34
+ - Or orchestrator layer for workflow coordination
35
+
36
+ Handlers receive their dependencies directly via constructor injection:
37
+ - HandlerSlackWebhook(webhook_url, http_session)
38
+
39
+ Coroutine Safety:
40
+ This node is async-safe. Handler coordination is performed by the
41
+ caller or orchestrator layer, not by this effect node.
42
+
43
+ Related Modules:
44
+ - contract.yaml: Handler routing and I/O model definitions
45
+ - ../../handlers/handler_slack_webhook.py: Webhook handler implementation
46
+ - ../../handlers/models/model_slack_alert.py: Alert payload models
47
+
48
+ Related Tickets:
49
+ - OMN-1905: Add declarative Slack webhook handler to omnibase_infra
50
+ """
51
+
52
+ from __future__ import annotations
53
+
54
+ from omnibase_core.nodes.node_effect import NodeEffect
55
+
56
+
57
+ class NodeSlackAlerterEffect(NodeEffect):
58
+ """Declarative effect node for Slack webhook alerting.
59
+
60
+ This effect node is a lightweight shell that defines the I/O contract
61
+ for Slack alert operations. All routing and execution logic is driven
62
+ by contract.yaml - this class contains NO custom routing code.
63
+
64
+ Supported Operations (defined in contract.yaml handler_routing):
65
+ - send_alert: Send a formatted alert to Slack
66
+ - send_message: Send a plain text message to Slack
67
+
68
+ Dependency Injection:
69
+ The HandlerSlackWebhook is instantiated by callers with its
70
+ dependencies (webhook_url from env, optional http_session).
71
+ This node contains NO instance variables for the handler.
72
+
73
+ Example:
74
+ ```python
75
+ from omnibase_core.models.container import ModelONEXContainer
76
+ from omnibase_infra.nodes.node_slack_alerter_effect import NodeSlackAlerterEffect
77
+ from omnibase_infra.handlers import HandlerSlackWebhook
78
+ from omnibase_infra.handlers.models import ModelSlackAlert, EnumAlertSeverity
79
+
80
+ # Create effect node via container
81
+ container = ModelONEXContainer()
82
+ effect = NodeSlackAlerterEffect(container)
83
+
84
+ # Handler receives dependencies directly via constructor
85
+ handler = HandlerSlackWebhook()
86
+
87
+ # Create and send alert
88
+ alert = ModelSlackAlert(
89
+ severity=EnumAlertSeverity.ERROR,
90
+ message="Circuit breaker opened",
91
+ title="Infrastructure Alert",
92
+ details={"service": "consul", "threshold": "5"},
93
+ )
94
+ result = await handler.handle(alert)
95
+
96
+ if result.success:
97
+ print(f"Alert delivered in {result.duration_ms}ms")
98
+ else:
99
+ print(f"Alert failed: {result.error}")
100
+ ```
101
+ """
102
+
103
+ # Pure declarative shell - all behavior defined in contract.yaml
104
+
105
+
106
+ __all__ = ["NodeSlackAlerterEffect"]
@@ -282,6 +282,11 @@ from omnibase_infra.runtime.baseline_subscriptions import (
282
282
  get_baseline_topics,
283
283
  )
284
284
 
285
+ # Contract dependency resolver (OMN-1732)
286
+ from omnibase_infra.runtime.contract_dependency_resolver import (
287
+ ContractDependencyResolver,
288
+ )
289
+
285
290
  # Chain-aware dispatch (OMN-951) - must be imported LAST to avoid circular import
286
291
  from omnibase_infra.runtime.chain_aware_dispatch import (
287
292
  ChainAwareDispatcher,
@@ -458,4 +463,6 @@ __all__: list[str] = [
458
463
  "BASELINE_CONTRACT_TOPICS",
459
464
  "BASELINE_PLATFORM_TOPICS",
460
465
  "get_baseline_topics",
466
+ # Contract dependency resolver (OMN-1732)
467
+ "ContractDependencyResolver",
461
468
  ]
@@ -72,10 +72,13 @@ This subset excludes heartbeat topics and is appropriate when:
72
72
  - Heartbeat processing is handled separately
73
73
  - You want to minimize subscription overhead
74
74
 
75
+ Note:
76
+ Topics are realm-agnostic in ONEX. The environment/realm is enforced via
77
+ envelope identity, not topic naming. Subscribe directly to the topic suffix.
78
+
75
79
  Example:
76
80
  >>> for topic_suffix in BASELINE_CONTRACT_TOPICS:
77
- ... full_topic = f"{environment}.{topic_suffix}"
78
- ... subscribe(full_topic)
81
+ ... subscribe(topic_suffix) # No environment prefix needed
79
82
  """
80
83
 
81
84
  # All platform baseline topics including heartbeat.
@@ -91,10 +94,13 @@ Includes:
91
94
  - Contract deregistered events
92
95
  - Node heartbeat events
93
96
 
97
+ Note:
98
+ Topics are realm-agnostic in ONEX. The environment/realm is enforced via
99
+ envelope identity, not topic naming. Subscribe directly to the topic suffix.
100
+
94
101
  Example:
95
102
  >>> for topic_suffix in BASELINE_PLATFORM_TOPICS:
96
- ... full_topic = f"{environment}.{topic_suffix}"
97
- ... subscribe(full_topic)
103
+ ... subscribe(topic_suffix) # No environment prefix needed
98
104
  """
99
105
 
100
106
 
@@ -111,8 +117,9 @@ def get_baseline_topics(*, include_heartbeat: bool = True) -> frozenset[str]:
111
117
  registration/deregistration topics.
112
118
 
113
119
  Returns:
114
- A frozenset of topic suffix strings. These are suffixes that should
115
- be prefixed with the environment name to form complete topic names.
120
+ A frozenset of topic suffix strings. Topics are realm-agnostic in ONEX;
121
+ subscribe directly to these suffixes without environment prefix.
122
+ The environment/realm is enforced via envelope identity, not topic naming.
116
123
 
117
124
  Example:
118
125
  >>> # For full platform observability