diff --git a/packages/errors/README.md b/packages/errors/README.md index 5a70354a2..4668324e5 100644 --- a/packages/errors/README.md +++ b/packages/errors/README.md @@ -15,6 +15,9 @@ service or client without pulling in pgpm. 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. +- **`httpStatusFor(code)`** — the canonical code → HTTP status mapping, so no + transport keeps its own table. Unregistered codes answer `500` *and are + reported*, never silently. ```ts import { parse, format, errors, classify } from '@constructive-io/errors'; @@ -46,6 +49,32 @@ throw errors.ACCOUNT_EXISTS(); pgpm CLI codes). These override the generated entries. - Unregistered codes still `parse()` and are classified `internal` (masked). +## HTTP status + +Every registry entry carries `http`, so an HTTP surface never needs its own +code → status table: `toError(err).http`, or `httpStatusFor(code)` when all you +have is a code. + +```ts +import { httpStatusFor, setUnmappedStatusReporter, toError } from '@constructive-io/errors'; + +setUnmappedStatusReporter(code => log.warn({ code }, 'error code has no HTTP status')); + +const err = toError(caught); +res.status(err.http).json(err.toExtensions()); + +httpStatusFor('ACCOUNT_DISABLED'); // { status: 403, mapped: true } +httpStatusFor('BRAND_NEW_CODE'); // { status: 500, mapped: false } + one report +``` + +`mapped: false` is the one case where a 500 does not mean "the server broke" — +it means the code never reached the registry. That is the failure mode this +exists to make loud: a refusal that is plainly a 403 or a 409 answering 500 +looks like a crash, and the codes most likely to be missing are the newest ones. +When a constructive-db release adds codes, refresh the registry (below); +`__tests__/registry-sync.test.ts` fails if the snapshot and the generated +registry disagree. + ## Regenerating the full registry The generated layer is produced from a committed audit snapshot: diff --git a/packages/errors/__tests__/http.test.ts b/packages/errors/__tests__/http.test.ts new file mode 100644 index 000000000..fd73f58d1 --- /dev/null +++ b/packages/errors/__tests__/http.test.ts @@ -0,0 +1,69 @@ +import { + httpStatusFor, + resetUnmappedStatusReports, + setUnmappedStatusReporter, + toError, + UNMAPPED_HTTP_STATUS +} from '../src'; + +const reported: string[] = []; + +beforeEach(() => { + reported.length = 0; + resetUnmappedStatusReports(); + setUnmappedStatusReporter(code => reported.push(code)); +}); + +afterAll(() => { + setUnmappedStatusReporter(null); +}); + +describe('httpStatusFor', () => { + it('resolves registered codes from the registry', () => { + expect(httpStatusFor('ACCOUNT_DISABLED')).toEqual({ status: 403, mapped: true }); + expect(httpStatusFor('ACCOUNT_EXISTS')).toEqual({ status: 409, mapped: true }); + expect(httpStatusFor('INVALID_CREDENTIALS')).toEqual({ status: 401, mapped: true }); + expect(reported).toEqual([]); + }); + + it('reports an unregistered code instead of silently answering 500', () => { + expect(httpStatusFor('SSO_LINK_TICKET_ALREADY_USED_NOT_REGISTERED')).toEqual({ + status: UNMAPPED_HTTP_STATUS, + mapped: false + }); + expect(reported).toEqual(['SSO_LINK_TICKET_ALREADY_USED_NOT_REGISTERED']); + }); + + it('reports each unmapped code once, not once per request', () => { + httpStatusFor('NOISY_UNREGISTERED_CODE'); + httpStatusFor('NOISY_UNREGISTERED_CODE'); + httpStatusFor('NOISY_UNREGISTERED_CODE'); + expect(reported).toEqual(['NOISY_UNREGISTERED_CODE']); + }); + + it('does not report when there is no code at all', () => { + expect(httpStatusFor(null)).toEqual({ status: UNMAPPED_HTTP_STATUS, mapped: false }); + expect(httpStatusFor(undefined)).toEqual({ status: UNMAPPED_HTTP_STATUS, mapped: false }); + expect(reported).toEqual([]); + }); +}); + +describe('toError', () => { + it('carries the registry status for a database refusal', () => { + const pgError = Object.assign(new Error('ACCOUNT_DISABLED'), { + code: 'P0001', + detail: JSON.stringify({ code: 'ACCOUNT_DISABLED', context: {}, class: 'public' }) + }); + expect(toError(pgError).http).toBe(403); + expect(reported).toEqual([]); + }); + + it('reports the unmapped code behind a 500', () => { + const pgError = Object.assign(new Error('UNREGISTERED_REFUSAL'), { + code: 'P0001', + detail: JSON.stringify({ code: 'UNREGISTERED_REFUSAL', context: {}, class: 'public' }) + }); + expect(toError(pgError).http).toBe(UNMAPPED_HTTP_STATUS); + expect(reported).toEqual(['UNREGISTERED_REFUSAL']); + }); +}); diff --git a/packages/errors/__tests__/parse.test.ts b/packages/errors/__tests__/parse.test.ts index fce24605f..da301fd44 100644 --- a/packages/errors/__tests__/parse.test.ts +++ b/packages/errors/__tests__/parse.test.ts @@ -226,11 +226,12 @@ describe('generated registry (full constructive-db audit)', () => { // require_step_up() raises one code per factor so the client knows which // re-verification to prompt for; a humanized code would prompt for the // wrong one, so each needs its own copy. + // STEP_UP_REQUIRED_PASSWORD_OR_MFA is deliberately absent: constructive-db + // split it into the per-factor codes below (require_step_up.sql). const factors = [ 'STEP_UP_REQUIRED_PASSWORD', 'STEP_UP_REQUIRED_MFA', - 'STEP_UP_REQUIRED_FRESH_AUTH', - 'STEP_UP_REQUIRED_PASSWORD_OR_MFA' + 'STEP_UP_REQUIRED_FRESH_AUTH' ]; for (const code of factors) { expect(classify(code)).toBe('public'); diff --git a/packages/errors/__tests__/registry-sync.test.ts b/packages/errors/__tests__/registry-sync.test.ts new file mode 100644 index 000000000..1a8f10c0d --- /dev/null +++ b/packages/errors/__tests__/registry-sync.test.ts @@ -0,0 +1,33 @@ +import { readFileSync } from 'fs'; +import { join } from 'path'; + +import { generatedRegistry } from '../src/generated/registry.generated'; +import { getDefinition } from '../src/registry'; + +const inventory: Record = JSON.parse( + readFileSync(join(__dirname, '../scripts/db-error-inventory.json'), 'utf-8') +); + +/** + * The generated registry is the only thing standing between a database refusal + * and a silent 500, so a refreshed inventory that was never regenerated (or the + * reverse) has to fail here rather than in production. + */ +describe('generated registry is in sync with the audit inventory', () => { + it('covers every audited code', () => { + const missing = Object.keys(inventory).filter(code => !generatedRegistry[code]); + expect(missing).toEqual([]); + }); + + it('carries no code the audit no longer knows about', () => { + const stale = Object.keys(generatedRegistry).filter(code => !(code in inventory)); + expect(stale).toEqual([]); + }); + + it('gives every audited code a resolvable HTTP status', () => { + const unstatused = Object.keys(inventory).filter( + code => typeof getDefinition(code)?.http !== 'number' + ); + expect(unstatused).toEqual([]); + }); +}); diff --git a/packages/errors/scripts/db-error-inventory.json b/packages/errors/scripts/db-error-inventory.json index 7f4fa1f80..fa53b7355 100644 --- a/packages/errors/scripts/db-error-inventory.json +++ b/packages/errors/scripts/db-error-inventory.json @@ -1,34 +1,34 @@ { "ACCOUNT_DISABLED": { - "count": 15, + "count": 11, "dynamic": false, "sample": "ACCOUNT_DISABLED", "n_source": 1, - "n_generated": 10, + "n_generated": 6, "class": "public" }, "ACCOUNT_EXISTS": { - "count": 37, + "count": 32, "dynamic": false, "sample": "ACCOUNT_EXISTS", "n_source": 9, - "n_generated": 12, + "n_generated": 7, "class": "public" }, "ACCOUNT_LOCKED_EXCEED_ATTEMPTS": { - "count": 26, + "count": 19, "dynamic": false, "sample": "ACCOUNT_LOCKED_EXCEED_ATTEMPTS", "n_source": 3, - "n_generated": 16, + "n_generated": 9, "class": "public" }, "ACCOUNT_NOT_FOUND": { - "count": 11, + "count": 10, "dynamic": false, "sample": "ACCOUNT_NOT_FOUND", "n_source": 4, - "n_generated": 4, + "n_generated": 3, "class": "public" }, "ALREADY_SCHEDULED": { @@ -46,27 +46,27 @@ "n_generated": 2 }, "API_KEYS_DISABLED": { - "count": 6, + "count": 4, "dynamic": false, "sample": "API_KEYS_DISABLED", "n_source": 0, - "n_generated": 5, + "n_generated": 3, "class": "public" }, "API_KEY_LIMIT_REACHED": { - "count": 10, + "count": 8, "dynamic": false, "sample": "API_KEY_LIMIT_REACHED", "n_source": 2, - "n_generated": 6, + "n_generated": 4, "class": "public" }, "API_KEY_NOT_FOUND": { - "count": 5, + "count": 4, "dynamic": false, "sample": "API_KEY_NOT_FOUND", "n_source": 1, - "n_generated": 4, + "n_generated": 3, "class": "public" }, "APPLY_RLS": { @@ -93,12 +93,28 @@ "n_generated": 1, "class": "public" }, + "APP_INSTALLATION_MISSING": { + "count": 4, + "dynamic": false, + "sample": "APP_INSTALLATION_MISSING", + "n_source": 1, + "n_generated": 3, + "class": "public" + }, + "APP_INSTALL_NO_SERVICE": { + "count": 4, + "dynamic": false, + "sample": "APP_INSTALL_NO_SERVICE", + "n_source": 1, + "n_generated": 3, + "class": "public" + }, "ASSIGN_PROFILES_PERMISSION_REQUIRED": { - "count": 14, + "count": 12, "dynamic": false, "sample": "ASSIGN_PROFILES_PERMISSION_REQUIRED", "n_source": 2, - "n_generated": 6 + "n_generated": 4 }, "AUTHZ_COLUMN_SECURITY_AUTHZ_NODE_REQUIRED": { "count": 2, @@ -165,11 +181,11 @@ "class": "public" }, "AUTH_METHOD_NOT_ALLOWED": { - "count": 21, + "count": 15, "dynamic": false, "sample": "AUTH_METHOD_NOT_ALLOWED", "n_source": 1, - "n_generated": 14, + "n_generated": 8, "class": "public" }, "BAD_CASING_FUNC": { @@ -371,6 +387,14 @@ "n_generated": 1, "class": "public" }, + "BEHAVIOR_FRAGMENT_EMPTY": { + "count": 2, + "dynamic": false, + "sample": "BEHAVIOR_FRAGMENT_EMPTY", + "n_source": 1, + "n_generated": 1, + "class": "internal" + }, "BM25": { "count": 2, "dynamic": false, @@ -412,27 +436,27 @@ "class": "internal" }, "CANNOT_DISCONNECT_LAST_AUTH_METHOD": { - "count": 5, + "count": 4, "dynamic": false, "sample": "CANNOT_DISCONNECT_LAST_AUTH_METHOD", "n_source": 1, - "n_generated": 4, + "n_generated": 3, "class": "public" }, "CANNOT_REVOKE_CURRENT_SESSION": { - "count": 5, + "count": 4, "dynamic": false, "sample": "CANNOT_REVOKE_CURRENT_SESSION", "n_source": 1, - "n_generated": 4, + "n_generated": 3, "class": "public" }, "CAP_CHECK_TRIGGER_ARGS": { - "count": 12, + "count": 10, "dynamic": true, "sample": "CAP_CHECK_TRIGGER_ARGS (%)", "n_source": 1, - "n_generated": 8 + "n_generated": 6 }, "CATALOG_BACKFILL": { "count": 4, @@ -471,11 +495,11 @@ "class": "public" }, "CONNECTED_ACCOUNT_NOT_FOUND": { - "count": 5, + "count": 4, "dynamic": false, "sample": "CONNECTED_ACCOUNT_NOT_FOUND", "n_source": 1, - "n_generated": 4, + "n_generated": 3, "class": "public" }, "CONSTRUCT_BLUEPRINT_ACHIEVEMENT_ENTITY_ID_REQUIRED": { @@ -644,13 +668,21 @@ "class": "public" }, "CSRF_TOKEN_REQUIRED": { - "count": 14, + "count": 12, "dynamic": false, "sample": "CSRF_TOKEN_REQUIRED", "n_source": 2, - "n_generated": 6, + "n_generated": 4, "class": "public" }, + "DATABASE_CLAIM_REQUIRED": { + "count": 4, + "dynamic": false, + "sample": "DATABASE_CLAIM_REQUIRED", + "n_source": 0, + "n_generated": 4, + "class": "internal" + }, "DATABASE_FIELD_RESERVED_WORD": { "count": 2, "dynamic": false, @@ -898,6 +930,38 @@ "n_generated": 1, "class": "public" }, + "DATA_HISTORY_NO_COPY_FIELDS": { + "count": 2, + "dynamic": false, + "sample": "DATA_HISTORY_NO_COPY_FIELDS", + "n_source": 1, + "n_generated": 1, + "class": "public" + }, + "DATA_HISTORY_ON_HISTORY_TABLE": { + "count": 2, + "dynamic": false, + "sample": "DATA_HISTORY_ON_HISTORY_TABLE", + "n_source": 1, + "n_generated": 1, + "class": "public" + }, + "DATA_HISTORY_REQUIRES_PK": { + "count": 2, + "dynamic": false, + "sample": "DATA_HISTORY_REQUIRES_PK", + "n_source": 1, + "n_generated": 1, + "class": "public" + }, + "DATA_HISTORY_TABLE_NOT_FOUND": { + "count": 2, + "dynamic": false, + "sample": "DATA_HISTORY_TABLE_NOT_FOUND", + "n_source": 1, + "n_generated": 1, + "class": "public" + }, "DATA_I18N_FIELDS_REQUIRED": { "count": 2, "dynamic": false, @@ -1097,6 +1161,14 @@ "n_generated": 1, "class": "public" }, + "DATA_JOB_TRIGGER_DATABASE_ID_FIELD_NOT_FOUND": { + "count": 2, + "dynamic": false, + "sample": "DATA_JOB_TRIGGER_DATABASE_ID_FIELD_NOT_FOUND", + "n_source": 1, + "n_generated": 1, + "class": "public" + }, "DATA_JOB_TRIGGER_ENTITY_FIELD_NOT_FOUND": { "count": 2, "dynamic": false, @@ -1401,74 +1473,74 @@ "n_generated": 1 }, "DOMAIN_CHECK_CERT_NOT_ISSUING": { - "count": 7, + "count": 6, "dynamic": true, "sample": "DOMAIN_CHECK_CERT_NOT_ISSUING: managed_domain % has cert_status % (issue a cert first)", "n_source": 0, - "n_generated": 7 + "n_generated": 6 }, "DOMAIN_CHECK_CERT_UNKNOWN_DOMAIN": { - "count": 7, + "count": 6, "dynamic": true, "sample": "DOMAIN_CHECK_CERT_UNKNOWN_DOMAIN: no managed_domain with id %", "n_source": 0, - "n_generated": 7 + "n_generated": 6 }, "DOMAIN_ISSUE_CERT_BAD_ISSUER": { - "count": 7, + "count": 6, "dynamic": true, "sample": "DOMAIN_ISSUE_CERT_BAD_ISSUER: unsupported issuer_env % (expected staging|production)", "n_source": 0, - "n_generated": 7 + "n_generated": 6 }, "DOMAIN_ISSUE_CERT_NOT_VERIFIED": { - "count": 7, + "count": 6, "dynamic": true, "sample": "DOMAIN_ISSUE_CERT_NOT_VERIFIED: managed_domain % is % (must be verified before issuing a cert)", "n_source": 0, - "n_generated": 7 + "n_generated": 6 }, "DOMAIN_ISSUE_CERT_UNKNOWN_DOMAIN": { - "count": 7, + "count": 6, "dynamic": true, "sample": "DOMAIN_ISSUE_CERT_UNKNOWN_DOMAIN: no managed_domain with id %", "n_source": 0, - "n_generated": 7 + "n_generated": 6 }, "DOMAIN_ISSUE_CHALLENGE_BAD_METHOD": { - "count": 7, + "count": 6, "dynamic": true, "sample": "DOMAIN_ISSUE_CHALLENGE_BAD_METHOD: unsupported verification method %", "n_source": 0, - "n_generated": 7 + "n_generated": 6 }, "DOMAIN_ISSUE_CHALLENGE_UNKNOWN_DOMAIN": { - "count": 7, + "count": 6, "dynamic": true, "sample": "DOMAIN_ISSUE_CHALLENGE_UNKNOWN_DOMAIN: no managed_domain with id %", "n_source": 0, - "n_generated": 7 + "n_generated": 6 }, "DOMAIN_RENEW_NOT_ACTIVE": { - "count": 7, + "count": 6, "dynamic": true, "sample": "DOMAIN_RENEW_NOT_ACTIVE: managed_domain % has cert_status % (only an active cert can be renewed)", "n_source": 0, - "n_generated": 7 + "n_generated": 6 }, "DOMAIN_RENEW_UNKNOWN_DOMAIN": { - "count": 7, + "count": 6, "dynamic": true, "sample": "DOMAIN_RENEW_UNKNOWN_DOMAIN: no managed_domain with id %", "n_source": 0, - "n_generated": 7 + "n_generated": 6 }, "DOMAIN_REVOKE_UNKNOWN_DOMAIN": { - "count": 7, + "count": 6, "dynamic": true, "sample": "DOMAIN_REVOKE_UNKNOWN_DOMAIN: no managed_domain with id %", "n_source": 0, - "n_generated": 7 + "n_generated": 6 }, "DOMAIN_ROOT_NOT_PUBLISHED": { "count": 2, @@ -1478,25 +1550,25 @@ "n_generated": 2 }, "DOMAIN_VERIFY_BAD_METHOD": { - "count": 7, + "count": 6, "dynamic": true, "sample": "DOMAIN_VERIFY_BAD_METHOD: unsupported verification method %", "n_source": 0, - "n_generated": 7 + "n_generated": 6 }, "DOMAIN_VERIFY_NO_CHALLENGE": { - "count": 7, + "count": 6, "dynamic": true, "sample": "DOMAIN_VERIFY_NO_CHALLENGE: no outstanding % challenge for managed_domain %", "n_source": 0, - "n_generated": 7 + "n_generated": 6 }, "EMAIL_NOT_VERIFIED": { - "count": 14, + "count": 12, "dynamic": false, "sample": "EMAIL_NOT_VERIFIED", "n_source": 2, - "n_generated": 6 + "n_generated": 4 }, "ENTITY_NOT_FOUND": { "count": 4, @@ -1766,11 +1838,11 @@ "n_generated": 1 }, "FEATURE_DISABLED": { - "count": 16, + "count": 14, "dynamic": true, "sample": "FEATURE_DISABLED (%)", "n_source": 1, - "n_generated": 8 + "n_generated": 6 }, "FIELDS_MISMATCH": { "count": 2, @@ -1780,6 +1852,14 @@ "n_generated": 1, "class": "public" }, + "FOREIGN_KEY_CONSTRAINT_NOT_FOUND": { + "count": 2, + "dynamic": false, + "sample": "FOREIGN_KEY_CONSTRAINT_NOT_FOUND", + "n_source": 1, + "n_generated": 1, + "class": "internal" + }, "FUNCTION_GRAPH_NOT_FOUND": { "count": 16, "dynamic": false, @@ -2045,11 +2125,11 @@ "class": "public" }, "HIERARCHY_CYCLE_DETECTED": { - "count": 5, + "count": 4, "dynamic": true, "sample": "HIERARCHY_CYCLE_DETECTED: Setting % as parent of % would create a cycle", "n_source": 1, - "n_generated": 4 + "n_generated": 3 }, "HIERARCHY_DEPTH_EXCEEDED": { "count": 4, @@ -2059,18 +2139,25 @@ "n_generated": 1 }, "HIERARCHY_INACTIVE_MEMBER": { - "count": 10, + "count": 8, "dynamic": true, "sample": "HIERARCHY_INACTIVE_MEMBER: Cannot add user % to hierarchy - user must be an active member of the organization first", "n_source": 1, - "n_generated": 4 + "n_generated": 3 }, "HIERARCHY_MEMBER_IN_USE": { - "count": 5, + "count": 4, "dynamic": true, "sample": "HIERARCHY_MEMBER_IN_USE: Cannot deactivate user % - user must be removed from the organization hierarchy first", "n_source": 1, - "n_generated": 4 + "n_generated": 3 + }, + "HISTORY": { + "count": 4, + "dynamic": true, + "sample": "HISTORY: history %.% registration has no copy_fields", + "n_source": 1, + "n_generated": 1 }, "HOSTNAME_BINDING_SYNC": { "count": 2, @@ -2094,59 +2181,59 @@ "n_generated": 1 }, "IDENTITY_ACCOUNT_NOT_FOUND": { - "count": 11, + "count": 10, "dynamic": false, "sample": "IDENTITY_ACCOUNT_NOT_FOUND", "n_source": 2, - "n_generated": 6, + "n_generated": 4, "class": "public" }, "IDENTITY_ALREADY_LINKED": { - "count": 5, + "count": 4, "dynamic": false, "sample": "IDENTITY_ALREADY_LINKED", "n_source": 1, - "n_generated": 4, + "n_generated": 3, "class": "public" }, "IDENTITY_LINK_AVAILABLE": { - "count": 5, + "count": 4, "dynamic": false, "sample": "IDENTITY_LINK_AVAILABLE", "n_source": 1, - "n_generated": 4, + "n_generated": 3, "class": "public" }, "IDENTITY_PROVIDER_NOT_CONFIGURED": { - "count": 10, + "count": 8, "dynamic": false, "sample": "IDENTITY_PROVIDER_NOT_CONFIGURED", "n_source": 2, - "n_generated": 6, + "n_generated": 4, "class": "public" }, "IDENTITY_PROVIDER_NOT_FOUND": { - "count": 5, + "count": 4, "dynamic": false, "sample": "IDENTITY_PROVIDER_NOT_FOUND", "n_source": 1, - "n_generated": 4, + "n_generated": 3, "class": "public" }, "IDENTITY_SIGN_IN_DISABLED": { - "count": 3, + "count": 2, "dynamic": false, "sample": "IDENTITY_SIGN_IN_DISABLED", "n_source": 0, - "n_generated": 3, + "n_generated": 2, "class": "public" }, "IDENTITY_SIGN_UP_DISABLED": { - "count": 3, + "count": 2, "dynamic": false, "sample": "IDENTITY_SIGN_UP_DISABLED", "n_source": 0, - "n_generated": 3, + "n_generated": 2, "class": "public" }, "IMMUTABLE_FIELD": { @@ -2173,11 +2260,11 @@ "class": "public" }, "IMMUTABLE_PROPERTY": { - "count": 12, + "count": 8, "dynamic": false, "sample": "IMMUTABLE_PROPERTY", "n_source": 0, - "n_generated": 9 + "n_generated": 5 }, "IMMUTABLE_PROPS": { "count": 4, @@ -2196,11 +2283,11 @@ "class": "public" }, "INCORRECT_PASSWORD": { - "count": 5, + "count": 4, "dynamic": false, "sample": "INCORRECT_PASSWORD", "n_source": 1, - "n_generated": 4, + "n_generated": 3, "class": "public" }, "INFRA_API_NOT_PROVISIONED": { @@ -2220,19 +2307,19 @@ "class": "internal" }, "INTERNAL_ERROR": { - "count": 290, + "count": 326, "dynamic": false, "sample": "INTERNAL_ERROR", - "n_source": 64, + "n_source": 67, "n_generated": 3, "class": "internal" }, "INVALID_ACCESS_LEVEL": { - "count": 10, + "count": 8, "dynamic": false, "sample": "INVALID_ACCESS_LEVEL", "n_source": 2, - "n_generated": 6, + "n_generated": 4, "class": "public" }, "INVALID_CHECK_OPTION": { @@ -2243,11 +2330,11 @@ "n_generated": 1 }, "INVALID_CODE": { - "count": 14, + "count": 12, "dynamic": false, "sample": "INVALID_CODE", "n_source": 2, - "n_generated": 4, + "n_generated": 3, "class": "public" }, "INVALID_COMPOSITE_TYPE_ATTRIBUTE": { @@ -2259,11 +2346,11 @@ "class": "public" }, "INVALID_CSRF_TOKEN": { - "count": 10, + "count": 8, "dynamic": false, "sample": "INVALID_CSRF_TOKEN", "n_source": 2, - "n_generated": 6, + "n_generated": 4, "class": "public" }, "INVALID_DEFAULT_LIMIT_ID": { @@ -2304,19 +2391,19 @@ "n_generated": 1 }, "INVALID_MFA_LEVEL": { - "count": 10, + "count": 8, "dynamic": false, "sample": "INVALID_MFA_LEVEL", "n_source": 2, - "n_generated": 6, + "n_generated": 4, "class": "public" }, "INVALID_ORGANIZATION": { - "count": 5, + "count": 4, "dynamic": false, "sample": "INVALID_ORGANIZATION", "n_source": 1, - "n_generated": 4, + "n_generated": 3, "class": "public" }, "INVALID_TOKEN": { @@ -2341,25 +2428,25 @@ "n_generated": 6 }, "INVITE_EMAIL_NOT_FOUND": { - "count": 19, + "count": 16, "dynamic": false, "sample": "INVITE_EMAIL_NOT_FOUND", "n_source": 2, - "n_generated": 6 + "n_generated": 4 }, "INVITE_LIMIT": { - "count": 14, + "count": 12, "dynamic": false, "sample": "INVITE_LIMIT", "n_source": 2, - "n_generated": 6 + "n_generated": 4 }, "INVITE_NOT_FOUND": { - "count": 14, + "count": 12, "dynamic": false, "sample": "INVITE_NOT_FOUND", "n_source": 2, - "n_generated": 6 + "n_generated": 4 }, "INVOCATION_QUOTA_GATE": { "count": 4, @@ -2584,11 +2671,11 @@ "class": "public" }, "LIMIT_REACHED": { - "count": 30, + "count": 24, "dynamic": false, "sample": "LIMIT_REACHED", "n_source": 2, - "n_generated": 17 + "n_generated": 11 }, "LIMIT_TRACK_USAGE_BILLING_MODULE_NOT_FOUND": { "count": 2, @@ -2639,11 +2726,11 @@ "class": "public" }, "LIMIT_TRIGGER_ARGS": { - "count": 50, + "count": 38, "dynamic": true, "sample": "LIMIT_TRIGGER_ARGS (%)", "n_source": 3, - "n_generated": 24 + "n_generated": 15 }, "LIMIT_WARNING_AGGREGATE_AGGREGATE_TABLE_NOT_PROVISIONED": { "count": 2, @@ -2798,18 +2885,18 @@ "class": "public" }, "MANAGED_DOMAIN_PUBLISH_FORBIDDEN": { - "count": 6, + "count": 4, "dynamic": false, "sample": "MANAGED_DOMAIN_PUBLISH_FORBIDDEN: not authorized to modify column(s): allow_public_usage", "n_source": 0, - "n_generated": 5 + "n_generated": 3 }, "MEMBERSHIP_NOT_FOUND": { - "count": 14, + "count": 12, "dynamic": false, "sample": "MEMBERSHIP_NOT_FOUND", "n_source": 2, - "n_generated": 6 + "n_generated": 4 }, "MEMBERSHIP_TYPE_MUST_BE_INT": { "count": 2, @@ -2904,11 +2991,11 @@ "class": "public" }, "MONOTONIC_FIELD": { - "count": 9, + "count": 6, "dynamic": false, "sample": "MONOTONIC_FIELD", "n_source": 0, - "n_generated": 7 + "n_generated": 4 }, "NONEXISTENT_TYPE": { "count": 2, @@ -2919,11 +3006,11 @@ "class": "public" }, "NOT_AUTHENTICATED": { - "count": 80, + "count": 70, "dynamic": false, "sample": "NOT_AUTHENTICATED", - "n_source": 21, - "n_generated": 28, + "n_source": 22, + "n_generated": 15, "class": "public" }, "NOT_FOUND": { @@ -2935,27 +3022,27 @@ "class": "public" }, "NOT_ORG_ADMIN": { - "count": 10, + "count": 8, "dynamic": false, "sample": "NOT_ORG_ADMIN", "n_source": 2, - "n_generated": 6, + "n_generated": 4, "class": "public" }, "NOT_ORG_PRINCIPAL": { - "count": 5, + "count": 4, "dynamic": false, "sample": "NOT_ORG_PRINCIPAL", "n_source": 1, - "n_generated": 4, + "n_generated": 3, "class": "public" }, "NOT_OWNER": { - "count": 5, + "count": 4, "dynamic": false, "sample": "NOT_OWNER", "n_source": 1, - "n_generated": 4, + "n_generated": 3, "class": "public" }, "NO_PRIMARY_IDENTIFIER": { @@ -2967,19 +3054,19 @@ "class": "internal" }, "NULL_VALUES_DISALLOWED": { - "count": 5, + "count": 4, "dynamic": false, "sample": "NULL_VALUES_DISALLOWED", "n_source": 1, - "n_generated": 4, + "n_generated": 3, "class": "public" }, "OBJECT_NOT_FOUND": { - "count": 24, + "count": 22, "dynamic": false, "sample": "OBJECT_NOT_FOUND", "n_source": 7, - "n_generated": 7, + "n_generated": 5, "class": "public" }, "OBJECT_NO_UPDATE": { @@ -2991,11 +3078,11 @@ "class": "public" }, "ORG_API_KEY_NOT_FOUND": { - "count": 10, + "count": 8, "dynamic": false, "sample": "ORG_API_KEY_NOT_FOUND", "n_source": 1, - "n_generated": 4, + "n_generated": 3, "class": "public" }, "OWNER_FIELD_NOT_FOUND": { @@ -3014,6 +3101,14 @@ "n_generated": 1, "class": "public" }, + "PAGES_MODULE": { + "count": 6, + "dynamic": false, + "sample": "PAGES_MODULE", + "n_source": 1, + "n_generated": 1, + "class": "internal" + }, "PARENT_ENTITY_TYPE_NOT_FOUND": { "count": 4, "dynamic": false, @@ -3029,43 +3124,43 @@ "n_generated": 1 }, "PASSWORD_INSECURE": { - "count": 7, + "count": 6, "dynamic": false, "sample": "PASSWORD_INSECURE", "n_source": 1, - "n_generated": 4, + "n_generated": 3, "class": "public" }, "PASSWORD_LEN": { - "count": 14, + "count": 12, "dynamic": false, "sample": "PASSWORD_LEN", "n_source": 1, - "n_generated": 4, + "n_generated": 3, "class": "public" }, "PASSWORD_RESET_LOCKED_EXCEED_ATTEMPTS": { - "count": 3, + "count": 2, "dynamic": false, "sample": "PASSWORD_RESET_LOCKED_EXCEED_ATTEMPTS", "n_source": 0, - "n_generated": 3, + "n_generated": 2, "class": "public" }, "PASSWORD_SIGN_IN_DISABLED": { - "count": 3, + "count": 2, "dynamic": false, "sample": "PASSWORD_SIGN_IN_DISABLED", "n_source": 0, - "n_generated": 3, + "n_generated": 2, "class": "public" }, "PASSWORD_SIGN_UP_DISABLED": { - "count": 3, + "count": 2, "dynamic": false, "sample": "PASSWORD_SIGN_UP_DISABLED", "n_source": 0, - "n_generated": 3, + "n_generated": 2, "class": "public" }, "PLATFORM_FLAG_IMMUTABLE": { @@ -3116,96 +3211,96 @@ "n_generated": 1 }, "PRIMARY_AUTH_METHOD_MISMATCH": { - "count": 12, + "count": 9, "dynamic": false, "sample": "PRIMARY_AUTH_METHOD_MISMATCH", "n_source": 1, - "n_generated": 8, + "n_generated": 5, "class": "public" }, "PRIMARY_REQUIRES_VERIFIED": { - "count": 23, + "count": 17, "dynamic": false, "sample": "PRIMARY_REQUIRES_VERIFIED", "n_source": 1, - "n_generated": 14 + "n_generated": 8 }, "PRINCIPAL_CANNOT_CREATE_API_KEY": { - "count": 10, + "count": 8, "dynamic": false, "sample": "PRINCIPAL_CANNOT_CREATE_API_KEY", "n_source": 2, - "n_generated": 6, + "n_generated": 4, "class": "public" }, "PRINCIPAL_CANNOT_CREATE_PRINCIPAL": { - "count": 10, + "count": 8, "dynamic": false, "sample": "PRINCIPAL_CANNOT_CREATE_PRINCIPAL", "n_source": 2, - "n_generated": 6, + "n_generated": 4, "class": "public" }, "PRINCIPAL_CANNOT_DELETE_PRINCIPAL": { - "count": 10, + "count": 8, "dynamic": false, "sample": "PRINCIPAL_CANNOT_DELETE_PRINCIPAL", "n_source": 2, - "n_generated": 6, + "n_generated": 4, "class": "public" }, "PRINCIPAL_CANNOT_REVOKE_API_KEY": { - "count": 10, + "count": 8, "dynamic": false, "sample": "PRINCIPAL_CANNOT_REVOKE_API_KEY", "n_source": 2, - "n_generated": 6, + "n_generated": 4, "class": "public" }, "PRINCIPAL_NOT_FOUND": { - "count": 15, + "count": 12, "dynamic": false, "sample": "PRINCIPAL_NOT_FOUND", "n_source": 3, - "n_generated": 8, + "n_generated": 5, "class": "public" }, "PRINCIPAL_NOT_IN_ORG": { - "count": 5, + "count": 4, "dynamic": false, "sample": "PRINCIPAL_NOT_IN_ORG", "n_source": 1, - "n_generated": 4, + "n_generated": 3, "class": "public" }, "PRINCIPAL_NOT_OWNED": { - "count": 5, + "count": 4, "dynamic": false, "sample": "PRINCIPAL_NOT_OWNED", "n_source": 1, - "n_generated": 4, + "n_generated": 3, "class": "public" }, "PROFILE_ASSIGNMENT_REQUIRES_EMAIL_INVITE": { - "count": 14, + "count": 12, "dynamic": false, "sample": "PROFILE_ASSIGNMENT_REQUIRES_EMAIL_INVITE", "n_source": 2, - "n_generated": 6 + "n_generated": 4 }, "PROFILE_EXCEEDS_PERMISSIONS": { - "count": 12, + "count": 10, "dynamic": false, "sample": "PROFILE_EXCEEDS_PERMISSIONS", "n_source": 2, - "n_generated": 6 + "n_generated": 4 }, "PROFILE_NOT_FOUND": { - "count": 14, + "count": 12, "dynamic": false, "sample": "PROFILE_NOT_FOUND", "n_source": 2, - "n_generated": 6 + "n_generated": 4 }, "PROVISION_CHECK_CONSTRAINT": { "count": 18, @@ -3310,11 +3405,11 @@ "class": "internal" }, "REPO_EXISTS": { - "count": 6, + "count": 5, "dynamic": false, "sample": "REPO_EXISTS", "n_source": 1, - "n_generated": 4 + "n_generated": 3 }, "REQUEST_DATABASE_INVALID_INPUT": { "count": 2, @@ -3333,19 +3428,19 @@ "class": "public" }, "REQUIRES": { - "count": 124, + "count": 126, "dynamic": false, "sample": "REQUIRES", - "n_source": 30, + "n_source": 31, "n_generated": 2, "class": "internal" }, "REQUIRES_ONE_OWNER": { - "count": 17, + "count": 14, "dynamic": false, "sample": "REQUIRES_ONE_OWNER", "n_source": 2, - "n_generated": 10 + "n_generated": 7 }, "RESOLVE_BLUEPRINT_FIELD": { "count": 2, @@ -3395,18 +3490,18 @@ "n_generated": 1 }, "ROUTE_TARGET_NOT_VISIBLE": { - "count": 12, + "count": 22, "dynamic": false, "sample": "ROUTE_TARGET_NOT_VISIBLE", "n_source": 0, - "n_generated": 4 + "n_generated": 6 }, "ROUTE_TARGET_REQUIRED": { - "count": 4, + "count": 6, "dynamic": false, "sample": "ROUTE_TARGET_REQUIRED", "n_source": 0, - "n_generated": 4 + "n_generated": 6 }, "ROUTING_API_TABLES_SCOPE_REQUIRED": { "count": 2, @@ -3417,10 +3512,10 @@ "class": "public" }, "ROUTING_SURFACE_NOT_PROVISIONED": { - "count": 28, + "count": 26, "dynamic": false, "sample": "ROUTING_SURFACE_NOT_PROVISIONED", - "n_source": 7, + "n_source": 6, "n_generated": 4, "class": "public" }, @@ -3673,14 +3768,14 @@ "class": "public" }, "SEED_CAP_DEFAULTS_REQUIRED": { - "count": 7, + "count": 8, "dynamic": false, "sample": "SEED_CAP_DEFAULTS_REQUIRED", "n_source": 1, "n_generated": 5 }, "SEED_LIMIT_DEFAULTS_REQUIRED": { - "count": 7, + "count": 8, "dynamic": false, "sample": "SEED_LIMIT_DEFAULTS_REQUIRED", "n_source": 1, @@ -3701,11 +3796,11 @@ "n_generated": 3 }, "SESSION_NOT_FOUND": { - "count": 5, + "count": 4, "dynamic": false, "sample": "SESSION_NOT_FOUND", "n_source": 1, - "n_generated": 4, + "n_generated": 3, "class": "public" }, "SETTINGS_SURFACE_NOT_PROVISIONED": { @@ -3717,11 +3812,11 @@ "class": "public" }, "SIGN_UP_DISABLED": { - "count": 6, + "count": 4, "dynamic": false, "sample": "SIGN_UP_DISABLED", "n_source": 0, - "n_generated": 5, + "n_generated": 3, "class": "public" }, "SITE_NOT_PROVISIONED": { @@ -3741,19 +3836,19 @@ "class": "public" }, "SMS_SIGN_IN_DISABLED": { - "count": 6, + "count": 4, "dynamic": false, "sample": "SMS_SIGN_IN_DISABLED", "n_source": 0, - "n_generated": 5, + "n_generated": 3, "class": "public" }, "SMS_SIGN_UP_DISABLED": { - "count": 3, + "count": 2, "dynamic": false, "sample": "SMS_SIGN_UP_DISABLED", "n_source": 0, - "n_generated": 3, + "n_generated": 2, "class": "public" }, "SOURCE_COMMIT_NOT_FOUND": { @@ -3787,6 +3882,14 @@ "n_generated": 1, "class": "internal" }, + "STATIC_SITES_LIMIT": { + "count": 2, + "dynamic": false, + "sample": "STATIC_SITES_LIMIT", + "n_source": 1, + "n_generated": 1, + "class": "public" + }, "STEP_UP_INVALID_TYPE": { "count": 4, "dynamic": false, @@ -3796,11 +3899,11 @@ "class": "public" }, "STEP_UP_REQUIRED": { - "count": 14, + "count": 11, "dynamic": false, "sample": "STEP_UP_REQUIRED", "n_source": 1, - "n_generated": 8, + "n_generated": 5, "class": "public" }, "STEP_UP_REQUIRED_FRESH_AUTH": { @@ -3812,27 +3915,44 @@ "class": "public" }, "STEP_UP_REQUIRED_MFA": { - "count": 5, + "count": 4, "dynamic": false, "sample": "STEP_UP_REQUIRED_MFA", "n_source": 1, - "n_generated": 4, + "n_generated": 3, "class": "public" }, "STEP_UP_REQUIRED_PASSWORD": { - "count": 5, + "count": 4, "dynamic": false, "sample": "STEP_UP_REQUIRED_PASSWORD", "n_source": 1, - "n_generated": 4, + "n_generated": 3, "class": "public" }, - "STEP_UP_REQUIRED_PASSWORD_OR_MFA": { - "count": 1, + "SUBDOMAIN_APEX_NOT_PUBLISHED": { + "count": 4, "dynamic": false, - "sample": "STEP_UP_REQUIRED_PASSWORD_OR_MFA", - "n_source": 0, - "n_generated": 1 + "sample": "SUBDOMAIN_APEX_NOT_PUBLISHED", + "n_source": 1, + "n_generated": 3, + "class": "public" + }, + "SUBDOMAIN_LABEL_EXHAUSTED": { + "count": 4, + "dynamic": false, + "sample": "SUBDOMAIN_LABEL_EXHAUSTED", + "n_source": 1, + "n_generated": 3, + "class": "public" + }, + "SUBDOMAIN_LABEL_INVALID": { + "count": 4, + "dynamic": false, + "sample": "SUBDOMAIN_LABEL_INVALID", + "n_source": 1, + "n_generated": 3, + "class": "public" }, "SUPER_CONSTRUCTIVE_REQUIRED": { "count": 2, @@ -3907,11 +4027,11 @@ "class": "internal" }, "TOO_MANY_REQUESTS": { - "count": 59, + "count": 41, "dynamic": false, "sample": "TOO_MANY_REQUESTS", "n_source": 1, - "n_generated": 38, + "n_generated": 20, "class": "public" }, "TOTP_ALREADY_ENABLED": { @@ -3922,11 +4042,11 @@ "n_generated": 1 }, "TOTP_NOT_ENABLED": { - "count": 7, + "count": 6, "dynamic": false, "sample": "TOTP_NOT_ENABLED", "n_source": 2, - "n_generated": 4, + "n_generated": 3, "class": "public" }, "TOTP_SETUP_NOT_INITIATED": { @@ -4102,43 +4222,43 @@ "class": "public" }, "WEBAUTHN_CREDENTIAL_NOT_FOUND": { - "count": 5, + "count": 4, "dynamic": false, "sample": "WEBAUTHN_CREDENTIAL_NOT_FOUND", "n_source": 1, - "n_generated": 4, + "n_generated": 3, "class": "public" }, "WEBAUTHN_REGISTER_CHALLENGE_NOT_FOUND_OR_EXPIRED": { - "count": 6, + "count": 5, "dynamic": false, "sample": "WEBAUTHN_REGISTER_CHALLENGE_NOT_FOUND_OR_EXPIRED", "n_source": 1, - "n_generated": 4, + "n_generated": 3, "class": "public" }, "WEBAUTHN_SIGN_IN_CHALLENGE_NOT_FOUND_OR_EXPIRED": { - "count": 5, + "count": 4, "dynamic": false, "sample": "WEBAUTHN_SIGN_IN_CHALLENGE_NOT_FOUND_OR_EXPIRED", "n_source": 1, - "n_generated": 4, + "n_generated": 3, "class": "public" }, "WEBAUTHN_SIGN_IN_DISABLED": { - "count": 6, + "count": 4, "dynamic": false, "sample": "WEBAUTHN_SIGN_IN_DISABLED", "n_source": 0, - "n_generated": 5, + "n_generated": 3, "class": "public" }, "WEBAUTHN_SIGN_UP_DISABLED": { - "count": 6, + "count": 4, "dynamic": false, "sample": "WEBAUTHN_SIGN_UP_DISABLED", "n_source": 0, - "n_generated": 5, + "n_generated": 3, "class": "public" }, "WITH_CHECK_NOT_ALLOWED": { diff --git a/packages/errors/src/generated/registry.generated.ts b/packages/errors/src/generated/registry.generated.ts index f222e6888..ad617d54d 100644 --- a/packages/errors/src/generated/registry.generated.ts +++ b/packages/errors/src/generated/registry.generated.ts @@ -2,7 +2,7 @@ /** * GENERATED FILE — DO NOT EDIT BY HAND. * - * Source of truth: the constructive-db error audit (531 distinct codes + * Source of truth: the constructive-db error audit (546 distinct codes * raised via EXCEPTION/THROW across deploy sources + generated output). * Regenerate with `python3 scripts/generate-registry.py` (see README.md). * @@ -10,7 +10,7 @@ * codes carry their raw message (with %-args rendered as {{argN}}). Curated * entries in `registry.ts` override anything here (typed context + refined copy). * - * Counts: 531 total, 401 public, 130 internal. + * Counts: 546 total, 410 public, 136 internal. */ import { defineError, type DefinedError } from '../define'; import type { ErrorContext } from '../types'; @@ -24,9 +24,9 @@ export interface GeneratedCodeMeta { /** 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_DISABLED': defineError({ code: 'ACCOUNT_DISABLED', class: 'public', http: 403, message: 'Account disabled.' }), + 'ACCOUNT_EXISTS': defineError({ code: 'ACCOUNT_EXISTS', class: 'public', http: 409, message: 'Account exists.' }), + 'ACCOUNT_LOCKED_EXCEED_ATTEMPTS': defineError({ code: 'ACCOUNT_LOCKED_EXCEED_ATTEMPTS', class: 'public', http: 423, message: 'Account locked exceed attempts.' }), '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: a column cannot be both generated and an identity column' }), @@ -36,6 +36,8 @@ export const generatedRegistry: Record> = { 'APPLY_RLS': defineError({ code: 'APPLY_RLS', class: 'internal', http: 500, message: 'Apply rls.' }), 'APPLY_RLS_BAD_ARGS': defineError({ code: 'APPLY_RLS_BAD_ARGS', class: 'internal', http: 500, message: 'Apply rls bad args.' }), 'APPLY_TABLE_STEP_UP_TABLE_NOT_FOUND': defineError({ code: 'APPLY_TABLE_STEP_UP_TABLE_NOT_FOUND', class: 'public', http: 404, message: 'Apply table step up table not found.' }), + 'APP_INSTALLATION_MISSING': defineError({ code: 'APP_INSTALLATION_MISSING', class: 'public', http: 400, message: 'App installation missing.' }), + 'APP_INSTALL_NO_SERVICE': defineError({ code: 'APP_INSTALL_NO_SERVICE', class: 'public', http: 400, message: 'App install no service.' }), 'ASSIGN_PROFILES_PERMISSION_REQUIRED': defineError({ code: 'ASSIGN_PROFILES_PERMISSION_REQUIRED', class: 'public', http: 403, message: 'Assign profiles permission required.' }), 'AUTHZ_COLUMN_SECURITY_AUTHZ_NODE_REQUIRED': defineError({ code: 'AUTHZ_COLUMN_SECURITY_AUTHZ_NODE_REQUIRED', class: 'public', http: 400, message: 'Authz column security authz node required.' }), 'AUTHZ_COLUMN_SECURITY_COLUMNS_REQUIRED': defineError({ code: 'AUTHZ_COLUMN_SECURITY_COLUMNS_REQUIRED', class: 'public', http: 400, message: 'Authz column security columns required.' }), @@ -71,6 +73,7 @@ export const generatedRegistry: Record> = { 'BAD_PRIVILEGE_FOR_POLICY': defineError({ code: 'BAD_PRIVILEGE_FOR_POLICY', class: 'public', http: 400, message: 'Bad privilege for policy.' }), 'BAD_RLS_EXPRESSION': defineError({ code: 'BAD_RLS_EXPRESSION', class: 'public', http: 400, message: 'Bad rls expression.' }), 'BAD_VIEW_EXPRESSION': defineError({ code: 'BAD_VIEW_EXPRESSION', class: 'public', http: 400, message: 'Bad view expression.' }), + 'BEHAVIOR_FRAGMENT_EMPTY': defineError({ code: 'BEHAVIOR_FRAGMENT_EMPTY', class: 'internal', http: 500, message: 'Behavior fragment empty.' }), 'BM25': defineError({ code: 'BM25', class: 'public', http: 400, message: 'Bm25.' }), '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.' }), @@ -107,6 +110,7 @@ export const generatedRegistry: Record> = { 'CROSS_DATABASE_CHECK': defineError({ code: 'CROSS_DATABASE_CHECK', class: 'public', http: 400, message: 'Cross database check.' }), '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_CLAIM_REQUIRED': defineError({ code: 'DATABASE_CLAIM_REQUIRED', class: 'internal', http: 500, message: 'Database claim required.' }), 'DATABASE_FIELD_RESERVED_WORD': defineError({ code: 'DATABASE_FIELD_RESERVED_WORD', class: 'public', http: 400, message: 'Database field reserved word.' }), 'DATABASE_NOT_FOUND': defineError({ code: 'DATABASE_NOT_FOUND', class: 'public', http: 404, message: 'Database not found.' }), 'DATABASE_SETTINGS_TABLES_SCOPE_REQUIRED': defineError({ code: 'DATABASE_SETTINGS_TABLES_SCOPE_REQUIRED', class: 'public', http: 400, message: 'Database settings tables scope required.' }), @@ -138,6 +142,10 @@ export const generatedRegistry: Record> = { 'DATA_GENERATED_TABLE_NOT_FOUND': defineError({ code: 'DATA_GENERATED_TABLE_NOT_FOUND', class: 'public', http: 404, message: 'Data generated table not found.' }), 'DATA_GENERATED_TARGET_REQUIRED': defineError({ code: 'DATA_GENERATED_TARGET_REQUIRED', class: 'public', http: 400, message: 'Data generated target required.' }), 'DATA_GENERATED_UNKNOWN_KIND': defineError({ code: 'DATA_GENERATED_UNKNOWN_KIND', class: 'public', http: 400, message: 'Data generated unknown kind.' }), + 'DATA_HISTORY_NO_COPY_FIELDS': defineError({ code: 'DATA_HISTORY_NO_COPY_FIELDS', class: 'public', http: 400, message: 'Data history no copy fields.' }), + 'DATA_HISTORY_ON_HISTORY_TABLE': defineError({ code: 'DATA_HISTORY_ON_HISTORY_TABLE', class: 'public', http: 400, message: 'Data history on history table.' }), + 'DATA_HISTORY_REQUIRES_PK': defineError({ code: 'DATA_HISTORY_REQUIRES_PK', class: 'public', http: 403, message: 'Data history requires pk.' }), + 'DATA_HISTORY_TABLE_NOT_FOUND': defineError({ code: 'DATA_HISTORY_TABLE_NOT_FOUND', class: 'public', http: 404, message: 'Data history table not found.' }), 'DATA_I18N_FIELDS_REQUIRED': defineError({ code: 'DATA_I18N_FIELDS_REQUIRED', class: 'public', http: 400, message: 'Data i18n fields required.' }), 'DATA_I18N_FIELD_NOT_FOUND': defineError({ code: 'DATA_I18N_FIELD_NOT_FOUND', class: 'public', http: 404, message: 'Data i18n field not found.' }), 'DATA_I18N_MODULE_NOT_PROVISIONED': defineError({ code: 'DATA_I18N_MODULE_NOT_PROVISIONED', class: 'public', http: 400, message: 'Data i18n module not provisioned.' }), @@ -163,6 +171,7 @@ export const generatedRegistry: Record> = { 'DATA_JOB_TRIGGER_CONDITIONS_WATCH_FIELDS_CONFLICT': defineError({ code: 'DATA_JOB_TRIGGER_CONDITIONS_WATCH_FIELDS_CONFLICT', class: 'public', http: 400, message: 'Data job trigger conditions watch fields conflict.' }), 'DATA_JOB_TRIGGER_CONDITION_FIELD_NOT_FOUND': defineError({ code: 'DATA_JOB_TRIGGER_CONDITION_FIELD_NOT_FOUND', class: 'public', http: 404, message: 'Data job trigger condition field not found.' }), 'DATA_JOB_TRIGGER_CONDITION_FIELD_WATCH_FIELDS_CONFLICT': defineError({ code: 'DATA_JOB_TRIGGER_CONDITION_FIELD_WATCH_FIELDS_CONFLICT', class: 'public', http: 400, message: 'Data job trigger condition field watch fields conflict.' }), + 'DATA_JOB_TRIGGER_DATABASE_ID_FIELD_NOT_FOUND': defineError({ code: 'DATA_JOB_TRIGGER_DATABASE_ID_FIELD_NOT_FOUND', class: 'public', http: 404, message: 'Data job trigger database id field not found.' }), 'DATA_JOB_TRIGGER_ENTITY_FIELD_NOT_FOUND': defineError({ code: 'DATA_JOB_TRIGGER_ENTITY_FIELD_NOT_FOUND', class: 'public', http: 404, message: 'Data job trigger entity field not found.' }), 'DATA_JOB_TRIGGER_ENTITY_LOOKUP_OBJ_FIELD_NOT_FOUND': defineError({ code: 'DATA_JOB_TRIGGER_ENTITY_LOOKUP_OBJ_FIELD_NOT_FOUND', class: 'public', http: 404, message: 'Data job trigger entity lookup obj field not found.' }), 'DATA_JOB_TRIGGER_ENTITY_LOOKUP_OBJ_FIELD_REQUIRED': defineError({ code: 'DATA_JOB_TRIGGER_ENTITY_LOOKUP_OBJ_FIELD_REQUIRED', class: 'public', http: 400, message: 'Data job trigger entity lookup obj field required.' }), @@ -214,7 +223,7 @@ export const generatedRegistry: Record> = { 'DOMAIN_ROOT_NOT_PUBLISHED': defineError({ code: 'DOMAIN_ROOT_NOT_PUBLISHED', class: 'internal', http: 500, message: 'Domain root not published.' }), '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.' }), + 'EMAIL_NOT_VERIFIED': defineError({ code: 'EMAIL_NOT_VERIFIED', class: 'public', http: 400, message: 'Email not verified.' }), '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.' }), @@ -247,10 +256,11 @@ export const generatedRegistry: Record> = { 'EVENT_TRACKER_UNKNOWN_EVENT': defineError({ code: 'EVENT_TRACKER_UNKNOWN_EVENT', class: 'public', http: 400, message: 'Event tracker unknown event.' }), 'EVENT_TRACKER_WATCH_FIELD_NOT_FOUND': defineError({ code: 'EVENT_TRACKER_WATCH_FIELD_NOT_FOUND', class: 'public', http: 404, message: 'Event tracker watch field not found.' }), 'EXECUTION_NOT_FOUND': defineError({ code: 'EXECUTION_NOT_FOUND', class: 'internal', http: 500, message: 'Execution not found.' }), - 'EXPIRED_TOKEN': defineError({ code: 'EXPIRED_TOKEN', class: 'public', http: 400, message: 'This reset link has expired. Please request a new one.' }), + 'EXPIRED_TOKEN': defineError({ code: 'EXPIRED_TOKEN', class: 'internal', http: 500, message: 'Expired token.' }), '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: 'public', http: 400, message: 'Fields mismatch.' }), + 'FOREIGN_KEY_CONSTRAINT_NOT_FOUND': defineError({ code: 'FOREIGN_KEY_CONSTRAINT_NOT_FOUND', class: 'internal', http: 500, message: 'Foreign key constraint not found.' }), 'FUNCTION_GRAPH_NOT_FOUND': defineError({ code: 'FUNCTION_GRAPH_NOT_FOUND', class: 'internal', http: 500, message: 'Function graph not found.' }), '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)' }), @@ -289,6 +299,7 @@ export const generatedRegistry: Record> = { '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'] }), + 'HISTORY': defineError({ code: 'HISTORY', class: 'internal', http: 500, message: 'HISTORY: history {{arg0}}.{{arg1}} registration has no copy_fields', positional: ['arg0', 'arg1'] }), '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:' }), 'HTTP_ROUTE_TARGET_OUTSIDE_SCOPE': defineError({ code: 'HTTP_ROUTE_TARGET_OUTSIDE_SCOPE', class: 'internal', http: 500, message: 'HTTP_ROUTE_TARGET_OUTSIDE_SCOPE:' }), @@ -305,7 +316,7 @@ export const generatedRegistry: Record> = { 'IMMUTABLE_PROPERTY': defineError({ code: 'IMMUTABLE_PROPERTY', class: 'internal', http: 500, message: 'Immutable property.' }), '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.' }), + 'INCORRECT_PASSWORD': defineError({ code: 'INCORRECT_PASSWORD', class: 'public', http: 401, message: 'Incorrect password.' }), 'INFRA_API_NOT_PROVISIONED': defineError({ code: 'INFRA_API_NOT_PROVISIONED', class: 'public', http: 400, message: 'Infra api not provisioned.' }), 'INSERT_GRAPH_EXECUTION_MODULE': defineError({ code: 'INSERT_GRAPH_EXECUTION_MODULE', class: 'internal', http: 500, message: 'Insert graph execution module.' }), 'INTERNAL_ERROR': defineError({ code: 'INTERNAL_ERROR', class: 'internal', http: 500, message: 'Internal error.' }), @@ -321,12 +332,12 @@ export const generatedRegistry: Record> = { '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: 'public', http: 403, message: 'Invalid mfa level.' }), 'INVALID_ORGANIZATION': defineError({ code: 'INVALID_ORGANIZATION', class: 'public', http: 400, 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_TOKEN': defineError({ code: 'INVALID_TOKEN', class: 'public', http: 401, message: 'Invalid token.' }), '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.' }), + 'INVITE_EMAIL_NOT_FOUND': defineError({ code: 'INVITE_EMAIL_NOT_FOUND', class: 'public', http: 404, message: 'Invite email not found.' }), + 'INVITE_LIMIT': defineError({ code: 'INVITE_LIMIT', class: 'public', http: 429, message: 'Invite limit.' }), + 'INVITE_NOT_FOUND': defineError({ code: 'INVITE_NOT_FOUND', class: 'public', http: 404, message: 'Invite 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_ENTITY_FIELD_NOT_FOUND': defineError({ code: 'LIMIT_ENFORCE_AGGREGATE_ENTITY_FIELD_NOT_FOUND', class: 'public', http: 404, message: 'Limit enforce aggregate entity field not found.' }), @@ -410,10 +421,11 @@ export const generatedRegistry: Record> = { 'ORG_API_KEY_NOT_FOUND': defineError({ code: 'ORG_API_KEY_NOT_FOUND', class: 'public', http: 404, message: 'Org api key not found.' }), 'OWNER_FIELD_NOT_FOUND': defineError({ code: 'OWNER_FIELD_NOT_FOUND', class: 'public', http: 404, message: 'Owner field not found.' }), 'OWNER_FIELD_NOT_IN_OWNER_TABLE': defineError({ code: 'OWNER_FIELD_NOT_IN_OWNER_TABLE', class: 'public', http: 400, message: 'Owner field not in owner table.' }), + 'PAGES_MODULE': defineError({ code: 'PAGES_MODULE', class: 'internal', http: 500, message: 'Pages module.' }), 'PARENT_ENTITY_TYPE_NOT_FOUND': defineError({ code: 'PARENT_ENTITY_TYPE_NOT_FOUND', class: 'internal', http: 500, message: 'Parent entity type not found.' }), 'PARENT_EXECUTION_NOT_FOUND': defineError({ code: 'PARENT_EXECUTION_NOT_FOUND', class: 'internal', http: 500, message: 'Parent execution 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_INSECURE': defineError({ code: 'PASSWORD_INSECURE', class: 'public', http: 400, message: 'Password insecure.' }), + 'PASSWORD_LEN': defineError({ code: 'PASSWORD_LEN', class: 'public', http: 400, message: 'Password len.' }), '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.' }), @@ -500,7 +512,7 @@ export const generatedRegistry: Record> = { 'SEED_PLAN_PLAN_NAME_REQUIRED': defineError({ code: 'SEED_PLAN_PLAN_NAME_REQUIRED', class: 'internal', http: 500, message: 'Seed plan plan name required.' }), 'SESSION_NOT_FOUND': defineError({ code: 'SESSION_NOT_FOUND', class: 'public', http: 404, message: 'Session not found.' }), 'SETTINGS_SURFACE_NOT_PROVISIONED': defineError({ code: 'SETTINGS_SURFACE_NOT_PROVISIONED', class: 'public', http: 400, message: 'Settings surface not provisioned.' }), - 'SIGN_UP_DISABLED': defineError({ code: 'SIGN_UP_DISABLED', class: 'public', http: 403, message: 'New registrations are currently disabled.' }), + 'SIGN_UP_DISABLED': defineError({ code: 'SIGN_UP_DISABLED', class: 'public', http: 403, message: 'Sign up disabled.' }), 'SITE_NOT_PROVISIONED': defineError({ code: 'SITE_NOT_PROVISIONED', class: 'public', http: 400, message: 'Site not provisioned.' }), 'SITE_SURFACE_NOT_PROVISIONED': defineError({ code: 'SITE_SURFACE_NOT_PROVISIONED', class: 'public', http: 400, message: 'Site surface not provisioned.' }), 'SMS_SIGN_IN_DISABLED': defineError({ code: 'SMS_SIGN_IN_DISABLED', class: 'public', http: 403, message: 'Sms sign in disabled.' }), @@ -509,12 +521,15 @@ export const generatedRegistry: Record> = { '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.' }), + 'STATIC_SITES_LIMIT': defineError({ code: 'STATIC_SITES_LIMIT', class: 'public', http: 429, message: 'Static sites limit.' }), 'STEP_UP_INVALID_TYPE': defineError({ code: 'STEP_UP_INVALID_TYPE', class: 'public', http: 403, message: 'This action requires verification that is not configured correctly. Please contact support.' }), - 'STEP_UP_REQUIRED': defineError({ code: 'STEP_UP_REQUIRED', class: 'public', http: 403, message: 'Please verify your identity to continue.' }), + 'STEP_UP_REQUIRED': defineError({ code: 'STEP_UP_REQUIRED', class: 'public', http: 403, message: 'Step up required.' }), 'STEP_UP_REQUIRED_FRESH_AUTH': defineError({ code: 'STEP_UP_REQUIRED_FRESH_AUTH', class: 'public', http: 403, message: 'Please verify your identity to continue.' }), 'STEP_UP_REQUIRED_MFA': defineError({ code: 'STEP_UP_REQUIRED_MFA', class: 'public', http: 403, message: 'Please enter a code from your authenticator app to continue.' }), 'STEP_UP_REQUIRED_PASSWORD': defineError({ code: 'STEP_UP_REQUIRED_PASSWORD', class: 'public', http: 403, message: 'Please re-enter your password to continue.' }), - 'STEP_UP_REQUIRED_PASSWORD_OR_MFA': defineError({ code: 'STEP_UP_REQUIRED_PASSWORD_OR_MFA', class: 'public', http: 403, message: 'Please verify your identity to continue.' }), + 'SUBDOMAIN_APEX_NOT_PUBLISHED': defineError({ code: 'SUBDOMAIN_APEX_NOT_PUBLISHED', class: 'public', http: 400, message: 'Subdomain apex not published.' }), + 'SUBDOMAIN_LABEL_EXHAUSTED': defineError({ code: 'SUBDOMAIN_LABEL_EXHAUSTED', class: 'public', http: 400, message: 'Subdomain label exhausted.' }), + 'SUBDOMAIN_LABEL_INVALID': defineError({ code: 'SUBDOMAIN_LABEL_INVALID', class: 'public', http: 400, message: 'Subdomain label invalid.' }), 'SUPER_CONSTRUCTIVE_REQUIRED': defineError({ code: 'SUPER_CONSTRUCTIVE_REQUIRED', class: 'public', http: 400, message: 'Super constructive required.' }), 'TABLE_MODULE': defineError({ code: 'TABLE_MODULE', class: 'internal', http: 500, message: 'Table module.' }), 'TABLE_MODULE_TABLE_NOT_FOUND': defineError({ code: 'TABLE_MODULE_TABLE_NOT_FOUND', class: 'internal', http: 500, message: 'Table module table not found.' }), @@ -571,6 +586,8 @@ export const GENERATED_CODE_META: Record = { 'APPLY_RLS': { class: 'internal', dynamic: false, generatedOnly: false }, 'APPLY_RLS_BAD_ARGS': { class: 'internal', dynamic: false, generatedOnly: false }, 'APPLY_TABLE_STEP_UP_TABLE_NOT_FOUND': { class: 'public', dynamic: false, generatedOnly: false }, + 'APP_INSTALLATION_MISSING': { class: 'public', dynamic: false, generatedOnly: false }, + 'APP_INSTALL_NO_SERVICE': { class: 'public', dynamic: false, generatedOnly: false }, 'ASSIGN_PROFILES_PERMISSION_REQUIRED': { class: 'public', dynamic: false, generatedOnly: false }, 'AUTHZ_COLUMN_SECURITY_AUTHZ_NODE_REQUIRED': { class: 'public', dynamic: false, generatedOnly: false }, 'AUTHZ_COLUMN_SECURITY_COLUMNS_REQUIRED': { class: 'public', dynamic: false, generatedOnly: false }, @@ -606,6 +623,7 @@ export const GENERATED_CODE_META: Record = { 'BAD_PRIVILEGE_FOR_POLICY': { class: 'public', dynamic: false, generatedOnly: false }, 'BAD_RLS_EXPRESSION': { class: 'public', dynamic: false, generatedOnly: false }, 'BAD_VIEW_EXPRESSION': { class: 'public', dynamic: false, generatedOnly: false }, + 'BEHAVIOR_FRAGMENT_EMPTY': { class: 'internal', dynamic: false, generatedOnly: false }, 'BM25': { class: 'public', dynamic: false, generatedOnly: false }, 'BOOTSTRAP_DATABASE_MISSING': { class: 'internal', dynamic: false, generatedOnly: false }, 'BOOTSTRAP_SOURCE_MISSING': { class: 'internal', dynamic: false, generatedOnly: false }, @@ -642,6 +660,7 @@ export const GENERATED_CODE_META: Record = { 'CROSS_DATABASE_CHECK': { class: 'public', dynamic: false, generatedOnly: false }, 'CROSS_DATABASE_REF': { class: 'public', dynamic: false, generatedOnly: false }, 'CSRF_TOKEN_REQUIRED': { class: 'public', dynamic: false, generatedOnly: false }, + 'DATABASE_CLAIM_REQUIRED': { class: 'internal', dynamic: false, generatedOnly: true }, 'DATABASE_FIELD_RESERVED_WORD': { class: 'public', dynamic: false, generatedOnly: false }, 'DATABASE_NOT_FOUND': { class: 'public', dynamic: false, generatedOnly: false }, 'DATABASE_SETTINGS_TABLES_SCOPE_REQUIRED': { class: 'public', dynamic: false, generatedOnly: false }, @@ -673,6 +692,10 @@ export const GENERATED_CODE_META: Record = { 'DATA_GENERATED_TABLE_NOT_FOUND': { class: 'public', dynamic: false, generatedOnly: false }, 'DATA_GENERATED_TARGET_REQUIRED': { class: 'public', dynamic: false, generatedOnly: false }, 'DATA_GENERATED_UNKNOWN_KIND': { class: 'public', dynamic: false, generatedOnly: false }, + 'DATA_HISTORY_NO_COPY_FIELDS': { class: 'public', dynamic: false, generatedOnly: false }, + 'DATA_HISTORY_ON_HISTORY_TABLE': { class: 'public', dynamic: false, generatedOnly: false }, + 'DATA_HISTORY_REQUIRES_PK': { class: 'public', dynamic: false, generatedOnly: false }, + 'DATA_HISTORY_TABLE_NOT_FOUND': { class: 'public', dynamic: false, generatedOnly: false }, 'DATA_I18N_FIELDS_REQUIRED': { class: 'public', dynamic: false, generatedOnly: false }, 'DATA_I18N_FIELD_NOT_FOUND': { class: 'public', dynamic: false, generatedOnly: false }, 'DATA_I18N_MODULE_NOT_PROVISIONED': { class: 'public', dynamic: false, generatedOnly: false }, @@ -698,6 +721,7 @@ export const GENERATED_CODE_META: Record = { 'DATA_JOB_TRIGGER_CONDITIONS_WATCH_FIELDS_CONFLICT': { class: 'public', dynamic: false, generatedOnly: false }, 'DATA_JOB_TRIGGER_CONDITION_FIELD_NOT_FOUND': { class: 'public', dynamic: false, generatedOnly: false }, 'DATA_JOB_TRIGGER_CONDITION_FIELD_WATCH_FIELDS_CONFLICT': { class: 'public', dynamic: false, generatedOnly: false }, + 'DATA_JOB_TRIGGER_DATABASE_ID_FIELD_NOT_FOUND': { class: 'public', dynamic: false, generatedOnly: false }, 'DATA_JOB_TRIGGER_ENTITY_FIELD_NOT_FOUND': { class: 'public', dynamic: false, generatedOnly: false }, 'DATA_JOB_TRIGGER_ENTITY_LOOKUP_OBJ_FIELD_NOT_FOUND': { class: 'public', dynamic: false, generatedOnly: false }, 'DATA_JOB_TRIGGER_ENTITY_LOOKUP_OBJ_FIELD_REQUIRED': { class: 'public', dynamic: false, generatedOnly: false }, @@ -782,10 +806,11 @@ export const GENERATED_CODE_META: Record = { 'EVENT_TRACKER_UNKNOWN_EVENT': { class: 'public', dynamic: false, generatedOnly: false }, 'EVENT_TRACKER_WATCH_FIELD_NOT_FOUND': { class: 'public', dynamic: false, generatedOnly: false }, 'EXECUTION_NOT_FOUND': { class: 'internal', dynamic: false, generatedOnly: false }, - 'EXPIRED_TOKEN': { class: 'public', dynamic: false, generatedOnly: false }, + 'EXPIRED_TOKEN': { class: 'internal', 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: 'public', dynamic: false, generatedOnly: false }, + 'FOREIGN_KEY_CONSTRAINT_NOT_FOUND': { class: 'internal', dynamic: false, generatedOnly: false }, 'FUNCTION_GRAPH_NOT_FOUND': { class: 'internal', dynamic: false, generatedOnly: false }, 'FUNCTION_SCHEDULES': { class: 'internal', dynamic: false, generatedOnly: false }, 'FUNCTION_SCHEDULES_INTERVAL': { class: 'internal', dynamic: false, generatedOnly: false }, @@ -824,6 +849,7 @@ export const GENERATED_CODE_META: Record = { '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 }, + 'HISTORY': { 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 }, @@ -945,6 +971,7 @@ export const GENERATED_CODE_META: Record = { 'ORG_API_KEY_NOT_FOUND': { class: 'public', dynamic: false, generatedOnly: false }, 'OWNER_FIELD_NOT_FOUND': { class: 'public', dynamic: false, generatedOnly: false }, 'OWNER_FIELD_NOT_IN_OWNER_TABLE': { class: 'public', dynamic: false, generatedOnly: false }, + 'PAGES_MODULE': { class: 'internal', dynamic: false, generatedOnly: false }, 'PARENT_ENTITY_TYPE_NOT_FOUND': { class: 'internal', dynamic: false, generatedOnly: false }, 'PARENT_EXECUTION_NOT_FOUND': { class: 'internal', dynamic: false, generatedOnly: false }, 'PASSWORD_INSECURE': { class: 'public', dynamic: false, generatedOnly: false }, @@ -1044,12 +1071,15 @@ export const GENERATED_CODE_META: Record = { '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 }, + 'STATIC_SITES_LIMIT': { class: 'public', dynamic: false, generatedOnly: false }, 'STEP_UP_INVALID_TYPE': { class: 'public', dynamic: false, generatedOnly: false }, 'STEP_UP_REQUIRED': { class: 'public', dynamic: false, generatedOnly: false }, 'STEP_UP_REQUIRED_FRESH_AUTH': { class: 'public', dynamic: false, generatedOnly: false }, 'STEP_UP_REQUIRED_MFA': { 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: true }, + 'SUBDOMAIN_APEX_NOT_PUBLISHED': { class: 'public', dynamic: false, generatedOnly: false }, + 'SUBDOMAIN_LABEL_EXHAUSTED': { class: 'public', dynamic: false, generatedOnly: false }, + 'SUBDOMAIN_LABEL_INVALID': { class: 'public', dynamic: false, generatedOnly: false }, 'SUPER_CONSTRUCTIVE_REQUIRED': { class: 'public', dynamic: false, generatedOnly: false }, 'TABLE_MODULE': { class: 'internal', dynamic: false, generatedOnly: false }, 'TABLE_MODULE_TABLE_NOT_FOUND': { class: 'internal', dynamic: false, generatedOnly: false }, @@ -1093,4 +1123,4 @@ export const GENERATED_CODE_META: Record = { }; /** Total number of codes collected from constructive-db. */ -export const GENERATED_CODE_COUNT = 531; +export const GENERATED_CODE_COUNT = 546; diff --git a/packages/errors/src/http.ts b/packages/errors/src/http.ts new file mode 100644 index 000000000..d61895c9e --- /dev/null +++ b/packages/errors/src/http.ts @@ -0,0 +1,63 @@ +import { getDefinition } from './registry'; + +/** Status used when a code carries no mapping. */ +export const UNMAPPED_HTTP_STATUS = 500; + +export interface HttpStatusResolution { + /** The status a transport should send. */ + status: number; + /** + * Whether `status` came from the registry. `false` means the code is not + * registered and `status` is the {@link UNMAPPED_HTTP_STATUS} fallback — the + * one case where a 500 does not mean "the server broke". + */ + mapped: boolean; +} + +/** Notified once per unmapped code. */ +export type UnmappedStatusReporter = (code: string) => void; + +const reported = new Set(); + +const defaultReporter: UnmappedStatusReporter = code => { + // eslint-disable-next-line no-console + console.warn( + `[constructive-errors] no HTTP status mapping for ${code}; responding ${UNMAPPED_HTTP_STATUS}. ` + + 'Register the code so intentional refusals stop surfacing as server errors.' + ); +}; + +let reporter: UnmappedStatusReporter = defaultReporter; + +/** + * Replace the unmapped-code reporter (pass `null` to restore the default, or a + * no-op to silence it). Transports with a real logger should route it there. + */ +export function setUnmappedStatusReporter(next: UnmappedStatusReporter | null): void { + reporter = next ?? defaultReporter; +} + +/** Test seam: forget which codes have already been reported. */ +export function resetUnmappedStatusReports(): void { + reported.clear(); +} + +/** + * Resolve the HTTP status for an error code. + * + * An unregistered code is reported (once per code) rather than silently + * degrading to 500: a refusal that is plainly a 403 or 409 turning into a 500 + * looks like a crash, and the only way anyone notices is by reading the + * transport's source. + */ +export function httpStatusFor(code: string | null | undefined): HttpStatusResolution { + const def = code ? getDefinition(code) : undefined; + if (def) return { status: def.http, mapped: true }; + + if (code && !reported.has(code)) { + reported.add(code); + reporter(code); + } + + return { status: UNMAPPED_HTTP_STATUS, mapped: false }; +} diff --git a/packages/errors/src/index.ts b/packages/errors/src/index.ts index fa5a7fb78..38170034a 100644 --- a/packages/errors/src/index.ts +++ b/packages/errors/src/index.ts @@ -2,6 +2,7 @@ export * from './classify'; export * from './error'; export * from './factory'; export * from './format'; +export * from './http'; export * from './interpolate'; export * from './parse'; export * from './pg'; diff --git a/packages/errors/src/parse.ts b/packages/errors/src/parse.ts index 49bc37aed..807076af1 100644 --- a/packages/errors/src/parse.ts +++ b/packages/errors/src/parse.ts @@ -1,6 +1,7 @@ import { classify } from './classify'; import { ConstructiveError } from './error'; import { format } from './format'; +import { httpStatusFor } from './http'; import { extractPgErrorFields, RAISE_EXCEPTION_SQLSTATE, SQLSTATE_TO_CODE } from './pg'; import { getDefinition } from './registry'; import type { ContextValue, ErrorClass, ErrorContext, ParsedError } from './types'; @@ -187,7 +188,9 @@ export function parse(error: unknown): ParsedError { * Delegates to {@link parse} for code/context/class recovery, then resolves a * localized message from the registry catalogs (falling back to the source * error's raw message when the code is unknown) and the registry's HTTP hint. - * Codes that could not be resolved become `UNKNOWN_ERROR` (internal). + * Codes that could not be resolved become `UNKNOWN_ERROR` (internal); a code + * with no registered status is reported by {@link httpStatusFor} rather than + * quietly becoming a 500. */ export function toError(error: unknown, locale?: string): ConstructiveError { if (error instanceof ConstructiveError) return error; @@ -203,7 +206,7 @@ export function toError(error: unknown, locale?: string): ConstructiveError { code, message, errorClass: parsed.class, - http: def?.http ?? 500, + http: def ? def.http : httpStatusFor(code).status, context: parsed.context }); }