From fc65ee67443138d76986ece14c7b46db169b3c45 Mon Sep 17 00:00:00 2001 From: yyyyaaa Date: Wed, 30 Sep 2026 10:34:29 +0700 Subject: [PATCH] feat(errors): register billing refusal codes (BILLING_SUBSCRIPTION_ACTIVE, BILLING_QUOTA_EXCEEDED) Re-audited constructive-db feat/platform-billing-generic (69a1913ffc8) and regenerated the registry; curated public copy for the deletion guard's BILLING_SUBSCRIPTION_ACTIVE (409) and the usage gate's BILLING_QUOTA_EXCEEDED (429). The same audit adds the internal BILLING_CHECKOUT_BINDING_INVALID, BOOTSTRAP_OWNER_NOT_ACTIVE and SCHEDULED_JOB_NOT_ENQUEUED, and drops FREE_PLAN_REQUIRED, which the billing branch no longer raises. --- .../errors/__tests__/billing-refusals.test.ts | 46 ++++++++++++ .../errors/scripts/db-error-inventory.json | 74 +++++++++++++------ .../src/generated/registry.generated.ts | 20 +++-- packages/errors/src/registry.ts | 12 +++ 4 files changed, 125 insertions(+), 27 deletions(-) create mode 100644 packages/errors/__tests__/billing-refusals.test.ts diff --git a/packages/errors/__tests__/billing-refusals.test.ts b/packages/errors/__tests__/billing-refusals.test.ts new file mode 100644 index 000000000..f85a38127 --- /dev/null +++ b/packages/errors/__tests__/billing-refusals.test.ts @@ -0,0 +1,46 @@ +import { readFileSync } from 'fs'; +import { join } from 'path'; + +import { httpStatusFor, parse, toError } from '../src'; + +const inventory: Record = JSON.parse( + readFileSync(join(__dirname, '../scripts/db-error-inventory.json'), 'utf-8') +); + +/** The shape errors.raise_error gives node-postgres: P0001 with the payload in DETAIL. */ +const raised = (code: string, context: Record, errorClass: 'public' | 'internal') => + Object.assign(new Error(code), { + code: 'P0001', + detail: JSON.stringify({ code, context, class: errorClass }) + }); + +describe('billing refusals raised by constructive-db', () => { + it('answers a delete blocked by a live subscription as a 409 with its context', () => { + const context = { entity_id: '6f1c2f0e-0000-4000-8000-000000000001', external_subscription_id: 'sub_123' }; + const err = raised('BILLING_SUBSCRIPTION_ACTIVE', context, 'public'); + + expect(inventory.BILLING_SUBSCRIPTION_ACTIVE).toBeDefined(); + expect(httpStatusFor(parse(err).code)).toEqual({ status: 409, mapped: true }); + expect(toError(err)).toMatchObject({ + code: 'BILLING_SUBSCRIPTION_ACTIVE', + errorClass: 'public', + http: 409, + context + }); + }); + + it('answers an exhausted usage allowance as a 429', () => { + const err = raised('BILLING_QUOTA_EXCEEDED', {}, 'public'); + + expect(inventory.BILLING_QUOTA_EXCEEDED).toBeDefined(); + expect(toError(err)).toMatchObject({ errorClass: 'public', http: 429 }); + }); + + it('keeps a malformed checkout binding a registered internal 500', () => { + const err = raised('BILLING_CHECKOUT_BINDING_INVALID', { entity_id: null, operation_id: null }, 'internal'); + + expect(inventory.BILLING_CHECKOUT_BINDING_INVALID).toBeDefined(); + expect(httpStatusFor('BILLING_CHECKOUT_BINDING_INVALID')).toEqual({ status: 500, mapped: true }); + expect(toError(err)).toMatchObject({ errorClass: 'internal', http: 500 }); + }); +}); diff --git a/packages/errors/scripts/db-error-inventory.json b/packages/errors/scripts/db-error-inventory.json index cc8a2ee76..de55f44c8 100644 --- a/packages/errors/scripts/db-error-inventory.json +++ b/packages/errors/scripts/db-error-inventory.json @@ -290,7 +290,7 @@ "class": "public" }, "BAD_EXPRESSION": { - "count": 581, + "count": 575, "dynamic": true, "sample": "BAD_EXPRESSION % (unrecognized format)", "n_source": 2, @@ -465,6 +465,14 @@ "n_generated": 1, "class": "internal" }, + "BILLING_CHECKOUT_BINDING_INVALID": { + "count": 2, + "dynamic": false, + "sample": "BILLING_CHECKOUT_BINDING_INVALID", + "n_source": 0, + "n_generated": 2, + "class": "internal" + }, "BILLING_DISPUTE_BALANCE_NOT_FOUND": { "count": 4, "dynamic": false, @@ -545,6 +553,14 @@ "n_generated": 2, "class": "internal" }, + "BILLING_QUOTA_EXCEEDED": { + "count": 4, + "dynamic": false, + "sample": "BILLING_QUOTA_EXCEEDED", + "n_source": 1, + "n_generated": 1, + "class": "public" + }, "BILLING_RECONCILE_BOUNDS_REQUIRED": { "count": 2, "dynamic": false, @@ -561,6 +577,14 @@ "n_generated": 3, "class": "internal" }, + "BILLING_SUBSCRIPTION_ACTIVE": { + "count": 3, + "dynamic": false, + "sample": "BILLING_SUBSCRIPTION_ACTIVE", + "n_source": 1, + "n_generated": 2, + "class": "public" + }, "BLUEPRINT_MODULE_NOT_INSTALLED": { "count": 2, "dynamic": false, @@ -609,6 +633,14 @@ "n_generated": 1, "class": "internal" }, + "BOOTSTRAP_OWNER_NOT_ACTIVE": { + "count": 4, + "dynamic": false, + "sample": "BOOTSTRAP_OWNER_NOT_ACTIVE", + "n_source": 1, + "n_generated": 1, + "class": "internal" + }, "BOOTSTRAP_SOURCE_MISSING": { "count": 2, "dynamic": false, @@ -1219,9 +1251,9 @@ "class": "public" }, "DATA_BILLING_METER": { - "count": 2, + "count": 4, "dynamic": true, - "sample": "DATA_BILLING_METER: unknown event %, expected INSERT, DELETE, or UPDATE", + "sample": "DATA_BILLING_METER: unknown event %, expected INSERT, DELETE, UPDATE, GATE or GATE_UPDATE", "n_source": 1, "n_generated": 1 }, @@ -2657,11 +2689,11 @@ "class": "internal" }, "FORBIDDEN": { - "count": 70, + "count": 76, "dynamic": false, "sample": "FORBIDDEN", - "n_source": 4, - "n_generated": 35, + "n_source": 6, + "n_generated": 36, "class": "public" }, "FOREIGN_KEY_CONSTRAINT_NOT_FOUND": { @@ -2672,14 +2704,6 @@ "n_generated": 1, "class": "internal" }, - "FREE_PLAN_REQUIRED": { - "count": 4, - "dynamic": false, - "sample": "FREE_PLAN_REQUIRED", - "n_source": 1, - "n_generated": 3, - "class": "internal" - }, "FUNCTION_BODY_DEPARSE_EMPTY": { "count": 2, "dynamic": false, @@ -3418,10 +3442,10 @@ "class": "public" }, "INTERNAL_ERROR": { - "count": 460, + "count": 464, "dynamic": false, "sample": "INTERNAL_ERROR", - "n_source": 96, + "n_source": 98, "n_generated": 4, "class": "internal" }, @@ -4011,7 +4035,7 @@ "class": "public" }, "LIMIT_TRACK_USAGE_UNKNOWN_EVENT": { - "count": 2, + "count": 4, "dynamic": false, "sample": "LIMIT_TRACK_USAGE_UNKNOWN_EVENT", "n_source": 1, @@ -4394,10 +4418,10 @@ "class": "public" }, "NOT_FOUND": { - "count": 76, + "count": 84, "dynamic": false, "sample": "NOT_FOUND", - "n_source": 20, + "n_source": 21, "n_generated": 3, "class": "public" }, @@ -5153,6 +5177,14 @@ "n_generated": 4, "class": "public" }, + "SCHEDULED_JOB_NOT_ENQUEUED": { + "count": 4, + "dynamic": false, + "sample": "SCHEDULED_JOB_NOT_ENQUEUED", + "n_source": 0, + "n_generated": 4, + "class": "internal" + }, "SCOPE_TYPE_BACKFILL": { "count": 6, "dynamic": false, @@ -6034,10 +6066,10 @@ "class": "public" }, "SUSPENSION_FORBIDDEN": { - "count": 2, + "count": 6, "dynamic": false, "sample": "SUSPENSION_FORBIDDEN", - "n_source": 1, + "n_source": 3, "n_generated": 1, "class": "public" }, diff --git a/packages/errors/src/generated/registry.generated.ts b/packages/errors/src/generated/registry.generated.ts index babfb0757..728772310 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 (817 distinct codes + * Source of truth: the constructive-db error audit (821 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: 817 total, 598 public, 219 internal. + * Counts: 821 total, 600 public, 221 internal. */ import { defineError, type DefinedError } from '../define'; import type { ErrorContext } from '../types'; @@ -83,6 +83,7 @@ export const generatedRegistry: Record> = { '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.' }), + 'BILLING_CHECKOUT_BINDING_INVALID': defineError({ code: 'BILLING_CHECKOUT_BINDING_INVALID', class: 'internal', http: 500, message: 'Billing checkout binding invalid.' }), '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.' }), @@ -93,14 +94,17 @@ export const generatedRegistry: Record> = { '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_QUOTA_EXCEEDED': defineError({ code: 'BILLING_QUOTA_EXCEEDED', class: 'public', http: 400, message: 'Billing quota exceeded.' }), '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.' }), + 'BILLING_SUBSCRIPTION_ACTIVE': defineError({ code: 'BILLING_SUBSCRIPTION_ACTIVE', class: 'public', http: 400, message: 'Billing subscription active.' }), '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.' }), 'BLUEPRINT_MODULE_TABLE_UNKNOWN': defineError({ code: 'BLUEPRINT_MODULE_TABLE_UNKNOWN', class: 'public', http: 400, message: 'Blueprint module table unknown.' }), '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_OWNER_NOT_ACTIVE': defineError({ code: 'BOOTSTRAP_OWNER_NOT_ACTIVE', class: 'internal', http: 500, message: 'Bootstrap owner not active.' }), 'BOOTSTRAP_SOURCE_MISSING': defineError({ code: 'BOOTSTRAP_SOURCE_MISSING', class: 'internal', http: 500, message: 'Bootstrap source missing.' }), 'BOOTSTRAP_TARGET_NOT_EMPTY': defineError({ code: 'BOOTSTRAP_TARGET_NOT_EMPTY', class: 'internal', http: 500, message: 'Bootstrap target not empty.' }), 'BUILD_CONDITION_EXPR': defineError({ code: 'BUILD_CONDITION_EXPR', class: 'internal', http: 500, message: 'Build condition expr.' }), @@ -178,7 +182,7 @@ export const generatedRegistry: Record> = { 'DATABASE_TRANSFER_NOT_PENDING': defineError({ code: 'DATABASE_TRANSFER_NOT_PENDING', class: 'public', http: 400, message: 'Database transfer not pending.' }), 'DATABASE_TRANSFER_NOT_SOURCE_OWNER': defineError({ code: 'DATABASE_TRANSFER_NOT_SOURCE_OWNER', class: 'public', http: 400, message: 'Database transfer not source owner.' }), 'DATABASE_TRANSFER_NOT_TARGET_OWNER': defineError({ code: 'DATABASE_TRANSFER_NOT_TARGET_OWNER', class: 'public', http: 400, message: 'Database transfer not target owner.' }), - 'DATA_BILLING_METER': defineError({ code: 'DATA_BILLING_METER', class: 'internal', http: 500, message: 'DATA_BILLING_METER: unknown event {{arg0}}, expected INSERT, DELETE, or UPDATE', positional: ['arg0'] }), + 'DATA_BILLING_METER': defineError({ code: 'DATA_BILLING_METER', class: 'internal', http: 500, message: 'DATA_BILLING_METER: unknown event {{arg0}}, expected INSERT, DELETE, UPDATE, GATE or GATE_UPDATE', positional: ['arg0'] }), 'DATA_COMPOSITE_FIELD_PRIVATE_SCHEMA_NOT_FOUND': defineError({ code: 'DATA_COMPOSITE_FIELD_PRIVATE_SCHEMA_NOT_FOUND', class: 'public', http: 404, message: 'Data composite field private schema not found.' }), 'DATA_COMPOSITE_FIELD_SOURCE_FIELDS_EMPTY': defineError({ code: 'DATA_COMPOSITE_FIELD_SOURCE_FIELDS_EMPTY', class: 'public', http: 400, message: 'Data composite field source fields empty.' }), 'DATA_COMPOSITE_FIELD_SOURCE_FIELDS_REQUIRED': defineError({ code: 'DATA_COMPOSITE_FIELD_SOURCE_FIELDS_REQUIRED', class: 'public', http: 400, message: 'Data composite field source fields required.' }), @@ -362,7 +366,6 @@ export const generatedRegistry: Record> = { 'FILE_REF_FIELD': defineError({ code: 'FILE_REF_FIELD', class: 'internal', http: 500, message: 'File ref field.' }), 'FORBIDDEN': defineError({ code: 'FORBIDDEN', class: 'public', http: 403, message: 'Forbidden.' }), 'FOREIGN_KEY_CONSTRAINT_NOT_FOUND': defineError({ code: 'FOREIGN_KEY_CONSTRAINT_NOT_FOUND', class: 'internal', http: 500, message: 'Foreign key constraint not found.' }), - 'FREE_PLAN_REQUIRED': defineError({ code: 'FREE_PLAN_REQUIRED', class: 'internal', http: 500, message: 'Free plan required.' }), 'FUNCTION_BODY_DEPARSE_EMPTY': defineError({ code: 'FUNCTION_BODY_DEPARSE_EMPTY', class: 'internal', http: 500, message: 'Function body deparse empty.' }), 'FUNCTION_ENVELOPE_VIOLATION': defineError({ code: 'FUNCTION_ENVELOPE_VIOLATION', class: 'internal', http: 500, message: 'Function envelope violation.' }), 'FUNCTION_GRAPH_NOT_FOUND': defineError({ code: 'FUNCTION_GRAPH_NOT_FOUND', class: 'internal', http: 500, message: 'Function graph not found.' }), @@ -678,6 +681,7 @@ export const generatedRegistry: Record> = { 'ROUTING_API_TABLES_SCOPE_REQUIRED': defineError({ code: 'ROUTING_API_TABLES_SCOPE_REQUIRED', class: 'public', http: 400, message: 'Routing api tables scope required.' }), 'ROUTING_SURFACE_NOT_PROVISIONED': defineError({ code: 'ROUTING_SURFACE_NOT_PROVISIONED', class: 'public', http: 400, message: 'Routing surface not provisioned.' }), 'ROW_LOCKED': defineError({ code: 'ROW_LOCKED', class: 'public', http: 423, message: 'Row locked.' }), + 'SCHEDULED_JOB_NOT_ENQUEUED': defineError({ code: 'SCHEDULED_JOB_NOT_ENQUEUED', class: 'internal', http: 500, message: 'Scheduled job not enqueued.' }), 'SCOPE_TYPE_BACKFILL': defineError({ code: 'SCOPE_TYPE_BACKFILL', class: 'internal', http: 500, message: 'SCOPE_TYPE_BACKFILL: the membership types table must be resolved before generating a statement' }), 'SCOPE_TYPE_SYNC': defineError({ code: 'SCOPE_TYPE_SYNC', class: 'internal', http: 500, message: 'SCOPE_TYPE_SYNC: the scope type projection must be resolved before generating a body' }), 'SEARCH_BM25': defineError({ code: 'SEARCH_BM25', class: 'public', http: 400, message: 'Search bm25.' }), @@ -904,6 +908,7 @@ export const GENERATED_CODE_META: Record = { '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 }, + 'BILLING_CHECKOUT_BINDING_INVALID': { class: 'internal', dynamic: false, generatedOnly: true }, '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 }, @@ -914,14 +919,17 @@ export const GENERATED_CODE_META: Record = { '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_QUOTA_EXCEEDED': { class: 'public', dynamic: false, generatedOnly: false }, 'BILLING_RECONCILE_BOUNDS_REQUIRED': { class: 'internal', dynamic: false, generatedOnly: true }, 'BILLING_SCHEDULED_CHANGE_INVALID': { class: 'internal', dynamic: false, generatedOnly: true }, + 'BILLING_SUBSCRIPTION_ACTIVE': { class: 'public', dynamic: false, generatedOnly: false }, '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 }, 'BLUEPRINT_MODULE_TABLE_UNKNOWN': { class: 'public', dynamic: false, generatedOnly: false }, 'BM25': { class: 'public', dynamic: false, generatedOnly: false }, 'BOOTSTRAP_DATABASE_MISSING': { class: 'internal', dynamic: false, generatedOnly: false }, + 'BOOTSTRAP_OWNER_NOT_ACTIVE': { class: 'internal', dynamic: false, generatedOnly: false }, 'BOOTSTRAP_SOURCE_MISSING': { class: 'internal', dynamic: false, generatedOnly: false }, 'BOOTSTRAP_TARGET_NOT_EMPTY': { class: 'internal', dynamic: false, generatedOnly: false }, 'BUILD_CONDITION_EXPR': { class: 'internal', dynamic: false, generatedOnly: false }, @@ -1183,7 +1191,6 @@ export const GENERATED_CODE_META: Record = { 'FILE_REF_FIELD': { class: 'internal', dynamic: false, generatedOnly: false }, 'FORBIDDEN': { class: 'public', dynamic: false, generatedOnly: false }, 'FOREIGN_KEY_CONSTRAINT_NOT_FOUND': { class: 'internal', dynamic: false, generatedOnly: false }, - 'FREE_PLAN_REQUIRED': { class: 'internal', dynamic: false, generatedOnly: false }, 'FUNCTION_BODY_DEPARSE_EMPTY': { class: 'internal', dynamic: false, generatedOnly: false }, 'FUNCTION_ENVELOPE_VIOLATION': { class: 'internal', dynamic: false, generatedOnly: false }, 'FUNCTION_GRAPH_NOT_FOUND': { class: 'internal', dynamic: false, generatedOnly: false }, @@ -1499,6 +1506,7 @@ export const GENERATED_CODE_META: Record = { 'ROUTING_API_TABLES_SCOPE_REQUIRED': { class: 'public', dynamic: false, generatedOnly: false }, 'ROUTING_SURFACE_NOT_PROVISIONED': { class: 'public', dynamic: false, generatedOnly: false }, 'ROW_LOCKED': { class: 'public', dynamic: false, generatedOnly: false }, + 'SCHEDULED_JOB_NOT_ENQUEUED': { class: 'internal', dynamic: false, generatedOnly: true }, 'SCOPE_TYPE_BACKFILL': { class: 'internal', dynamic: false, generatedOnly: false }, 'SCOPE_TYPE_SYNC': { class: 'internal', dynamic: true, generatedOnly: false }, 'SEARCH_BM25': { class: 'public', dynamic: false, generatedOnly: false }, @@ -1665,4 +1673,4 @@ export const GENERATED_CODE_META: Record = { }; /** Total number of codes collected from constructive-db. */ -export const GENERATED_CODE_COUNT = 817; +export const GENERATED_CODE_COUNT = 821; diff --git a/packages/errors/src/registry.ts b/packages/errors/src/registry.ts index 374e33c88..b6e96b741 100644 --- a/packages/errors/src/registry.ts +++ b/packages/errors/src/registry.ts @@ -386,6 +386,12 @@ export const registry = { message: 'You have reached a plan limit for this resource.', positional: ['resource', 'limit'] }), + BILLING_QUOTA_EXCEEDED: defineError({ + code: 'BILLING_QUOTA_EXCEEDED', + class: 'public', + http: 429, + message: 'You have used up your usage allowance for this resource.' + }), RATE_LIMITED: defineError({ code: 'RATE_LIMITED', class: 'public', @@ -420,6 +426,12 @@ export const registry = { http: 400, message: 'The billing operation request is invalid.' }), + BILLING_SUBSCRIPTION_ACTIVE: defineError<{ entity_id?: string; external_subscription_id?: string }>({ + code: 'BILLING_SUBSCRIPTION_ACTIVE', + class: 'public', + http: 409, + message: 'This account still has an active subscription. Cancel the subscription before deleting the account.' + }), UNKNOWN_METER: defineError<{ meter_slug?: string }>({ code: 'UNKNOWN_METER', class: 'public',