From c2bc7a607cac65754eb1fb41362e75ecf4635623 Mon Sep 17 00:00:00 2001 From: Dan Lynch Date: Sat, 19 Sep 2026 01:22:45 +0000 Subject: [PATCH] feat(errors): register verified-only identifier claim codes (IDENTIFIER_VERIFIED_ELSEWHERE, IDENTIFIER_UNVERIFIED_AMBIGUOUS, IDENTIFIER_CLAIM_LIMIT, MFA_IDENTIFIER_UNVERIFIED) Re-audited constructive-db main (post #3830) and regenerated the registry; curated public copy for the identifier/MFA/SMS codes and the new public billing-provider codes picked up by the same audit. --- .../__tests__/identifier-claims.test.ts | 45 ++++ .../errors/scripts/db-error-inventory.json | 232 +++++++++++++++--- .../src/generated/registry.generated.ts | 48 +++- packages/errors/src/registry.ts | 69 ++++++ 4 files changed, 354 insertions(+), 40 deletions(-) create mode 100644 packages/errors/__tests__/identifier-claims.test.ts diff --git a/packages/errors/__tests__/identifier-claims.test.ts b/packages/errors/__tests__/identifier-claims.test.ts new file mode 100644 index 000000000..5198edcde --- /dev/null +++ b/packages/errors/__tests__/identifier-claims.test.ts @@ -0,0 +1,45 @@ +import { readFileSync } from 'fs'; +import { join } from 'path'; + +import { classify, errors, format, getDefinition, httpStatusFor, toError } from '../src'; +import { generatedRegistry } from '../src/generated/registry.generated'; + +const inventory: Record = JSON.parse( + readFileSync(join(__dirname, '../scripts/db-error-inventory.json'), 'utf-8') +); + +const expected = [ + { code: 'IDENTIFIER_VERIFIED_ELSEWHERE', http: 409 }, + { code: 'IDENTIFIER_UNVERIFIED_AMBIGUOUS', http: 409 }, + { code: 'IDENTIFIER_CLAIM_LIMIT', http: 429 }, + { code: 'MFA_IDENTIFIER_UNVERIFIED', http: 403 }, + { code: 'SMS_VERIFICATION_DISABLED', http: 403 } +] as const; + +describe('verified-only identifier claim codes', () => { + it('are audited from constructive-db and curated as public', () => { + for (const { code, http } of expected) { + expect(inventory[code]).toBeDefined(); + expect(generatedRegistry[code]).toBeDefined(); + expect(classify(code)).toBe('public'); + expect(httpStatusFor(code)).toEqual({ status: http, mapped: true }); + expect(format(code)).not.toBe(code); + expect(getDefinition(code)?.message).toMatch(/[a-z] [a-z]/); + } + }); + + it('carries the claim limit through DETAIL context', () => { + const err = toError({ + message: 'IDENTIFIER_CLAIM_LIMIT', + code: 'P0001', + detail: JSON.stringify({ code: 'IDENTIFIER_CLAIM_LIMIT', context: { limit: 3 }, class: 'public' }) + }); + expect(err).toMatchObject({ + code: 'IDENTIFIER_CLAIM_LIMIT', + errorClass: 'public', + http: 429, + context: { limit: 3 } + }); + expect(errors.IDENTIFIER_CLAIM_LIMIT({ limit: 3 }).context).toEqual({ limit: 3 }); + }); +}); diff --git a/packages/errors/scripts/db-error-inventory.json b/packages/errors/scripts/db-error-inventory.json index e94eef218..306abe494 100644 --- a/packages/errors/scripts/db-error-inventory.json +++ b/packages/errors/scripts/db-error-inventory.json @@ -8,10 +8,10 @@ "class": "public" }, "ACCOUNT_EXISTS": { - "count": 34, + "count": 26, "dynamic": false, "sample": "ACCOUNT_EXISTS", - "n_source": 9, + "n_source": 5, "n_generated": 8, "class": "public" }, @@ -24,7 +24,7 @@ "class": "public" }, "ACCOUNT_NOT_FOUND": { - "count": 12, + "count": 14, "dynamic": false, "sample": "ACCOUNT_NOT_FOUND", "n_source": 4, @@ -40,11 +40,11 @@ "class": "internal" }, "ALREADY_AUTHENTICATED_USE_LINK": { - "count": 4, + "count": 8, "dynamic": false, "sample": "ALREADY_AUTHENTICATED_USE_LINK", - "n_source": 1, - "n_generated": 3, + "n_source": 2, + "n_generated": 4, "class": "public" }, "ALREADY_SCHEDULED": { @@ -154,6 +154,30 @@ "n_source": 2, "n_generated": 4 }, + "AST_OPTIONS_NOT_OBJECT": { + "count": 2, + "dynamic": false, + "sample": "AST_OPTIONS_NOT_OBJECT", + "n_source": 1, + "n_generated": 1, + "class": "internal" + }, + "AST_OPTION_REQUIRED": { + "count": 2, + "dynamic": false, + "sample": "AST_OPTION_REQUIRED", + "n_source": 1, + "n_generated": 1, + "class": "internal" + }, + "AST_OPTION_UNKNOWN": { + "count": 2, + "dynamic": false, + "sample": "AST_OPTION_UNKNOWN", + "n_source": 1, + "n_generated": 1, + "class": "internal" + }, "ATTRIBUTION_REQUIRED": { "count": 4, "dynamic": false, @@ -449,6 +473,94 @@ "n_generated": 3, "class": "internal" }, + "BILLING_ENTITY_BUSY": { + "count": 2, + "dynamic": false, + "sample": "BILLING_ENTITY_BUSY", + "n_source": 0, + "n_generated": 2, + "class": "public" + }, + "BILLING_HEALTH_INVALID": { + "count": 2, + "dynamic": false, + "sample": "BILLING_HEALTH_INVALID", + "n_source": 0, + "n_generated": 2, + "class": "internal" + }, + "BILLING_NOT_READY": { + "count": 3, + "dynamic": false, + "sample": "BILLING_NOT_READY", + "n_source": 1, + "n_generated": 2, + "class": "public" + }, + "BILLING_OBSERVATION_INVALID": { + "count": 2, + "dynamic": false, + "sample": "BILLING_OBSERVATION_INVALID", + "n_source": 0, + "n_generated": 2, + "class": "internal" + }, + "BILLING_OPERATION_CONFLICT": { + "count": 2, + "dynamic": false, + "sample": "BILLING_OPERATION_CONFLICT", + "n_source": 0, + "n_generated": 2, + "class": "public" + }, + "BILLING_OPERATION_INVALID": { + "count": 2, + "dynamic": false, + "sample": "BILLING_OPERATION_INVALID", + "n_source": 0, + "n_generated": 2, + "class": "public" + }, + "BILLING_OPERATION_NOT_FOUND": { + "count": 2, + "dynamic": false, + "sample": "BILLING_OPERATION_NOT_FOUND", + "n_source": 0, + "n_generated": 2, + "class": "internal" + }, + "BILLING_OPERATION_SETTLED": { + "count": 2, + "dynamic": false, + "sample": "BILLING_OPERATION_SETTLED", + "n_source": 0, + "n_generated": 2, + "class": "internal" + }, + "BILLING_OPERATION_STATE_INVALID": { + "count": 2, + "dynamic": false, + "sample": "BILLING_OPERATION_STATE_INVALID", + "n_source": 0, + "n_generated": 2, + "class": "internal" + }, + "BILLING_RECONCILE_BOUNDS_REQUIRED": { + "count": 2, + "dynamic": false, + "sample": "BILLING_RECONCILE_BOUNDS_REQUIRED", + "n_source": 0, + "n_generated": 2, + "class": "internal" + }, + "BILLING_SCHEDULED_CHANGE_INVALID": { + "count": 4, + "dynamic": false, + "sample": "BILLING_SCHEDULED_CHANGE_INVALID", + "n_source": 0, + "n_generated": 3, + "class": "internal" + }, "BLUEPRINT_MODULE_NOT_INSTALLED": { "count": 2, "dynamic": false, @@ -2212,11 +2324,11 @@ "class": "public" }, "ENTITY_REQUIRED": { - "count": 4, + "count": 8, "dynamic": false, "sample": "ENTITY_REQUIRED", "n_source": 1, - "n_generated": 3, + "n_generated": 4, "class": "internal" }, "ENTITY_TYPE_CLAIM_REQUIRED": { @@ -2545,11 +2657,11 @@ "class": "internal" }, "FORBIDDEN": { - "count": 44, + "count": 70, "dynamic": false, "sample": "FORBIDDEN", "n_source": 4, - "n_generated": 22, + "n_generated": 35, "class": "public" }, "FOREIGN_KEY_CONSTRAINT_NOT_FOUND": { @@ -2746,9 +2858,9 @@ "class": "public" }, "GUARD_STEP_UP": { - "count": 10, + "count": 12, "dynamic": true, - "sample": "GUARD_STEP_UP: unknown event %, expected INSERT, UPDATE, or DELETE", + "sample": "GUARD_STEP_UP: min_age anchor takes fk_col or anchor_conditions, not both", "n_source": 1, "n_generated": 1 }, @@ -3123,12 +3235,36 @@ "n_generated": 4, "class": "public" }, + "IDENTIFIER_CLAIM_LIMIT": { + "count": 7, + "dynamic": false, + "sample": "IDENTIFIER_CLAIM_LIMIT", + "n_source": 1, + "n_generated": 4, + "class": "public" + }, "IDENTIFIER_CONFLICT": { - "count": 4, + "count": 6, "dynamic": false, "sample": "IDENTIFIER_CONFLICT", "n_source": 1, - "n_generated": 3, + "n_generated": 4, + "class": "public" + }, + "IDENTIFIER_UNVERIFIED_AMBIGUOUS": { + "count": 19, + "dynamic": false, + "sample": "IDENTIFIER_UNVERIFIED_AMBIGUOUS", + "n_source": 1, + "n_generated": 8, + "class": "public" + }, + "IDENTIFIER_VERIFIED_ELSEWHERE": { + "count": 44, + "dynamic": false, + "sample": "IDENTIFIER_VERIFIED_ELSEWHERE", + "n_source": 1, + "n_generated": 10, "class": "public" }, "IDENTITY_ACCOUNT_NOT_FOUND": { @@ -3282,10 +3418,10 @@ "class": "public" }, "INTERNAL_ERROR": { - "count": 452, + "count": 460, "dynamic": false, "sample": "INTERNAL_ERROR", - "n_source": 95, + "n_source": 96, "n_generated": 4, "class": "internal" }, @@ -3454,11 +3590,11 @@ "class": "public" }, "INVITE_NOT_FOUND": { - "count": 12, + "count": 18, "dynamic": false, "sample": "INVITE_NOT_FOUND", - "n_source": 2, - "n_generated": 4, + "n_source": 3, + "n_generated": 6, "class": "public" }, "INVITE_PHONE_NOT_FOUND": { @@ -4125,6 +4261,14 @@ "n_generated": 3, "class": "public" }, + "MFA_IDENTIFIER_UNVERIFIED": { + "count": 16, + "dynamic": false, + "sample": "MFA_IDENTIFIER_UNVERIFIED", + "n_source": 4, + "n_generated": 6, + "class": "public" + }, "MFA_REQUIRED": { "count": 6, "dynamic": false, @@ -4234,11 +4378,11 @@ "class": "public" }, "NOT_AUTHENTICATED": { - "count": 122, + "count": 130, "dynamic": false, "sample": "NOT_AUTHENTICATED", - "n_source": 29, - "n_generated": 31, + "n_source": 30, + "n_generated": 33, "class": "public" }, "NOT_AUTHORIZED": { @@ -4401,11 +4545,11 @@ "class": "public" }, "PERMISSION_DENIED": { - "count": 24, + "count": 42, "dynamic": false, "sample": "PERMISSION_DENIED", - "n_source": 1, - "n_generated": 6, + "n_source": 2, + "n_generated": 10, "class": "public" }, "PLAN_REQUIRED": { @@ -4503,11 +4647,11 @@ "class": "public" }, "PRINCIPAL_CANNOT_CREATE_PRINCIPAL": { - "count": 12, + "count": 19, "dynamic": false, "sample": "PRINCIPAL_CANNOT_CREATE_PRINCIPAL", - "n_source": 3, - "n_generated": 5, + "n_source": 4, + "n_generated": 7, "class": "public" }, "PRINCIPAL_CANNOT_DELETE_PRINCIPAL": { @@ -5530,6 +5674,14 @@ "n_generated": 2, "class": "public" }, + "SMS_VERIFICATION_DISABLED": { + "count": 4, + "dynamic": false, + "sample": "SMS_VERIFICATION_DISABLED", + "n_source": 1, + "n_generated": 3, + "class": "public" + }, "SOURCE_COMMIT_NOT_FOUND": { "count": 2, "dynamic": false, @@ -5642,11 +5794,11 @@ "class": "public" }, "STEP_UP_REQUIRED": { - "count": 23, + "count": 19, "dynamic": false, "sample": "STEP_UP_REQUIRED", "n_source": 1, - "n_generated": 11, + "n_generated": 10, "class": "public" }, "STEP_UP_REQUIRED_FRESH_AUTH": { @@ -5658,11 +5810,11 @@ "class": "public" }, "STEP_UP_REQUIRED_MFA": { - "count": 4, + "count": 6, "dynamic": false, "sample": "STEP_UP_REQUIRED_MFA", "n_source": 1, - "n_generated": 3, + "n_generated": 4, "class": "public" }, "STEP_UP_REQUIRED_PASSWORD": { @@ -6026,11 +6178,11 @@ "class": "public" }, "TOO_MANY_REQUESTS": { - "count": 51, + "count": 59, "dynamic": false, "sample": "TOO_MANY_REQUESTS", - "n_source": 1, - "n_generated": 25, + "n_source": 2, + "n_generated": 27, "class": "public" }, "TOTP_ALREADY_ENABLED": { @@ -6144,6 +6296,14 @@ "n_generated": 4, "class": "public" }, + "UNKNOWN_METER": { + "count": 2, + "dynamic": false, + "sample": "UNKNOWN_METER", + "n_source": 0, + "n_generated": 2, + "class": "public" + }, "UNKNOWN_POLICY_TYPE": { "count": 2, "dynamic": false, @@ -6342,4 +6502,4 @@ "n_generated": 1, "class": "public" } -} +} \ No newline at end of file diff --git a/packages/errors/src/generated/registry.generated.ts b/packages/errors/src/generated/registry.generated.ts index d135fda91..e4a482417 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 (804 distinct codes + * Source of truth: the constructive-db error audit (824 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: 804 total, 594 public, 210 internal. + * Counts: 824 total, 604 public, 220 internal. */ import { defineError, type DefinedError } from '../define'; import type { ErrorContext } from '../types'; @@ -44,6 +44,9 @@ export const generatedRegistry: Record> = { '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_CAPABILITY_REQUIRED': defineError({ code: 'ASSIGN_PROFILES_CAPABILITY_REQUIRED', class: 'internal', http: 500, message: 'Assign profiles capability required.' }), + 'AST_OPTIONS_NOT_OBJECT': defineError({ code: 'AST_OPTIONS_NOT_OBJECT', class: 'internal', http: 500, message: 'Ast options not object.' }), + 'AST_OPTION_REQUIRED': defineError({ code: 'AST_OPTION_REQUIRED', class: 'internal', http: 500, message: 'Ast option required.' }), + 'AST_OPTION_UNKNOWN': defineError({ code: 'AST_OPTION_UNKNOWN', class: 'internal', http: 500, message: 'Ast option unknown.' }), 'ATTRIBUTION_REQUIRED': defineError({ code: 'ATTRIBUTION_REQUIRED', class: 'internal', http: 500, message: 'Attribution 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.' }), @@ -81,6 +84,17 @@ export const generatedRegistry: Record> = { '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.' }), 'BILLING_DISPUTE_BALANCE_NOT_FOUND': defineError({ code: 'BILLING_DISPUTE_BALANCE_NOT_FOUND', class: 'internal', http: 500, message: 'Billing dispute balance not found.' }), + 'BILLING_ENTITY_BUSY': defineError({ code: 'BILLING_ENTITY_BUSY', class: 'public', http: 400, message: 'Billing entity busy.' }), + 'BILLING_HEALTH_INVALID': defineError({ code: 'BILLING_HEALTH_INVALID', class: 'internal', http: 500, message: 'Billing health invalid.' }), + 'BILLING_NOT_READY': defineError({ code: 'BILLING_NOT_READY', class: 'public', http: 400, message: 'Billing not ready.' }), + 'BILLING_OBSERVATION_INVALID': defineError({ code: 'BILLING_OBSERVATION_INVALID', class: 'internal', http: 500, message: 'Billing observation invalid.' }), + 'BILLING_OPERATION_CONFLICT': defineError({ code: 'BILLING_OPERATION_CONFLICT', class: 'public', http: 400, message: 'Billing operation conflict.' }), + 'BILLING_OPERATION_INVALID': defineError({ code: 'BILLING_OPERATION_INVALID', class: 'public', http: 400, message: 'Billing operation invalid.' }), + 'BILLING_OPERATION_NOT_FOUND': defineError({ code: 'BILLING_OPERATION_NOT_FOUND', class: 'internal', http: 500, message: 'Billing operation not found.' }), + 'BILLING_OPERATION_SETTLED': defineError({ code: 'BILLING_OPERATION_SETTLED', class: 'internal', http: 500, message: 'Billing operation settled.' }), + 'BILLING_OPERATION_STATE_INVALID': defineError({ code: 'BILLING_OPERATION_STATE_INVALID', class: 'internal', http: 500, message: 'Billing operation state invalid.' }), + 'BILLING_RECONCILE_BOUNDS_REQUIRED': defineError({ code: 'BILLING_RECONCILE_BOUNDS_REQUIRED', class: 'internal', http: 500, message: 'Billing reconcile bounds required.' }), + 'BILLING_SCHEDULED_CHANGE_INVALID': defineError({ code: 'BILLING_SCHEDULED_CHANGE_INVALID', class: 'internal', http: 500, message: 'Billing scheduled change invalid.' }), 'BLUEPRINT_MODULE_NOT_INSTALLED': defineError({ code: 'BLUEPRINT_MODULE_NOT_INSTALLED', class: 'public', http: 400, message: 'Blueprint module not installed.' }), 'BLUEPRINT_MODULE_REF_AMBIGUOUS': defineError({ code: 'BLUEPRINT_MODULE_REF_AMBIGUOUS', class: 'public', http: 400, message: 'Blueprint module ref ambiguous.' }), 'BLUEPRINT_MODULE_REF_INVALID': defineError({ code: 'BLUEPRINT_MODULE_REF_INVALID', class: 'public', http: 400, message: 'Blueprint module ref invalid.' }), @@ -372,7 +386,7 @@ export const generatedRegistry: Record> = { 'GRAPH_TREE_NOT_FOUND': defineError({ code: 'GRAPH_TREE_NOT_FOUND', class: 'internal', http: 500, message: 'Graph tree not found.' }), '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: unknown event {{arg0}}, expected INSERT, UPDATE, or DELETE', positional: ['arg0'] }), + 'GUARD_STEP_UP': defineError({ code: 'GUARD_STEP_UP', class: 'internal', http: 500, message: 'GUARD_STEP_UP: min_age anchor takes fk_col or anchor_conditions, not both' }), 'GUARD_STEP_UP_CONDITIONS_CONDITION_FIELD_CONFLICT': defineError({ code: 'GUARD_STEP_UP_CONDITIONS_CONDITION_FIELD_CONFLICT', class: 'public', http: 403, message: 'Guard step up conditions condition field conflict.' }), 'GUARD_STEP_UP_CONDITIONS_WATCH_FIELDS_CONFLICT': defineError({ code: 'GUARD_STEP_UP_CONDITIONS_WATCH_FIELDS_CONFLICT', class: 'public', http: 403, message: 'Guard step up conditions watch fields conflict.' }), 'GUARD_STEP_UP_CONDITION_FIELD_NOT_FOUND': defineError({ code: 'GUARD_STEP_UP_CONDITION_FIELD_NOT_FOUND', class: 'public', http: 404, message: 'Guard step up condition field not found.' }), @@ -420,7 +434,10 @@ export const generatedRegistry: Record> = { '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'] }), 'HOSTNAME_CLAIMED_IN_OTHER_PLANE': defineError({ code: 'HOSTNAME_CLAIMED_IN_OTHER_PLANE', class: 'public', http: 400, message: 'Hostname claimed in other plane.' }), + 'IDENTIFIER_CLAIM_LIMIT': defineError({ code: 'IDENTIFIER_CLAIM_LIMIT', class: 'public', http: 429, message: 'Identifier claim limit.' }), 'IDENTIFIER_CONFLICT': defineError({ code: 'IDENTIFIER_CONFLICT', class: 'public', http: 400, message: 'Identifier conflict.' }), + 'IDENTIFIER_UNVERIFIED_AMBIGUOUS': defineError({ code: 'IDENTIFIER_UNVERIFIED_AMBIGUOUS', class: 'public', http: 400, message: 'Identifier unverified ambiguous.' }), + 'IDENTIFIER_VERIFIED_ELSEWHERE': defineError({ code: 'IDENTIFIER_VERIFIED_ELSEWHERE', class: 'public', http: 400, message: 'Identifier verified elsewhere.' }), '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.' }), @@ -547,6 +564,7 @@ export const generatedRegistry: Record> = { 'MERKLE_REF_NOT_FOUND': defineError({ code: 'MERKLE_REF_NOT_FOUND', class: 'public', http: 404, message: 'Merkle ref not found.' }), '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_IDENTIFIER_UNVERIFIED': defineError({ code: 'MFA_IDENTIFIER_UNVERIFIED', class: 'public', http: 403, message: 'Mfa identifier unverified.' }), '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: 'public', http: 400, message: 'Missing required field.' }), @@ -726,6 +744,7 @@ export const generatedRegistry: Record> = { '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.' }), 'SMS_SIGN_UP_DISABLED': defineError({ code: 'SMS_SIGN_UP_DISABLED', class: 'public', http: 403, message: 'Sms sign up disabled.' }), + 'SMS_VERIFICATION_DISABLED': defineError({ code: 'SMS_VERIFICATION_DISABLED', class: 'public', http: 403, message: 'Sms verification disabled.' }), 'SOURCE_COMMIT_NOT_FOUND': defineError({ code: 'SOURCE_COMMIT_NOT_FOUND', class: 'internal', http: 500, message: 'Source commit not found.' }), '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.' }), @@ -803,6 +822,7 @@ export const generatedRegistry: Record> = { 'TRIGGER_WHEN_AST_ROW_UNAVAILABLE': defineError({ code: 'TRIGGER_WHEN_AST_ROW_UNAVAILABLE', class: 'public', http: 400, message: 'Trigger when ast row unavailable.' }), 'TRUST_LADDER_OPTION_INVALID': defineError({ code: 'TRUST_LADDER_OPTION_INVALID', class: 'internal', http: 500, message: 'Trust ladder option invalid.' }), 'UNAUTHENTICATED': defineError({ code: 'UNAUTHENTICATED', class: 'public', http: 401, message: 'Unauthenticated.' }), + 'UNKNOWN_METER': defineError({ code: 'UNKNOWN_METER', class: 'public', http: 400, message: 'Unknown meter.' }), 'UNKNOWN_POLICY_TYPE': defineError({ code: 'UNKNOWN_POLICY_TYPE', class: 'public', http: 400, message: 'Unknown policy type.' }), 'UNKNOWN_VIEW_TYPE': defineError({ code: 'UNKNOWN_VIEW_TYPE', class: 'public', http: 400, message: 'Unknown view type.', positional: ['arg0'] }), 'UNSUPPORTED': defineError({ code: 'UNSUPPORTED', class: 'internal', http: 500, message: 'UNSUPPORTED POLICY ({{arg0}})', positional: ['arg0'] }), @@ -852,6 +872,9 @@ export const GENERATED_CODE_META: Record = { 'APP_INSTALLATION_MISSING': { class: 'public', dynamic: false, generatedOnly: false }, 'APP_INSTALL_NO_SERVICE': { class: 'public', dynamic: false, generatedOnly: false }, 'ASSIGN_PROFILES_CAPABILITY_REQUIRED': { class: 'internal', dynamic: false, generatedOnly: false }, + 'AST_OPTIONS_NOT_OBJECT': { class: 'internal', dynamic: false, generatedOnly: false }, + 'AST_OPTION_REQUIRED': { class: 'internal', dynamic: false, generatedOnly: false }, + 'AST_OPTION_UNKNOWN': { class: 'internal', dynamic: false, generatedOnly: false }, 'ATTRIBUTION_REQUIRED': { class: 'internal', dynamic: false, generatedOnly: true }, 'AUTHZ_COLUMN_SECURITY_AUTHZ_NODE_REQUIRED': { class: 'public', dynamic: false, generatedOnly: false }, 'AUTHZ_COLUMN_SECURITY_COLUMNS_REQUIRED': { class: 'public', dynamic: false, generatedOnly: false }, @@ -889,6 +912,17 @@ export const GENERATED_CODE_META: Record = { 'BAD_VIEW_EXPRESSION': { class: 'public', dynamic: false, generatedOnly: false }, 'BEHAVIOR_FRAGMENT_EMPTY': { class: 'internal', dynamic: false, generatedOnly: false }, 'BILLING_DISPUTE_BALANCE_NOT_FOUND': { class: 'internal', dynamic: false, generatedOnly: false }, + 'BILLING_ENTITY_BUSY': { class: 'public', dynamic: false, generatedOnly: true }, + 'BILLING_HEALTH_INVALID': { class: 'internal', dynamic: false, generatedOnly: true }, + 'BILLING_NOT_READY': { class: 'public', dynamic: false, generatedOnly: false }, + 'BILLING_OBSERVATION_INVALID': { class: 'internal', dynamic: false, generatedOnly: true }, + 'BILLING_OPERATION_CONFLICT': { class: 'public', dynamic: false, generatedOnly: true }, + 'BILLING_OPERATION_INVALID': { class: 'public', dynamic: false, generatedOnly: true }, + 'BILLING_OPERATION_NOT_FOUND': { class: 'internal', dynamic: false, generatedOnly: true }, + 'BILLING_OPERATION_SETTLED': { class: 'internal', dynamic: false, generatedOnly: true }, + 'BILLING_OPERATION_STATE_INVALID': { class: 'internal', dynamic: false, generatedOnly: true }, + 'BILLING_RECONCILE_BOUNDS_REQUIRED': { class: 'internal', dynamic: false, generatedOnly: true }, + 'BILLING_SCHEDULED_CHANGE_INVALID': { class: 'internal', dynamic: false, generatedOnly: true }, 'BLUEPRINT_MODULE_NOT_INSTALLED': { class: 'public', dynamic: false, generatedOnly: false }, 'BLUEPRINT_MODULE_REF_AMBIGUOUS': { class: 'public', dynamic: false, generatedOnly: false }, 'BLUEPRINT_MODULE_REF_INVALID': { class: 'public', dynamic: false, generatedOnly: false }, @@ -1228,7 +1262,10 @@ export const GENERATED_CODE_META: Record = { 'HISTORY': { class: 'internal', dynamic: true, generatedOnly: false }, 'HOSTNAME_BINDING_SYNC': { class: 'internal', dynamic: true, generatedOnly: false }, 'HOSTNAME_CLAIMED_IN_OTHER_PLANE': { class: 'public', dynamic: false, generatedOnly: true }, + 'IDENTIFIER_CLAIM_LIMIT': { class: 'public', dynamic: false, generatedOnly: false }, 'IDENTIFIER_CONFLICT': { class: 'public', dynamic: false, generatedOnly: false }, + 'IDENTIFIER_UNVERIFIED_AMBIGUOUS': { class: 'public', dynamic: false, generatedOnly: false }, + 'IDENTIFIER_VERIFIED_ELSEWHERE': { class: 'public', 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 }, @@ -1355,6 +1392,7 @@ export const GENERATED_CODE_META: Record = { 'MERKLE_REF_NOT_FOUND': { class: 'public', dynamic: false, generatedOnly: false }, 'METER_RATE_LIMIT': { class: 'internal', dynamic: true, generatedOnly: false }, 'MFA_CHALLENGE_EXPIRED': { class: 'public', dynamic: false, generatedOnly: false }, + 'MFA_IDENTIFIER_UNVERIFIED': { 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: 'public', dynamic: false, generatedOnly: false }, @@ -1534,6 +1572,7 @@ export const GENERATED_CODE_META: Record = { 'SITE_SURFACE_NOT_PROVISIONED': { 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 }, + 'SMS_VERIFICATION_DISABLED': { class: 'public', dynamic: false, generatedOnly: false }, 'SOURCE_COMMIT_NOT_FOUND': { class: 'internal', dynamic: false, generatedOnly: false }, 'SOURCE_EMAILS_NOT_FOUND': { class: 'internal', dynamic: false, generatedOnly: false }, 'SOURCE_SECRETS_NOT_FOUND': { class: 'internal', dynamic: false, generatedOnly: false }, @@ -1611,6 +1650,7 @@ export const GENERATED_CODE_META: Record = { 'TRIGGER_WHEN_AST_ROW_UNAVAILABLE': { class: 'public', dynamic: false, generatedOnly: false }, 'TRUST_LADDER_OPTION_INVALID': { class: 'internal', dynamic: false, generatedOnly: false }, 'UNAUTHENTICATED': { class: 'public', dynamic: false, generatedOnly: false }, + 'UNKNOWN_METER': { class: 'public', dynamic: false, generatedOnly: true }, 'UNKNOWN_POLICY_TYPE': { class: 'public', dynamic: false, generatedOnly: false }, 'UNKNOWN_VIEW_TYPE': { class: 'public', dynamic: true, generatedOnly: false }, 'UNSUPPORTED': { class: 'internal', dynamic: true, generatedOnly: false }, @@ -1639,4 +1679,4 @@ export const GENERATED_CODE_META: Record = { }; /** Total number of codes collected from constructive-db. */ -export const GENERATED_CODE_COUNT = 804; +export const GENERATED_CODE_COUNT = 824; diff --git a/packages/errors/src/registry.ts b/packages/errors/src/registry.ts index 0518b49e4..362dff60b 100644 --- a/packages/errors/src/registry.ts +++ b/packages/errors/src/registry.ts @@ -127,6 +127,41 @@ export const registry = { http: 409, message: 'The email and phone number belong to different accounts. Provide only one of them.' }), + // Verified-only identifier uniqueness: an email/phone may sit unverified on + // several accounts, but only one account may hold it verified. + IDENTIFIER_VERIFIED_ELSEWHERE: defineError({ + code: 'IDENTIFIER_VERIFIED_ELSEWHERE', + class: 'public', + http: 409, + message: + 'This email or phone number is already verified on another account. Sign in to that account or use a different one.' + }), + IDENTIFIER_UNVERIFIED_AMBIGUOUS: defineError({ + code: 'IDENTIFIER_UNVERIFIED_AMBIGUOUS', + class: 'public', + http: 409, + message: + 'This email or phone number is pending verification on more than one account. Verify it first to continue.' + }), + IDENTIFIER_CLAIM_LIMIT: defineError<{ limit?: number }>({ + code: 'IDENTIFIER_CLAIM_LIMIT', + class: 'public', + http: 429, + message: + 'This account has too many unverified emails or phone numbers. Verify or remove one before adding another.' + }), + MFA_IDENTIFIER_UNVERIFIED: defineError({ + code: 'MFA_IDENTIFIER_UNVERIFIED', + class: 'public', + http: 403, + message: 'Verify this email or phone number before using it for multi-factor authentication.' + }), + SMS_VERIFICATION_DISABLED: defineError({ + code: 'SMS_VERIFICATION_DISABLED', + class: 'public', + http: 403, + message: 'Phone number verification is not enabled.' + }), ACCOUNT_NOT_FOUND: defineError({ code: 'ACCOUNT_NOT_FOUND', class: 'public', @@ -442,6 +477,40 @@ export const registry = { message: 'Too many requests. Please slow down and try again shortly.' }), + // =========================================================================== + // Billing provider operations (public) + // =========================================================================== + BILLING_NOT_READY: defineError<{ database_id?: string; checked_at?: string | null; ready?: boolean | null }>({ + code: 'BILLING_NOT_READY', + class: 'public', + http: 409, + message: 'Billing is not ready for this database yet. Please try again shortly.' + }), + BILLING_ENTITY_BUSY: defineError<{ entity_id?: string }>({ + code: 'BILLING_ENTITY_BUSY', + class: 'public', + http: 409, + message: 'Another billing operation is already in progress for this account. Please wait for it to finish.' + }), + BILLING_OPERATION_CONFLICT: defineError<{ operation_id?: string }>({ + code: 'BILLING_OPERATION_CONFLICT', + class: 'public', + http: 409, + message: 'This billing operation ID was already used for a different request.' + }), + BILLING_OPERATION_INVALID: defineError({ + code: 'BILLING_OPERATION_INVALID', + class: 'public', + http: 400, + message: 'The billing operation request is invalid.' + }), + UNKNOWN_METER: defineError<{ meter_slug?: string }>({ + code: 'UNKNOWN_METER', + class: 'public', + http: 404, + message: 'The requested meter does not exist.' + }), + // =========================================================================== // Request protection (public, dynamic) — raised by the GraphQL runtime when a // request exceeds the bounds resolved from database_settings/api_settings.