# `error-codes` — `SAND_PERM.*` registry _(no submodules)_ ## Purpose This module owns the stable error-code registry for the `@suaveplan/api-sandbox-permissions` package. Every deny path raised by `PermissionChecker` or `BudgetTracker` surfaces as a `SandboxPermissionError` carrying a code from `SAND_PERM_CODES`. The namespace is pinned at module-import time so consumers can match either structurally (`instanceof SandboxPermissionError`) or by stable string constant (`error.code === SAND_PERM_CODES.DENIED`). Pinning the registry in a leaf module that depends only on `@suaveplan/error` keeps the deny-code surface tree-shakeable: a host that only consumes the budget tracker pulls one code, not the whole package, and avoids the OTel metric package on cold paths. ## Features - `SAND_PERM_CODES.DENIED` (`"SAND_PERM.DENIED"`) — raised by every `PermissionChecker.check*` deny return that the caller chooses to surface as a thrown error. - `SAND_PERM_CODES.BUDGET_EXCEEDED` (`"SAND_PERM.BUDGET_EXCEEDED"`) — raised by `BudgetTracker.tick()` when any of `cpuPercent`, `memoryMb`, or `wallClockMs` is exceeded. - `SandboxPermissionCode` discriminated string union. - `SandboxPermissionError` typed subclass of `SuaveplanError` with pre-set `name = "SandboxPermissionError"` for stack-trace clarity. ## Usage: Basic ```ts import { SAND_PERM_CODES, SandboxPermissionError, } from "@suaveplan/api-sandbox-permissions/error-codes"; try { // call into the checker } catch (err) { if (err instanceof SandboxPermissionError) { if (err.code === SAND_PERM_CODES.DENIED) { // map to PERMISSION_DENIED inside the plugin } } } ``` ## Usage: Advanced ```ts import { SAND_PERM_CODES, SandboxPermissionError, } from "@suaveplan/api-sandbox-permissions/error-codes"; function reraiseDeny(capability: string, reason: string): never { throw new SandboxPermissionError( SAND_PERM_CODES.DENIED, `sandbox permission denied for capability "${capability}"`, { context: { capability, reason } } ); } ``` The constructor signature mirrors `SuaveplanError`, so `{ cause, traceId, clock, context }` are all supported. The `context` object is what the wire boundary serialises as `details` — it MUST stay redaction-safe (no paths, no hosts, no secret names), exactly the `{ capability, reason }` envelope documented in the conformance spec. ## API Reference - `SAND_PERM_CODES: { readonly DENIED: "SAND_PERM.DENIED"; readonly BUDGET_EXCEEDED: "SAND_PERM.BUDGET_EXCEEDED" }` - `SandboxPermissionCode = "SAND_PERM.DENIED" | "SAND_PERM.BUDGET_EXCEEDED"` - `class SandboxPermissionError extends SuaveplanError` - `constructor(code, message, options?)` — accepts `{ context, cause, traceId, clock }`. ## Implementation Notes The registry uses `defineCodes` rather than a hand-rolled object literal so the union type is inferred from the `as const` tuple of reasons. This is the same pattern as `@suaveplan/api-host`'s `HOST_CODES` / `PLUGIN_CODES` etc. — every host-stack package collapses its codes to a single `defineCodes` call so adding a reason is one-line and the type flows through the rest of the package automatically. No OTel or zod imports here on purpose — the registry must compile even when the rest of the package is tree-shaken out. The instrument descriptors and `Zod` ingestion validators live in their own sub-modules.