omnibase_infra 0.2.1__py3-none-any.whl → 0.2.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- omnibase_infra/__init__.py +1 -1
- omnibase_infra/adapters/adapter_onex_tool_execution.py +451 -0
- omnibase_infra/capabilities/__init__.py +15 -0
- omnibase_infra/capabilities/capability_inference_rules.py +211 -0
- omnibase_infra/capabilities/contract_capability_extractor.py +221 -0
- omnibase_infra/capabilities/intent_type_extractor.py +160 -0
- omnibase_infra/cli/commands.py +1 -1
- omnibase_infra/configs/widget_mapping.yaml +176 -0
- omnibase_infra/contracts/handlers/filesystem/handler_contract.yaml +5 -2
- omnibase_infra/contracts/handlers/mcp/handler_contract.yaml +5 -2
- omnibase_infra/enums/__init__.py +6 -0
- omnibase_infra/enums/enum_handler_error_type.py +10 -0
- omnibase_infra/enums/enum_handler_source_mode.py +72 -0
- omnibase_infra/enums/enum_kafka_acks.py +99 -0
- omnibase_infra/errors/error_compute_registry.py +4 -1
- omnibase_infra/errors/error_event_bus_registry.py +4 -1
- omnibase_infra/errors/error_infra.py +3 -1
- omnibase_infra/errors/error_policy_registry.py +4 -1
- omnibase_infra/event_bus/event_bus_kafka.py +1 -1
- omnibase_infra/event_bus/models/config/model_kafka_event_bus_config.py +59 -10
- omnibase_infra/handlers/__init__.py +8 -1
- omnibase_infra/handlers/handler_consul.py +7 -1
- omnibase_infra/handlers/handler_db.py +10 -3
- omnibase_infra/handlers/handler_graph.py +10 -5
- omnibase_infra/handlers/handler_http.py +8 -2
- omnibase_infra/handlers/handler_intent.py +387 -0
- omnibase_infra/handlers/handler_mcp.py +745 -63
- omnibase_infra/handlers/handler_vault.py +11 -5
- omnibase_infra/handlers/mixins/mixin_consul_kv.py +4 -3
- omnibase_infra/handlers/mixins/mixin_consul_service.py +2 -1
- omnibase_infra/handlers/registration_storage/handler_registration_storage_postgres.py +7 -0
- omnibase_infra/handlers/service_discovery/handler_service_discovery_consul.py +308 -4
- omnibase_infra/handlers/service_discovery/models/model_service_info.py +10 -0
- omnibase_infra/mixins/mixin_async_circuit_breaker.py +3 -2
- omnibase_infra/mixins/mixin_node_introspection.py +42 -7
- omnibase_infra/mixins/mixin_retry_execution.py +1 -1
- omnibase_infra/models/discovery/model_introspection_config.py +11 -0
- omnibase_infra/models/handlers/__init__.py +48 -5
- omnibase_infra/models/handlers/model_bootstrap_handler_descriptor.py +162 -0
- omnibase_infra/models/handlers/model_contract_discovery_result.py +6 -4
- omnibase_infra/models/handlers/model_handler_descriptor.py +15 -0
- omnibase_infra/models/handlers/model_handler_source_config.py +220 -0
- omnibase_infra/models/mcp/__init__.py +15 -0
- omnibase_infra/models/mcp/model_mcp_contract_config.py +80 -0
- omnibase_infra/models/mcp/model_mcp_server_config.py +67 -0
- omnibase_infra/models/mcp/model_mcp_tool_definition.py +73 -0
- omnibase_infra/models/mcp/model_mcp_tool_parameter.py +35 -0
- omnibase_infra/models/registration/model_node_capabilities.py +11 -0
- omnibase_infra/models/registration/model_node_introspection_event.py +9 -0
- omnibase_infra/models/runtime/model_handler_contract.py +25 -9
- omnibase_infra/models/runtime/model_loaded_handler.py +9 -0
- omnibase_infra/nodes/architecture_validator/contract_architecture_validator.yaml +0 -5
- omnibase_infra/nodes/architecture_validator/registry/registry_infra_architecture_validator.py +17 -10
- omnibase_infra/nodes/effects/contract.yaml +0 -5
- omnibase_infra/nodes/node_registration_orchestrator/contract.yaml +7 -0
- omnibase_infra/nodes/node_registration_orchestrator/handlers/handler_node_introspected.py +86 -1
- omnibase_infra/nodes/node_registration_orchestrator/introspection_event_router.py +3 -3
- omnibase_infra/nodes/node_registration_orchestrator/plugin.py +1 -1
- omnibase_infra/nodes/node_registration_orchestrator/registry/registry_infra_node_registration_orchestrator.py +9 -8
- omnibase_infra/nodes/node_registration_orchestrator/timeout_coordinator.py +4 -3
- omnibase_infra/nodes/node_registration_orchestrator/wiring.py +14 -13
- omnibase_infra/nodes/node_registration_storage_effect/contract.yaml +0 -5
- omnibase_infra/nodes/node_registration_storage_effect/node.py +4 -1
- omnibase_infra/nodes/node_registration_storage_effect/registry/registry_infra_registration_storage.py +47 -26
- omnibase_infra/nodes/node_registry_effect/contract.yaml +0 -5
- omnibase_infra/nodes/node_registry_effect/handlers/handler_partial_retry.py +2 -1
- omnibase_infra/nodes/node_service_discovery_effect/registry/registry_infra_service_discovery.py +28 -20
- omnibase_infra/plugins/examples/plugin_json_normalizer.py +2 -2
- omnibase_infra/plugins/examples/plugin_json_normalizer_error_handling.py +2 -2
- omnibase_infra/plugins/plugin_compute_base.py +16 -2
- omnibase_infra/protocols/__init__.py +2 -0
- omnibase_infra/protocols/protocol_container_aware.py +200 -0
- omnibase_infra/protocols/protocol_event_projector.py +1 -1
- omnibase_infra/runtime/__init__.py +90 -1
- omnibase_infra/runtime/binding_config_resolver.py +102 -37
- omnibase_infra/runtime/constants_notification.py +75 -0
- omnibase_infra/runtime/contract_handler_discovery.py +6 -1
- omnibase_infra/runtime/handler_bootstrap_source.py +507 -0
- omnibase_infra/runtime/handler_contract_config_loader.py +603 -0
- omnibase_infra/runtime/handler_contract_source.py +267 -186
- omnibase_infra/runtime/handler_identity.py +81 -0
- omnibase_infra/runtime/handler_plugin_loader.py +19 -2
- omnibase_infra/runtime/handler_registry.py +11 -3
- omnibase_infra/runtime/handler_source_resolver.py +326 -0
- omnibase_infra/runtime/mixin_semver_cache.py +25 -1
- omnibase_infra/runtime/mixins/__init__.py +7 -0
- omnibase_infra/runtime/mixins/mixin_projector_notification_publishing.py +566 -0
- omnibase_infra/runtime/mixins/mixin_projector_sql_operations.py +31 -10
- omnibase_infra/runtime/models/__init__.py +24 -0
- omnibase_infra/runtime/models/model_health_check_result.py +2 -1
- omnibase_infra/runtime/models/model_projector_notification_config.py +171 -0
- omnibase_infra/runtime/models/model_transition_notification_outbox_config.py +112 -0
- omnibase_infra/runtime/models/model_transition_notification_outbox_metrics.py +140 -0
- omnibase_infra/runtime/models/model_transition_notification_publisher_metrics.py +357 -0
- omnibase_infra/runtime/projector_plugin_loader.py +1 -1
- omnibase_infra/runtime/projector_shell.py +229 -1
- omnibase_infra/runtime/protocol_lifecycle_executor.py +6 -6
- omnibase_infra/runtime/protocols/__init__.py +10 -0
- omnibase_infra/runtime/registry/registry_protocol_binding.py +16 -15
- omnibase_infra/runtime/registry_contract_source.py +693 -0
- omnibase_infra/runtime/registry_policy.py +9 -326
- omnibase_infra/runtime/secret_resolver.py +4 -2
- omnibase_infra/runtime/service_kernel.py +11 -3
- omnibase_infra/runtime/service_message_dispatch_engine.py +4 -2
- omnibase_infra/runtime/service_runtime_host_process.py +589 -106
- omnibase_infra/runtime/transition_notification_outbox.py +1190 -0
- omnibase_infra/runtime/transition_notification_publisher.py +764 -0
- omnibase_infra/runtime/util_container_wiring.py +6 -5
- omnibase_infra/runtime/util_wiring.py +17 -4
- omnibase_infra/schemas/schema_transition_notification_outbox.sql +245 -0
- omnibase_infra/services/__init__.py +21 -0
- omnibase_infra/services/corpus_capture.py +7 -1
- omnibase_infra/services/mcp/__init__.py +31 -0
- omnibase_infra/services/mcp/mcp_server_lifecycle.py +449 -0
- omnibase_infra/services/mcp/service_mcp_tool_discovery.py +411 -0
- omnibase_infra/services/mcp/service_mcp_tool_registry.py +329 -0
- omnibase_infra/services/mcp/service_mcp_tool_sync.py +547 -0
- omnibase_infra/services/registry_api/__init__.py +40 -0
- omnibase_infra/services/registry_api/main.py +261 -0
- omnibase_infra/services/registry_api/models/__init__.py +66 -0
- omnibase_infra/services/registry_api/models/model_capability_widget_mapping.py +38 -0
- omnibase_infra/services/registry_api/models/model_pagination_info.py +48 -0
- omnibase_infra/services/registry_api/models/model_registry_discovery_response.py +73 -0
- omnibase_infra/services/registry_api/models/model_registry_health_response.py +49 -0
- omnibase_infra/services/registry_api/models/model_registry_instance_view.py +88 -0
- omnibase_infra/services/registry_api/models/model_registry_node_view.py +88 -0
- omnibase_infra/services/registry_api/models/model_registry_summary.py +60 -0
- omnibase_infra/services/registry_api/models/model_response_list_instances.py +43 -0
- omnibase_infra/services/registry_api/models/model_response_list_nodes.py +51 -0
- omnibase_infra/services/registry_api/models/model_warning.py +49 -0
- omnibase_infra/services/registry_api/models/model_widget_defaults.py +28 -0
- omnibase_infra/services/registry_api/models/model_widget_mapping.py +51 -0
- omnibase_infra/services/registry_api/routes.py +371 -0
- omnibase_infra/services/registry_api/service.py +837 -0
- omnibase_infra/services/service_capability_query.py +4 -4
- omnibase_infra/services/service_health.py +3 -2
- omnibase_infra/services/service_timeout_emitter.py +20 -3
- omnibase_infra/services/service_timeout_scanner.py +7 -3
- omnibase_infra/services/session/__init__.py +56 -0
- omnibase_infra/services/session/config_consumer.py +120 -0
- omnibase_infra/services/session/config_store.py +139 -0
- omnibase_infra/services/session/consumer.py +1007 -0
- omnibase_infra/services/session/protocol_session_aggregator.py +117 -0
- omnibase_infra/services/session/store.py +997 -0
- omnibase_infra/utils/__init__.py +19 -0
- omnibase_infra/utils/util_atomic_file.py +261 -0
- omnibase_infra/utils/util_db_transaction.py +239 -0
- omnibase_infra/utils/util_dsn_validation.py +1 -1
- omnibase_infra/utils/util_retry_optimistic.py +281 -0
- omnibase_infra/validation/__init__.py +3 -19
- omnibase_infra/validation/contracts/security.validation.yaml +114 -0
- omnibase_infra/validation/infra_validators.py +35 -24
- omnibase_infra/validation/validation_exemptions.yaml +140 -9
- omnibase_infra/validation/validator_chain_propagation.py +2 -2
- omnibase_infra/validation/validator_runtime_shape.py +1 -1
- omnibase_infra/validation/validator_security.py +473 -370
- {omnibase_infra-0.2.1.dist-info → omnibase_infra-0.2.3.dist-info}/METADATA +3 -3
- {omnibase_infra-0.2.1.dist-info → omnibase_infra-0.2.3.dist-info}/RECORD +161 -98
- {omnibase_infra-0.2.1.dist-info → omnibase_infra-0.2.3.dist-info}/WHEEL +0 -0
- {omnibase_infra-0.2.1.dist-info → omnibase_infra-0.2.3.dist-info}/entry_points.txt +0 -0
- {omnibase_infra-0.2.1.dist-info → omnibase_infra-0.2.3.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
# Copyright (c) 2025 OmniNode Team
|
|
3
|
+
"""Optimistic locking retry helper for concurrent data operations.
|
|
4
|
+
|
|
5
|
+
This module provides utilities for retrying operations that may fail due to
|
|
6
|
+
optimistic locking conflicts. Optimistic locking is a concurrency control
|
|
7
|
+
strategy where conflicts are detected at write time rather than using locks.
|
|
8
|
+
|
|
9
|
+
Use Cases:
|
|
10
|
+
- Database UPDATE with version checks (row_count=0 indicates conflict)
|
|
11
|
+
- CAS (Compare-And-Swap) operations in distributed systems
|
|
12
|
+
- Consul KV ModifyIndex-based updates
|
|
13
|
+
- Any operation where conflict detection is based on return value
|
|
14
|
+
|
|
15
|
+
Design Decisions:
|
|
16
|
+
- No circuit breaker integration: Optimistic conflicts are application logic,
|
|
17
|
+
not infrastructure failures. They indicate contention, not service degradation.
|
|
18
|
+
- Lower initial backoff: 0.1s vs 1.0s for transient errors because conflicts
|
|
19
|
+
typically resolve faster as other transactions complete.
|
|
20
|
+
- Jitter by default: Critical for preventing thundering herd on high-contention
|
|
21
|
+
resources where multiple retries might synchronize.
|
|
22
|
+
- Caller-provided conflict check: Flexible for different conflict indicators
|
|
23
|
+
(row_count=0, version mismatch, boolean flags, etc.)
|
|
24
|
+
|
|
25
|
+
Example:
|
|
26
|
+
>>> import asyncio
|
|
27
|
+
>>> from omnibase_infra.utils.util_retry_optimistic import (
|
|
28
|
+
... retry_on_optimistic_conflict,
|
|
29
|
+
... OptimisticConflictError,
|
|
30
|
+
... )
|
|
31
|
+
>>>
|
|
32
|
+
>>> attempt_count = 0
|
|
33
|
+
>>> async def update_with_version_check():
|
|
34
|
+
... global attempt_count
|
|
35
|
+
... attempt_count += 1
|
|
36
|
+
... # Simulate success after 2 conflicts
|
|
37
|
+
... if attempt_count < 3:
|
|
38
|
+
... return {"row_count": 0} # Conflict
|
|
39
|
+
... return {"row_count": 1} # Success
|
|
40
|
+
>>>
|
|
41
|
+
>>> async def main():
|
|
42
|
+
... result = await retry_on_optimistic_conflict(
|
|
43
|
+
... update_with_version_check,
|
|
44
|
+
... check_conflict=lambda r: r["row_count"] == 0,
|
|
45
|
+
... max_retries=5,
|
|
46
|
+
... )
|
|
47
|
+
... print(f"Success after {attempt_count} attempts")
|
|
48
|
+
>>>
|
|
49
|
+
>>> asyncio.run(main()) # doctest: +SKIP
|
|
50
|
+
Success after 3 attempts
|
|
51
|
+
|
|
52
|
+
See Also:
|
|
53
|
+
- ONEX infrastructure patterns documentation
|
|
54
|
+
- PostgreSQL advisory locks vs optimistic locking
|
|
55
|
+
- Consul KV ModifyIndex documentation
|
|
56
|
+
|
|
57
|
+
.. versionadded:: 0.10.0
|
|
58
|
+
Created for database operations requiring optimistic concurrency control.
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
from __future__ import annotations
|
|
62
|
+
|
|
63
|
+
import asyncio
|
|
64
|
+
import logging
|
|
65
|
+
import random
|
|
66
|
+
from collections.abc import Awaitable, Callable
|
|
67
|
+
from typing import TYPE_CHECKING, TypeVar
|
|
68
|
+
|
|
69
|
+
if TYPE_CHECKING:
|
|
70
|
+
from uuid import UUID
|
|
71
|
+
|
|
72
|
+
logger = logging.getLogger(__name__)
|
|
73
|
+
|
|
74
|
+
# Generic type for the return value of the retried function
|
|
75
|
+
T = TypeVar("T")
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
class OptimisticConflictError(Exception):
|
|
79
|
+
"""Exception raised when optimistic locking retries are exhausted.
|
|
80
|
+
|
|
81
|
+
This exception indicates that an operation failed due to repeated optimistic
|
|
82
|
+
locking conflicts after all retry attempts. The caller should handle this
|
|
83
|
+
by either:
|
|
84
|
+
- Reporting the conflict to the user
|
|
85
|
+
- Using a different conflict resolution strategy
|
|
86
|
+
- Applying exponential backoff at a higher level
|
|
87
|
+
|
|
88
|
+
Note:
|
|
89
|
+
This is a standard Python exception, not an ONEX error. Optimistic
|
|
90
|
+
conflicts are expected application behavior, not infrastructure failures.
|
|
91
|
+
|
|
92
|
+
Attributes:
|
|
93
|
+
attempts: The total number of attempts made (including initial attempt).
|
|
94
|
+
last_result: The result from the final failed attempt, useful for
|
|
95
|
+
debugging or logging the conflict state.
|
|
96
|
+
|
|
97
|
+
Example:
|
|
98
|
+
>>> from omnibase_infra.utils.util_retry_optimistic import OptimisticConflictError
|
|
99
|
+
>>>
|
|
100
|
+
>>> try:
|
|
101
|
+
... # ... retry logic that exhausted retries
|
|
102
|
+
... raise OptimisticConflictError(
|
|
103
|
+
... attempts=4,
|
|
104
|
+
... last_result={"row_count": 0, "current_version": 5}
|
|
105
|
+
... )
|
|
106
|
+
... except OptimisticConflictError as e:
|
|
107
|
+
... print(f"Failed after {e.attempts} attempts")
|
|
108
|
+
... print(f"Last conflict state: {e.last_result}")
|
|
109
|
+
Failed after 4 attempts
|
|
110
|
+
Last conflict state: {'row_count': 0, 'current_version': 5}
|
|
111
|
+
"""
|
|
112
|
+
|
|
113
|
+
def __init__(self, *, attempts: int, last_result: object) -> None:
|
|
114
|
+
"""Initialize OptimisticConflictError.
|
|
115
|
+
|
|
116
|
+
Args:
|
|
117
|
+
attempts: Total number of attempts made.
|
|
118
|
+
last_result: Result from the final attempt.
|
|
119
|
+
"""
|
|
120
|
+
self.attempts = attempts
|
|
121
|
+
self.last_result = last_result
|
|
122
|
+
super().__init__(
|
|
123
|
+
f"Optimistic locking conflict persisted after {attempts} attempts. "
|
|
124
|
+
f"Last result: {last_result}"
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
async def retry_on_optimistic_conflict(
|
|
129
|
+
fn: Callable[[], Awaitable[T]],
|
|
130
|
+
*,
|
|
131
|
+
check_conflict: Callable[[T], bool],
|
|
132
|
+
max_retries: int = 3,
|
|
133
|
+
initial_backoff: float = 0.1,
|
|
134
|
+
max_backoff: float = 5.0,
|
|
135
|
+
backoff_multiplier: float = 2.0,
|
|
136
|
+
jitter: bool = True,
|
|
137
|
+
correlation_id: UUID | None = None,
|
|
138
|
+
) -> T:
|
|
139
|
+
"""Execute async function with retry on optimistic locking conflict.
|
|
140
|
+
|
|
141
|
+
This function implements exponential backoff retry logic for operations
|
|
142
|
+
that may fail due to optimistic locking conflicts. Unlike transient error
|
|
143
|
+
retries, this does NOT integrate with circuit breakers since conflicts
|
|
144
|
+
indicate contention, not service degradation.
|
|
145
|
+
|
|
146
|
+
Args:
|
|
147
|
+
fn: Async function to execute. Should take no arguments; use closures
|
|
148
|
+
or functools.partial to bind arguments. Example:
|
|
149
|
+
``lambda: update_record(id=123, data=data)``
|
|
150
|
+
check_conflict: Callable that inspects the result and returns True if
|
|
151
|
+
the result indicates a conflict. Examples:
|
|
152
|
+
- ``lambda r: r.row_count == 0`` (database update)
|
|
153
|
+
- ``lambda r: r.success is False`` (boolean result)
|
|
154
|
+
- ``lambda r: r.version != expected_version`` (version mismatch)
|
|
155
|
+
max_retries: Maximum number of retry attempts after the initial attempt.
|
|
156
|
+
Total attempts = max_retries + 1. Defaults to 3.
|
|
157
|
+
initial_backoff: Initial backoff delay in seconds before first retry.
|
|
158
|
+
Lower than transient error defaults (0.1s vs 1.0s) because conflicts
|
|
159
|
+
typically resolve quickly. Defaults to 0.1.
|
|
160
|
+
max_backoff: Maximum backoff delay cap in seconds. Prevents excessive
|
|
161
|
+
wait times. Defaults to 5.0.
|
|
162
|
+
backoff_multiplier: Multiplier for exponential backoff between retries.
|
|
163
|
+
Delay doubles by default: 0.1s -> 0.2s -> 0.4s -> 0.8s. Defaults to 2.0.
|
|
164
|
+
jitter: If True, adds random jitter (50-150% of delay) to prevent
|
|
165
|
+
thundering herd when multiple clients retry simultaneously.
|
|
166
|
+
Strongly recommended for high-contention scenarios. Defaults to True.
|
|
167
|
+
correlation_id: Optional correlation ID for structured logging. When
|
|
168
|
+
provided, retry attempts are logged with this ID for distributed
|
|
169
|
+
tracing. Defaults to None.
|
|
170
|
+
|
|
171
|
+
Returns:
|
|
172
|
+
The result of ``fn()`` when ``check_conflict(result)`` returns False.
|
|
173
|
+
|
|
174
|
+
Raises:
|
|
175
|
+
OptimisticConflictError: If all retry attempts are exhausted and the
|
|
176
|
+
operation still indicates a conflict. Contains ``attempts`` count
|
|
177
|
+
and ``last_result`` for debugging.
|
|
178
|
+
|
|
179
|
+
Example:
|
|
180
|
+
Basic usage with row count check::
|
|
181
|
+
|
|
182
|
+
from functools import partial
|
|
183
|
+
from omnibase_infra.utils.util_retry_optimistic import (
|
|
184
|
+
retry_on_optimistic_conflict,
|
|
185
|
+
OptimisticConflictError,
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
async def update_with_version(id: str, data: dict, version: int):
|
|
189
|
+
return await db.execute(
|
|
190
|
+
"UPDATE t SET data=$1, version=version+1 "
|
|
191
|
+
"WHERE id=$2 AND version=$3",
|
|
192
|
+
data, id, version
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
try:
|
|
196
|
+
result = await retry_on_optimistic_conflict(
|
|
197
|
+
partial(update_with_version, "abc", {"name": "new"}, 5),
|
|
198
|
+
check_conflict=lambda r: r.row_count == 0,
|
|
199
|
+
max_retries=5,
|
|
200
|
+
correlation_id=correlation_id,
|
|
201
|
+
)
|
|
202
|
+
except OptimisticConflictError as e:
|
|
203
|
+
logger.error(f"Update failed after {e.attempts} attempts")
|
|
204
|
+
raise
|
|
205
|
+
|
|
206
|
+
Warning:
|
|
207
|
+
The ``fn`` callable should be idempotent or at least safe to retry.
|
|
208
|
+
This function will call ``fn`` multiple times on conflicts.
|
|
209
|
+
|
|
210
|
+
Note:
|
|
211
|
+
Backoff timing with default parameters (jitter disabled for clarity):
|
|
212
|
+
- Attempt 1: immediate
|
|
213
|
+
- Attempt 2: wait 0.1s
|
|
214
|
+
- Attempt 3: wait 0.2s
|
|
215
|
+
- Attempt 4: wait 0.4s
|
|
216
|
+
- Total max wait: ~0.7s (with max_retries=3)
|
|
217
|
+
|
|
218
|
+
.. versionadded:: 0.10.0
|
|
219
|
+
"""
|
|
220
|
+
backoff = initial_backoff
|
|
221
|
+
last_result: T | None = None
|
|
222
|
+
|
|
223
|
+
for attempt in range(max_retries + 1): # +1 for initial attempt
|
|
224
|
+
result = await fn()
|
|
225
|
+
|
|
226
|
+
if not check_conflict(result):
|
|
227
|
+
# Success - no conflict detected
|
|
228
|
+
if attempt > 0 and correlation_id is not None:
|
|
229
|
+
logger.info(
|
|
230
|
+
"Optimistic conflict resolved after %d retries",
|
|
231
|
+
attempt,
|
|
232
|
+
extra={
|
|
233
|
+
"correlation_id": str(correlation_id),
|
|
234
|
+
"total_attempts": attempt + 1,
|
|
235
|
+
"action": "optimistic_conflict_resolved",
|
|
236
|
+
},
|
|
237
|
+
)
|
|
238
|
+
return result
|
|
239
|
+
|
|
240
|
+
# Conflict detected - store result for potential error reporting
|
|
241
|
+
last_result = result
|
|
242
|
+
|
|
243
|
+
if attempt == max_retries:
|
|
244
|
+
# All retries exhausted
|
|
245
|
+
break
|
|
246
|
+
|
|
247
|
+
# Log retry attempt if correlation_id provided
|
|
248
|
+
if correlation_id is not None:
|
|
249
|
+
logger.debug(
|
|
250
|
+
"Optimistic conflict detected, retrying (attempt %d/%d)",
|
|
251
|
+
attempt + 1,
|
|
252
|
+
max_retries + 1,
|
|
253
|
+
extra={
|
|
254
|
+
"correlation_id": str(correlation_id),
|
|
255
|
+
"attempt": attempt + 1,
|
|
256
|
+
"max_attempts": max_retries + 1,
|
|
257
|
+
"backoff_seconds": backoff,
|
|
258
|
+
"action": "optimistic_conflict_retry",
|
|
259
|
+
},
|
|
260
|
+
)
|
|
261
|
+
|
|
262
|
+
# Calculate delay with optional jitter
|
|
263
|
+
delay = min(backoff, max_backoff)
|
|
264
|
+
if jitter:
|
|
265
|
+
# Apply 50-150% jitter to prevent thundering herd
|
|
266
|
+
delay *= 0.5 + random.random()
|
|
267
|
+
|
|
268
|
+
await asyncio.sleep(delay)
|
|
269
|
+
backoff *= backoff_multiplier
|
|
270
|
+
|
|
271
|
+
# All retries exhausted - raise conflict error
|
|
272
|
+
# Defensive check - last_result is guaranteed to be set after at least one attempt
|
|
273
|
+
if last_result is None:
|
|
274
|
+
raise AssertionError("Unreachable: last_result must be set after retries")
|
|
275
|
+
raise OptimisticConflictError(attempts=max_retries + 1, last_result=last_result)
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
__all__: list[str] = [
|
|
279
|
+
"OptimisticConflictError",
|
|
280
|
+
"retry_on_optimistic_conflict",
|
|
281
|
+
]
|
|
@@ -200,17 +200,8 @@ from omnibase_infra.validation.validator_runtime_shape import (
|
|
|
200
200
|
enforce_execution_shape,
|
|
201
201
|
)
|
|
202
202
|
|
|
203
|
-
# Security validation
|
|
204
|
-
from omnibase_infra.validation.validator_security import
|
|
205
|
-
SENSITIVE_METHOD_PATTERNS,
|
|
206
|
-
SENSITIVE_PARAMETER_NAMES,
|
|
207
|
-
SecurityRuleId,
|
|
208
|
-
convert_to_validation_error,
|
|
209
|
-
has_sensitive_parameters,
|
|
210
|
-
is_sensitive_method_name,
|
|
211
|
-
validate_handler_security,
|
|
212
|
-
validate_method_exposure,
|
|
213
|
-
)
|
|
203
|
+
# Security validation (OMN-1277) - contract-driven validator
|
|
204
|
+
from omnibase_infra.validation.validator_security import ValidatorSecurity
|
|
214
205
|
|
|
215
206
|
# Topic category validation for execution shape enforcement
|
|
216
207
|
from omnibase_infra.validation.validator_topic_category import (
|
|
@@ -229,8 +220,6 @@ __all__: list[str] = [
|
|
|
229
220
|
"EXECUTION_SHAPE_RULES", # Runtime shape validation rules
|
|
230
221
|
"INFRA_MAX_UNIONS", # Infrastructure max union threshold
|
|
231
222
|
"NODE_ARCHETYPE_EXPECTED_CATEGORIES", # Node archetype categories
|
|
232
|
-
"SENSITIVE_METHOD_PATTERNS", # Security validation patterns
|
|
233
|
-
"SENSITIVE_PARAMETER_NAMES", # Security validation names
|
|
234
223
|
"TOPIC_CATEGORY_PATTERNS", # Topic category patterns
|
|
235
224
|
"TOPIC_SUFFIXES", # Topic suffix constants
|
|
236
225
|
# Errors
|
|
@@ -239,7 +228,6 @@ __all__: list[str] = [
|
|
|
239
228
|
"RoutingCoverageError", # Routing coverage error (OMN-958)
|
|
240
229
|
# Enums
|
|
241
230
|
"EnumContractViolationSeverity", # Contract violation severity
|
|
242
|
-
"SecurityRuleId", # Security rule identifiers
|
|
243
231
|
# Models
|
|
244
232
|
"ModelAnyTypeValidationResult", # Any type validation result (OMN-1276)
|
|
245
233
|
"ModelContractLintResult", # Contract lint result
|
|
@@ -261,9 +249,9 @@ __all__: list[str] = [
|
|
|
261
249
|
"TopicCategoryASTVisitor", # Topic category AST visitor
|
|
262
250
|
"TopicCategoryValidator", # Topic category validator
|
|
263
251
|
"ValidationAggregator", # Validation error aggregation (OMN-1091)
|
|
252
|
+
"ValidatorSecurity", # Contract-driven security validator (OMN-1277)
|
|
264
253
|
# Functions
|
|
265
254
|
"check_routing_coverage_ci", # CI routing coverage check
|
|
266
|
-
"convert_to_validation_error", # Error conversion utility
|
|
267
255
|
"detect_message_category", # Message category detection
|
|
268
256
|
"discover_message_types", # Message type discovery
|
|
269
257
|
"discover_registered_routes", # Route discovery
|
|
@@ -271,9 +259,7 @@ __all__: list[str] = [
|
|
|
271
259
|
"enforce_execution_shape", # Execution shape enforcement
|
|
272
260
|
"get_execution_shape_rules", # Get shape rules
|
|
273
261
|
"get_validation_summary", # Get validation summary
|
|
274
|
-
"has_sensitive_parameters", # Sensitive parameter check
|
|
275
262
|
"is_isinstance_union", # Check if union is in isinstance() call
|
|
276
|
-
"is_sensitive_method_name", # Sensitive method check
|
|
277
263
|
"lint_contract_file", # Lint single contract file
|
|
278
264
|
"lint_contracts_ci", # CI contract linting
|
|
279
265
|
"lint_contracts_in_directory", # Directory contract linting
|
|
@@ -286,7 +272,6 @@ __all__: list[str] = [
|
|
|
286
272
|
"validate_execution_shapes", # Execution shape validation
|
|
287
273
|
"validate_execution_shapes_ci", # CI shape validation
|
|
288
274
|
"validate_handler_registration", # Handler registration validation (OMN-1098)
|
|
289
|
-
"validate_handler_security", # Handler security validation
|
|
290
275
|
"validate_infra_all", # Infrastructure validation
|
|
291
276
|
"validate_infra_architecture", # Infrastructure architecture
|
|
292
277
|
"validate_infra_circular_imports", # Circular import check
|
|
@@ -298,7 +283,6 @@ __all__: list[str] = [
|
|
|
298
283
|
"validate_localhandler_in_file", # LocalHandler file validation (OMN-743)
|
|
299
284
|
"validate_message_chain", # Message chain validation
|
|
300
285
|
"validate_message_on_topic", # Topic message validation
|
|
301
|
-
"validate_method_exposure", # Method exposure validation
|
|
302
286
|
"validate_patterns", # Re-export from omnibase_core
|
|
303
287
|
"validate_routing_coverage_on_startup", # Startup routing check
|
|
304
288
|
"validate_topic_categories_in_directory", # Directory topic validation
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
contract_kind: validation_subcontract
|
|
2
|
+
validation:
|
|
3
|
+
version:
|
|
4
|
+
major: 1
|
|
5
|
+
minor: 0
|
|
6
|
+
patch: 0
|
|
7
|
+
validator_id: security
|
|
8
|
+
validator_name: ONEX Security Validator
|
|
9
|
+
validator_description: |
|
|
10
|
+
Validates Python source files for security concerns in handler implementations.
|
|
11
|
+
Detects sensitive method names that should be private, credential exposure in
|
|
12
|
+
method signatures, and admin/internal operations exposed publicly.
|
|
13
|
+
|
|
14
|
+
This validator performs static AST analysis to identify:
|
|
15
|
+
- Public methods with sensitive names (get_password, get_secret, etc.)
|
|
16
|
+
- Method signatures containing sensitive parameter names
|
|
17
|
+
- Admin/internal methods exposed without underscore prefix
|
|
18
|
+
- Decrypt operations exposed publicly
|
|
19
|
+
|
|
20
|
+
Part of OMN-1277: Refactor validators to be Handler and contract-driven.
|
|
21
|
+
target_patterns:
|
|
22
|
+
- "**/*.py"
|
|
23
|
+
exclude_patterns:
|
|
24
|
+
- "**/node_modules/**"
|
|
25
|
+
- "**/.git/**"
|
|
26
|
+
- "**/venv/**"
|
|
27
|
+
- "**/.venv/**"
|
|
28
|
+
- "**/__pycache__/**"
|
|
29
|
+
- "**/archived/**"
|
|
30
|
+
- "**/tests/**"
|
|
31
|
+
- "**/*_test.py"
|
|
32
|
+
- "**/test_*.py"
|
|
33
|
+
- "**/conftest.py"
|
|
34
|
+
rules:
|
|
35
|
+
- rule_id: sensitive_method_exposed
|
|
36
|
+
description: |
|
|
37
|
+
Detects public methods with sensitive names that should be private.
|
|
38
|
+
Matches patterns: get_password, get_secret, get_token, get_api_key,
|
|
39
|
+
get_credential*, fetch_password, fetch_secret, fetch_token,
|
|
40
|
+
validate_password, check_password, verify_password.
|
|
41
|
+
severity: error
|
|
42
|
+
enabled: true
|
|
43
|
+
parameters:
|
|
44
|
+
patterns:
|
|
45
|
+
- "^get_password$"
|
|
46
|
+
- "^get_secret$"
|
|
47
|
+
- "^get_token$"
|
|
48
|
+
- "^get_api_key$"
|
|
49
|
+
- "^get_credential"
|
|
50
|
+
- "^fetch_password$"
|
|
51
|
+
- "^fetch_secret$"
|
|
52
|
+
- "^fetch_token$"
|
|
53
|
+
- "^validate_password$"
|
|
54
|
+
- "^check_password$"
|
|
55
|
+
- "^verify_password$"
|
|
56
|
+
- rule_id: credential_in_signature
|
|
57
|
+
description: |
|
|
58
|
+
Detects method signatures containing sensitive parameter names.
|
|
59
|
+
Matches parameters: password, secret, token, api_key, apikey,
|
|
60
|
+
access_key, private_key, credential, auth_token, bearer_token,
|
|
61
|
+
decrypt_key, encryption_key.
|
|
62
|
+
severity: error
|
|
63
|
+
enabled: true
|
|
64
|
+
parameters:
|
|
65
|
+
sensitive_params:
|
|
66
|
+
- "password"
|
|
67
|
+
- "secret"
|
|
68
|
+
- "token"
|
|
69
|
+
- "api_key"
|
|
70
|
+
- "apikey"
|
|
71
|
+
- "access_key"
|
|
72
|
+
- "private_key"
|
|
73
|
+
- "credential"
|
|
74
|
+
- "auth_token"
|
|
75
|
+
- "bearer_token"
|
|
76
|
+
- "decrypt_key"
|
|
77
|
+
- "encryption_key"
|
|
78
|
+
- rule_id: admin_method_public
|
|
79
|
+
description: |
|
|
80
|
+
Detects admin or internal methods exposed without underscore prefix.
|
|
81
|
+
Matches patterns: admin_*, internal_*.
|
|
82
|
+
severity: warning
|
|
83
|
+
enabled: true
|
|
84
|
+
parameters:
|
|
85
|
+
patterns:
|
|
86
|
+
- "^admin_"
|
|
87
|
+
- "^internal_"
|
|
88
|
+
- rule_id: decrypt_method_public
|
|
89
|
+
description: |
|
|
90
|
+
Detects decrypt operations exposed publicly.
|
|
91
|
+
Matches patterns: decrypt_*.
|
|
92
|
+
severity: warning
|
|
93
|
+
enabled: true
|
|
94
|
+
parameters:
|
|
95
|
+
patterns:
|
|
96
|
+
- "^decrypt_"
|
|
97
|
+
# ============================================================================
|
|
98
|
+
# LINE-BASED SUPPRESSION
|
|
99
|
+
# ============================================================================
|
|
100
|
+
# If ANY pattern below appears on a source line, ALL security violations
|
|
101
|
+
# on that line are suppressed.
|
|
102
|
+
#
|
|
103
|
+
# Usage example:
|
|
104
|
+
# def get_password(self) -> str: # ONEX_EXCLUDE: security - required for legacy API
|
|
105
|
+
# def admin_reset(self) -> None: # security-ok: admin endpoint with auth guard
|
|
106
|
+
# ============================================================================
|
|
107
|
+
suppression_comments:
|
|
108
|
+
- "# ONEX_EXCLUDE: security"
|
|
109
|
+
- "# security-ok:"
|
|
110
|
+
severity_default: error
|
|
111
|
+
fail_on_error: true
|
|
112
|
+
fail_on_warning: false
|
|
113
|
+
max_violations: 0
|
|
114
|
+
parallel_execution: true
|
|
@@ -37,10 +37,12 @@ from typing import TypedDict
|
|
|
37
37
|
import yaml
|
|
38
38
|
|
|
39
39
|
from omnibase_core.models.common import ModelValidationMetadata
|
|
40
|
+
from omnibase_core.models.primitives import ModelSemVer
|
|
40
41
|
from omnibase_core.models.validation.model_union_pattern import ModelUnionPattern
|
|
41
42
|
from omnibase_core.validation import (
|
|
42
43
|
CircularImportValidator,
|
|
43
44
|
ModelContractValidationResult,
|
|
45
|
+
ModelImportValidationResult,
|
|
44
46
|
ModelModuleImportResult,
|
|
45
47
|
ModelValidationResult,
|
|
46
48
|
validate_architecture,
|
|
@@ -423,7 +425,11 @@ INFRA_NODES_PATH = "src/omnibase_infra/nodes/"
|
|
|
423
425
|
# (+2 unions for EnumPolicyType | str in validate_policy_type_value)
|
|
424
426
|
# (-1 union: fix PolicyTypeInput validator coercion, changed return
|
|
425
427
|
# type from str | EnumPolicyType to EnumPolicyType)
|
|
426
|
-
|
|
428
|
+
# - 98 (2026-01-20): OMN-1277 security validator contract refactoring (+2 unions)
|
|
429
|
+
# ast.FunctionDef | ast.AsyncFunctionDef for AST method type checking
|
|
430
|
+
# - 105 (2026-01-21): Contract-driven handler config loading (+4 unions)
|
|
431
|
+
# ModelHandlerContract transport config fields and lifecycle types
|
|
432
|
+
INFRA_MAX_UNIONS = 105
|
|
427
433
|
|
|
428
434
|
# Maximum allowed architecture violations in infrastructure code.
|
|
429
435
|
# Set to 0 (strict enforcement) to ensure one-model-per-file principle is always followed.
|
|
@@ -747,13 +753,14 @@ def validate_infra_contract_deep(
|
|
|
747
753
|
return result
|
|
748
754
|
|
|
749
755
|
# If result is a different type, wrap it in ModelContractValidationResult
|
|
750
|
-
# Default to
|
|
751
|
-
# Check '
|
|
756
|
+
# Default to is_valid=False for unknown result types to avoid silently masking failures
|
|
757
|
+
# Check 'is_valid' first, then 'passed' as fallback (some validators use passed)
|
|
752
758
|
return ModelContractValidationResult(
|
|
753
|
-
|
|
759
|
+
is_valid=getattr(result, "is_valid", getattr(result, "passed", False)),
|
|
754
760
|
score=getattr(result, "score", 0.0),
|
|
755
|
-
|
|
761
|
+
violations=getattr(result, "violations", getattr(result, "errors", [])),
|
|
756
762
|
warnings=getattr(result, "warnings", []),
|
|
763
|
+
interface_version=ModelSemVer(major=1, minor=0, patch=0),
|
|
757
764
|
)
|
|
758
765
|
|
|
759
766
|
|
|
@@ -1318,29 +1325,33 @@ def validate_infra_union_usage(
|
|
|
1318
1325
|
# Note: ModelValidationMetadata uses extra="allow", so extension fields
|
|
1319
1326
|
# are accepted as int values.
|
|
1320
1327
|
# See docstring "Metadata Extension Fields" section for field documentation.
|
|
1328
|
+
#
|
|
1329
|
+
# Extension fields are passed via model_construct() to satisfy type checker
|
|
1330
|
+
# while preserving runtime behavior with extra="allow".
|
|
1331
|
+
metadata_fields: dict[str, object] = {
|
|
1332
|
+
# Standard ModelValidationMetadata fields (formally defined)
|
|
1333
|
+
"validation_type": "union_usage",
|
|
1334
|
+
"files_processed": files_processed,
|
|
1335
|
+
"violations_found": len(filtered_issues),
|
|
1336
|
+
"total_unions": total_count, # Base field: all unions found
|
|
1337
|
+
"max_unions": max_unions, # Base field: configured threshold
|
|
1338
|
+
"strict_mode": strict, # Base field: whether strict mode enabled
|
|
1339
|
+
# Extension fields (via extra="allow", typed as int)
|
|
1340
|
+
# These provide transparency into the exclusion logic:
|
|
1341
|
+
"non_optional_unions": threshold_count, # What threshold actually checks
|
|
1342
|
+
"optional_unions_excluded": optional_count, # X | None patterns
|
|
1343
|
+
"isinstance_unions_excluded": isinstance_count, # isinstance(x, A | B) patterns
|
|
1344
|
+
}
|
|
1321
1345
|
return ModelValidationResult(
|
|
1322
1346
|
is_valid=is_valid,
|
|
1323
1347
|
errors=filtered_issues,
|
|
1324
|
-
metadata=ModelValidationMetadata(
|
|
1325
|
-
# Standard ModelValidationMetadata fields (formally defined)
|
|
1326
|
-
validation_type="union_usage",
|
|
1327
|
-
files_processed=files_processed,
|
|
1328
|
-
violations_found=len(filtered_issues),
|
|
1329
|
-
total_unions=total_count, # Base field: all unions found
|
|
1330
|
-
max_unions=max_unions, # Base field: configured threshold
|
|
1331
|
-
strict_mode=strict, # Base field: whether strict mode enabled
|
|
1332
|
-
# Extension fields (via extra="allow", typed as int)
|
|
1333
|
-
# These provide transparency into the exclusion logic:
|
|
1334
|
-
non_optional_unions=threshold_count, # What threshold actually checks
|
|
1335
|
-
optional_unions_excluded=optional_count, # X | None patterns
|
|
1336
|
-
isinstance_unions_excluded=isinstance_count, # isinstance(x, A | B) patterns
|
|
1337
|
-
),
|
|
1348
|
+
metadata=ModelValidationMetadata.model_construct(**metadata_fields), # type: ignore[arg-type]
|
|
1338
1349
|
)
|
|
1339
1350
|
|
|
1340
1351
|
|
|
1341
1352
|
def validate_infra_circular_imports(
|
|
1342
1353
|
directory: PathInput = INFRA_SRC_PATH,
|
|
1343
|
-
) ->
|
|
1354
|
+
) -> ModelImportValidationResult:
|
|
1344
1355
|
"""
|
|
1345
1356
|
Check for circular imports in infrastructure code.
|
|
1346
1357
|
|
|
@@ -1351,7 +1362,7 @@ def validate_infra_circular_imports(
|
|
|
1351
1362
|
directory: Directory to check. Defaults to infrastructure source.
|
|
1352
1363
|
|
|
1353
1364
|
Returns:
|
|
1354
|
-
|
|
1365
|
+
ModelImportValidationResult with detailed import validation results.
|
|
1355
1366
|
Use result.has_circular_imports to check for issues.
|
|
1356
1367
|
"""
|
|
1357
1368
|
validator = CircularImportValidator(source_path=Path(directory))
|
|
@@ -1361,7 +1372,7 @@ def validate_infra_circular_imports(
|
|
|
1361
1372
|
def validate_infra_all(
|
|
1362
1373
|
directory: PathInput = INFRA_SRC_PATH,
|
|
1363
1374
|
nodes_directory: PathInput = INFRA_NODES_PATH,
|
|
1364
|
-
) -> dict[str, ValidationResult |
|
|
1375
|
+
) -> dict[str, ValidationResult | ModelImportValidationResult]:
|
|
1365
1376
|
"""
|
|
1366
1377
|
Run all validations on infrastructure code.
|
|
1367
1378
|
|
|
@@ -1379,7 +1390,7 @@ def validate_infra_all(
|
|
|
1379
1390
|
Returns:
|
|
1380
1391
|
Dictionary mapping validator name to result.
|
|
1381
1392
|
"""
|
|
1382
|
-
results: dict[str, ValidationResult |
|
|
1393
|
+
results: dict[str, ValidationResult | ModelImportValidationResult] = {}
|
|
1383
1394
|
|
|
1384
1395
|
# HIGH priority validators
|
|
1385
1396
|
results["architecture"] = validate_infra_architecture(directory)
|
|
@@ -1394,7 +1405,7 @@ def validate_infra_all(
|
|
|
1394
1405
|
|
|
1395
1406
|
|
|
1396
1407
|
def get_validation_summary(
|
|
1397
|
-
results: dict[str, ValidationResult |
|
|
1408
|
+
results: dict[str, ValidationResult | ModelImportValidationResult],
|
|
1398
1409
|
) -> dict[str, int | list[str]]:
|
|
1399
1410
|
"""
|
|
1400
1411
|
Generate a summary of validation results.
|