# `RulesError` — Typed error registry for `@suaveplan/rules` _(no submodules)_ ## Purpose `RulesError` is the single error class thrown across the entire `@suaveplan/rules` surface. It extends `SuaveplanError` so every failure carries a stable, machine-readable code drawn from a frozen 24-entry registry, plus the standard `SuaveplanError` wire format, OTel-adapter integration, and cause-chain preservation. Callers should never catch a generic `Error` from this package — `instanceof RulesError` (or `isRulesError(value)`) is the canonical discrimination path. The registry is defined at module load via `defineCodes("RULES", [...] as const)`, which exports `RULES_CODES` as a readonly tuple literal. The `RulesErrorCode` type is derived from that tuple, so the TypeScript compiler enforces that every `new RulesError(code, ...)` call site uses a code that actually exists in the registry. Adding a new code requires an OpenSpec change (the conformance script verifies the registry against the spec's `## ADDED Requirements`). ## Features - 24 stable codes covering engine / operator / field-resolution / action / event / validator / serializer / optimizer / plugin failure modes. - `RulesError` class extends `SuaveplanError` for cross-package wire-format and OTel-adapter compatibility. - Optional `cause`, `traceId`, and `details` fields preserved through construction. - `isRulesError(value)` type guard for catch arms that need to discriminate package errors from unrelated runtime errors. - Every code is forward-declared at module load — no dynamic registration, no string-typed escape hatches. ## Usage: Basic Example ```ts import { RulesError, isRulesError } from "@suaveplan/rules/errors"; function evaluate(rule: unknown) { if (typeof rule !== "object" || rule === null) { throw new RulesError("RULES.RULE_INVALID", "rule must be an object"); } // ... real evaluation } try { evaluate(null); } catch (err) { if (isRulesError(err) && err.code === "RULES.RULE_INVALID") { // structured handling } else { throw err; } } ``` ## Usage: Advanced Example ```ts import { RulesError } from "@suaveplan/rules/errors"; async function resolveField(data: unknown, path: string, strict: boolean) { try { return walk(data, path); } catch (cause) { throw new RulesError( "RULES.FIELD_RESOLUTION_FAILED", `cannot resolve "${path}" in strict mode`, { cause, traceId: currentTraceId(), details: { path, strict, hasData: data !== undefined }, }, ); } } ``` The `details` object becomes part of the structured wire format when the error crosses a process boundary (for example, when a Hono middleware serialises a denied request into a JSON envelope). The OTel adapter on `SuaveplanError` projects `code`, `traceId`, and `details` as span attributes automatically. ## API Reference ### `RULES_CODES: readonly RulesErrorCode[]` The frozen 24-entry registry of stable error codes. Every entry is prefixed with `RULES.` and corresponds to a documented failure mode in the package's OpenSpec contract. ### `type RulesErrorCode` Literal-union type derived from `RULES_CODES`. Constructor calls that pass a string not in the union are rejected at compile time. ### `class RulesError extends SuaveplanError` - `new RulesError(code, message, opts?)` — constructor. - `code: RulesErrorCode` — required, must be a member of `RULES_CODES`. - `message: string` — required, human-readable summary. - `opts.cause?: unknown` — original thrown value when wrapping. - `opts.traceId?: string` — propagated to the SuaveplanError base; appears in OTel span attributes. - `opts.details?: Readonly>` — structured context; omitted from `err.details` when not supplied. - Inherits `code`, `message`, `cause`, `traceId`, `toWire()` from `SuaveplanError`. - `name` is `"RulesError"`. ### `isRulesError(value: unknown): value is RulesError` Returns `true` only when `value` is a `RulesError` instance. Plain `Error` objects with a `code` field do not satisfy the guard. ## Implementation Notes - The registry is intentionally flat (no nested namespaces). Sub-domain grouping is expressed via the prefix (`ENGINE_*`, `ACTION_*`, `VALIDATOR_*`) rather than a structured tree, matching the convention used by `@suaveplan/crypto-rsa` and `@suaveplan/webauthn`. - `details` is stored only when explicitly supplied; the property is `undefined` (not an empty object) otherwise. This keeps the wire format compact and lets receivers test `if (err.details)` without false positives. - `RulesError` is `extends SuaveplanError` not `extends Error` — the AGENTS.md §2c rule against deleting registry entries to satisfy a gate applies here. If a code is "unused", the fix is to wire it into the path the proposal designed it for, not to remove it. - Cross-runtime: tests import runner primitives from `@suaveplan/testing/runner` per CLAUDE.md rule 11. Direct `vitest` / `bun:test` imports are banned in this package.