omnibase_infra 0.3.1__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/enums/__init__.py +3 -0
- omnibase_infra/enums/enum_consumer_group_purpose.py +9 -0
- omnibase_infra/enums/enum_postgres_error_code.py +188 -0
- 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/handlers/registration_storage/handler_registration_storage_postgres.py +29 -20
- omnibase_infra/mixins/__init__.py +14 -0
- omnibase_infra/mixins/mixin_node_introspection.py +42 -20
- omnibase_infra/mixins/mixin_postgres_error_response.py +314 -0
- omnibase_infra/mixins/mixin_postgres_op_executor.py +298 -0
- omnibase_infra/models/__init__.py +3 -0
- 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/{nodes/effects/models → models}/model_backend_result.py +22 -6
- omnibase_infra/models/projection/__init__.py +11 -0
- omnibase_infra/models/projection/model_contract_projection.py +170 -0
- omnibase_infra/models/projection/model_topic_projection.py +148 -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/__init__.py +5 -0
- omnibase_infra/nodes/contract_registry_reducer/contract.yaml +6 -5
- omnibase_infra/nodes/contract_registry_reducer/contract_registration_event_router.py +689 -0
- omnibase_infra/nodes/contract_registry_reducer/reducer.py +9 -26
- omnibase_infra/nodes/effects/__init__.py +1 -1
- omnibase_infra/nodes/effects/models/__init__.py +6 -4
- omnibase_infra/nodes/effects/models/model_registry_response.py +1 -1
- omnibase_infra/nodes/effects/protocol_consul_client.py +1 -1
- omnibase_infra/nodes/effects/protocol_postgres_adapter.py +1 -1
- omnibase_infra/nodes/effects/registry_effect.py +1 -1
- omnibase_infra/nodes/node_contract_persistence_effect/__init__.py +101 -0
- omnibase_infra/nodes/node_contract_persistence_effect/contract.yaml +490 -0
- omnibase_infra/nodes/node_contract_persistence_effect/handlers/__init__.py +74 -0
- omnibase_infra/nodes/node_contract_persistence_effect/handlers/handler_postgres_cleanup_topics.py +217 -0
- omnibase_infra/nodes/node_contract_persistence_effect/handlers/handler_postgres_contract_upsert.py +242 -0
- omnibase_infra/nodes/node_contract_persistence_effect/handlers/handler_postgres_deactivate.py +194 -0
- omnibase_infra/nodes/node_contract_persistence_effect/handlers/handler_postgres_heartbeat.py +243 -0
- omnibase_infra/nodes/node_contract_persistence_effect/handlers/handler_postgres_mark_stale.py +208 -0
- omnibase_infra/nodes/node_contract_persistence_effect/handlers/handler_postgres_topic_update.py +298 -0
- omnibase_infra/nodes/node_contract_persistence_effect/models/__init__.py +15 -0
- omnibase_infra/nodes/node_contract_persistence_effect/models/model_persistence_result.py +52 -0
- omnibase_infra/nodes/node_contract_persistence_effect/node.py +131 -0
- omnibase_infra/nodes/node_contract_persistence_effect/registry/__init__.py +27 -0
- omnibase_infra/nodes/node_contract_persistence_effect/registry/registry_infra_contract_persistence_effect.py +251 -0
- omnibase_infra/nodes/node_registration_orchestrator/models/model_postgres_intent_payload.py +8 -12
- omnibase_infra/nodes/node_registry_effect/models/__init__.py +2 -2
- 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/projectors/__init__.py +6 -0
- omnibase_infra/projectors/projection_reader_contract.py +1301 -0
- omnibase_infra/runtime/__init__.py +12 -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 +500 -0
- omnibase_infra/runtime/db/__init__.py +4 -0
- omnibase_infra/runtime/db/models/__init__.py +15 -10
- omnibase_infra/runtime/db/models/model_db_operation.py +40 -0
- omnibase_infra/runtime/db/models/model_db_param.py +24 -0
- omnibase_infra/runtime/db/models/model_db_repository_contract.py +40 -0
- omnibase_infra/runtime/db/models/model_db_return.py +26 -0
- omnibase_infra/runtime/db/models/model_db_safety_policy.py +32 -0
- omnibase_infra/runtime/emit_daemon/event_registry.py +34 -22
- omnibase_infra/runtime/event_bus_subcontract_wiring.py +63 -23
- omnibase_infra/runtime/intent_execution_router.py +430 -0
- omnibase_infra/runtime/models/__init__.py +6 -0
- omnibase_infra/runtime/models/model_contract_registry_config.py +41 -0
- omnibase_infra/runtime/models/model_intent_execution_summary.py +79 -0
- omnibase_infra/runtime/models/model_runtime_config.py +8 -0
- omnibase_infra/runtime/protocols/__init__.py +16 -0
- omnibase_infra/runtime/protocols/protocol_intent_executor.py +107 -0
- omnibase_infra/runtime/publisher_topic_scoped.py +16 -11
- omnibase_infra/runtime/registry_policy.py +29 -15
- omnibase_infra/runtime/request_response_wiring.py +793 -0
- omnibase_infra/runtime/service_kernel.py +295 -8
- 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/services/registry_api/models/__init__.py +25 -0
- omnibase_infra/services/registry_api/models/model_contract_ref.py +44 -0
- omnibase_infra/services/registry_api/models/model_contract_view.py +81 -0
- omnibase_infra/services/registry_api/models/model_response_contracts.py +50 -0
- omnibase_infra/services/registry_api/models/model_response_topics.py +50 -0
- omnibase_infra/services/registry_api/models/model_topic_summary.py +57 -0
- omnibase_infra/services/registry_api/models/model_topic_view.py +63 -0
- omnibase_infra/services/registry_api/routes.py +205 -6
- omnibase_infra/services/registry_api/service.py +528 -1
- omnibase_infra/utils/__init__.py +7 -0
- omnibase_infra/utils/util_db_error_context.py +292 -0
- omnibase_infra/validation/infra_validators.py +3 -1
- omnibase_infra/validation/validation_exemptions.yaml +65 -0
- {omnibase_infra-0.3.1.dist-info → omnibase_infra-0.4.0.dist-info}/METADATA +3 -3
- {omnibase_infra-0.3.1.dist-info → omnibase_infra-0.4.0.dist-info}/RECORD +117 -58
- {omnibase_infra-0.3.1.dist-info → omnibase_infra-0.4.0.dist-info}/WHEEL +0 -0
- {omnibase_infra-0.3.1.dist-info → omnibase_infra-0.4.0.dist-info}/entry_points.txt +0 -0
- {omnibase_infra-0.3.1.dist-info → omnibase_infra-0.4.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
# Copyright (c) 2025 OmniNode Team
|
|
3
|
+
"""Registry for NodeContractPersistenceEffect infrastructure dependencies.
|
|
4
|
+
|
|
5
|
+
This registry provides factory methods for creating NodeContractPersistenceEffect
|
|
6
|
+
instances with their required dependencies resolved from the container.
|
|
7
|
+
|
|
8
|
+
Following ONEX naming conventions:
|
|
9
|
+
- File: registry_infra_<node_name>.py
|
|
10
|
+
- Class: RegistryInfra<NodeName>
|
|
11
|
+
|
|
12
|
+
The registry serves as the entry point for creating properly configured
|
|
13
|
+
effect node instances, documenting required protocols, and providing
|
|
14
|
+
node metadata for introspection.
|
|
15
|
+
|
|
16
|
+
Related:
|
|
17
|
+
- contract.yaml: Node contract defining operations and dependencies
|
|
18
|
+
- node.py: Declarative node implementation
|
|
19
|
+
- handlers/: PostgreSQL operation handlers
|
|
20
|
+
- OMN-1845: Implementation ticket
|
|
21
|
+
|
|
22
|
+
.. versionadded:: 0.5.0
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
from __future__ import annotations
|
|
26
|
+
|
|
27
|
+
import warnings
|
|
28
|
+
from typing import TYPE_CHECKING
|
|
29
|
+
|
|
30
|
+
if TYPE_CHECKING:
|
|
31
|
+
from omnibase_core.models.container.model_onex_container import ModelONEXContainer
|
|
32
|
+
from omnibase_infra.models.runtime.model_resolved_dependencies import (
|
|
33
|
+
ModelResolvedDependencies,
|
|
34
|
+
)
|
|
35
|
+
from omnibase_infra.nodes.node_contract_persistence_effect.node import (
|
|
36
|
+
NodeContractPersistenceEffect,
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class RegistryInfraContractPersistenceEffect:
|
|
41
|
+
"""Infrastructure registry for NodeContractPersistenceEffect.
|
|
42
|
+
|
|
43
|
+
Provides dependency resolution and factory methods for creating
|
|
44
|
+
properly configured NodeContractPersistenceEffect instances.
|
|
45
|
+
|
|
46
|
+
This registry follows the ONEX infrastructure registry pattern:
|
|
47
|
+
- Factory method for node creation with container injection
|
|
48
|
+
- Protocol requirements documentation for container validation
|
|
49
|
+
- Node type classification for routing decisions
|
|
50
|
+
- Capability listing for service discovery
|
|
51
|
+
|
|
52
|
+
Example:
|
|
53
|
+
>>> from omnibase_core.models.container import ModelONEXContainer
|
|
54
|
+
>>> from omnibase_infra.nodes.node_contract_persistence_effect.registry import (
|
|
55
|
+
... RegistryInfraContractPersistenceEffect,
|
|
56
|
+
... )
|
|
57
|
+
>>>
|
|
58
|
+
>>> # Create container with required protocols registered
|
|
59
|
+
>>> container = ModelONEXContainer()
|
|
60
|
+
>>> # ... register protocols ...
|
|
61
|
+
>>>
|
|
62
|
+
>>> # Create node instance via registry
|
|
63
|
+
>>> effect = RegistryInfraContractPersistenceEffect.create(container)
|
|
64
|
+
|
|
65
|
+
.. versionadded:: 0.5.0
|
|
66
|
+
"""
|
|
67
|
+
|
|
68
|
+
@staticmethod
|
|
69
|
+
def create(
|
|
70
|
+
container: ModelONEXContainer,
|
|
71
|
+
dependencies: ModelResolvedDependencies | None = None,
|
|
72
|
+
) -> NodeContractPersistenceEffect:
|
|
73
|
+
"""Create a NodeContractPersistenceEffect instance with resolved dependencies.
|
|
74
|
+
|
|
75
|
+
Factory method that creates a fully configured NodeContractPersistenceEffect
|
|
76
|
+
using the provided ONEX container for dependency injection.
|
|
77
|
+
|
|
78
|
+
Args:
|
|
79
|
+
container: ONEX dependency injection container. Must have the
|
|
80
|
+
following protocols registered:
|
|
81
|
+
- ProtocolPostgresAdapter: PostgreSQL database operations
|
|
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.
|
|
87
|
+
|
|
88
|
+
Returns:
|
|
89
|
+
Configured NodeContractPersistenceEffect instance ready for operation.
|
|
90
|
+
|
|
91
|
+
Raises:
|
|
92
|
+
OnexError: If required protocols are not registered in container.
|
|
93
|
+
|
|
94
|
+
Example:
|
|
95
|
+
>>> container = ModelONEXContainer()
|
|
96
|
+
>>> container.register(ProtocolPostgresAdapter, postgres_adapter)
|
|
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
|
+
... )
|
|
104
|
+
|
|
105
|
+
.. versionadded:: 0.5.0
|
|
106
|
+
.. versionchanged:: 0.6.0
|
|
107
|
+
Added optional ``dependencies`` parameter for constructor injection (OMN-1732).
|
|
108
|
+
"""
|
|
109
|
+
from omnibase_infra.nodes.node_contract_persistence_effect.node import (
|
|
110
|
+
NodeContractPersistenceEffect,
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
return NodeContractPersistenceEffect(container, dependencies=dependencies)
|
|
114
|
+
|
|
115
|
+
@staticmethod
|
|
116
|
+
def get_required_protocols() -> list[str]:
|
|
117
|
+
"""Get list of protocols required by this node.
|
|
118
|
+
|
|
119
|
+
Returns the protocol class names that must be registered in the
|
|
120
|
+
container before creating a NodeContractPersistenceEffect instance.
|
|
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
|
+
|
|
127
|
+
Returns:
|
|
128
|
+
List of protocol class names required for node operation.
|
|
129
|
+
|
|
130
|
+
Example:
|
|
131
|
+
>>> protocols = RegistryInfraContractPersistenceEffect.get_required_protocols()
|
|
132
|
+
>>> for proto in protocols:
|
|
133
|
+
... if not container.has(proto):
|
|
134
|
+
... raise ConfigurationError(f"Missing: {proto}")
|
|
135
|
+
|
|
136
|
+
.. versionadded:: 0.5.0
|
|
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
|
+
)
|
|
145
|
+
return [
|
|
146
|
+
"ProtocolPostgresAdapter",
|
|
147
|
+
"ProtocolCircuitBreakerAware",
|
|
148
|
+
]
|
|
149
|
+
|
|
150
|
+
@staticmethod
|
|
151
|
+
def get_node_type() -> str:
|
|
152
|
+
"""Get the node type classification.
|
|
153
|
+
|
|
154
|
+
Returns the ONEX node archetype for this node, used for
|
|
155
|
+
routing decisions and execution context selection.
|
|
156
|
+
|
|
157
|
+
Returns:
|
|
158
|
+
Node type string ("EFFECT").
|
|
159
|
+
|
|
160
|
+
Note:
|
|
161
|
+
EFFECT nodes perform external I/O operations and should
|
|
162
|
+
be treated as side-effecting by the runtime.
|
|
163
|
+
|
|
164
|
+
.. versionadded:: 0.5.0
|
|
165
|
+
"""
|
|
166
|
+
return "EFFECT"
|
|
167
|
+
|
|
168
|
+
@staticmethod
|
|
169
|
+
def get_node_name() -> str:
|
|
170
|
+
"""Get the canonical node name.
|
|
171
|
+
|
|
172
|
+
Returns:
|
|
173
|
+
The node name as defined in contract.yaml.
|
|
174
|
+
|
|
175
|
+
.. versionadded:: 0.5.0
|
|
176
|
+
"""
|
|
177
|
+
return "node_contract_persistence_effect"
|
|
178
|
+
|
|
179
|
+
@staticmethod
|
|
180
|
+
def get_capabilities() -> list[str]:
|
|
181
|
+
"""Get list of capabilities provided by this node.
|
|
182
|
+
|
|
183
|
+
Returns capability identifiers that can be used for service
|
|
184
|
+
discovery and feature detection.
|
|
185
|
+
|
|
186
|
+
Returns:
|
|
187
|
+
List of capability identifiers.
|
|
188
|
+
|
|
189
|
+
.. versionadded:: 0.5.0
|
|
190
|
+
"""
|
|
191
|
+
return [
|
|
192
|
+
"contract_persistence",
|
|
193
|
+
"topic_routing",
|
|
194
|
+
"staleness_detection",
|
|
195
|
+
"heartbeat_tracking",
|
|
196
|
+
"soft_delete",
|
|
197
|
+
"circuit_breaker_protection",
|
|
198
|
+
]
|
|
199
|
+
|
|
200
|
+
@staticmethod
|
|
201
|
+
def get_supported_operations() -> list[str]:
|
|
202
|
+
"""Get list of operations supported by this node.
|
|
203
|
+
|
|
204
|
+
Returns:
|
|
205
|
+
List of operation identifiers as defined in contract.yaml.
|
|
206
|
+
|
|
207
|
+
.. versionadded:: 0.5.0
|
|
208
|
+
"""
|
|
209
|
+
return [
|
|
210
|
+
"upsert_contract",
|
|
211
|
+
"update_topic",
|
|
212
|
+
"mark_stale",
|
|
213
|
+
"update_heartbeat",
|
|
214
|
+
"deactivate_contract",
|
|
215
|
+
"cleanup_topic_references",
|
|
216
|
+
]
|
|
217
|
+
|
|
218
|
+
@staticmethod
|
|
219
|
+
def get_supported_intent_types() -> list[str]:
|
|
220
|
+
"""Get list of intent types routed by this node.
|
|
221
|
+
|
|
222
|
+
Returns the payload.intent_type values that this effect node
|
|
223
|
+
can handle, matching ContractRegistryReducer output.
|
|
224
|
+
|
|
225
|
+
Returns:
|
|
226
|
+
List of intent type strings.
|
|
227
|
+
|
|
228
|
+
.. versionadded:: 0.5.0
|
|
229
|
+
"""
|
|
230
|
+
return [
|
|
231
|
+
"postgres.upsert_contract",
|
|
232
|
+
"postgres.update_topic",
|
|
233
|
+
"postgres.mark_stale",
|
|
234
|
+
"postgres.update_heartbeat",
|
|
235
|
+
"postgres.deactivate_contract",
|
|
236
|
+
"postgres.cleanup_topic_references",
|
|
237
|
+
]
|
|
238
|
+
|
|
239
|
+
@staticmethod
|
|
240
|
+
def get_backends() -> list[str]:
|
|
241
|
+
"""Get list of backend types this node interacts with.
|
|
242
|
+
|
|
243
|
+
Returns:
|
|
244
|
+
List of backend identifiers.
|
|
245
|
+
|
|
246
|
+
.. versionadded:: 0.5.0
|
|
247
|
+
"""
|
|
248
|
+
return ["postgres"]
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
__all__ = ["RegistryInfraContractPersistenceEffect"]
|
|
@@ -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():
|
|
@@ -21,9 +21,9 @@ Related:
|
|
|
21
21
|
|
|
22
22
|
from __future__ import annotations
|
|
23
23
|
|
|
24
|
-
# Re-export shared
|
|
24
|
+
# Re-export shared models for convenience
|
|
25
|
+
from omnibase_infra.models import ModelBackendResult
|
|
25
26
|
from omnibase_infra.nodes.effects.models import (
|
|
26
|
-
ModelBackendResult,
|
|
27
27
|
ModelEffectIdempotencyConfig,
|
|
28
28
|
ModelRegistryRequest,
|
|
29
29
|
ModelRegistryResponse,
|
|
@@ -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
|