From 896ab3b3d359731aafaaafeff1617e0e18ce205f Mon Sep 17 00:00:00 2001 From: Dan Lynch Date: Wed, 22 Jul 2026 06:40:35 +0000 Subject: [PATCH 1/2] feat(errors): add @constructive-io/errors + server code normalization Add a standalone, zero-dependency @constructive-io/errors package (canonical registry, parse/format/classify, i18n, type-safe factory), make pgpm consume it, and normalize DB codes into GraphQL extensions with class-based masking. --- graphql/server/package.json | 1 + graphql/server/src/middleware/graphile.ts | 75 ++++- packages/errors/README.md | 42 +++ packages/errors/__tests__/parse.test.ts | 111 +++++++ packages/errors/jest.config.js | 8 + packages/errors/package.json | 44 +++ packages/errors/src/classify.ts | 18 ++ packages/errors/src/error.ts | 58 ++++ packages/errors/src/factory.ts | 73 +++++ packages/errors/src/format.ts | 60 ++++ packages/errors/src/index.ts | 9 + packages/errors/src/interpolate.ts | 14 + packages/errors/src/parse.ts | 160 ++++++++++ packages/errors/src/pg.ts | 56 ++++ packages/errors/src/registry.ts | 345 ++++++++++++++++++++++ packages/errors/src/types.ts | 88 ++++++ packages/errors/tsconfig.esm.json | 7 + packages/errors/tsconfig.json | 9 + pgpm/types/package.json | 1 + pgpm/types/src/error-factory.ts | 165 +---------- pnpm-lock.yaml | 19 ++ 21 files changed, 1194 insertions(+), 169 deletions(-) create mode 100644 packages/errors/README.md create mode 100644 packages/errors/__tests__/parse.test.ts create mode 100644 packages/errors/jest.config.js create mode 100644 packages/errors/package.json create mode 100644 packages/errors/src/classify.ts create mode 100644 packages/errors/src/error.ts create mode 100644 packages/errors/src/factory.ts create mode 100644 packages/errors/src/format.ts create mode 100644 packages/errors/src/index.ts create mode 100644 packages/errors/src/interpolate.ts create mode 100644 packages/errors/src/parse.ts create mode 100644 packages/errors/src/pg.ts create mode 100644 packages/errors/src/registry.ts create mode 100644 packages/errors/src/types.ts create mode 100644 packages/errors/tsconfig.esm.json create mode 100644 packages/errors/tsconfig.json diff --git a/graphql/server/package.json b/graphql/server/package.json index 468083816c..b939ce5636 100644 --- a/graphql/server/package.json +++ b/graphql/server/package.json @@ -42,6 +42,7 @@ ], "dependencies": { "@constructive-io/csrf": "workspace:^", + "@constructive-io/errors": "workspace:^", "@constructive-io/express-context": "workspace:^", "@constructive-io/graphql-env": "workspace:^", "@constructive-io/graphql-types": "workspace:^", diff --git a/graphql/server/src/middleware/graphile.ts b/graphql/server/src/middleware/graphile.ts index 674fca1e59..07b237f1fb 100644 --- a/graphql/server/src/middleware/graphile.ts +++ b/graphql/server/src/middleware/graphile.ts @@ -1,4 +1,5 @@ import crypto from 'node:crypto'; +import { classify, type ErrorContext, parse } from '@constructive-io/errors'; import { getNodeEnv } from '@pgpmjs/env'; import type { ComputeConfig } from '@constructive-io/express-context'; import type { ConstructiveOptions } from '@constructive-io/graphql-types'; @@ -20,6 +21,14 @@ import { AuthCookiePlugin } from '../plugins/auth-cookie-plugin'; const maskErrorLog = new Logger('graphile:maskError'); +/** + * Transitional allowlist of transport/framework codes that are safe to surface + * but are not yet in the `@constructive-io/errors` registry (GraphQL protocol + * errors, plus auth codes pending registry generation in a later phase). A code + * is treated as public when it is classified `public` by the registry OR listed + * here. Once the registry is generated from constructive-db these entries move + * into the registry and this set shrinks to just the GraphQL protocol codes. + */ const SAFE_ERROR_CODES = new Set([ // GraphQL standard 'GRAPHQL_VALIDATION_FAILED', @@ -126,27 +135,67 @@ const SAFE_ERROR_CODES = new Set([ '23P01', // exclusion_violation ]); +/** A code is safe to surface when the registry classifies it public, or it is + * on the transitional transport allowlist. */ +const isPublicCode = (code: string | null | undefined): boolean => + Boolean(code) && (classify(code) === 'public' || SAFE_ERROR_CODES.has(code as string)); + /** - * Production-aware error masking function. + * Normalize any GraphQL/database error into a canonical Constructive shape. * - * In development: returns errors as-is for debugging. - * In production: returns errors with explicit codes from the SAFE_ERROR_CODES - * allowlist as-is, but masks unexpected/database errors with a reference ID - * and logs the original. + * Database errors surface through Grafast without a populated `extensions.code` + * (the semantic code lives in the message, and any SQLSTATE/DETAIL lives on the + * underlying pg error at `originalError`). We parse `originalError` first so we + * can recover the structured code, then fall back to the GraphQL error itself. + */ +const normalizeError = ( + error: GraphQLError, +): { code: string | null; context: ErrorContext; class: 'public' | 'internal' } => { + const original = (error as { originalError?: unknown }).originalError; + const fromOriginal = original ? parse(original) : null; + const parsed = fromOriginal?.code ? fromOriginal : parse(error); + return { code: parsed.code, context: parsed.context, class: parsed.class }; +}; + +/** + * Production-aware error handling backed by `@constructive-io/errors`. + * + * 1. Enrich `extensions.code`/`class`/`context` from the parsed error so clients + * always receive a machine-readable code (fixing the gap where database + * errors reached clients as a bare message with empty `extensions`). + * 2. Surface public (registered/allowlisted) errors as-is. + * 3. In development, pass everything through (enriched) for debugging. + * 4. In production, mask internal/unknown errors behind a reference ID and log + * the original. */ const maskError = (error: GraphQLError): GraphQLError | GraphQLFormattedError => { - if (getNodeEnv() === 'development') { - return error; + const { code, context, class: errorClass } = normalizeError(error); + + // Lift the structured code onto extensions for every recognized error so + // clients always receive a machine-readable code (`extensions` is read-only + // on GraphQLError, so we build a formatted error rather than mutating it). + const extensions: Record = { ...error.extensions }; + if (code) { + extensions.code = code; + extensions.class = errorClass; + if (Object.keys(context).length > 0) { + extensions.context = context; + } } - // Only expose errors with codes on the safe allowlist. - // Note: grafserv strips originalError and internal extensions before - // serializing to the client, so returning the full error object is safe here. - if (error.extensions?.code && SAFE_ERROR_CODES.has(error.extensions.code as string)) { - return error; + const effectiveCode = code ?? (error.extensions?.code as string | undefined); + if (isPublicCode(effectiveCode) || getNodeEnv() === 'development') { + // Note: grafserv strips originalError and internal extensions before + // serializing to the client, so returning the enriched error is safe. + return { + message: error.message, + ...(error.locations ? { locations: error.locations } : {}), + ...(error.path ? { path: error.path } : {}), + extensions, + } as GraphQLFormattedError; } - // Mask unexpected/database errors with a reference ID + // Mask internal/unknown errors with a reference ID. const errorId = crypto.randomBytes(8).toString('hex'); maskErrorLog.error(`[masked-error:${errorId}]`, error); diff --git a/packages/errors/README.md b/packages/errors/README.md new file mode 100644 index 0000000000..986e2c5de4 --- /dev/null +++ b/packages/errors/README.md @@ -0,0 +1,42 @@ +# @constructive-io/errors + +Canonical Constructive error system. Zero runtime dependencies — usable by any +service or client without pulling in pgpm. + +- **Registry** — the single source of truth mapping each stable `code` to its + classification (`public` vs `internal`), HTTP hint, and default message. +- **`parse(anyError)`** — normalize an error from any source (a + `ConstructiveError`, a node-postgres `DatabaseError`, a GraphQL error or + `{ errors: [...] }` wrapper, a plain `Error`, or a string) into a canonical + `{ code, context, class, known }`. +- **`format(code, context, locale)`** — render a localized, interpolated + message. `{{var}}` placeholders + registerable per-locale catalogs (i18n). +- **`errors.*` factory** — type-safe throwable builders derived from the + registry, e.g. `throw errors.MODULE_NOT_FOUND({ name })`. +- **`classify(code)`** — `public` or `internal`; unknown codes are `internal` + (fail safe) so transports never leak unregistered errors. + +```ts +import { parse, format, errors, classify } from '@constructive-io/errors'; + +// Normalize a DB error surfaced through GraphQL +const parsed = parse(graphqlError); +if (parsed.class === 'public') { + showUser(format(parsed.code!, parsed.context, locale)); +} + +// Throw a structured error +throw errors.ACCOUNT_EXISTS(); +``` + +## Design notes + +- The machine `code` is deliberately separate from user-facing copy so codes + are stable and messages are localizable. +- `parse()` recovers structure from the code's precedence: structured `DETAIL` + JSON → GraphQL `extensions.code` → a leading ALL_CAPS token in the message + (legacy DB `RAISE`, incl. `CODE (arg, arg)` positional args) → native + SQLSTATE constraint mapping. +- The seeded registry covers the public auth/limit codes, native PostgreSQL + constraint codes, and the pgpm CLI codes. The full constructive-db code set is + generated in a later phase; unregistered codes still parse and are masked. diff --git a/packages/errors/__tests__/parse.test.ts b/packages/errors/__tests__/parse.test.ts new file mode 100644 index 0000000000..1c9f64483c --- /dev/null +++ b/packages/errors/__tests__/parse.test.ts @@ -0,0 +1,111 @@ +import { classify, ConstructiveError, errors, format, parse, registerCatalog } from '../src'; + +describe('parse', () => { + it('parses a bare ALL_CAPS DB message (legacy RAISE)', () => { + const result = parse({ message: 'ACCOUNT_EXISTS', code: 'P0001' }); + expect(result.code).toBe('ACCOUNT_EXISTS'); + expect(result.class).toBe('public'); + expect(result.known).toBe(true); + }); + + it('recovers positional args from a dynamic DB message', () => { + const result = parse({ message: 'LIMIT_REACHED (api_keys, 5)', code: 'P0001' }); + expect(result.code).toBe('LIMIT_REACHED'); + expect(result.context).toEqual({ resource: 'api_keys', limit: 5 }); + }); + + it('prefers structured DETAIL json over the message', () => { + const result = parse({ + message: 'ACCOUNT_EXISTS', + code: 'CX001', + detail: '{"code":"ACCOUNT_EXISTS","context":{"email":"a@b.com"}}' + }); + expect(result.code).toBe('ACCOUNT_EXISTS'); + expect(result.context).toEqual({ email: 'a@b.com' }); + }); + + it('reads GraphQL extensions.code', () => { + const result = parse({ message: 'nope', extensions: { code: 'FORBIDDEN' } }); + expect(result.code).toBe('FORBIDDEN'); + expect(result.class).toBe('public'); + }); + + it('unwraps a GraphQL { errors: [...] } request wrapper', () => { + const result = parse({ errors: [{ message: 'ACCOUNT_EXISTS' }] }); + expect(result.code).toBe('ACCOUNT_EXISTS'); + }); + + it('maps native SQLSTATE constraint violations', () => { + const result = parse({ message: 'duplicate key', code: '23505', constraint: 'users_email_key' }); + expect(result.code).toBe('UNIQUE_VIOLATION'); + expect(result.context.constraint).toBe('users_email_key'); + }); + + it('classifies unknown codes as internal (masked)', () => { + const result = parse({ message: 'DATA_INVARIANT_BROKEN', code: 'P0001' }); + expect(result.code).toBe('DATA_INVARIANT_BROKEN'); + expect(result.known).toBe(false); + expect(result.class).toBe('internal'); + }); + + it('treats a bare P0001 with no semantic token as unknown', () => { + const result = parse({ message: 'some free text', code: 'P0001' }); + expect(result.code).toBeNull(); + expect(result.class).toBe('internal'); + }); + + it('round-trips a ConstructiveError', () => { + const err = errors.ACCOUNT_EXISTS(); + const result = parse(err); + expect(result.code).toBe('ACCOUNT_EXISTS'); + expect(result.class).toBe('public'); + }); +}); + +describe('classify', () => { + it('is internal for null/unknown', () => { + expect(classify(null)).toBe('internal'); + expect(classify('NOT_A_REAL_CODE')).toBe('internal'); + }); +}); + +describe('errors factory', () => { + it('builds a typed ConstructiveError', () => { + const err = errors.MODULE_NOT_FOUND({ name: 'auth' }); + expect(err).toBeInstanceOf(ConstructiveError); + expect(err.code).toBe('MODULE_NOT_FOUND'); + expect(err.message).toBe('Module "auth" not found in modules list.'); + expect(err.http).toBe(404); + }); + + it('supports an override message', () => { + const err = errors.MODULE_NOT_FOUND({ name: 'auth' }, 'custom'); + expect(err.message).toBe('custom'); + }); + + it('exposes GraphQL extensions', () => { + const err = errors.LIMIT_REACHED({ resource: 'api_keys', limit: 5 }); + expect(err.toExtensions()).toMatchObject({ + code: 'LIMIT_REACHED', + class: 'public', + context: { resource: 'api_keys', limit: 5 } + }); + }); +}); + +describe('format / i18n', () => { + it('interpolates templates', () => { + expect(format('MODULE_NOT_FOUND', { name: 'auth' })).toBe('Module "auth" not found in modules list.'); + }); + + it('uses a registered locale catalog with fallback to en', () => { + registerCatalog('es', { ACCOUNT_EXISTS: 'Ya existe una cuenta con este correo.' }); + expect(format('ACCOUNT_EXISTS', {}, 'es')).toBe('Ya existe una cuenta con este correo.'); + // falls back to en for codes not in the es catalog + expect(format('FORBIDDEN', {}, 'es')).toBe('You do not have permission to do that.'); + }); + + it('humanizes unknown codes as a last resort', () => { + expect(format('SOME_UNKNOWN_CODE')).toBe('Some unknown code'); + }); +}); diff --git a/packages/errors/jest.config.js b/packages/errors/jest.config.js new file mode 100644 index 0000000000..549bebee08 --- /dev/null +++ b/packages/errors/jest.config.js @@ -0,0 +1,8 @@ +module.exports = { + testEnvironment: 'node', + transform: { + '^.+\\.tsx?$': ['ts-jest', { useESM: false }] + }, + testMatch: ['**/__tests__/**/*.test.ts'], + moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'] +}; diff --git a/packages/errors/package.json b/packages/errors/package.json new file mode 100644 index 0000000000..55bcfc87d5 --- /dev/null +++ b/packages/errors/package.json @@ -0,0 +1,44 @@ +{ + "name": "@constructive-io/errors", + "version": "0.1.0", + "author": "Constructive ", + "description": "Canonical Constructive error system: registry, codes, i18n messages, and cross-source (PostgreSQL/GraphQL/client) parsing. Zero runtime dependencies.", + "main": "index.js", + "module": "esm/index.js", + "types": "index.d.ts", + "homepage": "https://github.com/constructive-io/constructive", + "license": "MIT", + "publishConfig": { + "access": "public", + "directory": "dist" + }, + "repository": { + "type": "git", + "url": "https://github.com/constructive-io/constructive" + }, + "bugs": { + "url": "https://github.com/constructive-io/constructive/issues" + }, + "scripts": { + "clean": "makage clean", + "prepack": "npm run build", + "build": "makage build", + "build:dev": "makage build --dev", + "lint": "eslint . --fix", + "test": "jest --passWithNoTests", + "test:watch": "jest --watch" + }, + "keywords": [ + "errors", + "error-codes", + "i18n", + "constructive", + "postgresql", + "graphql" + ], + "devDependencies": { + "@types/node": "^22.19.11", + "makage": "^0.3.0", + "ts-node": "^10.9.2" + } +} diff --git a/packages/errors/src/classify.ts b/packages/errors/src/classify.ts new file mode 100644 index 0000000000..d3a822edca --- /dev/null +++ b/packages/errors/src/classify.ts @@ -0,0 +1,18 @@ +import { getDefinition } from './registry'; +import type { ErrorClass } from './types'; + +/** + * Classify a code as `public` or `internal`. + * + * Unknown codes are classified `internal` on purpose: transports mask internal + * errors, so an unregistered code is never leaked to end users by default. + */ +export function classify(code: string | null | undefined): ErrorClass { + if (!code) return 'internal'; + return getDefinition(code)?.class ?? 'internal'; +} + +/** Whether a code is registered and classified `public`. */ +export function isPublicCode(code: string | null | undefined): boolean { + return classify(code) === 'public'; +} diff --git a/packages/errors/src/error.ts b/packages/errors/src/error.ts new file mode 100644 index 0000000000..98f9269f15 --- /dev/null +++ b/packages/errors/src/error.ts @@ -0,0 +1,58 @@ +import type { ErrorClass, ErrorContext } from './types'; + +export interface ConstructiveErrorArgs { + code: string; + message: string; + errorClass: ErrorClass; + http: number; + context?: ErrorContext; +} + +/** + * The single canonical error class for Constructive. Carries the machine + * `code`, its classification, an HTTP hint, and structured `context`. + * + * Replaces the previously separate `PgpmError`, `AuthError`, and `DataError` + * shapes. GraphQL transports should serialize {@link toExtensions}. + */ +export class ConstructiveError extends Error { + readonly code: string; + readonly errorClass: ErrorClass; + readonly http: number; + readonly context?: ErrorContext; + + constructor(args: ConstructiveErrorArgs) { + super(args.message); + this.name = 'ConstructiveError'; + this.code = args.code; + this.errorClass = args.errorClass; + this.http = args.http; + this.context = args.context; + + if (Error.captureStackTrace) { + Error.captureStackTrace(this, ConstructiveError); + } + } + + /** Whether this error is safe to surface to end users. */ + get isPublic(): boolean { + return this.errorClass === 'public'; + } + + /** GraphQL `extensions` payload for this error. */ + toExtensions(): { code: string; class: ErrorClass; http: number; context?: ErrorContext } { + const ext: { code: string; class: ErrorClass; http: number; context?: ErrorContext } = { + code: this.code, + class: this.errorClass, + http: this.http + }; + if (this.context && Object.keys(this.context).length > 0) { + ext.context = this.context; + } + return ext; + } + + toString(): string { + return `[${this.code}] ${this.message}`; + } +} diff --git a/packages/errors/src/factory.ts b/packages/errors/src/factory.ts new file mode 100644 index 0000000000..5641753ca9 --- /dev/null +++ b/packages/errors/src/factory.ts @@ -0,0 +1,73 @@ +import { ConstructiveError } from './error'; +import { format } from './format'; +import { registry } from './registry'; +import type { ErrorClass, ErrorContext, ErrorDefinition } from './types'; + +/** + * The callable produced for a registry entry. Codes with no context params can + * be called with no arguments; codes with params require a matching context. + * The `[keyof C]` tuple wrapper prevents `never` from distributing. + */ +export type ErrorFactory = [keyof C] extends [never] + ? (context?: Record, overrideMessage?: string) => ConstructiveError + : (context: C, overrideMessage?: string) => ConstructiveError; + +export type ErrorsApi = { + [K in keyof R]: R[K] extends { __context: (context: infer C) => void } + ? C extends ErrorContext + ? ErrorFactory + : never + : never; +}; + +/** Build a throwable factory for a single definition. */ +export function makeErrorFromDefinition( + def: ErrorDefinition +): ErrorFactory { + const factory = (context?: ErrorContext, overrideMessage?: string): ConstructiveError => + new ConstructiveError({ + code: def.code, + message: overrideMessage ?? format(def.code, context ?? {}), + errorClass: def.class, + http: def.http, + context + }); + return factory as ErrorFactory; +} + +/** Build the `errors.*` factory object from a registry. */ +export function buildErrors void }>>( + reg: R +): ErrorsApi { + const out: Record = {}; + for (const key of Object.keys(reg)) { + out[key] = makeErrorFromDefinition( + (reg as unknown as Record>)[key] + ); + } + return out as ErrorsApi; +} + +/** + * Low-level factory helper for defining an error inline (the pattern the former + * pgpm `error-factory` exposed). Prefer registering codes in the registry, but + * this remains available for ad-hoc / dynamically-coded errors. + */ +export function makeError( + code: string, + messageFn: (context: C) => string, + httpCode = 500, + errorClass: ErrorClass = 'internal' +): (context: C, overrideMessage?: string) => ConstructiveError { + return (context: C, overrideMessage?: string) => + new ConstructiveError({ + code, + message: overrideMessage ?? messageFn(context), + errorClass, + http: httpCode, + context + }); +} + +/** The canonical, type-safe `errors.*` factory built from the registry. */ +export const errors = buildErrors(registry); diff --git a/packages/errors/src/format.ts b/packages/errors/src/format.ts new file mode 100644 index 0000000000..c69be5b3b8 --- /dev/null +++ b/packages/errors/src/format.ts @@ -0,0 +1,60 @@ +import { interpolate } from './interpolate'; +import { getDefinition, registry } from './registry'; +import type { ErrorContext } from './types'; + +/** A locale catalog maps `code` → message template (with `{{var}}`). */ +export type MessageCatalog = Record; + +export const DEFAULT_LOCALE = 'en'; + +/** + * Built-in `en` catalog derived from the registry's string messages. Entries + * whose message is a function are omitted (they are computed, not templated) + * and fall back to the registry function at format time. + */ +const defaultEnCatalog: MessageCatalog = Object.fromEntries( + Object.values(registry) + .filter((def) => typeof def.message === 'string') + .map((def) => [def.code, def.message as string]) +); + +const catalogs: Record = { + [DEFAULT_LOCALE]: { ...defaultEnCatalog } +}; + +/** + * Register or extend a locale catalog. Merges with any existing entries for + * that locale (new entries win), so hosts can override individual codes. + */ +export function registerCatalog(locale: string, catalog: MessageCatalog): void { + catalogs[locale] = { ...catalogs[locale], ...catalog }; +} + +/** Humanize a code as a last-resort message, e.g. `LIMIT_REACHED` → `Limit reached`. */ +function humanize(code: string): string { + const lower = code.toLowerCase().replace(/_/g, ' '); + return lower.charAt(0).toUpperCase() + lower.slice(1); +} + +/** + * Render a localized message for a code. + * + * Resolution order: + * 1. requested-locale catalog entry (template) → interpolate + * 2. default-locale catalog entry (template) → interpolate + * 3. registry function message → call with context + * 4. humanized code + */ +export function format(code: string, context: ErrorContext = {}, locale: string = DEFAULT_LOCALE): string { + const template = catalogs[locale]?.[code] ?? catalogs[DEFAULT_LOCALE]?.[code]; + if (typeof template === 'string') { + return interpolate(template, context); + } + + const def = getDefinition(code); + if (def && typeof def.message === 'function') { + return (def.message as (ctx: ErrorContext) => string)(context); + } + + return humanize(code); +} diff --git a/packages/errors/src/index.ts b/packages/errors/src/index.ts new file mode 100644 index 0000000000..fa5a7fb78c --- /dev/null +++ b/packages/errors/src/index.ts @@ -0,0 +1,9 @@ +export * from './classify'; +export * from './error'; +export * from './factory'; +export * from './format'; +export * from './interpolate'; +export * from './parse'; +export * from './pg'; +export * from './registry'; +export * from './types'; diff --git a/packages/errors/src/interpolate.ts b/packages/errors/src/interpolate.ts new file mode 100644 index 0000000000..40561e70a4 --- /dev/null +++ b/packages/errors/src/interpolate.ts @@ -0,0 +1,14 @@ +import type { ErrorContext } from './types'; + +const PLACEHOLDER = /\{\{\s*(\w+)\s*\}\}/g; + +/** + * Replace `{{key}}` placeholders in a template with values from `context`. + * Missing keys render as an empty string. + */ +export function interpolate(template: string, context: ErrorContext = {}): string { + return template.replace(PLACEHOLDER, (_match, key: string) => { + const value = context[key]; + return value === undefined || value === null ? '' : String(value); + }); +} diff --git a/packages/errors/src/parse.ts b/packages/errors/src/parse.ts new file mode 100644 index 0000000000..1fdc210d7e --- /dev/null +++ b/packages/errors/src/parse.ts @@ -0,0 +1,160 @@ +import { classify } from './classify'; +import { ConstructiveError } from './error'; +import { extractPgErrorFields, RAISE_EXCEPTION_SQLSTATE, SQLSTATE_TO_CODE } from './pg'; +import { getDefinition } from './registry'; +import type { ContextValue, ErrorContext, ParsedError } from './types'; + +/** Leading ALL_CAPS token, optionally followed by `(arg, arg)` positional args. */ +const CODE_TOKEN = /^([A-Z][A-Z0-9_]+)(?:\s*\((.*)\))?\s*$/; + +interface GraphqlLike { + message?: string; + extensionsCode?: string; + extensionsContext?: ErrorContext; +} + +function getMessage(error: unknown): string { + if (typeof error === 'string') return error; + if (error instanceof Error) return error.message; + if (error && typeof error === 'object' && typeof (error as { message?: unknown }).message === 'string') { + return (error as { message: string }).message; + } + return String(error); +} + +/** Pull GraphQL-shaped fields, including the `{ errors: [...] }` request wrapper. */ +function extractGraphqlLike(error: unknown): GraphqlLike | null { + if (!error || typeof error !== 'object') return null; + const e = error as Record; + + const fromNode = (node: Record | undefined): GraphqlLike | null => { + if (!node) return null; + const extensions = node.extensions as Record | undefined; + const out: GraphqlLike = {}; + if (typeof node.message === 'string') out.message = node.message; + if (extensions && typeof extensions.code === 'string') out.extensionsCode = extensions.code; + if (extensions && extensions.context && typeof extensions.context === 'object') { + out.extensionsContext = extensions.context as ErrorContext; + } + return out.message || out.extensionsCode ? out : null; + }; + + if (Array.isArray(e.errors) && e.errors.length > 0) { + const first = fromNode(e.errors[0] as Record); + if (first) return first; + } + return fromNode(e); +} + +function parseDetailJson(detail?: string): { code: string; context: ErrorContext } | null { + if (!detail) return null; + const trimmed = detail.trim(); + if (!trimmed.startsWith('{')) return null; + try { + const obj = JSON.parse(trimmed) as { code?: unknown; context?: unknown }; + if (obj && typeof obj.code === 'string') { + const context = obj.context && typeof obj.context === 'object' ? (obj.context as ErrorContext) : {}; + return { code: obj.code, context }; + } + } catch { + // not structured json — fall through + } + return null; +} + +function coerce(value: string): ContextValue { + if (value === '') return value; + const num = Number(value); + return Number.isNaN(num) ? value : num; +} + +/** Map legacy positional args (`CODE (a, b)`) onto named context keys. */ +function mapPositional(code: string, args: string[]): ErrorContext { + if (args.length === 0) return {}; + const positional = getDefinition(code)?.positional; + if (!positional || positional.length === 0) { + return { args: args.join(', ') }; + } + const context: ErrorContext = {}; + positional.forEach((key, i) => { + if (i < args.length) context[key] = coerce(args[i]); + }); + return context; +} + +function parseMessageCode(message: string): { code: string; args: string[] } | null { + const m = message.trim().match(CODE_TOKEN); + if (!m) return null; + return { code: m[1], args: m[2] !== undefined ? m[2].split(',').map((s) => s.trim()).filter(Boolean) : [] }; +} + +/** + * Normalize an error from any source (a {@link ConstructiveError}, a node-postgres + * `DatabaseError`, a GraphQL error or `{ errors: [...] }` wrapper, a plain + * `Error`, or a string) into a canonical {@link ParsedError}. + * + * Resolution order for the code: + * 1. structured `DETAIL` JSON (`{ code, context }`) — Constructive's DB transport + * 2. GraphQL `extensions.code` + * 3. a leading ALL_CAPS token in the message (legacy DB `RAISE`), with any + * `(arg, arg)` positional args mapped onto named context via the registry + * 4. native SQLSTATE constraint mapping (23505 → `UNIQUE_VIOLATION`, …) + */ +export function parse(error: unknown): ParsedError { + if (error instanceof ConstructiveError) { + return { + code: error.code, + context: error.context ?? {}, + class: error.errorClass, + known: Boolean(getDefinition(error.code)), + rawMessage: error.message, + originalError: error + }; + } + + const pg = extractPgErrorFields(error); + const gql = extractGraphqlLike(error); + const rawMessage = gql?.message ?? getMessage(error); + const sqlState = pg?.code; + + let code: string | null = null; + let context: ErrorContext = {}; + + const detail = parseDetailJson(pg?.detail); + if (detail) { + code = detail.code; + context = { ...detail.context }; + } + + if (!code && gql?.extensionsCode) { + code = gql.extensionsCode; + if (gql.extensionsContext) context = { ...gql.extensionsContext }; + } + + if (!code) { + const parsedMsg = parseMessageCode(gql?.message ?? rawMessage); + if (parsedMsg && parsedMsg.code !== RAISE_EXCEPTION_SQLSTATE) { + code = parsedMsg.code; + context = mapPositional(parsedMsg.code, parsedMsg.args); + } + } + + if ((!code || code === RAISE_EXCEPTION_SQLSTATE) && sqlState && SQLSTATE_TO_CODE[sqlState]) { + code = SQLSTATE_TO_CODE[sqlState]; + if (pg?.constraint) context.constraint = pg.constraint; + if (pg?.table) context.table = pg.table; + if (pg?.column) context.column = pg.column; + } + + if (code === RAISE_EXCEPTION_SQLSTATE) code = null; + + return { + code, + context, + class: classify(code), + known: Boolean(code && getDefinition(code)), + rawMessage, + sqlState, + originalError: error + }; +} diff --git a/packages/errors/src/pg.ts b/packages/errors/src/pg.ts new file mode 100644 index 0000000000..59fa173686 --- /dev/null +++ b/packages/errors/src/pg.ts @@ -0,0 +1,56 @@ +import type { PgErrorFields } from './types'; + +/** + * PostgreSQL SQLSTATE codes for the native constraint violations we surface as + * public Constructive codes. + */ +export const SQLSTATE_TO_CODE: Record = { + 23505: 'UNIQUE_VIOLATION', + 23503: 'FOREIGN_KEY_VIOLATION', + 23502: 'NOT_NULL_VIOLATION', + 23514: 'CHECK_VIOLATION', + '23P01': 'EXCLUSION_VIOLATION' +}; + +/** SQLSTATE for a user-raised `RAISE EXCEPTION` without an explicit ERRCODE. */ +export const RAISE_EXCEPTION_SQLSTATE = 'P0001'; + +/** + * Extract extended PostgreSQL error fields from an error-like object. Returns + * `null` when the value does not look like a pg error (no `code`/`detail`/ + * `where`). + */ +export function extractPgErrorFields(err: unknown): PgErrorFields | null { + if (!err || typeof err !== 'object') return null; + + const e = err as Record; + if (!e.code && !e.detail && !e.where) return null; + + const fields: PgErrorFields = {}; + const assign = (key: keyof PgErrorFields) => { + const value = e[key]; + if (typeof value === 'string') fields[key] = value; + }; + + ( + [ + 'code', + 'detail', + 'hint', + 'where', + 'position', + 'internalPosition', + 'internalQuery', + 'schema', + 'table', + 'column', + 'dataType', + 'constraint', + 'file', + 'line', + 'routine' + ] as (keyof PgErrorFields)[] + ).forEach(assign); + + return fields; +} diff --git a/packages/errors/src/registry.ts b/packages/errors/src/registry.ts new file mode 100644 index 0000000000..c8e409963d --- /dev/null +++ b/packages/errors/src/registry.ts @@ -0,0 +1,345 @@ +import type { EmptyContext, ErrorContext, ErrorDefinition } from './types'; + +/** + * A registry entry plus a phantom type carrier for its context. The + * contravariant `__context` field lets `ErrorsApi` recover `C` by inference + * even when `C` appears only in optional positions of {@link ErrorDefinition}. + * It is type-level only — never set at runtime. + */ +export type DefinedError = ErrorDefinition & { + readonly __context: (context: C) => void; +}; + +/** + * Identity helper that captures each entry's context type for inference while + * keeping the registry a plain object. `errors.MODULE_NOT_FOUND({ name })` is + * then type-checked against the declared context. + */ +export function defineError( + def: ErrorDefinition +): DefinedError { + return def as DefinedError; +} + +/** + * The canonical Constructive error registry. + * + * This is the hand-seeded core (public auth/limit codes with real dashboard + * copy, native PostgreSQL constraint codes, and the pgpm CLI codes). The full + * 287-code set emitted by constructive-db is generated in a later phase; codes + * absent here still `parse()` — they are simply classified `internal` (masked) + * until registered. + */ +export const registry = { + // =========================================================================== + // Auth / account (public) — copy sourced from dashboard auth-errors.ts + // =========================================================================== + ACCOUNT_EXISTS: defineError({ + code: 'ACCOUNT_EXISTS', + class: 'public', + http: 409, + message: 'An account with this email already exists. Please sign in or use a different email.' + }), + ACCOUNT_NOT_FOUND: defineError({ + code: 'ACCOUNT_NOT_FOUND', + class: 'public', + http: 404, + message: 'No account was found.' + }), + NO_ACCOUNT_EXISTS: defineError<{ userId?: string }>({ + code: 'NO_ACCOUNT_EXISTS', + class: 'public', + http: 404, + message: 'No account exists for this user.' + }), + INVALID_CREDENTIALS: defineError({ + code: 'INVALID_CREDENTIALS', + class: 'public', + http: 401, + message: 'Invalid email or password.' + }), + INCORRECT_PASSWORD: defineError({ + code: 'INCORRECT_PASSWORD', + class: 'public', + http: 401, + message: 'The password you entered is incorrect. Please try again.' + }), + ACCOUNT_LOCKED_EXCEED_ATTEMPTS: defineError({ + code: 'ACCOUNT_LOCKED_EXCEED_ATTEMPTS', + class: 'public', + http: 423, + message: + 'Your account has been temporarily locked due to too many failed login attempts. Please try again later or reset your password.' + }), + ACCOUNT_DISABLED: defineError({ + code: 'ACCOUNT_DISABLED', + class: 'public', + http: 403, + message: 'Your account has been disabled. Please contact support for assistance.' + }), + PASSWORD_INSECURE: defineError({ + code: 'PASSWORD_INSECURE', + class: 'public', + http: 400, + message: 'This password is not secure enough. Please choose a stronger password.' + }), + PASSWORD_LEN: defineError({ + code: 'PASSWORD_LEN', + class: 'public', + http: 400, + message: 'Password must be between 8 and 63 characters long.' + }), + EMAIL_NOT_VERIFIED: defineError({ + code: 'EMAIL_NOT_VERIFIED', + class: 'public', + http: 403, + message: 'Please verify your email address to continue.' + }), + SIGN_UP_DISABLED: defineError({ + code: 'SIGN_UP_DISABLED', + class: 'public', + http: 403, + message: 'Sign-up is currently disabled.' + }), + + // =========================================================================== + // Invites (public) + // =========================================================================== + INVITE_NOT_FOUND: defineError({ + code: 'INVITE_NOT_FOUND', + class: 'public', + http: 404, + message: + 'The invitation code is invalid or has expired. Please check the code or request a new invitation.' + }), + INVITE_LIMIT: defineError({ + code: 'INVITE_LIMIT', + class: 'public', + http: 429, + message: 'This invitation has reached its usage limit. Please request a new invitation.' + }), + INVITE_EMAIL_NOT_FOUND: defineError({ + code: 'INVITE_EMAIL_NOT_FOUND', + class: 'public', + http: 404, + message: + 'This email is not associated with the invitation. Please use the email address the invitation was sent to.' + }), + + // =========================================================================== + // Authorization / step-up (public) + // =========================================================================== + UNAUTHENTICATED: defineError({ + code: 'UNAUTHENTICATED', + class: 'public', + http: 401, + message: 'You must be signed in to do that.' + }), + FORBIDDEN: defineError({ + code: 'FORBIDDEN', + class: 'public', + http: 403, + message: 'You do not have permission to do that.' + }), + STEP_UP_REQUIRED: defineError({ + code: 'STEP_UP_REQUIRED', + class: 'public', + http: 403, + message: 'Additional verification is required to continue.' + }), + MFA_REQUIRED: defineError({ + code: 'MFA_REQUIRED', + class: 'public', + http: 403, + message: 'Multi-factor authentication is required to continue.' + }), + INVALID_TOKEN: defineError({ + code: 'INVALID_TOKEN', + class: 'public', + http: 401, + message: 'This link or token is invalid or has expired.' + }), + + // =========================================================================== + // Limits (public, dynamic) + // =========================================================================== + LIMIT_REACHED: defineError<{ resource?: string; limit?: number }>({ + code: 'LIMIT_REACHED', + class: 'public', + http: 429, + message: 'You have reached a plan limit for this resource.', + positional: ['resource', 'limit'] + }), + RATE_LIMITED: defineError({ + code: 'RATE_LIMITED', + class: 'public', + http: 429, + message: 'Too many requests. Please slow down and try again shortly.' + }), + + // =========================================================================== + // Native PostgreSQL constraint violations (public) — synthetic codes mapped + // from SQLSTATE by parse(). + // =========================================================================== + UNIQUE_VIOLATION: defineError<{ constraint?: string; table?: string }>({ + code: 'UNIQUE_VIOLATION', + class: 'public', + http: 409, + message: 'That value already exists.' + }), + FOREIGN_KEY_VIOLATION: defineError<{ constraint?: string; table?: string }>({ + code: 'FOREIGN_KEY_VIOLATION', + class: 'public', + http: 409, + message: 'A related record is required or still referenced.' + }), + NOT_NULL_VIOLATION: defineError<{ column?: string; table?: string }>({ + code: 'NOT_NULL_VIOLATION', + class: 'public', + http: 400, + message: 'A required value is missing.' + }), + CHECK_VIOLATION: defineError<{ constraint?: string; table?: string }>({ + code: 'CHECK_VIOLATION', + class: 'public', + http: 400, + message: 'A value did not satisfy a constraint.' + }), + EXCLUSION_VIOLATION: defineError<{ constraint?: string; table?: string }>({ + code: 'EXCLUSION_VIOLATION', + class: 'public', + http: 409, + message: 'A value conflicts with an existing record.' + }), + + // =========================================================================== + // pgpm CLI / engine (mostly internal) — behavior preserved from the former + // pgpm/types error-factory so existing call sites are unchanged. + // =========================================================================== + NOT_FOUND: defineError({ + code: 'NOT_FOUND', + class: 'public', + http: 404, + message: 'Not found.' + }), + MODULE_NOT_FOUND: defineError<{ name: string }>({ + code: 'MODULE_NOT_FOUND', + class: 'internal', + http: 404, + message: 'Module "{{name}}" not found in modules list.' + }), + INTERNAL_FAILURE: defineError<{ details: string }>({ + code: 'INTERNAL_FAILURE', + class: 'internal', + http: 500, + message: 'Something went wrong: {{details}}' + }), + CONTEXT_MISSING: defineError({ + code: 'CONTEXT_MISSING', + class: 'internal', + http: 500, + message: 'Context is not initialized. Did you run setup()?' + }), + NOT_IN_WORKSPACE: defineError({ + code: 'NOT_IN_WORKSPACE', + class: 'internal', + http: 400, + message: 'You must be in a PGPM workspace. Initialize with "pgpm init workspace".' + }), + NOT_IN_WORKSPACE_MODULE: defineError({ + code: 'NOT_IN_WORKSPACE_MODULE', + class: 'internal', + http: 400, + message: 'Error: You must be inside one of the workspace packages.' + }), + DEPLOYMENT_FAILED: defineError<{ type: 'Deployment' | 'Revert' | 'Verify'; module: string }>({ + code: 'DEPLOYMENT_FAILED', + class: 'internal', + http: 500, + message: '{{type}} failed for module: {{module}}' + }), + UNSUPPORTED_TYPE_HINT: defineError<{ typeHint: string }>({ + code: 'UNSUPPORTED_TYPE_HINT', + class: 'internal', + http: 400, + message: 'Unsupported type hint: {{typeHint}}' + }), + BAD_FILE_NAME: defineError<{ name: string }>({ + code: 'BAD_FILE_NAME', + class: 'internal', + http: 400, + message: 'Invalid file name: {{name}}' + }), + UNKNOWN_COMMAND: defineError<{ cmd: string }>({ + code: 'UNKNOWN_COMMAND', + class: 'internal', + http: 400, + message: 'Unknown command: {{cmd}}' + }), + CHANGE_NOT_FOUND: defineError<{ change: string; plan?: string }>({ + code: 'CHANGE_NOT_FOUND', + class: 'internal', + http: 404, + message: ({ change, plan }) => + `Change '${change}' not found in plan${plan ? ` file: ${plan}` : ''}` + }), + TAG_NOT_FOUND: defineError<{ tag: string; project?: string }>({ + code: 'TAG_NOT_FOUND', + class: 'internal', + http: 404, + message: ({ tag, project }) => + `Tag '${tag}' not found${project ? ` in project ${project}` : ' in plan'}` + }), + PATH_NOT_FOUND: defineError<{ path: string; type: 'module' | 'workspace' | 'file' }>({ + code: 'PATH_NOT_FOUND', + class: 'internal', + http: 404, + message: ({ path, type }) => `${type} path not found: ${path}` + }), + OPERATION_FAILED: defineError<{ operation: string; target?: string; reason?: string }>({ + code: 'OPERATION_FAILED', + class: 'internal', + http: 500, + message: ({ operation, target, reason }) => + `${operation} failed${target ? ` for ${target}` : ''}${reason ? `: ${reason}` : ''}` + }), + PLAN_PARSE_ERROR: defineError<{ planPath: string; errors: string }>({ + code: 'PLAN_PARSE_ERROR', + class: 'internal', + http: 400, + message: ({ planPath, errors }) => `Failed to parse plan file ${planPath}: ${errors}` + }), + CIRCULAR_DEPENDENCY: defineError<{ module: string; dependency: string }>({ + code: 'CIRCULAR_DEPENDENCY', + class: 'internal', + http: 400, + message: ({ module, dependency }) => `Circular reference detected: ${module} → ${dependency}` + }), + INVALID_NAME: defineError<{ name: string; type: 'tag' | 'change' | 'module'; rules?: string }>({ + code: 'INVALID_NAME', + class: 'internal', + http: 400, + message: ({ name, type, rules }) => `Invalid ${type} name: ${name}${rules ? `. ${rules}` : ''}` + }), + WORKSPACE_OPERATION_ERROR: defineError<{ operation: string }>({ + code: 'WORKSPACE_OPERATION_ERROR', + class: 'internal', + http: 400, + message: ({ operation }) => + `Cannot perform non-recursive ${operation} on workspace. Use recursive=true or specify a target module.` + }), + FILE_NOT_FOUND: defineError<{ filePath: string; type?: string }>({ + code: 'FILE_NOT_FOUND', + class: 'internal', + http: 404, + message: ({ filePath, type }) => `${type ? `${type} file` : 'File'} not found: ${filePath}` + }) +} as const; + +export type Registry = typeof registry; +export type RegistryCode = keyof Registry; + +/** Look up a definition by code (from anywhere, not just typed keys). */ +export function getDefinition(code: string): ErrorDefinition | undefined { + return (registry as unknown as Record>)[code]; +} diff --git a/packages/errors/src/types.ts b/packages/errors/src/types.ts new file mode 100644 index 0000000000..a8f35f827e --- /dev/null +++ b/packages/errors/src/types.ts @@ -0,0 +1,88 @@ +/** + * Core types for the Constructive error system. + * + * A Constructive error is identified by a stable, machine-readable `code` + * (ALL_CAPS). Human-facing copy is kept separate (in i18n catalogs) so the + * same code can be localized. Every code is classified as `public` (safe to + * surface to end users) or `internal` (an invariant/developer error that must + * be masked in production). + */ + +/** Values that can be interpolated into a message or carried as error context. */ +export type ContextValue = string | number | boolean | null | undefined; + +/** Structured, machine-readable context attached to an error. */ +export type ErrorContext = Record; + +/** A context with no parameters (codes that take no dynamic values). */ +export type EmptyContext = Record; + +/** + * Classification of a code. + * - `public` — user-facing; safe to send to clients as-is. + * - `internal` — developer/invariant error; masked in production. + */ +export type ErrorClass = 'public' | 'internal'; + +/** + * A single registry entry describing one error code. This is the source of + * truth; message copy, factories, and classification all derive from it. + */ +export interface ErrorDefinition { + /** Stable machine code, e.g. `ACCOUNT_EXISTS`. */ + code: string; + /** Public (surface to users) vs internal (mask in prod). */ + class: ErrorClass; + /** HTTP status hint for transports that want one. */ + http: number; + /** + * Default message. Either an i18n template using `{{var}}` placeholders + * (preferred — localizable) or a function for messages with conditional + * logic that a flat template cannot express. + */ + message: string | ((context: C) => string); + /** Documented context parameter names (for tooling / codegen). */ + params?: readonly (keyof C & string)[]; + /** + * For legacy dynamic DB throws of the form `CODE (arg0, arg1)`, maps the + * positional args to context keys so `parse()` can recover structure. + */ + positional?: readonly (keyof C & string)[]; +} + +/** The canonical, normalized result of parsing an error from any source. */ +export interface ParsedError { + /** Constructive code if one could be determined, else `null`. */ + code: string | null; + /** Structured context recovered from the error (may be empty). */ + context: ErrorContext; + /** Classification (`internal` when the code is unknown — fail safe). */ + class: ErrorClass; + /** `true` when `code` is present in the registry. */ + known: boolean; + /** Best-effort raw message from the source error. */ + rawMessage: string; + /** Original PostgreSQL SQLSTATE, when the source was a pg error. */ + sqlState?: string; + /** The original error, for logging/debugging. */ + originalError: unknown; +} + +/** Extended PostgreSQL error fields (populated by pg-protocol on errors). */ +export interface PgErrorFields { + code?: string; + detail?: string; + hint?: string; + where?: string; + position?: string; + internalPosition?: string; + internalQuery?: string; + schema?: string; + table?: string; + column?: string; + dataType?: string; + constraint?: string; + file?: string; + line?: string; + routine?: string; +} diff --git a/packages/errors/tsconfig.esm.json b/packages/errors/tsconfig.esm.json new file mode 100644 index 0000000000..03da56818b --- /dev/null +++ b/packages/errors/tsconfig.esm.json @@ -0,0 +1,7 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "ES2022", + "outDir": "./dist/esm" + } +} diff --git a/packages/errors/tsconfig.json b/packages/errors/tsconfig.json new file mode 100644 index 0000000000..d8f755cb3d --- /dev/null +++ b/packages/errors/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "./dist", + "rootDir": "./src" + }, + "include": ["src/**/*"], + "exclude": ["dist", "node_modules", "__tests__"] +} diff --git a/pgpm/types/package.json b/pgpm/types/package.json index 35f77ac538..eba99a3e64 100644 --- a/pgpm/types/package.json +++ b/pgpm/types/package.json @@ -29,6 +29,7 @@ "test:watch": "jest --watch" }, "dependencies": { + "@constructive-io/errors": "workspace:^", "pg-env": "workspace:^" }, "keywords": [ diff --git a/pgpm/types/src/error-factory.ts b/pgpm/types/src/error-factory.ts index 117505c52e..23c123ad43 100644 --- a/pgpm/types/src/error-factory.ts +++ b/pgpm/types/src/error-factory.ts @@ -1,156 +1,9 @@ -import { PgpmError } from './error'; - -type ErrorContext = Record; - -export const makeError = ( - code: string, - messageFn: (context: T) => string, - httpCode?: number -) => { - return ( - context: T, - overrideMessage?: string - ): PgpmError => { - const message = overrideMessage || messageFn(context); - return new PgpmError(code, message, context, httpCode); - }; -}; - -export const errors = { - NOT_FOUND: makeError( - 'NOT_FOUND', - () => `Not found.`, - 404 - ), - - MODULE_NOT_FOUND: makeError( - 'MODULE_NOT_FOUND', - ({ name }: { name: string }) => `Module "${name}" not found in modules list.`, - 404 - ), - - NO_ACCOUNT_EXISTS: makeError( - 'NO_ACCOUNT_EXISTS', - ({ userId }) => `No account exists for user: ${userId}`, - 404 - ), - - INTERNAL_FAILURE: makeError( - 'INTERNAL_FAILURE', - ({ details }) => `Something went wrong: ${details}`, - 500 - ), - - CONTEXT_MISSING: makeError( - 'CONTEXT_MISSING', - () => `Context is not initialized. Did you run setup()?`, - 500 - ), - - NOT_IN_WORKSPACE: makeError( - 'NOT_IN_WORKSPACE', - () => `You must be in a PGPM workspace. Initialize with "pgpm init workspace".`, - 400 - ), - - NOT_IN_WORKSPACE_MODULE: makeError( - 'NOT_IN_WORKSPACE_MODULE', - () => `Error: You must be inside one of the workspace packages.`, - 400 - ), - - DEPLOYMENT_FAILED: makeError( - 'DEPLOYMENT_FAILED', - ({ type, module }: {type: 'Deployment' | 'Revert' | 'Verify', module: string}) => `${type} failed for module: ${module}`, - 500 - ), - - UNSUPPORTED_TYPE_HINT: makeError( - 'UNSUPPORTED_TYPE_HINT', - ({ typeHint }) => `Unsupported type hint: ${typeHint}`, - 400 - ), - - BAD_FILE_NAME: makeError( - 'BAD_FILE_NAME', - ({ name }) => `Invalid file name: ${name}`, - 400 - ), - - UNKNOWN_COMMAND: makeError( - 'UNKNOWN_COMMAND', - ({ cmd }) => `Unknown command: ${cmd}`, - 400 - ), - - CHANGE_NOT_FOUND: makeError( - 'CHANGE_NOT_FOUND', - ({ change, plan }: { change: string, plan?: string }) => - `Change '${change}' not found in plan${plan ? ` file: ${plan}` : ''}`, - 404 - ), - - TAG_NOT_FOUND: makeError( - 'TAG_NOT_FOUND', - ({ tag, project }: { tag: string, project?: string }) => - `Tag '${tag}' not found${project ? ` in project ${project}` : ' in plan'}`, - 404 - ), - - PATH_NOT_FOUND: makeError( - 'PATH_NOT_FOUND', - ({ path, type }: { path: string, type: 'module' | 'workspace' | 'file' }) => - `${type} path not found: ${path}`, - 404 - ), - - OPERATION_FAILED: makeError( - 'OPERATION_FAILED', - ({ operation, target, reason }: { operation: string, target?: string, reason?: string }) => - `${operation} failed${target ? ` for ${target}` : ''}${reason ? `: ${reason}` : ''}`, - 500 - ), - - PLAN_PARSE_ERROR: makeError( - 'PLAN_PARSE_ERROR', - ({ planPath, errors }: { planPath: string, errors: string }) => - `Failed to parse plan file ${planPath}: ${errors}`, - 400 - ), - - CIRCULAR_DEPENDENCY: makeError( - 'CIRCULAR_DEPENDENCY', - ({ module, dependency }: { module: string, dependency: string }) => - `Circular reference detected: ${module} → ${dependency}`, - 400 - ), - - INVALID_NAME: makeError( - 'INVALID_NAME', - ({ name, type, rules }: { name: string, type: 'tag' | 'change' | 'module', rules?: string }) => - `Invalid ${type} name: ${name}${rules ? `. ${rules}` : ''}`, - 400 - ), - - WORKSPACE_OPERATION_ERROR: makeError( - 'WORKSPACE_OPERATION_ERROR', - ({ operation }: { operation: string }) => - `Cannot perform non-recursive ${operation} on workspace. Use recursive=true or specify a target module.`, - 400 - ), - - FILE_NOT_FOUND: makeError( - 'FILE_NOT_FOUND', - ({ filePath, type }: { filePath: string, type?: string }) => - `${type ? `${type} file` : 'File'} not found: ${filePath}`, - 404 - ), -}; - -// throw errors.MODULE_NOT_FOUND({ name: 'auth' }); - -// throw errors.INTERNAL_FAILURE({ details: 'Could not connect to DB' }); - -// throw errors.UNKNOWN_COMMAND({ cmd: 'foo' }, 'Unsupported command "foo"'); - -// throw errors.CONTEXT_MISSING((); +/** + * pgpm error factory. + * + * The canonical error system now lives in `@constructive-io/errors` (a + * standalone, zero-dependency package). pgpm's error codes are registered + * there; this module re-exports the shared factory so existing call sites + * (`throw errors.MODULE_NOT_FOUND({ name })`) are unchanged. + */ +export { errors, makeError } from '@constructive-io/errors'; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7c59fb8680..95390f3f13 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1776,6 +1776,9 @@ importers: '@constructive-io/csrf': specifier: workspace:^ version: link:../../packages/csrf/dist + '@constructive-io/errors': + specifier: workspace:^ + version: link:../../packages/errors/dist '@constructive-io/express-context': specifier: workspace:^ version: link:../../packages/express-context/dist @@ -2517,6 +2520,19 @@ importers: version: 0.3.0 publishDirectory: dist + packages/errors: + devDependencies: + '@types/node': + specifier: ^22.19.11 + version: 22.19.11 + makage: + specifier: ^0.3.0 + version: 0.3.0 + ts-node: + specifier: ^10.9.2 + version: 10.9.2(@types/node@22.19.11)(typescript@5.9.3) + publishDirectory: dist + packages/csrf: devDependencies: '@types/node': @@ -3061,6 +3077,9 @@ importers: pgpm/types: dependencies: + '@constructive-io/errors': + specifier: workspace:^ + version: link:../../packages/errors/dist pg-env: specifier: workspace:^ version: link:../../postgres/pg-env/dist From 8bd0417f2e89fecc0b65fefc5821a537eeadb3fe Mon Sep 17 00:00:00 2001 From: Dan Lynch Date: Wed, 22 Jul 2026 06:51:23 +0000 Subject: [PATCH 2/2] feat(errors): generate full 287-code constructive-db registry Collect every constructive-db EXCEPTION/THROW code (287) into a generated registry layer with public/internal class, HTTP hint, and message (public copy seeded from dashboard catalogs). Curated typed entries override generated ones. Reproducible via scripts/generate-registry.py from a committed audit snapshot. --- packages/errors/README.md | 25 +- packages/errors/__tests__/parse.test.ts | 41 +- .../errors/scripts/db-error-inventory.json | 2909 +++++++++++++++++ packages/errors/scripts/generate-registry.py | 276 ++ packages/errors/src/define.ts | 22 + packages/errors/src/factory.ts | 15 +- packages/errors/src/format.ts | 14 +- .../src/generated/registry.generated.ts | 608 ++++ packages/errors/src/registry.ts | 53 +- 9 files changed, 3923 insertions(+), 40 deletions(-) create mode 100644 packages/errors/scripts/db-error-inventory.json create mode 100644 packages/errors/scripts/generate-registry.py create mode 100644 packages/errors/src/define.ts create mode 100644 packages/errors/src/generated/registry.generated.ts diff --git a/packages/errors/README.md b/packages/errors/README.md index 986e2c5de4..a67b7fb81f 100644 --- a/packages/errors/README.md +++ b/packages/errors/README.md @@ -37,6 +37,25 @@ throw errors.ACCOUNT_EXISTS(); JSON → GraphQL `extensions.code` → a leading ALL_CAPS token in the message (legacy DB `RAISE`, incl. `CODE (arg, arg)` positional args) → native SQLSTATE constraint mapping. -- The seeded registry covers the public auth/limit codes, native PostgreSQL - constraint codes, and the pgpm CLI codes. The full constructive-db code set is - generated in a later phase; unregistered codes still parse and are masked. +- The registry has two layers, merged at lookup time (curated wins): + - **Generated** (`src/generated/registry.generated.ts`) — every code raised via + `EXCEPTION`/`THROW` across constructive-db (deploy sources + generated output), + each with a `public`/`internal` class, HTTP hint, and default message. + - **Curated** (`src/registry.ts`) — refined, typed-context entries for the codes + that matter most (public auth/limit copy, native PostgreSQL constraint codes, + pgpm CLI codes). These override the generated entries. +- Unregistered codes still `parse()` and are classified `internal` (masked). + +## Regenerating the full registry + +The generated layer is produced from a committed audit snapshot: + +```bash +python3 scripts/generate-registry.py +``` + +It reads `scripts/db-error-inventory.json` (the constructive-db error audit) and, +when a `constructive-io/dashboard` checkout is found (via `DASHBOARD_DIR` or a +sibling `../dashboard`), seeds public copy from its error catalogs. Re-run the +constructive-db audit and overwrite `scripts/db-error-inventory.json` before +regenerating when DB error codes change. Never hand-edit the generated file. diff --git a/packages/errors/__tests__/parse.test.ts b/packages/errors/__tests__/parse.test.ts index 1c9f64483c..ef8f0eb4b4 100644 --- a/packages/errors/__tests__/parse.test.ts +++ b/packages/errors/__tests__/parse.test.ts @@ -1,4 +1,9 @@ -import { classify, ConstructiveError, errors, format, parse, registerCatalog } from '../src'; +import { allCodes, classify, ConstructiveError, errors, format, parse, registerCatalog } from '../src'; +import { + GENERATED_CODE_COUNT, + GENERATED_CODE_META, + generatedRegistry +} from '../src/generated/registry.generated'; describe('parse', () => { it('parses a bare ALL_CAPS DB message (legacy RAISE)', () => { @@ -109,3 +114,37 @@ describe('format / i18n', () => { expect(format('SOME_UNKNOWN_CODE')).toBe('Some unknown code'); }); }); + +describe('generated registry (full constructive-db audit)', () => { + it('collects every audited constructive-db code', () => { + expect(GENERATED_CODE_COUNT).toBe(287); + expect(Object.keys(generatedRegistry)).toHaveLength(287); + // curated + generated codes are all reachable + expect(allCodes().length).toBeGreaterThanOrEqual(287); + }); + + it('classifies a generator-only auth code as public', () => { + // SIGN_UP_DISABLED is emitted only by DB generators (no hand-written source) + expect(GENERATED_CODE_META.SIGN_UP_DISABLED.generatedOnly).toBe(true); + expect(classify('SIGN_UP_DISABLED')).toBe('public'); + }); + + it('classifies internal invariant codes as internal (masked)', () => { + expect(classify('DATA_ID')).toBe('internal'); + expect(classify('APPLY_RLS')).toBe('internal'); + expect(classify('ALTER_TABLE_ADD_COLUMN')).toBe('internal'); + }); + + it('parses a generated public code end-to-end', () => { + const result = parse({ message: 'API_KEY_LIMIT_REACHED', code: 'P0001' }); + expect(result.code).toBe('API_KEY_LIMIT_REACHED'); + expect(result.class).toBe('public'); + expect(result.known).toBe(true); + }); + + it('exposes generated codes on the errors factory', () => { + const err = errors.SIGN_UP_DISABLED(); + expect(err.code).toBe('SIGN_UP_DISABLED'); + expect(err.isPublic).toBe(true); + }); +}); diff --git a/packages/errors/scripts/db-error-inventory.json b/packages/errors/scripts/db-error-inventory.json new file mode 100644 index 0000000000..0ad029166d --- /dev/null +++ b/packages/errors/scripts/db-error-inventory.json @@ -0,0 +1,2909 @@ +{ + "ACCOUNT_DISABLED": { + "count": 6, + "dynamic": false, + "sample": "ACCOUNT_DISABLED", + "source_files": [ + "packages/ast-plpgsql/deploy/schemas/ast_plpgsql_helpers/procedures/utils.sql" + ], + "n_source": 1, + "n_generated": 4 + }, + "ACCOUNT_EXISTS": { + "count": 14, + "dynamic": false, + "sample": "ACCOUNT_EXISTS", + "source_files": [ + "packages/ast-plpgsql/deploy/schemas/ast_plpgsql_helpers/procedures/invites/procedures/send_invite_email_before.sql", + "packages/ast-plpgsql/deploy/schemas/ast_plpgsql_helpers/procedures/invites/procedures/send_invite_email_entity_before.sql", + "packages/ast-plpgsql/deploy/schemas/ast_plpgsql_helpers/procedures/users/challenge/sign_up_unique_key.sql" + ], + "n_source": 8, + "n_generated": 5 + }, + "ACCOUNT_LOCKED_EXCEED_ATTEMPTS": { + "count": 10, + "dynamic": false, + "sample": "ACCOUNT_LOCKED_EXCEED_ATTEMPTS", + "source_files": [ + "packages/ast-plpgsql/deploy/schemas/ast_plpgsql_helpers/procedures/statements.sql", + "packages/ast-plpgsql/deploy/schemas/ast_plpgsql_helpers/procedures/users/challenge/sign_in_request_challenge.sql", + "packages/ast-plpgsql/deploy/schemas/ast_plpgsql_helpers/procedures/users/new-challenge/sign_in_owned_object_request_challenge.sql" + ], + "n_source": 3, + "n_generated": 7 + }, + "ACCOUNT_NOT_FOUND": { + "count": 5, + "dynamic": false, + "sample": "ACCOUNT_NOT_FOUND", + "source_files": [ + "packages/ast-plpgsql/deploy/schemas/ast_plpgsql_helpers/procedures/users/procedures/send_email_otp.sql", + "packages/ast-plpgsql/deploy/schemas/ast_plpgsql_helpers/procedures/users/procedures/sign_in_email_otp.sql", + "packages/ast-plpgsql/deploy/schemas/ast_plpgsql_helpers/procedures/users/procedures/sign_in_magic_link.sql" + ], + "n_source": 4, + "n_generated": 1 + }, + "ALREADY_SCHEDULED": { + "count": 2, + "dynamic": false, + "sample": "ALREADY_SCHEDULED", + "source_files": [ + "pgpm-modules/database-jobs/deploy/schemas/app_jobs/procedures/run_scheduled_job.sql", + "pgpm-modules/jobs/deploy/schemas/app_jobs/procedures/run_scheduled_job.sql" + ], + "n_source": 2, + "n_generated": 0 + }, + "ALTER_TABLE_ADD_COLUMN": { + "count": 2, + "dynamic": true, + "sample": "ALTER_TABLE_ADD_COLUMN: generation_type must be stored or virtual, got %", + "source_files": [ + "packages/ast-actions/deploy/schemas/actions_public/procedures/alter_table_add_column.sql", + "packages/ast/deploy/schemas/ast_helpers/procedures/helpers.sql" + ], + "n_source": 2, + "n_generated": 0 + }, + "API_KEYS_DISABLED": { + "count": 2, + "dynamic": false, + "sample": "API_KEYS_DISABLED", + "source_files": [], + "n_source": 0, + "n_generated": 2 + }, + "API_KEY_LIMIT_REACHED": { + "count": 4, + "dynamic": false, + "sample": "API_KEY_LIMIT_REACHED", + "source_files": [ + "packages/ast-plpgsql/deploy/schemas/ast_plpgsql_helpers/procedures/users/procedures/create_api_key.sql", + "packages/ast-plpgsql/deploy/schemas/ast_plpgsql_helpers/procedures/users/procedures/create_org_api_key.sql" + ], + "n_source": 2, + "n_generated": 2 + }, + "API_KEY_NOT_FOUND": { + "count": 2, + "dynamic": false, + "sample": "API_KEY_NOT_FOUND", + "source_files": [ + "packages/ast-plpgsql/deploy/schemas/ast_plpgsql_helpers/procedures/users/procedures/revoke_api_key.sql" + ], + "n_source": 1, + "n_generated": 1 + }, + "APPLY_RLS": { + "count": 2, + "dynamic": true, + "sample": "APPLY_RLS (bad privs) %", + "source_files": [ + "packages/metaschema-utils/deploy/schemas/metaschema_public/procedures/apply_rls.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "APPLY_RLS_BAD_ARGS": { + "count": 2, + "dynamic": false, + "sample": "APPLY_RLS_BAD_ARGS", + "source_files": [ + "packages/metaschema-utils/deploy/schemas/metaschema_public/procedures/apply_rls.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "APPLY_TABLE_STEP_UP": { + "count": 1, + "dynamic": true, + "sample": "APPLY_TABLE_STEP_UP: table not found for table_id %", + "source_files": [ + "packages/metaschema-generators/deploy/schemas/metaschema_generators/procedures/apply_table_step_up.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "APP_COMPONENT_NOT_VISIBLE": { + "count": 8, + "dynamic": false, + "sample": "APP_COMPONENT_NOT_VISIBLE", + "source_files": [ + "database-modules/constructive-apps-triggers/deploy/schemas/apps_private/trigger_fns/tg_app_components_component_guard.sql" + ], + "n_source": 1, + "n_generated": 1 + }, + "APP_COMPONENT_TARGET_REQUIRED": { + "count": 2, + "dynamic": false, + "sample": "APP_COMPONENT_TARGET_REQUIRED", + "source_files": [ + "database-modules/constructive-apps-triggers/deploy/schemas/apps_private/trigger_fns/tg_app_components_component_guard.sql" + ], + "n_source": 1, + "n_generated": 1 + }, + "APP_SCOPE_FRAMES_SCOPE_REQUIRED": { + "count": 1, + "dynamic": false, + "sample": "APP_SCOPE_FRAMES_SCOPE_REQUIRED: execution_scope is required (no default scope)", + "source_files": [ + "pgpm-modules/app-scope/deploy/schemas/app_scope/procedures/frames.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "APP_SCOPE_LOCAL_FRAMES_DEPTH_EXCEEDED": { + "count": 1, + "dynamic": true, + "sample": "APP_SCOPE_LOCAL_FRAMES_DEPTH_EXCEEDED: scope chain exceeded 32 hops from scope \"%\" (database_id=%)", + "source_files": [ + "pgpm-modules/app-scope/deploy/schemas/app_scope/procedures/local_frames.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "APP_SCOPE_LOCAL_FRAMES_SCOPE_REQUIRED": { + "count": 1, + "dynamic": false, + "sample": "APP_SCOPE_LOCAL_FRAMES_SCOPE_REQUIRED: execution_scope is required (no default scope)", + "source_files": [ + "pgpm-modules/app-scope/deploy/schemas/app_scope/procedures/local_frames.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "ASSIGN_PROFILES_PERMISSION_REQUIRED": { + "count": 6, + "dynamic": false, + "sample": "ASSIGN_PROFILES_PERMISSION_REQUIRED", + "source_files": [ + "packages/ast-plpgsql/deploy/schemas/ast_plpgsql_helpers/procedures/invites/procedures/invite_profile_check.sql", + "packages/ast-plpgsql/deploy/schemas/ast_plpgsql_helpers/procedures/invites/procedures/invite_profile_check_entity.sql" + ], + "n_source": 2, + "n_generated": 2 + }, + "AUTHZ_COLUMN_SECURITY": { + "count": 8, + "dynamic": true, + "sample": "AUTHZ_COLUMN_SECURITY: rule transition requires a non-empty allowed array", + "source_files": [ + "packages/metaschema-generators/deploy/schemas/metaschema_generators/procedures/authz_column_security.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "AUTH_METHOD_NOT_ALLOWED": { + "count": 8, + "dynamic": false, + "sample": "AUTH_METHOD_NOT_ALLOWED", + "source_files": [ + "packages/ast-plpgsql/deploy/schemas/ast_plpgsql_helpers/procedures/statements.sql" + ], + "n_source": 1, + "n_generated": 6 + }, + "BAD_CASING_FUNC": { + "count": 1, + "dynamic": true, + "sample": "BAD_CASING_FUNC (%, %)", + "source_files": [ + "packages/ast-actions/deploy/schemas/actions_public/procedures/inflection_field_trigger.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "BAD_EXPRESSION": { + "count": 291, + "dynamic": true, + "sample": "BAD_EXPRESSION % (unrecognized format)", + "source_files": [ + "packages/ast/deploy/schemas/deparser/procedures/deparse.sql", + "packages/metaschema/deploy/schemas/rls_parser/procedures/parse.sql" + ], + "n_source": 2, + "n_generated": 0 + }, + "BAD_FIELD_INPUT": { + "count": 8, + "dynamic": true, + "sample": "BAD_FIELD_INPUT: generation expression cannot be changed after creation", + "source_files": [ + "packages/metaschema/deploy/schemas/metaschema_public/tables/field/triggers/_00001_ensure_no_sql_injection.sql", + "packages/metaschema/deploy/schemas/metaschema_public/tables/field/triggers/before_insert_field_trigger.sql" + ], + "n_source": 2, + "n_generated": 0 + }, + "BAD_FIELD_ORDERING_CROSS_TABLE": { + "count": 1, + "dynamic": false, + "sample": "BAD_FIELD_ORDERING_CROSS_TABLE", + "source_files": [ + "packages/metaschema/deploy/schemas/metaschema_public/procedures/set_field_order.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "BAD_PRIVILEGE_FOR_POLICY": { + "count": 1, + "dynamic": false, + "sample": "BAD_PRIVILEGE_FOR_POLICY", + "source_files": [ + "packages/metaschema/deploy/schemas/metaschema_public/tables/policy/triggers/policy_sql_trigger.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "BAD_RLS_EXPRESSION": { + "count": 40, + "dynamic": true, + "sample": "BAD_RLS_EXPRESSION % (policy.AuthzValueExists requires ref_schema/ref_table or ref_table_id)", + "source_files": [ + "packages/metaschema/deploy/schemas/rls_parser/procedures/parse.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "BAD_VIEW_EXPRESSION": { + "count": 9, + "dynamic": true, + "sample": "BAD_VIEW_EXPRESSION % (view.ViewTableProjection)", + "source_files": [ + "packages/metaschema/deploy/schemas/view_parser/procedures/parse.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "BM25": { + "count": 1, + "dynamic": true, + "sample": "BM25: invalid text_config \"%\", must be a valid pg_ts_config", + "source_files": [ + "packages/metaschema-utils/deploy/procedures/utils.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "BOOTSTRAP_DATABASE_MISSING": { + "count": 1, + "dynamic": false, + "sample": "BOOTSTRAP_DATABASE_MISSING", + "source_files": [ + "packages/metaschema/deploy/schemas/metaschema_private/procedures/database_bootstrap_owner.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "BOOTSTRAP_SOURCE_MISSING": { + "count": 1, + "dynamic": false, + "sample": "BOOTSTRAP_SOURCE_MISSING", + "source_files": [ + "packages/metaschema/deploy/schemas/metaschema_private/procedures/database_bootstrap_owner.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "BOOTSTRAP_TARGET_NOT_EMPTY": { + "count": 1, + "dynamic": false, + "sample": "BOOTSTRAP_TARGET_NOT_EMPTY", + "source_files": [ + "packages/metaschema/deploy/schemas/metaschema_private/procedures/bootstrap_owner_into_database.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "BUILD_CONDITION_EXPR": { + "count": 9, + "dynamic": true, + "sample": "BUILD_CONDITION_EXPR: unknown condition op \"%\", expected =|!=|>|<|>=|<=|LIKE|NOT LIKE|IS NULL|IS NOT NULL|IS DISTINCT FROM", + "source_files": [ + "packages/metaschema-generators/deploy/schemas/metaschema_generators/procedures/build_condition_expr.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "CANNOT_DISCONNECT_LAST_AUTH_METHOD": { + "count": 2, + "dynamic": false, + "sample": "CANNOT_DISCONNECT_LAST_AUTH_METHOD", + "source_files": [ + "packages/ast-plpgsql/deploy/schemas/ast_plpgsql_helpers/procedures/users/procedures/disconnect_account.sql" + ], + "n_source": 1, + "n_generated": 1 + }, + "CANNOT_REVOKE_CURRENT_SESSION": { + "count": 2, + "dynamic": false, + "sample": "CANNOT_REVOKE_CURRENT_SESSION", + "source_files": [ + "packages/ast-plpgsql/deploy/schemas/ast_plpgsql_helpers/procedures/users/procedures/revoke_session.sql" + ], + "n_source": 1, + "n_generated": 1 + }, + "CAP_CHECK_TRIGGER_ARGS": { + "count": 5, + "dynamic": true, + "sample": "CAP_CHECK_TRIGGER_ARGS (%)", + "source_files": [ + "packages/ast-plpgsql/deploy/schemas/ast_plpgsql_helpers/procedures/limits/cap_check_trigger.sql", + "testing/rls-seed/deploy/schemas/rls-pets-limits-private/trigger_fns/app_limits_cap_check_tg_fn.sql" + ], + "n_source": 2, + "n_generated": 2 + }, + "CATALOG_SYNC": { + "count": 2, + "dynamic": true, + "sample": "CATALOG_SYNC: catalog %.% registration has no copy_fields", + "source_files": [ + "packages/ast-plpgsql/deploy/schemas/ast_plpgsql_helpers/procedures/triggers/catalog_sync_trigger.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "COMMIT_NOT_FOUND": { + "count": 3, + "dynamic": false, + "sample": "COMMIT_NOT_FOUND", + "source_files": [ + "packages/db-migrate/deploy/schemas/db_migrate/tables/sql_actions/triggers/update_tree.sql", + "pgpm-modules/object-tree/deploy/schemas/object_tree_public/procedures/set_and_commit.sql" + ], + "n_source": 2, + "n_generated": 0 + }, + "CONNECTED_ACCOUNT_NOT_FOUND": { + "count": 1, + "dynamic": false, + "sample": "CONNECTED_ACCOUNT_NOT_FOUND", + "source_files": [], + "n_source": 0, + "n_generated": 1 + }, + "CONSTRUCT_BLUEPRINT": { + "count": 13, + "dynamic": true, + "sample": "CONSTRUCT_BLUEPRINT: achievements entry \"%\" references entity_prefix \"%\" but no events_module found for that prefix", + "source_files": [ + "packages/metaschema/deploy/schemas/metaschema_modules_public/procedures/construct_blueprint/procedure.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "CONST_TYPE_FIELDS_IMMUTABLE": { + "count": 2, + "dynamic": false, + "sample": "CONST_TYPE_FIELDS_IMMUTABLE", + "source_files": [ + "packages/metaschema/deploy/schemas/metaschema_public/tables/field/triggers/on_update_field_constraints.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "COPY_TEMPLATE_TO_BLUEPRINT": { + "count": 2, + "dynamic": true, + "sample": "COPY_TEMPLATE_TO_BLUEPRINT: template % is private and not owned by %", + "source_files": [ + "packages/metaschema/deploy/schemas/metaschema_modules_public/procedures/copy_template_to_blueprint/procedure.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "CREDIT_CODE_EXPIRED": { + "count": 2, + "dynamic": false, + "sample": "CREDIT_CODE_EXPIRED", + "source_files": [ + "packages/ast-plpgsql/deploy/schemas/ast_plpgsql_helpers/procedures/limits/credit_redemptions_trigger.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "CREDIT_CODE_MAX_REDEMPTIONS_REACHED": { + "count": 2, + "dynamic": false, + "sample": "CREDIT_CODE_MAX_REDEMPTIONS_REACHED", + "source_files": [ + "packages/ast-plpgsql/deploy/schemas/ast_plpgsql_helpers/procedures/limits/credit_redemptions_trigger.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "CREDIT_CODE_NOT_FOUND": { + "count": 3, + "dynamic": false, + "sample": "CREDIT_CODE_NOT_FOUND", + "source_files": [ + "packages/ast-plpgsql/deploy/schemas/ast_plpgsql_helpers/procedures/limits/credit_redemptions_trigger.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "CROSS_DATABASE_CHECK": { + "count": 1, + "dynamic": true, + "sample": "CROSS_DATABASE_CHECK: table % not found (%)", + "source_files": [ + "packages/metaschema/deploy/schemas/metaschema_private/procedures/assert_same_database.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "CROSS_DATABASE_REF": { + "count": 5, + "dynamic": true, + "sample": "CROSS_DATABASE_REF: ref_table_id does not belong to database_id (denormalized_table_field)", + "source_files": [ + "packages/metaschema-generators/deploy/schemas/metaschema_generators/procedures/denormalized_table_field.sql", + "packages/metaschema/deploy/schemas/metaschema_private/procedures/assert_same_database.sql", + "packages/metaschema/deploy/schemas/metaschema_public/tables/foreign_key_constraint/triggers/on_insert_foreign_key_constraint.sql" + ], + "n_source": 4, + "n_generated": 0 + }, + "CSRF_TOKEN_REQUIRED": { + "count": 6, + "dynamic": false, + "sample": "CSRF_TOKEN_REQUIRED", + "source_files": [ + "packages/ast-plpgsql/deploy/schemas/ast_plpgsql_helpers/procedures/users/procedures/sign_in.sql", + "packages/ast-plpgsql/deploy/schemas/ast_plpgsql_helpers/procedures/users/procedures/sign_up.sql" + ], + "n_source": 2, + "n_generated": 2 + }, + "DATABASE_FIELD_RESERVED_WORD": { + "count": 1, + "dynamic": false, + "sample": "DATABASE_FIELD_RESERVED_WORD", + "source_files": [ + "packages/metaschema/deploy/schemas/metaschema_public/tables/field/triggers/ensure_field_not_reserved_word.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "DATABASE_TABLE_RESERVED_WORD": { + "count": 1, + "dynamic": false, + "sample": "DATABASE_TABLE_RESERVED_WORD", + "source_files": [ + "packages/metaschema/deploy/schemas/metaschema_public/tables/table/triggers/_000002_ensure_table_not_reserved_word.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "DATA_BILLING_METER": { + "count": 1, + "dynamic": true, + "sample": "DATA_BILLING_METER: unknown event %, expected INSERT, DELETE, or UPDATE", + "source_files": [ + "packages/ast-plpgsql/deploy/schemas/ast_plpgsql_helpers/procedures/triggers/billing_meter_trigger.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "DATA_COMPOSITE_FIELD": { + "count": 6, + "dynamic": true, + "sample": "DATA_COMPOSITE_FIELD: source_fields is required and must be a JSON array", + "source_files": [ + "packages/metaschema-generators/deploy/schemas/metaschema_generators/procedures/data_composite_field.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "DATA_DIRECT_OWNER": { + "count": 1, + "dynamic": true, + "sample": "DATA_DIRECT_OWNER: table not found for table_id %", + "source_files": [ + "packages/metaschema-generators/deploy/schemas/metaschema_generators/procedures/data_direct_owner.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "DATA_DIRECT_OWNER_IN_ENTITY": { + "count": 1, + "dynamic": true, + "sample": "DATA_DIRECT_OWNER_IN_ENTITY: table not found for table_id %", + "source_files": [ + "packages/metaschema-generators/deploy/schemas/metaschema_generators/procedures/data_direct_owner_in_entity.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "DATA_FORCE_CURRENT_USER": { + "count": 2, + "dynamic": true, + "sample": "DATA_FORCE_CURRENT_USER: field \"%\" not found on table \"%\"", + "source_files": [ + "packages/metaschema-generators/deploy/schemas/metaschema_generators/procedures/data_force_current_user.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "DATA_GENERATED": { + "count": 11, + "dynamic": true, + "sample": "DATA_GENERATED: unknown kind %, expected expression|concat|slug|object_name|hash", + "source_files": [ + "packages/metaschema-generators/deploy/schemas/metaschema_generators/procedures/data_generated.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "DATA_I18N": { + "count": 4, + "dynamic": true, + "sample": "DATA_I18N: \"fields\" parameter is required and must be a non-empty array", + "source_files": [ + "packages/metaschema-generators/deploy/schemas/metaschema_generators/procedures/data_i18n.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "DATA_ID": { + "count": 1, + "dynamic": true, + "sample": "DATA_ID: table not found for table_id %", + "source_files": [ + "packages/metaschema-generators/deploy/schemas/metaschema_generators/procedures/data_id.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "DATA_IMMUTABLE_FIELDS": { + "count": 3, + "dynamic": true, + "sample": "DATA_IMMUTABLE_FIELDS: fields array is required and must not be empty", + "source_files": [ + "packages/metaschema-generators/deploy/schemas/metaschema_generators/procedures/data_immutable_fields.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "DATA_INFLECTION": { + "count": 4, + "dynamic": true, + "sample": "DATA_INFLECTION: field \"%\" not found on table \"%\"", + "source_files": [ + "packages/metaschema-generators/deploy/schemas/metaschema_generators/procedures/data_inflection.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "DATA_INHERIT_FROM_PARENT": { + "count": 6, + "dynamic": true, + "sample": "DATA_INHERIT_FROM_PARENT: could not resolve parent table from FK on field %. Provide parent_table in data.", + "source_files": [ + "packages/metaschema-generators/deploy/schemas/metaschema_generators/procedures/data_inherit_from_parent.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "DATA_JOB_TRIGGER": { + "count": 19, + "dynamic": true, + "sample": "DATA_JOB_TRIGGER: \"function_target\" is not a valid option. Ledger routing is derived from the function registry and cannot be set by callers.", + "source_files": [ + "packages/ast-plpgsql/deploy/schemas/ast_plpgsql_helpers/procedures/triggers/job_trigger.sql", + "packages/metaschema-generators/deploy/schemas/metaschema_generators/procedures/data_job_trigger.sql" + ], + "n_source": 2, + "n_generated": 0 + }, + "DATA_JSONB": { + "count": 1, + "dynamic": true, + "sample": "DATA_JSONB: table not found for table_id %", + "source_files": [ + "packages/metaschema-generators/deploy/schemas/metaschema_generators/procedures/data_jsonb.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "DATA_MEMBERSHIP_BY_FIELD": { + "count": 1, + "dynamic": true, + "sample": "DATA_MEMBERSHIP_BY_FIELD: table not found for table_id %", + "source_files": [ + "packages/metaschema-generators/deploy/schemas/metaschema_generators/procedures/data_membership_by_field.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "DATA_MEMBER_OWNER": { + "count": 1, + "dynamic": true, + "sample": "DATA_MEMBER_OWNER: table not found for table_id %", + "source_files": [ + "packages/metaschema-generators/deploy/schemas/metaschema_generators/procedures/data_member_owner.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "DATA_OWNED_FIELDS": { + "count": 5, + "dynamic": true, + "sample": "DATA_OWNED_FIELDS: protected field \"%\" not found on table \"%\"", + "source_files": [ + "packages/metaschema-generators/deploy/schemas/metaschema_generators/procedures/data_owned_fields.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "DATA_PEOPLESTAMPS": { + "count": 1, + "dynamic": true, + "sample": "DATA_PEOPLESTAMPS: table not found for table_id %", + "source_files": [ + "packages/metaschema-generators/deploy/schemas/metaschema_generators/procedures/data_peoplestamps.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "DATA_PUBLISHABLE": { + "count": 3, + "dynamic": true, + "sample": "DATA_PUBLISHABLE: is_published_field_name must not be empty", + "source_files": [ + "packages/metaschema-generators/deploy/schemas/metaschema_generators/procedures/data_publishable.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "DATA_REALTIME": { + "count": 2, + "dynamic": true, + "sample": "DATA_REALTIME: unknown operation %, expected INSERT, UPDATE, or DELETE", + "source_files": [ + "packages/metaschema-generators/deploy/schemas/metaschema_generators/procedures/data_realtime.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "DATA_SLUG": { + "count": 3, + "dynamic": true, + "sample": "DATA_SLUG: source field \"%\" not found on table \"%\"", + "source_files": [ + "packages/metaschema-generators/deploy/schemas/metaschema_generators/procedures/data_slug.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "DATA_STATUS_FIELD": { + "count": 2, + "dynamic": true, + "sample": "DATA_STATUS_FIELD: unsupported type \"%\" for status field, expected a scalar type (text, varchar, integer, smallint, bigint, boolean, uuid, citext)", + "source_files": [ + "packages/metaschema-generators/deploy/schemas/metaschema_generators/procedures/data_status_field.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "DATA_TAGS": { + "count": 1, + "dynamic": true, + "sample": "DATA_TAGS: table not found for table_id %", + "source_files": [ + "packages/metaschema-generators/deploy/schemas/metaschema_generators/procedures/data_tags.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "DATA_TIMESTAMPS": { + "count": 1, + "dynamic": true, + "sample": "DATA_TIMESTAMPS: table not found for table_id %", + "source_files": [ + "packages/metaschema-generators/deploy/schemas/metaschema_generators/procedures/data_timestamps.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "DB_PRESET_MODULE": { + "count": 2, + "dynamic": true, + "sample": "DB_PRESET_MODULE: scope (%) does not match backing merkle_store_module % scope (%)", + "source_files": [ + "packages/metaschema-generators/deploy/schemas/metaschema_generators/procedures/db_preset_module.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "DELETE_FIRST": { + "count": 1, + "dynamic": false, + "sample": "DELETE_FIRST", + "source_files": [ + "packages/metaschema/deploy/schemas/metaschema_public/tables/index/triggers/index_sql_trigger.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "DOMAIN_CHECK_CERT_NOT_ISSUING": { + "count": 1, + "dynamic": true, + "sample": "DOMAIN_CHECK_CERT_NOT_ISSUING: managed_domain % has cert_status % (issue a cert first)", + "source_files": [ + "pgpm-modules/services/deploy/schemas/services_private/procedures/domain_check_cert.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "DOMAIN_CHECK_CERT_UNKNOWN_DOMAIN": { + "count": 1, + "dynamic": true, + "sample": "DOMAIN_CHECK_CERT_UNKNOWN_DOMAIN: no managed_domain with id %", + "source_files": [ + "pgpm-modules/services/deploy/schemas/services_private/procedures/domain_check_cert.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "DOMAIN_ISSUE_CERT_BAD_ISSUER": { + "count": 1, + "dynamic": true, + "sample": "DOMAIN_ISSUE_CERT_BAD_ISSUER: unsupported issuer_env % (expected staging|production)", + "source_files": [ + "pgpm-modules/services/deploy/schemas/services_private/procedures/domain_issue_cert.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "DOMAIN_ISSUE_CERT_NOT_VERIFIED": { + "count": 1, + "dynamic": true, + "sample": "DOMAIN_ISSUE_CERT_NOT_VERIFIED: managed_domain % is % (must be verified before issuing a cert)", + "source_files": [ + "pgpm-modules/services/deploy/schemas/services_private/procedures/domain_issue_cert.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "DOMAIN_ISSUE_CERT_UNKNOWN_DOMAIN": { + "count": 1, + "dynamic": true, + "sample": "DOMAIN_ISSUE_CERT_UNKNOWN_DOMAIN: no managed_domain with id %", + "source_files": [ + "pgpm-modules/services/deploy/schemas/services_private/procedures/domain_issue_cert.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "DOMAIN_ISSUE_CHALLENGE_BAD_METHOD": { + "count": 1, + "dynamic": true, + "sample": "DOMAIN_ISSUE_CHALLENGE_BAD_METHOD: unsupported verification method %", + "source_files": [ + "pgpm-modules/services/deploy/schemas/services_private/procedures/domain_issue_challenge.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "DOMAIN_ISSUE_CHALLENGE_NO_OWNER": { + "count": 1, + "dynamic": true, + "sample": "DOMAIN_ISSUE_CHALLENGE_NO_OWNER: managed_domain % has no owning entity", + "source_files": [ + "pgpm-modules/services/deploy/schemas/services_private/procedures/domain_issue_challenge.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "DOMAIN_ISSUE_CHALLENGE_UNKNOWN_DOMAIN": { + "count": 1, + "dynamic": true, + "sample": "DOMAIN_ISSUE_CHALLENGE_UNKNOWN_DOMAIN: no managed_domain with id %", + "source_files": [ + "pgpm-modules/services/deploy/schemas/services_private/procedures/domain_issue_challenge.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "DOMAIN_RENEW_NOT_ACTIVE": { + "count": 1, + "dynamic": true, + "sample": "DOMAIN_RENEW_NOT_ACTIVE: managed_domain % has cert_status % (only an active cert can be renewed)", + "source_files": [ + "pgpm-modules/services/deploy/schemas/services_private/procedures/domain_renew.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "DOMAIN_RENEW_UNKNOWN_DOMAIN": { + "count": 1, + "dynamic": true, + "sample": "DOMAIN_RENEW_UNKNOWN_DOMAIN: no managed_domain with id %", + "source_files": [ + "pgpm-modules/services/deploy/schemas/services_private/procedures/domain_renew.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "DOMAIN_REVOKE_UNKNOWN_DOMAIN": { + "count": 1, + "dynamic": true, + "sample": "DOMAIN_REVOKE_UNKNOWN_DOMAIN: no managed_domain with id %", + "source_files": [ + "pgpm-modules/services/deploy/schemas/services_private/procedures/domain_revoke.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "DOMAIN_VERIFY_BAD_METHOD": { + "count": 1, + "dynamic": true, + "sample": "DOMAIN_VERIFY_BAD_METHOD: unsupported verification method %", + "source_files": [ + "pgpm-modules/services/deploy/schemas/services_private/procedures/domain_verify.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "DOMAIN_VERIFY_NO_CHALLENGE": { + "count": 1, + "dynamic": true, + "sample": "DOMAIN_VERIFY_NO_CHALLENGE: no outstanding % challenge for managed_domain %", + "source_files": [ + "pgpm-modules/services/deploy/schemas/services_private/procedures/domain_verify.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "EMAIL_NOT_VERIFIED": { + "count": 6, + "dynamic": false, + "sample": "EMAIL_NOT_VERIFIED", + "source_files": [ + "packages/ast-plpgsql/deploy/schemas/ast_plpgsql_helpers/procedures/invites/procedures/submit_invite_code.sql", + "packages/ast-plpgsql/deploy/schemas/ast_plpgsql_helpers/procedures/invites/procedures/submit_invite_code_entity.sql" + ], + "n_source": 2, + "n_generated": 2 + }, + "EMBEDDING_CHUNKS": { + "count": 3, + "dynamic": true, + "sample": "EMBEDDING_CHUNKS: unknown metric %, expected cosine|l2|ip", + "source_files": [ + "packages/metaschema/deploy/schemas/metaschema_public/tables/embedding_chunks/triggers/embedding_chunks_trigger.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "ENQUEUE_SCOPE_REQUIRED": { + "count": 1, + "dynamic": false, + "sample": "ENQUEUE_SCOPE_REQUIRED: scope is required (no default scope)", + "source_files": [ + "pgpm-modules/function-resolution/deploy/schemas/function_resolution/procedures/enqueue.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "ENTITY_NOT_FOUND": { + "count": 2, + "dynamic": false, + "sample": "ENTITY_NOT_FOUND", + "source_files": [ + "packages/ast-plpgsql/deploy/schemas/ast_plpgsql_helpers/procedures/memberships/get_organization_id.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "ENTITY_TYPE_NOT_FOUND": { + "count": 2, + "dynamic": false, + "sample": "ENTITY_TYPE_NOT_FOUND", + "source_files": [ + "packages/ast-plpgsql/deploy/schemas/ast_plpgsql_helpers/procedures/memberships/get_organization_id.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "ENTITY_TYPE_PROVISION": { + "count": 7, + "dynamic": true, + "sample": "ENTITY_TYPE_PROVISION: derived table name \"%\" exceeds safe length (prefix: %, key: %)", + "source_files": [ + "packages/metaschema/deploy/schemas/metaschema_modules_public/tables/entity_type_provision/triggers/insert_entity_type_provision.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "ENUM_VALUES_CANNOT_BE_REMOVED": { + "count": 1, + "dynamic": false, + "sample": "ENUM_VALUES_CANNOT_BE_REMOVED: PostgreSQL does not support removing enum values", + "source_files": [ + "packages/metaschema/deploy/schemas/metaschema_public/tables/enum/triggers/enum_sql_trigger.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "ENUM_VALUES_CANNOT_BE_REORDERED": { + "count": 1, + "dynamic": false, + "sample": "ENUM_VALUES_CANNOT_BE_REORDERED: existing enum values must remain in the same order", + "source_files": [ + "packages/metaschema/deploy/schemas/metaschema_public/tables/enum/triggers/enum_sql_trigger.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "EVENT_REFERRAL": { + "count": 14, + "dynamic": true, + "sample": "EVENT_REFERRAL: invites module not found for database_id %. Ensure invites module is enabled (has_invites=true).", + "source_files": [ + "packages/metaschema-generators/deploy/schemas/metaschema_generators/procedures/event_referral.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "EVENT_TRACKER": { + "count": 12, + "dynamic": true, + "sample": "EVENT_TRACKER: events module not found for database_id %. Ensure events module is enabled.", + "source_files": [ + "packages/metaschema-generators/deploy/schemas/metaschema_generators/procedures/event_tracker.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "EXPIRED_TOKEN": { + "count": 1, + "dynamic": false, + "sample": "EXPIRED_TOKEN", + "source_files": [ + "packages/ast-plpgsql/deploy/schemas/ast_plpgsql_helpers/procedures/users/procedures/approve_device.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "EXTERNAL_MEMBERS_NOT_ALLOWED": { + "count": 2, + "dynamic": false, + "sample": "EXTERNAL_MEMBERS_NOT_ALLOWED", + "source_files": [ + "packages/ast-plpgsql/deploy/schemas/ast_plpgsql_helpers/procedures/memberships/on_external_upcreate.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "FEATURE_DISABLED": { + "count": 7, + "dynamic": true, + "sample": "FEATURE_DISABLED (%)", + "source_files": [ + "packages/ast-plpgsql/deploy/schemas/ast_plpgsql_helpers/procedures/limits/cap_check_trigger.sql", + "testing/rls-seed/deploy/schemas/rls-pets-limits-private/trigger_fns/app_limits_cap_check_tg_fn.sql" + ], + "n_source": 2, + "n_generated": 2 + }, + "FIELDS_MISMATCH": { + "count": 1, + "dynamic": true, + "sample": "FIELDS_MISMATCH (denormalized_table_field %[%] %[%])", + "source_files": [ + "packages/metaschema-generators/deploy/schemas/metaschema_generators/procedures/denormalized_table_field.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "FULL_TEXT_SEARCH": { + "count": 2, + "dynamic": true, + "sample": "FULL_TEXT_SEARCH: invalid lang \"%\" at index %, must be a valid pg_ts_config", + "source_files": [ + "packages/metaschema/deploy/schemas/metaschema_public/tables/full_text_search/triggers/full_text_search_sql_trigger.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "FUNCTION_DEFINITION_INVALID_PAIR": { + "count": 1, + "dynamic": true, + "sample": "FUNCTION_DEFINITION_INVALID_PAIR: function_definition_id % / definition_scope \"%\" does not resolve to task_identifier \"%\" (database_id=%)", + "source_files": [ + "pgpm-modules/function-resolution/deploy/schemas/function_resolution/procedures/resolve_invocation.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "FUNCTION_DEFINITION_NOT_FOUND": { + "count": 1, + "dynamic": true, + "sample": "FUNCTION_DEFINITION_NOT_FOUND: no definition for task_identifier \"%\" in the scope chain starting at scope \"%\" (database_id=%)", + "source_files": [ + "pgpm-modules/function-resolution/deploy/schemas/function_resolution/procedures/resolve.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "FUNCTION_SCHEDULES": { + "count": 2, + "dynamic": false, + "sample": "FUNCTION_SCHEDULES: function definition does not allow the cron access channel", + "source_files": [ + "packages/ast-plpgsql/deploy/schemas/ast_plpgsql_helpers/procedures/triggers/function_schedule_sync_trigger.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "FUNCTION_SCHEDULES_INTERVAL": { + "count": 1, + "dynamic": false, + "sample": "FUNCTION_SCHEDULES_INTERVAL (min_schedule_interval)", + "source_files": [ + "packages/ast-plpgsql/deploy/schemas/ast_plpgsql_helpers/procedures/triggers/function_schedule_sync_trigger.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "FUNCTION_SCHEDULES_LIMIT": { + "count": 1, + "dynamic": false, + "sample": "FUNCTION_SCHEDULES_LIMIT (max_schedules_per_entity)", + "source_files": [ + "packages/ast-plpgsql/deploy/schemas/ast_plpgsql_helpers/procedures/triggers/function_schedule_sync_trigger.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "FUNCTION_SCHEDULE_SYNC": { + "count": 1, + "dynamic": true, + "sample": "FUNCTION_SCHEDULE_SYNC: unknown event %, expected UPSERT or DELETE", + "source_files": [ + "packages/ast-plpgsql/deploy/schemas/ast_plpgsql_helpers/procedures/triggers/function_schedule_sync_trigger.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "GRAPH_EXECUTION_MODULE": { + "count": 2, + "dynamic": true, + "sample": "GRAPH_EXECUTION_MODULE: merkle_store_module % not found (from graph_module %)", + "source_files": [ + "packages/metaschema-generators/deploy/schemas/metaschema_generators/procedures/graph_execution_module.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "GRAPH_MODULE": { + "count": 2, + "dynamic": true, + "sample": "GRAPH_MODULE: scope (%) does not match backing merkle_store_module % scope (%)", + "source_files": [ + "packages/metaschema-generators/deploy/schemas/metaschema_generators/procedures/graph_module.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "GRAPH_VALIDATION_FAILED": { + "count": 3, + "dynamic": true, + "sample": "GRAPH_VALIDATION_FAILED: graph % failed pre-execution validation: %", + "source_files": [ + "packages/ast-plpgsql/deploy/schemas/ast_plpgsql_helpers/procedures/graph/graph_bodies.sql", + "platform-modules/constructive-compute-platform/deploy/schemas/compute_private/procedures/start_invocation_execution/procedure.sql" + ], + "n_source": 2, + "n_generated": 1 + }, + "GROUPS_REQ_ENTITIES": { + "count": 1, + "dynamic": false, + "sample": "GROUPS_REQ_ENTITIES", + "source_files": [ + "packages/metaschema-generators/deploy/schemas/metaschema_generators/procedures/memberships_module.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "GUARD_STEP_UP": { + "count": 23, + "dynamic": true, + "sample": "GUARD_STEP_UP: user_auth_module not found for database_id % \u2014 required for require_step_up()", + "source_files": [ + "packages/ast-plpgsql/deploy/schemas/ast_plpgsql_helpers/procedures/triggers/guard_step_up_trigger.sql", + "packages/metaschema-generators/deploy/schemas/metaschema_generators/procedures/guard_step_up.sql" + ], + "n_source": 2, + "n_generated": 0 + }, + "HIERARCHY_CYCLE_DETECTED": { + "count": 2, + "dynamic": true, + "sample": "HIERARCHY_CYCLE_DETECTED: Setting % as parent of % would create a cycle", + "source_files": [ + "packages/ast-plpgsql/deploy/schemas/ast_plpgsql_helpers/procedures/hierarchy/hierarchy_triggers.sql" + ], + "n_source": 1, + "n_generated": 1 + }, + "HIERARCHY_DEPTH_EXCEEDED": { + "count": 2, + "dynamic": false, + "sample": "HIERARCHY_DEPTH_EXCEEDED", + "source_files": [ + "packages/ast-plpgsql/deploy/schemas/ast_plpgsql_helpers/procedures/memberships/get_organization_id.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "HIERARCHY_INACTIVE_MEMBER": { + "count": 4, + "dynamic": true, + "sample": "HIERARCHY_INACTIVE_MEMBER: Cannot add user % to hierarchy - user must be an active member of the organization first", + "source_files": [ + "packages/ast-plpgsql/deploy/schemas/ast_plpgsql_helpers/procedures/hierarchy/hierarchy_triggers.sql" + ], + "n_source": 1, + "n_generated": 1 + }, + "HIERARCHY_MEMBER_IN_USE": { + "count": 2, + "dynamic": true, + "sample": "HIERARCHY_MEMBER_IN_USE: Cannot deactivate user % - user must be removed from the organization hierarchy first", + "source_files": [ + "packages/ast-plpgsql/deploy/schemas/ast_plpgsql_helpers/procedures/hierarchy/hierarchy_triggers.sql" + ], + "n_source": 1, + "n_generated": 1 + }, + "HOSTNAME_BINDING_SYNC": { + "count": 1, + "dynamic": true, + "sample": "HOSTNAME_BINDING_SYNC: unknown event %, expected UPSERT or DELETE", + "source_files": [ + "packages/ast-plpgsql/deploy/schemas/ast_plpgsql_helpers/procedures/triggers/routing_bindings_trigger.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "HTTP_ROUTE_TARGET_NOT_FOUND": { + "count": 6, + "dynamic": false, + "sample": "HTTP_ROUTE_TARGET_NOT_FOUND:function", + "source_files": [ + "packages/ast-plpgsql/deploy/schemas/ast_plpgsql_helpers/procedures/http_routes.sql" + ], + "n_source": 1, + "n_generated": 1 + }, + "HTTP_ROUTE_TARGET_OUTSIDE_SCOPE": { + "count": 2, + "dynamic": false, + "sample": "HTTP_ROUTE_TARGET_OUTSIDE_SCOPE:domain_id", + "source_files": [ + "packages/ast-plpgsql/deploy/schemas/ast_plpgsql_helpers/procedures/http_routes.sql" + ], + "n_source": 1, + "n_generated": 1 + }, + "IDENTITY_ACCOUNT_NOT_FOUND": { + "count": 3, + "dynamic": false, + "sample": "IDENTITY_ACCOUNT_NOT_FOUND", + "source_files": [ + "packages/ast-plpgsql/deploy/schemas/ast_plpgsql_helpers/procedures/users/procedures/sign_in_identity.sql" + ], + "n_source": 1, + "n_generated": 1 + }, + "IDENTITY_ALREADY_LINKED": { + "count": 2, + "dynamic": false, + "sample": "IDENTITY_ALREADY_LINKED", + "source_files": [ + "packages/ast-plpgsql/deploy/schemas/ast_plpgsql_helpers/procedures/users/procedures/link_identity.sql" + ], + "n_source": 1, + "n_generated": 1 + }, + "IDENTITY_LINK_AVAILABLE": { + "count": 2, + "dynamic": false, + "sample": "IDENTITY_LINK_AVAILABLE", + "source_files": [ + "packages/ast-plpgsql/deploy/schemas/ast_plpgsql_helpers/procedures/users/procedures/sign_in_identity.sql" + ], + "n_source": 1, + "n_generated": 1 + }, + "IDENTITY_PROVIDER_NOT_CONFIGURED": { + "count": 4, + "dynamic": false, + "sample": "IDENTITY_PROVIDER_NOT_CONFIGURED", + "source_files": [ + "packages/ast-plpgsql/deploy/schemas/ast_plpgsql_helpers/procedures/users/procedures/link_identity.sql", + "packages/ast-plpgsql/deploy/schemas/ast_plpgsql_helpers/procedures/users/procedures/sign_in_identity.sql" + ], + "n_source": 2, + "n_generated": 2 + }, + "IDENTITY_PROVIDER_NOT_FOUND": { + "count": 1, + "dynamic": false, + "sample": "IDENTITY_PROVIDER_NOT_FOUND", + "source_files": [], + "n_source": 0, + "n_generated": 1 + }, + "IDENTITY_PROVIDER_QUOTA_EXCEEDED": { + "count": 1, + "dynamic": false, + "sample": "IDENTITY_PROVIDER_QUOTA_EXCEEDED", + "source_files": [ + "pgpm-modules/utils/deploy/schemas/utils/procedures/enforce_identity_providers_quota.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "IDENTITY_SIGN_IN_DISABLED": { + "count": 1, + "dynamic": false, + "sample": "IDENTITY_SIGN_IN_DISABLED", + "source_files": [], + "n_source": 0, + "n_generated": 1 + }, + "IDENTITY_SIGN_UP_DISABLED": { + "count": 1, + "dynamic": false, + "sample": "IDENTITY_SIGN_UP_DISABLED", + "source_files": [], + "n_source": 0, + "n_generated": 1 + }, + "IMMUTABLE_FIELD": { + "count": 1, + "dynamic": false, + "sample": "IMMUTABLE_FIELD", + "source_files": [ + "packages/ast-plpgsql/deploy/schemas/ast_plpgsql_helpers/procedures/triggers/immutable_field_trigger.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "IMMUTABLE_PEOPLESTAMPS": { + "count": 1, + "dynamic": false, + "sample": "IMMUTABLE_PEOPLESTAMPS", + "source_files": [ + "packages/metaschema/deploy/schemas/metaschema_public/tables/table/triggers/_000002_stamps.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "IMMUTABLE_PROPS": { + "count": 2, + "dynamic": false, + "sample": "IMMUTABLE_PROPS", + "source_files": [ + "packages/metaschema/deploy/schemas/metaschema_public/tables/policy/triggers/policy_sql_trigger.sql", + "packages/metaschema/deploy/schemas/metaschema_public/tables/view/triggers/view_sql_trigger.sql" + ], + "n_source": 2, + "n_generated": 0 + }, + "IMMUTABLE_TIMESTAMPS": { + "count": 1, + "dynamic": false, + "sample": "IMMUTABLE_TIMESTAMPS", + "source_files": [ + "packages/metaschema/deploy/schemas/metaschema_public/tables/table/triggers/_000002_stamps.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "INCORRECT_PASSWORD": { + "count": 2, + "dynamic": false, + "sample": "INCORRECT_PASSWORD", + "source_files": [ + "packages/ast-plpgsql/deploy/schemas/ast_plpgsql_helpers/procedures/users/procedures/set_password.sql" + ], + "n_source": 1, + "n_generated": 1 + }, + "INSERT_GRAPH_EXECUTION_MODULE": { + "count": 1, + "dynamic": true, + "sample": "INSERT_GRAPH_EXECUTION_MODULE: graph_module % not found", + "source_files": [ + "packages/metaschema/deploy/schemas/metaschema_modules_public/tables/graph_execution_module/triggers/insert_graph_execution_module.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "INVALID_ACCESS_LEVEL": { + "count": 4, + "dynamic": false, + "sample": "INVALID_ACCESS_LEVEL", + "source_files": [ + "packages/ast-plpgsql/deploy/schemas/ast_plpgsql_helpers/procedures/users/procedures/create_api_key.sql", + "packages/ast-plpgsql/deploy/schemas/ast_plpgsql_helpers/procedures/users/procedures/create_org_api_key.sql" + ], + "n_source": 2, + "n_generated": 2 + }, + "INVALID_BASE32": { + "count": 1, + "dynamic": false, + "sample": "INVALID_BASE32", + "source_files": [ + "pgpm-modules/base32/deploy/schemas/base32/procedures/decode.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "INVALID_CODE": { + "count": 6, + "dynamic": false, + "sample": "INVALID_CODE", + "source_files": [ + "packages/ast-plpgsql/deploy/schemas/ast_plpgsql_helpers/procedures/users/procedures/sign_in_email_otp.sql", + "packages/ast-plpgsql/deploy/schemas/ast_plpgsql_helpers/procedures/users/procedures/sign_up_sms.sql" + ], + "n_source": 2, + "n_generated": 1 + }, + "INVALID_CSRF_TOKEN": { + "count": 2, + "dynamic": false, + "sample": "INVALID_CSRF_TOKEN", + "source_files": [], + "n_source": 0, + "n_generated": 2 + }, + "INVALID_DEFAULT_LIMIT_ID": { + "count": 3, + "dynamic": false, + "sample": "INVALID_DEFAULT_LIMIT_ID", + "source_files": [ + "packages/ast-plpgsql/deploy/schemas/ast_plpgsql_helpers/procedures/limits/limit_credits_trigger.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "INVALID_FUNCTION_REF": { + "count": 1, + "dynamic": true, + "sample": "INVALID_FUNCTION_REF: node_type \"%\" field \"%\" references task_identifier \"%\" which is not a registered function.", + "source_files": [ + "packages/metaschema/deploy/schemas/metaschema_public/procedures/apply_registry_defaults.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "INVALID_METER_ID": { + "count": 3, + "dynamic": false, + "sample": "INVALID_METER_ID", + "source_files": [ + "packages/ast-plpgsql/deploy/schemas/ast_plpgsql_helpers/procedures/billing/meter_credits_trigger.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "INVALID_MFA_CHALLENGE": { + "count": 2, + "dynamic": false, + "sample": "INVALID_MFA_CHALLENGE", + "source_files": [ + "packages/ast-plpgsql/deploy/schemas/ast_plpgsql_helpers/procedures/users/procedures/complete_mfa_challenge.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "INVALID_MFA_LEVEL": { + "count": 4, + "dynamic": false, + "sample": "INVALID_MFA_LEVEL", + "source_files": [ + "packages/ast-plpgsql/deploy/schemas/ast_plpgsql_helpers/procedures/users/procedures/create_api_key.sql", + "packages/ast-plpgsql/deploy/schemas/ast_plpgsql_helpers/procedures/users/procedures/create_org_api_key.sql" + ], + "n_source": 2, + "n_generated": 2 + }, + "INVALID_NODE_EDGES": { + "count": 2, + "dynamic": true, + "sample": "INVALID_NODE_EDGES: node \"%\" edges must be a JSON array, got %", + "source_files": [ + "platform-modules/constructive-compute-platform/deploy/schemas/compute_public/procedures/import_graph_json/procedure.sql" + ], + "n_source": 1, + "n_generated": 1 + }, + "INVALID_NODE_NODES": { + "count": 2, + "dynamic": true, + "sample": "INVALID_NODE_NODES: node \"%\" nodes must be a JSON array, got %", + "source_files": [ + "platform-modules/constructive-compute-platform/deploy/schemas/compute_public/procedures/import_graph_json/procedure.sql" + ], + "n_source": 1, + "n_generated": 1 + }, + "INVALID_NODE_PROPS": { + "count": 2, + "dynamic": true, + "sample": "INVALID_NODE_PROPS: node \"%\" props must be a JSON array, got %", + "source_files": [ + "platform-modules/constructive-compute-platform/deploy/schemas/compute_public/procedures/import_graph_json/procedure.sql" + ], + "n_source": 1, + "n_generated": 1 + }, + "INVALID_ORGANIZATION": { + "count": 2, + "dynamic": false, + "sample": "INVALID_ORGANIZATION", + "source_files": [ + "packages/ast-plpgsql/deploy/schemas/ast_plpgsql_helpers/procedures/principals/create_org_principal.sql" + ], + "n_source": 1, + "n_generated": 1 + }, + "INVALID_TOKEN": { + "count": 3, + "dynamic": false, + "sample": "INVALID_TOKEN", + "source_files": [ + "packages/ast-plpgsql/deploy/schemas/ast_plpgsql_helpers/procedures/users/procedures/approve_device.sql", + "packages/ast-plpgsql/deploy/schemas/ast_plpgsql_helpers/procedures/users/procedures/sign_in_magic_link.sql", + "packages/ast-plpgsql/deploy/schemas/ast_plpgsql_helpers/procedures/users/procedures/sign_up_magic_link.sql" + ], + "n_source": 3, + "n_generated": 0 + }, + "INVALID_USER": { + "count": 2, + "dynamic": false, + "sample": "INVALID_USER", + "source_files": [ + "packages/ast-plpgsql/deploy/schemas/ast_plpgsql_helpers/procedures/users/procedures/send_mfa_email_code.sql", + "packages/ast-plpgsql/deploy/schemas/ast_plpgsql_helpers/procedures/users/procedures/send_mfa_sms_code.sql" + ], + "n_source": 2, + "n_generated": 0 + }, + "INVALID_WORKER_ID": { + "count": 4, + "dynamic": false, + "sample": "INVALID_WORKER_ID", + "source_files": [ + "pgpm-modules/database-jobs/deploy/schemas/app_jobs/procedures/get_job.sql", + "pgpm-modules/database-jobs/deploy/schemas/app_jobs/procedures/get_scheduled_job.sql", + "pgpm-modules/jobs/deploy/schemas/app_jobs/procedures/get_job.sql" + ], + "n_source": 4, + "n_generated": 0 + }, + "INVITE_EMAIL_NOT_FOUND": { + "count": 8, + "dynamic": false, + "sample": "INVITE_EMAIL_NOT_FOUND", + "source_files": [ + "packages/ast-plpgsql/deploy/schemas/ast_plpgsql_helpers/procedures/invites/procedures/submit_invite_code.sql", + "packages/ast-plpgsql/deploy/schemas/ast_plpgsql_helpers/procedures/invites/procedures/submit_invite_code_entity.sql" + ], + "n_source": 2, + "n_generated": 2 + }, + "INVITE_LIMIT": { + "count": 6, + "dynamic": false, + "sample": "INVITE_LIMIT", + "source_files": [ + "packages/ast-plpgsql/deploy/schemas/ast_plpgsql_helpers/procedures/invites/procedures/submit_invite_code.sql", + "packages/ast-plpgsql/deploy/schemas/ast_plpgsql_helpers/procedures/invites/procedures/submit_invite_code_entity.sql" + ], + "n_source": 2, + "n_generated": 2 + }, + "INVITE_NOT_FOUND": { + "count": 6, + "dynamic": false, + "sample": "INVITE_NOT_FOUND", + "source_files": [ + "packages/ast-plpgsql/deploy/schemas/ast_plpgsql_helpers/procedures/invites/procedures/submit_invite_code.sql", + "packages/ast-plpgsql/deploy/schemas/ast_plpgsql_helpers/procedures/invites/procedures/submit_invite_code_entity.sql" + ], + "n_source": 2, + "n_generated": 2 + }, + "INVOCATION_QUOTA_GATE": { + "count": 2, + "dynamic": false, + "sample": "INVOCATION_QUOTA_GATE: quota_schema and quota_function are required \u2014 the generator must only build this trigger when billing is provisioned", + "source_files": [ + "packages/ast-plpgsql/deploy/schemas/ast_plpgsql_helpers/procedures/triggers/invocation_quota_gate_trigger.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "INVOCATION_USAGE": { + "count": 2, + "dynamic": false, + "sample": "INVOCATION_USAGE: billing_schema and record_usage_fn are required \u2014 the generator must only build this trigger when billing is provisioned", + "source_files": [ + "packages/ast-plpgsql/deploy/schemas/ast_plpgsql_helpers/procedures/triggers/invocation_usage_trigger.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "LIMIT_ENFORCE_AGGREGATE": { + "count": 6, + "dynamic": true, + "sample": "LIMIT_ENFORCE_AGGREGATE: limits_module with aggregate table not found for database_id % scope \"%\"", + "source_files": [ + "packages/metaschema-generators/deploy/schemas/metaschema_generators/procedures/limit_enforce_aggregate.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "LIMIT_ENFORCE_COUNTER": { + "count": 7, + "dynamic": true, + "sample": "LIMIT_ENFORCE_COUNTER: limits_module not found for database_id % with scope %", + "source_files": [ + "packages/metaschema-generators/deploy/schemas/metaschema_generators/procedures/limit_enforce_counter.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "LIMIT_ENFORCE_FEATURE": { + "count": 6, + "dynamic": true, + "sample": "LIMIT_ENFORCE_FEATURE: cap_check_trigger not configured in limits_module for scope %", + "source_files": [ + "packages/metaschema-generators/deploy/schemas/metaschema_generators/procedures/limit_enforce_feature.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "LIMIT_ENFORCE_RATE": { + "count": 7, + "dynamic": true, + "sample": "LIMIT_ENFORCE_RATE: rate_limit_meters_module with check_rate_limit_function not found for database_id %", + "source_files": [ + "packages/metaschema-generators/deploy/schemas/metaschema_generators/procedures/limit_enforce_rate.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "LIMIT_REACHED": { + "count": 12, + "dynamic": false, + "sample": "LIMIT_REACHED", + "source_files": [ + "packages/ast-plpgsql/deploy/schemas/ast_plpgsql_helpers/procedures/limits/limit_increment_trigger.sql", + "packages/ast-plpgsql/deploy/schemas/ast_plpgsql_helpers/procedures/limits/limit_update_trigger.sql", + "testing/rls-seed/deploy/schemas/rls-pets-limits-private/trigger_fns/app_limits_inc_tg.sql" + ], + "n_source": 4, + "n_generated": 6 + }, + "LIMIT_TRACK_USAGE": { + "count": 6, + "dynamic": true, + "sample": "LIMIT_TRACK_USAGE: billing_module with record_usage_function not found for database_id %", + "source_files": [ + "packages/metaschema-generators/deploy/schemas/metaschema_generators/procedures/limit_track_usage.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "LIMIT_TRIGGER_ARGS": { + "count": 19, + "dynamic": true, + "sample": "LIMIT_TRIGGER_ARGS (%)", + "source_files": [ + "packages/ast-plpgsql/deploy/schemas/ast_plpgsql_helpers/procedures/limits/limit_decrement_trigger.sql", + "packages/ast-plpgsql/deploy/schemas/ast_plpgsql_helpers/procedures/limits/limit_increment_trigger.sql", + "packages/ast-plpgsql/deploy/schemas/ast_plpgsql_helpers/procedures/limits/limit_update_trigger.sql" + ], + "n_source": 6, + "n_generated": 9 + }, + "LIMIT_WARNING_AGGREGATE": { + "count": 6, + "dynamic": true, + "sample": "LIMIT_WARNING_AGGREGATE: aggregate table not provisioned for database_id % scope \"%\"", + "source_files": [ + "packages/metaschema-generators/deploy/schemas/metaschema_generators/procedures/limit_warning_aggregate.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "LIMIT_WARNING_COUNTER": { + "count": 6, + "dynamic": true, + "sample": "LIMIT_WARNING_COUNTER: limit_warnings table not provisioned for database_id % with scope %", + "source_files": [ + "packages/metaschema-generators/deploy/schemas/metaschema_generators/procedures/limit_warning_counter.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "LIMIT_WARNING_RATE": { + "count": 7, + "dynamic": true, + "sample": "LIMIT_WARNING_RATE: limit_warnings table not provisioned for database_id %", + "source_files": [ + "packages/metaschema-generators/deploy/schemas/metaschema_generators/procedures/limit_warning_rate.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "MANAGED_DOMAIN_PUBLISH_FORBIDDEN": { + "count": 1, + "dynamic": false, + "sample": "MANAGED_DOMAIN_PUBLISH_FORBIDDEN: not authorized to modify column(s): allow_public_usage", + "source_files": [], + "n_source": 0, + "n_generated": 1 + }, + "MEMBERSHIP_NOT_FOUND": { + "count": 6, + "dynamic": false, + "sample": "MEMBERSHIP_NOT_FOUND", + "source_files": [ + "packages/ast-plpgsql/deploy/schemas/ast_plpgsql_helpers/procedures/invites/procedures/invite_profile_check.sql", + "packages/ast-plpgsql/deploy/schemas/ast_plpgsql_helpers/procedures/invites/procedures/invite_profile_check_entity.sql" + ], + "n_source": 2, + "n_generated": 2 + }, + "MEMBERSHIP_TYPE_MUST_BE_INT": { + "count": 1, + "dynamic": true, + "sample": "MEMBERSHIP_TYPE_MUST_BE_INT (got %; use entity_type for scope strings)", + "source_files": [ + "packages/metaschema/deploy/schemas/rls_parser/procedures/parse.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "METER_RATE_LIMIT": { + "count": 2, + "dynamic": true, + "sample": "METER_RATE_LIMIT: unknown event %, expected INSERT or UPDATE", + "source_files": [ + "packages/ast-plpgsql/deploy/schemas/ast_plpgsql_helpers/procedures/triggers/meter_rate_limit_trigger.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "MFA_CHALLENGE_EXPIRED": { + "count": 1, + "dynamic": false, + "sample": "MFA_CHALLENGE_EXPIRED", + "source_files": [ + "packages/ast-plpgsql/deploy/schemas/ast_plpgsql_helpers/procedures/users/procedures/complete_mfa_challenge.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "MFA_REQUIRED": { + "count": 2, + "dynamic": false, + "sample": "MFA_REQUIRED", + "source_files": [ + "packages/ast-plpgsql/deploy/schemas/ast_plpgsql_helpers/procedures/users/procedures/one_time_token.sql", + "packages/ast-plpgsql/deploy/schemas/ast_plpgsql_helpers/procedures/users/procedures/request_cross_origin_token.sql" + ], + "n_source": 2, + "n_generated": 0 + }, + "MISSING_FIXTURE_TYPE": { + "count": 1, + "dynamic": false, + "sample": "MISSING_FIXTURE_TYPE", + "source_files": [ + "packages/ast/deploy/schemas/ast_helpers/procedures/fixtures.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "MISSING_REQUIRED_FIELD": { + "count": 1, + "dynamic": true, + "sample": "MISSING_REQUIRED_FIELD: node_type \"%\" requires field \"%\" but it is NULL after applying defaults.", + "source_files": [ + "packages/metaschema/deploy/schemas/metaschema_public/procedures/apply_registry_defaults.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "MISSING_RLS": { + "count": 1, + "dynamic": true, + "sample": "MISSING_RLS: The following infrastructure tables have no RLS policies: %", + "source_files": [ + "packages/introspection/deploy/schemas/introspection/procedures/apply_metaschema_rls.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "MODULES_HASH_DUPLICATE_ENTRY": { + "count": 1, + "dynamic": true, + "sample": "MODULES_HASH_DUPLICATE_ENTRY: duplicate module entries in %", + "source_files": [ + "packages/metaschema/deploy/schemas/metaschema_private/procedures/modules_hash.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "MODULES_HASH_INVALID_ENTRY": { + "count": 3, + "dynamic": true, + "sample": "MODULES_HASH_INVALID_ENTRY: expected string or [name, options?], got %", + "source_files": [ + "packages/metaschema/deploy/schemas/metaschema_private/procedures/modules_hash.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "MODULES_HASH_INVALID_INPUT": { + "count": 1, + "dynamic": true, + "sample": "MODULES_HASH_INVALID_INPUT: modules must be a jsonb array, got %", + "source_files": [ + "packages/metaschema/deploy/schemas/metaschema_private/procedures/modules_hash.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "MODULE_SECURITY": { + "count": 1, + "dynamic": false, + "sample": "MODULE_SECURITY: policy entry missing $type", + "source_files": [ + "packages/metaschema-generators/deploy/schemas/metaschema_generators/procedures/apply_module_security.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "NONEXISTENT_TYPE": { + "count": 1, + "dynamic": false, + "sample": "NONEXISTENT_TYPE", + "source_files": [ + "packages/metaschema/deploy/schemas/metaschema_public/tables/field/triggers/ensure_valid_type.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "NOT_AUTHENTICATED": { + "count": 32, + "dynamic": false, + "sample": "NOT_AUTHENTICATED", + "source_files": [ + "packages/ast-plpgsql/deploy/schemas/ast_plpgsql_helpers/procedures/principals/create_org_principal.sql", + "packages/ast-plpgsql/deploy/schemas/ast_plpgsql_helpers/procedures/principals/create_principal.sql", + "packages/ast-plpgsql/deploy/schemas/ast_plpgsql_helpers/procedures/principals/delete_org_principal.sql" + ], + "n_source": 20, + "n_generated": 12 + }, + "NOT_FOUND": { + "count": 35, + "dynamic": true, + "sample": "NOT_FOUND: metaschema_public.database table not found for database_id=%", + "source_files": [ + "packages/introspection/deploy/schemas/introspection/procedures/apply_metaschema_rls.sql", + "packages/introspection/deploy/schemas/introspection/procedures/provision_base_modules.sql", + "packages/metaschema-generators/deploy/schemas/metaschema_generators/procedures/user_auth_module.sql" + ], + "n_source": 14, + "n_generated": 0 + }, + "NOT_ORG_ADMIN": { + "count": 4, + "dynamic": false, + "sample": "NOT_ORG_ADMIN", + "source_files": [ + "packages/ast-plpgsql/deploy/schemas/ast_plpgsql_helpers/procedures/principals/create_org_principal.sql", + "packages/ast-plpgsql/deploy/schemas/ast_plpgsql_helpers/procedures/principals/delete_org_principal.sql" + ], + "n_source": 2, + "n_generated": 2 + }, + "NOT_ORG_PRINCIPAL": { + "count": 2, + "dynamic": false, + "sample": "NOT_ORG_PRINCIPAL", + "source_files": [ + "packages/ast-plpgsql/deploy/schemas/ast_plpgsql_helpers/procedures/principals/delete_org_principal.sql" + ], + "n_source": 1, + "n_generated": 1 + }, + "NOT_OWNER": { + "count": 2, + "dynamic": false, + "sample": "NOT_OWNER", + "source_files": [ + "packages/ast-plpgsql/deploy/schemas/ast_plpgsql_helpers/procedures/principals/delete_principal.sql" + ], + "n_source": 1, + "n_generated": 1 + }, + "NO_PRIMARY_IDENTIFIER": { + "count": 1, + "dynamic": false, + "sample": "NO_PRIMARY_IDENTIFIER", + "source_files": [ + "packages/metaschema/deploy/schemas/metaschema_private/procedures/bootstrap_owner_into_database.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "NULL_VALUES_DISALLOWED": { + "count": 2, + "dynamic": false, + "sample": "NULL_VALUES_DISALLOWED", + "source_files": [ + "packages/ast-plpgsql/deploy/schemas/ast_plpgsql_helpers/procedures/users/procedures/reset_password.sql" + ], + "n_source": 1, + "n_generated": 1 + }, + "OBJECT_NOT_FOUND": { + "count": 9, + "dynamic": true, + "sample": "OBJECT_NOT_FOUND: schema % not found", + "source_files": [ + "packages/ast-plpgsql/deploy/schemas/ast_plpgsql_helpers/procedures/invites/procedures/submit_invite_code.sql", + "packages/ast-plpgsql/deploy/schemas/ast_plpgsql_helpers/procedures/invites/procedures/submit_invite_code_entity.sql", + "packages/metaschema/deploy/schemas/metaschema_public/tables/composite_type/triggers/composite_type_sql_trigger.sql" + ], + "n_source": 5, + "n_generated": 2 + }, + "OBJECT_NO_UPDATE": { + "count": 1, + "dynamic": false, + "sample": "OBJECT_NO_UPDATE", + "source_files": [ + "packages/metaschema/deploy/schemas/metaschema_public/tables/schema_grant/triggers/schema_grant_sql_trigger.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "ORG_API_KEY_NOT_FOUND": { + "count": 4, + "dynamic": false, + "sample": "ORG_API_KEY_NOT_FOUND", + "source_files": [ + "packages/ast-plpgsql/deploy/schemas/ast_plpgsql_helpers/procedures/users/procedures/revoke_org_api_key.sql" + ], + "n_source": 1, + "n_generated": 1 + }, + "OWNER_FIELD_NOT_FOUND": { + "count": 1, + "dynamic": false, + "sample": "OWNER_FIELD_NOT_FOUND", + "source_files": [ + "packages/metaschema/deploy/schemas/metaschema_public/tables/spatial_relation/triggers/sync_spatial_relation_tags.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "OWNER_FIELD_NOT_IN_OWNER_TABLE": { + "count": 1, + "dynamic": false, + "sample": "OWNER_FIELD_NOT_IN_OWNER_TABLE", + "source_files": [ + "packages/metaschema/deploy/schemas/metaschema_public/tables/spatial_relation/triggers/sync_spatial_relation_tags.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "PARENT_ENTITY_TYPE_NOT_FOUND": { + "count": 2, + "dynamic": false, + "sample": "PARENT_ENTITY_TYPE_NOT_FOUND", + "source_files": [ + "packages/ast-plpgsql/deploy/schemas/ast_plpgsql_helpers/procedures/memberships/get_organization_id.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "PASSWORD_INSECURE": { + "count": 3, + "dynamic": false, + "sample": "PASSWORD_INSECURE", + "source_files": [ + "packages/ast-plpgsql/deploy/schemas/ast_plpgsql_helpers/procedures/users/procedures/password_check.sql" + ], + "n_source": 1, + "n_generated": 1 + }, + "PASSWORD_LEN": { + "count": 6, + "dynamic": false, + "sample": "PASSWORD_LEN", + "source_files": [ + "packages/ast-plpgsql/deploy/schemas/ast_plpgsql_helpers/procedures/users/procedures/password_check.sql" + ], + "n_source": 1, + "n_generated": 1 + }, + "PASSWORD_RESET_LOCKED_EXCEED_ATTEMPTS": { + "count": 1, + "dynamic": false, + "sample": "PASSWORD_RESET_LOCKED_EXCEED_ATTEMPTS", + "source_files": [], + "n_source": 0, + "n_generated": 1 + }, + "PASSWORD_SIGN_IN_DISABLED": { + "count": 1, + "dynamic": false, + "sample": "PASSWORD_SIGN_IN_DISABLED", + "source_files": [], + "n_source": 0, + "n_generated": 1 + }, + "PASSWORD_SIGN_UP_DISABLED": { + "count": 1, + "dynamic": false, + "sample": "PASSWORD_SIGN_UP_DISABLED", + "source_files": [], + "n_source": 0, + "n_generated": 1 + }, + "PLATFORM_DATABASE_NOT_REGISTERED": { + "count": 2, + "dynamic": false, + "sample": "PLATFORM_DATABASE_NOT_REGISTERED: no metaschema_public.database row has platform = true", + "source_files": [ + "packages/metaschema/deploy/schemas/metaschema_private/procedures/platform_database_id.sql", + "pgpm-modules/app-scope/deploy/schemas/app_scope/procedures/platform_database_id.sql" + ], + "n_source": 2, + "n_generated": 0 + }, + "PLATFORM_FLAG_IMMUTABLE": { + "count": 1, + "dynamic": false, + "sample": "PLATFORM_FLAG_IMMUTABLE: metaschema_public.database.platform cannot be changed once set", + "source_files": [ + "packages/metaschema/deploy/schemas/metaschema_public/tables/database/triggers/guard_platform_flag.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "POOL_BOOTSTRAP_INCOMPLETE": { + "count": 1, + "dynamic": true, + "sample": "POOL_BOOTSTRAP_INCOMPLETE: pool row % is missing database_id or claimed_by", + "source_files": [ + "packages/metaschema/deploy/schemas/metaschema_private/procedures/db_pool_bootstrap_owner.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "POOL_CONFIG_NOT_FOUND": { + "count": 1, + "dynamic": true, + "sample": "POOL_CONFIG_NOT_FOUND: no db_pool_config for preset %", + "source_files": [ + "packages/metaschema/deploy/schemas/metaschema_private/procedures/db_pool_warm_database.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "POOL_ROW_NOT_FOUND": { + "count": 1, + "dynamic": true, + "sample": "POOL_ROW_NOT_FOUND: no db_pool row with id %", + "source_files": [ + "packages/metaschema/deploy/schemas/metaschema_private/procedures/db_pool_warm_database.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "PRESET_MODULES_INVALID": { + "count": 1, + "dynamic": true, + "sample": "PRESET_MODULES_INVALID: preset % definition.modules must be a jsonb array", + "source_files": [ + "packages/metaschema/deploy/schemas/metaschema_private/procedures/db_pool_warm_database.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "PRESET_NOT_FOUND": { + "count": 4, + "dynamic": true, + "sample": "PRESET_NOT_FOUND: no active db_preset with slug %", + "source_files": [ + "database-modules/constructive-infra/deploy/schemas/infra_private/procedures/db_preset_head/procedure.sql", + "packages/ast-plpgsql/deploy/schemas/ast_plpgsql_helpers/procedures/db_preset/db_preset_bodies.sql" + ], + "n_source": 2, + "n_generated": 1 + }, + "PRIMARY_AUTH_METHOD_MISMATCH": { + "count": 5, + "dynamic": false, + "sample": "PRIMARY_AUTH_METHOD_MISMATCH", + "source_files": [ + "packages/ast-plpgsql/deploy/schemas/ast_plpgsql_helpers/procedures/statements.sql" + ], + "n_source": 1, + "n_generated": 3 + }, + "PRIMARY_REQUIRES_VERIFIED": { + "count": 9, + "dynamic": false, + "sample": "PRIMARY_REQUIRES_VERIFIED", + "source_files": [ + "packages/ast-plpgsql/deploy/schemas/ast_plpgsql_helpers/procedures/owned/owned_object_is_primary_field.sql" + ], + "n_source": 1, + "n_generated": 6 + }, + "PRINCIPAL_CANNOT_CREATE_API_KEY": { + "count": 4, + "dynamic": false, + "sample": "PRINCIPAL_CANNOT_CREATE_API_KEY", + "source_files": [ + "packages/ast-plpgsql/deploy/schemas/ast_plpgsql_helpers/procedures/users/procedures/create_api_key.sql", + "packages/ast-plpgsql/deploy/schemas/ast_plpgsql_helpers/procedures/users/procedures/create_org_api_key.sql" + ], + "n_source": 2, + "n_generated": 2 + }, + "PRINCIPAL_CANNOT_CREATE_PRINCIPAL": { + "count": 4, + "dynamic": false, + "sample": "PRINCIPAL_CANNOT_CREATE_PRINCIPAL", + "source_files": [ + "packages/ast-plpgsql/deploy/schemas/ast_plpgsql_helpers/procedures/principals/create_org_principal.sql", + "packages/ast-plpgsql/deploy/schemas/ast_plpgsql_helpers/procedures/principals/create_principal.sql" + ], + "n_source": 2, + "n_generated": 2 + }, + "PRINCIPAL_CANNOT_DELETE_PRINCIPAL": { + "count": 4, + "dynamic": false, + "sample": "PRINCIPAL_CANNOT_DELETE_PRINCIPAL", + "source_files": [ + "packages/ast-plpgsql/deploy/schemas/ast_plpgsql_helpers/procedures/principals/delete_org_principal.sql", + "packages/ast-plpgsql/deploy/schemas/ast_plpgsql_helpers/procedures/principals/delete_principal.sql" + ], + "n_source": 2, + "n_generated": 2 + }, + "PRINCIPAL_CANNOT_REVOKE_API_KEY": { + "count": 4, + "dynamic": false, + "sample": "PRINCIPAL_CANNOT_REVOKE_API_KEY", + "source_files": [ + "packages/ast-plpgsql/deploy/schemas/ast_plpgsql_helpers/procedures/users/procedures/revoke_api_key.sql", + "packages/ast-plpgsql/deploy/schemas/ast_plpgsql_helpers/procedures/users/procedures/revoke_org_api_key.sql" + ], + "n_source": 2, + "n_generated": 2 + }, + "PRINCIPAL_NOT_FOUND": { + "count": 6, + "dynamic": false, + "sample": "PRINCIPAL_NOT_FOUND", + "source_files": [ + "packages/ast-plpgsql/deploy/schemas/ast_plpgsql_helpers/procedures/principals/delete_org_principal.sql", + "packages/ast-plpgsql/deploy/schemas/ast_plpgsql_helpers/procedures/principals/delete_principal.sql", + "packages/ast-plpgsql/deploy/schemas/ast_plpgsql_helpers/procedures/users/procedures/create_org_api_key.sql" + ], + "n_source": 3, + "n_generated": 3 + }, + "PRINCIPAL_NOT_IN_ORG": { + "count": 2, + "dynamic": false, + "sample": "PRINCIPAL_NOT_IN_ORG", + "source_files": [ + "packages/ast-plpgsql/deploy/schemas/ast_plpgsql_helpers/procedures/users/procedures/create_org_api_key.sql" + ], + "n_source": 1, + "n_generated": 1 + }, + "PRINCIPAL_NOT_OWNED": { + "count": 2, + "dynamic": false, + "sample": "PRINCIPAL_NOT_OWNED", + "source_files": [ + "packages/ast-plpgsql/deploy/schemas/ast_plpgsql_helpers/procedures/users/procedures/create_api_key.sql" + ], + "n_source": 1, + "n_generated": 1 + }, + "PROFILE_ASSIGNMENT_REQUIRES_EMAIL_INVITE": { + "count": 6, + "dynamic": false, + "sample": "PROFILE_ASSIGNMENT_REQUIRES_EMAIL_INVITE", + "source_files": [ + "packages/ast-plpgsql/deploy/schemas/ast_plpgsql_helpers/procedures/invites/procedures/invite_profile_check.sql", + "packages/ast-plpgsql/deploy/schemas/ast_plpgsql_helpers/procedures/invites/procedures/invite_profile_check_entity.sql" + ], + "n_source": 2, + "n_generated": 2 + }, + "PROFILE_EXCEEDS_PERMISSIONS": { + "count": 5, + "dynamic": false, + "sample": "PROFILE_EXCEEDS_PERMISSIONS", + "source_files": [ + "packages/ast-plpgsql/deploy/schemas/ast_plpgsql_helpers/procedures/invites/procedures/invite_profile_check.sql", + "packages/ast-plpgsql/deploy/schemas/ast_plpgsql_helpers/procedures/invites/procedures/invite_profile_check_entity.sql" + ], + "n_source": 2, + "n_generated": 2 + }, + "PROFILE_NOT_FOUND": { + "count": 6, + "dynamic": false, + "sample": "PROFILE_NOT_FOUND", + "source_files": [ + "packages/ast-plpgsql/deploy/schemas/ast_plpgsql_helpers/procedures/invites/procedures/invite_profile_check.sql", + "packages/ast-plpgsql/deploy/schemas/ast_plpgsql_helpers/procedures/invites/procedures/invite_profile_check_entity.sql" + ], + "n_source": 2, + "n_generated": 2 + }, + "PROVISION_CHECK_CONSTRAINT": { + "count": 9, + "dynamic": true, + "sample": "PROVISION_CHECK_CONSTRAINT: unknown $type \"%\" for constraint \"%\" on table \"%\". Supported: CheckOneOf, CheckGreaterThan, CheckLessThan, CheckNotEqual", + "source_files": [ + "packages/metaschema/deploy/schemas/metaschema_modules_public/procedures/provision_check_constraint/procedure.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "PROVISION_FULL_TEXT_SEARCH": { + "count": 2, + "dynamic": true, + "sample": "PROVISION_FULL_TEXT_SEARCH: tsvector field \"%\" not found on table \"%\"", + "source_files": [ + "packages/metaschema/deploy/schemas/metaschema_modules_public/procedures/provision_full_text_search/procedure.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "PROVISION_INDEX": { + "count": 3, + "dynamic": true, + "sample": "PROVISION_INDEX: \"columns\" array or \"column\" string is required", + "source_files": [ + "packages/metaschema/deploy/schemas/metaschema_modules_public/procedures/provision_index/procedure.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "PROVISION_INVALID_DATABASE_ID": { + "count": 1, + "dynamic": false, + "sample": "PROVISION_INVALID_DATABASE_ID: database_id may only be pre-set for a warm pool claim owned by the requester", + "source_files": [ + "packages/metaschema/deploy/schemas/metaschema_modules_public/tables/database_provision_module/triggers/insert_database_provision_module.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "PROVISION_RELATION": { + "count": 9, + "dynamic": true, + "sample": "PROVISION_RELATION: delete_action is required for RelationBelongsTo (e.g., r=RESTRICT, c=CASCADE, n=SET NULL)", + "source_files": [ + "packages/metaschema/deploy/schemas/metaschema_modules_public/procedures/provision_relation/procedure.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "PROVISION_TABLE": { + "count": 6, + "dynamic": true, + "sample": "PROVISION_TABLE: invalid index access method \"%\" for field \"%\", expected btree|gin|gist|brin|hash", + "source_files": [ + "packages/metaschema/deploy/schemas/metaschema_modules_public/procedures/provision_table/procedure.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "PROVISION_UNIQUE_CONSTRAINT": { + "count": 2, + "dynamic": true, + "sample": "PROVISION_UNIQUE_CONSTRAINT: \"columns\" array is required for table \"%\"", + "source_files": [ + "packages/metaschema/deploy/schemas/metaschema_modules_public/procedures/provision_unique_constraint/procedure.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "RATE_LIMIT_EXCEEDED": { + "count": 4, + "dynamic": false, + "sample": "RATE_LIMIT_EXCEEDED", + "source_files": [ + "packages/ast-plpgsql/deploy/schemas/ast_plpgsql_helpers/procedures/triggers/meter_rate_limit_trigger.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "REF_NOT_FOUND": { + "count": 3, + "dynamic": false, + "sample": "REF_NOT_FOUND", + "source_files": [ + "packages/db-migrate/deploy/schemas/db_migrate/tables/sql_actions/triggers/update_tree.sql", + "pgpm-modules/object-tree/deploy/schemas/object_tree_public/procedures/set_and_commit.sql" + ], + "n_source": 2, + "n_generated": 0 + }, + "RELATION_PROVISION": { + "count": 9, + "dynamic": true, + "sample": "RELATION_PROVISION: delete_action is required for RelationBelongsTo (e.g., r=RESTRICT, c=CASCADE, n=SET NULL)", + "source_files": [ + "packages/metaschema/deploy/schemas/metaschema_modules_public/tables/relation_provision/triggers/insert_relation_provision.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "REPO_EXISTS": { + "count": 10, + "dynamic": false, + "sample": "REPO_EXISTS", + "source_files": [ + "database-modules/constructive-compute/deploy/schemas/compute_public/procedures/infra_init_empty_repo/procedure.sql", + "packages/ast-plpgsql/deploy/schemas/ast_plpgsql_helpers/procedures/merkle/merkle_store_bodies.sql", + "pgpm-modules/object-tree/deploy/schemas/object_tree_public/procedures/init_empty_repo.sql" + ], + "n_source": 5, + "n_generated": 4 + }, + "REQUEST_DATABASE_INVALID_INPUT": { + "count": 1, + "dynamic": false, + "sample": "REQUEST_DATABASE_INVALID_INPUT: provide exactly one of preset_slug or modules", + "source_files": [ + "packages/metaschema/deploy/schemas/metaschema_public/procedures/request_database.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "REQUEST_DATABASE_UNKNOWN_PRESET": { + "count": 1, + "dynamic": true, + "sample": "REQUEST_DATABASE_UNKNOWN_PRESET: no active preset with slug %", + "source_files": [ + "packages/metaschema/deploy/schemas/metaschema_public/procedures/request_database.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "REQUIRES": { + "count": 62, + "dynamic": true, + "sample": "REQUIRES (user_settings_module or user_settings_table_id) when has_settings_extension is enabled", + "source_files": [ + "packages/metaschema-generators/deploy/schemas/metaschema_generators/procedures/identity_providers_module.sql", + "packages/metaschema-generators/deploy/schemas/metaschema_generators/procedures/memberships_module.sql", + "packages/metaschema-generators/deploy/schemas/metaschema_generators/procedures/user_auth_module.sql" + ], + "n_source": 30, + "n_generated": 0 + }, + "REQUIRES_ONE_OWNER": { + "count": 7, + "dynamic": false, + "sample": "REQUIRES_ONE_OWNER", + "source_files": [ + "packages/ast-plpgsql/deploy/schemas/ast_plpgsql_helpers/procedures/permissions/grants/grants.sql", + "packages/ast-plpgsql/deploy/schemas/ast_plpgsql_helpers/procedures/permissions/memberships/memberships.sql", + "testing/rls-seed/deploy/schemas/rls-pets-memberships-private/trigger_fns/app_owner_grants_apply_tg.sql" + ], + "n_source": 3, + "n_generated": 3 + }, + "RESOLVE_BLUEPRINT_FIELD": { + "count": 1, + "dynamic": true, + "sample": "RESOLVE_BLUEPRINT_FIELD: field \"%\" not found on table % (database %)", + "source_files": [ + "packages/metaschema/deploy/schemas/metaschema_modules_public/procedures/resolve_blueprint_field/procedure.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "RESOLVE_BLUEPRINT_TABLE": { + "count": 4, + "dynamic": true, + "sample": "RESOLVE_BLUEPRINT_TABLE: table \"%\" not found in database % (not in blueprint tables and not in existing tables)", + "source_files": [ + "packages/metaschema/deploy/schemas/metaschema_modules_public/procedures/resolve_blueprint_table/procedure.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "RESOURCE_MODULE": { + "count": 1, + "dynamic": true, + "sample": "RESOURCE_MODULE: merkle_store_module % not found", + "source_files": [ + "packages/metaschema-generators/deploy/schemas/metaschema_generators/procedures/resource_module.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "ROUTE_BINDING_SYNC": { + "count": 1, + "dynamic": true, + "sample": "ROUTE_BINDING_SYNC: unknown event %, expected UPSERT or DELETE", + "source_files": [ + "packages/ast-plpgsql/deploy/schemas/ast_plpgsql_helpers/procedures/triggers/routing_bindings_trigger.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "ROUTE_TARGET_NOT_VISIBLE": { + "count": 6, + "dynamic": false, + "sample": "ROUTE_TARGET_NOT_VISIBLE", + "source_files": [ + "database-modules/constructive-routing-triggers/deploy/schemas/routing_private/trigger_fns/tg_routes_target_guard.sql" + ], + "n_source": 1, + "n_generated": 1 + }, + "ROUTE_TARGET_REQUIRED": { + "count": 2, + "dynamic": false, + "sample": "ROUTE_TARGET_REQUIRED", + "source_files": [ + "database-modules/constructive-routing-triggers/deploy/schemas/routing_private/trigger_fns/tg_routes_target_guard.sql" + ], + "n_source": 1, + "n_generated": 1 + }, + "SEARCH_BM25": { + "count": 3, + "dynamic": true, + "sample": "SEARCH_BM25: field \"%\" not found on table \"%\". The field must already exist.", + "source_files": [ + "packages/metaschema-generators/deploy/schemas/metaschema_generators/procedures/search_bm25.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "SEARCH_FULL_TEXT": { + "count": 4, + "dynamic": true, + "sample": "SEARCH_FULL_TEXT: source_fields is required and must be a non-empty array of {field, weight, lang} objects", + "source_files": [ + "packages/metaschema-generators/deploy/schemas/metaschema_generators/procedures/search_full_text.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "SEARCH_SPATIAL": { + "count": 4, + "dynamic": true, + "sample": "SEARCH_SPATIAL: unknown geometry_type %, expected Point|LineString|Polygon|MultiPoint|MultiLineString|MultiPolygon|GeometryCollection|Geometry", + "source_files": [ + "packages/metaschema-generators/deploy/schemas/metaschema_generators/procedures/search_spatial.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "SEARCH_SPATIAL_AGGREGATE": { + "count": 11, + "dynamic": true, + "sample": "SEARCH_SPATIAL_AGGREGATE: unknown geometry_type %, expected Point|LineString|Polygon|MultiPoint|MultiLineString|MultiPolygon|GeometryCollection|Geometry", + "source_files": [ + "packages/metaschema-generators/deploy/schemas/metaschema_generators/procedures/search_spatial_aggregate.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "SEARCH_TRGM": { + "count": 4, + "dynamic": true, + "sample": "SEARCH_TRGM: \"fields\" parameter is required and must be a JSON array", + "source_files": [ + "packages/metaschema-generators/deploy/schemas/metaschema_generators/procedures/search_trgm.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "SEARCH_UNIFIED": { + "count": 2, + "dynamic": true, + "sample": "SEARCH_UNIFIED: trgm field \"%\" not found on table \"%\"", + "source_files": [ + "packages/metaschema-generators/deploy/schemas/metaschema_generators/procedures/search_unified.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "SEARCH_VECTOR": { + "count": 4, + "dynamic": true, + "sample": "SEARCH_VECTOR: unknown index_method %, expected hnsw|ivfflat", + "source_files": [ + "packages/metaschema-generators/deploy/schemas/metaschema_generators/procedures/search_vector.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "SECURITY": { + "count": 2, + "dynamic": true, + "sample": "SECURITY: obj_table_id references a table from a different database (expected %, got %)", + "source_files": [ + "packages/metaschema/deploy/schemas/rls_parser/procedures/parse.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "SERVICES_API_BRIDGE": { + "count": 1, + "dynamic": true, + "sample": "SERVICES_API_BRIDGE: unknown event %, expected UPSERT or DELETE", + "source_files": [ + "packages/ast-plpgsql/deploy/schemas/ast_plpgsql_helpers/procedures/triggers/services_bridge_trigger.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "SERVICES_DOMAIN_BRIDGE": { + "count": 1, + "dynamic": true, + "sample": "SERVICES_DOMAIN_BRIDGE: unknown event %, expected UPSERT or DELETE", + "source_files": [ + "packages/ast-plpgsql/deploy/schemas/ast_plpgsql_helpers/procedures/triggers/services_bridge_trigger.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "SERVICES_SITE_BRIDGE": { + "count": 1, + "dynamic": true, + "sample": "SERVICES_SITE_BRIDGE: unknown event %, expected UPSERT or DELETE", + "source_files": [ + "packages/ast-plpgsql/deploy/schemas/ast_plpgsql_helpers/procedures/triggers/services_bridge_trigger.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "SESSION_NOT_FOUND": { + "count": 1, + "dynamic": false, + "sample": "SESSION_NOT_FOUND", + "source_files": [], + "n_source": 0, + "n_generated": 1 + }, + "SIGN_UP_DISABLED": { + "count": 2, + "dynamic": false, + "sample": "SIGN_UP_DISABLED", + "source_files": [], + "n_source": 0, + "n_generated": 2 + }, + "SINGLETON_TABLE": { + "count": 1, + "dynamic": false, + "sample": "SINGLETON_TABLE", + "source_files": [ + "pgpm-modules/utils/deploy/schemas/utils/procedures/ensure_singleton.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "SMS_SIGN_IN_DISABLED": { + "count": 2, + "dynamic": false, + "sample": "SMS_SIGN_IN_DISABLED", + "source_files": [], + "n_source": 0, + "n_generated": 2 + }, + "SMS_SIGN_UP_DISABLED": { + "count": 1, + "dynamic": false, + "sample": "SMS_SIGN_UP_DISABLED", + "source_files": [], + "n_source": 0, + "n_generated": 1 + }, + "SOURCE_EMAILS_NOT_FOUND": { + "count": 1, + "dynamic": false, + "sample": "SOURCE_EMAILS_NOT_FOUND", + "source_files": [ + "packages/metaschema/deploy/schemas/metaschema_private/procedures/bootstrap_owner_into_database.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "SOURCE_SECRETS_NOT_FOUND": { + "count": 1, + "dynamic": false, + "sample": "SOURCE_SECRETS_NOT_FOUND", + "source_files": [ + "packages/metaschema/deploy/schemas/metaschema_private/procedures/bootstrap_owner_into_database.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "SOURCE_USERS_NOT_FOUND": { + "count": 1, + "dynamic": false, + "sample": "SOURCE_USERS_NOT_FOUND", + "source_files": [ + "packages/metaschema/deploy/schemas/metaschema_private/procedures/bootstrap_owner_into_database.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "STEP_UP_REQUIRED": { + "count": 6, + "dynamic": false, + "sample": "STEP_UP_REQUIRED", + "source_files": [ + "packages/ast-plpgsql/deploy/schemas/ast_plpgsql_helpers/procedures/statements.sql" + ], + "n_source": 1, + "n_generated": 3 + }, + "STEP_UP_REQUIRED_PASSWORD": { + "count": 2, + "dynamic": false, + "sample": "STEP_UP_REQUIRED_PASSWORD", + "source_files": [ + "packages/ast-plpgsql/deploy/schemas/ast_plpgsql_helpers/procedures/users/procedures/require_step_up.sql" + ], + "n_source": 1, + "n_generated": 1 + }, + "STEP_UP_REQUIRED_PASSWORD_OR_MFA": { + "count": 2, + "dynamic": false, + "sample": "STEP_UP_REQUIRED_PASSWORD_OR_MFA", + "source_files": [ + "packages/ast-plpgsql/deploy/schemas/ast_plpgsql_helpers/procedures/users/procedures/require_step_up.sql" + ], + "n_source": 1, + "n_generated": 1 + }, + "SUPER_CONSTRUCTIVE_REQUIRED": { + "count": 1, + "dynamic": true, + "sample": "SUPER_CONSTRUCTIVE_REQUIRED: % requires constructive.allow_super_constructive GUC", + "source_files": [ + "packages/metaschema/deploy/schemas/metaschema_private/procedures/ensure_super_constructive.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "TABLE_MODULE": { + "count": 2, + "dynamic": true, + "sample": "TABLE_MODULE: table not found for table_id %", + "source_files": [ + "packages/metaschema-generators/deploy/schemas/metaschema_generators/procedures/table_module.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "TARGET_EMAILS_NOT_FOUND": { + "count": 1, + "dynamic": false, + "sample": "TARGET_EMAILS_NOT_FOUND", + "source_files": [ + "packages/metaschema/deploy/schemas/metaschema_private/procedures/bootstrap_owner_into_database.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "TARGET_FIELD_NOT_FOUND": { + "count": 1, + "dynamic": false, + "sample": "TARGET_FIELD_NOT_FOUND", + "source_files": [ + "packages/metaschema/deploy/schemas/metaschema_public/tables/spatial_relation/triggers/sync_spatial_relation_tags.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "TARGET_FIELD_NOT_IN_TARGET_TABLE": { + "count": 1, + "dynamic": false, + "sample": "TARGET_FIELD_NOT_IN_TARGET_TABLE", + "source_files": [ + "packages/metaschema/deploy/schemas/metaschema_public/tables/spatial_relation/triggers/sync_spatial_relation_tags.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "TARGET_MEMBERSHIPS_NOT_FOUND": { + "count": 1, + "dynamic": false, + "sample": "TARGET_MEMBERSHIPS_NOT_FOUND", + "source_files": [ + "packages/metaschema/deploy/schemas/metaschema_private/procedures/bootstrap_owner_into_database.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "TARGET_SECRETS_NOT_FOUND": { + "count": 1, + "dynamic": false, + "sample": "TARGET_SECRETS_NOT_FOUND", + "source_files": [ + "packages/metaschema/deploy/schemas/metaschema_private/procedures/bootstrap_owner_into_database.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "TARGET_USERS_NOT_FOUND": { + "count": 1, + "dynamic": false, + "sample": "TARGET_USERS_NOT_FOUND", + "source_files": [ + "packages/metaschema/deploy/schemas/metaschema_private/procedures/bootstrap_owner_into_database.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "THROWN_ERROR": { + "count": 1, + "dynamic": true, + "sample": "THROWN_ERROR (%)", + "source_files": [ + "pgpm-modules/utils/deploy/schemas/utils/procedures/throw.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "TOO_MANY_REQUESTS": { + "count": 21, + "dynamic": false, + "sample": "TOO_MANY_REQUESTS", + "source_files": [ + "packages/ast-plpgsql/deploy/schemas/ast_plpgsql_helpers/procedures/statements.sql" + ], + "n_source": 1, + "n_generated": 18 + }, + "TOTP_ALREADY_ENABLED": { + "count": 1, + "dynamic": false, + "sample": "TOTP_ALREADY_ENABLED", + "source_files": [ + "packages/ast-plpgsql/deploy/schemas/ast_plpgsql_helpers/procedures/users/procedures/enable_totp.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "TOTP_NOT_ENABLED": { + "count": 3, + "dynamic": false, + "sample": "TOTP_NOT_ENABLED", + "source_files": [ + "packages/ast-plpgsql/deploy/schemas/ast_plpgsql_helpers/procedures/users/procedures/disable_totp.sql", + "packages/ast-plpgsql/deploy/schemas/ast_plpgsql_helpers/procedures/users/procedures/verify_totp.sql" + ], + "n_source": 2, + "n_generated": 1 + }, + "TOTP_SETUP_NOT_INITIATED": { + "count": 1, + "dynamic": false, + "sample": "TOTP_SETUP_NOT_INITIATED", + "source_files": [ + "packages/ast-plpgsql/deploy/schemas/ast_plpgsql_helpers/procedures/users/procedures/confirm_totp_setup.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "TRANSFER_NOT_ALLOWED_IN_POOLED_MODE": { + "count": 2, + "dynamic": false, + "sample": "TRANSFER_NOT_ALLOWED_IN_POOLED_MODE", + "source_files": [ + "packages/ast-plpgsql/deploy/schemas/ast_plpgsql_helpers/procedures/limits/limit_transfer_function.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "UNAUTHENTICATED": { + "count": 2, + "dynamic": false, + "sample": "UNAUTHENTICATED: request_database requires a signed-in user", + "source_files": [ + "packages/metaschema/deploy/schemas/metaschema_private/procedures/db_pool_claim.sql", + "packages/metaschema/deploy/schemas/metaschema_public/procedures/request_database.sql" + ], + "n_source": 2, + "n_generated": 0 + }, + "UNKNOWN_POLICY_TYPE": { + "count": 1, + "dynamic": true, + "sample": "UNKNOWN_POLICY_TYPE %", + "source_files": [ + "packages/metaschema/deploy/schemas/rls_parser/procedures/parse.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "UNKNOWN_VIEW_TYPE": { + "count": 2, + "dynamic": true, + "sample": "UNKNOWN_VIEW_TYPE %", + "source_files": [ + "packages/ast/deploy/schemas/ast_helpers/procedures/view_ast_builders.sql", + "packages/metaschema/deploy/schemas/view_parser/procedures/parse.sql" + ], + "n_source": 2, + "n_generated": 0 + }, + "UNSUPPORTED": { + "count": 1, + "dynamic": true, + "sample": "UNSUPPORTED POLICY (%)", + "source_files": [ + "packages/ast/deploy/schemas/ast_helpers/procedures/policy_ast_builders.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "UNSUPPORTED_EXPRESSION": { + "count": 1, + "dynamic": true, + "sample": "UNSUPPORTED_EXPRESSION %", + "source_files": [ + "packages/ast/deploy/schemas/deparser/procedures/deparse.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "UNSUPPORTED_VIEW_RULE_ACTION": { + "count": 1, + "dynamic": true, + "sample": "UNSUPPORTED_VIEW_RULE_ACTION %", + "source_files": [ + "packages/metaschema/deploy/schemas/metaschema_public/tables/view_rule/triggers/view_rule_sql_trigger.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "UUID": { + "count": 1, + "dynamic": true, + "sample": "UUID seed is NULL on table %", + "source_files": [ + "pgpm-modules/uuid/deploy/schemas/uuids/procedures/trigger_set_uuid_related_field.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "VALIDATE_BLUEPRINT": { + "count": 179, + "dynamic": true, + "sample": "VALIDATE_BLUEPRINT: definition must contain a non-empty \"tables\" array, a non-empty \"entity_types\" array, or at least one of relations/indexes/full_text_searches/unique_constraints", + "source_files": [ + "packages/metaschema/deploy/schemas/metaschema_modules_public/procedures/validate_blueprint_definition/procedure.sql" + ], + "n_source": 1, + "n_generated": 0 + }, + "WEBAUTHN_CREDENTIAL_NOT_FOUND": { + "count": 2, + "dynamic": false, + "sample": "WEBAUTHN_CREDENTIAL_NOT_FOUND", + "source_files": [ + "packages/ast-plpgsql/deploy/schemas/ast_plpgsql_helpers/procedures/webauthn_auth/finish_sign_in.sql" + ], + "n_source": 1, + "n_generated": 1 + }, + "WEBAUTHN_REGISTER_CHALLENGE_NOT_FOUND_OR_EXPIRED": { + "count": 3, + "dynamic": false, + "sample": "WEBAUTHN_REGISTER_CHALLENGE_NOT_FOUND_OR_EXPIRED", + "source_files": [ + "packages/ast-plpgsql/deploy/schemas/ast_plpgsql_helpers/procedures/webauthn_auth/finish_registration.sql" + ], + "n_source": 1, + "n_generated": 1 + }, + "WEBAUTHN_SIGN_IN_CHALLENGE_NOT_FOUND_OR_EXPIRED": { + "count": 2, + "dynamic": false, + "sample": "WEBAUTHN_SIGN_IN_CHALLENGE_NOT_FOUND_OR_EXPIRED", + "source_files": [ + "packages/ast-plpgsql/deploy/schemas/ast_plpgsql_helpers/procedures/webauthn_auth/finish_sign_in.sql" + ], + "n_source": 1, + "n_generated": 1 + }, + "WEBAUTHN_SIGN_IN_DISABLED": { + "count": 2, + "dynamic": false, + "sample": "WEBAUTHN_SIGN_IN_DISABLED", + "source_files": [], + "n_source": 0, + "n_generated": 2 + }, + "WEBAUTHN_SIGN_UP_DISABLED": { + "count": 2, + "dynamic": false, + "sample": "WEBAUTHN_SIGN_UP_DISABLED", + "source_files": [], + "n_source": 0, + "n_generated": 2 + }, + "WITH_CHECK_NOT_ALLOWED_FOR_": { + "count": 2, + "dynamic": true, + "sample": "WITH_CHECK_NOT_ALLOWED_FOR_% (for INSERT, the policy node itself is the check)", + "source_files": [ + "packages/metaschema/deploy/schemas/metaschema_public/tables/policy/triggers/policy_sql_trigger.sql" + ], + "n_source": 1, + "n_generated": 0 + } +} \ No newline at end of file diff --git a/packages/errors/scripts/generate-registry.py b/packages/errors/scripts/generate-registry.py new file mode 100644 index 0000000000..8897a26895 --- /dev/null +++ b/packages/errors/scripts/generate-registry.py @@ -0,0 +1,276 @@ +#!/usr/bin/env python3 +"""Generate `src/generated/registry.generated.ts` from the constructive-db error +audit. + +This is the reproducible source of truth for the full error registry. It reads +the committed audit inventory (`scripts/db-error-inventory.json`) — a snapshot of +every code raised via EXCEPTION/THROW across constructive-db deploy sources and +generated output — and, when available, seeds user-facing copy from the dashboard +error catalogs. + +Usage: + python3 scripts/generate-registry.py + +Environment: + DASHBOARD_DIR Optional path to the constructive-io/dashboard checkout used + to seed public copy. Defaults to ../../../dashboard, then + ~/repos/dashboard. If not found, humanized copy is used. + +Regenerate whenever the inventory changes (re-run the constructive-db audit and +overwrite scripts/db-error-inventory.json first). +""" +import glob +import json +import os +import re + +HERE = os.path.dirname(os.path.abspath(__file__)) +PKG = os.path.dirname(HERE) +INVENTORY = os.path.join(HERE, 'db-error-inventory.json') +OUT = os.path.join(PKG, 'src', 'generated', 'registry.generated.ts') + + +def find_dashboard(): + candidates = [ + os.environ.get('DASHBOARD_DIR'), + os.path.join(PKG, '..', '..', '..', 'dashboard'), + os.path.expanduser('~/repos/dashboard'), + ] + for c in candidates: + if c and os.path.isdir(c): + return os.path.abspath(c) + return None + + +# --------------------------------------------------------------------------- +# 1. Load the authoritative inventory. +# --------------------------------------------------------------------------- +inv = json.load(open(INVENTORY)) + +# --------------------------------------------------------------------------- +# 2. Collect user-facing copy from the dashboard (auth-errors + block catalogs). +# --------------------------------------------------------------------------- +copy = {} # CODE -> message + + +def add_copy(code, msg): + if not msg: + return + if code not in copy or len(msg) > len(copy[code]): + copy[code] = msg + + +pair_re = re.compile(r"\b([A-Z][A-Z0-9_]+)\s*:\s*(['\"])((?:\\.|(?!\2).)*)\2") + +dashboard = find_dashboard() +if dashboard: + globs = ( + glob.glob(f'{dashboard}/**/messages.ts', recursive=True) + + glob.glob(f'{dashboard}/**/auth-errors.ts', recursive=True) + ) + for path in globs: + try: + text = open(path).read() + except OSError: + continue + for m in pair_re.finditer(text): + code, _q, msg = m.group(1), m.group(2), m.group(3) + if msg == code: + continue + msg = msg.replace("\\'", "'").replace('\\"', '"') + add_copy(code, msg) + +# --------------------------------------------------------------------------- +# 3. Classification: public (user-facing, localizable) vs internal (invariants). +# --------------------------------------------------------------------------- +PUBLIC_EXPLICIT = { + 'GRAPHQL_VALIDATION_FAILED', 'GRAPHQL_PARSE_FAILED', 'PERSISTED_QUERY_NOT_FOUND', + 'PERSISTED_QUERY_NOT_SUPPORTED', 'UNAUTHENTICATED', 'NOT_AUTHENTICATED', + 'USER_NOT_AUTHENTICATED', 'FORBIDDEN', 'BAD_USER_INPUT', 'INCORRECT_PASSWORD', + 'PASSWORD_INSECURE', 'ACCOUNT_LOCKED', 'ACCOUNT_LOCKED_EXCEED_ATTEMPTS', + 'ACCOUNT_DISABLED', 'ACCOUNT_EXISTS', 'ACCOUNT_NOT_FOUND', 'USER_NOT_FOUND', + 'INVALID_USER', 'INVALID_TOKEN', 'INVALID_CODE', 'NO_PRIMARY_EMAIL', 'NO_CREDENTIALS', + 'PASSWORD_LEN', 'INVITE_NOT_FOUND', 'INVITE_LIMIT', 'INVITE_EMAIL_NOT_FOUND', + 'EMAIL_NOT_VERIFIED', 'PROFILE_ASSIGNMENT_REQUIRES_EMAIL_INVITE', + 'ASSIGN_PROFILES_PERMISSION_REQUIRED', 'PROFILE_NOT_FOUND', 'PROFILE_EXCEEDS_PERMISSIONS', + 'MEMBERSHIP_NOT_FOUND', 'INVALID_CREDENTIALS', 'SIGN_UP_DISABLED', + 'PASSWORD_SIGN_IN_DISABLED', 'PASSWORD_SIGN_UP_DISABLED', 'SSO_SIGN_IN_DISABLED', + 'SSO_SIGN_UP_DISABLED', 'SSO_ACCOUNT_NOT_FOUND', 'CONNECTED_ACCOUNT_NOT_FOUND', + 'MAGIC_LINK_SIGN_IN_DISABLED', 'MAGIC_LINK_SIGN_UP_DISABLED', 'EMAIL_OTP_SIGN_IN_DISABLED', + 'SMS_SIGN_IN_DISABLED', 'SMS_SIGN_UP_DISABLED', 'CSRF_TOKEN_REQUIRED', 'INVALID_CSRF_TOKEN', + 'TOO_MANY_REQUESTS', 'PASSWORD_RESET_LOCKED_EXCEED_ATTEMPTS', 'TOTP_NOT_ENABLED', + 'TOTP_ALREADY_ENABLED', 'TOTP_SETUP_NOT_INITIATED', 'MFA_REQUIRED', 'MFA_CHALLENGE_EXPIRED', + 'INVALID_MFA_CHALLENGE', 'STEP_UP_REQUIRED', 'STEP_UP_REQUIRED_PASSWORD', + 'STEP_UP_REQUIRED_PASSWORD_OR_MFA', 'SESSION_NOT_FOUND', 'API_KEY_NOT_FOUND', + 'CANNOT_DISCONNECT_LAST_AUTH_METHOD', 'CANNOT_REVOKE_CURRENT_SESSION', 'NOT_FOUND', + 'NULL_VALUES_DISALLOWED', 'OBJECT_NOT_FOUND', 'OBJECT_NO_UPDATE', 'LIMIT_REACHED', + 'REQUIRES_ONE_OWNER', 'DELETE_FIRST', 'REF_NOT_FOUND', 'CROSS_DATABASE_REF', + 'GROUPS_REQ_ENTITIES', 'ALREADY_SCHEDULED', 'SINGLETON_TABLE', 'IMMUTABLE_FIELD', + 'IMMUTABLE_PROPS', 'IMMUTABLE_PEOPLESTAMPS', 'IMMUTABLE_TIMESTAMPS', + 'CONST_TYPE_FIELDS_IMMUTABLE', 'FEATURE_DISABLED', 'INVALID_PUBLIC_KEY', 'INVALID_MESSAGE', + 'INVALID_SIGNATURE', 'NO_ACCOUNT_EXISTS', 'BAD_SIGNIN', 'UPLOAD_MIMETYPE', + 'API_KEYS_DISABLED', 'API_KEY_LIMIT_REACHED', 'IDENTITY_PROVIDER_NOT_FOUND', + 'IDENTITY_SIGN_IN_DISABLED', 'IDENTITY_SIGN_UP_DISABLED', 'MANAGED_DOMAIN_PUBLISH_FORBIDDEN', + 'WEBAUTHN_SIGN_IN_DISABLED', 'WEBAUTHN_SIGN_UP_DISABLED', 'PRIMARY_AUTH_METHOD_MISMATCH', + 'AUTH_METHOD_NOT_ALLOWED', 'EXTERNAL_MEMBERS_NOT_ALLOWED', 'RATE_LIMIT_EXCEEDED', + 'TRANSFER_EXPIRED', 'EMAIL_OTP_SIGN_UP_DISABLED', 'SMS_OTP_SIGN_IN_DISABLED', + 'WEBAUTHN_NOT_ENABLED', 'INVITE_EXPIRED', 'INVITE_ALREADY_USED', +} + +PUBLIC_PREFIXES = ( + 'ACCOUNT_', 'PASSWORD_', 'INVITE_', 'MFA_', 'TOTP_', 'STEP_UP_', 'SSO_', 'SMS_', + 'EMAIL_OTP_', 'MAGIC_LINK_', 'WEBAUTHN_', 'IDENTITY_', 'SESSION_', 'API_KEY_', + 'RATE_', 'CSRF_', 'PROFILE_', 'MEMBERSHIP_', 'AUTH_METHOD_', 'SIGN_UP_', 'SIGN_IN_', +) +INTERNAL_PREFIXES = ( + 'DATA_', 'APPLY_', 'BUILD_', 'CONSTRUCT_', 'CATALOG', 'ALTER_TABLE', 'DOMAIN_', + 'PROVISION', 'SEARCH_', 'AST_', 'DEPARSE', 'RLS_', 'AUTHZ_', 'SPRT', 'META_', + 'INTROSPECT', 'CODEGEN', 'GENERATOR', 'SCHEMA_', 'TRIGGER_', 'POLICY_', 'GRANT_', + 'SEED_', 'SNAPSHOT', 'NODE_', 'EXPR', 'STMT', 'RELATION_', 'FIELD_', 'TABLE_', +) +# Developer invariants a domain prefix would otherwise mark public — force internal. +INTERNAL_EXPLICIT = { + 'MEMBERSHIP_TYPE_MUST_BE_INT', +} + + +def classify(code): + if code in INTERNAL_EXPLICIT: + return 'internal' + if code in PUBLIC_EXPLICIT or code in copy: + return 'public' + if code.startswith(INTERNAL_PREFIXES): + return 'internal' + if code.startswith(PUBLIC_PREFIXES): + return 'public' + return 'internal' + + +# --------------------------------------------------------------------------- +# 4. HTTP status heuristic. +# --------------------------------------------------------------------------- +def http_for(code, klass): + if klass == 'internal': + return 500 + if code.endswith('_NOT_FOUND') or code == 'NOT_FOUND': + return 404 + if code.endswith('_EXISTS') or 'ALREADY' in code: + return 409 + if 'LIMIT' in code or 'RATE' in code or 'TOO_MANY' in code: + return 429 + if 'LOCKED' in code: + return 423 + if any(k in code for k in ('FORBIDDEN', 'DISABLED', 'NOT_ALLOWED', 'PERMISSION', + 'STEP_UP', 'MFA', 'REQUIRES', 'IMMUTABLE')): + return 403 + if any(k in code for k in ('UNAUTHENTICATED', 'NOT_AUTHENTICATED', 'CREDENTIALS', + 'INCORRECT_PASSWORD', 'INVALID_TOKEN', 'INVALID_CODE', + 'INVALID_SIGNATURE', 'SIGNIN', 'CSRF')): + return 401 + return 400 + + +# --------------------------------------------------------------------------- +# 5. Message: dashboard copy > humanized (public) > developer sample (internal). +# --------------------------------------------------------------------------- +def humanize(code): + words = code.replace('_', ' ').lower().strip() + return words[:1].upper() + words[1:] + '.' if words else code + + +def message_for(code, klass, sample): + if code in copy: + return copy[code] + if klass == 'public': + return humanize(code) + if sample and sample != code: + n = [0] + + def repl(_m): + i = n[0] + n[0] += 1 + return f'{{{{arg{i}}}}}' + + return re.sub(r'%', repl, sample) + return humanize(code) + + +def ts_str(s): + return "'" + s.replace('\\', '\\\\').replace("'", "\\'") + "'" + + +# --------------------------------------------------------------------------- +# 6. Emit the generated module. +# --------------------------------------------------------------------------- +codes = sorted(inv.keys()) +entries = [] +meta = [] +n_public = 0 +for code in codes: + info = inv[code] + sample = info.get('sample', code) + dynamic = bool(info.get('dynamic')) + generated_only = info.get('n_source', 0) == 0 + klass = classify(code) + if klass == 'public': + n_public += 1 + http = http_for(code, klass) + msg = message_for(code, klass, sample) + nargs = sample.count('%') if (dynamic and sample) else 0 + pos = '' + if nargs > 0: + pos_list = ', '.join(ts_str(f'arg{i}') for i in range(nargs)) + pos = f', positional: [{pos_list}]' + entries.append( + f" '{code}': defineError({{ code: '{code}', class: '{klass}', " + f"http: {http}, message: {ts_str(msg)}{pos} }})," + ) + meta.append( + f" '{code}': {{ class: '{klass}', dynamic: {str(dynamic).lower()}, " + f"generatedOnly: {str(generated_only).lower()} }}," + ) + +header = f'''/* eslint-disable */ +/** + * GENERATED FILE — DO NOT EDIT BY HAND. + * + * Source of truth: the constructive-db error audit ({len(codes)} distinct codes + * raised via EXCEPTION/THROW across deploy sources + generated output). + * Regenerate with `python3 scripts/generate-registry.py` (see README.md). + * + * Public copy is seeded from the dashboard error catalogs; developer/invariant + * codes carry their raw message (with %-args rendered as {{{{argN}}}}). Curated + * entries in `registry.ts` override anything here (typed context + refined copy). + * + * Counts: {len(codes)} total, {n_public} public, {len(codes) - n_public} internal. + */ +import {{ defineError, type DefinedError }} from '../define'; +import type {{ ErrorContext }} from '../types'; + +export interface GeneratedCodeMeta {{ + class: 'public' | 'internal'; + dynamic: boolean; + /** True when the code is only emitted by DB code generators (no hand source). */ + generatedOnly: boolean; +}} + +/** Every constructive-db error code, keyed by code. */ +export const generatedRegistry: Record> = {{ +{chr(10).join(entries)} +}}; + +/** Audit metadata for each generated code. */ +export const GENERATED_CODE_META: Record = {{ +{chr(10).join(meta)} +}}; + +/** Total number of codes collected from constructive-db. */ +export const GENERATED_CODE_COUNT = {len(codes)}; +''' + +os.makedirs(os.path.dirname(OUT), exist_ok=True) +open(OUT, 'w').write(header) +print(f'Wrote {OUT}') +print(f'dashboard={dashboard or "(not found — humanized copy)"}') +print(f'total={len(codes)} public={n_public} internal={len(codes) - n_public} ' + f'dashboard_copy_codes={len(copy)}') diff --git a/packages/errors/src/define.ts b/packages/errors/src/define.ts new file mode 100644 index 0000000000..7f5d0274e4 --- /dev/null +++ b/packages/errors/src/define.ts @@ -0,0 +1,22 @@ +import type { EmptyContext, ErrorContext, ErrorDefinition } from './types'; + +/** + * A registry entry plus a phantom type carrier for its context. The + * contravariant `__context` field lets `ErrorsApi` recover `C` by inference + * even when `C` appears only in optional positions of {@link ErrorDefinition}. + * It is type-level only — never set at runtime. + */ +export type DefinedError = ErrorDefinition & { + readonly __context: (context: C) => void; +}; + +/** + * Identity helper that captures each entry's context type for inference while + * keeping the registry a plain object. `errors.MODULE_NOT_FOUND({ name })` is + * then type-checked against the declared context. + */ +export function defineError( + def: ErrorDefinition +): DefinedError { + return def as DefinedError; +} diff --git a/packages/errors/src/factory.ts b/packages/errors/src/factory.ts index 5641753ca9..8d34d01c95 100644 --- a/packages/errors/src/factory.ts +++ b/packages/errors/src/factory.ts @@ -1,5 +1,6 @@ import { ConstructiveError } from './error'; import { format } from './format'; +import { generatedRegistry } from './generated/registry.generated'; import { registry } from './registry'; import type { ErrorClass, ErrorContext, ErrorDefinition } from './types'; @@ -69,5 +70,15 @@ export function makeError( }); } -/** The canonical, type-safe `errors.*` factory built from the registry. */ -export const errors = buildErrors(registry); +/** + * The canonical `errors.*` factory. + * + * Curated codes are strongly typed (`errors.MODULE_NOT_FOUND({ name })`); every + * other constructive-db code is available via a generic factory through the + * index signature (`errors.ACCOUNT_EXISTS()`), backed by the generated + * registry. Curated entries override generated ones on conflict. + */ +export const errors = buildErrors({ + ...generatedRegistry, + ...registry +} as typeof registry) as ErrorsApi & Record>; diff --git a/packages/errors/src/format.ts b/packages/errors/src/format.ts index c69be5b3b8..a3365be07c 100644 --- a/packages/errors/src/format.ts +++ b/packages/errors/src/format.ts @@ -1,5 +1,5 @@ import { interpolate } from './interpolate'; -import { getDefinition, registry } from './registry'; +import { allCodes, getDefinition } from './registry'; import type { ErrorContext } from './types'; /** A locale catalog maps `code` → message template (with `{{var}}`). */ @@ -8,13 +8,15 @@ export type MessageCatalog = Record; export const DEFAULT_LOCALE = 'en'; /** - * Built-in `en` catalog derived from the registry's string messages. Entries - * whose message is a function are omitted (they are computed, not templated) - * and fall back to the registry function at format time. + * Built-in `en` catalog derived from every registered code's string message + * (curated + generated). Entries whose message is a function are omitted (they + * are computed, not templated) and fall back to the registry function at + * format time. */ const defaultEnCatalog: MessageCatalog = Object.fromEntries( - Object.values(registry) - .filter((def) => typeof def.message === 'string') + allCodes() + .map((code) => getDefinition(code)) + .filter((def): def is NonNullable => !!def && typeof def.message === 'string') .map((def) => [def.code, def.message as string]) ); diff --git a/packages/errors/src/generated/registry.generated.ts b/packages/errors/src/generated/registry.generated.ts new file mode 100644 index 0000000000..ae6d520755 --- /dev/null +++ b/packages/errors/src/generated/registry.generated.ts @@ -0,0 +1,608 @@ +/* eslint-disable */ +/** + * GENERATED FILE — DO NOT EDIT BY HAND. + * + * Source of truth: the constructive-db error audit (287 distinct codes + * raised via EXCEPTION/THROW across deploy sources + generated output). + * Regenerate with `python3 scripts/generate-registry.py` (see README.md). + * + * Public copy is seeded from the dashboard error catalogs; developer/invariant + * codes carry their raw message (with %-args rendered as {{argN}}). Curated + * entries in `registry.ts` override anything here (typed context + refined copy). + * + * Counts: 287 total, 83 public, 204 internal. + */ +import { defineError, type DefinedError } from '../define'; +import type { ErrorContext } from '../types'; + +export interface GeneratedCodeMeta { + class: 'public' | 'internal'; + dynamic: boolean; + /** True when the code is only emitted by DB code generators (no hand source). */ + generatedOnly: boolean; +} + +/** Every constructive-db error code, keyed by code. */ +export const generatedRegistry: Record> = { + 'ACCOUNT_DISABLED': defineError({ code: 'ACCOUNT_DISABLED', class: 'public', http: 403, message: 'Your account has been disabled. Please contact support for assistance.' }), + 'ACCOUNT_EXISTS': defineError({ code: 'ACCOUNT_EXISTS', class: 'public', http: 409, message: 'An account with this email already exists. Please sign in or use a different email.' }), + 'ACCOUNT_LOCKED_EXCEED_ATTEMPTS': defineError({ code: 'ACCOUNT_LOCKED_EXCEED_ATTEMPTS', class: 'public', http: 423, message: 'Your account has been temporarily locked due to too many failed login attempts. Please try again later or reset your password.' }), + 'ACCOUNT_NOT_FOUND': defineError({ code: 'ACCOUNT_NOT_FOUND', class: 'public', http: 404, message: 'Account not found.' }), + 'ALREADY_SCHEDULED': defineError({ code: 'ALREADY_SCHEDULED', class: 'public', http: 409, message: 'Already scheduled.' }), + 'ALTER_TABLE_ADD_COLUMN': defineError({ code: 'ALTER_TABLE_ADD_COLUMN', class: 'internal', http: 500, message: 'ALTER_TABLE_ADD_COLUMN: generation_type must be stored or virtual, got {{arg0}}', positional: ['arg0'] }), + 'API_KEYS_DISABLED': defineError({ code: 'API_KEYS_DISABLED', class: 'public', http: 403, message: 'Api keys disabled.' }), + 'API_KEY_LIMIT_REACHED': defineError({ code: 'API_KEY_LIMIT_REACHED', class: 'public', http: 429, message: 'Api key limit reached.' }), + 'API_KEY_NOT_FOUND': defineError({ code: 'API_KEY_NOT_FOUND', class: 'public', http: 404, message: 'Api key not found.' }), + 'APPLY_RLS': defineError({ code: 'APPLY_RLS', class: 'internal', http: 500, message: 'APPLY_RLS (bad privs) {{arg0}}', positional: ['arg0'] }), + 'APPLY_RLS_BAD_ARGS': defineError({ code: 'APPLY_RLS_BAD_ARGS', class: 'internal', http: 500, message: 'Apply rls bad args.' }), + 'APPLY_TABLE_STEP_UP': defineError({ code: 'APPLY_TABLE_STEP_UP', class: 'internal', http: 500, message: 'APPLY_TABLE_STEP_UP: table not found for table_id {{arg0}}', positional: ['arg0'] }), + 'APP_COMPONENT_NOT_VISIBLE': defineError({ code: 'APP_COMPONENT_NOT_VISIBLE', class: 'internal', http: 500, message: 'App component not visible.' }), + 'APP_COMPONENT_TARGET_REQUIRED': defineError({ code: 'APP_COMPONENT_TARGET_REQUIRED', class: 'internal', http: 500, message: 'App component target required.' }), + 'APP_SCOPE_FRAMES_SCOPE_REQUIRED': defineError({ code: 'APP_SCOPE_FRAMES_SCOPE_REQUIRED', class: 'internal', http: 500, message: 'APP_SCOPE_FRAMES_SCOPE_REQUIRED: execution_scope is required (no default scope)' }), + 'APP_SCOPE_LOCAL_FRAMES_DEPTH_EXCEEDED': defineError({ code: 'APP_SCOPE_LOCAL_FRAMES_DEPTH_EXCEEDED', class: 'internal', http: 500, message: 'APP_SCOPE_LOCAL_FRAMES_DEPTH_EXCEEDED: scope chain exceeded 32 hops from scope "{{arg0}}" (database_id={{arg1}})', positional: ['arg0', 'arg1'] }), + 'APP_SCOPE_LOCAL_FRAMES_SCOPE_REQUIRED': defineError({ code: 'APP_SCOPE_LOCAL_FRAMES_SCOPE_REQUIRED', class: 'internal', http: 500, message: 'APP_SCOPE_LOCAL_FRAMES_SCOPE_REQUIRED: execution_scope is required (no default scope)' }), + 'ASSIGN_PROFILES_PERMISSION_REQUIRED': defineError({ code: 'ASSIGN_PROFILES_PERMISSION_REQUIRED', class: 'public', http: 403, message: 'Assign profiles permission required.' }), + 'AUTHZ_COLUMN_SECURITY': defineError({ code: 'AUTHZ_COLUMN_SECURITY', class: 'internal', http: 500, message: 'AUTHZ_COLUMN_SECURITY: rule transition requires a non-empty allowed array' }), + 'AUTH_METHOD_NOT_ALLOWED': defineError({ code: 'AUTH_METHOD_NOT_ALLOWED', class: 'public', http: 403, message: 'Auth method not allowed.' }), + 'BAD_CASING_FUNC': defineError({ code: 'BAD_CASING_FUNC', class: 'internal', http: 500, message: 'BAD_CASING_FUNC ({{arg0}}, {{arg1}})', positional: ['arg0', 'arg1'] }), + 'BAD_EXPRESSION': defineError({ code: 'BAD_EXPRESSION', class: 'internal', http: 500, message: 'BAD_EXPRESSION {{arg0}} (unrecognized format)', positional: ['arg0'] }), + 'BAD_FIELD_INPUT': defineError({ code: 'BAD_FIELD_INPUT', class: 'internal', http: 500, message: 'BAD_FIELD_INPUT: generation expression cannot be changed after creation' }), + 'BAD_FIELD_ORDERING_CROSS_TABLE': defineError({ code: 'BAD_FIELD_ORDERING_CROSS_TABLE', class: 'internal', http: 500, message: 'Bad field ordering cross table.' }), + 'BAD_PRIVILEGE_FOR_POLICY': defineError({ code: 'BAD_PRIVILEGE_FOR_POLICY', class: 'internal', http: 500, message: 'Bad privilege for policy.' }), + 'BAD_RLS_EXPRESSION': defineError({ code: 'BAD_RLS_EXPRESSION', class: 'internal', http: 500, message: 'BAD_RLS_EXPRESSION {{arg0}} (policy.AuthzValueExists requires ref_schema/ref_table or ref_table_id)', positional: ['arg0'] }), + 'BAD_VIEW_EXPRESSION': defineError({ code: 'BAD_VIEW_EXPRESSION', class: 'internal', http: 500, message: 'BAD_VIEW_EXPRESSION {{arg0}} (view.ViewTableProjection)', positional: ['arg0'] }), + 'BM25': defineError({ code: 'BM25', class: 'internal', http: 500, message: 'BM25: invalid text_config "{{arg0}}", must be a valid pg_ts_config', positional: ['arg0'] }), + 'BOOTSTRAP_DATABASE_MISSING': defineError({ code: 'BOOTSTRAP_DATABASE_MISSING', class: 'internal', http: 500, message: 'Bootstrap database missing.' }), + 'BOOTSTRAP_SOURCE_MISSING': defineError({ code: 'BOOTSTRAP_SOURCE_MISSING', class: 'internal', http: 500, message: 'Bootstrap source missing.' }), + 'BOOTSTRAP_TARGET_NOT_EMPTY': defineError({ code: 'BOOTSTRAP_TARGET_NOT_EMPTY', class: 'internal', http: 500, message: 'Bootstrap target not empty.' }), + 'BUILD_CONDITION_EXPR': defineError({ code: 'BUILD_CONDITION_EXPR', class: 'internal', http: 500, message: 'BUILD_CONDITION_EXPR: unknown condition op "{{arg0}}", expected =|!=|>|<|>=|<=|LIKE|NOT LIKE|IS NULL|IS NOT NULL|IS DISTINCT FROM', positional: ['arg0'] }), + 'CANNOT_DISCONNECT_LAST_AUTH_METHOD': defineError({ code: 'CANNOT_DISCONNECT_LAST_AUTH_METHOD', class: 'public', http: 400, message: 'Cannot disconnect last auth method.' }), + 'CANNOT_REVOKE_CURRENT_SESSION': defineError({ code: 'CANNOT_REVOKE_CURRENT_SESSION', class: 'public', http: 400, message: 'Cannot revoke current session.' }), + 'CAP_CHECK_TRIGGER_ARGS': defineError({ code: 'CAP_CHECK_TRIGGER_ARGS', class: 'internal', http: 500, message: 'CAP_CHECK_TRIGGER_ARGS ({{arg0}})', positional: ['arg0'] }), + 'CATALOG_SYNC': defineError({ code: 'CATALOG_SYNC', class: 'internal', http: 500, message: 'CATALOG_SYNC: catalog {{arg0}}.{{arg1}} registration has no copy_fields', positional: ['arg0', 'arg1'] }), + 'COMMIT_NOT_FOUND': defineError({ code: 'COMMIT_NOT_FOUND', class: 'internal', http: 500, message: 'Commit not found.' }), + 'CONNECTED_ACCOUNT_NOT_FOUND': defineError({ code: 'CONNECTED_ACCOUNT_NOT_FOUND', class: 'public', http: 404, message: 'Connected account not found.' }), + 'CONSTRUCT_BLUEPRINT': defineError({ code: 'CONSTRUCT_BLUEPRINT', class: 'internal', http: 500, message: 'CONSTRUCT_BLUEPRINT: achievements entry "{{arg0}}" references entity_prefix "{{arg1}}" but no events_module found for that prefix', positional: ['arg0', 'arg1'] }), + 'CONST_TYPE_FIELDS_IMMUTABLE': defineError({ code: 'CONST_TYPE_FIELDS_IMMUTABLE', class: 'public', http: 403, message: 'Const type fields immutable.' }), + 'COPY_TEMPLATE_TO_BLUEPRINT': defineError({ code: 'COPY_TEMPLATE_TO_BLUEPRINT', class: 'internal', http: 500, message: 'COPY_TEMPLATE_TO_BLUEPRINT: template {{arg0}} is private and not owned by {{arg1}}', positional: ['arg0', 'arg1'] }), + 'CREDIT_CODE_EXPIRED': defineError({ code: 'CREDIT_CODE_EXPIRED', class: 'internal', http: 500, message: 'Credit code expired.' }), + 'CREDIT_CODE_MAX_REDEMPTIONS_REACHED': defineError({ code: 'CREDIT_CODE_MAX_REDEMPTIONS_REACHED', class: 'internal', http: 500, message: 'Credit code max redemptions reached.' }), + 'CREDIT_CODE_NOT_FOUND': defineError({ code: 'CREDIT_CODE_NOT_FOUND', class: 'internal', http: 500, message: 'Credit code not found.' }), + 'CROSS_DATABASE_CHECK': defineError({ code: 'CROSS_DATABASE_CHECK', class: 'internal', http: 500, message: 'CROSS_DATABASE_CHECK: table {{arg0}} not found ({{arg1}})', positional: ['arg0', 'arg1'] }), + 'CROSS_DATABASE_REF': defineError({ code: 'CROSS_DATABASE_REF', class: 'public', http: 400, message: 'Cross database ref.' }), + 'CSRF_TOKEN_REQUIRED': defineError({ code: 'CSRF_TOKEN_REQUIRED', class: 'public', http: 401, message: 'Csrf token required.' }), + 'DATABASE_FIELD_RESERVED_WORD': defineError({ code: 'DATABASE_FIELD_RESERVED_WORD', class: 'internal', http: 500, message: 'Database field reserved word.' }), + 'DATABASE_TABLE_RESERVED_WORD': defineError({ code: 'DATABASE_TABLE_RESERVED_WORD', class: 'internal', http: 500, message: 'Database table reserved word.' }), + 'DATA_BILLING_METER': defineError({ code: 'DATA_BILLING_METER', class: 'internal', http: 500, message: 'DATA_BILLING_METER: unknown event {{arg0}}, expected INSERT, DELETE, or UPDATE', positional: ['arg0'] }), + 'DATA_COMPOSITE_FIELD': defineError({ code: 'DATA_COMPOSITE_FIELD', class: 'internal', http: 500, message: 'DATA_COMPOSITE_FIELD: source_fields is required and must be a JSON array' }), + 'DATA_DIRECT_OWNER': defineError({ code: 'DATA_DIRECT_OWNER', class: 'internal', http: 500, message: 'DATA_DIRECT_OWNER: table not found for table_id {{arg0}}', positional: ['arg0'] }), + 'DATA_DIRECT_OWNER_IN_ENTITY': defineError({ code: 'DATA_DIRECT_OWNER_IN_ENTITY', class: 'internal', http: 500, message: 'DATA_DIRECT_OWNER_IN_ENTITY: table not found for table_id {{arg0}}', positional: ['arg0'] }), + 'DATA_FORCE_CURRENT_USER': defineError({ code: 'DATA_FORCE_CURRENT_USER', class: 'internal', http: 500, message: 'DATA_FORCE_CURRENT_USER: field "{{arg0}}" not found on table "{{arg1}}"', positional: ['arg0', 'arg1'] }), + 'DATA_GENERATED': defineError({ code: 'DATA_GENERATED', class: 'internal', http: 500, message: 'DATA_GENERATED: unknown kind {{arg0}}, expected expression|concat|slug|object_name|hash', positional: ['arg0'] }), + 'DATA_I18N': defineError({ code: 'DATA_I18N', class: 'internal', http: 500, message: 'DATA_I18N: "fields" parameter is required and must be a non-empty array' }), + 'DATA_ID': defineError({ code: 'DATA_ID', class: 'internal', http: 500, message: 'DATA_ID: table not found for table_id {{arg0}}', positional: ['arg0'] }), + 'DATA_IMMUTABLE_FIELDS': defineError({ code: 'DATA_IMMUTABLE_FIELDS', class: 'internal', http: 500, message: 'DATA_IMMUTABLE_FIELDS: fields array is required and must not be empty' }), + 'DATA_INFLECTION': defineError({ code: 'DATA_INFLECTION', class: 'internal', http: 500, message: 'DATA_INFLECTION: field "{{arg0}}" not found on table "{{arg1}}"', positional: ['arg0', 'arg1'] }), + 'DATA_INHERIT_FROM_PARENT': defineError({ code: 'DATA_INHERIT_FROM_PARENT', class: 'internal', http: 500, message: 'DATA_INHERIT_FROM_PARENT: could not resolve parent table from FK on field {{arg0}}. Provide parent_table in data.', positional: ['arg0'] }), + 'DATA_JOB_TRIGGER': defineError({ code: 'DATA_JOB_TRIGGER', class: 'internal', http: 500, message: 'DATA_JOB_TRIGGER: "function_target" is not a valid option. Ledger routing is derived from the function registry and cannot be set by callers.' }), + 'DATA_JSONB': defineError({ code: 'DATA_JSONB', class: 'internal', http: 500, message: 'DATA_JSONB: table not found for table_id {{arg0}}', positional: ['arg0'] }), + 'DATA_MEMBERSHIP_BY_FIELD': defineError({ code: 'DATA_MEMBERSHIP_BY_FIELD', class: 'internal', http: 500, message: 'DATA_MEMBERSHIP_BY_FIELD: table not found for table_id {{arg0}}', positional: ['arg0'] }), + 'DATA_MEMBER_OWNER': defineError({ code: 'DATA_MEMBER_OWNER', class: 'internal', http: 500, message: 'DATA_MEMBER_OWNER: table not found for table_id {{arg0}}', positional: ['arg0'] }), + 'DATA_OWNED_FIELDS': defineError({ code: 'DATA_OWNED_FIELDS', class: 'internal', http: 500, message: 'DATA_OWNED_FIELDS: protected field "{{arg0}}" not found on table "{{arg1}}"', positional: ['arg0', 'arg1'] }), + 'DATA_PEOPLESTAMPS': defineError({ code: 'DATA_PEOPLESTAMPS', class: 'internal', http: 500, message: 'DATA_PEOPLESTAMPS: table not found for table_id {{arg0}}', positional: ['arg0'] }), + 'DATA_PUBLISHABLE': defineError({ code: 'DATA_PUBLISHABLE', class: 'internal', http: 500, message: 'DATA_PUBLISHABLE: is_published_field_name must not be empty' }), + 'DATA_REALTIME': defineError({ code: 'DATA_REALTIME', class: 'internal', http: 500, message: 'DATA_REALTIME: unknown operation {{arg0}}, expected INSERT, UPDATE, or DELETE', positional: ['arg0'] }), + 'DATA_SLUG': defineError({ code: 'DATA_SLUG', class: 'internal', http: 500, message: 'DATA_SLUG: source field "{{arg0}}" not found on table "{{arg1}}"', positional: ['arg0', 'arg1'] }), + 'DATA_STATUS_FIELD': defineError({ code: 'DATA_STATUS_FIELD', class: 'internal', http: 500, message: 'DATA_STATUS_FIELD: unsupported type "{{arg0}}" for status field, expected a scalar type (text, varchar, integer, smallint, bigint, boolean, uuid, citext)', positional: ['arg0'] }), + 'DATA_TAGS': defineError({ code: 'DATA_TAGS', class: 'internal', http: 500, message: 'DATA_TAGS: table not found for table_id {{arg0}}', positional: ['arg0'] }), + 'DATA_TIMESTAMPS': defineError({ code: 'DATA_TIMESTAMPS', class: 'internal', http: 500, message: 'DATA_TIMESTAMPS: table not found for table_id {{arg0}}', positional: ['arg0'] }), + 'DB_PRESET_MODULE': defineError({ code: 'DB_PRESET_MODULE', class: 'internal', http: 500, message: 'DB_PRESET_MODULE: scope ({{arg0}}) does not match backing merkle_store_module {{arg1}} scope ({{arg2}})', positional: ['arg0', 'arg1', 'arg2'] }), + 'DELETE_FIRST': defineError({ code: 'DELETE_FIRST', class: 'public', http: 400, message: 'Delete first.' }), + 'DOMAIN_CHECK_CERT_NOT_ISSUING': defineError({ code: 'DOMAIN_CHECK_CERT_NOT_ISSUING', class: 'internal', http: 500, message: 'DOMAIN_CHECK_CERT_NOT_ISSUING: managed_domain {{arg0}} has cert_status {{arg1}} (issue a cert first)', positional: ['arg0', 'arg1'] }), + 'DOMAIN_CHECK_CERT_UNKNOWN_DOMAIN': defineError({ code: 'DOMAIN_CHECK_CERT_UNKNOWN_DOMAIN', class: 'internal', http: 500, message: 'DOMAIN_CHECK_CERT_UNKNOWN_DOMAIN: no managed_domain with id {{arg0}}', positional: ['arg0'] }), + 'DOMAIN_ISSUE_CERT_BAD_ISSUER': defineError({ code: 'DOMAIN_ISSUE_CERT_BAD_ISSUER', class: 'internal', http: 500, message: 'DOMAIN_ISSUE_CERT_BAD_ISSUER: unsupported issuer_env {{arg0}} (expected staging|production)', positional: ['arg0'] }), + 'DOMAIN_ISSUE_CERT_NOT_VERIFIED': defineError({ code: 'DOMAIN_ISSUE_CERT_NOT_VERIFIED', class: 'internal', http: 500, message: 'DOMAIN_ISSUE_CERT_NOT_VERIFIED: managed_domain {{arg0}} is {{arg1}} (must be verified before issuing a cert)', positional: ['arg0', 'arg1'] }), + 'DOMAIN_ISSUE_CERT_UNKNOWN_DOMAIN': defineError({ code: 'DOMAIN_ISSUE_CERT_UNKNOWN_DOMAIN', class: 'internal', http: 500, message: 'DOMAIN_ISSUE_CERT_UNKNOWN_DOMAIN: no managed_domain with id {{arg0}}', positional: ['arg0'] }), + 'DOMAIN_ISSUE_CHALLENGE_BAD_METHOD': defineError({ code: 'DOMAIN_ISSUE_CHALLENGE_BAD_METHOD', class: 'internal', http: 500, message: 'DOMAIN_ISSUE_CHALLENGE_BAD_METHOD: unsupported verification method {{arg0}}', positional: ['arg0'] }), + 'DOMAIN_ISSUE_CHALLENGE_NO_OWNER': defineError({ code: 'DOMAIN_ISSUE_CHALLENGE_NO_OWNER', class: 'internal', http: 500, message: 'DOMAIN_ISSUE_CHALLENGE_NO_OWNER: managed_domain {{arg0}} has no owning entity', positional: ['arg0'] }), + 'DOMAIN_ISSUE_CHALLENGE_UNKNOWN_DOMAIN': defineError({ code: 'DOMAIN_ISSUE_CHALLENGE_UNKNOWN_DOMAIN', class: 'internal', http: 500, message: 'DOMAIN_ISSUE_CHALLENGE_UNKNOWN_DOMAIN: no managed_domain with id {{arg0}}', positional: ['arg0'] }), + 'DOMAIN_RENEW_NOT_ACTIVE': defineError({ code: 'DOMAIN_RENEW_NOT_ACTIVE', class: 'internal', http: 500, message: 'DOMAIN_RENEW_NOT_ACTIVE: managed_domain {{arg0}} has cert_status {{arg1}} (only an active cert can be renewed)', positional: ['arg0', 'arg1'] }), + 'DOMAIN_RENEW_UNKNOWN_DOMAIN': defineError({ code: 'DOMAIN_RENEW_UNKNOWN_DOMAIN', class: 'internal', http: 500, message: 'DOMAIN_RENEW_UNKNOWN_DOMAIN: no managed_domain with id {{arg0}}', positional: ['arg0'] }), + 'DOMAIN_REVOKE_UNKNOWN_DOMAIN': defineError({ code: 'DOMAIN_REVOKE_UNKNOWN_DOMAIN', class: 'internal', http: 500, message: 'DOMAIN_REVOKE_UNKNOWN_DOMAIN: no managed_domain with id {{arg0}}', positional: ['arg0'] }), + 'DOMAIN_VERIFY_BAD_METHOD': defineError({ code: 'DOMAIN_VERIFY_BAD_METHOD', class: 'internal', http: 500, message: 'DOMAIN_VERIFY_BAD_METHOD: unsupported verification method {{arg0}}', positional: ['arg0'] }), + 'DOMAIN_VERIFY_NO_CHALLENGE': defineError({ code: 'DOMAIN_VERIFY_NO_CHALLENGE', class: 'internal', http: 500, message: 'DOMAIN_VERIFY_NO_CHALLENGE: no outstanding {{arg0}} challenge for managed_domain {{arg1}}', positional: ['arg0', 'arg1'] }), + 'EMAIL_NOT_VERIFIED': defineError({ code: 'EMAIL_NOT_VERIFIED', class: 'public', http: 400, message: 'Please verify your email before accepting this invitation.' }), + 'EMBEDDING_CHUNKS': defineError({ code: 'EMBEDDING_CHUNKS', class: 'internal', http: 500, message: 'EMBEDDING_CHUNKS: unknown metric {{arg0}}, expected cosine|l2|ip', positional: ['arg0'] }), + 'ENQUEUE_SCOPE_REQUIRED': defineError({ code: 'ENQUEUE_SCOPE_REQUIRED', class: 'internal', http: 500, message: 'ENQUEUE_SCOPE_REQUIRED: scope is required (no default scope)' }), + 'ENTITY_NOT_FOUND': defineError({ code: 'ENTITY_NOT_FOUND', class: 'internal', http: 500, message: 'Entity not found.' }), + 'ENTITY_TYPE_NOT_FOUND': defineError({ code: 'ENTITY_TYPE_NOT_FOUND', class: 'internal', http: 500, message: 'Entity type not found.' }), + 'ENTITY_TYPE_PROVISION': defineError({ code: 'ENTITY_TYPE_PROVISION', class: 'internal', http: 500, message: 'ENTITY_TYPE_PROVISION: derived table name "{{arg0}}" exceeds safe length (prefix: {{arg1}}, key: {{arg2}})', positional: ['arg0', 'arg1', 'arg2'] }), + 'ENUM_VALUES_CANNOT_BE_REMOVED': defineError({ code: 'ENUM_VALUES_CANNOT_BE_REMOVED', class: 'internal', http: 500, message: 'ENUM_VALUES_CANNOT_BE_REMOVED: PostgreSQL does not support removing enum values' }), + 'ENUM_VALUES_CANNOT_BE_REORDERED': defineError({ code: 'ENUM_VALUES_CANNOT_BE_REORDERED', class: 'internal', http: 500, message: 'ENUM_VALUES_CANNOT_BE_REORDERED: existing enum values must remain in the same order' }), + 'EVENT_REFERRAL': defineError({ code: 'EVENT_REFERRAL', class: 'internal', http: 500, message: 'EVENT_REFERRAL: invites module not found for database_id {{arg0}}. Ensure invites module is enabled (has_invites=true).', positional: ['arg0'] }), + 'EVENT_TRACKER': defineError({ code: 'EVENT_TRACKER', class: 'internal', http: 500, message: 'EVENT_TRACKER: events module not found for database_id {{arg0}}. Ensure events module is enabled.', positional: ['arg0'] }), + 'EXPIRED_TOKEN': defineError({ code: 'EXPIRED_TOKEN', class: 'public', http: 400, message: 'This reset link has expired. Please request a new one.' }), + 'EXTERNAL_MEMBERS_NOT_ALLOWED': defineError({ code: 'EXTERNAL_MEMBERS_NOT_ALLOWED', class: 'public', http: 403, message: 'External members not allowed.' }), + 'FEATURE_DISABLED': defineError({ code: 'FEATURE_DISABLED', class: 'public', http: 403, message: 'Feature disabled.', positional: ['arg0'] }), + 'FIELDS_MISMATCH': defineError({ code: 'FIELDS_MISMATCH', class: 'internal', http: 500, message: 'FIELDS_MISMATCH (denormalized_table_field {{arg0}}[{{arg1}}] {{arg2}}[{{arg3}}])', positional: ['arg0', 'arg1', 'arg2', 'arg3'] }), + 'FULL_TEXT_SEARCH': defineError({ code: 'FULL_TEXT_SEARCH', class: 'internal', http: 500, message: 'FULL_TEXT_SEARCH: invalid lang "{{arg0}}" at index {{arg1}}, must be a valid pg_ts_config', positional: ['arg0', 'arg1'] }), + 'FUNCTION_DEFINITION_INVALID_PAIR': defineError({ code: 'FUNCTION_DEFINITION_INVALID_PAIR', class: 'internal', http: 500, message: 'FUNCTION_DEFINITION_INVALID_PAIR: function_definition_id {{arg0}} / definition_scope "{{arg1}}" does not resolve to task_identifier "{{arg2}}" (database_id={{arg3}})', positional: ['arg0', 'arg1', 'arg2', 'arg3'] }), + 'FUNCTION_DEFINITION_NOT_FOUND': defineError({ code: 'FUNCTION_DEFINITION_NOT_FOUND', class: 'internal', http: 500, message: 'FUNCTION_DEFINITION_NOT_FOUND: no definition for task_identifier "{{arg0}}" in the scope chain starting at scope "{{arg1}}" (database_id={{arg2}})', positional: ['arg0', 'arg1', 'arg2'] }), + 'FUNCTION_SCHEDULES': defineError({ code: 'FUNCTION_SCHEDULES', class: 'internal', http: 500, message: 'FUNCTION_SCHEDULES: function definition does not allow the cron access channel' }), + 'FUNCTION_SCHEDULES_INTERVAL': defineError({ code: 'FUNCTION_SCHEDULES_INTERVAL', class: 'internal', http: 500, message: 'FUNCTION_SCHEDULES_INTERVAL (min_schedule_interval)' }), + 'FUNCTION_SCHEDULES_LIMIT': defineError({ code: 'FUNCTION_SCHEDULES_LIMIT', class: 'internal', http: 500, message: 'FUNCTION_SCHEDULES_LIMIT (max_schedules_per_entity)' }), + 'FUNCTION_SCHEDULE_SYNC': defineError({ code: 'FUNCTION_SCHEDULE_SYNC', class: 'internal', http: 500, message: 'FUNCTION_SCHEDULE_SYNC: unknown event {{arg0}}, expected UPSERT or DELETE', positional: ['arg0'] }), + 'GRAPH_EXECUTION_MODULE': defineError({ code: 'GRAPH_EXECUTION_MODULE', class: 'internal', http: 500, message: 'GRAPH_EXECUTION_MODULE: merkle_store_module {{arg0}} not found (from graph_module {{arg1}})', positional: ['arg0', 'arg1'] }), + 'GRAPH_MODULE': defineError({ code: 'GRAPH_MODULE', class: 'internal', http: 500, message: 'GRAPH_MODULE: scope ({{arg0}}) does not match backing merkle_store_module {{arg1}} scope ({{arg2}})', positional: ['arg0', 'arg1', 'arg2'] }), + 'GRAPH_VALIDATION_FAILED': defineError({ code: 'GRAPH_VALIDATION_FAILED', class: 'internal', http: 500, message: 'GRAPH_VALIDATION_FAILED: graph {{arg0}} failed pre-execution validation: {{arg1}}', positional: ['arg0', 'arg1'] }), + 'GROUPS_REQ_ENTITIES': defineError({ code: 'GROUPS_REQ_ENTITIES', class: 'public', http: 400, message: 'Groups req entities.' }), + 'GUARD_STEP_UP': defineError({ code: 'GUARD_STEP_UP', class: 'internal', http: 500, message: 'GUARD_STEP_UP: user_auth_module not found for database_id {{arg0}} — required for require_step_up()', positional: ['arg0'] }), + 'HIERARCHY_CYCLE_DETECTED': defineError({ code: 'HIERARCHY_CYCLE_DETECTED', class: 'internal', http: 500, message: 'HIERARCHY_CYCLE_DETECTED: Setting {{arg0}} as parent of {{arg1}} would create a cycle', positional: ['arg0', 'arg1'] }), + 'HIERARCHY_DEPTH_EXCEEDED': defineError({ code: 'HIERARCHY_DEPTH_EXCEEDED', class: 'internal', http: 500, message: 'Hierarchy depth exceeded.' }), + 'HIERARCHY_INACTIVE_MEMBER': defineError({ code: 'HIERARCHY_INACTIVE_MEMBER', class: 'internal', http: 500, message: 'HIERARCHY_INACTIVE_MEMBER: Cannot add user {{arg0}} to hierarchy - user must be an active member of the organization first', positional: ['arg0'] }), + 'HIERARCHY_MEMBER_IN_USE': defineError({ code: 'HIERARCHY_MEMBER_IN_USE', class: 'internal', http: 500, message: 'HIERARCHY_MEMBER_IN_USE: Cannot deactivate user {{arg0}} - user must be removed from the organization hierarchy first', positional: ['arg0'] }), + 'HOSTNAME_BINDING_SYNC': defineError({ code: 'HOSTNAME_BINDING_SYNC', class: 'internal', http: 500, message: 'HOSTNAME_BINDING_SYNC: unknown event {{arg0}}, expected UPSERT or DELETE', positional: ['arg0'] }), + 'HTTP_ROUTE_TARGET_NOT_FOUND': defineError({ code: 'HTTP_ROUTE_TARGET_NOT_FOUND', class: 'internal', http: 500, message: 'HTTP_ROUTE_TARGET_NOT_FOUND:function' }), + 'HTTP_ROUTE_TARGET_OUTSIDE_SCOPE': defineError({ code: 'HTTP_ROUTE_TARGET_OUTSIDE_SCOPE', class: 'internal', http: 500, message: 'HTTP_ROUTE_TARGET_OUTSIDE_SCOPE:domain_id' }), + 'IDENTITY_ACCOUNT_NOT_FOUND': defineError({ code: 'IDENTITY_ACCOUNT_NOT_FOUND', class: 'public', http: 404, message: 'Identity account not found.' }), + 'IDENTITY_ALREADY_LINKED': defineError({ code: 'IDENTITY_ALREADY_LINKED', class: 'public', http: 409, message: 'Identity already linked.' }), + 'IDENTITY_LINK_AVAILABLE': defineError({ code: 'IDENTITY_LINK_AVAILABLE', class: 'public', http: 400, message: 'Identity link available.' }), + 'IDENTITY_PROVIDER_NOT_CONFIGURED': defineError({ code: 'IDENTITY_PROVIDER_NOT_CONFIGURED', class: 'public', http: 400, message: 'Identity provider not configured.' }), + 'IDENTITY_PROVIDER_NOT_FOUND': defineError({ code: 'IDENTITY_PROVIDER_NOT_FOUND', class: 'public', http: 404, message: 'Identity provider not found.' }), + 'IDENTITY_PROVIDER_QUOTA_EXCEEDED': defineError({ code: 'IDENTITY_PROVIDER_QUOTA_EXCEEDED', class: 'public', http: 400, message: 'Identity provider quota exceeded.' }), + 'IDENTITY_SIGN_IN_DISABLED': defineError({ code: 'IDENTITY_SIGN_IN_DISABLED', class: 'public', http: 403, message: 'Identity sign in disabled.' }), + 'IDENTITY_SIGN_UP_DISABLED': defineError({ code: 'IDENTITY_SIGN_UP_DISABLED', class: 'public', http: 403, message: 'Identity sign up disabled.' }), + 'IMMUTABLE_FIELD': defineError({ code: 'IMMUTABLE_FIELD', class: 'public', http: 403, message: 'Immutable field.' }), + 'IMMUTABLE_PEOPLESTAMPS': defineError({ code: 'IMMUTABLE_PEOPLESTAMPS', class: 'public', http: 403, message: 'Immutable peoplestamps.' }), + 'IMMUTABLE_PROPS': defineError({ code: 'IMMUTABLE_PROPS', class: 'public', http: 403, message: 'Immutable props.' }), + 'IMMUTABLE_TIMESTAMPS': defineError({ code: 'IMMUTABLE_TIMESTAMPS', class: 'public', http: 403, message: 'Immutable timestamps.' }), + 'INCORRECT_PASSWORD': defineError({ code: 'INCORRECT_PASSWORD', class: 'public', http: 401, message: 'The password you entered is incorrect. Please try again.' }), + 'INSERT_GRAPH_EXECUTION_MODULE': defineError({ code: 'INSERT_GRAPH_EXECUTION_MODULE', class: 'internal', http: 500, message: 'INSERT_GRAPH_EXECUTION_MODULE: graph_module {{arg0}} not found', positional: ['arg0'] }), + 'INVALID_ACCESS_LEVEL': defineError({ code: 'INVALID_ACCESS_LEVEL', class: 'internal', http: 500, message: 'Invalid access level.' }), + 'INVALID_BASE32': defineError({ code: 'INVALID_BASE32', class: 'internal', http: 500, message: 'Invalid base32.' }), + 'INVALID_CODE': defineError({ code: 'INVALID_CODE', class: 'public', http: 401, message: 'Invalid code.' }), + 'INVALID_CSRF_TOKEN': defineError({ code: 'INVALID_CSRF_TOKEN', class: 'public', http: 401, message: 'Invalid csrf token.' }), + 'INVALID_DEFAULT_LIMIT_ID': defineError({ code: 'INVALID_DEFAULT_LIMIT_ID', class: 'internal', http: 500, message: 'Invalid default limit id.' }), + 'INVALID_FUNCTION_REF': defineError({ code: 'INVALID_FUNCTION_REF', class: 'internal', http: 500, message: 'INVALID_FUNCTION_REF: node_type "{{arg0}}" field "{{arg1}}" references task_identifier "{{arg2}}" which is not a registered function.', positional: ['arg0', 'arg1', 'arg2'] }), + 'INVALID_METER_ID': defineError({ code: 'INVALID_METER_ID', class: 'internal', http: 500, message: 'Invalid meter id.' }), + 'INVALID_MFA_CHALLENGE': defineError({ code: 'INVALID_MFA_CHALLENGE', class: 'public', http: 403, message: 'Invalid mfa challenge.' }), + 'INVALID_MFA_LEVEL': defineError({ code: 'INVALID_MFA_LEVEL', class: 'internal', http: 500, message: 'Invalid mfa level.' }), + 'INVALID_NODE_EDGES': defineError({ code: 'INVALID_NODE_EDGES', class: 'internal', http: 500, message: 'INVALID_NODE_EDGES: node "{{arg0}}" edges must be a JSON array, got {{arg1}}', positional: ['arg0', 'arg1'] }), + 'INVALID_NODE_NODES': defineError({ code: 'INVALID_NODE_NODES', class: 'internal', http: 500, message: 'INVALID_NODE_NODES: node "{{arg0}}" nodes must be a JSON array, got {{arg1}}', positional: ['arg0', 'arg1'] }), + 'INVALID_NODE_PROPS': defineError({ code: 'INVALID_NODE_PROPS', class: 'internal', http: 500, message: 'INVALID_NODE_PROPS: node "{{arg0}}" props must be a JSON array, got {{arg1}}', positional: ['arg0', 'arg1'] }), + 'INVALID_ORGANIZATION': defineError({ code: 'INVALID_ORGANIZATION', class: 'internal', http: 500, message: 'Invalid organization.' }), + 'INVALID_TOKEN': defineError({ code: 'INVALID_TOKEN', class: 'public', http: 401, message: 'This reset link is invalid. Please request a new one.' }), + 'INVALID_USER': defineError({ code: 'INVALID_USER', class: 'public', http: 400, message: 'Invalid user.' }), + 'INVALID_WORKER_ID': defineError({ code: 'INVALID_WORKER_ID', class: 'internal', http: 500, message: 'Invalid worker id.' }), + 'INVITE_EMAIL_NOT_FOUND': defineError({ code: 'INVITE_EMAIL_NOT_FOUND', class: 'public', http: 404, message: 'This invitation was sent to a different email address.' }), + 'INVITE_LIMIT': defineError({ code: 'INVITE_LIMIT', class: 'public', http: 429, message: 'This invitation has reached its usage limit.' }), + 'INVITE_NOT_FOUND': defineError({ code: 'INVITE_NOT_FOUND', class: 'public', http: 404, message: 'This invitation was not found.' }), + 'INVOCATION_QUOTA_GATE': defineError({ code: 'INVOCATION_QUOTA_GATE', class: 'internal', http: 500, message: 'INVOCATION_QUOTA_GATE: quota_schema and quota_function are required — the generator must only build this trigger when billing is provisioned' }), + 'INVOCATION_USAGE': defineError({ code: 'INVOCATION_USAGE', class: 'internal', http: 500, message: 'INVOCATION_USAGE: billing_schema and record_usage_fn are required — the generator must only build this trigger when billing is provisioned' }), + 'LIMIT_ENFORCE_AGGREGATE': defineError({ code: 'LIMIT_ENFORCE_AGGREGATE', class: 'internal', http: 500, message: 'LIMIT_ENFORCE_AGGREGATE: limits_module with aggregate table not found for database_id {{arg0}} scope "{{arg1}}"', positional: ['arg0', 'arg1'] }), + 'LIMIT_ENFORCE_COUNTER': defineError({ code: 'LIMIT_ENFORCE_COUNTER', class: 'internal', http: 500, message: 'LIMIT_ENFORCE_COUNTER: limits_module not found for database_id {{arg0}} with scope {{arg1}}', positional: ['arg0', 'arg1'] }), + 'LIMIT_ENFORCE_FEATURE': defineError({ code: 'LIMIT_ENFORCE_FEATURE', class: 'internal', http: 500, message: 'LIMIT_ENFORCE_FEATURE: cap_check_trigger not configured in limits_module for scope {{arg0}}', positional: ['arg0'] }), + 'LIMIT_ENFORCE_RATE': defineError({ code: 'LIMIT_ENFORCE_RATE', class: 'internal', http: 500, message: 'LIMIT_ENFORCE_RATE: rate_limit_meters_module with check_rate_limit_function not found for database_id {{arg0}}', positional: ['arg0'] }), + 'LIMIT_REACHED': defineError({ code: 'LIMIT_REACHED', class: 'public', http: 429, message: 'Limit reached.' }), + 'LIMIT_TRACK_USAGE': defineError({ code: 'LIMIT_TRACK_USAGE', class: 'internal', http: 500, message: 'LIMIT_TRACK_USAGE: billing_module with record_usage_function not found for database_id {{arg0}}', positional: ['arg0'] }), + 'LIMIT_TRIGGER_ARGS': defineError({ code: 'LIMIT_TRIGGER_ARGS', class: 'internal', http: 500, message: 'LIMIT_TRIGGER_ARGS ({{arg0}})', positional: ['arg0'] }), + 'LIMIT_WARNING_AGGREGATE': defineError({ code: 'LIMIT_WARNING_AGGREGATE', class: 'internal', http: 500, message: 'LIMIT_WARNING_AGGREGATE: aggregate table not provisioned for database_id {{arg0}} scope "{{arg1}}"', positional: ['arg0', 'arg1'] }), + 'LIMIT_WARNING_COUNTER': defineError({ code: 'LIMIT_WARNING_COUNTER', class: 'internal', http: 500, message: 'LIMIT_WARNING_COUNTER: limit_warnings table not provisioned for database_id {{arg0}} with scope {{arg1}}', positional: ['arg0', 'arg1'] }), + 'LIMIT_WARNING_RATE': defineError({ code: 'LIMIT_WARNING_RATE', class: 'internal', http: 500, message: 'LIMIT_WARNING_RATE: limit_warnings table not provisioned for database_id {{arg0}}', positional: ['arg0'] }), + 'MANAGED_DOMAIN_PUBLISH_FORBIDDEN': defineError({ code: 'MANAGED_DOMAIN_PUBLISH_FORBIDDEN', class: 'public', http: 403, message: 'Managed domain publish forbidden.' }), + 'MEMBERSHIP_NOT_FOUND': defineError({ code: 'MEMBERSHIP_NOT_FOUND', class: 'public', http: 404, message: 'Membership not found.' }), + 'MEMBERSHIP_TYPE_MUST_BE_INT': defineError({ code: 'MEMBERSHIP_TYPE_MUST_BE_INT', class: 'internal', http: 500, message: 'MEMBERSHIP_TYPE_MUST_BE_INT (got {{arg0}}; use entity_type for scope strings)', positional: ['arg0'] }), + 'METER_RATE_LIMIT': defineError({ code: 'METER_RATE_LIMIT', class: 'internal', http: 500, message: 'METER_RATE_LIMIT: unknown event {{arg0}}, expected INSERT or UPDATE', positional: ['arg0'] }), + 'MFA_CHALLENGE_EXPIRED': defineError({ code: 'MFA_CHALLENGE_EXPIRED', class: 'public', http: 403, message: 'Mfa challenge expired.' }), + 'MFA_REQUIRED': defineError({ code: 'MFA_REQUIRED', class: 'public', http: 403, message: 'Mfa required.' }), + 'MISSING_FIXTURE_TYPE': defineError({ code: 'MISSING_FIXTURE_TYPE', class: 'internal', http: 500, message: 'Missing fixture type.' }), + 'MISSING_REQUIRED_FIELD': defineError({ code: 'MISSING_REQUIRED_FIELD', class: 'internal', http: 500, message: 'MISSING_REQUIRED_FIELD: node_type "{{arg0}}" requires field "{{arg1}}" but it is NULL after applying defaults.', positional: ['arg0', 'arg1'] }), + 'MISSING_RLS': defineError({ code: 'MISSING_RLS', class: 'internal', http: 500, message: 'MISSING_RLS: The following infrastructure tables have no RLS policies: {{arg0}}', positional: ['arg0'] }), + 'MODULES_HASH_DUPLICATE_ENTRY': defineError({ code: 'MODULES_HASH_DUPLICATE_ENTRY', class: 'internal', http: 500, message: 'MODULES_HASH_DUPLICATE_ENTRY: duplicate module entries in {{arg0}}', positional: ['arg0'] }), + 'MODULES_HASH_INVALID_ENTRY': defineError({ code: 'MODULES_HASH_INVALID_ENTRY', class: 'internal', http: 500, message: 'MODULES_HASH_INVALID_ENTRY: expected string or [name, options?], got {{arg0}}', positional: ['arg0'] }), + 'MODULES_HASH_INVALID_INPUT': defineError({ code: 'MODULES_HASH_INVALID_INPUT', class: 'internal', http: 500, message: 'MODULES_HASH_INVALID_INPUT: modules must be a jsonb array, got {{arg0}}', positional: ['arg0'] }), + 'MODULE_SECURITY': defineError({ code: 'MODULE_SECURITY', class: 'internal', http: 500, message: 'MODULE_SECURITY: policy entry missing $type' }), + 'NONEXISTENT_TYPE': defineError({ code: 'NONEXISTENT_TYPE', class: 'internal', http: 500, message: 'Nonexistent type.' }), + 'NOT_AUTHENTICATED': defineError({ code: 'NOT_AUTHENTICATED', class: 'public', http: 401, message: 'Not authenticated.' }), + 'NOT_FOUND': defineError({ code: 'NOT_FOUND', class: 'public', http: 404, message: 'Not found.', positional: ['arg0'] }), + 'NOT_ORG_ADMIN': defineError({ code: 'NOT_ORG_ADMIN', class: 'internal', http: 500, message: 'Not org admin.' }), + 'NOT_ORG_PRINCIPAL': defineError({ code: 'NOT_ORG_PRINCIPAL', class: 'internal', http: 500, message: 'Not org principal.' }), + 'NOT_OWNER': defineError({ code: 'NOT_OWNER', class: 'internal', http: 500, message: 'Not owner.' }), + 'NO_PRIMARY_IDENTIFIER': defineError({ code: 'NO_PRIMARY_IDENTIFIER', class: 'internal', http: 500, message: 'No primary identifier.' }), + 'NULL_VALUES_DISALLOWED': defineError({ code: 'NULL_VALUES_DISALLOWED', class: 'public', http: 400, message: 'Null values disallowed.' }), + 'OBJECT_NOT_FOUND': defineError({ code: 'OBJECT_NOT_FOUND', class: 'public', http: 404, message: 'Object not found.', positional: ['arg0'] }), + 'OBJECT_NO_UPDATE': defineError({ code: 'OBJECT_NO_UPDATE', class: 'public', http: 400, message: 'Object no update.' }), + 'ORG_API_KEY_NOT_FOUND': defineError({ code: 'ORG_API_KEY_NOT_FOUND', class: 'internal', http: 500, message: 'Org api key not found.' }), + 'OWNER_FIELD_NOT_FOUND': defineError({ code: 'OWNER_FIELD_NOT_FOUND', class: 'internal', http: 500, message: 'Owner field not found.' }), + 'OWNER_FIELD_NOT_IN_OWNER_TABLE': defineError({ code: 'OWNER_FIELD_NOT_IN_OWNER_TABLE', class: 'internal', http: 500, message: 'Owner field not in owner table.' }), + 'PARENT_ENTITY_TYPE_NOT_FOUND': defineError({ code: 'PARENT_ENTITY_TYPE_NOT_FOUND', class: 'internal', http: 500, message: 'Parent entity type not found.' }), + 'PASSWORD_INSECURE': defineError({ code: 'PASSWORD_INSECURE', class: 'public', http: 400, message: 'This password is not secure enough. Please choose a stronger password.' }), + 'PASSWORD_LEN': defineError({ code: 'PASSWORD_LEN', class: 'public', http: 400, message: 'Password must be between 8 and 63 characters long.' }), + 'PASSWORD_RESET_LOCKED_EXCEED_ATTEMPTS': defineError({ code: 'PASSWORD_RESET_LOCKED_EXCEED_ATTEMPTS', class: 'public', http: 423, message: 'Password reset locked exceed attempts.' }), + 'PASSWORD_SIGN_IN_DISABLED': defineError({ code: 'PASSWORD_SIGN_IN_DISABLED', class: 'public', http: 403, message: 'Password sign in disabled.' }), + 'PASSWORD_SIGN_UP_DISABLED': defineError({ code: 'PASSWORD_SIGN_UP_DISABLED', class: 'public', http: 403, message: 'Password sign up disabled.' }), + 'PLATFORM_DATABASE_NOT_REGISTERED': defineError({ code: 'PLATFORM_DATABASE_NOT_REGISTERED', class: 'internal', http: 500, message: 'PLATFORM_DATABASE_NOT_REGISTERED: no metaschema_public.database row has platform = true' }), + 'PLATFORM_FLAG_IMMUTABLE': defineError({ code: 'PLATFORM_FLAG_IMMUTABLE', class: 'internal', http: 500, message: 'PLATFORM_FLAG_IMMUTABLE: metaschema_public.database.platform cannot be changed once set' }), + 'POOL_BOOTSTRAP_INCOMPLETE': defineError({ code: 'POOL_BOOTSTRAP_INCOMPLETE', class: 'internal', http: 500, message: 'POOL_BOOTSTRAP_INCOMPLETE: pool row {{arg0}} is missing database_id or claimed_by', positional: ['arg0'] }), + 'POOL_CONFIG_NOT_FOUND': defineError({ code: 'POOL_CONFIG_NOT_FOUND', class: 'internal', http: 500, message: 'POOL_CONFIG_NOT_FOUND: no db_pool_config for preset {{arg0}}', positional: ['arg0'] }), + 'POOL_ROW_NOT_FOUND': defineError({ code: 'POOL_ROW_NOT_FOUND', class: 'internal', http: 500, message: 'POOL_ROW_NOT_FOUND: no db_pool row with id {{arg0}}', positional: ['arg0'] }), + 'PRESET_MODULES_INVALID': defineError({ code: 'PRESET_MODULES_INVALID', class: 'internal', http: 500, message: 'PRESET_MODULES_INVALID: preset {{arg0}} definition.modules must be a jsonb array', positional: ['arg0'] }), + 'PRESET_NOT_FOUND': defineError({ code: 'PRESET_NOT_FOUND', class: 'internal', http: 500, message: 'PRESET_NOT_FOUND: no active db_preset with slug {{arg0}}', positional: ['arg0'] }), + 'PRIMARY_AUTH_METHOD_MISMATCH': defineError({ code: 'PRIMARY_AUTH_METHOD_MISMATCH', class: 'public', http: 400, message: 'Primary auth method mismatch.' }), + 'PRIMARY_REQUIRES_VERIFIED': defineError({ code: 'PRIMARY_REQUIRES_VERIFIED', class: 'internal', http: 500, message: 'Primary requires verified.' }), + 'PRINCIPAL_CANNOT_CREATE_API_KEY': defineError({ code: 'PRINCIPAL_CANNOT_CREATE_API_KEY', class: 'internal', http: 500, message: 'Principal cannot create api key.' }), + 'PRINCIPAL_CANNOT_CREATE_PRINCIPAL': defineError({ code: 'PRINCIPAL_CANNOT_CREATE_PRINCIPAL', class: 'internal', http: 500, message: 'Principal cannot create principal.' }), + 'PRINCIPAL_CANNOT_DELETE_PRINCIPAL': defineError({ code: 'PRINCIPAL_CANNOT_DELETE_PRINCIPAL', class: 'internal', http: 500, message: 'Principal cannot delete principal.' }), + 'PRINCIPAL_CANNOT_REVOKE_API_KEY': defineError({ code: 'PRINCIPAL_CANNOT_REVOKE_API_KEY', class: 'internal', http: 500, message: 'Principal cannot revoke api key.' }), + 'PRINCIPAL_NOT_FOUND': defineError({ code: 'PRINCIPAL_NOT_FOUND', class: 'internal', http: 500, message: 'Principal not found.' }), + 'PRINCIPAL_NOT_IN_ORG': defineError({ code: 'PRINCIPAL_NOT_IN_ORG', class: 'internal', http: 500, message: 'Principal not in org.' }), + 'PRINCIPAL_NOT_OWNED': defineError({ code: 'PRINCIPAL_NOT_OWNED', class: 'internal', http: 500, message: 'Principal not owned.' }), + 'PROFILE_ASSIGNMENT_REQUIRES_EMAIL_INVITE': defineError({ code: 'PROFILE_ASSIGNMENT_REQUIRES_EMAIL_INVITE', class: 'public', http: 403, message: 'Profile assignment requires email invite.' }), + 'PROFILE_EXCEEDS_PERMISSIONS': defineError({ code: 'PROFILE_EXCEEDS_PERMISSIONS', class: 'public', http: 403, message: 'Profile exceeds permissions.' }), + 'PROFILE_NOT_FOUND': defineError({ code: 'PROFILE_NOT_FOUND', class: 'public', http: 404, message: 'Profile not found.' }), + 'PROVISION_CHECK_CONSTRAINT': defineError({ code: 'PROVISION_CHECK_CONSTRAINT', class: 'internal', http: 500, message: 'PROVISION_CHECK_CONSTRAINT: unknown $type "{{arg0}}" for constraint "{{arg1}}" on table "{{arg2}}". Supported: CheckOneOf, CheckGreaterThan, CheckLessThan, CheckNotEqual', positional: ['arg0', 'arg1', 'arg2'] }), + 'PROVISION_FULL_TEXT_SEARCH': defineError({ code: 'PROVISION_FULL_TEXT_SEARCH', class: 'internal', http: 500, message: 'PROVISION_FULL_TEXT_SEARCH: tsvector field "{{arg0}}" not found on table "{{arg1}}"', positional: ['arg0', 'arg1'] }), + 'PROVISION_INDEX': defineError({ code: 'PROVISION_INDEX', class: 'internal', http: 500, message: 'PROVISION_INDEX: "columns" array or "column" string is required' }), + 'PROVISION_INVALID_DATABASE_ID': defineError({ code: 'PROVISION_INVALID_DATABASE_ID', class: 'internal', http: 500, message: 'PROVISION_INVALID_DATABASE_ID: database_id may only be pre-set for a warm pool claim owned by the requester' }), + 'PROVISION_RELATION': defineError({ code: 'PROVISION_RELATION', class: 'internal', http: 500, message: 'PROVISION_RELATION: delete_action is required for RelationBelongsTo (e.g., r=RESTRICT, c=CASCADE, n=SET NULL)' }), + 'PROVISION_TABLE': defineError({ code: 'PROVISION_TABLE', class: 'internal', http: 500, message: 'PROVISION_TABLE: invalid index access method "{{arg0}}" for field "{{arg1}}", expected btree|gin|gist|brin|hash', positional: ['arg0', 'arg1'] }), + 'PROVISION_UNIQUE_CONSTRAINT': defineError({ code: 'PROVISION_UNIQUE_CONSTRAINT', class: 'internal', http: 500, message: 'PROVISION_UNIQUE_CONSTRAINT: "columns" array is required for table "{{arg0}}"', positional: ['arg0'] }), + 'RATE_LIMIT_EXCEEDED': defineError({ code: 'RATE_LIMIT_EXCEEDED', class: 'public', http: 429, message: 'Rate limit exceeded.' }), + 'REF_NOT_FOUND': defineError({ code: 'REF_NOT_FOUND', class: 'public', http: 404, message: 'Ref not found.' }), + 'RELATION_PROVISION': defineError({ code: 'RELATION_PROVISION', class: 'internal', http: 500, message: 'RELATION_PROVISION: delete_action is required for RelationBelongsTo (e.g., r=RESTRICT, c=CASCADE, n=SET NULL)' }), + 'REPO_EXISTS': defineError({ code: 'REPO_EXISTS', class: 'internal', http: 500, message: 'Repo exists.' }), + 'REQUEST_DATABASE_INVALID_INPUT': defineError({ code: 'REQUEST_DATABASE_INVALID_INPUT', class: 'internal', http: 500, message: 'REQUEST_DATABASE_INVALID_INPUT: provide exactly one of preset_slug or modules' }), + 'REQUEST_DATABASE_UNKNOWN_PRESET': defineError({ code: 'REQUEST_DATABASE_UNKNOWN_PRESET', class: 'internal', http: 500, message: 'REQUEST_DATABASE_UNKNOWN_PRESET: no active preset with slug {{arg0}}', positional: ['arg0'] }), + 'REQUIRES': defineError({ code: 'REQUIRES', class: 'internal', http: 500, message: 'REQUIRES (user_settings_module or user_settings_table_id) when has_settings_extension is enabled' }), + 'REQUIRES_ONE_OWNER': defineError({ code: 'REQUIRES_ONE_OWNER', class: 'public', http: 403, message: 'Requires one owner.' }), + 'RESOLVE_BLUEPRINT_FIELD': defineError({ code: 'RESOLVE_BLUEPRINT_FIELD', class: 'internal', http: 500, message: 'RESOLVE_BLUEPRINT_FIELD: field "{{arg0}}" not found on table {{arg1}} (database {{arg2}})', positional: ['arg0', 'arg1', 'arg2'] }), + 'RESOLVE_BLUEPRINT_TABLE': defineError({ code: 'RESOLVE_BLUEPRINT_TABLE', class: 'internal', http: 500, message: 'RESOLVE_BLUEPRINT_TABLE: table "{{arg0}}" not found in database {{arg1}} (not in blueprint tables and not in existing tables)', positional: ['arg0', 'arg1'] }), + 'RESOURCE_MODULE': defineError({ code: 'RESOURCE_MODULE', class: 'internal', http: 500, message: 'RESOURCE_MODULE: merkle_store_module {{arg0}} not found', positional: ['arg0'] }), + 'ROUTE_BINDING_SYNC': defineError({ code: 'ROUTE_BINDING_SYNC', class: 'internal', http: 500, message: 'ROUTE_BINDING_SYNC: unknown event {{arg0}}, expected UPSERT or DELETE', positional: ['arg0'] }), + 'ROUTE_TARGET_NOT_VISIBLE': defineError({ code: 'ROUTE_TARGET_NOT_VISIBLE', class: 'internal', http: 500, message: 'Route target not visible.' }), + 'ROUTE_TARGET_REQUIRED': defineError({ code: 'ROUTE_TARGET_REQUIRED', class: 'internal', http: 500, message: 'Route target required.' }), + 'SEARCH_BM25': defineError({ code: 'SEARCH_BM25', class: 'internal', http: 500, message: 'SEARCH_BM25: field "{{arg0}}" not found on table "{{arg1}}". The field must already exist.', positional: ['arg0', 'arg1'] }), + 'SEARCH_FULL_TEXT': defineError({ code: 'SEARCH_FULL_TEXT', class: 'internal', http: 500, message: 'SEARCH_FULL_TEXT: source_fields is required and must be a non-empty array of {field, weight, lang} objects' }), + 'SEARCH_SPATIAL': defineError({ code: 'SEARCH_SPATIAL', class: 'internal', http: 500, message: 'SEARCH_SPATIAL: unknown geometry_type {{arg0}}, expected Point|LineString|Polygon|MultiPoint|MultiLineString|MultiPolygon|GeometryCollection|Geometry', positional: ['arg0'] }), + 'SEARCH_SPATIAL_AGGREGATE': defineError({ code: 'SEARCH_SPATIAL_AGGREGATE', class: 'internal', http: 500, message: 'SEARCH_SPATIAL_AGGREGATE: unknown geometry_type {{arg0}}, expected Point|LineString|Polygon|MultiPoint|MultiLineString|MultiPolygon|GeometryCollection|Geometry', positional: ['arg0'] }), + 'SEARCH_TRGM': defineError({ code: 'SEARCH_TRGM', class: 'internal', http: 500, message: 'SEARCH_TRGM: "fields" parameter is required and must be a JSON array' }), + 'SEARCH_UNIFIED': defineError({ code: 'SEARCH_UNIFIED', class: 'internal', http: 500, message: 'SEARCH_UNIFIED: trgm field "{{arg0}}" not found on table "{{arg1}}"', positional: ['arg0', 'arg1'] }), + 'SEARCH_VECTOR': defineError({ code: 'SEARCH_VECTOR', class: 'internal', http: 500, message: 'SEARCH_VECTOR: unknown index_method {{arg0}}, expected hnsw|ivfflat', positional: ['arg0'] }), + 'SECURITY': defineError({ code: 'SECURITY', class: 'internal', http: 500, message: 'SECURITY: obj_table_id references a table from a different database (expected {{arg0}}, got {{arg1}})', positional: ['arg0', 'arg1'] }), + 'SERVICES_API_BRIDGE': defineError({ code: 'SERVICES_API_BRIDGE', class: 'internal', http: 500, message: 'SERVICES_API_BRIDGE: unknown event {{arg0}}, expected UPSERT or DELETE', positional: ['arg0'] }), + 'SERVICES_DOMAIN_BRIDGE': defineError({ code: 'SERVICES_DOMAIN_BRIDGE', class: 'internal', http: 500, message: 'SERVICES_DOMAIN_BRIDGE: unknown event {{arg0}}, expected UPSERT or DELETE', positional: ['arg0'] }), + 'SERVICES_SITE_BRIDGE': defineError({ code: 'SERVICES_SITE_BRIDGE', class: 'internal', http: 500, message: 'SERVICES_SITE_BRIDGE: unknown event {{arg0}}, expected UPSERT or DELETE', positional: ['arg0'] }), + 'SESSION_NOT_FOUND': defineError({ code: 'SESSION_NOT_FOUND', class: 'public', http: 404, message: 'Session not found.' }), + 'SIGN_UP_DISABLED': defineError({ code: 'SIGN_UP_DISABLED', class: 'public', http: 403, message: 'New registrations are currently disabled.' }), + 'SINGLETON_TABLE': defineError({ code: 'SINGLETON_TABLE', class: 'public', http: 400, message: 'Singleton table.' }), + 'SMS_SIGN_IN_DISABLED': defineError({ code: 'SMS_SIGN_IN_DISABLED', class: 'public', http: 403, message: 'Sms sign in disabled.' }), + 'SMS_SIGN_UP_DISABLED': defineError({ code: 'SMS_SIGN_UP_DISABLED', class: 'public', http: 403, message: 'Sms sign up disabled.' }), + 'SOURCE_EMAILS_NOT_FOUND': defineError({ code: 'SOURCE_EMAILS_NOT_FOUND', class: 'internal', http: 500, message: 'Source emails not found.' }), + 'SOURCE_SECRETS_NOT_FOUND': defineError({ code: 'SOURCE_SECRETS_NOT_FOUND', class: 'internal', http: 500, message: 'Source secrets not found.' }), + 'SOURCE_USERS_NOT_FOUND': defineError({ code: 'SOURCE_USERS_NOT_FOUND', class: 'internal', http: 500, message: 'Source users not found.' }), + 'STEP_UP_REQUIRED': defineError({ code: 'STEP_UP_REQUIRED', class: 'public', http: 403, message: 'Please verify your identity to continue.' }), + 'STEP_UP_REQUIRED_PASSWORD': defineError({ code: 'STEP_UP_REQUIRED_PASSWORD', class: 'public', http: 403, message: 'Step up required password.' }), + 'STEP_UP_REQUIRED_PASSWORD_OR_MFA': defineError({ code: 'STEP_UP_REQUIRED_PASSWORD_OR_MFA', class: 'public', http: 403, message: 'Step up required password or mfa.' }), + 'SUPER_CONSTRUCTIVE_REQUIRED': defineError({ code: 'SUPER_CONSTRUCTIVE_REQUIRED', class: 'internal', http: 500, message: 'SUPER_CONSTRUCTIVE_REQUIRED: {{arg0}} requires constructive.allow_super_constructive GUC', positional: ['arg0'] }), + 'TABLE_MODULE': defineError({ code: 'TABLE_MODULE', class: 'internal', http: 500, message: 'TABLE_MODULE: table not found for table_id {{arg0}}', positional: ['arg0'] }), + 'TARGET_EMAILS_NOT_FOUND': defineError({ code: 'TARGET_EMAILS_NOT_FOUND', class: 'internal', http: 500, message: 'Target emails not found.' }), + 'TARGET_FIELD_NOT_FOUND': defineError({ code: 'TARGET_FIELD_NOT_FOUND', class: 'internal', http: 500, message: 'Target field not found.' }), + 'TARGET_FIELD_NOT_IN_TARGET_TABLE': defineError({ code: 'TARGET_FIELD_NOT_IN_TARGET_TABLE', class: 'internal', http: 500, message: 'Target field not in target table.' }), + 'TARGET_MEMBERSHIPS_NOT_FOUND': defineError({ code: 'TARGET_MEMBERSHIPS_NOT_FOUND', class: 'internal', http: 500, message: 'Target memberships not found.' }), + 'TARGET_SECRETS_NOT_FOUND': defineError({ code: 'TARGET_SECRETS_NOT_FOUND', class: 'internal', http: 500, message: 'Target secrets not found.' }), + 'TARGET_USERS_NOT_FOUND': defineError({ code: 'TARGET_USERS_NOT_FOUND', class: 'internal', http: 500, message: 'Target users not found.' }), + 'THROWN_ERROR': defineError({ code: 'THROWN_ERROR', class: 'internal', http: 500, message: 'THROWN_ERROR ({{arg0}})', positional: ['arg0'] }), + 'TOO_MANY_REQUESTS': defineError({ code: 'TOO_MANY_REQUESTS', class: 'public', http: 429, message: 'Too many requests.' }), + 'TOTP_ALREADY_ENABLED': defineError({ code: 'TOTP_ALREADY_ENABLED', class: 'public', http: 409, message: 'Totp already enabled.' }), + 'TOTP_NOT_ENABLED': defineError({ code: 'TOTP_NOT_ENABLED', class: 'public', http: 400, message: 'Totp not enabled.' }), + 'TOTP_SETUP_NOT_INITIATED': defineError({ code: 'TOTP_SETUP_NOT_INITIATED', class: 'public', http: 400, message: 'Totp setup not initiated.' }), + 'TRANSFER_NOT_ALLOWED_IN_POOLED_MODE': defineError({ code: 'TRANSFER_NOT_ALLOWED_IN_POOLED_MODE', class: 'internal', http: 500, message: 'Transfer not allowed in pooled mode.' }), + 'UNAUTHENTICATED': defineError({ code: 'UNAUTHENTICATED', class: 'public', http: 401, message: 'Unauthenticated.' }), + 'UNKNOWN_POLICY_TYPE': defineError({ code: 'UNKNOWN_POLICY_TYPE', class: 'internal', http: 500, message: 'UNKNOWN_POLICY_TYPE {{arg0}}', positional: ['arg0'] }), + 'UNKNOWN_VIEW_TYPE': defineError({ code: 'UNKNOWN_VIEW_TYPE', class: 'internal', http: 500, message: 'UNKNOWN_VIEW_TYPE {{arg0}}', positional: ['arg0'] }), + 'UNSUPPORTED': defineError({ code: 'UNSUPPORTED', class: 'internal', http: 500, message: 'UNSUPPORTED POLICY ({{arg0}})', positional: ['arg0'] }), + 'UNSUPPORTED_EXPRESSION': defineError({ code: 'UNSUPPORTED_EXPRESSION', class: 'internal', http: 500, message: 'UNSUPPORTED_EXPRESSION {{arg0}}', positional: ['arg0'] }), + 'UNSUPPORTED_VIEW_RULE_ACTION': defineError({ code: 'UNSUPPORTED_VIEW_RULE_ACTION', class: 'internal', http: 500, message: 'UNSUPPORTED_VIEW_RULE_ACTION {{arg0}}', positional: ['arg0'] }), + 'UUID': defineError({ code: 'UUID', class: 'internal', http: 500, message: 'UUID seed is NULL on table {{arg0}}', positional: ['arg0'] }), + 'VALIDATE_BLUEPRINT': defineError({ code: 'VALIDATE_BLUEPRINT', class: 'internal', http: 500, message: 'VALIDATE_BLUEPRINT: definition must contain a non-empty "tables" array, a non-empty "entity_types" array, or at least one of relations/indexes/full_text_searches/unique_constraints' }), + 'WEBAUTHN_CREDENTIAL_NOT_FOUND': defineError({ code: 'WEBAUTHN_CREDENTIAL_NOT_FOUND', class: 'public', http: 404, message: 'Webauthn credential not found.' }), + 'WEBAUTHN_REGISTER_CHALLENGE_NOT_FOUND_OR_EXPIRED': defineError({ code: 'WEBAUTHN_REGISTER_CHALLENGE_NOT_FOUND_OR_EXPIRED', class: 'public', http: 400, message: 'Webauthn register challenge not found or expired.' }), + 'WEBAUTHN_SIGN_IN_CHALLENGE_NOT_FOUND_OR_EXPIRED': defineError({ code: 'WEBAUTHN_SIGN_IN_CHALLENGE_NOT_FOUND_OR_EXPIRED', class: 'public', http: 400, message: 'Webauthn sign in challenge not found or expired.' }), + 'WEBAUTHN_SIGN_IN_DISABLED': defineError({ code: 'WEBAUTHN_SIGN_IN_DISABLED', class: 'public', http: 403, message: 'Webauthn sign in disabled.' }), + 'WEBAUTHN_SIGN_UP_DISABLED': defineError({ code: 'WEBAUTHN_SIGN_UP_DISABLED', class: 'public', http: 403, message: 'Webauthn sign up disabled.' }), + 'WITH_CHECK_NOT_ALLOWED_FOR_': defineError({ code: 'WITH_CHECK_NOT_ALLOWED_FOR_', class: 'internal', http: 500, message: 'WITH_CHECK_NOT_ALLOWED_FOR_{{arg0}} (for INSERT, the policy node itself is the check)', positional: ['arg0'] }), +}; + +/** Audit metadata for each generated code. */ +export const GENERATED_CODE_META: Record = { + 'ACCOUNT_DISABLED': { class: 'public', dynamic: false, generatedOnly: false }, + 'ACCOUNT_EXISTS': { class: 'public', dynamic: false, generatedOnly: false }, + 'ACCOUNT_LOCKED_EXCEED_ATTEMPTS': { class: 'public', dynamic: false, generatedOnly: false }, + 'ACCOUNT_NOT_FOUND': { class: 'public', dynamic: false, generatedOnly: false }, + 'ALREADY_SCHEDULED': { class: 'public', dynamic: false, generatedOnly: false }, + 'ALTER_TABLE_ADD_COLUMN': { class: 'internal', dynamic: true, generatedOnly: false }, + 'API_KEYS_DISABLED': { class: 'public', dynamic: false, generatedOnly: true }, + 'API_KEY_LIMIT_REACHED': { class: 'public', dynamic: false, generatedOnly: false }, + 'API_KEY_NOT_FOUND': { class: 'public', dynamic: false, generatedOnly: false }, + 'APPLY_RLS': { class: 'internal', dynamic: true, generatedOnly: false }, + 'APPLY_RLS_BAD_ARGS': { class: 'internal', dynamic: false, generatedOnly: false }, + 'APPLY_TABLE_STEP_UP': { class: 'internal', dynamic: true, generatedOnly: false }, + 'APP_COMPONENT_NOT_VISIBLE': { class: 'internal', dynamic: false, generatedOnly: false }, + 'APP_COMPONENT_TARGET_REQUIRED': { class: 'internal', dynamic: false, generatedOnly: false }, + 'APP_SCOPE_FRAMES_SCOPE_REQUIRED': { class: 'internal', dynamic: false, generatedOnly: false }, + 'APP_SCOPE_LOCAL_FRAMES_DEPTH_EXCEEDED': { class: 'internal', dynamic: true, generatedOnly: false }, + 'APP_SCOPE_LOCAL_FRAMES_SCOPE_REQUIRED': { class: 'internal', dynamic: false, generatedOnly: false }, + 'ASSIGN_PROFILES_PERMISSION_REQUIRED': { class: 'public', dynamic: false, generatedOnly: false }, + 'AUTHZ_COLUMN_SECURITY': { class: 'internal', dynamic: true, generatedOnly: false }, + 'AUTH_METHOD_NOT_ALLOWED': { class: 'public', dynamic: false, generatedOnly: false }, + 'BAD_CASING_FUNC': { class: 'internal', dynamic: true, generatedOnly: false }, + 'BAD_EXPRESSION': { class: 'internal', dynamic: true, generatedOnly: false }, + 'BAD_FIELD_INPUT': { class: 'internal', dynamic: true, generatedOnly: false }, + 'BAD_FIELD_ORDERING_CROSS_TABLE': { class: 'internal', dynamic: false, generatedOnly: false }, + 'BAD_PRIVILEGE_FOR_POLICY': { class: 'internal', dynamic: false, generatedOnly: false }, + 'BAD_RLS_EXPRESSION': { class: 'internal', dynamic: true, generatedOnly: false }, + 'BAD_VIEW_EXPRESSION': { class: 'internal', dynamic: true, generatedOnly: false }, + 'BM25': { class: 'internal', dynamic: true, generatedOnly: false }, + 'BOOTSTRAP_DATABASE_MISSING': { class: 'internal', dynamic: false, generatedOnly: false }, + 'BOOTSTRAP_SOURCE_MISSING': { class: 'internal', dynamic: false, generatedOnly: false }, + 'BOOTSTRAP_TARGET_NOT_EMPTY': { class: 'internal', dynamic: false, generatedOnly: false }, + 'BUILD_CONDITION_EXPR': { class: 'internal', dynamic: true, generatedOnly: false }, + 'CANNOT_DISCONNECT_LAST_AUTH_METHOD': { class: 'public', dynamic: false, generatedOnly: false }, + 'CANNOT_REVOKE_CURRENT_SESSION': { class: 'public', dynamic: false, generatedOnly: false }, + 'CAP_CHECK_TRIGGER_ARGS': { class: 'internal', dynamic: true, generatedOnly: false }, + 'CATALOG_SYNC': { class: 'internal', dynamic: true, generatedOnly: false }, + 'COMMIT_NOT_FOUND': { class: 'internal', dynamic: false, generatedOnly: false }, + 'CONNECTED_ACCOUNT_NOT_FOUND': { class: 'public', dynamic: false, generatedOnly: true }, + 'CONSTRUCT_BLUEPRINT': { class: 'internal', dynamic: true, generatedOnly: false }, + 'CONST_TYPE_FIELDS_IMMUTABLE': { class: 'public', dynamic: false, generatedOnly: false }, + 'COPY_TEMPLATE_TO_BLUEPRINT': { class: 'internal', dynamic: true, generatedOnly: false }, + 'CREDIT_CODE_EXPIRED': { class: 'internal', dynamic: false, generatedOnly: false }, + 'CREDIT_CODE_MAX_REDEMPTIONS_REACHED': { class: 'internal', dynamic: false, generatedOnly: false }, + 'CREDIT_CODE_NOT_FOUND': { class: 'internal', dynamic: false, generatedOnly: false }, + 'CROSS_DATABASE_CHECK': { class: 'internal', dynamic: true, generatedOnly: false }, + 'CROSS_DATABASE_REF': { class: 'public', dynamic: true, generatedOnly: false }, + 'CSRF_TOKEN_REQUIRED': { class: 'public', dynamic: false, generatedOnly: false }, + 'DATABASE_FIELD_RESERVED_WORD': { class: 'internal', dynamic: false, generatedOnly: false }, + 'DATABASE_TABLE_RESERVED_WORD': { class: 'internal', dynamic: false, generatedOnly: false }, + 'DATA_BILLING_METER': { class: 'internal', dynamic: true, generatedOnly: false }, + 'DATA_COMPOSITE_FIELD': { class: 'internal', dynamic: true, generatedOnly: false }, + 'DATA_DIRECT_OWNER': { class: 'internal', dynamic: true, generatedOnly: false }, + 'DATA_DIRECT_OWNER_IN_ENTITY': { class: 'internal', dynamic: true, generatedOnly: false }, + 'DATA_FORCE_CURRENT_USER': { class: 'internal', dynamic: true, generatedOnly: false }, + 'DATA_GENERATED': { class: 'internal', dynamic: true, generatedOnly: false }, + 'DATA_I18N': { class: 'internal', dynamic: true, generatedOnly: false }, + 'DATA_ID': { class: 'internal', dynamic: true, generatedOnly: false }, + 'DATA_IMMUTABLE_FIELDS': { class: 'internal', dynamic: true, generatedOnly: false }, + 'DATA_INFLECTION': { class: 'internal', dynamic: true, generatedOnly: false }, + 'DATA_INHERIT_FROM_PARENT': { class: 'internal', dynamic: true, generatedOnly: false }, + 'DATA_JOB_TRIGGER': { class: 'internal', dynamic: true, generatedOnly: false }, + 'DATA_JSONB': { class: 'internal', dynamic: true, generatedOnly: false }, + 'DATA_MEMBERSHIP_BY_FIELD': { class: 'internal', dynamic: true, generatedOnly: false }, + 'DATA_MEMBER_OWNER': { class: 'internal', dynamic: true, generatedOnly: false }, + 'DATA_OWNED_FIELDS': { class: 'internal', dynamic: true, generatedOnly: false }, + 'DATA_PEOPLESTAMPS': { class: 'internal', dynamic: true, generatedOnly: false }, + 'DATA_PUBLISHABLE': { class: 'internal', dynamic: true, generatedOnly: false }, + 'DATA_REALTIME': { class: 'internal', dynamic: true, generatedOnly: false }, + 'DATA_SLUG': { class: 'internal', dynamic: true, generatedOnly: false }, + 'DATA_STATUS_FIELD': { class: 'internal', dynamic: true, generatedOnly: false }, + 'DATA_TAGS': { class: 'internal', dynamic: true, generatedOnly: false }, + 'DATA_TIMESTAMPS': { class: 'internal', dynamic: true, generatedOnly: false }, + 'DB_PRESET_MODULE': { class: 'internal', dynamic: true, generatedOnly: false }, + 'DELETE_FIRST': { class: 'public', dynamic: false, generatedOnly: false }, + 'DOMAIN_CHECK_CERT_NOT_ISSUING': { class: 'internal', dynamic: true, generatedOnly: false }, + 'DOMAIN_CHECK_CERT_UNKNOWN_DOMAIN': { class: 'internal', dynamic: true, generatedOnly: false }, + 'DOMAIN_ISSUE_CERT_BAD_ISSUER': { class: 'internal', dynamic: true, generatedOnly: false }, + 'DOMAIN_ISSUE_CERT_NOT_VERIFIED': { class: 'internal', dynamic: true, generatedOnly: false }, + 'DOMAIN_ISSUE_CERT_UNKNOWN_DOMAIN': { class: 'internal', dynamic: true, generatedOnly: false }, + 'DOMAIN_ISSUE_CHALLENGE_BAD_METHOD': { class: 'internal', dynamic: true, generatedOnly: false }, + 'DOMAIN_ISSUE_CHALLENGE_NO_OWNER': { class: 'internal', dynamic: true, generatedOnly: false }, + 'DOMAIN_ISSUE_CHALLENGE_UNKNOWN_DOMAIN': { class: 'internal', dynamic: true, generatedOnly: false }, + 'DOMAIN_RENEW_NOT_ACTIVE': { class: 'internal', dynamic: true, generatedOnly: false }, + 'DOMAIN_RENEW_UNKNOWN_DOMAIN': { class: 'internal', dynamic: true, generatedOnly: false }, + 'DOMAIN_REVOKE_UNKNOWN_DOMAIN': { class: 'internal', dynamic: true, generatedOnly: false }, + 'DOMAIN_VERIFY_BAD_METHOD': { class: 'internal', dynamic: true, generatedOnly: false }, + 'DOMAIN_VERIFY_NO_CHALLENGE': { class: 'internal', dynamic: true, generatedOnly: false }, + 'EMAIL_NOT_VERIFIED': { class: 'public', dynamic: false, generatedOnly: false }, + 'EMBEDDING_CHUNKS': { class: 'internal', dynamic: true, generatedOnly: false }, + 'ENQUEUE_SCOPE_REQUIRED': { class: 'internal', dynamic: false, generatedOnly: false }, + 'ENTITY_NOT_FOUND': { class: 'internal', dynamic: false, generatedOnly: false }, + 'ENTITY_TYPE_NOT_FOUND': { class: 'internal', dynamic: false, generatedOnly: false }, + 'ENTITY_TYPE_PROVISION': { class: 'internal', dynamic: true, generatedOnly: false }, + 'ENUM_VALUES_CANNOT_BE_REMOVED': { class: 'internal', dynamic: false, generatedOnly: false }, + 'ENUM_VALUES_CANNOT_BE_REORDERED': { class: 'internal', dynamic: false, generatedOnly: false }, + 'EVENT_REFERRAL': { class: 'internal', dynamic: true, generatedOnly: false }, + 'EVENT_TRACKER': { class: 'internal', dynamic: true, generatedOnly: false }, + 'EXPIRED_TOKEN': { class: 'public', dynamic: false, generatedOnly: false }, + 'EXTERNAL_MEMBERS_NOT_ALLOWED': { class: 'public', dynamic: false, generatedOnly: false }, + 'FEATURE_DISABLED': { class: 'public', dynamic: true, generatedOnly: false }, + 'FIELDS_MISMATCH': { class: 'internal', dynamic: true, generatedOnly: false }, + 'FULL_TEXT_SEARCH': { class: 'internal', dynamic: true, generatedOnly: false }, + 'FUNCTION_DEFINITION_INVALID_PAIR': { class: 'internal', dynamic: true, generatedOnly: false }, + 'FUNCTION_DEFINITION_NOT_FOUND': { class: 'internal', dynamic: true, generatedOnly: false }, + 'FUNCTION_SCHEDULES': { class: 'internal', dynamic: false, generatedOnly: false }, + 'FUNCTION_SCHEDULES_INTERVAL': { class: 'internal', dynamic: false, generatedOnly: false }, + 'FUNCTION_SCHEDULES_LIMIT': { class: 'internal', dynamic: false, generatedOnly: false }, + 'FUNCTION_SCHEDULE_SYNC': { class: 'internal', dynamic: true, generatedOnly: false }, + 'GRAPH_EXECUTION_MODULE': { class: 'internal', dynamic: true, generatedOnly: false }, + 'GRAPH_MODULE': { class: 'internal', dynamic: true, generatedOnly: false }, + 'GRAPH_VALIDATION_FAILED': { class: 'internal', dynamic: true, generatedOnly: false }, + 'GROUPS_REQ_ENTITIES': { class: 'public', dynamic: false, generatedOnly: false }, + 'GUARD_STEP_UP': { class: 'internal', dynamic: true, generatedOnly: false }, + 'HIERARCHY_CYCLE_DETECTED': { class: 'internal', dynamic: true, generatedOnly: false }, + 'HIERARCHY_DEPTH_EXCEEDED': { class: 'internal', dynamic: false, generatedOnly: false }, + 'HIERARCHY_INACTIVE_MEMBER': { class: 'internal', dynamic: true, generatedOnly: false }, + 'HIERARCHY_MEMBER_IN_USE': { class: 'internal', dynamic: true, generatedOnly: false }, + 'HOSTNAME_BINDING_SYNC': { class: 'internal', dynamic: true, generatedOnly: false }, + 'HTTP_ROUTE_TARGET_NOT_FOUND': { class: 'internal', dynamic: false, generatedOnly: false }, + 'HTTP_ROUTE_TARGET_OUTSIDE_SCOPE': { class: 'internal', dynamic: false, generatedOnly: false }, + 'IDENTITY_ACCOUNT_NOT_FOUND': { class: 'public', dynamic: false, generatedOnly: false }, + 'IDENTITY_ALREADY_LINKED': { class: 'public', dynamic: false, generatedOnly: false }, + 'IDENTITY_LINK_AVAILABLE': { class: 'public', dynamic: false, generatedOnly: false }, + 'IDENTITY_PROVIDER_NOT_CONFIGURED': { class: 'public', dynamic: false, generatedOnly: false }, + 'IDENTITY_PROVIDER_NOT_FOUND': { class: 'public', dynamic: false, generatedOnly: true }, + 'IDENTITY_PROVIDER_QUOTA_EXCEEDED': { class: 'public', dynamic: false, generatedOnly: false }, + 'IDENTITY_SIGN_IN_DISABLED': { class: 'public', dynamic: false, generatedOnly: true }, + 'IDENTITY_SIGN_UP_DISABLED': { class: 'public', dynamic: false, generatedOnly: true }, + 'IMMUTABLE_FIELD': { class: 'public', dynamic: false, generatedOnly: false }, + 'IMMUTABLE_PEOPLESTAMPS': { class: 'public', dynamic: false, generatedOnly: false }, + 'IMMUTABLE_PROPS': { class: 'public', dynamic: false, generatedOnly: false }, + 'IMMUTABLE_TIMESTAMPS': { class: 'public', dynamic: false, generatedOnly: false }, + 'INCORRECT_PASSWORD': { class: 'public', dynamic: false, generatedOnly: false }, + 'INSERT_GRAPH_EXECUTION_MODULE': { class: 'internal', dynamic: true, generatedOnly: false }, + 'INVALID_ACCESS_LEVEL': { class: 'internal', dynamic: false, generatedOnly: false }, + 'INVALID_BASE32': { class: 'internal', dynamic: false, generatedOnly: false }, + 'INVALID_CODE': { class: 'public', dynamic: false, generatedOnly: false }, + 'INVALID_CSRF_TOKEN': { class: 'public', dynamic: false, generatedOnly: true }, + 'INVALID_DEFAULT_LIMIT_ID': { class: 'internal', dynamic: false, generatedOnly: false }, + 'INVALID_FUNCTION_REF': { class: 'internal', dynamic: true, generatedOnly: false }, + 'INVALID_METER_ID': { class: 'internal', dynamic: false, generatedOnly: false }, + 'INVALID_MFA_CHALLENGE': { class: 'public', dynamic: false, generatedOnly: false }, + 'INVALID_MFA_LEVEL': { class: 'internal', dynamic: false, generatedOnly: false }, + 'INVALID_NODE_EDGES': { class: 'internal', dynamic: true, generatedOnly: false }, + 'INVALID_NODE_NODES': { class: 'internal', dynamic: true, generatedOnly: false }, + 'INVALID_NODE_PROPS': { class: 'internal', dynamic: true, generatedOnly: false }, + 'INVALID_ORGANIZATION': { class: 'internal', dynamic: false, generatedOnly: false }, + 'INVALID_TOKEN': { class: 'public', dynamic: false, generatedOnly: false }, + 'INVALID_USER': { class: 'public', dynamic: false, generatedOnly: false }, + 'INVALID_WORKER_ID': { class: 'internal', dynamic: false, generatedOnly: false }, + 'INVITE_EMAIL_NOT_FOUND': { class: 'public', dynamic: false, generatedOnly: false }, + 'INVITE_LIMIT': { class: 'public', dynamic: false, generatedOnly: false }, + 'INVITE_NOT_FOUND': { class: 'public', dynamic: false, generatedOnly: false }, + 'INVOCATION_QUOTA_GATE': { class: 'internal', dynamic: false, generatedOnly: false }, + 'INVOCATION_USAGE': { class: 'internal', dynamic: false, generatedOnly: false }, + 'LIMIT_ENFORCE_AGGREGATE': { class: 'internal', dynamic: true, generatedOnly: false }, + 'LIMIT_ENFORCE_COUNTER': { class: 'internal', dynamic: true, generatedOnly: false }, + 'LIMIT_ENFORCE_FEATURE': { class: 'internal', dynamic: true, generatedOnly: false }, + 'LIMIT_ENFORCE_RATE': { class: 'internal', dynamic: true, generatedOnly: false }, + 'LIMIT_REACHED': { class: 'public', dynamic: false, generatedOnly: false }, + 'LIMIT_TRACK_USAGE': { class: 'internal', dynamic: true, generatedOnly: false }, + 'LIMIT_TRIGGER_ARGS': { class: 'internal', dynamic: true, generatedOnly: false }, + 'LIMIT_WARNING_AGGREGATE': { class: 'internal', dynamic: true, generatedOnly: false }, + 'LIMIT_WARNING_COUNTER': { class: 'internal', dynamic: true, generatedOnly: false }, + 'LIMIT_WARNING_RATE': { class: 'internal', dynamic: true, generatedOnly: false }, + 'MANAGED_DOMAIN_PUBLISH_FORBIDDEN': { class: 'public', dynamic: false, generatedOnly: true }, + 'MEMBERSHIP_NOT_FOUND': { class: 'public', dynamic: false, generatedOnly: false }, + 'MEMBERSHIP_TYPE_MUST_BE_INT': { class: 'internal', dynamic: true, generatedOnly: false }, + 'METER_RATE_LIMIT': { class: 'internal', dynamic: true, generatedOnly: false }, + 'MFA_CHALLENGE_EXPIRED': { class: 'public', dynamic: false, generatedOnly: false }, + 'MFA_REQUIRED': { class: 'public', dynamic: false, generatedOnly: false }, + 'MISSING_FIXTURE_TYPE': { class: 'internal', dynamic: false, generatedOnly: false }, + 'MISSING_REQUIRED_FIELD': { class: 'internal', dynamic: true, generatedOnly: false }, + 'MISSING_RLS': { class: 'internal', dynamic: true, generatedOnly: false }, + 'MODULES_HASH_DUPLICATE_ENTRY': { class: 'internal', dynamic: true, generatedOnly: false }, + 'MODULES_HASH_INVALID_ENTRY': { class: 'internal', dynamic: true, generatedOnly: false }, + 'MODULES_HASH_INVALID_INPUT': { class: 'internal', dynamic: true, generatedOnly: false }, + 'MODULE_SECURITY': { class: 'internal', dynamic: false, generatedOnly: false }, + 'NONEXISTENT_TYPE': { class: 'internal', dynamic: false, generatedOnly: false }, + 'NOT_AUTHENTICATED': { class: 'public', dynamic: false, generatedOnly: false }, + 'NOT_FOUND': { class: 'public', dynamic: true, generatedOnly: false }, + 'NOT_ORG_ADMIN': { class: 'internal', dynamic: false, generatedOnly: false }, + 'NOT_ORG_PRINCIPAL': { class: 'internal', dynamic: false, generatedOnly: false }, + 'NOT_OWNER': { class: 'internal', dynamic: false, generatedOnly: false }, + 'NO_PRIMARY_IDENTIFIER': { class: 'internal', dynamic: false, generatedOnly: false }, + 'NULL_VALUES_DISALLOWED': { class: 'public', dynamic: false, generatedOnly: false }, + 'OBJECT_NOT_FOUND': { class: 'public', dynamic: true, generatedOnly: false }, + 'OBJECT_NO_UPDATE': { class: 'public', dynamic: false, generatedOnly: false }, + 'ORG_API_KEY_NOT_FOUND': { class: 'internal', dynamic: false, generatedOnly: false }, + 'OWNER_FIELD_NOT_FOUND': { class: 'internal', dynamic: false, generatedOnly: false }, + 'OWNER_FIELD_NOT_IN_OWNER_TABLE': { class: 'internal', dynamic: false, generatedOnly: false }, + 'PARENT_ENTITY_TYPE_NOT_FOUND': { class: 'internal', dynamic: false, generatedOnly: false }, + 'PASSWORD_INSECURE': { class: 'public', dynamic: false, generatedOnly: false }, + 'PASSWORD_LEN': { class: 'public', dynamic: false, generatedOnly: false }, + 'PASSWORD_RESET_LOCKED_EXCEED_ATTEMPTS': { class: 'public', dynamic: false, generatedOnly: true }, + 'PASSWORD_SIGN_IN_DISABLED': { class: 'public', dynamic: false, generatedOnly: true }, + 'PASSWORD_SIGN_UP_DISABLED': { class: 'public', dynamic: false, generatedOnly: true }, + 'PLATFORM_DATABASE_NOT_REGISTERED': { class: 'internal', dynamic: false, generatedOnly: false }, + 'PLATFORM_FLAG_IMMUTABLE': { class: 'internal', dynamic: false, generatedOnly: false }, + 'POOL_BOOTSTRAP_INCOMPLETE': { class: 'internal', dynamic: true, generatedOnly: false }, + 'POOL_CONFIG_NOT_FOUND': { class: 'internal', dynamic: true, generatedOnly: false }, + 'POOL_ROW_NOT_FOUND': { class: 'internal', dynamic: true, generatedOnly: false }, + 'PRESET_MODULES_INVALID': { class: 'internal', dynamic: true, generatedOnly: false }, + 'PRESET_NOT_FOUND': { class: 'internal', dynamic: true, generatedOnly: false }, + 'PRIMARY_AUTH_METHOD_MISMATCH': { class: 'public', dynamic: false, generatedOnly: false }, + 'PRIMARY_REQUIRES_VERIFIED': { class: 'internal', dynamic: false, generatedOnly: false }, + 'PRINCIPAL_CANNOT_CREATE_API_KEY': { class: 'internal', dynamic: false, generatedOnly: false }, + 'PRINCIPAL_CANNOT_CREATE_PRINCIPAL': { class: 'internal', dynamic: false, generatedOnly: false }, + 'PRINCIPAL_CANNOT_DELETE_PRINCIPAL': { class: 'internal', dynamic: false, generatedOnly: false }, + 'PRINCIPAL_CANNOT_REVOKE_API_KEY': { class: 'internal', dynamic: false, generatedOnly: false }, + 'PRINCIPAL_NOT_FOUND': { class: 'internal', dynamic: false, generatedOnly: false }, + 'PRINCIPAL_NOT_IN_ORG': { class: 'internal', dynamic: false, generatedOnly: false }, + 'PRINCIPAL_NOT_OWNED': { class: 'internal', dynamic: false, generatedOnly: false }, + 'PROFILE_ASSIGNMENT_REQUIRES_EMAIL_INVITE': { class: 'public', dynamic: false, generatedOnly: false }, + 'PROFILE_EXCEEDS_PERMISSIONS': { class: 'public', dynamic: false, generatedOnly: false }, + 'PROFILE_NOT_FOUND': { class: 'public', dynamic: false, generatedOnly: false }, + 'PROVISION_CHECK_CONSTRAINT': { class: 'internal', dynamic: true, generatedOnly: false }, + 'PROVISION_FULL_TEXT_SEARCH': { class: 'internal', dynamic: true, generatedOnly: false }, + 'PROVISION_INDEX': { class: 'internal', dynamic: true, generatedOnly: false }, + 'PROVISION_INVALID_DATABASE_ID': { class: 'internal', dynamic: false, generatedOnly: false }, + 'PROVISION_RELATION': { class: 'internal', dynamic: true, generatedOnly: false }, + 'PROVISION_TABLE': { class: 'internal', dynamic: true, generatedOnly: false }, + 'PROVISION_UNIQUE_CONSTRAINT': { class: 'internal', dynamic: true, generatedOnly: false }, + 'RATE_LIMIT_EXCEEDED': { class: 'public', dynamic: false, generatedOnly: false }, + 'REF_NOT_FOUND': { class: 'public', dynamic: false, generatedOnly: false }, + 'RELATION_PROVISION': { class: 'internal', dynamic: true, generatedOnly: false }, + 'REPO_EXISTS': { class: 'internal', dynamic: false, generatedOnly: false }, + 'REQUEST_DATABASE_INVALID_INPUT': { class: 'internal', dynamic: false, generatedOnly: false }, + 'REQUEST_DATABASE_UNKNOWN_PRESET': { class: 'internal', dynamic: true, generatedOnly: false }, + 'REQUIRES': { class: 'internal', dynamic: true, generatedOnly: false }, + 'REQUIRES_ONE_OWNER': { class: 'public', dynamic: false, generatedOnly: false }, + 'RESOLVE_BLUEPRINT_FIELD': { class: 'internal', dynamic: true, generatedOnly: false }, + 'RESOLVE_BLUEPRINT_TABLE': { class: 'internal', dynamic: true, generatedOnly: false }, + 'RESOURCE_MODULE': { class: 'internal', dynamic: true, generatedOnly: false }, + 'ROUTE_BINDING_SYNC': { class: 'internal', dynamic: true, generatedOnly: false }, + 'ROUTE_TARGET_NOT_VISIBLE': { class: 'internal', dynamic: false, generatedOnly: false }, + 'ROUTE_TARGET_REQUIRED': { class: 'internal', dynamic: false, generatedOnly: false }, + 'SEARCH_BM25': { class: 'internal', dynamic: true, generatedOnly: false }, + 'SEARCH_FULL_TEXT': { class: 'internal', dynamic: true, generatedOnly: false }, + 'SEARCH_SPATIAL': { class: 'internal', dynamic: true, generatedOnly: false }, + 'SEARCH_SPATIAL_AGGREGATE': { class: 'internal', dynamic: true, generatedOnly: false }, + 'SEARCH_TRGM': { class: 'internal', dynamic: true, generatedOnly: false }, + 'SEARCH_UNIFIED': { class: 'internal', dynamic: true, generatedOnly: false }, + 'SEARCH_VECTOR': { class: 'internal', dynamic: true, generatedOnly: false }, + 'SECURITY': { class: 'internal', dynamic: true, generatedOnly: false }, + 'SERVICES_API_BRIDGE': { class: 'internal', dynamic: true, generatedOnly: false }, + 'SERVICES_DOMAIN_BRIDGE': { class: 'internal', dynamic: true, generatedOnly: false }, + 'SERVICES_SITE_BRIDGE': { class: 'internal', dynamic: true, generatedOnly: false }, + 'SESSION_NOT_FOUND': { class: 'public', dynamic: false, generatedOnly: true }, + 'SIGN_UP_DISABLED': { class: 'public', dynamic: false, generatedOnly: true }, + 'SINGLETON_TABLE': { class: 'public', dynamic: false, generatedOnly: false }, + 'SMS_SIGN_IN_DISABLED': { class: 'public', dynamic: false, generatedOnly: true }, + 'SMS_SIGN_UP_DISABLED': { class: 'public', dynamic: false, generatedOnly: true }, + 'SOURCE_EMAILS_NOT_FOUND': { class: 'internal', dynamic: false, generatedOnly: false }, + 'SOURCE_SECRETS_NOT_FOUND': { class: 'internal', dynamic: false, generatedOnly: false }, + 'SOURCE_USERS_NOT_FOUND': { class: 'internal', dynamic: false, generatedOnly: false }, + 'STEP_UP_REQUIRED': { class: 'public', dynamic: false, generatedOnly: false }, + 'STEP_UP_REQUIRED_PASSWORD': { class: 'public', dynamic: false, generatedOnly: false }, + 'STEP_UP_REQUIRED_PASSWORD_OR_MFA': { class: 'public', dynamic: false, generatedOnly: false }, + 'SUPER_CONSTRUCTIVE_REQUIRED': { class: 'internal', dynamic: true, generatedOnly: false }, + 'TABLE_MODULE': { class: 'internal', dynamic: true, generatedOnly: false }, + 'TARGET_EMAILS_NOT_FOUND': { class: 'internal', dynamic: false, generatedOnly: false }, + 'TARGET_FIELD_NOT_FOUND': { class: 'internal', dynamic: false, generatedOnly: false }, + 'TARGET_FIELD_NOT_IN_TARGET_TABLE': { class: 'internal', dynamic: false, generatedOnly: false }, + 'TARGET_MEMBERSHIPS_NOT_FOUND': { class: 'internal', dynamic: false, generatedOnly: false }, + 'TARGET_SECRETS_NOT_FOUND': { class: 'internal', dynamic: false, generatedOnly: false }, + 'TARGET_USERS_NOT_FOUND': { class: 'internal', dynamic: false, generatedOnly: false }, + 'THROWN_ERROR': { class: 'internal', dynamic: true, generatedOnly: false }, + 'TOO_MANY_REQUESTS': { class: 'public', dynamic: false, generatedOnly: false }, + 'TOTP_ALREADY_ENABLED': { class: 'public', dynamic: false, generatedOnly: false }, + 'TOTP_NOT_ENABLED': { class: 'public', dynamic: false, generatedOnly: false }, + 'TOTP_SETUP_NOT_INITIATED': { class: 'public', dynamic: false, generatedOnly: false }, + 'TRANSFER_NOT_ALLOWED_IN_POOLED_MODE': { class: 'internal', dynamic: false, generatedOnly: false }, + 'UNAUTHENTICATED': { class: 'public', dynamic: false, generatedOnly: false }, + 'UNKNOWN_POLICY_TYPE': { class: 'internal', dynamic: true, generatedOnly: false }, + 'UNKNOWN_VIEW_TYPE': { class: 'internal', dynamic: true, generatedOnly: false }, + 'UNSUPPORTED': { class: 'internal', dynamic: true, generatedOnly: false }, + 'UNSUPPORTED_EXPRESSION': { class: 'internal', dynamic: true, generatedOnly: false }, + 'UNSUPPORTED_VIEW_RULE_ACTION': { class: 'internal', dynamic: true, generatedOnly: false }, + 'UUID': { class: 'internal', dynamic: true, generatedOnly: false }, + 'VALIDATE_BLUEPRINT': { class: 'internal', dynamic: true, generatedOnly: false }, + 'WEBAUTHN_CREDENTIAL_NOT_FOUND': { class: 'public', dynamic: false, generatedOnly: false }, + 'WEBAUTHN_REGISTER_CHALLENGE_NOT_FOUND_OR_EXPIRED': { class: 'public', dynamic: false, generatedOnly: false }, + 'WEBAUTHN_SIGN_IN_CHALLENGE_NOT_FOUND_OR_EXPIRED': { class: 'public', dynamic: false, generatedOnly: false }, + 'WEBAUTHN_SIGN_IN_DISABLED': { class: 'public', dynamic: false, generatedOnly: true }, + 'WEBAUTHN_SIGN_UP_DISABLED': { class: 'public', dynamic: false, generatedOnly: true }, + 'WITH_CHECK_NOT_ALLOWED_FOR_': { class: 'internal', dynamic: true, generatedOnly: false }, +}; + +/** Total number of codes collected from constructive-db. */ +export const GENERATED_CODE_COUNT = 287; diff --git a/packages/errors/src/registry.ts b/packages/errors/src/registry.ts index c8e409963d..f8701d82cf 100644 --- a/packages/errors/src/registry.ts +++ b/packages/errors/src/registry.ts @@ -1,34 +1,17 @@ -import type { EmptyContext, ErrorContext, ErrorDefinition } from './types'; +import {defineError } from './define'; +import { generatedRegistry } from './generated/registry.generated'; +import type { ErrorContext, ErrorDefinition } from './types'; -/** - * A registry entry plus a phantom type carrier for its context. The - * contravariant `__context` field lets `ErrorsApi` recover `C` by inference - * even when `C` appears only in optional positions of {@link ErrorDefinition}. - * It is type-level only — never set at runtime. - */ -export type DefinedError = ErrorDefinition & { - readonly __context: (context: C) => void; -}; - -/** - * Identity helper that captures each entry's context type for inference while - * keeping the registry a plain object. `errors.MODULE_NOT_FOUND({ name })` is - * then type-checked against the declared context. - */ -export function defineError( - def: ErrorDefinition -): DefinedError { - return def as DefinedError; -} +export { type DefinedError,defineError } from './define'; /** - * The canonical Constructive error registry. + * The curated Constructive error registry. * - * This is the hand-seeded core (public auth/limit codes with real dashboard - * copy, native PostgreSQL constraint codes, and the pgpm CLI codes). The full - * 287-code set emitted by constructive-db is generated in a later phase; codes - * absent here still `parse()` — they are simply classified `internal` (masked) - * until registered. + * These entries carry refined, typed context and hand-written copy for the + * codes that matter most (public auth/limit codes with real dashboard copy, + * native PostgreSQL constraint codes, and the pgpm CLI codes). They OVERRIDE + * the generated entries in {@link generatedRegistry} — every constructive-db + * code is present via generation; these just refine a subset. */ export const registry = { // =========================================================================== @@ -339,7 +322,21 @@ export const registry = { export type Registry = typeof registry; export type RegistryCode = keyof Registry; +/** + * All known definitions, keyed by code: every constructive-db code (generated) + * merged with the curated entries, curated winning on conflict. + */ +const allDefinitions: Record> = { + ...generatedRegistry, + ...(registry as unknown as Record>) +}; + /** Look up a definition by code (from anywhere, not just typed keys). */ export function getDefinition(code: string): ErrorDefinition | undefined { - return (registry as unknown as Record>)[code]; + return allDefinitions[code]; +} + +/** Every registered code (curated + generated). */ +export function allCodes(): string[] { + return Object.keys(allDefinitions); }