Skip to content

feat(gateway): Add runtime gateway with envelope signing and realm enforcement [OMN-1899] - #249

Merged
jonahgabriel merged 17 commits into
mainfrom
feat/OMN-1899-runtime-gateway-envelope-signing
Feb 6, 2026
Merged

jonahgabriel merged 17 commits into
mainfrom
feat/OMN-1899-runtime-gateway-envelope-signing

Conversation

@jonahgabriel

@jonahgabriel jonahgabriel commented Feb 5, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Implement runtime gateway functionality that signs outbound envelopes and validates inbound envelopes, enforcing realm boundaries per the ONEX security architecture.

  • R1: Runtime Configuration - Add ModelGatewayConfig with realm, runtime_id, Ed25519 keypair paths, topic allowlist
  • R2: Outbound Envelope Signing - ServiceEnvelopeSigner wraps payloads in ModelMessageEnvelope with Ed25519 signatures
  • R3: Inbound Envelope Validation - ServiceEnvelopeValidator validates signatures, detects tampering, enforces realm boundaries
  • R4: Policy Engine - ServicePolicyEngine enforces topic allowlist with wildcard support

Architecture

┌──────────────┐        ┌──────────────┐
│ Runtime A    │        │ Runtime B    │
│ (realm: dev) │        │ (realm: prod)│
│  ┌────────┐  │        │ ┌────────┐   │
│  │ Policy │  │        │ │ Policy │   │
│  │ Engine │  │        │ │ Engine │   │
│  └───┬────┘  │        │ └───┬────┘   │
│  ┌───▼────┐  │        │ ┌───▼────┐   │
│  │ Gateway│◄─────────►│ │ Gateway│   │
│  └───┬────┘  │        │ └───┬────┘   │
│  ┌───▼────┐  │        │ ┌───▼────┐   │
│  │  Bus   │  │        │ │  Bus   │   │
│  └────────┘  │        │ └────────┘   │
└──────────────┘        └──────────────┘

Key Rule: Messages never cross realms directly. They cross only via runtime gateways with explicit policy.

Components Created

Component Location Purpose
ModelGatewayConfig gateway/models/ Configuration model
ServiceEnvelopeSigner gateway/services/ Ed25519 envelope signing
ServiceEnvelopeValidator gateway/services/ Signature & realm validation
ServicePolicyEngine gateway/services/ Topic allowlist enforcement
util_key_loader gateway/utils/ Ed25519 PEM key loading

Runtime Integration

  • Gateway initialization in RuntimeHostProcess.start()
  • Outbound signing in _publish_envelope_safe()
  • Inbound validation in _handle_envelope()
  • Graceful degradation when gateway disabled (default)

Dependencies

  • Upgraded omnibase-core to 0.15.0 for ModelMessageEnvelope
  • Added blake3 for cryptographic hashing (transitive from core)

Test Plan

  • 92 integration tests covering signing, validation, policy, and config
  • All tests pass: poetry run pytest tests/integration/gateway/ -v
  • Type checking passes: poetry run mypy src/omnibase_infra/gateway/
  • Lint passes: poetry run ruff check src/omnibase_infra/gateway/
  • Pre-commit hooks pass

Blocked By

  • OMN-1898 ✅ (ModelMessageEnvelope in omnibase-core 0.15.0)

Linear

Closes OMN-1899

Summary by CodeRabbit

  • New Features

    • Gateway support: envelope signing, signature validation, message policy enforcement, key-loading utilities, and a frozen gateway configuration; runtime can wire signing/validation/policy.
  • Tests

    • Extensive integration tests covering signing, validation, policy rules, key loading, gateway configuration, and runtime publish flows.
  • Chores

    • Updated core pre-release note to reference v0.15.0.
  • Bug Fixes

    • Duplicate validation exemption entries were introduced and should be removed.

…forcement [OMN-1899]

Implement runtime gateway functionality that signs outbound envelopes and
validates inbound envelopes, enforcing realm boundaries per the ONEX
security architecture.

Components added:
- ModelGatewayConfig: Configuration for realm, runtime_id, keys, policies
- ServiceEnvelopeSigner: Signs payloads with Ed25519, wraps in ModelMessageEnvelope
- ServiceEnvelopeValidator: Validates signatures, realms, detects tampering
- ServicePolicyEngine: Enforces topic allowlist with wildcard support
- Key loader utilities for Ed25519 PEM files

Runtime integration:
- Gateway initialization in RuntimeHostProcess.start()
- Outbound signing in _publish_envelope_safe()
- Inbound validation in _handle_envelope()
- Graceful degradation when gateway disabled (default)

Dependencies:
- Upgraded omnibase-core to 0.15.0 for ModelMessageEnvelope
- Added blake3 for cryptographic hashing

Validation updates:
- Increased INFRA_MAX_UNIONS to 119 (EnumPolicyDecision union)
- Added pattern exemptions for runtime_id (str by design) and sign_envelope params

Tests: 92 integration tests covering signing, validation, policy, and config
@coderabbitai

coderabbitai Bot commented Feb 5, 2026 •

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

Adds a gateway subsystem (signer, validator, policy engine, key loaders, config model), exposes a consolidated gateway package API, wires lazy gateway initialization and inbound/outbound signing/validation into RuntimeHostProcess, bumps omnibase-core to ^0.15.0, adds extensive integration tests, and inserts duplicated validation exemptions.

Changes

