Files
2026-05-27 23:29:53 +01:00

3.2 KiB

Change: enterprise-error

Why

@suaveplan/error is the foundation error substrate — every other package in the monorepo extends SuaveplanError and uses its stable code registry. On the genesis branch it does not yet exist. It must land before any other Layer 1 package (events, crypto, bitpacker, timing, datetime, serialize, mime, env, opentelemetry) because each of them declares its own error subclass against this base.

Per the project rule for foundation packages (memory: foundation-100-coverage), this ships at 100% coverage (statements / branches / functions / lines) — not the 95% floor. Anything less puts a long-term ceiling on every consumer.

What Changes

New package: packages/universal/error

Ships a self-contained, zero-dependency (no @suaveplan/*, no runtime browser/Node/React APIs) TypeScript library:

  • SuaveplanError abstract base class with { code, message, cause?, context?, timestamp, traceId? } shape
  • Stable code registry — namespaced PACKAGE.DOMAIN.REASON strings, typed as a const record; each package adds its own leaf registry that extends this one without renumbering
  • Typed error discriminants — generics propagate the code union through instanceof checks so consumers get exhaustive switch coverage without casts
  • Cause-chain traversal — walkCauses(err) generator, cycle-safe, depth-bounded
  • Wire format — Zod schema for JSON and MessagePack (MessagePack encoder ships as a pure function, no external msgpack dep; binary format is documented and versioned)
  • Redaction — @sensitive tag on context fields, applied at the serialization boundary; redacted fields become "[REDACTED]" in the wire payload but remain accessible on the in-memory instance
  • Injectable clock — createError({ clock }) takes an optional clock so timestamps are deterministic under test; the module-level default uses Date.now() but is never touched at import time
  • OTel span event adapter — toSpanEvent(err) returns the payload an OTel span recorder expects (exception.type, exception.message, exception.stacktrace, all attribute values serialized)
  • i18n-ready — every user-visible string is keyed, never literal; the default locale is pass-through (returns the key)

Why this stays foundation-pure

The enterprise brief calls for @suaveplan/schema-registry, @suaveplan/logger, @suaveplan/i18n, and @suaveplan/opentelemetry integration. Those are adapters, not dependencies — this package exposes the hooks (toSpanEvent, i18n-key strings, Zod schemas, logger-compatible toJSON) but does not import any of them. Wiring happens in the dependent packages.

Coverage gate

Vitest thresholds set to 100/100/100/100 from day one. No istanbul ignore pragmas (empirically stripped by the esbuild transform). Dead defensive branches are either removed or upgraded to as assertions with invariant comments.

Impact

  • Unblocks every other Layer 1 package (events, crypto, bitpacker, timing, datetime, serialize, mime, env, opentelemetry) which extend SuaveplanError
  • Adds one new spec capability: error
  • No existing code paths affected (genesis branch has no consumers yet)