omnibase_infra 0.3.1__py3-none-any.whl → 0.4.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- omnibase_infra/__init__.py +1 -1
- omnibase_infra/enums/__init__.py +3 -0
- omnibase_infra/enums/enum_consumer_group_purpose.py +9 -0
- omnibase_infra/enums/enum_postgres_error_code.py +188 -0
- omnibase_infra/errors/__init__.py +4 -0
- omnibase_infra/errors/error_infra.py +60 -0
- omnibase_infra/handlers/__init__.py +3 -0
- omnibase_infra/handlers/handler_slack_webhook.py +426 -0
- omnibase_infra/handlers/models/__init__.py +14 -0
- omnibase_infra/handlers/models/enum_alert_severity.py +36 -0
- omnibase_infra/handlers/models/model_slack_alert.py +24 -0
- omnibase_infra/handlers/models/model_slack_alert_payload.py +77 -0
- omnibase_infra/handlers/models/model_slack_alert_result.py +73 -0
- omnibase_infra/handlers/registration_storage/handler_registration_storage_postgres.py +29 -20
- omnibase_infra/mixins/__init__.py +14 -0
- omnibase_infra/mixins/mixin_node_introspection.py +42 -20
- omnibase_infra/mixins/mixin_postgres_error_response.py +314 -0
- omnibase_infra/mixins/mixin_postgres_op_executor.py +298 -0
- omnibase_infra/models/__init__.py +3 -0
- omnibase_infra/models/discovery/model_dependency_spec.py +1 -0
- omnibase_infra/models/discovery/model_discovered_capabilities.py +1 -1
- omnibase_infra/models/discovery/model_introspection_config.py +28 -1
- omnibase_infra/models/discovery/model_introspection_performance_metrics.py +1 -0
- omnibase_infra/models/discovery/model_introspection_task_config.py +1 -0
- omnibase_infra/{nodes/effects/models → models}/model_backend_result.py +22 -6
- omnibase_infra/models/projection/__init__.py +11 -0
- omnibase_infra/models/projection/model_contract_projection.py +170 -0
- omnibase_infra/models/projection/model_topic_projection.py +148 -0
- omnibase_infra/models/runtime/__init__.py +4 -0
- omnibase_infra/models/runtime/model_resolved_dependencies.py +116 -0
- omnibase_infra/nodes/contract_registry_reducer/__init__.py +5 -0
- omnibase_infra/nodes/contract_registry_reducer/contract.yaml +6 -5
- omnibase_infra/nodes/contract_registry_reducer/contract_registration_event_router.py +689 -0
- omnibase_infra/nodes/contract_registry_reducer/reducer.py +9 -26
- omnibase_infra/nodes/effects/__init__.py +1 -1
- omnibase_infra/nodes/effects/models/__init__.py +6 -4
- omnibase_infra/nodes/effects/models/model_registry_response.py +1 -1
- omnibase_infra/nodes/effects/protocol_consul_client.py +1 -1
- omnibase_infra/nodes/effects/protocol_postgres_adapter.py +1 -1
- omnibase_infra/nodes/effects/registry_effect.py +1 -1
- omnibase_infra/nodes/node_contract_persistence_effect/__init__.py +101 -0
- omnibase_infra/nodes/node_contract_persistence_effect/contract.yaml +490 -0
- omnibase_infra/nodes/node_contract_persistence_effect/handlers/__init__.py +74 -0
- omnibase_infra/nodes/node_contract_persistence_effect/handlers/handler_postgres_cleanup_topics.py +217 -0
- omnibase_infra/nodes/node_contract_persistence_effect/handlers/handler_postgres_contract_upsert.py +242 -0
- omnibase_infra/nodes/node_contract_persistence_effect/handlers/handler_postgres_deactivate.py +194 -0
- omnibase_infra/nodes/node_contract_persistence_effect/handlers/handler_postgres_heartbeat.py +243 -0
- omnibase_infra/nodes/node_contract_persistence_effect/handlers/handler_postgres_mark_stale.py +208 -0
- omnibase_infra/nodes/node_contract_persistence_effect/handlers/handler_postgres_topic_update.py +298 -0
- omnibase_infra/nodes/node_contract_persistence_effect/models/__init__.py +15 -0
- omnibase_infra/nodes/node_contract_persistence_effect/models/model_persistence_result.py +52 -0
- omnibase_infra/nodes/node_contract_persistence_effect/node.py +131 -0
- omnibase_infra/nodes/node_contract_persistence_effect/registry/__init__.py +27 -0
- omnibase_infra/nodes/node_contract_persistence_effect/registry/registry_infra_contract_persistence_effect.py +251 -0
- omnibase_infra/nodes/node_registration_orchestrator/models/model_postgres_intent_payload.py +8 -12
- omnibase_infra/nodes/node_registry_effect/models/__init__.py +2 -2
- omnibase_infra/nodes/node_slack_alerter_effect/__init__.py +33 -0
- omnibase_infra/nodes/node_slack_alerter_effect/contract.yaml +291 -0
- omnibase_infra/nodes/node_slack_alerter_effect/node.py +106 -0
- omnibase_infra/projectors/__init__.py +6 -0
- omnibase_infra/projectors/projection_reader_contract.py +1301 -0
- omnibase_infra/runtime/__init__.py +12 -0
- omnibase_infra/runtime/baseline_subscriptions.py +13 -6
- omnibase_infra/runtime/contract_dependency_resolver.py +455 -0
- omnibase_infra/runtime/contract_registration_event_router.py +500 -0
- omnibase_infra/runtime/db/__init__.py +4 -0
- omnibase_infra/runtime/db/models/__init__.py +15 -10
- omnibase_infra/runtime/db/models/model_db_operation.py +40 -0
- omnibase_infra/runtime/db/models/model_db_param.py +24 -0
- omnibase_infra/runtime/db/models/model_db_repository_contract.py +40 -0
- omnibase_infra/runtime/db/models/model_db_return.py +26 -0
- omnibase_infra/runtime/db/models/model_db_safety_policy.py +32 -0
- omnibase_infra/runtime/emit_daemon/event_registry.py +34 -22
- omnibase_infra/runtime/event_bus_subcontract_wiring.py +63 -23
- omnibase_infra/runtime/intent_execution_router.py +430 -0
- omnibase_infra/runtime/models/__init__.py +6 -0
- omnibase_infra/runtime/models/model_contract_registry_config.py +41 -0
- omnibase_infra/runtime/models/model_intent_execution_summary.py +79 -0
- omnibase_infra/runtime/models/model_runtime_config.py +8 -0
- omnibase_infra/runtime/protocols/__init__.py +16 -0
- omnibase_infra/runtime/protocols/protocol_intent_executor.py +107 -0
- omnibase_infra/runtime/publisher_topic_scoped.py +16 -11
- omnibase_infra/runtime/registry_policy.py +29 -15
- omnibase_infra/runtime/request_response_wiring.py +793 -0
- omnibase_infra/runtime/service_kernel.py +295 -8
- omnibase_infra/runtime/service_runtime_host_process.py +149 -5
- omnibase_infra/runtime/util_version.py +5 -1
- omnibase_infra/schemas/schema_latency_baseline.sql +135 -0
- omnibase_infra/services/contract_publisher/config.py +4 -4
- omnibase_infra/services/contract_publisher/service.py +8 -5
- omnibase_infra/services/observability/injection_effectiveness/__init__.py +67 -0
- omnibase_infra/services/observability/injection_effectiveness/config.py +295 -0
- omnibase_infra/services/observability/injection_effectiveness/consumer.py +1461 -0
- omnibase_infra/services/observability/injection_effectiveness/models/__init__.py +32 -0
- omnibase_infra/services/observability/injection_effectiveness/models/model_agent_match.py +79 -0
- omnibase_infra/services/observability/injection_effectiveness/models/model_context_utilization.py +118 -0
- omnibase_infra/services/observability/injection_effectiveness/models/model_latency_breakdown.py +107 -0
- omnibase_infra/services/observability/injection_effectiveness/models/model_pattern_utilization.py +46 -0
- omnibase_infra/services/observability/injection_effectiveness/writer_postgres.py +596 -0
- omnibase_infra/services/registry_api/models/__init__.py +25 -0
- omnibase_infra/services/registry_api/models/model_contract_ref.py +44 -0
- omnibase_infra/services/registry_api/models/model_contract_view.py +81 -0
- omnibase_infra/services/registry_api/models/model_response_contracts.py +50 -0
- omnibase_infra/services/registry_api/models/model_response_topics.py +50 -0
- omnibase_infra/services/registry_api/models/model_topic_summary.py +57 -0
- omnibase_infra/services/registry_api/models/model_topic_view.py +63 -0
- omnibase_infra/services/registry_api/routes.py +205 -6
- omnibase_infra/services/registry_api/service.py +528 -1
- omnibase_infra/utils/__init__.py +7 -0
- omnibase_infra/utils/util_db_error_context.py +292 -0
- omnibase_infra/validation/infra_validators.py +3 -1
- omnibase_infra/validation/validation_exemptions.yaml +65 -0
- {omnibase_infra-0.3.1.dist-info → omnibase_infra-0.4.0.dist-info}/METADATA +3 -3
- {omnibase_infra-0.3.1.dist-info → omnibase_infra-0.4.0.dist-info}/RECORD +117 -58
- {omnibase_infra-0.3.1.dist-info → omnibase_infra-0.4.0.dist-info}/WHEEL +0 -0
- {omnibase_infra-0.3.1.dist-info → omnibase_infra-0.4.0.dist-info}/entry_points.txt +0 -0
- {omnibase_infra-0.3.1.dist-info → omnibase_infra-0.4.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
-- Materialized View: latency_baseline
|
|
2
|
+
-- Description: Hourly latency baseline for A/B comparison (OMN-1890)
|
|
3
|
+
-- Created: 2026-02-04
|
|
4
|
+
--
|
|
5
|
+
-- Purpose: Pre-computes hourly latency statistics per cohort for dashboard queries.
|
|
6
|
+
-- Refreshed hourly (not daily) to catch intra-day drift. Dashboard queries use
|
|
7
|
+
-- COALESCE with 7-day rolling fallback when sample_count is insufficient.
|
|
8
|
+
--
|
|
9
|
+
-- Refresh Strategy:
|
|
10
|
+
-- - Scheduled via pg_cron: REFRESH MATERIALIZED VIEW CONCURRENTLY latency_baseline;
|
|
11
|
+
-- - Recommended schedule: Every hour at :05 (e.g., 00:05, 01:05, ...)
|
|
12
|
+
-- - CONCURRENTLY allows queries during refresh (requires UNIQUE index)
|
|
13
|
+
--
|
|
14
|
+
-- Usage in Dashboard Queries:
|
|
15
|
+
-- SELECT
|
|
16
|
+
-- ie.session_id,
|
|
17
|
+
-- ie.cohort,
|
|
18
|
+
-- ie.user_visible_latency_ms,
|
|
19
|
+
-- ie.user_visible_latency_ms - COALESCE(
|
|
20
|
+
-- lb.avg_latency_ms,
|
|
21
|
+
-- (SELECT AVG(avg_latency_ms) FROM latency_baseline
|
|
22
|
+
-- WHERE cohort = ie.cohort AND hour > NOW() - INTERVAL '7 days')
|
|
23
|
+
-- ) AS latency_delta_ms
|
|
24
|
+
-- FROM injection_effectiveness ie
|
|
25
|
+
-- LEFT JOIN latency_baseline lb
|
|
26
|
+
-- ON DATE_TRUNC('hour', ie.created_at) = lb.hour
|
|
27
|
+
-- AND ie.cohort = lb.cohort;
|
|
28
|
+
--
|
|
29
|
+
-- Related Tickets:
|
|
30
|
+
-- - OMN-1890: Store injection metrics with corrected schema
|
|
31
|
+
|
|
32
|
+
-- ============================================================================
|
|
33
|
+
-- MATERIALIZED VIEW
|
|
34
|
+
-- ============================================================================
|
|
35
|
+
|
|
36
|
+
CREATE MATERIALIZED VIEW IF NOT EXISTS latency_baseline AS
|
|
37
|
+
SELECT
|
|
38
|
+
DATE_TRUNC('hour', created_at) AS hour,
|
|
39
|
+
cohort,
|
|
40
|
+
AVG(user_visible_latency_ms) AS avg_latency_ms,
|
|
41
|
+
PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY user_visible_latency_ms) AS p50_latency_ms,
|
|
42
|
+
PERCENTILE_CONT(0.95) WITHIN GROUP (ORDER BY user_visible_latency_ms) AS p95_latency_ms,
|
|
43
|
+
PERCENTILE_CONT(0.99) WITHIN GROUP (ORDER BY user_visible_latency_ms) AS p99_latency_ms,
|
|
44
|
+
MIN(user_visible_latency_ms) AS min_latency_ms,
|
|
45
|
+
MAX(user_visible_latency_ms) AS max_latency_ms,
|
|
46
|
+
STDDEV(user_visible_latency_ms) AS stddev_latency_ms,
|
|
47
|
+
COUNT(*) AS sample_count
|
|
48
|
+
FROM injection_effectiveness
|
|
49
|
+
WHERE user_visible_latency_ms IS NOT NULL
|
|
50
|
+
AND cohort IS NOT NULL
|
|
51
|
+
GROUP BY DATE_TRUNC('hour', created_at), cohort;
|
|
52
|
+
|
|
53
|
+
-- ============================================================================
|
|
54
|
+
-- INDEXES FOR MATERIALIZED VIEW
|
|
55
|
+
-- ============================================================================
|
|
56
|
+
|
|
57
|
+
-- Required for REFRESH CONCURRENTLY
|
|
58
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_latency_baseline_hour_cohort
|
|
59
|
+
ON latency_baseline (hour, cohort);
|
|
60
|
+
|
|
61
|
+
-- Fast lookups by cohort
|
|
62
|
+
CREATE INDEX IF NOT EXISTS idx_latency_baseline_cohort
|
|
63
|
+
ON latency_baseline (cohort);
|
|
64
|
+
|
|
65
|
+
-- Recent hour queries
|
|
66
|
+
CREATE INDEX IF NOT EXISTS idx_latency_baseline_hour_desc
|
|
67
|
+
ON latency_baseline (hour DESC);
|
|
68
|
+
|
|
69
|
+
-- ============================================================================
|
|
70
|
+
-- COMMENTS
|
|
71
|
+
-- ============================================================================
|
|
72
|
+
|
|
73
|
+
COMMENT ON MATERIALIZED VIEW latency_baseline IS
|
|
74
|
+
'Hourly latency baseline per cohort for A/B testing comparison (OMN-1890). '
|
|
75
|
+
'Refresh hourly via pg_cron. Use COALESCE with 7-day rolling fallback for '
|
|
76
|
+
'hours with insufficient sample_count.';
|
|
77
|
+
|
|
78
|
+
COMMENT ON COLUMN latency_baseline.hour IS
|
|
79
|
+
'Truncated hour timestamp for aggregation';
|
|
80
|
+
COMMENT ON COLUMN latency_baseline.cohort IS
|
|
81
|
+
'A/B test cohort: control or treatment';
|
|
82
|
+
COMMENT ON COLUMN latency_baseline.avg_latency_ms IS
|
|
83
|
+
'Average user-visible latency in milliseconds';
|
|
84
|
+
COMMENT ON COLUMN latency_baseline.p50_latency_ms IS
|
|
85
|
+
'Median (50th percentile) latency';
|
|
86
|
+
COMMENT ON COLUMN latency_baseline.p95_latency_ms IS
|
|
87
|
+
'95th percentile latency for tail analysis';
|
|
88
|
+
COMMENT ON COLUMN latency_baseline.p99_latency_ms IS
|
|
89
|
+
'99th percentile latency for extreme tail analysis';
|
|
90
|
+
COMMENT ON COLUMN latency_baseline.sample_count IS
|
|
91
|
+
'Number of sessions in this hour/cohort. Check for reliability (recommended N>=20).';
|
|
92
|
+
|
|
93
|
+
-- ============================================================================
|
|
94
|
+
-- HELPER FUNCTION: Refresh with logging
|
|
95
|
+
-- ============================================================================
|
|
96
|
+
|
|
97
|
+
-- Note: REFRESH MATERIALIZED VIEW CONCURRENTLY cannot be used inside PL/pgSQL
|
|
98
|
+
-- functions due to transaction context restrictions. Use regular REFRESH here.
|
|
99
|
+
-- For concurrent refresh (allowing queries during refresh), call directly via
|
|
100
|
+
-- pg_cron: REFRESH MATERIALIZED VIEW CONCURRENTLY latency_baseline;
|
|
101
|
+
|
|
102
|
+
CREATE OR REPLACE FUNCTION refresh_latency_baseline()
|
|
103
|
+
RETURNS void AS $$
|
|
104
|
+
DECLARE
|
|
105
|
+
start_time TIMESTAMPTZ;
|
|
106
|
+
end_time TIMESTAMPTZ;
|
|
107
|
+
duration_ms NUMERIC;
|
|
108
|
+
row_count INTEGER;
|
|
109
|
+
BEGIN
|
|
110
|
+
start_time := clock_timestamp();
|
|
111
|
+
|
|
112
|
+
-- Use regular REFRESH (not CONCURRENTLY) inside PL/pgSQL function.
|
|
113
|
+
-- CONCURRENTLY is not allowed inside PL/pgSQL transaction context.
|
|
114
|
+
REFRESH MATERIALIZED VIEW latency_baseline;
|
|
115
|
+
|
|
116
|
+
end_time := clock_timestamp();
|
|
117
|
+
|
|
118
|
+
-- Get row count for logging
|
|
119
|
+
SELECT COUNT(*) INTO row_count FROM latency_baseline;
|
|
120
|
+
|
|
121
|
+
-- Calculate total duration in milliseconds using EPOCH (handles any duration).
|
|
122
|
+
-- Note: EXTRACT(MILLISECONDS FROM interval) only returns the ms component,
|
|
123
|
+
-- not total ms. EXTRACT(EPOCH FROM ...) returns total seconds as decimal.
|
|
124
|
+
duration_ms := EXTRACT(EPOCH FROM (end_time - start_time)) * 1000;
|
|
125
|
+
|
|
126
|
+
RAISE NOTICE 'latency_baseline refreshed: % rows in % ms',
|
|
127
|
+
row_count,
|
|
128
|
+
ROUND(duration_ms)::INTEGER;
|
|
129
|
+
END;
|
|
130
|
+
$$ LANGUAGE plpgsql;
|
|
131
|
+
|
|
132
|
+
COMMENT ON FUNCTION refresh_latency_baseline() IS
|
|
133
|
+
'Refresh latency_baseline materialized view with timing log. '
|
|
134
|
+
'Uses regular REFRESH (not CONCURRENTLY) due to PL/pgSQL transaction restrictions. '
|
|
135
|
+
'For concurrent refresh, call directly: REFRESH MATERIALIZED VIEW CONCURRENTLY latency_baseline;';
|
|
@@ -58,7 +58,7 @@ class ModelContractPublisherConfig(BaseModel):
|
|
|
58
58
|
package_module: Module name for package resource discovery
|
|
59
59
|
fail_fast: If True, raise immediately on infrastructure errors
|
|
60
60
|
allow_zero_contracts: If True, allow empty publish results
|
|
61
|
-
environment: Environment
|
|
61
|
+
environment: Environment identifier (used for consumer groups, not topic naming)
|
|
62
62
|
|
|
63
63
|
Example:
|
|
64
64
|
>>> config = ModelContractPublisherConfig(
|
|
@@ -67,8 +67,8 @@ class ModelContractPublisherConfig(BaseModel):
|
|
|
67
67
|
... fail_fast=True,
|
|
68
68
|
... allow_zero_contracts=False,
|
|
69
69
|
... )
|
|
70
|
-
>>>
|
|
71
|
-
>>> print(
|
|
70
|
+
>>> # Topics are realm-agnostic (no environment prefix)
|
|
71
|
+
>>> print("Publishing to onex.evt.contract-registered.v1")
|
|
72
72
|
|
|
73
73
|
.. versionadded:: 0.3.0
|
|
74
74
|
"""
|
|
@@ -96,7 +96,7 @@ class ModelContractPublisherConfig(BaseModel):
|
|
|
96
96
|
)
|
|
97
97
|
environment: str | None = Field(
|
|
98
98
|
default=None,
|
|
99
|
-
description="Environment
|
|
99
|
+
description="Environment identifier for consumer groups (resolved via resolve_environment)",
|
|
100
100
|
)
|
|
101
101
|
|
|
102
102
|
@model_validator(mode="after")
|
|
@@ -26,7 +26,6 @@ from uuid import uuid4
|
|
|
26
26
|
import yaml
|
|
27
27
|
from pydantic import ValidationError
|
|
28
28
|
|
|
29
|
-
from omnibase_core.constants import TOPIC_SUFFIX_CONTRACT_REGISTERED
|
|
30
29
|
from omnibase_core.models.contracts.model_handler_contract import ModelHandlerContract
|
|
31
30
|
from omnibase_core.models.events import ModelContractRegisteredEvent
|
|
32
31
|
from omnibase_core.protocols.event_bus import ProtocolEventBusPublisher
|
|
@@ -35,6 +34,9 @@ from omnibase_infra.errors import (
|
|
|
35
34
|
InfraTimeoutError,
|
|
36
35
|
InfraUnavailableError,
|
|
37
36
|
)
|
|
37
|
+
from omnibase_infra.runtime.contract_registration_event_router import (
|
|
38
|
+
TOPIC_SUFFIX_CONTRACT_REGISTERED,
|
|
39
|
+
)
|
|
38
40
|
from omnibase_infra.services.contract_publisher.config import (
|
|
39
41
|
ModelContractPublisherConfig,
|
|
40
42
|
)
|
|
@@ -214,17 +216,18 @@ class ServiceContractPublisher:
|
|
|
214
216
|
)
|
|
215
217
|
|
|
216
218
|
def resolve_topic(self, topic_suffix: str) -> str:
|
|
217
|
-
"""Resolve topic suffix to
|
|
219
|
+
"""Resolve topic suffix to topic name (realm-agnostic, no environment prefix).
|
|
218
220
|
|
|
219
|
-
|
|
221
|
+
Topics are realm-agnostic in ONEX. The environment/realm is enforced via
|
|
222
|
+
envelope identity, not topic naming.
|
|
220
223
|
|
|
221
224
|
Args:
|
|
222
225
|
topic_suffix: Topic suffix (e.g., "onex.evt.contract-registered.v1")
|
|
223
226
|
|
|
224
227
|
Returns:
|
|
225
|
-
|
|
228
|
+
Topic name (same as suffix, no environment prefix)
|
|
226
229
|
"""
|
|
227
|
-
return
|
|
230
|
+
return topic_suffix
|
|
228
231
|
|
|
229
232
|
async def publish_all(self) -> ModelPublishResult:
|
|
230
233
|
"""Discover and publish all contracts from configured source.
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
# Copyright (c) 2025 OmniNode Team
|
|
3
|
+
"""Injection Effectiveness Observability Service.
|
|
4
|
+
|
|
5
|
+
This module provides Kafka consumers and PostgreSQL writers for injection
|
|
6
|
+
effectiveness metrics collected from omniclaude hooks.
|
|
7
|
+
|
|
8
|
+
Topics consumed:
|
|
9
|
+
- onex.evt.omniclaude.context-utilization.v1
|
|
10
|
+
- onex.evt.omniclaude.agent-match.v1
|
|
11
|
+
- onex.evt.omniclaude.latency-breakdown.v1
|
|
12
|
+
|
|
13
|
+
Related Tickets:
|
|
14
|
+
- OMN-1890: Store injection metrics with corrected schema
|
|
15
|
+
- OMN-1889: Emit injection metrics + utilization signal (producer)
|
|
16
|
+
|
|
17
|
+
Example:
|
|
18
|
+
>>> from omnibase_infra.services.observability.injection_effectiveness import (
|
|
19
|
+
... InjectionEffectivenessConsumer,
|
|
20
|
+
... ConfigInjectionEffectivenessConsumer,
|
|
21
|
+
... )
|
|
22
|
+
>>>
|
|
23
|
+
>>> config = ConfigInjectionEffectivenessConsumer(
|
|
24
|
+
... kafka_bootstrap_servers="localhost:9092",
|
|
25
|
+
... postgres_dsn="postgresql://postgres:secret@localhost:5432/omninode_bridge",
|
|
26
|
+
... )
|
|
27
|
+
>>> consumer = InjectionEffectivenessConsumer(config)
|
|
28
|
+
>>>
|
|
29
|
+
>>> await consumer.start()
|
|
30
|
+
>>> await consumer.run()
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
from omnibase_infra.services.observability.injection_effectiveness.config import (
|
|
34
|
+
ConfigInjectionEffectivenessConsumer,
|
|
35
|
+
)
|
|
36
|
+
from omnibase_infra.services.observability.injection_effectiveness.consumer import (
|
|
37
|
+
TOPIC_TO_MODEL,
|
|
38
|
+
TOPIC_TO_WRITER_METHOD,
|
|
39
|
+
ConsumerMetrics,
|
|
40
|
+
EnumHealthStatus,
|
|
41
|
+
InjectionEffectivenessConsumer,
|
|
42
|
+
mask_dsn_password,
|
|
43
|
+
)
|
|
44
|
+
from omnibase_infra.services.observability.injection_effectiveness.models import (
|
|
45
|
+
ModelAgentMatchEvent,
|
|
46
|
+
ModelContextUtilizationEvent,
|
|
47
|
+
ModelLatencyBreakdownEvent,
|
|
48
|
+
ModelPatternUtilization,
|
|
49
|
+
)
|
|
50
|
+
from omnibase_infra.services.observability.injection_effectiveness.writer_postgres import (
|
|
51
|
+
WriterInjectionEffectivenessPostgres,
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
__all__ = [
|
|
55
|
+
"ConfigInjectionEffectivenessConsumer",
|
|
56
|
+
"ConsumerMetrics",
|
|
57
|
+
"EnumHealthStatus",
|
|
58
|
+
"InjectionEffectivenessConsumer",
|
|
59
|
+
"ModelAgentMatchEvent",
|
|
60
|
+
"ModelContextUtilizationEvent",
|
|
61
|
+
"ModelLatencyBreakdownEvent",
|
|
62
|
+
"ModelPatternUtilization",
|
|
63
|
+
"TOPIC_TO_MODEL",
|
|
64
|
+
"TOPIC_TO_WRITER_METHOD",
|
|
65
|
+
"WriterInjectionEffectivenessPostgres",
|
|
66
|
+
"mask_dsn_password",
|
|
67
|
+
]
|
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
# Copyright (c) 2025 OmniNode Team
|
|
3
|
+
"""Configuration for injection effectiveness observability consumer.
|
|
4
|
+
|
|
5
|
+
This module provides Pydantic Settings configuration for the injection
|
|
6
|
+
effectiveness Kafka consumer service. Configuration is loaded from environment
|
|
7
|
+
variables with the ``OMNIBASE_INFRA_INJECTION_EFFECTIVENESS_`` prefix.
|
|
8
|
+
|
|
9
|
+
Configuration Groups:
|
|
10
|
+
- **Kafka**: Bootstrap servers, consumer group, topics, auto-offset reset
|
|
11
|
+
- **PostgreSQL**: DSN connection string, pool sizing
|
|
12
|
+
- **Batch Processing**: Batch size, timeout, poll buffer
|
|
13
|
+
- **Circuit Breaker**: Threshold, reset timeout, half-open successes
|
|
14
|
+
- **Health Check**: Port, host, staleness thresholds, startup grace period
|
|
15
|
+
- **Pattern Analytics**: Minimum support threshold for statistical confidence
|
|
16
|
+
|
|
17
|
+
Environment Variables:
|
|
18
|
+
All configuration values can be set via environment variables with the
|
|
19
|
+
``OMNIBASE_INFRA_INJECTION_EFFECTIVENESS_`` prefix. For example:
|
|
20
|
+
|
|
21
|
+
- ``OMNIBASE_INFRA_INJECTION_EFFECTIVENESS_KAFKA_BOOTSTRAP_SERVERS``
|
|
22
|
+
- ``OMNIBASE_INFRA_INJECTION_EFFECTIVENESS_POSTGRES_DSN``
|
|
23
|
+
- ``OMNIBASE_INFRA_INJECTION_EFFECTIVENESS_BATCH_SIZE``
|
|
24
|
+
- ``OMNIBASE_INFRA_INJECTION_EFFECTIVENESS_CIRCUIT_BREAKER_THRESHOLD``
|
|
25
|
+
|
|
26
|
+
Validation:
|
|
27
|
+
The configuration validates:
|
|
28
|
+
- At least one topic must be configured
|
|
29
|
+
- Pool min size must be <= pool max size
|
|
30
|
+
- Timing relationships (warns if circuit breaker timeout < 2x batch timeout)
|
|
31
|
+
|
|
32
|
+
Related Tickets:
|
|
33
|
+
- OMN-1890: Store injection metrics with corrected schema
|
|
34
|
+
- OMN-1889: Emit injection metrics from omniclaude hooks (producer)
|
|
35
|
+
|
|
36
|
+
Example:
|
|
37
|
+
>>> from omnibase_infra.services.observability.injection_effectiveness.config import (
|
|
38
|
+
... ConfigInjectionEffectivenessConsumer,
|
|
39
|
+
... )
|
|
40
|
+
>>>
|
|
41
|
+
>>> # Load from environment (default)
|
|
42
|
+
>>> config = ConfigInjectionEffectivenessConsumer()
|
|
43
|
+
>>>
|
|
44
|
+
>>> # Or with explicit values
|
|
45
|
+
>>> config = ConfigInjectionEffectivenessConsumer(
|
|
46
|
+
... kafka_bootstrap_servers="kafka.example.com:9092",
|
|
47
|
+
... postgres_dsn="postgresql://user:pass@host:5432/db",
|
|
48
|
+
... batch_size=200,
|
|
49
|
+
... )
|
|
50
|
+
>>>
|
|
51
|
+
>>> print(config.topics)
|
|
52
|
+
['onex.evt.omniclaude.context-utilization.v1', ...]
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
from __future__ import annotations
|
|
56
|
+
|
|
57
|
+
import logging
|
|
58
|
+
from typing import Self
|
|
59
|
+
|
|
60
|
+
from pydantic import Field, model_validator
|
|
61
|
+
from pydantic_settings import BaseSettings, SettingsConfigDict
|
|
62
|
+
|
|
63
|
+
from omnibase_infra.enums import EnumInfraTransportType
|
|
64
|
+
from omnibase_infra.errors import ModelInfraErrorContext, ProtocolConfigurationError
|
|
65
|
+
|
|
66
|
+
logger = logging.getLogger(__name__)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class ConfigInjectionEffectivenessConsumer(BaseSettings):
|
|
70
|
+
"""Configuration for the injection effectiveness Kafka consumer.
|
|
71
|
+
|
|
72
|
+
Environment variables use the OMNIBASE_INFRA_INJECTION_EFFECTIVENESS_ prefix.
|
|
73
|
+
Example: OMNIBASE_INFRA_INJECTION_EFFECTIVENESS_KAFKA_BOOTSTRAP_SERVERS=kafka.example.com:9092
|
|
74
|
+
|
|
75
|
+
This consumer subscribes to injection effectiveness topics and
|
|
76
|
+
persists events to PostgreSQL for A/B testing analytics.
|
|
77
|
+
"""
|
|
78
|
+
|
|
79
|
+
model_config = SettingsConfigDict(
|
|
80
|
+
env_prefix="OMNIBASE_INFRA_INJECTION_EFFECTIVENESS_",
|
|
81
|
+
env_file=".env",
|
|
82
|
+
env_file_encoding="utf-8",
|
|
83
|
+
case_sensitive=False,
|
|
84
|
+
extra="ignore",
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
# Kafka connection
|
|
88
|
+
kafka_bootstrap_servers: str = Field(
|
|
89
|
+
default="localhost:9092",
|
|
90
|
+
description=(
|
|
91
|
+
"Kafka bootstrap servers. Set via "
|
|
92
|
+
"OMNIBASE_INFRA_INJECTION_EFFECTIVENESS_KAFKA_BOOTSTRAP_SERVERS env var."
|
|
93
|
+
),
|
|
94
|
+
)
|
|
95
|
+
kafka_group_id: str = Field(
|
|
96
|
+
default="injection-effectiveness-postgres",
|
|
97
|
+
description="Consumer group ID for offset tracking",
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
# Topics to subscribe (3 injection effectiveness topics from OMN-1889)
|
|
101
|
+
topics: list[str] = Field(
|
|
102
|
+
default_factory=lambda: [
|
|
103
|
+
"onex.evt.omniclaude.context-utilization.v1",
|
|
104
|
+
"onex.evt.omniclaude.agent-match.v1",
|
|
105
|
+
"onex.evt.omniclaude.latency-breakdown.v1",
|
|
106
|
+
],
|
|
107
|
+
description="Kafka topics to consume for injection effectiveness",
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
# Consumer behavior
|
|
111
|
+
auto_offset_reset: str = Field(
|
|
112
|
+
default="earliest",
|
|
113
|
+
description="Where to start consuming if no offset exists",
|
|
114
|
+
)
|
|
115
|
+
enable_auto_commit: bool = Field(
|
|
116
|
+
default=False,
|
|
117
|
+
description="Disable auto-commit for at-least-once delivery",
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
# PostgreSQL connection
|
|
121
|
+
postgres_dsn: str = Field(
|
|
122
|
+
description=(
|
|
123
|
+
"PostgreSQL connection string. Set via "
|
|
124
|
+
"OMNIBASE_INFRA_INJECTION_EFFECTIVENESS_POSTGRES_DSN env var."
|
|
125
|
+
),
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
# Batch processing
|
|
129
|
+
batch_size: int = Field(
|
|
130
|
+
default=100,
|
|
131
|
+
ge=1,
|
|
132
|
+
le=1000,
|
|
133
|
+
description="Maximum records per batch write",
|
|
134
|
+
)
|
|
135
|
+
batch_timeout_ms: int = Field(
|
|
136
|
+
default=1000,
|
|
137
|
+
ge=100,
|
|
138
|
+
le=60000,
|
|
139
|
+
description="Timeout for batch accumulation in milliseconds",
|
|
140
|
+
)
|
|
141
|
+
poll_timeout_buffer_seconds: float = Field(
|
|
142
|
+
default=5.0,
|
|
143
|
+
ge=1.0,
|
|
144
|
+
le=30.0,
|
|
145
|
+
description=(
|
|
146
|
+
"Additional buffer time added to batch_timeout_ms for asyncio.wait_for."
|
|
147
|
+
),
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
# Circuit breaker
|
|
151
|
+
circuit_breaker_threshold: int = Field(
|
|
152
|
+
default=5,
|
|
153
|
+
ge=1,
|
|
154
|
+
le=100,
|
|
155
|
+
description="Failures before circuit opens",
|
|
156
|
+
)
|
|
157
|
+
circuit_breaker_reset_timeout: float = Field(
|
|
158
|
+
default=60.0,
|
|
159
|
+
ge=1.0,
|
|
160
|
+
le=3600.0,
|
|
161
|
+
description="Seconds before circuit half-opens for retry",
|
|
162
|
+
)
|
|
163
|
+
circuit_breaker_half_open_successes: int = Field(
|
|
164
|
+
default=1,
|
|
165
|
+
ge=1,
|
|
166
|
+
le=10,
|
|
167
|
+
description="Successful requests required to close circuit from half-open state",
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
# Minimum support gating for pattern confidence (R3 requirement)
|
|
171
|
+
min_pattern_support: int = Field(
|
|
172
|
+
default=20,
|
|
173
|
+
ge=1,
|
|
174
|
+
le=1000,
|
|
175
|
+
description=(
|
|
176
|
+
"Minimum number of sessions required before pattern utilization "
|
|
177
|
+
"metrics are considered statistically reliable (N=20 default)."
|
|
178
|
+
),
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
# PostgreSQL pool settings
|
|
182
|
+
pool_min_size: int = Field(
|
|
183
|
+
default=2,
|
|
184
|
+
ge=1,
|
|
185
|
+
le=20,
|
|
186
|
+
description="Minimum number of connections in the PostgreSQL connection pool.",
|
|
187
|
+
)
|
|
188
|
+
pool_max_size: int = Field(
|
|
189
|
+
default=10,
|
|
190
|
+
ge=2,
|
|
191
|
+
le=100,
|
|
192
|
+
description="Maximum number of connections in the PostgreSQL connection pool.",
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
# Health check
|
|
196
|
+
health_check_port: int = Field(
|
|
197
|
+
default=8088,
|
|
198
|
+
ge=1024,
|
|
199
|
+
le=65535,
|
|
200
|
+
description="Port for HTTP health check endpoint",
|
|
201
|
+
)
|
|
202
|
+
health_check_host: str = Field(
|
|
203
|
+
default="0.0.0.0", # noqa: S104 - Configurable for container access
|
|
204
|
+
description="Host/IP for health check server binding.",
|
|
205
|
+
)
|
|
206
|
+
health_check_staleness_seconds: int = Field(
|
|
207
|
+
default=300,
|
|
208
|
+
ge=60,
|
|
209
|
+
le=3600,
|
|
210
|
+
description="Maximum age for last successful write before DEGRADED status.",
|
|
211
|
+
)
|
|
212
|
+
health_check_poll_staleness_seconds: int = Field(
|
|
213
|
+
default=60,
|
|
214
|
+
ge=10,
|
|
215
|
+
le=300,
|
|
216
|
+
description="Maximum age for last poll before DEGRADED status.",
|
|
217
|
+
)
|
|
218
|
+
startup_grace_period_seconds: float = Field(
|
|
219
|
+
default=60.0,
|
|
220
|
+
ge=10.0,
|
|
221
|
+
le=300.0,
|
|
222
|
+
description=(
|
|
223
|
+
"Grace period in seconds after startup during which the consumer is "
|
|
224
|
+
"considered healthy even without successful writes. Allows time for "
|
|
225
|
+
"initial Kafka partition assignment and first message processing."
|
|
226
|
+
),
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
@model_validator(mode="after")
|
|
230
|
+
def validate_topic_configuration(self) -> Self:
|
|
231
|
+
"""Ensure topics are configured.
|
|
232
|
+
|
|
233
|
+
Returns:
|
|
234
|
+
Self if validation passes.
|
|
235
|
+
|
|
236
|
+
Raises:
|
|
237
|
+
ProtocolConfigurationError: If no topics are configured.
|
|
238
|
+
"""
|
|
239
|
+
if not self.topics:
|
|
240
|
+
# Auto-generate correlation_id for configuration errors
|
|
241
|
+
# (no request context available during model validation)
|
|
242
|
+
context = ModelInfraErrorContext.with_correlation(
|
|
243
|
+
transport_type=EnumInfraTransportType.RUNTIME,
|
|
244
|
+
operation="validate_topic_configuration",
|
|
245
|
+
target_name="ConfigInjectionEffectivenessConsumer",
|
|
246
|
+
)
|
|
247
|
+
raise ProtocolConfigurationError(
|
|
248
|
+
"No topics configured for injection effectiveness consumer.",
|
|
249
|
+
context=context,
|
|
250
|
+
)
|
|
251
|
+
return self
|
|
252
|
+
|
|
253
|
+
@model_validator(mode="after")
|
|
254
|
+
def validate_timing_relationships(self) -> Self:
|
|
255
|
+
"""Validate timing relationships between configuration values.
|
|
256
|
+
|
|
257
|
+
Returns:
|
|
258
|
+
Self if validation passes.
|
|
259
|
+
"""
|
|
260
|
+
batch_timeout_seconds = self.batch_timeout_ms / 1000
|
|
261
|
+
min_recommended_circuit_timeout = batch_timeout_seconds * 2
|
|
262
|
+
|
|
263
|
+
if self.circuit_breaker_reset_timeout < min_recommended_circuit_timeout:
|
|
264
|
+
logger.warning(
|
|
265
|
+
"Circuit breaker timeout (%.1fs) is less than 2x batch timeout (%.1fs).",
|
|
266
|
+
self.circuit_breaker_reset_timeout,
|
|
267
|
+
batch_timeout_seconds,
|
|
268
|
+
)
|
|
269
|
+
return self
|
|
270
|
+
|
|
271
|
+
@model_validator(mode="after")
|
|
272
|
+
def validate_pool_size_relationship(self) -> Self:
|
|
273
|
+
"""Validate pool size relationship (min <= max).
|
|
274
|
+
|
|
275
|
+
Returns:
|
|
276
|
+
Self if validation passes.
|
|
277
|
+
|
|
278
|
+
Raises:
|
|
279
|
+
ProtocolConfigurationError: If pool_min_size > pool_max_size.
|
|
280
|
+
"""
|
|
281
|
+
if self.pool_min_size > self.pool_max_size:
|
|
282
|
+
context = ModelInfraErrorContext.with_correlation(
|
|
283
|
+
transport_type=EnumInfraTransportType.RUNTIME,
|
|
284
|
+
operation="validate_pool_size_relationship",
|
|
285
|
+
target_name="ConfigInjectionEffectivenessConsumer",
|
|
286
|
+
)
|
|
287
|
+
raise ProtocolConfigurationError(
|
|
288
|
+
f"pool_min_size ({self.pool_min_size}) must be <= pool_max_size "
|
|
289
|
+
f"({self.pool_max_size}).",
|
|
290
|
+
context=context,
|
|
291
|
+
)
|
|
292
|
+
return self
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
__all__ = ["ConfigInjectionEffectivenessConsumer"]
|