Cohort / File(s) Summary
Dependency
pyproject.toml
Bumps omnibase-core to ^0.15.0 and updates release note to describe gateway additions.
Gateway Package Surface
src/omnibase_infra/gateway/__init__.py
New package initializer re-exporting enums, models, services, and utils to form the public gateway API (__all__).
Enums
src/omnibase_infra/gateway/enums/...
src/omnibase_infra/gateway/enums/enum_validation_error.py
Adds EnumValidationErrorCode and package __init__.
Models
src/omnibase_infra/gateway/models/...
src/omnibase_infra/gateway/models/model_gateway_config.py
Adds frozen ModelGatewayConfig (realm, runtime_id, key paths, enabled, allowed_topics, reject_unsigned) with strict Pydantic validation and examples.
Services — Signer
src/omnibase_infra/gateway/services/...
src/omnibase_infra/gateway/services/service_envelope_signer.py
Implements ServiceEnvelopeSigner: canonical JSON → Blake3 hash → Ed25519 signature; provides sign_envelope and sign_dict.
Services — Validator
src/omnibase_infra/gateway/services/...
src/omnibase_infra/gateway/services/service_envelope_validator.py
Implements ServiceEnvelopeValidator and ValidationResult: realm checks, unsigned handling, trusted signer registry, payload hash recompute, Ed25519 verification, and explicit error codes.
Services — Policy Engine
src/omnibase_infra/gateway/services/...
src/omnibase_infra/gateway/services/service_policy_engine.py
Adds ServicePolicyEngine, EnumPolicyDecision, and PolicyDecision for topic allowlisting (glob patterns) and optional realm enforcement.
Key Utilities
src/omnibase_infra/gateway/utils/...
src/omnibase_infra/gateway/utils/util_key_loader.py
Adds PEM Ed25519 loaders load_private_key_from_pem and load_public_key_from_pem with validation, contextual errors, and secure logging guidance.
Runtime Integration
src/omnibase_infra/runtime/models/model_runtime_config.py, src/omnibase_infra/runtime/service_runtime_host_process.py
Adds optional gateway field to runtime config and lazy gateway initialization in RuntimeHostProcess; wires inbound validation, outbound signing, and policy checks with fail-open semantics and serialization updates.
Validation Exemptions
src/omnibase_infra/validation/validation_exemptions.yaml
Adds exemptions for signer methods and runtime_id rule (OMN-1899); entries are duplicated, resulting in repeated blocks.
Tests — Integration Suite
tests/integration/gateway/..., tests/integration/...
Adds integration test package, fixtures, and extensive test suites for signer, validator, policy engine, key loader, gateway config, and runtime gateway end-to-end flows (many new tests).
Tests — Misc
tests/unit/observability/wiring_health/test_mixin_emission_counter.py, tests/integration/runtime/test_runtime_introspection.py
Minor test reformatting and test-mark additions. Note: small unrelated edits present.

Sequence Diagram(s)

sequenceDiagram
    participant Client as Client/Service
    participant Runtime as RuntimeHostProcess
    participant Policy as ServicePolicyEngine
    participant Signer as ServiceEnvelopeSigner
    participant Validator as ServiceEnvelopeValidator
    Client->>Runtime: publish(payload, topic)
    Runtime->>Policy: evaluate_outbound(topic)
    Policy-->>Runtime: PolicyDecision (allow/deny)
    alt allowed and signer configured
        Runtime->>Signer: sign_envelope(payload, bus_id, ...)
        Signer-->>Runtime: ModelMessageEnvelope (signed)
    end
    Runtime->>Runtime: serialize / publish to bus
    Note right of Runtime: incoming message received
    Runtime->>Validator: validate_envelope(incoming_envelope)
    Validator->>Validator: check realm, recompute Blake3(hash), lookup signer
    Validator->>Validator: verify Ed25519 signature
    Validator-->>Runtime: ValidationResult (is_valid / error_code)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐰 I hummed a Blake3 tune and twitched my nose,
I signed small packets where the data flows,
I checked the realm and counted keys,
I hop-approved or shook my knees,
A tiny seal — the gateway grows.

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title clearly and specifically describes the main change: adding a runtime gateway with envelope signing and realm enforcement. It references the ticket number and is concise.
Docstring Coverage ✅ Passed Docstring coverage is 99.53% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/OMN-1899-runtime-gateway-envelope-signing

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/omnibase_infra/runtime/service_runtime_host_process.py (1)

55-55: ⚠️ Potential issue | 🟠 Major

Dict envelopes skip gateway signing and gateway logs lack correlation_id

When gateway signing is enabled, only BaseModel payloads are signed (line 2533). Error and duplicate responses are published as dicts via _create_error_response() and _create_duplicate_response().model_dump(), bypassing the signer and causing rejection by downstream runtimes with reject_unsigned=True.

Additionally, gateway validation rejection logs (lines 3778–3851) lack correlation_id in their structured context, violating the coding guideline to include correlation_id in all error contexts for observability.

Recommended fixes:

  1. Wrap dict envelopes in RootModel[dict[str, object]] before signing, or extract correlation_id from dict envelopes and pass it to the signer
  2. Extract correlation_id from inbound envelope and include it in all gateway rejection log messages
🤖 Fix all issues with AI agents
In `@src/omnibase_infra/gateway/services/service_policy_engine.py`:
- Around line 223-277: Add an optional correlation_id parameter to
evaluate_inbound (and mirror the same change in evaluate_outbound) so callers
can pass a trace id; inside each function, if correlation_id is None generate
one with uuid.uuid4(); when calling _log_rejection("inbound"/"outbound", topic,
reason) include the correlation_id in the log extra/context payload so the
rejection warnings carry the id; update any return paths that call
_log_rejection to forward the generated or provided correlation_id and ensure
function signatures (evaluate_inbound, evaluate_outbound) and any call sites are
updated to accept/propagate the new parameter.

In `@src/omnibase_infra/gateway/utils/util_key_loader.py`:
- Around line 46-125: The load_private_key_from_pem function currently calls
load_pem_private_key but doesn't catch
cryptography.exceptions.UnsupportedAlgorithm; update the except block that
handles load_pem_private_key to also catch UnsupportedAlgorithm (alongside
ValueError and TypeError), log the parsing failure (using the existing
logger.exception call with context.correlation_id and path) and raise a
ProtocolConfigurationError (preserving context and chaining from the caught
UnsupportedAlgorithm) so unsupported-algorithm errors are wrapped the same way
as other PEM parse errors.

