Initial Commit

This commit is contained in:
2026-05-27 23:29:53 +01:00
commit fbc70f96e6
10000 changed files with 1102152 additions and 0 deletions

View File

@@ -0,0 +1,51 @@
# OpenSpec Change: events-spec-tightening
- **Change ID**: events-spec-tightening
- **Package**: `@suaveplan/events`
- **Status**: PROPOSED
- **Date**: 2026-04-27
## Summary
Tighten `openspec/specs/events/spec.md` to cover behaviors already shipped by `@suaveplan/events`: `emitAsync` timeout (`EVENTS.EMIT.TIMED_OUT`), abort (`EVENTS.EMIT.ABORTED`), envelope `traceId` plumbing, and the `onEmitError` observability hook. Resolve the `createBus` vs `new EventBus` API ambiguity (impl ships only the class) and add an explicit async listener-throw scenario. Pin every gap behind a `*.spec-conformance.test.ts` so future drift exits the 5-gate non-zero.
## Current state
`@suaveplan/events` is fully implemented:
- `EventBus<TMap>` exposes `new EventBus({ clock?, idGenerator? })`; no `createBus` factory.
- `emitAsync(type, payload, { signal?, timeoutMs?, traceId? })` — `withTimeout` wraps the dispatch chain and rejects with `EventsError("EVENTS.EMIT.TIMED_OUT")` when `timeoutMs` elapses.
- Per-listener `signal.aborted` check throws `EventsError("EVENTS.EMIT.ABORTED")` with `cause === signal.reason`.
- `wrap` accepts and emits `traceId` on `EventEnvelope`.
- `withObservability(bus, adapter)` registers an interceptor that calls `adapter.onEmit?` / `onEmitComplete?` / `onEmitError?`.
- `EVENTS_CODES` no longer carries `SCHEMA.REJECTED` (delegated to the future `events-schema` interceptor package).
The spec, however, still says `createBus({ clock?, idGenerator? })` (line 118) and has zero scenarios for timeout, abort, `traceId`, `onEmitError`, or async listener-throw. A consumer reading the spec gets a misleading picture of the public API surface, and conformance can silently regress without a single CI signal.
## What changes
1. **Modify Requirement: Deterministic id and clock sources** — replace `createBus({ clock?, idGenerator? })` with `new EventBus({ clock?, idGenerator? })`. The class constructor is the canonical injection point.
2. **Modify Requirement: Structured errors** — add a scenario covering async listener throw (parallel to the existing sync-emit scenario), since `dispatch()` and `emit()` capture errors via independent paths.
3. **Modify Requirement: Passive observability adapter** — add a scenario covering `adapter.onEmitError(envelope, error)` firing when the dispatch chain rejects.
4. **ADD Requirement: emitAsync timeout** — codify `timeoutMs` semantics and `EVENTS.EMIT.TIMED_OUT`.
5. **ADD Requirement: emitAsync abort** — codify `signal` semantics and `EVENTS.EMIT.ABORTED` (including `cause === signal.reason`).
6. **ADD Requirement: Envelope traceId propagation** — codify that `emit`/`emitAsync` accept `traceId` and surface it on `EventEnvelope.traceId`.
7. **Add `src/conformance.spec-conformance.test.ts`** pinning the new scenarios so the 5-gate publish flow catches drift.
8. **Add `**Verified by:**` lines** to every requirement (matching the conformance convention used by `datetime`, `i18n`, and other recently-folded packages).
## Why now
These are the five spec gaps surfaced during a focused enterprise-quality audit of `openspec/archive/2026-04-19-enterprise-events/`. The implementation has the behaviors today (and 100% coverage), but the spec is the contract — without scenarios, a future refactor can drop timeout handling and ship green. Tightening now is cheap (~30 minutes) and prevents both phantom enhancement requests and silent behavior regression in the substrate every domain-event-emitting package depends on.
## Impact
- `@suaveplan/events` — no runtime changes. Adds one `*.spec-conformance.test.ts` (~6 scenarios). Coverage stays at 100%.
- Downstream `events-metrics`, `events-otel`, `events-schema`, `events-transport` — none affected; their interceptor contract is unchanged.
- Spec readers — get an accurate picture of the API including timeout / abort / traceId / onEmitError, which removes the most likely sources of misuse.