Initial Commit
This commit is contained in:
225
openspec/archive/2026-04-24-enterprise-events-schema/proposal.md
Normal file
225
openspec/archive/2026-04-24-enterprise-events-schema/proposal.md
Normal file
@@ -0,0 +1,225 @@
|
||||
# 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<string, number> // keyed by event.name
|
||||
rejectionsTotal: Record<string, number>
|
||||
avgValidationMs: Record<string, number>
|
||||
unknownSchemaTotal: number
|
||||
}
|
||||
```
|
||||
|
||||
### Error handling
|
||||
|
||||
```ts
|
||||
class EventsSchemaError extends SuaveplanError<EventsSchemaErrorCode>
|
||||
|
||||
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).
|
||||
@@ -0,0 +1,84 @@
|
||||
# Tasks: enterprise-events-schema
|
||||
|
||||
- **Change ID**: enterprise-events-schema
|
||||
- **Package**: `@suaveplan/events-schema`
|
||||
- **Date**: 2026-04-24
|
||||
|
||||
## Prerequisites (must be published to Verdaccio first)
|
||||
|
||||
- [x] `@suaveplan/events`
|
||||
- [x] `@suaveplan/schema-registry`
|
||||
- [x] `@suaveplan/error`
|
||||
- [x] `@suaveplan/types`
|
||||
|
||||
## Tasks
|
||||
|
||||
### 1. Scaffold package
|
||||
|
||||
- [x] Create `universal/events/events-schema/` directory tree per file layout in `proposal.md`
|
||||
- [x] Write `package.json` (name `@suaveplan/events-schema`, version `0.1.0`, `sideEffects: false`, correct workspace deps and catalog externals)
|
||||
- [x] Write `tsconfig.json` extending `@suaveplan/typescript-config/library.json`
|
||||
- [x] Write `vitest.config.ts` using `createTestConfig()` from `@suaveplan/testing/config`
|
||||
|
||||
### 2. Error codes (`src/error-codes.ts`)
|
||||
|
||||
- [x] Define `EventsSchemaErrorCode` union type
|
||||
- [x] Call `defineCodes` to register all `EVENTS_SCHEMA.*` codes
|
||||
- [x] Export `registerEventsSchemaErrorCodes()` and `EventsSchemaError`
|
||||
|
||||
### 3. Types (`src/types/common.ts`)
|
||||
|
||||
- [x] Define and export `EventSchemasOptions`, `EventSchemasHandle`, `ValidationMode`, `RejectionHandler`, `ValidationRejection`, `ValidationError`, `SchemaValidationStats`, `SchemaRegistrationOptions`
|
||||
- [x] Write `common.md`
|
||||
|
||||
### 4. Sensitive field redaction (`src/validation/redaction/`)
|
||||
|
||||
- [x] Implement redaction logic: scan `ZodError` issues for paths, mask `.received` values when field schema carries `.meta({ sensitive: true })`
|
||||
- [x] Write `.md`, `.test.ts`, `index.ts`
|
||||
|
||||
### 5. Validator (`src/validation/validator/`)
|
||||
|
||||
- [x] Implement validation logic for `strict`, `warn`, and `sample` modes
|
||||
- [x] Inject `rng` for sample-rate decisions
|
||||
- [x] Apply redaction before constructing `ValidationError` entries
|
||||
- [x] Write `.md`, `.types.ts`, `.test.ts`, `index.ts`
|
||||
|
||||
### 6. Schema registration helper (`src/schema/register-event-schema/`)
|
||||
|
||||
- [x] Implement `registerEventSchema(registry, eventName, schema, options?)` that calls `registry.checkCompatibility` before registering
|
||||
- [x] Throw `EventsSchemaError("EVENTS_SCHEMA_COMPATIBILITY_BREAK")` on incompatible change
|
||||
- [x] Write `.md`, `.test.ts`, `index.ts`
|
||||
|
||||
### 7. Core `attachEventSchemas` (`src/core/attach-event-schemas/`)
|
||||
|
||||
- [x] Implement `attachEventSchemas(bus, options?)` via `@suaveplan/events` interceptor API only
|
||||
- [x] Validate payload against registered schema on each dispatch using configured `mode`
|
||||
- [x] Accumulate `SchemaValidationStats`
|
||||
- [x] Emit OTel counters and histogram per `proposal.md` §4
|
||||
- [x] Return `EventSchemasHandle` with `detach()`, `registerSchema()`, `stats()`
|
||||
- [x] Write `.md`, `.types.ts`, `.test.ts`, `index.ts`
|
||||
|
||||
### 8. Tests
|
||||
|
||||
- [x] Unit tests co-located (100% coverage target)
|
||||
- [x] `tests/integration.test.ts` — attach to real EventBus, register schema, dispatch valid and invalid payloads, assert rejection counts and mode behaviour
|
||||
- [x] `tests/property.test.ts` — fast-check properties per `proposal.md` §5
|
||||
|
||||
### 9. Docs
|
||||
|
||||
- [x] Every `.ts` implementation file has a sibling `.md`
|
||||
- [x] Root `README.md`
|
||||
|
||||
### 10. Pre-publish gate
|
||||
|
||||
- [x] `bunx turbo lint typecheck test --filter=@suaveplan/events-schema` green
|
||||
- [x] `bunx turbo test:coverage --filter=@suaveplan/events-schema` ≥ 100% on touched modules
|
||||
|
||||
### 11. Publish
|
||||
|
||||
- [x] `bun scripts/publish.ts --package @suaveplan/events-schema`
|
||||
|
||||
### 12. Archive
|
||||
|
||||
- [x] Fold spec delta into `openspec/specs/events/spec.md`
|
||||
- [x] `mv openspec/changes/enterprise-events-schema openspec/archive/2026-04-24-enterprise-events-schema`
|
||||
Reference in New Issue
Block a user