PermDock
Decisions

0015: No runtime dependencies in core

Why permdock depends only on @standard-schema/spec, ships ESM-only, and keeps validators, serialisers and OpenTelemetry out of core.

Status

Accepted, September 2026. The size-budget bullet is superseded by 0020.

Context

PermDock core runs in browsers, React Native, edge runtimes, serverless functions, MCP servers and Node. Every dependency it carries is paid by all of them. The landscape has clear examples of the cost:

  • Kilpi depends on zod and superjson in core, so a Kilpi client bundle carries a validator it never calls and a serialiser for dates and maps.
  • @zap-studio/permit makes @opentelemetry/api a required peer, so apps without tracing still install it.
  • permix stays at 2.64 kB gzip precisely because it has no dependencies; CASL core is around 6 kB with @ucast.

Standard Schema exists so that a library can accept Zod, Valibot, ArkType or Effect schemas without depending on any of them. Its spec package is types-only, versioned, and part of PermDock's public API surface (resource definitions take a StandardSchemaV1), which is the documented case for installing it as a regular dependency rather than vendoring the types.

Decision

  • permdock has exactly one runtime dependency: @standard-schema/spec. Everything else is an optional peer dependency of the adapter that needs it (react, next, hono, @modelcontextprotocol/server, ai, drizzle-orm, ...).
  • No validator, no serialiser, no telemetry in core. Conditions and snapshots are plain JSON with tagged ISO date strings; there is no superjson. OpenTelemetry lives in permdock/otel behind a structural, type-only Tracer interface.
  • ESM-only. No CommonJS build, no dual package hazard, "type": "module" with an exports map.
  • Size is measured, not capped in advance. Per-entry gzip is recorded in tests/bundle; the regression baseline is the measured size after core ships, not a 3 kB guess (0020).
  • No eval, no new Function, no dynamic require; condition evaluation is a switch over the AST.

Consequences

  • One install brings no transitive surprises; a React Native or edge bundle contains only what it imports.
  • Heavy work belongs in @permdock/cli (parsers) and @permdock/testing (test frameworks), never in permdock (0002).
  • Features that need libraries (delegation-chain signature verification, quota stores, RLS parsing) are adapters or CLI commands, which shapes the roadmap.
  • Contributors cannot reach for a utility package; small helpers are written in-tree and covered by tests.
  • Keeping validators, serialisers and telemetry out of core also keeps the condition AST, the permission tree and snapshots small. A numeric gzip cap is not set until that core is measured (0020).

Alternatives considered

  • Depend on Zod for schemas and validation (Kilpi). Rejected: locks out Valibot and ArkType users and adds tens of kilobytes to clients.
  • superjson for dates and sets in conditions. Rejected: tagged ISO strings cover the RLS-portable subset, and one JSON format must work in Postgres, snapshots and catalogs.
  • Required @opentelemetry/api peer (@zap-studio/permit). Rejected; a type-only logger and tracer interface plus an optional permdock/otel adapter gives the same spans without the install.
  • Dual CJS and ESM builds (CASL). Rejected: the target runtimes are ESM, Node 24 is the floor, and dual builds double the test matrix.
  • Vendoring the Standard Schema types. Acceptable per the spec, rejected because the types are part of PermDock's public API and should version with the spec.

On this page