Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 16 additions & 7 deletions .claude/architecture-handshake.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
<!-- HANDSHAKE_METADATA
source: omnibase_core/architecture-handshakes/repos/omnibase_infra.md
source_version: 0.13.1
source_sha256: cb694fe620ef8224ebbe53852d7fd7b498aab3249913bc4d07c8c3cbb7552762
installed_at: 2026-02-03T14:43:13Z
source_version: 0.16.0
source_sha256: 1ffa2bd30b553ea05cd62444800a4ae5215826be9f073480254c3f408580e93f
installed_at: 2026-02-10T17:46:59Z
installed_by: jonah
-->

Expand All @@ -25,6 +25,18 @@ installed_by: jonah
- Handler implementations
- Infrastructure adapters

## Platform-Wide Rules

1. **No backwards compatibility** - Breaking changes always acceptable. No deprecation periods, shims, or migration paths.
2. **Delete old code immediately** - Never leave deprecated code "for reference." If unused, delete it.
3. **No speculative refactors** - Only make changes that are directly requested or clearly necessary.
4. **No silent schema changes** - All schema changes must be explicit and deliberate.
5. **Frozen event schemas** - All models crossing boundaries (events, intents, actions, envelopes, projections) must use `frozen=True`. Internal mutable state is fine.
6. **Explicit timestamps** - Never use `datetime.now()` defaults. Inject timestamps explicitly.
7. **No hardcoded configuration** - All config via `.env` or Pydantic Settings. No localhost defaults.
8. **Kafka is required infrastructure** - Use async/non-blocking patterns. Never block the calling thread waiting for Kafka acks.
9. **No `# type: ignore` without justification** - Requires explanation comment and ticket reference.

## Rules the Agent Must Obey

1. **Nodes MUST be declarative** - `node.py` extends base class with NO custom logic
Expand All @@ -33,11 +45,9 @@ installed_by: jonah
4. **Orchestrators emit, never return** - Cannot return `result`
5. **Contracts are source of truth** - YAML contracts define behavior
6. **Container injection required** - All services via `ModelONEXContainer`
7. **Never block on Kafka** - Kafka is optional; operations must succeed without it

## Non-Goals (DO NOT)

- ❌ No backwards compatibility - change freely, no deprecation periods
- ❌ No versioned directories (`v1_0_0/`, `v2/`) - version through contract.yaml fields only
- ❌ No convenience over correctness - contract violations fail loudly
- ❌ No business logic in nodes - nodes coordinate, handlers compute
Expand All @@ -48,11 +58,10 @@ installed_by: jonah
- Custom logic in `node.py` beyond base class extension
- Error handling in nodes (belongs in handlers)
- Direct Kafka/Postgres calls in nodes (use effect handlers)
- Leaving deprecated code "for reference" - DELETE IT

## Layer Boundaries

```text
```
omnibase_core (contracts, models)
↑ imported by
omnibase_spi (protocols)
Expand Down
53 changes: 53 additions & 0 deletions .github/workflows/check-handshake.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
name: Check Architecture Handshake

on:
push:
branches: [main, develop]
paths:
- '.claude/architecture-handshake.md'
- '.github/workflows/check-handshake.yml'
pull_request:
branches: [main, develop]
paths:
- '.claude/architecture-handshake.md'
- '.github/workflows/check-handshake.yml'
workflow_dispatch:

jobs:
check-handshake:
name: Check architecture handshake
runs-on: ubuntu-latest
timeout-minutes: 5

steps:
- name: Checkout repo
uses: actions/checkout@v4

- name: Checkout omnibase_core
uses: actions/checkout@v4
with:
repository: OmniNode-ai/omnibase_core
# Pinned to omnibase_core main as of 2026-02-10.
# Update by running: gh api repos/OmniNode-ai/omnibase_core/commits/main --jq '.sha'
ref: 4dc7a0adf299e47eef57ab9950b0f0a587638a3e
path: omnibase_core
# OMNIBASE_CORE_TOKEN is required when omnibase_core is private.
# GITHUB_TOKEN only has read access to the current repo by default.
token: ${{ secrets.OMNIBASE_CORE_TOKEN || secrets.GITHUB_TOKEN }}

- name: Verify omnibase_core checkout
run: |
if [ ! -f omnibase_core/architecture-handshakes/check-handshake.sh ]; then
echo "::error::Failed to checkout omnibase_core or check-handshake.sh not found."
echo ""
echo "Diagnostic information:"
echo " omnibase_core directory exists: $([ -d omnibase_core ] && echo 'yes' || echo 'no')"
echo " omnibase_core is empty: $([ -z "$(ls -A omnibase_core 2>/dev/null)" ] && echo 'yes' || echo 'no')"
echo ""
echo "If omnibase_core is private, GITHUB_TOKEN does not have cross-repo access."
echo "Set the OMNIBASE_CORE_TOKEN repository secret with a PAT that has repo scope."
exit 1
fi

- name: Check architecture handshake
run: bash omnibase_core/architecture-handshakes/check-handshake.sh
38 changes: 38 additions & 0 deletions docs/decisions/adr-kafka-required-infrastructure.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# ADR: Kafka is Required Infrastructure (Rule #8)

**Status**: Accepted
**Date**: 2026-02-10
**Ticket**: OMN-2084

## Context

The platform originally operated under the rule "Never block on Kafka — Kafka is optional;
operations must succeed without it." This was encoded in the architecture handshake as a
constraint that all repos observed.

As the platform matured, Kafka became the sole event transport for inter-node communication,
projection publishing, DLQ routing, and wiring health monitoring. The "optional Kafka"
invariant was no longer accurate — every production deployment requires a running Kafka
cluster, and "graceful degradation" without Kafka meant silently dropping events.

