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.
- omnibase_infra/__init__.py +1 -1
- omnibase_infra/errors/__init__.py +4 -0
- omnibase_infra/errors/error_infra.py +60 -0
- omnibase_infra/handlers/__init__.py +3 -0
- omnibase_infra/handlers/handler_slack_webhook.py +426 -0
- omnibase_infra/handlers/models/__init__.py +14 -0
- omnibase_infra/handlers/models/enum_alert_severity.py +36 -0
- omnibase_infra/handlers/models/model_slack_alert.py +24 -0
- omnibase_infra/handlers/models/model_slack_alert_payload.py +77 -0
- omnibase_infra/handlers/models/model_slack_alert_result.py +73 -0
- omnibase_infra/mixins/mixin_node_introspection.py +42 -20
- omnibase_infra/models/discovery/model_dependency_spec.py +1 -0
- omnibase_infra/models/discovery/model_discovered_capabilities.py +1 -1
- omnibase_infra/models/discovery/model_introspection_config.py +28 -1
- omnibase_infra/models/discovery/model_introspection_performance_metrics.py +1 -0
- omnibase_infra/models/discovery/model_introspection_task_config.py +1 -0
- omnibase_infra/models/runtime/__init__.py +4 -0
- omnibase_infra/models/runtime/model_resolved_dependencies.py +116 -0
- omnibase_infra/nodes/contract_registry_reducer/contract.yaml +6 -5
- omnibase_infra/nodes/contract_registry_reducer/reducer.py +9 -26
- omnibase_infra/nodes/node_contract_persistence_effect/node.py +18 -1
- omnibase_infra/nodes/node_contract_persistence_effect/registry/registry_infra_contract_persistence_effect.py +33 -2
- omnibase_infra/nodes/node_registration_orchestrator/models/model_postgres_intent_payload.py +8 -12
- omnibase_infra/nodes/node_slack_alerter_effect/__init__.py +33 -0
- omnibase_infra/nodes/node_slack_alerter_effect/contract.yaml +291 -0
- omnibase_infra/nodes/node_slack_alerter_effect/node.py +106 -0
- omnibase_infra/runtime/__init__.py +7 -0
- omnibase_infra/runtime/baseline_subscriptions.py +13 -6
- omnibase_infra/runtime/contract_dependency_resolver.py +455 -0
- omnibase_infra/runtime/contract_registration_event_router.py +5 -5
- omnibase_infra/runtime/emit_daemon/event_registry.py +34 -22
- omnibase_infra/runtime/event_bus_subcontract_wiring.py +63 -23
- omnibase_infra/runtime/publisher_topic_scoped.py +16 -11
- omnibase_infra/runtime/registry_policy.py +29 -15
- omnibase_infra/runtime/request_response_wiring.py +15 -7
- omnibase_infra/runtime/service_runtime_host_process.py +149 -5
- omnibase_infra/runtime/util_version.py +5 -1
- omnibase_infra/schemas/schema_latency_baseline.sql +135 -0
- omnibase_infra/services/contract_publisher/config.py +4 -4
- omnibase_infra/services/contract_publisher/service.py +8 -5
- omnibase_infra/services/observability/injection_effectiveness/__init__.py +67 -0
- omnibase_infra/services/observability/injection_effectiveness/config.py +295 -0
- omnibase_infra/services/observability/injection_effectiveness/consumer.py +1461 -0
- omnibase_infra/services/observability/injection_effectiveness/models/__init__.py +32 -0
- omnibase_infra/services/observability/injection_effectiveness/models/model_agent_match.py +79 -0
- omnibase_infra/services/observability/injection_effectiveness/models/model_context_utilization.py +118 -0
- omnibase_infra/services/observability/injection_effectiveness/models/model_latency_breakdown.py +107 -0
- omnibase_infra/services/observability/injection_effectiveness/models/model_pattern_utilization.py +46 -0
- omnibase_infra/services/observability/injection_effectiveness/writer_postgres.py +596 -0
- omnibase_infra/utils/__init__.py +7 -0
- omnibase_infra/utils/util_db_error_context.py +292 -0
- omnibase_infra/validation/validation_exemptions.yaml +11 -0
- {omnibase_infra-0.3.2.dist-info → omnibase_infra-0.4.0.dist-info}/METADATA +2 -2
- {omnibase_infra-0.3.2.dist-info → omnibase_infra-0.4.0.dist-info}/RECORD +57 -36
- {omnibase_infra-0.3.2.dist-info → omnibase_infra-0.4.0.dist-info}/WHEEL +0 -0
- {omnibase_infra-0.3.2.dist-info → omnibase_infra-0.4.0.dist-info}/entry_points.txt +0 -0
- {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__(
|
|
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(
|
|
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 ``{}``:
|
|
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 ``{}``:
|
|
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
|
-
#
|
|
196
|
-
# This
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
"
|
|
200
|
-
"
|
|
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
|
-
...
|
|
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
|
-
...
|
|
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.
|
|
115
|
-
|
|
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
|