auth/scope_kind_schema.ts

Scope-kind registry for role_grants and role_grant offers.

Role grants have a polymorphic scope_id that references whatever entity the consumer chooses (a classroom, a tenant, a workspace, etc.); the scope_kind column tags each row with a machine-readable kind so admin UIs, codegen, and (in v2) registry-time (role, scope_kind) compatibility checks can read it without re-deriving from scope_id.

scope_kind is encoded as nullable paired with the existing nullable scope_id — both null for global, both non-null for scoped, mismatch rejected at the DB layer by the role_grant_scope_kind_paired / role_grant_offer_scope_kind_paired CHECK constraints. There is no 'global' magic-string value; the global case is unambiguously (scope_kind=NULL, scope_id=NULL).

Open registry, no builtins. Consumers declare their kinds via create_scope_kind_schema(consumer_kinds) and pass the result to create_role_schema so RoleSpec.applicable_scope_kinds can be validated at construction time. Mirrors the open-string registry pattern used for RoleName, AuditEventTypeName, and CredentialType.

The literal 'GLOBAL' (uppercase) appears as an index expression inside the partial unique indexes on role_grant and role_grant_offer (COALESCE(scope_kind, 'GLOBAL')) — never as a column value, never as a registry entry. The uppercase form is structurally distinct from any consumer-declared kind (which match the lowercase ScopeKindName regex), so it cannot collide.

Declarations
#

5 declarations

view source

create_scope_kind_schema
#

auth/scope_kind_schema.ts view source

(consumer_kinds: Record<string, ScopeKindMeta>): ScopeKindSchemaResult

Create a scope-kind schema from a consumer-declared registry.

Open registry — no builtins. The 'GLOBAL' token used inside the partial unique indexes on role_grant and role_grant_offer is not a registry entry (it's an index expression only) and cannot collide with consumer-declared kinds because the regex rejects uppercase.

Call once at server init. Pass the result into create_role_schema's optional scope_kinds parameter so each role's applicable_scope_kinds entries are validated against this set at construction time. v1 keeps applicable_scope_kinds informative-only (registry-membership validation only); v2 may add INSERT-time (role, scope_kind) enforcement once the shape is clear from real consumer usage.

consumer_kinds

the consumer-declared scope-kind set with optional metadata

type Record<string, ScopeKindMeta>

returns

ScopeKindSchemaResult

{ScopeKind, scope_kinds} — Zod schema and metadata map

throws

  • Error - if any `consumer_kinds` key fails the `ScopeKindName` regex or appears more than once

examples

// visiones const {ScopeKind, scope_kinds} = create_scope_kind_schema({ classroom: {description: 'A classroom — teacher and student role_grants scope here.'}, });

SCOPE_KIND_NAME_REGEX
#

auth/scope_kind_schema.ts view source

RegExp

Letter (lowercase a-z) start and end (or single letter), with letters and underscores in between. Mirrors RoleName. Rejects empty strings, leading or trailing underscores, uppercase, digits, and the index-side 'GLOBAL' token.

ScopeKindMeta
#

auth/scope_kind_schema.ts view source

ScopeKindMeta

Per-scope-kind metadata. description is admin-UI-facing copy (mirrors RoleSpec.description). Open shape so v2 can extend without a breaking change.

description

type string

ScopeKindName
#

ScopeKindSchemaResult
#

auth/scope_kind_schema.ts view source

ScopeKindSchemaResult

The result of create_scope_kind_schema — a Zod schema and metadata map.

ScopeKind

Zod schema that validates scope-kind name strings against the registered set. Use at I/O boundaries (admin UIs, codegen) and as the construction-time check inside create_role_schema for every RoleSpec.applicable_scope_kinds entry.

type z.ZodType<string>

scope_kinds

Map of every registered scope-kind to its metadata. Keyed by name. Read at startup by admin / codegen surfaces.

type ReadonlyMap<string, ScopeKindMeta>