# `checker/checker` — `PermissionChecker` _(no submodules)_ ## Purpose This module is the composite entry point that the two sandbox hosts (`api-sandbox-vm`, `api-sandbox-wasm`) wire into their worker capability proxy. It binds a `PermissionPolicy` to a `PermissionInstruments` handle and exposes the three production check methods plus three `assert*` helpers that throw `SandboxPermissionError` in the default `"enforce"` deny mode. Construction is per plugin-worker pair — never global — because the policy and instruments both pin the `plugin` label and the worker boundary is the natural granularity for the check + telemetry pair. ## Features - `PermissionChecker.checkFs / checkFetch / checkCapability` — invoke the underlying pure function AND record the deny counter on a deny outcome. - `PermissionChecker.assertFs / assertFetch / assertCapability` — same as above, but in `"enforce"` mode raise `SandboxPermissionError` on deny; in `"warn"` mode return the deny result silently after recording the counter. - `DenyMode` — `"enforce"` (default) or `"warn"`. - Deny telemetry — every deny path records exactly one `plugin.permission_denied_total` increment with the documented `{ plugin, capability, reason }` label set. ## Usage: Basic ```ts import { PermissionChecker } from "@suaveplan/api-sandbox-permissions/checker"; import { createPermissionInstruments } from "@suaveplan/api-sandbox-permissions/instruments"; import { PermissionPolicy } from "@suaveplan/api-sandbox-permissions/policy"; const checker = new PermissionChecker({ policy: new PermissionPolicy({ permissions: { fs: ["./uploads/*"], fetch: ["api.stripe.com"] }, }), instruments: createPermissionInstruments({ plugin: "billing" }), }); const fs = checker.checkFs("./uploads/avatar.png", "read"); console.assert(fs.outcome === "allow"); ``` ## Usage: Advanced ```ts import { PermissionChecker } from "@suaveplan/api-sandbox-permissions/checker"; import { createPermissionInstruments } from "@suaveplan/api-sandbox-permissions/instruments"; import { PermissionPolicy } from "@suaveplan/api-sandbox-permissions/policy"; import { SAND_PERM_CODES, SandboxPermissionError } from "@suaveplan/api-sandbox-permissions/error-codes"; // Canary mode — record denies on metrics, do not block the call. const canary = new PermissionChecker({ policy: new PermissionPolicy({ permissions: { fs: ["./uploads/*"] }, }), instruments: createPermissionInstruments({ plugin: "billing" }), mode: "warn", }); const warnResult = canary.assertFs("./etc/passwd", "read"); console.assert(warnResult.outcome === "deny"); // `plugin.permission_denied_total{plugin: "billing", capability: "fs", // reason: "path-not-in-allowlist"}` incremented; no throw. // Enforcing mode — throws on deny. const strict = new PermissionChecker({ policy: canary.policy, instruments: canary.instruments, // mode: "enforce" — default }); try { strict.assertFs("./etc/passwd", "read"); } catch (err) { if (err instanceof SandboxPermissionError) { console.assert(err.code === SAND_PERM_CODES.DENIED); } } ``` ## API Reference - `class PermissionChecker` - `constructor({ policy, instruments, mode? })` - `readonly policy: PermissionPolicy` - `readonly instruments: PermissionInstruments` - `readonly mode: DenyMode` - `checkFs(path, mode): CheckResult` - `checkFetch(url): CheckResult` - `checkCapability(payload): CheckResult` - `assertFs(path, mode): CheckResult` (throws in enforce mode) - `assertFetch(url): CheckResult` - `assertCapability(payload): CheckResult` - `type DenyMode = "enforce" | "warn"` - `type CheckerCapabilitySource = "fs" | "fetch" | "db" | "secrets" | "events"` ## Implementation Notes The class is deliberately stateless apart from its three readonly fields — deny telemetry is delegated to the `PermissionInstruments` handle, which is itself stateless behind the OTel counters. This means the checker is safe to instantiate once per plugin-worker pair and share across every in-flight capability call. The `assert*` helpers wrap `check*` rather than the pure functions so the counter increments exactly once per deny: the underlying pure functions do not call `recordDenial`, only the checker methods do. A deny-mode-`warn` call therefore records the same counter the `enforce` mode would, which is what dashboards rely on to detect canary-phase regressions.