In `@src/omnibase_infra/runtime/service_runtime_host_process.py`:
- Around line 3729-3855: Compute a normalized correlation_id at the start of the
inbound handling (e.g., corr_id = envelope.get("correlation_id") or
str(envelope.get("trace_id")) or str(uuid4())) and import uuid4 from uuid; then
include this correlation_id in the extra dict of every rejection/logging call in
this block (the logger.warning calls around self._policy_engine.evaluate_inbound
rejection, the "Envelope signature validation failed" warning after
self._envelope_validator.validate_envelope, the "Failed to validate signed
envelope" exception handler, and the "Unsigned envelope rejected
(reject_unsigned=True)" warning), and also add it to the successful "Signed
envelope validated successfully" debug extra so all paths propagate
correlation_id consistently (reference symbols:
self._policy_engine.evaluate_inbound,
self._envelope_validator.validate_envelope, msg_envelope.trace_id, and the
logger.warning/logger.debug calls).
🧹 Nitpick comments (4)
pyproject.toml (1)

39-41: Stale inline comment after version bump.

The comment at Line 39 still references "v0.14.0: Latest core release" but the dependency is now ^0.15.0. Update the comment to reflect the new version and its changelog (e.g., ModelMessageEnvelope support).

📝 Suggested fix
-# omnibase-core v0.14.0: Latest core release
-# (v0.13.1 included: Contract registration support)
+# omnibase-core v0.15.0: Adds ModelMessageEnvelope for gateway signing
+# (v0.14.0 included: Contract registration support)
 omnibase-core = "^0.15.0"
tests/integration/gateway/test_policy_engine.py (1)

27-28: Missing @pytest.mark.integration marker.

Per coding guidelines, integration tests should use the @pytest.mark.integration marker for test classification. Add the marker to the test classes or module level.

🔧 Suggested fix
+import pytest
+
+pytestmark = pytest.mark.integration
+
+
 class TestPolicyEngineTopicAllowlist:
     """Tests for topic allowlist enforcement."""

Alternatively, add @pytest.mark.integration to each test class.

As per coding guidelines: "Use pytest markers: @pytest.mark.unit, @pytest.mark.integration, @pytest.mark.slow, @pytest.mark.chaos, @pytest.mark.serial for test classification"

tests/integration/gateway/test_gateway_config.py (1)

25-26: Missing @pytest.mark.integration marker.

Per coding guidelines, integration tests should use the @pytest.mark.integration marker. Add the marker at module level or to each test class.

🔧 Suggested fix
+pytestmark = pytest.mark.integration
+
+
 class TestGatewayConfigRequired:
     """Tests for required configuration fields."""

As per coding guidelines: "Use pytest markers: @pytest.mark.unit, @pytest.mark.integration, @pytest.mark.slow, @pytest.mark.chaos, @pytest.mark.serial for test classification"

tests/integration/gateway/test_envelope_validator.py (1)

41-42: Missing @pytest.mark.integration marker.

Per coding guidelines, integration tests should use the @pytest.mark.integration marker. Add the marker at module level.

🔧 Suggested fix
 from .conftest import ModelTestPayload
 
+pytestmark = pytest.mark.integration
+
 
 class TestEnvelopeValidatorValidEnvelope:
     """Tests for valid envelope validation."""

As per coding guidelines: "Use pytest markers: @pytest.mark.unit, @pytest.mark.integration, @pytest.mark.slow, @pytest.mark.chaos, @pytest.mark.serial for test classification"

Comment on lines +223 to +277
def evaluate_inbound(
self,
topic: str,
realm: str | None = None,
) -> PolicyDecision:
"""Evaluate inbound message policy.

Checks whether an inbound message should be allowed based on:
1. Topic allowlist (if configured)
2. Realm boundary enforcement (if configured)

Both checks must pass for the message to be allowed. Checks are
performed in order and short-circuit on first failure.

Args:
topic: The topic the message is arriving on.
realm: The realm identifier from the message metadata.
Required if expected_realm is configured.

Returns:
PolicyDecision with decision (ALLOW/DENY) and reason.
The reason is always populated for DENY decisions.

Example:
>>> engine = ServicePolicyEngine(
... allowed_topics=["events.*"],
... expected_realm="tenant-123",
... )
>>> decision = engine.evaluate_inbound(
... topic="events.order.created",
... realm="tenant-123",
... )
>>> if not decision:
... raise PermissionError(decision.reason)
"""
# Check 1: Topic allowlist
if not self.is_topic_allowed(topic):
reason = f"Topic '{topic}' not in allowlist"
self._log_rejection("inbound", topic, reason)
return PolicyDecision(
decision=EnumPolicyDecision.DENY,
reason=reason,
)

# Check 2: Realm boundary enforcement
if self._expected_realm is not None:
if realm != self._expected_realm:
reason = (
f"Realm mismatch: expected '{self._expected_realm}', got '{realm}'"
)
self._log_rejection("inbound", topic, reason)
return PolicyDecision(
decision=EnumPolicyDecision.DENY,
reason=reason,
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Include correlation_id in policy rejection logs

_log_rejection emits warnings without a correlation_id, and callers don’t propagate one. Add an optional correlation_id parameter to evaluate_inbound/evaluate_outbound and include it in the log extra payload so rejections are traceable.

🔧 Proposed fix
@@
-import logging
+import logging
+from uuid import UUID
@@
     def evaluate_inbound(
         self,
         topic: str,
         realm: str | None = None,
+        correlation_id: UUID | None = None,
     ) -> PolicyDecision:
@@
-            self._log_rejection("inbound", topic, reason)
+            self._log_rejection("inbound", topic, reason, correlation_id)
@@
-                self._log_rejection("inbound", topic, reason)
+                self._log_rejection("inbound", topic, reason, correlation_id)
@@
     def evaluate_outbound(
         self,
         topic: str,
+        correlation_id: UUID | None = None,
     ) -> PolicyDecision:
@@
     def _log_rejection(
         self,
         direction: str,
         topic: str,
         reason: str,
+        correlation_id: UUID | None = None,
     ) -> None:
@@
         logger.warning(
             "Policy rejection: %s message on topic '%s' - %s",
             direction,
             topic,
             reason,
             extra={
                 "direction": direction,
                 "topic": topic,
                 "reason": reason,
                 "policy_engine": "ServicePolicyEngine",
+                "correlation_id": str(correlation_id) if correlation_id else None,
             },
         )

As per coding guidelines, "Propagate correlation_id from incoming requests; auto-generate with uuid4() if missing; include in all error context".

Also applies to: 383-418

🤖 Prompt for AI Agents
In `@src/omnibase_infra/gateway/services/service_policy_engine.py` around lines
223 - 277, Add an optional correlation_id parameter to evaluate_inbound (and
mirror the same change in evaluate_outbound) so callers can pass a trace id;
inside each function, if correlation_id is None generate one with uuid.uuid4();
when calling _log_rejection("inbound"/"outbound", topic, reason) include the
correlation_id in the log extra/context payload so the rejection warnings carry
the id; update any return paths that call _log_rejection to forward the
generated or provided correlation_id and ensure function signatures
(evaluate_inbound, evaluate_outbound) and any call sites are updated to
accept/propagate the new parameter.

Comment thread src/omnibase_infra/gateway/utils/util_key_loader.py
Comment thread src/omnibase_infra/runtime/service_runtime_host_process.py
Refactor _publish_envelope_safe to use concrete types instead of
dict[str, object] | BaseModel union:

- _serialize_envelope now accepts only dict[str, object]
- _publish_envelope_safe now accepts only dict[str, object]
- Add _publish_model_safe for BaseModel payloads with signing support
- Update call sites to dispatch based on response type
- Revert INFRA_MAX_UNIONS from 119 to 118 (no new union added)

This maintains the union count at 118 while preserving full
envelope signing functionality for BaseModel payloads.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Fix all issues with AI agents
In `@src/omnibase_infra/runtime/service_runtime_host_process.py`:
- Around line 2514-2526: The policy rejection log in the outbound check (inside
the method that calls self._policy_engine.evaluate_outbound(topic)) must include
a correlation_id: pull correlation_id from the current envelope (e.g.,
envelope.get("correlation_id") or envelope.correlation_id) and if missing create
one with uuid.uuid4().hex (or str(uuid.uuid4())), then add that correlation_id
to the logger.warning extra dict alongside "topic" and "reason" before
returning; reference the evaluate_outbound call, the logger.warning invocation,
and use uuid.uuid4() to generate when needed.
- Around line 3815-3853: When extracting the validated inner payload in the
block after self._envelope_validator.validate_envelope (working with
msg_envelope, payload, extracted_envelope, and model_dump), ensure the original
trace_id is propagated as correlation_id and if missing generate one with
uuid.uuid4(); specifically compute corr_id = str(msg_envelope.trace_id) if
present else str(uuid.uuid4()), then inject it into the extracted_envelope (use
extracted_envelope.setdefault("correlation_id", corr_id) for dict cases and
include the same key when constructing the {"payload": payload} fallback) so
downstream consumers always receive a correlation_id.
- Around line 2395-2409: BaseModel responses currently skip adding
correlation_id because _publish_model_safe is called without it; update
_publish_model_safe to accept an optional correlation_id parameter and ensure it
inserts correlation_id into the envelope_dict when absent (similar to the dict
branch), then call _publish_model_safe(response, self._output_topic,
correlation_id=correlation_id) from the handler path; adjust any signing or
trace_id extraction in _publish_model_safe to preserve behavior while ensuring
the final published envelope contains the correlation_id.

Comment thread src/omnibase_infra/runtime/service_runtime_host_process.py
Comment on lines +2514 to +2526
# Check outbound policy (if policy engine configured)
if self._policy_engine is not None:
decision = self._policy_engine.evaluate_outbound(topic)
if not decision:
logger.warning(
"Outbound message rejected by policy",
extra={
"topic": topic,
"reason": decision.reason,
},
)
return

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Add correlation_id to outbound policy rejection logs.

Policy rejections are warnings but currently omit correlation_id. Please normalize (or generate) correlation_id from the envelope and include it in the warning extra.

Suggested fix
-        if self._policy_engine is not None:
+        if self._policy_engine is not None:
+            correlation_id = normalize_correlation_id(envelope.get("correlation_id"))
             decision = self._policy_engine.evaluate_outbound(topic)
             if not decision:
                 logger.warning(
                     "Outbound message rejected by policy",
                     extra={
                         "topic": topic,
                         "reason": decision.reason,
+                        "correlation_id": str(correlation_id),
                     },
                 )
                 return

As per coding guidelines, "Propagate correlation_id from incoming requests; auto-generate with uuid4() if missing; include in all error context".

🤖 Prompt for AI Agents
In `@src/omnibase_infra/runtime/service_runtime_host_process.py` around lines 2514
- 2526, The policy rejection log in the outbound check (inside the method that
calls self._policy_engine.evaluate_outbound(topic)) must include a
correlation_id: pull correlation_id from the current envelope (e.g.,
envelope.get("correlation_id") or envelope.correlation_id) and if missing create
one with uuid.uuid4().hex (or str(uuid.uuid4())), then add that correlation_id
to the logger.warning extra dict alongside "topic" and "reason" before
returning; reference the evaluate_outbound call, the logger.warning invocation,
and use uuid.uuid4() to generate when needed.

Comment thread src/omnibase_infra/runtime/service_runtime_host_process.py Outdated
- Add correlation_id tracking throughout gateway publish flow
- Implement sign_dict() for dict envelope signing (parity with models)
- Add UnsupportedAlgorithm exception handling in key loader
- Include correlation_id in policy rejection and gateway rejection logs
- Preserve trace_id as correlation_id when extracting signed payloads
- Add @pytest.mark.integration markers to gateway test files
- Update stale version comment in pyproject.toml
- Add 7 new tests for sign_dict functionality
- Add validation exemption for sign_dict parameters

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@src/omnibase_infra/gateway/services/service_policy_engine.py`:
- Around line 224-277: The evaluate_inbound method currently logs rejections
without a correlation_id when correlation_id is None; update evaluate_inbound to
ensure a correlation id exists by generating one with uuid.uuid4() (or UUID(...)
as appropriate) at the start of the rejection path and pass that generated id
into _log_rejection and any returned denial context; modify the code paths in
evaluate_inbound (the realm mismatch branch and the topic allowlist branch) to
set correlation_id = correlation_id or uuid.uuid4() before calling
self._log_rejection, and add the necessary import for uuid.uuid4 at the top if
missing so all rejection logs include a traceable correlation_id.
🧹 Nitpick comments (1)
tests/integration/gateway/test_envelope_validator.py (1)

17-40: Consider moving pytestmark after all imports.

The pytestmark = pytest.mark.integration assignment on line 23 is placed between import groups, which is unconventional. Standard Python style places all imports first, then module-level assignments.

🧹 Suggested reordering
 from __future__ import annotations
 
 import base64
 
 import pytest
+from cryptography.hazmat.primitives.asymmetric.ed25519 import (
+    Ed25519PrivateKey,
+    Ed25519PublicKey,
+)
+
+from omnibase_core.models.envelope.model_envelope_signature import (
+    ModelEnvelopeSignature,
+)
+from omnibase_core.models.envelope.model_message_envelope import ModelMessageEnvelope
+from omnibase_infra.gateway import (
+    EnumValidationErrorCode,
+    ServiceEnvelopeSigner,
+    ServiceEnvelopeValidator,
+    ValidationResult,
+)
+
+from .conftest import ModelTestPayload
 
 pytestmark = pytest.mark.integration
-from cryptography.hazmat.primitives.asymmetric.ed25519 import (
-    Ed25519PrivateKey,
-    Ed25519PublicKey,
-)
-
-from omnibase_core.models.envelope.model_envelope_signature import (
-    ModelEnvelopeSignature,
-)
-from omnibase_core.models.envelope.model_message_envelope import ModelMessageEnvelope
-from omnibase_infra.gateway import (
-    EnumValidationErrorCode,
-    ServiceEnvelopeSigner,
-    ServiceEnvelopeValidator,
-    ValidationResult,
-)
-
-from .conftest import ModelTestPayload

Comment thread src/omnibase_infra/gateway/services/service_policy_engine.py
- Auto-generate correlation_id in policy engine rejection logs
- Add correlation_id to unsigned envelope acceptance logs
- Add trace_id to _publish_model_safe error logging
- Move pytestmark after imports in test files (4 files)
- Fix docstring formatting (D413 blank line violations)

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@src/omnibase_infra/gateway/services/service_envelope_validator.py`:
- Around line 281-433: The validation code omits correlation_id in its log
extras; compute a normalized correlation_id at the start of validation (e.g.,
corr_id = str(envelope.correlation_id) if present else str(uuid4())) and include
"correlation_id": corr_id in the extra dict for every logging call that reports
failures or warnings (realm mismatch in the realm check, missing signature path
and the unsigned-accepted debug in the signature check, unknown signer, payload
hash mismatch, signature verification failure and both exception handlers in the
Ed25519 verification block). Update service_envelope_validator.py
functions/methods that reference envelope (e.g., the validator method containing
checks and variables signer, public_key, computed_hash, message, signature_b64)
to import uuid4 and use the single computed corr_id variable in all
logger.*(..., extra={...}) payloads for consistent propagation and
auto-generation when missing.

Comment thread src/omnibase_infra/gateway/services/service_envelope_validator.py
…arkers [OMN-1899]

Address remaining PR #249 review feedback:

- Add correlation_id parameter to evaluate_outbound() in policy engine
- Include correlation_id in all envelope validation failure logs
- Pass correlation_id to evaluate_outbound() in runtime publish methods
- Extract correlation_id from envelope or auto-generate in gateway validation
- Move pytestmark after all imports in test_policy_engine.py
…alidation [OMN-1899]

- Fix model_dump(mode="python") to mode="json" to prevent datetime
  serialization failure when gateway signing is enabled
- Add threading.Lock to ServiceEnvelopeValidator._public_keys for
  thread-safe signer mutation during concurrent access
- Add absolute path validation for key paths in ModelGatewayConfig
- Add private key file permission warning in util_key_loader
- Use logger.exception for signing failures (TRY400 compliance)
- Remove invalid noqa directive on runtime_id field
- Add key loader and runtime gateway integration tests (45 tests)

Review iteration: 1/10
…ustness [OMN-1899]

- Document reject_unsigned dependency on public_key_path being configured
- Add structural discriminator for signed envelope detection: verify
  signature is a dict with algorithm key before parsing as signed

Review iteration: 2/10

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Fix all issues with AI agents
In `@src/omnibase_infra/gateway/utils/util_key_loader.py`:
- Around line 89-138: The warning/exception log calls in util_key_loader.py are
including context.correlation_id only in the formatted message but not in the
structured `extra` payload; update all logger.warning and logger.exception calls
in the key-loading paths (the block that checks file permissions, the read_bytes
OSError handler, and the PEM parsing/UnsupportedAlgorithm handlers around
load_pem_private_key) to include "correlation_id": context.correlation_id inside
the extra dict so structured logs can be correlated; do the same change for the
other loader block referenced (the similar handlers around lines 194-224) and
ensure the ProtocolConfigurationError raises remain unchanged except for logging
extras.

In `@src/omnibase_infra/runtime/service_runtime_host_process.py`:
- Around line 3971-4000: The unsigned-envelope branch currently re-extracts
correlation_id into variables cid/cid_str and may log None; instead reuse the
already-normalized correlation id that was computed earlier (the
normalized/generated correlation_id variable used across the function, e.g.,
correlation_id or normalized_correlation_id) in both logger.warning and
logger.debug extra contexts; remove or stop using cid/cid_str for logging and
ensure the normalized id (which will be uuid4() if auto-generated) is passed as
"correlation_id" so tracing is consistent.
- Around line 2599-2695: In _publish_model_safe, when signing is disabled
(self._envelope_signer is None) or signing fails (except block) ensure the
outgoing envelope_dict includes a correlation_id: if correlation_id arg (or
extracted trace_id) is present, inject it into envelope_dict (as a string); if
neither exists, generate one with uuid.uuid4() and set it. Update both the
except signing-failure branch (where envelope_dict = model.model_dump(...)) and
the "no signer" branch (where envelope_dict = model.model_dump(...)) to perform
this fallback injection before calling self._serialize_envelope and publishing;
use the uuid module for generation and reference _publish_model_safe,
envelope_dict, correlation_id/trace_id, and _serialize_envelope to locate the
changes.

Comment thread src/omnibase_infra/gateway/utils/util_key_loader.py
Comment thread src/omnibase_infra/runtime/service_runtime_host_process.py
Comment thread src/omnibase_infra/runtime/service_runtime_host_process.py Outdated
…ze unsigned paths [OMN-1899]

- Add correlation_id to all structured `extra` dicts in key loader logs
- Remove redundant cid/cid_str extraction in unsigned envelope branch,
  reuse normalized correlation_id already computed at function scope
- Inject correlation_id into unsigned BaseModel envelope dicts for
  tracing parity with signed path

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Fix all issues with AI agents
In `@src/omnibase_infra/runtime/service_runtime_host_process.py`:
- Around line 3739-3813: The gateway init failure logs in
_initialize_gateway_from_config call logger.warning without a correlation_id;
update both warning calls (the "Invalid gateway config type" and the final
"Failed to parse gateway config, proceeding without") to include a
correlation_id in extra (use self._correlation_id if present else generate one
with uuid.uuid4()), and ensure uuid is imported; keep the rest of the extra
dicts intact and include the correlation_id key so all gateway initialization
warning logs include tracing context (references:
_initialize_gateway_from_config, gateway_raw, ModelGatewayConfig,
self._initialize_gateway, logger.warning).
- Around line 2521-2547: The code currently reads correlation_id from envelope
into local variable correlation_id (via cid) but doesn't generate or propagate
one when missing; update the logic so that if correlation_id is None after the
UUID parsing, create a new UUID with uuid.uuid4(), assign it to correlation_id
and also write it back into the envelope (envelope["correlation_id"] =
str(correlation_id) or UUID as appropriate) before calling
self._policy_engine.evaluate_outbound and before any signing/publishing code;
ensure the logger.warning extra uses the generated correlation_id string and any
downstream signing/publish functions read the correlation_id from the envelope
or the local correlation_id variable so the same id is propagated.
- Around line 3892-3999: The code lets unsigned envelopes through when
self._envelope_validator is None even if self._gateway_config.reject_unsigned is
True; fix by enforcing reject_unsigned regardless of validator availability:
either add config-time validation (in ModelGatewayConfig or the service runtime
host initializer) that raises an error if gateway_config.reject_unsigned is True
while public_key_path is None/empty, or modify the envelope-processing logic to
check if self._envelope_validator is None and self._gateway_config and
self._gateway_config.reject_unsigned and then log and return None (reject)
before returning envelope; reference symbols: self._envelope_validator,
self._gateway_config.reject_unsigned, public_key_path, and ModelGatewayConfig.

Comment thread src/omnibase_infra/runtime/service_runtime_host_process.py
Comment on lines +3739 to +3813
except Exception as e:
# Gateway initialization failure is non-fatal - log and continue
# The system will operate without signing/validation
logger.warning(
"Failed to initialize gateway, proceeding without",
extra={
"error": str(e),
"realm": config.realm,
"runtime_id": config.runtime_id,
},
)
# Clear any partially initialized components
self._gateway_config = None
self._envelope_signer = None
self._envelope_validator = None
self._policy_engine = None

async def _initialize_gateway_from_config(self) -> None:
"""Initialize gateway from runtime configuration.

Reads gateway configuration from the runtime config dict and initializes
gateway components. Called during start() to enable envelope signing,
validation, and policy enforcement.

Configuration keys:
- gateway.enabled: bool (default: True)
- gateway.realm: str (required if enabled)
- gateway.runtime_id: str (required if enabled)
- gateway.private_key_path: str | None (optional, enables signing)
- gateway.public_key_path: str | None (optional, enables validation)
- gateway.allowed_topics: list[str] (optional, topic allowlist)
- gateway.reject_unsigned: bool (default: True)

Example config:
{
"gateway": {
"enabled": True,
"realm": "dev",
"runtime_id": "runtime-dev-001",
"private_key_path": "/etc/onex/keys/private.pem",
"public_key_path": "/etc/onex/keys/public.pem",
"allowed_topics": ["events.*", "commands.*"],
"reject_unsigned": True,
}
}
"""
if self._config is None:
logger.debug("No runtime config provided, skipping gateway setup")
return

gateway_raw = self._config.get("gateway")
if gateway_raw is None:
logger.debug("Gateway not configured, skipping")
return

try:
if isinstance(gateway_raw, dict):
gateway_config = ModelGatewayConfig.model_validate(gateway_raw)
elif isinstance(gateway_raw, ModelGatewayConfig):
gateway_config = gateway_raw
else:
logger.warning(
"Invalid gateway config type",
extra={"type": type(gateway_raw).__name__},
)
return

# Call the synchronous initialization method
self._initialize_gateway(gateway_config)

except Exception as e:
logger.warning(
"Failed to parse gateway config, proceeding without",
extra={"error": str(e)},
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Add correlation_id to gateway init failure logs.

Gateway init failures are warnings but currently lack correlation_id in structured extras, which weakens tracing.

🛠️ Proposed fix
     def _initialize_gateway(self, config: ModelGatewayConfig) -> None:
+        correlation_id = uuid4()
         ...
         except Exception as e:
             logger.warning(
                 "Failed to initialize gateway, proceeding without",
                 extra={
                     "error": str(e),
                     "realm": config.realm,
                     "runtime_id": config.runtime_id,
+                    "correlation_id": str(correlation_id),
                 },
             )
@@
     async def _initialize_gateway_from_config(self) -> None:
+        correlation_id = uuid4()
         ...
             else:
                 logger.warning(
                     "Invalid gateway config type",
-                    extra={"type": type(gateway_raw).__name__},
+                    extra={
+                        "type": type(gateway_raw).__name__,
+                        "correlation_id": str(correlation_id),
+                    },
                 )
                 return
@@
         except Exception as e:
             logger.warning(
                 "Failed to parse gateway config, proceeding without",
-                extra={"error": str(e)},
+                extra={"error": str(e), "correlation_id": str(correlation_id)},
             )

As per coding guidelines, "Propagate correlation_id from incoming requests; auto-generate with uuid4() if missing; include in all error context".

🤖 Prompt for AI Agents
In `@src/omnibase_infra/runtime/service_runtime_host_process.py` around lines 3739
- 3813, The gateway init failure logs in _initialize_gateway_from_config call
logger.warning without a correlation_id; update both warning calls (the "Invalid
gateway config type" and the final "Failed to parse gateway config, proceeding
without") to include a correlation_id in extra (use self._correlation_id if
present else generate one with uuid.uuid4()), and ensure uuid is imported; keep
the rest of the extra dicts intact and include the correlation_id key so all
gateway initialization warning logs include tracing context (references:
_initialize_gateway_from_config, gateway_raw, ModelGatewayConfig,
self._initialize_gateway, logger.warning).

Comment thread src/omnibase_infra/runtime/service_runtime_host_process.py Outdated
Resolve merge conflict in service_runtime_host_process.py __init__,
keeping both gateway components (OMN-1899) and introspection service (OMN-1930).
…tion_id to init logs [OMN-1899]

- Move reject_unsigned check outside envelope_validator block so it is
  enforced even when public_key_path is not configured
- Auto-generate correlation_id in _publish_envelope_safe when missing
- Add correlation_id to gateway init failure warning logs
- Update stale v0.14.0 comment in pyproject.toml

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Fix all issues with AI agents
In `@src/omnibase_infra/runtime/service_runtime_host_process.py`:
- Around line 2718-2739: In _publish_model_safe, ensure trace_id is never None
by auto-generating a UUID via uuid.uuid4() when both correlation_id arg and
model.correlation_id are missing; set trace_id to this generated UUID, propagate
it back into the model if the model has a writable correlation_id attribute, and
use that trace_id for the policy check (_policy_engine.evaluate_outbound),
subsequent signing, and all logger.extra contexts (replace existing nullable
uses of trace_id with the guaranteed stringified trace_id).
- Around line 4065-4078: The code extracts or generates a correlation_id but
never writes it back to the envelope; update the block where correlation_id is
set (the variables named correlation_id, cid and the uuid4() fallback in
service_runtime_host_process.py) to assign the resolved ID into the envelope
(e.g., envelope["correlation_id"] = <resolved id>), ensuring the stored form
matches expected envelope usage (string if the envelope expects strings or UUID
otherwise), and ensure subsequent error/context logging uses that same envelope
value so downstream processing and error contexts see the propagated
correlation_id.
- Around line 4132-4137: The current validation branch only runs when
_envelope_validator is present and has_signature is true, allowing attackers to
set has_signature=True while _envelope_validator is None to bypass
reject_unsigned; update the condition and handling so that
signed-but-unverifiable envelopes are treated as invalid (or as unsigned) by
explicitly checking for has_signature when _envelope_validator is None and
calling the existing reject_unsigned (or the invalid path). Specifically, modify
the block around _envelope_validator, has_signature, and has_required_fields to:
if has_signature and _envelope_validator is None -> reject or mark as unsigned
using the same code path as reject_unsigned; otherwise keep the current flow
that invokes _envelope_validator when present.

Comment thread src/omnibase_infra/runtime/service_runtime_host_process.py
Comment thread src/omnibase_infra/runtime/service_runtime_host_process.py
Comment thread src/omnibase_infra/runtime/service_runtime_host_process.py Outdated
…n handling

- Fixed: service_runtime_host_process.py:4193 - Narrowed broad `except Exception`
  to `(ValidationError, ValueError, KeyError, TypeError)` to avoid silently
  swallowing real bugs like ImportError or AttributeError
- Fixed: test_gateway_config.py - Added TestKeyPathAbsoluteValidation class with
  6 tests covering path traversal prevention in _validate_key_path_is_absolute
- Fixed: test_runtime_gateway_integration.py - Added 9 tests covering inbound
  _validate_gateway_envelope: reject_unsigned enforcement (including regression
  test for commit 4e2181d), policy engine checks, realm mismatch, signed
  envelope validation, invalid signatures, and malformed envelope handling
- Fixed: service_envelope_validator.py:563 - Added type annotation to __all__
  for consistency with other gateway modules

Review iteration: 1/10
…ce_id, propagate correlation_id [OMN-1899]

- Reject signed envelopes when no validator is configured (fail-closed
  security: forged signatures must not be silently accepted)
- Auto-generate trace_id in _publish_model_safe when missing, including
  string correlation_id parsing from model attributes
- Inject auto-generated correlation_id into inbound envelopes for
  downstream handler tracing

Addresses remaining PR #249 review feedback.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@src/omnibase_infra/runtime/service_runtime_host_process.py`:
- Around line 2783-2790: The logger.exception call in the signing-failure path
currently only includes trace_id; update the error context to also include
correlation_id by retrieving it from the current request/envelope (or generating
one with uuid.uuid4() when missing) and add "correlation_id":
str(correlation_id) to the extra dict passed to logger.exception (alongside
topic and trace_id) so correlation IDs are propagated for this error path;
ensure you import uuid where needed and use the same correlation_id value used
elsewhere in functions handling outbound envelopes.
- Around line 4194-4207: When building extracted_envelope in
service_runtime_host_process, ensure correlation_id is always propagated: if
msg_envelope.trace_id is present set extracted_envelope["correlation_id"] =
msg_envelope.trace_id, otherwise fall back to msg_envelope.correlation_id if
available, and if neither exists generate a new UUID via uuid.uuid4() and assign
that value; update the logic around msg_envelope, trace_id and
extracted_envelope to perform this fallback path and add an import for uuid
where needed so every outgoing envelope has a normalized correlation_id.

Comment on lines +2783 to +2790
# Signing failure is non-fatal - log and publish unsigned
logger.exception(
"Failed to sign outbound envelope, publishing unsigned",
extra={
"topic": topic,
"trace_id": str(trace_id) if trace_id else None,
},
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Add correlation_id to signing-failure log context.
The exception log only includes trace_id; error contexts should always carry correlation_id for tracing.

🛠️ Proposed fix
                 logger.exception(
                     "Failed to sign outbound envelope, publishing unsigned",
                     extra={
                         "topic": topic,
-                        "trace_id": str(trace_id) if trace_id else None,
+                        "correlation_id": str(trace_id) if trace_id else None,
+                        "trace_id": str(trace_id) if trace_id else None,
                     },
                 )

As per coding guidelines, "Propagate correlation_id from incoming requests; auto-generate with uuid4() if missing; include in all error context".

🤖 Prompt for AI Agents
In `@src/omnibase_infra/runtime/service_runtime_host_process.py` around lines 2783
- 2790, The logger.exception call in the signing-failure path currently only
includes trace_id; update the error context to also include correlation_id by
retrieving it from the current request/envelope (or generating one with
uuid.uuid4() when missing) and add "correlation_id": str(correlation_id) to the
extra dict passed to logger.exception (alongside topic and trace_id) so
correlation IDs are propagated for this error path; ensure you import uuid where
needed and use the same correlation_id value used elsewhere in functions
handling outbound envelopes.

Comment on lines +4194 to +4207
# The payload could be a dict or a BaseModel
payload = msg_envelope.payload
if isinstance(payload, dict):
extracted_envelope = dict(payload) # Copy to avoid mutation
elif hasattr(payload, "model_dump"):
extracted_envelope = payload.model_dump(mode="json")
else:
# Unknown payload type, use as-is
extracted_envelope = {"payload": payload}

# Preserve trace_id as correlation_id for downstream tracking
if msg_envelope.trace_id is not None:
extracted_envelope["correlation_id"] = msg_envelope.trace_id

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Preserve correlation_id when signed envelope lacks trace_id.
If trace_id is missing, the extracted payload can lose correlation_id even though one was generated earlier. Ensure a fallback to the normalized correlation_id.

🛠️ Proposed fix
-                if msg_envelope.trace_id is not None:
-                    extracted_envelope["correlation_id"] = msg_envelope.trace_id
+                if msg_envelope.trace_id is not None:
+                    extracted_envelope["correlation_id"] = msg_envelope.trace_id
+                else:
+                    extracted_envelope.setdefault("correlation_id", correlation_id)

As per coding guidelines, "Propagate correlation_id from incoming requests; auto-generate with uuid4() if missing; include in all error context".

🤖 Prompt for AI Agents
In `@src/omnibase_infra/runtime/service_runtime_host_process.py` around lines 4194
- 4207, When building extracted_envelope in service_runtime_host_process, ensure
correlation_id is always propagated: if msg_envelope.trace_id is present set
extracted_envelope["correlation_id"] = msg_envelope.trace_id, otherwise fall
back to msg_envelope.correlation_id if available, and if neither exists generate
a new UUID via uuid.uuid4() and assign that value; update the logic around
msg_envelope, trace_id and extracted_envelope to perform this fallback path and
add an import for uuid where needed so every outgoing envelope has a normalized
correlation_id.

…ency and test markers [OMN-1899]

- Simplify redundant correlation_id conditionals in _publish_envelope_safe
  (guaranteed non-None after auto-generation)
- Add correlation_id to signing success/failure log extras in _publish_model_safe
- Preserve correlation_id fallback when signed envelope lacks trace_id
- Add pytestmark = pytest.mark.integration to test_runtime_introspection
- Fix versionadded directive to use semver (0.4.1) instead of ticket ID

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@src/omnibase_infra/runtime/service_runtime_host_process.py`:
- Around line 2605-2666: The envelope must be serialized to convert UUIDs to
strings before signing to avoid json.dumps serialization failures; update the
signing block in the method using self._envelope_signer.sign_dict to call
self._serialize_envelope(envelope) (or equivalent) and pass that serialized dict
as payload to _envelope_signer.sign_dict (ensure correlation_id in the
serialized envelope is a string), then use the returned signed model_dump as
final_envelope; reference _serialize_envelope, _envelope_signer.sign_dict, and
hash_canonical_json in your changes.

Comment thread src/omnibase_infra/runtime/service_runtime_host_process.py
…TypeError [OMN-1899]

hash_canonical_json uses json.dumps which raises TypeError on UUID objects.
Previously serialization only happened after signing, causing sign_dict to
silently fail and degrade to unsigned publish. Now _serialize_envelope is
called before sign_dict so UUID fields are converted to strings first.
…e inconsistency [OMN-1899]

- Updated reject_unsigned docstring and field description to reflect
  actual runtime behavior (enforced without public_key_path)
- Replaced fnmatch.fnmatch() with fnmatch.fnmatchcase() in policy engine
  for consistent case-sensitive topic matching across all platforms
- Normalized correlation_id assignment to str() in both branches of
  _validate_gateway_envelope to prevent mixed UUID/str types

Review iteration: 1/10

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@src/omnibase_infra/gateway/models/model_gateway_config.py`:
- Around line 126-129: The allowed_topics Field currently expects a tuple[str,
...] but with Pydantic v2 strict mode list inputs (e.g.,
allowed_topics=["events.*", "commands.*"]) will fail; add a Pydantic v2 field
validator on "allowed_topics" (use field_validator('allowed_topics',
mode='before') or mode='before') to coerce incoming list inputs to a tuple (and
pass tuples/None through unchanged) inside the ModelGatewayConfig (or the class
that defines allowed_topics) so list-style configuration shown in the docstring
validates correctly.
- Around line 140-162: The validator _validate_key_path_is_absolute must return
Path objects (or None) not the original input; update it to coerce v into a
pathlib.Path (e.g., path = Path(v) if v is not None and not isinstance(v, Path)
else v), validate path.is_absolute(), and then return that Path instance (or
None) so Pydantic strict=True type checks pass; adjust the method in class-level
validator decorated on "private_key_path" and "public_key_path" accordingly.

Comment thread src/omnibase_infra/gateway/models/model_gateway_config.py
Comment thread src/omnibase_infra/gateway/models/model_gateway_config.py
…OMN-1899]

Coerce list→tuple for allowed_topics, return Path objects from key-path
validator, and move pytestmark after imports in test file.
@jonahgabriel
jonahgabriel merged commit 2e2a9f8 into main Feb 6, 2026
19 checks passed
@jonahgabriel
jonahgabriel deleted the feat/OMN-1899-runtime-gateway-envelope-signing branch February 6, 2026 20:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant