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

3.4 KiB

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

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

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<SandboxPermissionCode>
    • 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.