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:
SuaveplanErrorabstract base class with{ code, message, cause?, context?, timestamp, traceId? }shape- Stable code registry — namespaced
PACKAGE.DOMAIN.REASONstrings, 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
instanceofchecks 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
msgpackdep; binary format is documented and versioned) - Redaction —
@sensitivetag 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 usesDate.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)