## Decision

**Kafka is required infrastructure** (platform-wide rule #8). Specifically:

- Connection failures during `start()` raise and must be treated as fatal by callers.
- There is no "degraded mode" — if Kafka is unreachable, the node cannot operate.
- Async/non-blocking patterns are still required to avoid blocking the calling thread
while waiting for Kafka acks.
- Resilience patterns (circuit breaker, retry with backoff) protect against *transient*
broker failures, not permanent unavailability.

This replaces the previous rule "Never block on Kafka."

## Consequences

- Callers of `EventBusKafka.start()` and `ProviderKafkaProducer.create()` must not
swallow connection errors.
- Integration tests that previously mocked Kafka away need explicit opt-in for
Kafka-less testing via markers (`@pytest.mark.no_kafka`).
- The `localhost:9092` defaults in config models remain as development conveniences but
must be overridden in deployed environments (per rule #7: no hardcoded configuration).
Comment on lines +37 to +38

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

Reconcile localhost-default guidance with rule #7.

This says localhost defaults remain for development, but the architecture-handshake rule #7 now states “No localhost defaults.” Please align the ADR (and any dependent docs) with the intended rule to avoid conflicting guidance.

🤖 Prompt for AI Agents
In `@docs/decisions/adr-kafka-required-infrastructure.md` around lines 37 - 38,
Update the ADR to remove the conflicting guidance by eliminating the statement
that "localhost:9092 defaults in config models remain as development
conveniences" and instead state that no service defaults may be localhost per
rule `#7` (no hardcoded configuration); locate and edit the paragraph referencing
"localhost:9092" and replace it with guidance that configs must require explicit
environment-specific bootstrap servers or use a clearly-scoped development-only
profile (named e.g., "dev" and not a default), and update any dependent docs or
config model examples to either omit a default or mark the value as required to
satisfy rule `#7`.

4 changes: 2 additions & 2 deletions docs/operations/EVENT_BUS_OPERATIONS_RUNBOOK.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ The EventBusKafka provides production-grade message streaming using Apache Kafka
- **Circuit breaker** for connection failure protection
- **Retry with exponential backoff** on publish failures
- **Dead letter queue (DLQ)** for failed message processing
- **Graceful degradation** when Kafka is unavailable
- **Resilience** against transient broker failures (per platform-wide rule #8: Kafka is required infrastructure)

**Source Location**: `src/omnibase_infra/event_bus/event_bus_kafka.py`

Expand Down Expand Up @@ -57,7 +57,7 @@ All configuration is managed through environment variables with sensible default

| Variable | Default | Range | Description |
|----------|---------|-------|-------------|
| `KAFKA_BOOTSTRAP_SERVERS` | `localhost:9092` | - | Comma-separated broker addresses |
| `KAFKA_BOOTSTRAP_SERVERS` | `localhost:9092` | - | Comma-separated broker addresses. **Must be overridden** — per rule #7 localhost defaults are for local development only; per rule #8 Kafka is required infrastructure. |
| `KAFKA_ENVIRONMENT` | `local` | - | Environment identifier (e.g., `dev`, `staging`, `prod`) |
| `KAFKA_GROUP` | `default` | - | Consumer group identifier |

Expand Down
15 changes: 8 additions & 7 deletions src/omnibase_infra/event_bus/event_bus_kafka.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
- Circuit breaker for connection failure protection
- Retry with exponential backoff on publish failures
- Dead letter queue (DLQ) for failed message processing
- Graceful degradation when Kafka is unavailable
- Resilience against transient Kafka broker failures (per platform-wide rule #8)
- Support for environment/group-based routing
- Proper producer/consumer lifecycle management

Expand Down Expand Up @@ -220,8 +220,8 @@ class EventBusKafka(

Implements ProtocolEventBus interface using Apache Kafka (via aiokafka)
with resilience patterns including circuit breaker, retry with exponential
backoff, dead letter queue support, and graceful degradation when Kafka
is unavailable.
backoff, dead letter queue support, and resilience against transient
broker failures (per platform-wide rule #8: Kafka is required infrastructure).

Features:
- Topic-based message routing with Kafka partitioning
Expand Down Expand Up @@ -484,12 +484,13 @@ async def start(self) -> None:
"""Start the event bus and connect to Kafka.

Initializes the Kafka producer with connection retry and circuit
breaker protection. If connection fails, the bus operates in
degraded mode where publishes will fail gracefully.
breaker protection. Per platform-wide rule #8, Kafka is required
infrastructure — connection failures raise and must be treated as
fatal by the caller.

Raises:
InfraConnectionError: If connection fails after all retries and
circuit breaker is open
InfraTimeoutError: If connection times out
InfraConnectionError: If connection fails after retries
"""
if self._started:
logger.debug("EventBusKafka already started")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
"""Kafka producer provider.

Creates AIOKafkaProducer instances from environment-driven configuration.
Respects architecture constraint #7: Never block on Kafka.
Respects platform-wide rule #8: Kafka is required infrastructure —
use async/non-blocking patterns.

Part of OMN-1976: Contract dependency materialization.
"""
Expand All @@ -27,8 +28,9 @@ class ProviderKafkaProducer:
Producers are created from KAFKA_* environment variables and shared
across all contracts that declare kafka_producer dependencies.

Per architecture constraint #7: Kafka is optional. Creation failures
are logged but do not block startup.
Per platform-wide rule #8: Kafka is required infrastructure.
Creation failures propagate to the caller — callers must treat them
as fatal. Uses async patterns to avoid blocking the calling thread.
"""

def __init__(self, config: ModelKafkaProducerConfig) -> None:
Expand Down