omnibase_infra 0.2.5__py3-none-any.whl → 0.2.7__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.
Files changed (139) hide show
  1. omnibase_infra/constants_topic_patterns.py +26 -0
  2. omnibase_infra/enums/__init__.py +3 -0
  3. omnibase_infra/enums/enum_consumer_group_purpose.py +92 -0
  4. omnibase_infra/enums/enum_handler_source_mode.py +16 -2
  5. omnibase_infra/errors/__init__.py +4 -0
  6. omnibase_infra/errors/error_binding_resolution.py +128 -0
  7. omnibase_infra/event_bus/configs/kafka_event_bus_config.yaml +0 -2
  8. omnibase_infra/event_bus/event_bus_inmemory.py +64 -10
  9. omnibase_infra/event_bus/event_bus_kafka.py +105 -47
  10. omnibase_infra/event_bus/mixin_kafka_broadcast.py +3 -7
  11. omnibase_infra/event_bus/mixin_kafka_dlq.py +12 -6
  12. omnibase_infra/event_bus/models/config/model_kafka_event_bus_config.py +0 -81
  13. omnibase_infra/event_bus/testing/__init__.py +26 -0
  14. omnibase_infra/event_bus/testing/adapter_protocol_event_publisher_inmemory.py +418 -0
  15. omnibase_infra/event_bus/testing/model_publisher_metrics.py +64 -0
  16. omnibase_infra/handlers/handler_consul.py +2 -0
  17. omnibase_infra/handlers/mixins/__init__.py +5 -0
  18. omnibase_infra/handlers/mixins/mixin_consul_service.py +274 -10
  19. omnibase_infra/handlers/mixins/mixin_consul_topic_index.py +585 -0
  20. omnibase_infra/handlers/models/model_filesystem_config.py +4 -4
  21. omnibase_infra/migrations/001_create_event_ledger.sql +166 -0
  22. omnibase_infra/migrations/001_drop_event_ledger.sql +18 -0
  23. omnibase_infra/mixins/mixin_node_introspection.py +189 -19
  24. omnibase_infra/models/__init__.py +8 -0
  25. omnibase_infra/models/bindings/__init__.py +59 -0
  26. omnibase_infra/models/bindings/constants.py +144 -0
  27. omnibase_infra/models/bindings/model_binding_resolution_result.py +103 -0
  28. omnibase_infra/models/bindings/model_operation_binding.py +44 -0
  29. omnibase_infra/models/bindings/model_operation_bindings_subcontract.py +152 -0
  30. omnibase_infra/models/bindings/model_parsed_binding.py +52 -0
  31. omnibase_infra/models/discovery/model_introspection_config.py +25 -17
  32. omnibase_infra/models/dispatch/__init__.py +8 -0
  33. omnibase_infra/models/dispatch/model_debug_trace_snapshot.py +114 -0
  34. omnibase_infra/models/dispatch/model_materialized_dispatch.py +141 -0
  35. omnibase_infra/models/handlers/model_handler_source_config.py +1 -1
  36. omnibase_infra/models/model_node_identity.py +126 -0
  37. omnibase_infra/models/projection/model_snapshot_topic_config.py +3 -2
  38. omnibase_infra/models/registration/__init__.py +9 -0
  39. omnibase_infra/models/registration/model_event_bus_topic_entry.py +59 -0
  40. omnibase_infra/models/registration/model_node_event_bus_config.py +99 -0
  41. omnibase_infra/models/registration/model_node_introspection_event.py +11 -0
  42. omnibase_infra/models/runtime/__init__.py +9 -0
  43. omnibase_infra/models/validation/model_coverage_metrics.py +2 -2
  44. omnibase_infra/nodes/__init__.py +9 -0
  45. omnibase_infra/nodes/contract_registry_reducer/__init__.py +29 -0
  46. omnibase_infra/nodes/contract_registry_reducer/contract.yaml +255 -0
  47. omnibase_infra/nodes/contract_registry_reducer/models/__init__.py +38 -0
  48. omnibase_infra/nodes/contract_registry_reducer/models/model_contract_registry_state.py +266 -0
  49. omnibase_infra/nodes/contract_registry_reducer/models/model_payload_cleanup_topic_references.py +55 -0
  50. omnibase_infra/nodes/contract_registry_reducer/models/model_payload_deactivate_contract.py +58 -0
  51. omnibase_infra/nodes/contract_registry_reducer/models/model_payload_mark_stale.py +49 -0
  52. omnibase_infra/nodes/contract_registry_reducer/models/model_payload_update_heartbeat.py +71 -0
  53. omnibase_infra/nodes/contract_registry_reducer/models/model_payload_update_topic.py +66 -0
  54. omnibase_infra/nodes/contract_registry_reducer/models/model_payload_upsert_contract.py +92 -0
  55. omnibase_infra/nodes/contract_registry_reducer/node.py +121 -0
  56. omnibase_infra/nodes/contract_registry_reducer/reducer.py +784 -0
  57. omnibase_infra/nodes/contract_registry_reducer/registry/__init__.py +9 -0
  58. omnibase_infra/nodes/contract_registry_reducer/registry/registry_infra_contract_registry_reducer.py +101 -0
  59. omnibase_infra/nodes/handlers/consul/contract.yaml +85 -0
  60. omnibase_infra/nodes/handlers/db/contract.yaml +72 -0
  61. omnibase_infra/nodes/handlers/graph/contract.yaml +127 -0
  62. omnibase_infra/nodes/handlers/http/contract.yaml +74 -0
  63. omnibase_infra/nodes/handlers/intent/contract.yaml +66 -0
  64. omnibase_infra/nodes/handlers/mcp/contract.yaml +69 -0
  65. omnibase_infra/nodes/handlers/vault/contract.yaml +91 -0
  66. omnibase_infra/nodes/node_ledger_projection_compute/__init__.py +50 -0
  67. omnibase_infra/nodes/node_ledger_projection_compute/contract.yaml +104 -0
  68. omnibase_infra/nodes/node_ledger_projection_compute/node.py +284 -0
  69. omnibase_infra/nodes/node_ledger_projection_compute/registry/__init__.py +29 -0
  70. omnibase_infra/nodes/node_ledger_projection_compute/registry/registry_infra_ledger_projection.py +118 -0
  71. omnibase_infra/nodes/node_ledger_write_effect/__init__.py +82 -0
  72. omnibase_infra/nodes/node_ledger_write_effect/contract.yaml +200 -0
  73. omnibase_infra/nodes/node_ledger_write_effect/handlers/__init__.py +22 -0
  74. omnibase_infra/nodes/node_ledger_write_effect/handlers/handler_ledger_append.py +372 -0
  75. omnibase_infra/nodes/node_ledger_write_effect/handlers/handler_ledger_query.py +597 -0
  76. omnibase_infra/nodes/node_ledger_write_effect/models/__init__.py +31 -0
  77. omnibase_infra/nodes/node_ledger_write_effect/models/model_ledger_append_result.py +54 -0
  78. omnibase_infra/nodes/node_ledger_write_effect/models/model_ledger_entry.py +92 -0
  79. omnibase_infra/nodes/node_ledger_write_effect/models/model_ledger_query.py +53 -0
  80. omnibase_infra/nodes/node_ledger_write_effect/models/model_ledger_query_result.py +41 -0
  81. omnibase_infra/nodes/node_ledger_write_effect/node.py +89 -0
  82. omnibase_infra/nodes/node_ledger_write_effect/protocols/__init__.py +13 -0
  83. omnibase_infra/nodes/node_ledger_write_effect/protocols/protocol_ledger_persistence.py +127 -0
  84. omnibase_infra/nodes/node_ledger_write_effect/registry/__init__.py +9 -0
  85. omnibase_infra/nodes/node_ledger_write_effect/registry/registry_infra_ledger_write.py +121 -0
  86. omnibase_infra/nodes/node_registration_orchestrator/registry/registry_infra_node_registration_orchestrator.py +7 -5
  87. omnibase_infra/nodes/reducers/models/__init__.py +7 -2
  88. omnibase_infra/nodes/reducers/models/model_payload_consul_register.py +11 -0
  89. omnibase_infra/nodes/reducers/models/model_payload_ledger_append.py +133 -0
  90. omnibase_infra/nodes/reducers/registration_reducer.py +1 -0
  91. omnibase_infra/protocols/__init__.py +3 -0
  92. omnibase_infra/protocols/protocol_dispatch_engine.py +152 -0
  93. omnibase_infra/runtime/__init__.py +60 -0
  94. omnibase_infra/runtime/binding_resolver.py +753 -0
  95. omnibase_infra/runtime/constants_security.py +70 -0
  96. omnibase_infra/runtime/contract_loaders/__init__.py +9 -0
  97. omnibase_infra/runtime/contract_loaders/operation_bindings_loader.py +789 -0
  98. omnibase_infra/runtime/emit_daemon/__init__.py +97 -0
  99. omnibase_infra/runtime/emit_daemon/cli.py +844 -0
  100. omnibase_infra/runtime/emit_daemon/client.py +811 -0
  101. omnibase_infra/runtime/emit_daemon/config.py +535 -0
  102. omnibase_infra/runtime/emit_daemon/daemon.py +812 -0
  103. omnibase_infra/runtime/emit_daemon/event_registry.py +477 -0
  104. omnibase_infra/runtime/emit_daemon/model_daemon_request.py +139 -0
  105. omnibase_infra/runtime/emit_daemon/model_daemon_response.py +191 -0
  106. omnibase_infra/runtime/emit_daemon/queue.py +618 -0
  107. omnibase_infra/runtime/event_bus_subcontract_wiring.py +466 -0
  108. omnibase_infra/runtime/handler_source_resolver.py +43 -2
  109. omnibase_infra/runtime/kafka_contract_source.py +984 -0
  110. omnibase_infra/runtime/models/__init__.py +13 -0
  111. omnibase_infra/runtime/models/model_contract_load_result.py +224 -0
  112. omnibase_infra/runtime/models/model_runtime_contract_config.py +268 -0
  113. omnibase_infra/runtime/models/model_runtime_scheduler_config.py +4 -3
  114. omnibase_infra/runtime/models/model_security_config.py +109 -0
  115. omnibase_infra/runtime/publisher_topic_scoped.py +294 -0
  116. omnibase_infra/runtime/runtime_contract_config_loader.py +406 -0
  117. omnibase_infra/runtime/service_kernel.py +76 -6
  118. omnibase_infra/runtime/service_message_dispatch_engine.py +558 -15
  119. omnibase_infra/runtime/service_runtime_host_process.py +770 -20
  120. omnibase_infra/runtime/transition_notification_publisher.py +3 -2
  121. omnibase_infra/runtime/util_wiring.py +206 -62
  122. omnibase_infra/services/mcp/service_mcp_tool_sync.py +27 -9
  123. omnibase_infra/services/session/config_consumer.py +25 -8
  124. omnibase_infra/services/session/config_store.py +2 -2
  125. omnibase_infra/services/session/consumer.py +1 -1
  126. omnibase_infra/topics/__init__.py +45 -0
  127. omnibase_infra/topics/platform_topic_suffixes.py +140 -0
  128. omnibase_infra/topics/util_topic_composition.py +95 -0
  129. omnibase_infra/types/typed_dict/__init__.py +9 -1
  130. omnibase_infra/types/typed_dict/typed_dict_envelope_build_params.py +115 -0
  131. omnibase_infra/utils/__init__.py +9 -0
  132. omnibase_infra/utils/util_consumer_group.py +232 -0
  133. omnibase_infra/validation/infra_validators.py +18 -1
  134. omnibase_infra/validation/validation_exemptions.yaml +192 -0
  135. {omnibase_infra-0.2.5.dist-info → omnibase_infra-0.2.7.dist-info}/METADATA +3 -3
  136. {omnibase_infra-0.2.5.dist-info → omnibase_infra-0.2.7.dist-info}/RECORD +139 -52
  137. {omnibase_infra-0.2.5.dist-info → omnibase_infra-0.2.7.dist-info}/entry_points.txt +1 -0
  138. {omnibase_infra-0.2.5.dist-info → omnibase_infra-0.2.7.dist-info}/WHEEL +0 -0
  139. {omnibase_infra-0.2.5.dist-info → omnibase_infra-0.2.7.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,59 @@
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright (c) 2025 OmniNode Team
3
+ """Operation bindings models for declarative handler parameter mapping.
4
+
5
+ This module provides models for binding expressions used in declarative
6
+ handler parameter resolution from contract.yaml.
7
+
8
+ .. versionadded:: 0.2.6
9
+ Added ModelOperationBindingsSubcontract and ModelBindingResolutionResult
10
+ as part of OMN-1518 - Declarative operation bindings.
11
+ """
12
+
13
+ from omnibase_infra.models.bindings.constants import (
14
+ DEFAULT_JSON_RECURSION_DEPTH,
15
+ EXPRESSION_PATTERN,
16
+ MAX_EXPRESSION_LENGTH,
17
+ MAX_JSON_RECURSION_DEPTH,
18
+ MAX_PATH_SEGMENTS,
19
+ MIN_JSON_RECURSION_DEPTH,
20
+ VALID_CONTEXT_PATHS,
21
+ VALID_SOURCES,
22
+ )
23
+ from omnibase_infra.models.bindings.model_binding_resolution_result import (
24
+ ModelBindingResolutionResult,
25
+ )
26
+ from omnibase_infra.models.bindings.model_operation_binding import (
27
+ ModelOperationBinding,
28
+ )
29
+ from omnibase_infra.models.bindings.model_operation_bindings_subcontract import (
30
+ MAX_EXPRESSION_LENGTH_LIMIT,
31
+ MAX_PATH_SEGMENTS_LIMIT,
32
+ MIN_EXPRESSION_LENGTH,
33
+ MIN_PATH_SEGMENTS,
34
+ ModelOperationBindingsSubcontract,
35
+ )
36
+ from omnibase_infra.models.bindings.model_parsed_binding import (
37
+ ModelParsedBinding,
38
+ )
39
+
40
+ __all__: list[str] = [
41
+ # Constants
42
+ "DEFAULT_JSON_RECURSION_DEPTH",
43
+ "EXPRESSION_PATTERN",
44
+ "MAX_EXPRESSION_LENGTH",
45
+ "MAX_EXPRESSION_LENGTH_LIMIT",
46
+ "MAX_JSON_RECURSION_DEPTH",
47
+ "MAX_PATH_SEGMENTS",
48
+ "MAX_PATH_SEGMENTS_LIMIT",
49
+ "MIN_EXPRESSION_LENGTH",
50
+ "MIN_JSON_RECURSION_DEPTH",
51
+ "MIN_PATH_SEGMENTS",
52
+ "VALID_CONTEXT_PATHS",
53
+ "VALID_SOURCES",
54
+ # Models
55
+ "ModelBindingResolutionResult",
56
+ "ModelOperationBinding",
57
+ "ModelOperationBindingsSubcontract",
58
+ "ModelParsedBinding",
59
+ ]
@@ -0,0 +1,144 @@
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright (c) 2025 OmniNode Team
3
+ """Shared constants for binding expression parsing and validation.
4
+
5
+ This module contains guardrail constants and patterns used by both:
6
+ - :mod:`omnibase_infra.runtime.binding_resolver` (runtime resolution)
7
+ - :mod:`omnibase_infra.runtime.contract_loaders.operation_bindings_loader` (load-time validation)
8
+
9
+ These constants define the security and validation boundaries for binding expressions.
10
+
11
+ .. versionadded:: 0.2.6
12
+ Created as part of OMN-1518 - Declarative operation bindings.
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ import re
18
+ from typing import Final
19
+
20
+ # =============================================================================
21
+ # Guardrail Constants
22
+ # =============================================================================
23
+
24
+ MAX_EXPRESSION_LENGTH: Final[int] = 256
25
+ """Maximum allowed length for binding expressions (characters).
26
+
27
+ Prevents denial-of-service via extremely long expressions that could
28
+ exhaust memory or CPU during regex matching.
29
+ """
30
+
31
+ MAX_PATH_SEGMENTS: Final[int] = 20
32
+ """Maximum allowed path depth (dot-separated segments).
33
+
34
+ Prevents deep nesting attacks and potential stack overflow during
35
+ path traversal. Also limits complexity of binding expressions.
36
+ """
37
+
38
+ # =============================================================================
39
+ # JSON Recursion Depth Limits
40
+ # =============================================================================
41
+
42
+ DEFAULT_JSON_RECURSION_DEPTH: Final[int] = 100
43
+ """Default maximum recursion depth for JSON compatibility validation.
44
+
45
+ This constant limits how deeply nested structures are validated in
46
+ ``_is_json_compatible_recursive()``. It prevents stack overflow on
47
+ pathological inputs such as deeply nested dicts/lists or cyclic
48
+ references that somehow bypass Python's normal recursion limit.
49
+
50
+ The value of 100 is chosen to:
51
+ - Allow normal JSON structures (rarely exceed 10-20 levels)
52
+ - Prevent stack overflow on malicious or malformed inputs
53
+ - Align with common JSON parser depth limits
54
+
55
+ .. versionadded:: 0.2.6
56
+ """
57
+
58
+ MIN_JSON_RECURSION_DEPTH: Final[int] = 10
59
+ """Minimum configurable JSON recursion depth.
60
+
61
+ Values below this threshold would be too restrictive for practical use,
62
+ as even simple nested structures (like user -> address -> country)
63
+ can easily reach 5+ levels.
64
+
65
+ .. versionadded:: 0.2.6
66
+ """
67
+
68
+ MAX_JSON_RECURSION_DEPTH: Final[int] = 1000
69
+ """Maximum configurable JSON recursion depth.
70
+
71
+ Values above this threshold increase risk of stack overflow and would
72
+ exceed any reasonable JSON structure depth. Most JSON parsers use
73
+ similar limits (e.g., Python's json module has implicit limits).
74
+
75
+ .. versionadded:: 0.2.6
76
+ """
77
+
78
+ # =============================================================================
79
+ # Valid Sources and Context Paths
80
+ # =============================================================================
81
+
82
+ VALID_SOURCES: Final[frozenset[str]] = frozenset({"payload", "envelope", "context"})
83
+ """Valid source names for binding expressions.
84
+
85
+ Binding expressions must start with one of these sources:
86
+ - ``payload``: Access fields from the event payload
87
+ - ``envelope``: Access fields from the event envelope (correlation_id, etc.)
88
+ - ``context``: Access runtime context values (now_iso, dispatcher_id, etc.)
89
+ """
90
+
91
+ VALID_CONTEXT_PATHS: Final[frozenset[str]] = frozenset(
92
+ {
93
+ "now_iso",
94
+ "dispatcher_id",
95
+ "correlation_id",
96
+ }
97
+ )
98
+ """Exhaustive allowlist of valid context paths.
99
+
100
+ Context paths are special runtime-provided values injected by the
101
+ dispatch infrastructure. Adding new context paths requires:
102
+
103
+ 1. Update this allowlist
104
+ 2. Update the dispatch context provider to supply the value
105
+ 3. Document the new context path in relevant docstrings
106
+
107
+ Current paths:
108
+ - ``now_iso``: Current timestamp in ISO 8601 format
109
+ - ``dispatcher_id``: Unique identifier of the dispatcher instance
110
+ - ``correlation_id``: Request correlation ID for distributed tracing
111
+ """
112
+
113
+ # =============================================================================
114
+ # Expression Pattern
115
+ # =============================================================================
116
+
117
+ EXPRESSION_PATTERN: Final[re.Pattern[str]] = re.compile(
118
+ r"^\$\{([a-z]+)\.([a-zA-Z0-9_.]+)\}$"
119
+ )
120
+ """Compiled regex for parsing binding expressions.
121
+
122
+ Pattern breakdown:
123
+ - ``^\\$\\{``: Literal ``${`` at start
124
+ - ``([a-z]+)``: Group 1 - source (lowercase letters only)
125
+ - ``\\.``: Literal dot separator
126
+ - ``([a-zA-Z0-9_.]+)``: Group 2 - path (letters, numbers, underscores, dots)
127
+ - ``\\}$``: Literal ``}`` at end
128
+
129
+ Examples of valid expressions:
130
+ - ``${payload.user.id}`` -> source="payload", path="user.id"
131
+ - ``${envelope.correlation_id}`` -> source="envelope", path="correlation_id"
132
+ - ``${context.now_iso}`` -> source="context", path="now_iso"
133
+ """
134
+
135
+ __all__: list[str] = [
136
+ "DEFAULT_JSON_RECURSION_DEPTH",
137
+ "EXPRESSION_PATTERN",
138
+ "MAX_EXPRESSION_LENGTH",
139
+ "MAX_JSON_RECURSION_DEPTH",
140
+ "MAX_PATH_SEGMENTS",
141
+ "MIN_JSON_RECURSION_DEPTH",
142
+ "VALID_CONTEXT_PATHS",
143
+ "VALID_SOURCES",
144
+ ]
@@ -0,0 +1,103 @@
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright (c) 2025 OmniNode Team
3
+ """Binding resolution result model.
4
+
5
+ This model represents the outcome of resolving operation bindings, containing
6
+ the resolved parameters and debug information for troubleshooting.
7
+
8
+ .. versionadded:: 0.2.6
9
+ Created as part of OMN-1518 - Declarative operation bindings.
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ from pydantic import BaseModel, Field
15
+
16
+ from omnibase_core.types import JsonType
17
+
18
+
19
+ class ModelBindingResolutionResult(BaseModel):
20
+ """Result of binding resolution - execution fact, never mutated.
21
+
22
+ Contains resolved parameters plus debug information for troubleshooting.
23
+ Once created, this is immutable - bindings become execution facts.
24
+
25
+ Attributes:
26
+ operation_name: Operation that was resolved.
27
+ resolved_parameters: Mapping of parameter name to resolved value.
28
+ resolved_from: Mapping of parameter name to original expression (debug gold).
29
+ success: True if all required bindings resolved successfully.
30
+ error: Error message if success=False.
31
+
32
+ Example:
33
+ >>> result = resolver.resolve(envelope, "db.query")
34
+ >>> if result:
35
+ ... execute_query(result.resolved_parameters)
36
+ ... else:
37
+ ... log_error(result.error)
38
+
39
+ .. versionadded:: 0.2.6
40
+ """
41
+
42
+ operation_name: str = Field(
43
+ ...,
44
+ description="Operation that was resolved",
45
+ )
46
+ resolved_parameters: dict[str, JsonType] = Field(
47
+ default_factory=dict,
48
+ description="Parameter name -> resolved value",
49
+ )
50
+ resolved_from: dict[str, str] = Field(
51
+ default_factory=dict,
52
+ description="Parameter name -> original expression (debug gold)",
53
+ )
54
+ success: bool = Field(
55
+ ...,
56
+ description="True if all required bindings resolved successfully",
57
+ )
58
+ error: str | None = Field(
59
+ default=None,
60
+ description="Error message if success=False",
61
+ )
62
+
63
+ model_config = {"frozen": True, "extra": "forbid"}
64
+
65
+ def __bool__(self) -> bool:
66
+ """Return True if resolution succeeded.
67
+
68
+ Warning:
69
+ **Non-standard __bool__ behavior**: This model overrides ``__bool__`` to
70
+ return ``True`` only when ``success`` is True. This differs from typical
71
+ Pydantic model behavior where ``bool(model)`` always returns ``True`` for
72
+ any valid model instance.
73
+
74
+ This design enables idiomatic conditional checks for resolution results::
75
+
76
+ result = resolver.resolve(envelope, "db.query")
77
+ if result:
78
+ # Resolution succeeded - use parameters
79
+ execute_query(result.resolved_parameters)
80
+ else:
81
+ # Resolution failed - handle error
82
+ log_error(result.error)
83
+
84
+ If you need to check model existence instead, use explicit attribute access::
85
+
86
+ # Check for resolution success (uses __bool__)
87
+ if result:
88
+ ...
89
+
90
+ # Check model is not None
91
+ if result is not None:
92
+ ...
93
+
94
+ # Explicit success check (preferred for clarity)
95
+ if result.success:
96
+ ...
97
+
98
+ Returns:
99
+ True if resolution succeeded, False otherwise.
100
+
101
+ .. versionadded:: 0.2.6
102
+ """
103
+ return self.success
@@ -0,0 +1,44 @@
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright (c) 2025 OmniNode Team
3
+ """Operation binding model for contract.yaml entries."""
4
+
5
+ from __future__ import annotations
6
+
7
+ from pydantic import BaseModel, Field
8
+
9
+ from omnibase_core.types import JsonType
10
+
11
+
12
+ class ModelOperationBinding(BaseModel):
13
+ """Single binding entry from contract.yaml.
14
+
15
+ Maps a handler parameter to an expression that extracts data from
16
+ envelope, payload, or context.
17
+
18
+ Example YAML:
19
+ - parameter_name: "correlation_id"
20
+ expression: "${envelope.correlation_id}"
21
+ required: true
22
+ """
23
+
24
+ parameter_name: str = Field(
25
+ ...,
26
+ description="Target handler input field name",
27
+ )
28
+ expression: str = Field(
29
+ ...,
30
+ description="Source expression in ${source.path} format",
31
+ )
32
+ required: bool = Field(
33
+ default=True,
34
+ description="If True, fail fast when field is missing",
35
+ )
36
+ default: JsonType | None = Field(
37
+ default=None,
38
+ description="Default value if not required and missing",
39
+ )
40
+
41
+ model_config = {"frozen": True, "extra": "forbid"}
42
+
43
+
44
+ __all__ = ["ModelOperationBinding"]
@@ -0,0 +1,152 @@
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright (c) 2025 OmniNode Team
3
+ """Operation bindings subcontract model for contract.yaml section.
4
+
5
+ This model represents the full operation_bindings section from a contract.yaml file,
6
+ containing pre-parsed bindings for all operations plus optional global bindings.
7
+
8
+ .. versionadded:: 0.2.6
9
+ Created as part of OMN-1518 - Declarative operation bindings.
10
+
11
+ .. versionchanged:: 0.2.7
12
+ Added max_expression_length and max_path_segments for per-contract guardrail overrides.
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ from pydantic import BaseModel, Field
18
+
19
+ from omnibase_core.models.primitives.model_semver import ModelSemVer
20
+ from omnibase_infra.models.bindings.constants import (
21
+ DEFAULT_JSON_RECURSION_DEPTH,
22
+ MAX_EXPRESSION_LENGTH,
23
+ MAX_JSON_RECURSION_DEPTH,
24
+ MAX_PATH_SEGMENTS,
25
+ MIN_JSON_RECURSION_DEPTH,
26
+ )
27
+ from omnibase_infra.models.bindings.model_parsed_binding import ModelParsedBinding
28
+
29
+ # =============================================================================
30
+ # Guardrail Bounds Constants
31
+ # =============================================================================
32
+
33
+ # Expression length bounds
34
+ MIN_EXPRESSION_LENGTH: int = 32
35
+ """Minimum allowed value for max_expression_length override.
36
+
37
+ Prevents trivially small expressions that would be unusable for real bindings.
38
+ A minimal valid expression like ${payload.x} is 12 characters.
39
+ """
40
+
41
+ MAX_EXPRESSION_LENGTH_LIMIT: int = 1024
42
+ """Maximum allowed value for max_expression_length override.
43
+
44
+ Prevents DoS via excessively long expressions that could exhaust memory
45
+ or CPU during regex matching.
46
+ """
47
+
48
+ # Path segment bounds
49
+ MIN_PATH_SEGMENTS: int = 3
50
+ """Minimum allowed value for max_path_segments override.
51
+
52
+ Need at least source.path.field (3 segments) for useful bindings.
53
+ """
54
+
55
+ MAX_PATH_SEGMENTS_LIMIT: int = 50
56
+ """Maximum allowed value for max_path_segments override.
57
+
58
+ Prevents absurdly deep paths that could cause stack issues or DoS.
59
+ """
60
+
61
+
62
+ class ModelOperationBindingsSubcontract(BaseModel):
63
+ """Full operation_bindings section from contract.yaml.
64
+
65
+ Contains pre-parsed bindings for all operations, plus optional
66
+ global bindings applied to every operation.
67
+
68
+ Example YAML:
69
+ operation_bindings:
70
+ version: { major: 1, minor: 0, patch: 0 }
71
+ max_expression_length: 512 # Override default (256)
72
+ max_path_segments: 30 # Override default (20)
73
+ max_json_recursion_depth: 50 # Override default (100)
74
+ additional_context_paths:
75
+ - "request_id" # Handler needs request ID
76
+ - "tenant_id" # Multi-tenant context
77
+ global_bindings:
78
+ - parameter_name: "correlation_id"
79
+ expression: "${envelope.correlation_id}"
80
+ bindings:
81
+ "db.query":
82
+ - parameter_name: "sql"
83
+ expression: "${payload.sql}"
84
+ - parameter_name: "tenant"
85
+ expression: "${context.tenant_id}"
86
+ required: true
87
+
88
+ Attributes:
89
+ version: Schema version for evolution tracking.
90
+ additional_context_paths: Additional context paths this handler can resolve
91
+ beyond the base set (now_iso, dispatcher_id, correlation_id).
92
+ When declared, the dispatch engine is CONTRACTED to provide these
93
+ values in the context dict. Pattern: ^[a-z][a-z0-9_]*$
94
+ bindings: Mapping of operation name to list of parsed bindings.
95
+ global_bindings: Optional bindings applied to all operations (can be overridden).
96
+ max_expression_length: Override default expression length limit (32-1024).
97
+ max_path_segments: Override default path segment limit (3-50).
98
+ max_json_recursion_depth: Maximum depth for JSON compatibility validation (10-1000).
99
+ Limits how deeply nested structures are validated to prevent stack overflow.
100
+
101
+ .. versionadded:: 0.2.6
102
+ .. versionchanged:: 0.2.7
103
+ Added max_expression_length, max_path_segments, and max_json_recursion_depth
104
+ for per-contract guardrail overrides.
105
+ Added additional_context_paths for extensible context resolution.
106
+ """
107
+
108
+ version: ModelSemVer = Field(
109
+ default_factory=lambda: ModelSemVer(major=1, minor=0, patch=0),
110
+ description="Schema version for evolution tracking",
111
+ )
112
+ additional_context_paths: list[str] = Field(
113
+ default_factory=list,
114
+ description=(
115
+ "Additional context paths this handler can resolve beyond the base set "
116
+ "(now_iso, dispatcher_id, correlation_id). When declared, the dispatch "
117
+ "engine is CONTRACTED to provide these values. "
118
+ "Pattern: ^[a-z][a-z0-9_]*$ (lowercase letters, numbers, underscores)"
119
+ ),
120
+ )
121
+ bindings: dict[str, list[ModelParsedBinding]] = Field(
122
+ default_factory=dict,
123
+ description="Operation name -> list of parsed bindings",
124
+ )
125
+ global_bindings: list[ModelParsedBinding] | None = Field(
126
+ default=None,
127
+ description="Bindings applied to all operations (can be overridden)",
128
+ )
129
+ max_expression_length: int = Field(
130
+ default=MAX_EXPRESSION_LENGTH,
131
+ ge=MIN_EXPRESSION_LENGTH,
132
+ le=MAX_EXPRESSION_LENGTH_LIMIT,
133
+ description="Override default expression length limit (32-1024)",
134
+ )
135
+ max_path_segments: int = Field(
136
+ default=MAX_PATH_SEGMENTS,
137
+ ge=MIN_PATH_SEGMENTS,
138
+ le=MAX_PATH_SEGMENTS_LIMIT,
139
+ description="Override default path segment limit (3-50)",
140
+ )
141
+ max_json_recursion_depth: int = Field(
142
+ default=DEFAULT_JSON_RECURSION_DEPTH,
143
+ ge=MIN_JSON_RECURSION_DEPTH,
144
+ le=MAX_JSON_RECURSION_DEPTH,
145
+ description=(
146
+ f"Maximum depth for JSON compatibility validation ({MIN_JSON_RECURSION_DEPTH}-"
147
+ f"{MAX_JSON_RECURSION_DEPTH}). Limits how deeply nested structures are "
148
+ "validated to prevent stack overflow on pathological inputs."
149
+ ),
150
+ )
151
+
152
+ model_config = {"frozen": True, "extra": "forbid"}
@@ -0,0 +1,52 @@
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright (c) 2025 OmniNode Team
3
+ """Parsed binding model with pre-compiled expression components."""
4
+
5
+ from __future__ import annotations
6
+
7
+ from typing import Literal
8
+
9
+ from pydantic import BaseModel, Field
10
+
11
+ from omnibase_core.types import JsonType
12
+
13
+
14
+ class ModelParsedBinding(BaseModel):
15
+ """Binding with pre-parsed expression for fast resolution.
16
+
17
+ The loader parses and validates expressions once at load time.
18
+ The resolver just executes path traversal without re-parsing.
19
+
20
+ Example:
21
+ Original expression: "${payload.user.id}"
22
+ Parsed:
23
+ source: "payload"
24
+ path_segments: ("user", "id")
25
+ """
26
+
27
+ parameter_name: str = Field(
28
+ ...,
29
+ description="Target handler input field name",
30
+ )
31
+ source: Literal["payload", "envelope", "context"] = Field(
32
+ ...,
33
+ description="Data source for binding resolution",
34
+ )
35
+ path_segments: tuple[str, ...] = Field(
36
+ ...,
37
+ description="Pre-parsed path segments for traversal",
38
+ )
39
+ required: bool = Field(
40
+ default=True,
41
+ description="If True, fail fast when field is missing",
42
+ )
43
+ default: JsonType | None = Field(
44
+ default=None,
45
+ description="Default value if not required and missing",
46
+ )
47
+ original_expression: str = Field(
48
+ ...,
49
+ description="Original ${source.path} expression for error messages",
50
+ )
51
+
52
+ model_config = {"frozen": True, "extra": "forbid"}
@@ -28,6 +28,11 @@ from pydantic import BaseModel, ConfigDict, Field, field_validator
28
28
 
29
29
  from omnibase_core.enums import EnumNodeKind
30
30
  from omnibase_core.models.contracts import ModelContractBase
31
+ from omnibase_infra.topics import (
32
+ SUFFIX_NODE_HEARTBEAT,
33
+ SUFFIX_NODE_INTROSPECTION,
34
+ SUFFIX_REQUEST_INTROSPECTION,
35
+ )
31
36
 
32
37
  if TYPE_CHECKING:
33
38
  from omnibase_core.protocols.event_bus.protocol_event_bus import ProtocolEventBus
@@ -42,11 +47,11 @@ else:
42
47
 
43
48
  logger = logging.getLogger(__name__)
44
49
 
45
- # Default topic constants following ONEX legacy conventions
46
- # Note: These use the legacy "node." prefix for backward compatibility
47
- DEFAULT_INTROSPECTION_TOPIC = "node.introspection"
48
- DEFAULT_HEARTBEAT_TOPIC = "node.heartbeat"
49
- DEFAULT_REQUEST_INTROSPECTION_TOPIC = "node.request_introspection"
50
+ # Default topic constants using ONEX platform suffix constants
51
+ # These are the canonical source for introspection-related topic defaults
52
+ DEFAULT_INTROSPECTION_TOPIC = SUFFIX_NODE_INTROSPECTION
53
+ DEFAULT_HEARTBEAT_TOPIC = SUFFIX_NODE_HEARTBEAT
54
+ DEFAULT_REQUEST_INTROSPECTION_TOPIC = SUFFIX_REQUEST_INTROSPECTION
50
55
 
51
56
  # Topic validation patterns
52
57
  # Matches valid topic characters: lowercase alphanumeric, dots, hyphens, underscores
@@ -79,14 +84,14 @@ class ModelIntrospectionConfig(BaseModel):
79
84
  discovery. Methods starting with these prefixes are filtered out.
80
85
  If None, uses MixinNodeIntrospection.DEFAULT_EXCLUDE_PREFIXES.
81
86
  introspection_topic: Topic for publishing introspection events.
82
- Defaults to "node.introspection". ONEX topics (onex.*) require
83
- version suffix (.v1, .v2, etc.).
87
+ Defaults to SUFFIX_NODE_INTROSPECTION (onex.evt.platform.node-introspection.v1).
88
+ ONEX topics (onex.*) require version suffix (.v1, .v2, etc.).
84
89
  heartbeat_topic: Topic for publishing heartbeat events.
85
- Defaults to "node.heartbeat". ONEX topics (onex.*) require
86
- version suffix (.v1, .v2, etc.).
90
+ Defaults to SUFFIX_NODE_HEARTBEAT (onex.evt.platform.node-heartbeat.v1).
91
+ ONEX topics (onex.*) require version suffix (.v1, .v2, etc.).
87
92
  request_introspection_topic: Topic for receiving introspection requests.
88
- Defaults to "node.request_introspection". ONEX topics (onex.*)
89
- require version suffix (.v1, .v2, etc.).
93
+ Defaults to SUFFIX_REQUEST_INTROSPECTION (onex.cmd.platform.request-introspection.v1).
94
+ ONEX topics (onex.*) require version suffix (.v1, .v2, etc.).
90
95
  contract: Optional typed contract model for capability extraction.
91
96
  When provided, MixinNodeIntrospection extracts contract_capabilities
92
97
  using ContractCapabilityExtractor. None for legacy nodes.
@@ -282,6 +287,7 @@ class ModelIntrospectionConfig(BaseModel):
282
287
  arbitrary_types_allowed=True, # Allow arbitrary types for event_bus
283
288
  json_schema_extra={
284
289
  "examples": [
290
+ # First example: Default topic configuration using suffix constants
285
291
  {
286
292
  "node_id": "550e8400-e29b-41d4-a716-446655440000",
287
293
  "node_type": "EFFECT",
@@ -290,10 +296,12 @@ class ModelIntrospectionConfig(BaseModel):
290
296
  "cache_ttl": 300.0,
291
297
  "operation_keywords": None,
292
298
  "exclude_prefixes": None,
293
- "introspection_topic": "node.introspection",
294
- "heartbeat_topic": "node.heartbeat",
295
- "request_introspection_topic": "node.request_introspection",
299
+ "introspection_topic": "onex.evt.platform.node-introspection.v1",
300
+ "heartbeat_topic": "onex.evt.platform.node-heartbeat.v1",
301
+ "request_introspection_topic": "onex.cmd.platform.request-introspection.v1",
296
302
  },
303
+ # Second example: Custom prefixed topics for environment isolation
304
+ # Demonstrates adding env/namespace prefix to suffix constants
297
305
  {
298
306
  "node_id": "550e8400-e29b-41d4-a716-446655440001",
299
307
  "node_type": "COMPUTE",
@@ -302,9 +310,9 @@ class ModelIntrospectionConfig(BaseModel):
302
310
  "cache_ttl": 120.0,
303
311
  "operation_keywords": ["process", "transform", "analyze"],
304
312
  "exclude_prefixes": ["_internal", "_private"],
305
- "introspection_topic": "onex.node.introspection.published.v1",
306
- "heartbeat_topic": "onex.node.heartbeat.published.v1",
307
- "request_introspection_topic": "onex.registry.introspection.requested.v1",
313
+ "introspection_topic": "prod.myapp.onex.evt.platform.node-introspection.v1",
314
+ "heartbeat_topic": "prod.myapp.onex.evt.platform.node-heartbeat.v1",
315
+ "request_introspection_topic": "prod.myapp.onex.cmd.platform.request-introspection.v1",
308
316
  },
309
317
  ]
310
318
  },
@@ -95,6 +95,9 @@ See Also:
95
95
  """
96
96
 
97
97
  from omnibase_infra.enums import EnumDispatchStatus, EnumTopicStandard
98
+ from omnibase_infra.models.dispatch.model_debug_trace_snapshot import (
99
+ ModelDebugTraceSnapshot,
100
+ )
98
101
  from omnibase_infra.models.dispatch.model_dispatch_context import ModelDispatchContext
99
102
  from omnibase_infra.models.dispatch.model_dispatch_error import ModelDispatchError
100
103
  from omnibase_infra.models.dispatch.model_dispatch_log_context import (
@@ -112,6 +115,9 @@ from omnibase_infra.models.dispatch.model_dispatcher_metrics import (
112
115
  from omnibase_infra.models.dispatch.model_dispatcher_registration import (
113
116
  ModelDispatcherRegistration,
114
117
  )
118
+ from omnibase_infra.models.dispatch.model_materialized_dispatch import (
119
+ ModelMaterializedDispatch,
120
+ )
115
121
  from omnibase_infra.models.dispatch.model_parsed_topic import ModelParsedTopic
116
122
  from omnibase_infra.models.dispatch.model_topic_parser import (
117
123
  ModelTopicParser,
@@ -128,9 +134,11 @@ __all__ = [
128
134
  "EnumDispatchStatus",
129
135
  "EnumTopicStandard",
130
136
  # Models
137
+ "ModelDebugTraceSnapshot",
131
138
  "ModelDispatchContext",
132
139
  "ModelDispatchError",
133
140
  "ModelDispatchLogContext",
141
+ "ModelMaterializedDispatch",
134
142
  "ModelDispatchMetadata",
135
143
  "ModelDispatchMetrics",
136
144
  "ModelDispatchOutcome",