Skip to content

@substrat-run/kernel

The kernel's behavioral seams in pure TypeScript. This package imports no platform APIs — Cloudflare specifics live only in adapters, and every adapter must pass the same conformance suite.

sh
pnpm add @substrat-run/kernel @substrat-run/contracts

Scope host (scope-host.ts)

The adapter seam. Full semantics in Operations & the scope host.

ExportKindPurpose
ScopeHostinterfacegetScope, provisionScope, registerModule, defineOperation, admin, close
ScopeStubinterfacethe capability — the only way code outside a scope reaches it
OperationContextinterfacewhat a handler sees: ambient tenantId/scopeId/principal, sql, emit, check, link
OperationHandler<I, O>type(ctx, input) => O | Promise<O>
ConsumerHandlertypeevent consumer; at-least-once, must be idempotent
ModuleRegistrationinterfacemanifest + migrations + operations + consumers
SqlMigrationinterface{ version, sql } — ordered, journaled per module
ScopedSql, SqlValuetypessynchronous scope-local SQL: query<T>(), exec()
ExecutorHandlertype(admin, event) => void — out-of-band host code effecting what a module asked for via an event (K-22). Registered with registerExecutor; receives HostAdmin, never ctx, because it acts with platform authority
HostAdmininterfacethe audited control-plane surface — enforcement input (defineRole, assignRole, grant, grantToOrg, addMember, removeMember, listMembers), organizations (createOrg, listOrgs, getOrg), identity (registerIdentityPool, linkIdentity, resolveIdentity, listIdentityTenants), tenant registry (createTenant, setTenantStatus, listTenants, getTenant), scope lifecycle (suspendScope, unsuspendScope, archiveScope, unarchiveScope), entitlements (grantEntitlement, revokeEntitlement, listEntitlements), and auditLog. Every mutation takes a PlatformActorId and writes an append-only audit row
ProvisionScopeInputinterfacetenant, scope, optional shape + jurisdiction (provisioned via provisionScope(actor, input))

Permission checker (permission-checker.ts)

The evaluation seam — the model is kernel-owned, the engine is swappable. See Permissions.

ExportPurpose
PermissionCheckercheck(principal, permission, node, entity?) → Promise<Decision>
assertAllowed(decision)throws PermissionDenied unless allowed; the standard first line of an operation. Narrows the type to the proof-carrying allow.
PermissionDeniedthe error class
denyAllCheckersecure default — denies everything
UNSAFE_allowAllCheckertest-only; grants everything via a synthetic proof tuple. The name is the warning.

ulid()

A dependency-free ULID generator — the ID scheme used everywhere (ids in @substrat-run/contracts).

ts
import { ulid } from '@substrat-run/kernel';
const id = ulid(); // '01JZX6ZH2E...'

Guarantees adapters must uphold

Any ScopeHost implementation must provide — verified by @substrat-run/contract-tests:

  • Strict serialization per scope — one operation at a time, to completion.
  • Structured-clone boundary — inputs/results cloned both directions, even in-process.
  • Kernel-stamped events — id, timestamp, tenant, scope, actor stamped below the API surface.
  • Fail-closed addressing — mismatched (tenantId, scopeId) throws, never resolves elsewhere.
  • PII invariant at emit — PII-classed events without subjectId are rejected.

The hard parts, hosted.