# OpenSpec Change: enterprise-events-schema - **Change ID**: enterprise-events-schema - **Package**: `@suaveplan/events-schema` - **Category**: universal - **Target path**: `universal/events/events-schema` - **Status**: PROPOSED - **Date**: 2026-04-24 - **Reference spec**: `/mnt/s/web/suaveplan/enterprise/events-schema.md` --- ## Summary Deliver `@suaveplan/events-schema` on the genesis branch from scratch at enterprise quality. The package is a runtime schema-validation companion for `@suaveplan/events`: it validates event envelopes and payloads at publish and subscribe boundaries against Zod schemas registered in `@suaveplan/schema-registry`, surfaces structured rejections with redacted field values, and supports three validation modes (strict, warn, sample) for production rollout. Attaches via the public `@suaveplan/events` interceptor API. --- ## Current-state audit (genesis) The package does not exist on genesis yet — fresh build. No prior source, no test results, no coverage data. --- ## Available workspace deps on genesis | Package | Role | |---|---| | `@suaveplan/events` (`workspace:^`) | Event bus and interceptor API | | `@suaveplan/schema-registry` (`workspace:^`) | Schema registration, version history, compatibility checking | | `@suaveplan/error` (`workspace:^`) | Base class for `EventsSchemaError`; `defineCodes` for `EVENTS_SCHEMA.*` codes | | `@suaveplan/types` (`workspace:^`) | `LoggerLike`, `Clock`, `Rng` injection interfaces | | `zod` (`catalog:forms`) | Zod schemas for event envelope contract and option types | | `@opentelemetry/api` (`catalog:observability`) | Rejection counters, validation latency histograms | | `tslib` (`catalog:build`) | Runtime helpers | Dev deps: `vitest`, `@vitest/coverage-istanbul`, `fast-check` (all `catalog:testing`). --- ## Public API surface ### Setup ```ts // Attaches schema-validation companion to an existing EventBus function attachEventSchemas(bus: EventBusLike, options?: EventSchemasOptions): EventSchemasHandle interface EventSchemasOptions { registry?: SchemaRegistry // default: global registry mode?: ValidationMode // default: "strict" sampleRate?: number // used when mode === "sample"; 0.0–1.0 onRejection?: RejectionHandler redactSensitive?: boolean // default: true clock?: Clock rng?: Rng logger?: LoggerLike } type ValidationMode = "strict" | "warn" | "sample" type RejectionHandler = (rejection: ValidationRejection) => void interface EventSchemasHandle { detach(): void registerSchema(eventName: string, schema: ZodSchema, version?: string): void stats(): SchemaValidationStats } ``` ### Schema registration ```ts // Register a Zod schema for an event name (standalone, outside attachEventSchemas) function registerEventSchema( registry: SchemaRegistry, eventName: string, schema: ZodSchema, options?: SchemaRegistrationOptions ): void interface SchemaRegistrationOptions { version?: string // default: "1.0.0" description?: string compatibility?: "BACKWARD" | "FORWARD" | "FULL" | "NONE" } ``` ### Validation result ```ts interface ValidationRejection { eventName: string eventId?: string payload: unknown errors: ValidationError[] mode: ValidationMode timestamp: Date } interface ValidationError { path: string message: string expected?: string received?: string // redacted if field is marked @sensitive } ``` ### Stats ```ts interface SchemaValidationStats { validationsTotal: Record // keyed by event.name rejectionsTotal: Record avgValidationMs: Record unknownSchemaTotal: number } ``` ### Error handling ```ts class EventsSchemaError extends SuaveplanError type EventsSchemaErrorCode = | "EVENTS_SCHEMA_VALIDATION_FAILED" | "EVENTS_SCHEMA_UNKNOWN" | "EVENTS_SCHEMA_COMPATIBILITY_BREAK" | "EVENTS_SCHEMA_ATTACH_FAILED" function registerEventsSchemaErrorCodes(): void ``` ### Zod schemas ```ts const EventSchemasOptionsSchema: ZodSchema const ValidationRejectionSchema: ZodSchema const SchemaRegistrationOptionsSchema: ZodSchema ``` --- ## New additions for genesis ### 1. Three validation modes - `"strict"` — reject the dispatch and throw `EventsSchemaError` if validation fails - `"warn"` — log the rejection via `LoggerLike` but let the event through; useful for canary rollouts - `"sample"` — validate a random `sampleRate` fraction of events; the rest pass-through unchecked; useful for high-volume events where full validation is too expensive ### 2. Sensitive field redaction When `redactSensitive: true`, field values that fail validation are replaced with `"[REDACTED]"` in the `ValidationError.received` output. Fields are marked sensitive via a Zod `.meta({ sensitive: true })` convention. ### 3. Schema compatibility CI hook `EventSchemasHandle.registerSchema` calls `registry.checkCompatibility` under the hood. An incompatible schema registration throws `EventsSchemaError("EVENTS_SCHEMA_COMPATIBILITY_BREAK")` synchronously. ### 4. OTel instrumentation - Counter: `events_schema.validations_total` (attribute: `event.name`, `validation.result: "pass"|"fail"`) - Counter: `events_schema.unknown_schema_total` - Histogram: `events_schema.validation_duration_ms` (attribute: `event.name`) ### 5. Property-based tests (fast-check) - Schema round-trip: valid payloads generated by the schema always pass validation - Rejection completeness: every field-level Zod error surfaces as a `ValidationError` entry - Mode behaviour: `"warn"` mode never throws; `"strict"` always throws on invalid payloads; `"sample"` at 0.0 never validates - Compatibility check: re-registering a schema with a removed required field always throws under `BACKWARD` compatibility --- ## File layout ```text universal/events/events-schema/ package.json tsconfig.json vitest.config.ts src/ index.ts error-codes.ts constants/ index.ts core/ attach-event-schemas/ attach-event-schemas.ts attach-event-schemas.md attach-event-schemas.types.ts attach-event-schemas.test.ts index.ts schema/ register-event-schema/ register-event-schema.ts register-event-schema.md register-event-schema.test.ts index.ts validation/ validator/ validator.ts validator.md validator.types.ts validator.test.ts index.ts redaction/ redaction.ts redaction.md redaction.test.ts index.ts tests/ integration.test.ts property.test.ts ``` --- ## Impact **Downstream packages**: any event-driven application needing runtime contract enforcement without deploying a separate message broker schema registry. Works alongside `@suaveplan/events-otel` and `@suaveplan/events-metrics`. **Workspace dep prerequisites**: `@suaveplan/events`, `@suaveplan/schema-registry`, `@suaveplan/error`, `@suaveplan/types` must be published to Verdaccio. **Breaking changes**: none (new package).