/**
 * EventBusPort — Agent-to-Agent pub/sub via CloudEvents 1.0 + Redis Streams.
 *
 * ADR-007 defines the transport (Redis Streams + consumer groups). This port
 * defines ONLY the interface — implementations live in Forge (redis-event-bus.ts)
 * and in the SDK testing module (TestEventBus).
 *
 * Guarantees (baked into the port, not optional):
 *   - At-least-once delivery with consumer-side dedup via CloudEvent `id`.
 *   - MAXLEN ~ 10000 on every stream (backpressure by truncation).
 *   - Poison pill: 3 consecutive XAUTOCLAIM failures → auto-move to DLQ.
 *   - Consumer lag alert: XPENDING > 100 for 5min → Prometheus alert.
 *   - Every CloudEvent MUST carry `id` (UUIDv7) — publish-side enforces.
 *
 * Schema versioning: breaking schema change → new `type` string (e.g.
 * `tech.vauban.vault.compound.ready.v2`). Consumers register for the type
 * they support. subscribe() validates against a local Zod schema.
 *
 * ADR-ECO-017: Cross-product events MUST carry a DomainEvent signature.
 * Use DomainEvent for inter-product events and CloudEvent for intra-product.
 *
 * OTel instrumentation: import { createTracedEventBusPort } to wrap any
 * EventBusPort implementation with OpenTelemetry spans per publish/subscribe call.
 * Gracefully degrades to noop spans when no OTel SDK is installed.
 *
 * @public
 */
// ─── Typed errors ─────────────────────────────────────────────────────────────
/**
 * Thrown when an event could not be published to Redis (connection lost,
 * stream key conflict, or OOM on XADD). The original event is available
 * for retry or DLQ routing.
 */
export class EventPublishError extends Error {
    cause;
    /** The stream key the event was targeting. */
    stream;
    /** The event id (UUIDv7) that failed to publish. */
    eventId;
    constructor(message, stream, eventId, cause) {
        super(message);
        this.cause = cause;
        this.name = "EventPublishError";
        this.stream = stream;
        this.eventId = eventId;
        Object.setPrototypeOf(this, new.target.prototype);
    }
}
import { SpanStatusCode, trace } from "@opentelemetry/api";
const PORT_TRACER = trace.getTracer("vauban-agent-sdk.ports", "0.1.0");
/**
 * Wrap any EventBusPort implementation with OTel spans.
 * Publishes and subscribeDomain calls each get a span with the event type
 * and stream as attributes. Gracefully degrades to noop spans when no OTel
 * SDK is installed.
 *
 * Usage:
 *   const raw: EventBusPort = buildRedisEventBus(...);
 *   const traced = createTracedEventBusPort(raw);
 *   await traced.publish(event, "vault.events") // emits "event-bus.publish" span
 */
export function createTracedEventBusPort(impl) {
    function spanName(op) {
        return `event-bus.${op}`;
    }
    return {
        async publish(event, stream) {
            return PORT_TRACER.startActiveSpan(spanName("publish"), {
                attributes: {
                    "event.type": event.type,
                    "event.stream": stream,
                    "event.id": event.id,
                    "vauban.port.name": "event-bus",
                },
            }, async (span) => {
                try {
                    await impl.publish(event, stream);
                    span.setStatus({ code: SpanStatusCode.OK });
                }
                catch (err) {
                    const message = err instanceof Error ? err.message : String(err);
                    span.setStatus({ code: SpanStatusCode.ERROR, message });
                    if (err instanceof Error)
                        span.recordException(err);
                    throw err;
                }
                finally {
                    span.end();
                }
            });
        },
        async publishWithIdempotency(event, key) {
            return PORT_TRACER.startActiveSpan(spanName("publishWithIdempotency"), {
                attributes: {
                    "event.type": event.type,
                    "event.idempotency_key": key,
                    "vauban.port.name": "event-bus",
                },
            }, async (span) => {
                try {
                    await impl.publishWithIdempotency(event, key);
                    span.setStatus({ code: SpanStatusCode.OK });
                }
                catch (err) {
                    const message = err instanceof Error ? err.message : String(err);
                    span.setStatus({ code: SpanStatusCode.ERROR, message });
                    if (err instanceof Error)
                        span.recordException(err);
                    throw err;
                }
                finally {
                    span.end();
                }
            });
        },
        subscribeDomain(eventType, handler, opts) {
            return impl.subscribeDomain(eventType, handler, opts);
        },
        replayFrom(streamKey, fromId) {
            return impl.replayFrom(streamKey, fromId);
        },
        dlq() {
            return impl.dlq();
        },
        pendingCount(stream, consumerGroup) {
            return impl.pendingCount(stream, consumerGroup);
        },
    };
}
//# sourceMappingURL=event-bus.js.map