omnibase_infra 0.2.8__py3-none-any.whl → 0.3.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 +4 -0
- omnibase_infra/enums/enum_declarative_node_violation.py +102 -0
- omnibase_infra/errors/__init__.py +18 -0
- omnibase_infra/errors/repository/__init__.py +78 -0
- omnibase_infra/errors/repository/errors_repository.py +424 -0
- omnibase_infra/event_bus/adapters/__init__.py +31 -0
- omnibase_infra/event_bus/adapters/adapter_protocol_event_publisher_kafka.py +517 -0
- omnibase_infra/mixins/mixin_async_circuit_breaker.py +113 -1
- omnibase_infra/models/__init__.py +9 -0
- omnibase_infra/models/event_bus/__init__.py +22 -0
- omnibase_infra/models/event_bus/model_consumer_retry_config.py +367 -0
- omnibase_infra/models/event_bus/model_dlq_config.py +177 -0
- omnibase_infra/models/event_bus/model_idempotency_config.py +131 -0
- omnibase_infra/models/event_bus/model_offset_policy_config.py +107 -0
- omnibase_infra/models/resilience/model_circuit_breaker_config.py +15 -0
- omnibase_infra/models/validation/__init__.py +8 -0
- omnibase_infra/models/validation/model_declarative_node_validation_result.py +139 -0
- omnibase_infra/models/validation/model_declarative_node_violation.py +169 -0
- omnibase_infra/nodes/architecture_validator/__init__.py +28 -7
- omnibase_infra/nodes/architecture_validator/constants.py +36 -0
- omnibase_infra/nodes/architecture_validator/handlers/__init__.py +28 -0
- omnibase_infra/nodes/architecture_validator/handlers/contract.yaml +120 -0
- omnibase_infra/nodes/architecture_validator/handlers/handler_architecture_validation.py +359 -0
- omnibase_infra/nodes/architecture_validator/node.py +1 -0
- omnibase_infra/nodes/architecture_validator/node_architecture_validator.py +48 -336
- omnibase_infra/nodes/contract_registry_reducer/reducer.py +12 -2
- omnibase_infra/nodes/node_ledger_projection_compute/__init__.py +16 -2
- omnibase_infra/nodes/node_ledger_projection_compute/contract.yaml +14 -4
- omnibase_infra/nodes/node_ledger_projection_compute/handlers/__init__.py +18 -0
- omnibase_infra/nodes/node_ledger_projection_compute/handlers/contract.yaml +53 -0
- omnibase_infra/nodes/node_ledger_projection_compute/handlers/handler_ledger_projection.py +354 -0
- omnibase_infra/nodes/node_ledger_projection_compute/node.py +20 -256
- omnibase_infra/nodes/node_registry_effect/node.py +20 -73
- omnibase_infra/protocols/protocol_dispatch_engine.py +90 -0
- omnibase_infra/runtime/__init__.py +11 -0
- omnibase_infra/runtime/baseline_subscriptions.py +150 -0
- omnibase_infra/runtime/db/__init__.py +73 -0
- omnibase_infra/runtime/db/models/__init__.py +41 -0
- omnibase_infra/runtime/db/models/model_repository_runtime_config.py +211 -0
- omnibase_infra/runtime/db/postgres_repository_runtime.py +545 -0
- omnibase_infra/runtime/event_bus_subcontract_wiring.py +455 -24
- omnibase_infra/runtime/kafka_contract_source.py +13 -5
- omnibase_infra/runtime/service_message_dispatch_engine.py +112 -0
- omnibase_infra/runtime/service_runtime_host_process.py +6 -11
- omnibase_infra/services/__init__.py +36 -0
- omnibase_infra/services/contract_publisher/__init__.py +95 -0
- omnibase_infra/services/contract_publisher/config.py +199 -0
- omnibase_infra/services/contract_publisher/errors.py +243 -0
- omnibase_infra/services/contract_publisher/models/__init__.py +28 -0
- omnibase_infra/services/contract_publisher/models/model_contract_error.py +67 -0
- omnibase_infra/services/contract_publisher/models/model_infra_error.py +62 -0
- omnibase_infra/services/contract_publisher/models/model_publish_result.py +112 -0
- omnibase_infra/services/contract_publisher/models/model_publish_stats.py +79 -0
- omnibase_infra/services/contract_publisher/service.py +617 -0
- omnibase_infra/services/contract_publisher/sources/__init__.py +52 -0
- omnibase_infra/services/contract_publisher/sources/model_discovered.py +155 -0
- omnibase_infra/services/contract_publisher/sources/protocol.py +101 -0
- omnibase_infra/services/contract_publisher/sources/source_composite.py +309 -0
- omnibase_infra/services/contract_publisher/sources/source_filesystem.py +174 -0
- omnibase_infra/services/contract_publisher/sources/source_package.py +221 -0
- omnibase_infra/services/observability/__init__.py +40 -0
- omnibase_infra/services/observability/agent_actions/__init__.py +64 -0
- omnibase_infra/services/observability/agent_actions/config.py +209 -0
- omnibase_infra/services/observability/agent_actions/consumer.py +1320 -0
- omnibase_infra/services/observability/agent_actions/models/__init__.py +87 -0
- omnibase_infra/services/observability/agent_actions/models/model_agent_action.py +142 -0
- omnibase_infra/services/observability/agent_actions/models/model_detection_failure.py +125 -0
- omnibase_infra/services/observability/agent_actions/models/model_envelope.py +85 -0
- omnibase_infra/services/observability/agent_actions/models/model_execution_log.py +159 -0
- omnibase_infra/services/observability/agent_actions/models/model_performance_metric.py +130 -0
- omnibase_infra/services/observability/agent_actions/models/model_routing_decision.py +138 -0
- omnibase_infra/services/observability/agent_actions/models/model_transformation_event.py +124 -0
- omnibase_infra/services/observability/agent_actions/tests/__init__.py +20 -0
- omnibase_infra/services/observability/agent_actions/tests/test_consumer.py +1154 -0
- omnibase_infra/services/observability/agent_actions/tests/test_models.py +645 -0
- omnibase_infra/services/observability/agent_actions/tests/test_writer.py +709 -0
- omnibase_infra/services/observability/agent_actions/writer_postgres.py +926 -0
- omnibase_infra/validation/__init__.py +12 -0
- omnibase_infra/validation/contracts/declarative_node.validation.yaml +143 -0
- omnibase_infra/validation/infra_validators.py +4 -1
- omnibase_infra/validation/validation_exemptions.yaml +111 -0
- omnibase_infra/validation/validator_declarative_node.py +850 -0
- {omnibase_infra-0.2.8.dist-info → omnibase_infra-0.3.0.dist-info}/METADATA +2 -2
- {omnibase_infra-0.2.8.dist-info → omnibase_infra-0.3.0.dist-info}/RECORD +88 -30
- {omnibase_infra-0.2.8.dist-info → omnibase_infra-0.3.0.dist-info}/WHEEL +0 -0
- {omnibase_infra-0.2.8.dist-info → omnibase_infra-0.3.0.dist-info}/entry_points.txt +0 -0
- {omnibase_infra-0.2.8.dist-info → omnibase_infra-0.3.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -21,9 +21,12 @@ Event Topics (Platform Reserved):
|
|
|
21
21
|
- Registration: {env}.{TOPIC_SUFFIX_CONTRACT_REGISTERED}
|
|
22
22
|
- Deregistration: {env}.{TOPIC_SUFFIX_CONTRACT_DEREGISTERED}
|
|
23
23
|
|
|
24
|
-
Topic suffixes are imported from omnibase_core.constants
|
|
24
|
+
Topic suffixes are imported from omnibase_core.constants. For runtime subscription
|
|
25
|
+
wiring, use baseline_subscriptions.BASELINE_CONTRACT_TOPICS which aggregates the
|
|
26
|
+
platform-reserved contract topics.
|
|
25
27
|
|
|
26
28
|
See Also:
|
|
29
|
+
- baseline_subscriptions: Baseline topic assembly for runtime wiring
|
|
27
30
|
- HandlerContractSource: Filesystem-based discovery
|
|
28
31
|
- RegistryContractSource: Consul KV-based discovery
|
|
29
32
|
- ProtocolContractSource: Protocol definition
|
|
@@ -76,10 +79,6 @@ from uuid import UUID, uuid4
|
|
|
76
79
|
import yaml
|
|
77
80
|
from pydantic import ValidationError
|
|
78
81
|
|
|
79
|
-
from omnibase_core.constants import (
|
|
80
|
-
TOPIC_SUFFIX_CONTRACT_DEREGISTERED,
|
|
81
|
-
TOPIC_SUFFIX_CONTRACT_REGISTERED,
|
|
82
|
-
)
|
|
83
82
|
from omnibase_core.models.contracts.model_handler_contract import ModelHandlerContract
|
|
84
83
|
from omnibase_core.models.errors import ModelOnexError
|
|
85
84
|
from omnibase_core.models.events import (
|
|
@@ -94,6 +93,12 @@ from omnibase_infra.models.handlers import (
|
|
|
94
93
|
ModelHandlerDescriptor,
|
|
95
94
|
ModelHandlerIdentifier,
|
|
96
95
|
)
|
|
96
|
+
from omnibase_infra.runtime.baseline_subscriptions import (
|
|
97
|
+
BASELINE_CONTRACT_TOPICS,
|
|
98
|
+
BASELINE_PLATFORM_TOPICS,
|
|
99
|
+
TOPIC_SUFFIX_CONTRACT_DEREGISTERED,
|
|
100
|
+
TOPIC_SUFFIX_CONTRACT_REGISTERED,
|
|
101
|
+
)
|
|
97
102
|
from omnibase_infra.runtime.protocol_contract_source import ProtocolContractSource
|
|
98
103
|
|
|
99
104
|
logger = logging.getLogger(__name__)
|
|
@@ -979,6 +984,9 @@ __all__ = [
|
|
|
979
984
|
# Re-exported from omnibase_core for convenience
|
|
980
985
|
"ModelContractDeregisteredEvent",
|
|
981
986
|
"ModelContractRegisteredEvent",
|
|
987
|
+
# Re-exported from baseline_subscriptions for runtime wiring (OMN-1696)
|
|
988
|
+
"BASELINE_CONTRACT_TOPICS",
|
|
989
|
+
"BASELINE_PLATFORM_TOPICS",
|
|
982
990
|
"TOPIC_SUFFIX_CONTRACT_DEREGISTERED",
|
|
983
991
|
"TOPIC_SUFFIX_CONTRACT_REGISTERED",
|
|
984
992
|
]
|
|
@@ -1420,6 +1420,118 @@ class MessageDispatchEngine:
|
|
|
1420
1420
|
output_events=[],
|
|
1421
1421
|
)
|
|
1422
1422
|
|
|
1423
|
+
async def dispatch_with_transaction(
|
|
1424
|
+
self,
|
|
1425
|
+
*,
|
|
1426
|
+
topic: str,
|
|
1427
|
+
envelope: ModelEventEnvelope[object],
|
|
1428
|
+
tx: object,
|
|
1429
|
+
) -> ModelDispatchResult:
|
|
1430
|
+
"""Dispatch an event envelope with database transaction context.
|
|
1431
|
+
|
|
1432
|
+
This method enables transaction-scoped dispatch for correct idempotency
|
|
1433
|
+
semantics. The transaction parameter allows handlers to participate in
|
|
1434
|
+
the same database transaction as the idempotency insert, ensuring
|
|
1435
|
+
exactly-once processing.
|
|
1436
|
+
|
|
1437
|
+
Current Implementation:
|
|
1438
|
+
This initial implementation delegates to the standard ``dispatch()``
|
|
1439
|
+
method. The ``tx`` parameter is stored in the dispatch context for
|
|
1440
|
+
handlers that need transactional access. Future iterations may pass
|
|
1441
|
+
``tx`` directly to handlers via a context object.
|
|
1442
|
+
|
|
1443
|
+
Design Decision:
|
|
1444
|
+
The ``tx`` parameter is typed as ``object`` rather than a specific
|
|
1445
|
+
database type (e.g., ``asyncpg.Connection``) to avoid leaking
|
|
1446
|
+
infrastructure types into the protocol. Handlers that need the
|
|
1447
|
+
transaction should type-narrow based on their database backend.
|
|
1448
|
+
|
|
1449
|
+
Thread Safety:
|
|
1450
|
+
This method is safe for concurrent calls from multiple coroutines.
|
|
1451
|
+
However, the transaction context (``tx``) is typically connection-
|
|
1452
|
+
scoped and should not be shared across coroutines.
|
|
1453
|
+
|
|
1454
|
+
Args:
|
|
1455
|
+
topic: The full topic name from which the envelope was consumed.
|
|
1456
|
+
Used for routing and logging context.
|
|
1457
|
+
Example: "dev.onex.evt.node.introspected.v1"
|
|
1458
|
+
envelope: The deserialized event envelope containing the payload.
|
|
1459
|
+
The payload type varies by topic/event type.
|
|
1460
|
+
tx: Database transaction/connection context. Typed as ``object``
|
|
1461
|
+
to avoid infrastructure type leakage; handlers should type-
|
|
1462
|
+
narrow based on their database backend (e.g.,
|
|
1463
|
+
``asyncpg.Connection``, ``aiosqlite.Connection``).
|
|
1464
|
+
|
|
1465
|
+
Returns:
|
|
1466
|
+
ModelDispatchResult with dispatch status, metrics, and dispatcher outputs.
|
|
1467
|
+
|
|
1468
|
+
Raises:
|
|
1469
|
+
ModelOnexError: If engine is not frozen (INVALID_STATE)
|
|
1470
|
+
ModelOnexError: If topic is empty (INVALID_PARAMETER)
|
|
1471
|
+
ModelOnexError: If envelope is None (INVALID_PARAMETER)
|
|
1472
|
+
|
|
1473
|
+
Example:
|
|
1474
|
+
.. code-block:: python
|
|
1475
|
+
|
|
1476
|
+
# Idempotency consumer with transaction context
|
|
1477
|
+
async with pool.acquire() as conn:
|
|
1478
|
+
async with conn.transaction():
|
|
1479
|
+
# Insert idempotency record and dispatch in same transaction
|
|
1480
|
+
await insert_idempotency_record(conn, message_id)
|
|
1481
|
+
result = await engine.dispatch_with_transaction(
|
|
1482
|
+
topic=topic,
|
|
1483
|
+
envelope=envelope,
|
|
1484
|
+
tx=conn,
|
|
1485
|
+
)
|
|
1486
|
+
# Both committed atomically
|
|
1487
|
+
|
|
1488
|
+
Related:
|
|
1489
|
+
- OMN-1740: Transaction-scoped dispatch for idempotency
|
|
1490
|
+
- dispatch(): Non-transactional dispatch method
|
|
1491
|
+
|
|
1492
|
+
.. versionadded:: 0.2.9
|
|
1493
|
+
"""
|
|
1494
|
+
# Enforce freeze contract (same as dispatch())
|
|
1495
|
+
if not self._frozen:
|
|
1496
|
+
raise ModelOnexError(
|
|
1497
|
+
message="dispatch_with_transaction() called before freeze(). "
|
|
1498
|
+
"Registration MUST complete and freeze() MUST be called before dispatch. "
|
|
1499
|
+
"This is required for thread safety.",
|
|
1500
|
+
error_code=EnumCoreErrorCode.INVALID_STATE,
|
|
1501
|
+
)
|
|
1502
|
+
|
|
1503
|
+
# Validate inputs (same as dispatch())
|
|
1504
|
+
if not topic or not topic.strip():
|
|
1505
|
+
raise ModelOnexError(
|
|
1506
|
+
message="Topic cannot be empty or whitespace.",
|
|
1507
|
+
error_code=EnumCoreErrorCode.INVALID_PARAMETER,
|
|
1508
|
+
)
|
|
1509
|
+
|
|
1510
|
+
if envelope is None:
|
|
1511
|
+
raise ModelOnexError(
|
|
1512
|
+
message="Cannot dispatch None envelope. ModelEventEnvelope is required.",
|
|
1513
|
+
error_code=EnumCoreErrorCode.INVALID_PARAMETER,
|
|
1514
|
+
)
|
|
1515
|
+
|
|
1516
|
+
# Log transaction context at DEBUG level for traceability
|
|
1517
|
+
correlation_id = envelope.correlation_id or uuid4()
|
|
1518
|
+
self._logger.debug(
|
|
1519
|
+
"dispatch_with_transaction called with tx context (tx_type=%s)",
|
|
1520
|
+
type(tx).__name__,
|
|
1521
|
+
extra=self._build_log_context(
|
|
1522
|
+
topic=topic,
|
|
1523
|
+
correlation_id=correlation_id,
|
|
1524
|
+
trace_id=envelope.trace_id,
|
|
1525
|
+
),
|
|
1526
|
+
)
|
|
1527
|
+
|
|
1528
|
+
# Current implementation: delegate to standard dispatch()
|
|
1529
|
+
# The tx parameter is available for future handler context injection
|
|
1530
|
+
# TODO(OMN-1740): Pass tx to handlers via dispatch context when needed
|
|
1531
|
+
_ = tx # Explicitly acknowledge tx parameter for future use
|
|
1532
|
+
|
|
1533
|
+
return await self.dispatch(topic=topic, envelope=envelope)
|
|
1534
|
+
|
|
1423
1535
|
def _find_matching_dispatchers(
|
|
1424
1536
|
self,
|
|
1425
1537
|
topic: str,
|
|
@@ -2730,18 +2730,12 @@ class RuntimeHostProcess:
|
|
|
2730
2730
|
# Import architecture validator components
|
|
2731
2731
|
from omnibase_infra.errors import ArchitectureViolationError
|
|
2732
2732
|
from omnibase_infra.nodes.architecture_validator import (
|
|
2733
|
+
HandlerArchitectureValidation,
|
|
2733
2734
|
ModelArchitectureValidationRequest,
|
|
2734
|
-
NodeArchitectureValidatorCompute,
|
|
2735
2735
|
)
|
|
2736
2736
|
|
|
2737
|
-
# Create
|
|
2738
|
-
|
|
2739
|
-
|
|
2740
|
-
# Instantiate validator with rules
|
|
2741
|
-
validator = NodeArchitectureValidatorCompute(
|
|
2742
|
-
container=container,
|
|
2743
|
-
rules=self._architecture_rules,
|
|
2744
|
-
)
|
|
2737
|
+
# Create handler with rules (declarative pattern - handler owns the logic)
|
|
2738
|
+
handler = HandlerArchitectureValidation(rules=self._architecture_rules)
|
|
2745
2739
|
|
|
2746
2740
|
# Build validation request
|
|
2747
2741
|
# Note: At this point, handlers haven't been instantiated yet (that happens
|
|
@@ -2769,8 +2763,8 @@ class RuntimeHostProcess:
|
|
|
2769
2763
|
handlers=tuple(handler_classes),
|
|
2770
2764
|
)
|
|
2771
2765
|
|
|
2772
|
-
# Execute validation
|
|
2773
|
-
result =
|
|
2766
|
+
# Execute validation via handler
|
|
2767
|
+
result = handler.validate_architecture(request)
|
|
2774
2768
|
|
|
2775
2769
|
# Separate blocking and non-blocking violations
|
|
2776
2770
|
blocking_violations = tuple(v for v in result.violations if v.blocks_startup())
|
|
@@ -2924,6 +2918,7 @@ class RuntimeHostProcess:
|
|
|
2924
2918
|
event_bus=cast("ProtocolEventBusSubscriber", self._event_bus),
|
|
2925
2919
|
dispatch_engine=self._dispatch_engine,
|
|
2926
2920
|
environment=environment,
|
|
2921
|
+
node_name="runtime-host",
|
|
2927
2922
|
)
|
|
2928
2923
|
|
|
2929
2924
|
# Wire subscriptions for each handler with a contract
|
|
@@ -13,6 +13,7 @@ Exports:
|
|
|
13
13
|
ModelTimeoutEmissionResult: Result model for timeout emission processing
|
|
14
14
|
ModelTimeoutQueryResult: Result model for timeout queries
|
|
15
15
|
ServiceCapabilityQuery: Query nodes by capability, not by name
|
|
16
|
+
ServiceContractPublisher: Publish contracts to Kafka for dynamic discovery
|
|
16
17
|
ServiceNodeSelector: Select nodes from candidates using various strategies
|
|
17
18
|
ServiceSnapshot: Generic snapshot service for point-in-time state capture
|
|
18
19
|
ServiceTimeoutEmitter: Emitter for timeout events with markers
|
|
@@ -24,6 +25,25 @@ Exports:
|
|
|
24
25
|
"""
|
|
25
26
|
|
|
26
27
|
from omnibase_infra.enums import EnumSelectionStrategy
|
|
28
|
+
|
|
29
|
+
# Contract publisher service (OMN-1752)
|
|
30
|
+
from omnibase_infra.services.contract_publisher import (
|
|
31
|
+
ContractPublisherError,
|
|
32
|
+
ContractPublishingInfraError,
|
|
33
|
+
ContractSourceNotConfiguredError,
|
|
34
|
+
ModelContractError,
|
|
35
|
+
ModelContractPublisherConfig,
|
|
36
|
+
ModelDiscoveredContract,
|
|
37
|
+
ModelInfraError,
|
|
38
|
+
ModelPublishResult,
|
|
39
|
+
ModelPublishStats,
|
|
40
|
+
NoContractsFoundError,
|
|
41
|
+
ProtocolContractPublisherSource,
|
|
42
|
+
ServiceContractPublisher,
|
|
43
|
+
SourceContractComposite,
|
|
44
|
+
SourceContractFilesystem,
|
|
45
|
+
SourceContractPackage,
|
|
46
|
+
)
|
|
27
47
|
from omnibase_infra.services.corpus_capture import CorpusCapture
|
|
28
48
|
from omnibase_infra.services.service_capability_query import ServiceCapabilityQuery
|
|
29
49
|
from omnibase_infra.services.service_node_selector import (
|
|
@@ -86,4 +106,20 @@ __all__ = [
|
|
|
86
106
|
"SessionEventConsumer",
|
|
87
107
|
"SessionSnapshotStore",
|
|
88
108
|
"SessionStoreNotInitializedError",
|
|
109
|
+
# Contract publisher service (OMN-1752)
|
|
110
|
+
"ContractPublisherError",
|
|
111
|
+
"ContractPublishingInfraError",
|
|
112
|
+
"ContractSourceNotConfiguredError",
|
|
113
|
+
"ModelContractError",
|
|
114
|
+
"ModelContractPublisherConfig",
|
|
115
|
+
"ModelDiscoveredContract",
|
|
116
|
+
"ModelInfraError",
|
|
117
|
+
"ModelPublishResult",
|
|
118
|
+
"ModelPublishStats",
|
|
119
|
+
"NoContractsFoundError",
|
|
120
|
+
"ProtocolContractPublisherSource",
|
|
121
|
+
"ServiceContractPublisher",
|
|
122
|
+
"SourceContractComposite",
|
|
123
|
+
"SourceContractFilesystem",
|
|
124
|
+
"SourceContractPackage",
|
|
89
125
|
]
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
# Copyright (c) 2025 OmniNode Team
|
|
3
|
+
"""Contract Publisher Service Module.
|
|
4
|
+
|
|
5
|
+
Provides infrastructure for bulk contract discovery and publishing to Kafka.
|
|
6
|
+
This service discovers contracts from configured sources (filesystem, package)
|
|
7
|
+
and publishes them to the contract registration topic for dynamic discovery.
|
|
8
|
+
|
|
9
|
+
Moved from omniclaude as part of OMN-1752 (ARCH-002 compliance).
|
|
10
|
+
|
|
11
|
+
Design Principle:
|
|
12
|
+
Infra standardizes the publishing *engine*; apps provide *source configuration*.
|
|
13
|
+
|
|
14
|
+
Flow:
|
|
15
|
+
Source → Validate → Normalize → Publish → Report
|
|
16
|
+
- Sources provide origin (filesystem, package)
|
|
17
|
+
- Event bus provides distribution (broadcast plane)
|
|
18
|
+
|
|
19
|
+
Example:
|
|
20
|
+
>>> from omnibase_infra.services.contract_publisher import (
|
|
21
|
+
... ServiceContractPublisher,
|
|
22
|
+
... ModelContractPublisherConfig,
|
|
23
|
+
... )
|
|
24
|
+
>>> config = ModelContractPublisherConfig(
|
|
25
|
+
... mode="filesystem",
|
|
26
|
+
... filesystem_root=Path("/app/contracts/handlers"),
|
|
27
|
+
... )
|
|
28
|
+
>>> publisher = await ServiceContractPublisher.from_container(container, config)
|
|
29
|
+
>>> result = await publisher.publish_all()
|
|
30
|
+
>>> if result:
|
|
31
|
+
... print(f"Published {len(result.published)} contracts")
|
|
32
|
+
... else:
|
|
33
|
+
... print(f"No contracts published, {len(result.contract_errors)} errors")
|
|
34
|
+
|
|
35
|
+
.. versionadded:: 0.3.0
|
|
36
|
+
Created as part of OMN-1752 (ContractPublisher extraction).
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
# Config
|
|
40
|
+
from omnibase_infra.services.contract_publisher.config import (
|
|
41
|
+
ModelContractPublisherConfig,
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
# Errors
|
|
45
|
+
from omnibase_infra.services.contract_publisher.errors import (
|
|
46
|
+
ContractPublisherError,
|
|
47
|
+
ContractPublishingInfraError,
|
|
48
|
+
ContractSourceNotConfiguredError,
|
|
49
|
+
NoContractsFoundError,
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
# Result models
|
|
53
|
+
from omnibase_infra.services.contract_publisher.models import (
|
|
54
|
+
ModelContractError,
|
|
55
|
+
ModelInfraError,
|
|
56
|
+
ModelPublishResult,
|
|
57
|
+
ModelPublishStats,
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
# Service
|
|
61
|
+
from omnibase_infra.services.contract_publisher.service import (
|
|
62
|
+
ServiceContractPublisher,
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
# Sources
|
|
66
|
+
from omnibase_infra.services.contract_publisher.sources import (
|
|
67
|
+
ModelDiscoveredContract,
|
|
68
|
+
ProtocolContractPublisherSource,
|
|
69
|
+
SourceContractComposite,
|
|
70
|
+
SourceContractFilesystem,
|
|
71
|
+
SourceContractPackage,
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
__all__ = [
|
|
75
|
+
# Service
|
|
76
|
+
"ServiceContractPublisher",
|
|
77
|
+
# Config
|
|
78
|
+
"ModelContractPublisherConfig",
|
|
79
|
+
# Result models
|
|
80
|
+
"ModelPublishResult",
|
|
81
|
+
"ModelPublishStats",
|
|
82
|
+
"ModelContractError",
|
|
83
|
+
"ModelInfraError",
|
|
84
|
+
# Errors
|
|
85
|
+
"ContractPublisherError",
|
|
86
|
+
"ContractSourceNotConfiguredError",
|
|
87
|
+
"ContractPublishingInfraError",
|
|
88
|
+
"NoContractsFoundError",
|
|
89
|
+
# Sources
|
|
90
|
+
"ProtocolContractPublisherSource",
|
|
91
|
+
"ModelDiscoveredContract",
|
|
92
|
+
"SourceContractFilesystem",
|
|
93
|
+
"SourceContractPackage",
|
|
94
|
+
"SourceContractComposite",
|
|
95
|
+
]
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
# Copyright (c) 2025 OmniNode Team
|
|
3
|
+
"""Contract Publisher Configuration Model.
|
|
4
|
+
|
|
5
|
+
This module defines the configuration model for contract publishing operations.
|
|
6
|
+
Configuration is validated at construction time using Pydantic model validators.
|
|
7
|
+
|
|
8
|
+
Configuration Options:
|
|
9
|
+
mode: Publishing source mode (filesystem, package, composite)
|
|
10
|
+
filesystem_root: Root directory for filesystem mode
|
|
11
|
+
package_module: Module name for package mode
|
|
12
|
+
fail_fast: Whether to raise on infrastructure errors
|
|
13
|
+
allow_zero_contracts: Whether to allow empty publish results
|
|
14
|
+
environment: Environment prefix for Kafka topics
|
|
15
|
+
|
|
16
|
+
Environment Resolution:
|
|
17
|
+
The environment is resolved with precedence:
|
|
18
|
+
1. config.environment (if provided)
|
|
19
|
+
2. ONEX_ENV environment variable
|
|
20
|
+
3. Default "dev"
|
|
21
|
+
|
|
22
|
+
Related:
|
|
23
|
+
- OMN-1752: Extract ContractPublisher to omnibase_infra
|
|
24
|
+
- ARCH-002: Runtime owns all Kafka plumbing
|
|
25
|
+
|
|
26
|
+
.. versionadded:: 0.3.0
|
|
27
|
+
Created as part of OMN-1752 (ContractPublisher extraction).
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
from __future__ import annotations
|
|
31
|
+
|
|
32
|
+
import os
|
|
33
|
+
from pathlib import Path
|
|
34
|
+
from typing import Literal, Self
|
|
35
|
+
|
|
36
|
+
from pydantic import BaseModel, ConfigDict, Field, model_validator
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class ModelContractPublisherConfig(BaseModel):
|
|
40
|
+
"""Configuration for contract publishing.
|
|
41
|
+
|
|
42
|
+
Defines the source mode and configuration for discovering and
|
|
43
|
+
publishing contracts to Kafka.
|
|
44
|
+
|
|
45
|
+
Source Modes:
|
|
46
|
+
filesystem: Discover contracts from a directory tree
|
|
47
|
+
package: Discover contracts from installed package resources
|
|
48
|
+
composite: Merge both sources with conflict detection
|
|
49
|
+
|
|
50
|
+
Configuration Rules (enforced by validator):
|
|
51
|
+
- filesystem mode requires filesystem_root
|
|
52
|
+
- package mode requires package_module
|
|
53
|
+
- composite mode requires at least one of filesystem_root or package_module
|
|
54
|
+
|
|
55
|
+
Attributes:
|
|
56
|
+
mode: Publishing source mode
|
|
57
|
+
filesystem_root: Root directory for filesystem discovery
|
|
58
|
+
package_module: Module name for package resource discovery
|
|
59
|
+
fail_fast: If True, raise immediately on infrastructure errors
|
|
60
|
+
allow_zero_contracts: If True, allow empty publish results
|
|
61
|
+
environment: Environment prefix for topics (defaults via resolve_environment)
|
|
62
|
+
|
|
63
|
+
Example:
|
|
64
|
+
>>> config = ModelContractPublisherConfig(
|
|
65
|
+
... mode="filesystem",
|
|
66
|
+
... filesystem_root=Path("/app/contracts/handlers"),
|
|
67
|
+
... fail_fast=True,
|
|
68
|
+
... allow_zero_contracts=False,
|
|
69
|
+
... )
|
|
70
|
+
>>> env = config.resolve_environment()
|
|
71
|
+
>>> print(f"Publishing to {env}.onex.evt.contract-registered.v1")
|
|
72
|
+
|
|
73
|
+
.. versionadded:: 0.3.0
|
|
74
|
+
"""
|
|
75
|
+
|
|
76
|
+
model_config = ConfigDict(frozen=True, extra="forbid")
|
|
77
|
+
|
|
78
|
+
mode: Literal["filesystem", "package", "composite"] = Field(
|
|
79
|
+
description="Publishing source mode"
|
|
80
|
+
)
|
|
81
|
+
filesystem_root: Path | None = Field(
|
|
82
|
+
default=None,
|
|
83
|
+
description="Root directory for filesystem discovery",
|
|
84
|
+
)
|
|
85
|
+
package_module: str | None = Field(
|
|
86
|
+
default=None,
|
|
87
|
+
description="Module name for package resource discovery (e.g., 'myapp.contracts')",
|
|
88
|
+
)
|
|
89
|
+
fail_fast: bool = Field(
|
|
90
|
+
default=True,
|
|
91
|
+
description="If True, raise immediately on infrastructure errors",
|
|
92
|
+
)
|
|
93
|
+
allow_zero_contracts: bool = Field(
|
|
94
|
+
default=False,
|
|
95
|
+
description="If True, allow empty publish results without raising",
|
|
96
|
+
)
|
|
97
|
+
environment: str | None = Field(
|
|
98
|
+
default=None,
|
|
99
|
+
description="Environment prefix for topics (resolved via resolve_environment)",
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
@model_validator(mode="after")
|
|
103
|
+
def validate_source_configured(self) -> Self:
|
|
104
|
+
"""Validate that required source fields are configured for the mode.
|
|
105
|
+
|
|
106
|
+
Rules:
|
|
107
|
+
- filesystem mode requires filesystem_root
|
|
108
|
+
- package mode requires package_module
|
|
109
|
+
- composite mode requires at least one source
|
|
110
|
+
|
|
111
|
+
Returns:
|
|
112
|
+
Self if validation passes
|
|
113
|
+
|
|
114
|
+
Raises:
|
|
115
|
+
ValueError: If required source configuration is missing
|
|
116
|
+
"""
|
|
117
|
+
match self.mode:
|
|
118
|
+
case "filesystem":
|
|
119
|
+
if not self.filesystem_root:
|
|
120
|
+
raise ValueError("filesystem mode requires filesystem_root")
|
|
121
|
+
case "package":
|
|
122
|
+
if not self.package_module:
|
|
123
|
+
raise ValueError("package mode requires package_module")
|
|
124
|
+
case "composite":
|
|
125
|
+
if not self.filesystem_root and not self.package_module:
|
|
126
|
+
raise ValueError(
|
|
127
|
+
"composite mode requires at least one source "
|
|
128
|
+
"(filesystem_root or package_module)"
|
|
129
|
+
)
|
|
130
|
+
return self
|
|
131
|
+
|
|
132
|
+
def resolve_environment(self) -> str:
|
|
133
|
+
"""Resolve environment with precedence: config > env var > 'dev'.
|
|
134
|
+
|
|
135
|
+
Resolution Order:
|
|
136
|
+
1. self.environment (if provided and non-empty after normalization)
|
|
137
|
+
2. ONEX_ENV environment variable (if set and non-empty after normalization)
|
|
138
|
+
3. Default "dev"
|
|
139
|
+
|
|
140
|
+
Normalization:
|
|
141
|
+
- Whitespace is stripped
|
|
142
|
+
- Trailing dots are removed (to prevent "dev..topic" issues)
|
|
143
|
+
|
|
144
|
+
Note:
|
|
145
|
+
Whitespace-only strings (e.g., " ") are treated as empty and
|
|
146
|
+
fall through to the next priority level.
|
|
147
|
+
|
|
148
|
+
Returns:
|
|
149
|
+
Resolved environment string, normalized
|
|
150
|
+
|
|
151
|
+
Example:
|
|
152
|
+
>>> config = ModelContractPublisherConfig(
|
|
153
|
+
... mode="filesystem",
|
|
154
|
+
... filesystem_root=Path("/app"),
|
|
155
|
+
... environment="prod",
|
|
156
|
+
... )
|
|
157
|
+
>>> config.resolve_environment()
|
|
158
|
+
'prod'
|
|
159
|
+
|
|
160
|
+
>>> # With no config.environment and ONEX_ENV=staging
|
|
161
|
+
>>> config2 = ModelContractPublisherConfig(
|
|
162
|
+
... mode="filesystem",
|
|
163
|
+
... filesystem_root=Path("/app"),
|
|
164
|
+
... )
|
|
165
|
+
>>> # Returns "staging" if ONEX_ENV is set, else "dev"
|
|
166
|
+
"""
|
|
167
|
+
# Priority 1: Explicit config (if non-empty after normalization)
|
|
168
|
+
if self.environment:
|
|
169
|
+
normalized = self._normalize_environment(self.environment)
|
|
170
|
+
if normalized:
|
|
171
|
+
return normalized
|
|
172
|
+
|
|
173
|
+
# Priority 2: Environment variable (if non-empty after normalization)
|
|
174
|
+
env_var = os.getenv("ONEX_ENV", "")
|
|
175
|
+
if env_var:
|
|
176
|
+
normalized = self._normalize_environment(env_var)
|
|
177
|
+
if normalized:
|
|
178
|
+
return normalized
|
|
179
|
+
|
|
180
|
+
# Priority 3: Default
|
|
181
|
+
return "dev"
|
|
182
|
+
|
|
183
|
+
@staticmethod
|
|
184
|
+
def _normalize_environment(value: str) -> str:
|
|
185
|
+
"""Normalize environment string.
|
|
186
|
+
|
|
187
|
+
Strips whitespace and removes trailing dots to prevent
|
|
188
|
+
topic formatting issues like "dev..topic".
|
|
189
|
+
|
|
190
|
+
Args:
|
|
191
|
+
value: Raw environment value
|
|
192
|
+
|
|
193
|
+
Returns:
|
|
194
|
+
Normalized environment string
|
|
195
|
+
"""
|
|
196
|
+
return value.strip().rstrip(".")
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
__all__ = ["ModelContractPublisherConfig"]
|