omnibase_infra 0.3.1__py3-none-any.whl → 0.3.2__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/handlers/registration_storage/handler_registration_storage_postgres.py +29 -20
- omnibase_infra/mixins/__init__.py +14 -0
- 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/{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/nodes/contract_registry_reducer/__init__.py +5 -0
- omnibase_infra/nodes/contract_registry_reducer/contract_registration_event_router.py +689 -0
- 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 +114 -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 +220 -0
- omnibase_infra/nodes/node_registry_effect/models/__init__.py +2 -2
- omnibase_infra/projectors/__init__.py +6 -0
- omnibase_infra/projectors/projection_reader_contract.py +1301 -0
- omnibase_infra/runtime/__init__.py +5 -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/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/request_response_wiring.py +785 -0
- omnibase_infra/runtime/service_kernel.py +295 -8
- 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/validation/infra_validators.py +3 -1
- omnibase_infra/validation/validation_exemptions.yaml +54 -0
- {omnibase_infra-0.3.1.dist-info → omnibase_infra-0.3.2.dist-info}/METADATA +3 -3
- {omnibase_infra-0.3.1.dist-info → omnibase_infra-0.3.2.dist-info}/RECORD +72 -34
- {omnibase_infra-0.3.1.dist-info → omnibase_infra-0.3.2.dist-info}/WHEEL +0 -0
- {omnibase_infra-0.3.1.dist-info → omnibase_infra-0.3.2.dist-info}/entry_points.txt +0 -0
- {omnibase_infra-0.3.1.dist-info → omnibase_infra-0.3.2.dist-info}/licenses/LICENSE +0 -0
omnibase_infra/__init__.py
CHANGED
omnibase_infra/enums/__init__.py
CHANGED
|
@@ -36,6 +36,7 @@ Exports:
|
|
|
36
36
|
EnumNodeOutputType: Node output types for execution shape validation
|
|
37
37
|
EnumNonRetryableErrorCategory: Non-retryable error categories for DLQ
|
|
38
38
|
EnumPolicyType: Policy types for RegistryPolicy plugins
|
|
39
|
+
EnumPostgresErrorCode: PostgreSQL error codes for contract persistence operations
|
|
39
40
|
EnumRegistrationState: Registration FSM states for two-way registration
|
|
40
41
|
EnumRegistrationStatus: Registration workflow status (IDLE, PENDING, PARTIAL, COMPLETE, FAILED)
|
|
41
42
|
EnumRegistryResponseStatus: Registry operation response status (SUCCESS, PARTIAL, FAILED)
|
|
@@ -82,6 +83,7 @@ from omnibase_infra.enums.enum_non_retryable_error_category import (
|
|
|
82
83
|
EnumNonRetryableErrorCategory,
|
|
83
84
|
)
|
|
84
85
|
from omnibase_infra.enums.enum_policy_type import EnumPolicyType
|
|
86
|
+
from omnibase_infra.enums.enum_postgres_error_code import EnumPostgresErrorCode
|
|
85
87
|
from omnibase_infra.enums.enum_registration_state import EnumRegistrationState
|
|
86
88
|
from omnibase_infra.enums.enum_registration_status import EnumRegistrationStatus
|
|
87
89
|
from omnibase_infra.enums.enum_registry_response_status import (
|
|
@@ -123,6 +125,7 @@ __all__: list[str] = [
|
|
|
123
125
|
"EnumNodeOutputType",
|
|
124
126
|
"EnumNonRetryableErrorCategory",
|
|
125
127
|
"EnumPolicyType",
|
|
128
|
+
"EnumPostgresErrorCode",
|
|
126
129
|
"EnumRegistrationState",
|
|
127
130
|
"EnumRegistrationStatus",
|
|
128
131
|
"EnumRegistryResponseStatus",
|
|
@@ -11,6 +11,7 @@ Consumer Group Purpose Categories:
|
|
|
11
11
|
- REPLAY: Reprocess historical data from earliest offset
|
|
12
12
|
- AUDIT: Compliance and read-only consumption
|
|
13
13
|
- BACKFILL: One-shot bounded consumers for populating derived state
|
|
14
|
+
- CONTRACT_REGISTRY: Contract lifecycle events (registration, deregistration, heartbeat)
|
|
14
15
|
|
|
15
16
|
The purpose determines consumer group naming conventions and default
|
|
16
17
|
offset reset policies in the Kafka adapter layer.
|
|
@@ -63,6 +64,11 @@ class EnumConsumerGroupPurpose(str, Enum):
|
|
|
63
64
|
- Naming: {base_group_id}-backfill
|
|
64
65
|
- Pattern: Bounded consumption until caught up
|
|
65
66
|
|
|
67
|
+
CONTRACT_REGISTRY: Contract lifecycle events processing.
|
|
68
|
+
- Default offset reset: latest
|
|
69
|
+
- Naming: {base_group_id}-contract-registry
|
|
70
|
+
- Pattern: Continuous consumption of registration, deregistration, heartbeat events
|
|
71
|
+
|
|
66
72
|
Example:
|
|
67
73
|
>>> purpose = EnumConsumerGroupPurpose.REPLAY
|
|
68
74
|
>>> f"order-processor-{purpose.value}"
|
|
@@ -84,6 +90,9 @@ class EnumConsumerGroupPurpose(str, Enum):
|
|
|
84
90
|
BACKFILL = "backfill"
|
|
85
91
|
"""One-shot bounded consumers for populating derived state."""
|
|
86
92
|
|
|
93
|
+
CONTRACT_REGISTRY = "contract-registry"
|
|
94
|
+
"""Contract lifecycle events (registration, deregistration, heartbeat)."""
|
|
95
|
+
|
|
87
96
|
def __str__(self) -> str:
|
|
88
97
|
"""Return the string value for serialization."""
|
|
89
98
|
return self.value
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
# Copyright (c) 2025 OmniNode Team
|
|
3
|
+
"""PostgreSQL Error Code Enumeration.
|
|
4
|
+
|
|
5
|
+
Defines structured error codes for PostgreSQL persistence operations. These codes
|
|
6
|
+
enable precise error classification, debugging, and programmatic error handling
|
|
7
|
+
for contract registry persistence via NodeContractPersistenceEffect.
|
|
8
|
+
|
|
9
|
+
Error Code Categories:
|
|
10
|
+
- Connection errors: Connection, timeout, authentication failures
|
|
11
|
+
- Operation errors: Specific operation failures (upsert, topic, etc.)
|
|
12
|
+
- Unknown errors: Catch-all for unclassified failures
|
|
13
|
+
|
|
14
|
+
Usage:
|
|
15
|
+
>>> from omnibase_infra.enums import EnumPostgresErrorCode
|
|
16
|
+
>>> error_code = EnumPostgresErrorCode.CONNECTION_ERROR
|
|
17
|
+
>>> print(f"Error: {error_code.value}")
|
|
18
|
+
Error: POSTGRES_CONNECTION_ERROR
|
|
19
|
+
|
|
20
|
+
>>> # Check if error is retriable
|
|
21
|
+
>>> if error_code.is_retriable:
|
|
22
|
+
... print("Will retry operation")
|
|
23
|
+
|
|
24
|
+
>>> # Categorize error type
|
|
25
|
+
>>> if error_code.is_connection_error:
|
|
26
|
+
... print("Connection-level failure")
|
|
27
|
+
|
|
28
|
+
See Also:
|
|
29
|
+
- NodeContractPersistenceEffect: Effect node using these error codes
|
|
30
|
+
- ContractRegistryReducer: Source of intents that may trigger these errors
|
|
31
|
+
- contract.yaml: Error code definitions in error_handling.error_codes
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
from enum import Enum
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class EnumPostgresErrorCode(str, Enum):
|
|
38
|
+
"""Error codes for PostgreSQL persistence operations.
|
|
39
|
+
|
|
40
|
+
These codes provide structured classification for failures during
|
|
41
|
+
contract registry persistence operations. Each code maps to a specific
|
|
42
|
+
failure scenario as defined in the contract.yaml error_codes section.
|
|
43
|
+
|
|
44
|
+
Connection Errors (retriable):
|
|
45
|
+
CONNECTION_ERROR: Connection to PostgreSQL server failed.
|
|
46
|
+
The database server is unreachable or connection was refused.
|
|
47
|
+
Verify PostgreSQL server is running and network is accessible.
|
|
48
|
+
|
|
49
|
+
TIMEOUT_ERROR: PostgreSQL operation exceeded timeout.
|
|
50
|
+
The operation took longer than the configured timeout threshold.
|
|
51
|
+
Check database load and query performance.
|
|
52
|
+
|
|
53
|
+
Authentication Errors (non-retriable):
|
|
54
|
+
AUTH_ERROR: Authentication with PostgreSQL server failed.
|
|
55
|
+
Invalid credentials or insufficient privileges.
|
|
56
|
+
Verify POSTGRES_USER and POSTGRES_PASSWORD in .env.
|
|
57
|
+
|
|
58
|
+
Operation Errors (non-retriable):
|
|
59
|
+
UPSERT_ERROR: PostgreSQL upsert operation failed.
|
|
60
|
+
Insert/update of contract record failed due to constraint
|
|
61
|
+
violation, invalid data, or schema mismatch.
|
|
62
|
+
|
|
63
|
+
TOPIC_UPDATE_ERROR: PostgreSQL topic update failed.
|
|
64
|
+
Failed to update topic routing table. Check JSONB array
|
|
65
|
+
operations and topic table schema.
|
|
66
|
+
|
|
67
|
+
MARK_STALE_ERROR: PostgreSQL mark stale operation failed.
|
|
68
|
+
Batch staleness marking failed. Check is_stale column
|
|
69
|
+
and last_seen_at timestamp handling.
|
|
70
|
+
|
|
71
|
+
HEARTBEAT_ERROR: PostgreSQL heartbeat update failed.
|
|
72
|
+
Heartbeat timestamp update failed. Verify contract_id
|
|
73
|
+
exists and last_seen_at column is writable.
|
|
74
|
+
|
|
75
|
+
DEACTIVATE_ERROR: PostgreSQL deactivation failed.
|
|
76
|
+
Soft delete (is_active=false) failed. Check contract_id
|
|
77
|
+
validity and is_active column constraints.
|
|
78
|
+
|
|
79
|
+
CLEANUP_ERROR: PostgreSQL topic cleanup failed.
|
|
80
|
+
Failed to remove contract from topic arrays. Check JSONB
|
|
81
|
+
array manipulation and referential integrity.
|
|
82
|
+
|
|
83
|
+
Unknown Errors (non-retriable):
|
|
84
|
+
UNKNOWN_ERROR: Unknown error during PostgreSQL operation.
|
|
85
|
+
Catch-all for unclassified PostgreSQL failures.
|
|
86
|
+
Check logs for underlying exception details.
|
|
87
|
+
"""
|
|
88
|
+
|
|
89
|
+
# Connection errors
|
|
90
|
+
CONNECTION_ERROR = "POSTGRES_CONNECTION_ERROR"
|
|
91
|
+
TIMEOUT_ERROR = "POSTGRES_TIMEOUT_ERROR"
|
|
92
|
+
AUTH_ERROR = "POSTGRES_AUTH_ERROR"
|
|
93
|
+
|
|
94
|
+
# Operation errors
|
|
95
|
+
UPSERT_ERROR = "POSTGRES_UPSERT_ERROR"
|
|
96
|
+
TOPIC_UPDATE_ERROR = "POSTGRES_TOPIC_UPDATE_ERROR"
|
|
97
|
+
MARK_STALE_ERROR = "POSTGRES_MARK_STALE_ERROR"
|
|
98
|
+
HEARTBEAT_ERROR = "POSTGRES_HEARTBEAT_ERROR"
|
|
99
|
+
DEACTIVATE_ERROR = "POSTGRES_DEACTIVATE_ERROR"
|
|
100
|
+
CLEANUP_ERROR = "POSTGRES_CLEANUP_ERROR"
|
|
101
|
+
|
|
102
|
+
# Unknown errors
|
|
103
|
+
UNKNOWN_ERROR = "POSTGRES_UNKNOWN_ERROR"
|
|
104
|
+
|
|
105
|
+
@property
|
|
106
|
+
def is_retriable(self) -> bool:
|
|
107
|
+
"""Check if this error is retriable.
|
|
108
|
+
|
|
109
|
+
Retriable errors indicate transient failures that may succeed
|
|
110
|
+
on retry, such as connection issues or timeouts. Non-retriable
|
|
111
|
+
errors indicate permanent failures requiring intervention.
|
|
112
|
+
|
|
113
|
+
Returns:
|
|
114
|
+
True if the error is retriable, False otherwise.
|
|
115
|
+
"""
|
|
116
|
+
return self in {
|
|
117
|
+
EnumPostgresErrorCode.CONNECTION_ERROR,
|
|
118
|
+
EnumPostgresErrorCode.TIMEOUT_ERROR,
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
@property
|
|
122
|
+
def is_connection_error(self) -> bool:
|
|
123
|
+
"""Check if this is a connection-level error.
|
|
124
|
+
|
|
125
|
+
Connection errors indicate infrastructure-level failures
|
|
126
|
+
rather than operation-specific issues.
|
|
127
|
+
|
|
128
|
+
Returns:
|
|
129
|
+
True if this is a connection-level error.
|
|
130
|
+
"""
|
|
131
|
+
return self in {
|
|
132
|
+
EnumPostgresErrorCode.CONNECTION_ERROR,
|
|
133
|
+
EnumPostgresErrorCode.TIMEOUT_ERROR,
|
|
134
|
+
EnumPostgresErrorCode.AUTH_ERROR,
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
@property
|
|
138
|
+
def is_operation_error(self) -> bool:
|
|
139
|
+
"""Check if this is an operation-specific error.
|
|
140
|
+
|
|
141
|
+
Operation errors indicate failures in specific database
|
|
142
|
+
operations rather than infrastructure issues.
|
|
143
|
+
|
|
144
|
+
Returns:
|
|
145
|
+
True if this is an operation-specific error.
|
|
146
|
+
"""
|
|
147
|
+
return self in {
|
|
148
|
+
EnumPostgresErrorCode.UPSERT_ERROR,
|
|
149
|
+
EnumPostgresErrorCode.TOPIC_UPDATE_ERROR,
|
|
150
|
+
EnumPostgresErrorCode.MARK_STALE_ERROR,
|
|
151
|
+
EnumPostgresErrorCode.HEARTBEAT_ERROR,
|
|
152
|
+
EnumPostgresErrorCode.DEACTIVATE_ERROR,
|
|
153
|
+
EnumPostgresErrorCode.CLEANUP_ERROR,
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
@property
|
|
157
|
+
def description(self) -> str:
|
|
158
|
+
"""Get human-readable description of the error code.
|
|
159
|
+
|
|
160
|
+
Returns:
|
|
161
|
+
Description string for the error code.
|
|
162
|
+
"""
|
|
163
|
+
descriptions = {
|
|
164
|
+
EnumPostgresErrorCode.CONNECTION_ERROR: (
|
|
165
|
+
"Connection to PostgreSQL server failed"
|
|
166
|
+
),
|
|
167
|
+
EnumPostgresErrorCode.TIMEOUT_ERROR: (
|
|
168
|
+
"PostgreSQL operation exceeded timeout"
|
|
169
|
+
),
|
|
170
|
+
EnumPostgresErrorCode.AUTH_ERROR: (
|
|
171
|
+
"Authentication with PostgreSQL server failed"
|
|
172
|
+
),
|
|
173
|
+
EnumPostgresErrorCode.UPSERT_ERROR: "PostgreSQL upsert operation failed",
|
|
174
|
+
EnumPostgresErrorCode.TOPIC_UPDATE_ERROR: "PostgreSQL topic update failed",
|
|
175
|
+
EnumPostgresErrorCode.MARK_STALE_ERROR: (
|
|
176
|
+
"PostgreSQL mark stale operation failed"
|
|
177
|
+
),
|
|
178
|
+
EnumPostgresErrorCode.HEARTBEAT_ERROR: "PostgreSQL heartbeat update failed",
|
|
179
|
+
EnumPostgresErrorCode.DEACTIVATE_ERROR: "PostgreSQL deactivation failed",
|
|
180
|
+
EnumPostgresErrorCode.CLEANUP_ERROR: "PostgreSQL topic cleanup failed",
|
|
181
|
+
EnumPostgresErrorCode.UNKNOWN_ERROR: (
|
|
182
|
+
"Unknown error during PostgreSQL operation"
|
|
183
|
+
),
|
|
184
|
+
}
|
|
185
|
+
return descriptions.get(self, "Unknown error")
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
__all__ = ["EnumPostgresErrorCode"]
|
|
@@ -50,6 +50,7 @@ import asyncpg
|
|
|
50
50
|
|
|
51
51
|
from omnibase_core.container import ModelONEXContainer
|
|
52
52
|
from omnibase_core.enums.enum_node_kind import EnumNodeKind
|
|
53
|
+
from omnibase_core.models.primitives.model_semver import ModelSemVer
|
|
53
54
|
from omnibase_infra.enums import EnumInfraTransportType
|
|
54
55
|
from omnibase_infra.errors import (
|
|
55
56
|
InfraConnectionError,
|
|
@@ -87,6 +88,9 @@ DEFAULT_POOL_SIZE = 10
|
|
|
87
88
|
DEFAULT_TIMEOUT_SECONDS = 30.0
|
|
88
89
|
|
|
89
90
|
# SQL statements
|
|
91
|
+
# NOTE: Database column is `registered_at` but model uses `created_at`. The column
|
|
92
|
+
# is aliased in queries for mapping. This aligns with the existing database schema
|
|
93
|
+
# on 192.168.86.200 which uses `registered_at` for the creation timestamp.
|
|
90
94
|
SQL_CREATE_TABLE = """
|
|
91
95
|
CREATE TABLE IF NOT EXISTS node_registrations (
|
|
92
96
|
node_id UUID PRIMARY KEY,
|
|
@@ -95,13 +99,13 @@ CREATE TABLE IF NOT EXISTS node_registrations (
|
|
|
95
99
|
capabilities JSONB NOT NULL DEFAULT '[]',
|
|
96
100
|
endpoints JSONB NOT NULL DEFAULT '{}',
|
|
97
101
|
metadata JSONB NOT NULL DEFAULT '{}',
|
|
98
|
-
|
|
102
|
+
registered_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
99
103
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
100
104
|
);
|
|
101
105
|
"""
|
|
102
106
|
|
|
103
107
|
SQL_UPSERT = """
|
|
104
|
-
INSERT INTO node_registrations (node_id, node_type, node_version, capabilities, endpoints, metadata,
|
|
108
|
+
INSERT INTO node_registrations (node_id, node_type, node_version, capabilities, endpoints, metadata, registered_at, updated_at)
|
|
105
109
|
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
|
|
106
110
|
ON CONFLICT (node_id) DO UPDATE SET
|
|
107
111
|
node_type = EXCLUDED.node_type,
|
|
@@ -114,7 +118,7 @@ RETURNING (xmax = 0) AS was_insert;
|
|
|
114
118
|
"""
|
|
115
119
|
|
|
116
120
|
SQL_QUERY_BASE = """
|
|
117
|
-
SELECT node_id, node_type, node_version, capabilities, endpoints, metadata, created_at, updated_at
|
|
121
|
+
SELECT node_id, node_type, node_version, capabilities, endpoints, metadata, registered_at AS created_at, updated_at
|
|
118
122
|
FROM node_registrations
|
|
119
123
|
"""
|
|
120
124
|
|
|
@@ -384,9 +388,9 @@ class HandlerRegistrationStoragePostgres(MixinAsyncCircuitBreaker):
|
|
|
384
388
|
result = await asyncio.wait_for(
|
|
385
389
|
conn.fetchrow(
|
|
386
390
|
SQL_UPSERT,
|
|
387
|
-
record.node_id,
|
|
391
|
+
str(record.node_id), # VARCHAR column requires string
|
|
388
392
|
record.node_type.value,
|
|
389
|
-
record.node_version,
|
|
393
|
+
str(record.node_version), # VARCHAR column requires string
|
|
390
394
|
capabilities_json,
|
|
391
395
|
endpoints_json,
|
|
392
396
|
metadata_json,
|
|
@@ -502,7 +506,7 @@ class HandlerRegistrationStoragePostgres(MixinAsyncCircuitBreaker):
|
|
|
502
506
|
# Filter by node_id if specified (exact match)
|
|
503
507
|
if query.node_id is not None:
|
|
504
508
|
conditions.append(f"node_id = ${param_idx}")
|
|
505
|
-
params.append(query.node_id)
|
|
509
|
+
params.append(str(query.node_id)) # VARCHAR column requires string
|
|
506
510
|
param_idx += 1
|
|
507
511
|
|
|
508
512
|
# Filter by node_type if specified
|
|
@@ -531,15 +535,15 @@ class HandlerRegistrationStoragePostgres(MixinAsyncCircuitBreaker):
|
|
|
531
535
|
count_params = params[:-2] # Exclude limit and offset
|
|
532
536
|
|
|
533
537
|
async with pool.acquire() as conn:
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
538
|
+
# NOTE: asyncpg connections don't support concurrent operations,
|
|
539
|
+
# so we run these queries sequentially instead of with asyncio.gather
|
|
540
|
+
rows = await asyncio.wait_for(
|
|
541
|
+
conn.fetch(sql_query, *params),
|
|
542
|
+
timeout=self._timeout_seconds,
|
|
543
|
+
)
|
|
544
|
+
count_result = await asyncio.wait_for(
|
|
545
|
+
conn.fetchval(count_query, *count_params),
|
|
546
|
+
timeout=self._timeout_seconds,
|
|
543
547
|
)
|
|
544
548
|
|
|
545
549
|
# Reset circuit breaker on success
|
|
@@ -555,11 +559,14 @@ class HandlerRegistrationStoragePostgres(MixinAsyncCircuitBreaker):
|
|
|
555
559
|
endpoints = json.loads(row["endpoints"]) if row["endpoints"] else {}
|
|
556
560
|
metadata = json.loads(row["metadata"]) if row["metadata"] else {}
|
|
557
561
|
|
|
562
|
+
# Convert database types to model types:
|
|
563
|
+
# - node_id: VARCHAR -> UUID
|
|
564
|
+
# - node_version: VARCHAR -> ModelSemVer
|
|
558
565
|
records.append(
|
|
559
566
|
ModelRegistrationRecord(
|
|
560
|
-
node_id=row["node_id"],
|
|
567
|
+
node_id=UUID(row["node_id"]),
|
|
561
568
|
node_type=EnumNodeKind(row["node_type"]),
|
|
562
|
-
node_version=row["node_version"],
|
|
569
|
+
node_version=ModelSemVer.parse(row["node_version"]),
|
|
563
570
|
capabilities=capabilities,
|
|
564
571
|
endpoints=endpoints,
|
|
565
572
|
metadata=metadata,
|
|
@@ -684,11 +691,11 @@ class HandlerRegistrationStoragePostgres(MixinAsyncCircuitBreaker):
|
|
|
684
691
|
result = await asyncio.wait_for(
|
|
685
692
|
conn.fetchval(
|
|
686
693
|
SQL_UPDATE,
|
|
687
|
-
node_id,
|
|
694
|
+
str(node_id), # VARCHAR column requires string
|
|
688
695
|
capabilities_json,
|
|
689
696
|
endpoints_json,
|
|
690
697
|
metadata_json,
|
|
691
|
-
node_version,
|
|
698
|
+
str(node_version) if node_version is not None else None,
|
|
692
699
|
),
|
|
693
700
|
timeout=self._timeout_seconds,
|
|
694
701
|
)
|
|
@@ -792,7 +799,9 @@ class HandlerRegistrationStoragePostgres(MixinAsyncCircuitBreaker):
|
|
|
792
799
|
|
|
793
800
|
async with pool.acquire() as conn:
|
|
794
801
|
result = await asyncio.wait_for(
|
|
795
|
-
conn.fetchval(
|
|
802
|
+
conn.fetchval(
|
|
803
|
+
SQL_DELETE, str(node_id)
|
|
804
|
+
), # VARCHAR column requires string
|
|
796
805
|
timeout=self._timeout_seconds,
|
|
797
806
|
)
|
|
798
807
|
|
|
@@ -7,6 +7,7 @@ Reusable mixin classes providing:
|
|
|
7
7
|
- Infrastructure error integration
|
|
8
8
|
- Correlation ID propagation
|
|
9
9
|
- Configurable behavior
|
|
10
|
+
- PostgreSQL error response building for effect persistence
|
|
10
11
|
|
|
11
12
|
Exports (in __all__):
|
|
12
13
|
Mixins:
|
|
@@ -14,8 +15,13 @@ Exports (in __all__):
|
|
|
14
15
|
- MixinDictLikeAccessors: Dictionary-style access helpers
|
|
15
16
|
- MixinEnvelopeExtraction: Event envelope extraction utilities
|
|
16
17
|
- MixinNodeIntrospection: Node capability introspection
|
|
18
|
+
- MixinPostgresErrorResponse: PostgreSQL exception handling for persistence
|
|
19
|
+
- MixinPostgresOpExecutor: PostgreSQL operation execution with error handling
|
|
17
20
|
- MixinRetryExecution: Retry logic with exponential backoff
|
|
18
21
|
|
|
22
|
+
Dataclasses:
|
|
23
|
+
- PostgresErrorContext: Context for PostgreSQL error handling
|
|
24
|
+
|
|
19
25
|
Protocols (co-located with their tightly-coupled mixins):
|
|
20
26
|
- ProtocolCircuitBreakerAware: Interface for circuit breaker capability.
|
|
21
27
|
Co-located here because it is tightly coupled to MixinAsyncCircuitBreaker.
|
|
@@ -48,6 +54,11 @@ from omnibase_infra.mixins.mixin_async_circuit_breaker import MixinAsyncCircuitB
|
|
|
48
54
|
from omnibase_infra.mixins.mixin_dict_like_accessors import MixinDictLikeAccessors
|
|
49
55
|
from omnibase_infra.mixins.mixin_envelope_extraction import MixinEnvelopeExtraction
|
|
50
56
|
from omnibase_infra.mixins.mixin_node_introspection import MixinNodeIntrospection
|
|
57
|
+
from omnibase_infra.mixins.mixin_postgres_error_response import (
|
|
58
|
+
MixinPostgresErrorResponse,
|
|
59
|
+
PostgresErrorContext,
|
|
60
|
+
)
|
|
61
|
+
from omnibase_infra.mixins.mixin_postgres_op_executor import MixinPostgresOpExecutor
|
|
51
62
|
from omnibase_infra.mixins.mixin_retry_execution import MixinRetryExecution
|
|
52
63
|
from omnibase_infra.mixins.protocol_circuit_breaker_aware import (
|
|
53
64
|
ProtocolCircuitBreakerAware,
|
|
@@ -63,6 +74,9 @@ __all__: list[str] = [
|
|
|
63
74
|
"MixinDictLikeAccessors",
|
|
64
75
|
"MixinEnvelopeExtraction",
|
|
65
76
|
"MixinNodeIntrospection",
|
|
77
|
+
"MixinPostgresErrorResponse",
|
|
78
|
+
"MixinPostgresOpExecutor",
|
|
79
|
+
"PostgresErrorContext",
|
|
66
80
|
"MixinRetryExecution",
|
|
67
81
|
"ModelCircuitBreakerConfig",
|
|
68
82
|
"ModelRetryErrorClassification",
|