ObjectStackObjectStack

Error Code Ledger

Error Code Ledger protocol schemas

Error-Code Ledger (ADR-0112 D3).

The top-level error.code vocabulary is two-tier:

  1. Standard catalogStandardErrorCode (errors.zod.ts): a small, closed set with platform-wide HTTP semantics. It does NOT grow when a service invents a code.
  2. Registered extension codes — THIS ledger: every service-specific code a route may put in error.code, registered under its owning package.

ErrorCode (exported below) is the union, and is what ApiErrorSchema.code validates against. An unregistered code fails schema parse — which fails the envelope conformance suites — which fails CI. That friction is the point (ADR-0112: "no silent fourth state" for error codes, per ADR-0049/0078).

Scope: THIS ledger registers framework packages only (#4805)

Every owner key below is a package published from this repository, and that is a RULE — not an accident of the current list, and not something a reader should have to infer by scanning the package names. A downstream product repo (objectstack-ai/cloud, or any product built on the platform) does not register its codes here. It maintains its OWN ledger, in its own repo, and composes the validation itself:

  1. shapeenvelopeViolations(body) (contract.zod.ts), and
  2. vocabularycode ∈ StandardErrorCode ∪ <its own ledger>, which makeApiErrorSchema(<its own ledger>) (contract.zod.ts) gives as a single parse instead of the two-step assertion.

The deployed wire vocabulary stays closed and checkable either way — which is what ADR-0112's "no silent fourth state" asks for. It never asked for every entry to live physically in one file.

Why federated rather than admitting downstream entries (maintainer ruling on #4805, 2026-08-03, re-confirmed 2026-08-09; raised from cloud#930/#944):

  • A commercial vocabulary does not belong in an Apache-2.0 spec. The codes worth registering are precisely the product-specific ones (billing and plan-gating states, control-plane provisioning refusals), and registering them here would have the OSS spec enumerate a closed-source product's states under package names absent from this distribution.
  • Cadence mismatch breeds bypass. A downstream code arrives with a downstream feature; making each one cost a cross-repo PR plus a pin bump pushes authors toward reusing a semantically wrong existing code, which is less visible than inventing one.

The corollary for THIS file: a PR adding an owner key for a package that is not published from this repository is out of scope by construction — the fix for that need is a ledger in the owning repo, composed as above. The one thing a downstream repo must NOT do is emit a code registered nowhere: that is the silent fourth state, wherever the ledger lives.

Registering a new code

Add it to your package's entry (create the entry if your package has none — a framework package; see the scope rule above if yours ships from another repo), SCREAMING_SNAKE (^[A-Z][A-Z0-9_]*$ — lint-enforced by error-code-ledger.test.ts), with a trailing // comment when the name alone doesn't carry the meaning. Prefer a domain prefix for anything not self-evidently global (ATTACHMENT_*, REPORT_*, SETTINGS_*). If the condition is generic (not found / permission / validation / rate limit), use the standard catalog instead of registering a synonym.

Since #8211 (adjudicated 2026-08-12, option C) that last sentence is MECHANICAL, not prose: the admission gate (error-code-ledger.test.ts) refuses a new code that standardSynonymOf maps to a standard-catalog member, unless the code carries a STANDARD_SYNONYM_WAIVERS entry recording why it stays and which member it shadows. Four synonyms had accumulated by the time the rule got teeth precisely because nothing was checking; they (plus a fifth the detector surfaced on landing) are grandfathered via waivers below — their wire values are unchanged, and consolidating any of them onto its standard member is a deliberate wire change DEFERRED by the #8211 adjudication (option B) until a specific code has a measured victim.

One rule, two doors (#8087): registration here is the ADMISSION door — what the vocabulary may contain. The DISPATCHER door is ruled (#8087, option B-as-a-gate, maintainer 2026-08-12) to parse every body it emits against the closed vocabulary; until that gate lands, resolveThrownHttpError (@objectstack/types, PR #8088) carries code (narrowed) / declaredCode (verbatim) across the gap. Both doors state the same rule: a code either IS the standard member for its condition, or it is registered here — and if it merely re-spells a standard member, that registration is a recorded waiver, never drift.

A code emitted by several packages is listed once per emitting package — the union dedupes; the per-package rows are provenance, not identity.

Retiring a code

A row whose last EMITTER is deleted comes out with it. The admission rules below check casing, duplication and shadowing — never whether anyone still throws the code — so a registered-but-unemittable row stays green forever while promising a client a code no response can carry. That is ADR-0112's "no silent fourth state" read backwards, and it is not hypothetical: OVERLAY_PERSISTENCE_FAILED outlived its only producer by one PR (#5264 deleted saveMetaItem's legacy raw-engine branch; #5783 unregistered the code). Nor does a row need to LOSE its producer to be in this class — a throw site whose error can never reach a response envelope was unemittable from birth: MONGODB_MULTI_TENANT_UNSUPPORTED (registered by #3724, unregistered by #8035) is a BOOT refusal — the CLI rethrows it pre-HTTP and aborts, and the one request-reachable trigger sits inside a documented best-effort catch that logs and continues. Its throw site and constant (MULTI_TENANT_UNSUPPORTED_CODE, @objectstack/driver-mongodb) live on: host boot matching is not wire vocabulary. Before deleting a row, check that no producer remains repo-wide AND that no consumer — including objectui and cloud — reads the literal; tests that merely CONSTRUCT the code are not producers, and a test pinned to a producerless code is pinning nothing (#4984's phantom-check family).

Field-level codes (FieldErrorSchema.code, the fields[] array) are a SEPARATE vocabulary and do not belong here — see #3977 (ADR-0112 D6).

Source: packages/spec/src/api/error-code-ledger.zod.ts

TypeScript Usage

import { ErrorCode, StandardSynonymWaiverSchema } from '@objectstack/spec/api';
import type { ErrorCode, StandardSynonymWaiver } from '@objectstack/spec/api';

// Validate data
const result = ErrorCode.parse(data);

ErrorCode

Allowed Values

  • VALIDATION_ERROR
  • INVALID_FIELD
  • MISSING_REQUIRED_FIELD
  • INVALID_FORMAT
  • VALUE_TOO_LONG
  • VALUE_TOO_SHORT
  • VALUE_OUT_OF_RANGE
  • INVALID_REFERENCE
  • DUPLICATE_VALUE
  • INVALID_QUERY
  • INVALID_FILTER
  • INVALID_SORT
  • MAX_RECORDS_EXCEEDED
  • UNAUTHENTICATED
  • INVALID_CREDENTIALS
  • EXPIRED_TOKEN
  • INVALID_TOKEN
  • SESSION_EXPIRED
  • MFA_REQUIRED
  • EMAIL_NOT_VERIFIED
  • PERMISSION_DENIED
  • INSUFFICIENT_PRIVILEGES
  • FIELD_NOT_ACCESSIBLE
  • RECORD_NOT_ACCESSIBLE
  • LICENSE_REQUIRED
  • IP_RESTRICTED
  • TIME_RESTRICTED
  • RESOURCE_NOT_FOUND
  • OBJECT_NOT_FOUND
  • RECORD_NOT_FOUND
  • FIELD_NOT_FOUND
  • ENDPOINT_NOT_FOUND
  • RESOURCE_CONFLICT
  • CONCURRENT_MODIFICATION
  • DELETE_RESTRICTED
  • DUPLICATE_RECORD
  • LOCK_CONFLICT
  • METHOD_NOT_ALLOWED
  • PRECONDITION_REQUIRED
  • RATE_LIMIT_EXCEEDED
  • QUOTA_EXCEEDED
  • CONCURRENT_LIMIT_EXCEEDED
  • INTERNAL_ERROR
  • DATABASE_ERROR
  • TIMEOUT
  • SERVICE_UNAVAILABLE
  • NOT_IMPLEMENTED
  • EXTERNAL_SERVICE_ERROR
  • INTEGRATION_ERROR
  • WEBHOOK_DELIVERY_FAILED
  • BATCH_PARTIAL_FAILURE
  • BATCH_COMPLETE_FAILURE
  • TRANSACTION_FAILED
  • ACCOUNT_LOCKED
  • ALREADY_REVERTED
  • AMBIGUOUS_MATCH
  • ANALYTICS_QUERY_FAILED
  • APPROVAL_ACTIONS_FAILED
  • APPROVAL_RECALL_FAILED
  • APPROVAL_REQUEST_GET_FAILED
  • APPROVAL_REQUEST_LIST_FAILED
  • ASYNC_NOT_SUPPORTED
  • ATTACHMENT_DELETE_DENIED
  • ATTACHMENT_DOWNLOAD_DENIED
  • ATTACHMENT_PARENT_ACCESS
  • AUDIENCE_NOT_ALLOWED
  • AUTH_CONFIG_ERROR
  • AUTH_REQUIRED
  • AUTOMATION_UNSCOPED_RUN_DATA_ACCESS
  • BATCH_ABORTED
  • BATCH_NOT_ATOMIC
  • BATCH_TOO_LARGE
  • BATCH_UNRESOLVED_REF
  • BLANK_MATCH_KEY
  • CLONE_DISABLED
  • CLOUD_FETCH_FAILED
  • CLOUD_UNCONFIGURED
  • COMMIT_NOT_FOUND
  • CONCURRENT_UPDATE
  • CONFLICT
  • CONFLICTING_MAPPING
  • CONNECTOR_UPSTREAM_UNAVAILABLE
  • CREATE_FAILED
  • CUBE_NOT_FOUND
  • DATASET_INVALID
  • DATASOURCE_ADMIN_ERROR
  • DELEGABLE_SCOPE_FAILED
  • DELIVERY_NEVER_SENT
  • DELIVERY_NOT_ELIGIBLE
  • DESTRUCTIVE_CHANGE
  • DEVICE_CODE_FAILED
  • DOMAIN_VERIFICATION_DISABLED
  • DOMAIN_VERIFICATION_FAILED
  • DRIVER_UNAVAILABLE
  • DUPLICATE_REQUEST
  • ELIGIBILITY_UNEVALUABLE
  • EMAIL_SEND_FAILED
  • EMAIL_SERVICE_REQUIRED
  • ENQUEUE_FAILED
  • ENVIRONMENT_BIND_FAILED
  • ENVIRONMENT_NOT_FOUND
  • ENV_ACCESS_DENIED
  • ERR_BULK_RESULT_MISMATCH
  • ERR_DATASOURCE_UNAVAILABLE
  • ERR_DRIVER_CONNECT
  • ERR_FILE_CONSTRAINT
  • ERR_FILE_REFERENCE_COPY
  • ERR_READONLY_FIELD_REJECTED
  • ERR_SUMMARY_RECOMPUTE
  • EXECUTION_ERROR
  • EXPIRED_OR_REVOKED
  • EXPIRY_IN_PAST
  • EXPIRY_TOO_LONG
  • EXPLAIN_FAILED
  • EXPORT_NOT_PERMITTED
  • EXTERNAL_DATASOURCE_ERROR
  • EXTERNAL_IMPORT_ERROR
  • EXTERNAL_SCHEMA_MISMATCH
  • EXTERNAL_SCHEMA_MODE_VIOLATION
  • EXTERNAL_WRITE_FORBIDDEN
  • FEEDS_DISABLED
  • FILES_DISABLED
  • FILE_DOWNLOAD_DENIED
  • FILE_FIELD_BULK_WRITE_REFUSED
  • FILE_NOT_FOUND
  • FILTER_TOKEN_UNKNOWN
  • FILTER_TOKEN_UNRESOLVED
  • FORBIDDEN
  • FORM_NOT_FOUND
  • FORM_RESOLVE_FAILED
  • IMPORT_JOB_CREATE_FAILED
  • IMPORT_ROW_FAILED
  • INTERNAL
  • INVALID_EMAIL
  • INVALID_EXPIRY
  • INVALID_METADATA
  • INVALID_OR_EXPIRED
  • INVALID_PHONE
  • INVALID_REQUEST
  • INVALID_RESUME_TOKEN
  • INVALID_SIGNAL
  • INVALID_SIGNATURE
  • INVALID_STATE
  • INVITE_EMAIL_FAILED
  • INVITE_REQUIRES_EMAIL
  • INVITE_SMS_FAILED
  • IP_NOT_ALLOWED
  • ITEM_LOCKED
  • LAST_LOCAL_CREDENTIAL
  • LOOKUP_NOT_PUBLIC
  • LOOKUP_TARGET_MISSING
  • MANIFEST_CONFLICT
  • MAPPING_FORMAT_MISMATCH
  • MAPPING_FORMAT_UNSUPPORTED
  • MAPPING_NOT_FOUND
  • MAPPING_TARGET_MISMATCH
  • MARKETPLACE_PROXY_FAILED
  • MARKETPLACE_STORAGE_FAILED
  • MARKETPLACE_UNAVAILABLE
  • METADATA_BRANCH
  • METADATA_CONFLICT
  • METADATA_NOT_FOUND
  • METADATA_SCHEMA_INVALID
  • NAMESPACE_PREFIX
  • NEEDS_PASSWORD
  • NODE_FAILURE
  • NOTHING_TO_PURGE
  • NOT_ATTEMPTED
  • NOT_CREATABLE
  • NOT_FOUND
  • NOT_OVERRIDABLE
  • NOT_UNDOABLE
  • NO_DRAFT
  • NO_EXECUTOR
  • NO_IDENTITY
  • NO_MATCH
  • NO_PENDING_VERIFICATION
  • OAUTH_REGISTER_FAILED
  • OBJECT_API_DISABLED
  • OBJECT_API_METHOD_NOT_ALLOWED
  • OBJECT_OVERLAY_PACKAGE_MISMATCH
  • OBJECT_PACKAGE_DISABLED
  • OPENAPI_UNAVAILABLE
  • OS_PROTOCOL_INCOMPATIBLE
  • PACKAGE_DELETE_FAILED
  • PACKAGE_DELETE_PARTIAL
  • PACKAGE_MANIFEST_INVALID
  • PACKAGE_PUBLISH_FAILED
  • PASSWORD_ALREADY_SET
  • PASSWORD_EXPIRED
  • PASSWORD_POLICY_VIOLATION
  • PASSWORD_REUSE
  • PAYLOAD_TOO_LARGE
  • PERMISSION_NOT_ALLOWED
  • PHONE_NOT_ENABLED
  • PLUGIN_INSTALL_FAILED
  • PLUGIN_MANIFEST_INVALID
  • PLUGIN_REGISTER_FAILED
  • PROJECT_MEMBERSHIP_REQUIRED
  • PROJECT_NOT_FOUND
  • PROJECT_PROVISIONING
  • PROJECT_PROVISIONING_FAILED
  • RAW_SQL_UNSUPPORTED
  • READ_SCOPE_COMPILE_FAILED
  • RECORD_GONE
  • RECORD_LOCKED
  • RECORD_NOT_ELIGIBLE
  • REPORTS_LIST_FAILED
  • REPORT_DELETE_FAILED
  • REPORT_GET_FAILED
  • REPORT_NOT_FOUND
  • REPORT_RUN_FAILED
  • REPORT_SAVE_FAILED
  • REPORT_SCHEDULE_FAILED
  • REQUEST_NOT_FOUND
  • RESEED_NO_ROWS
  • RESEED_SKIPPED
  • RESUME_FAILED
  • RESUME_IN_PROGRESS
  • RESUME_TARGET_LOST
  • ROLLED_BACK
  • ROUTE_NOT_FOUND
  • RULE_DEFINE_FAILED
  • RULE_DELETE_FAILED
  • RULE_EVALUATE_FAILED
  • RULE_GET_FAILED
  • RULE_LIST_FAILED
  • RULE_NOT_FOUND
  • RUN_NOT_FOUND
  • SAML_REGISTER_FAILED
  • SCHEDULES_LIST_FAILED
  • SCHEDULE_DELETE_FAILED
  • SETTINGS_ACTION_FAILED
  • SETTINGS_CRYPTO_UNAVAILABLE
  • SETTINGS_FORBIDDEN
  • SETTINGS_LOCKED
  • SETTINGS_UNKNOWN_KEY
  • SETTINGS_UNKNOWN_NAMESPACE
  • SETTINGS_VALIDATION
  • SHARES_LIST_FAILED
  • SHARE_GRANT_FAILED
  • SHARE_REVOKE_FAILED
  • SHARING_NOT_ENABLED
  • SIGN_IN_REQUIRED
  • SSO_REGISTER_FAILED
  • SSO_REGISTER_FORBIDDEN
  • STORE_UNAVAILABLE
  • SUGGESTION_CONFIRM_FAILED
  • SUGGESTION_DISMISS_FAILED
  • SUGGESTION_LIST_FAILED
  • SUGGESTION_NOT_FOUND
  • SUGGESTION_STATE
  • SUMMARY_RECOMPUTE_FAILED
  • TENANT_SCOPE_REQUIRED
  • UNAUTHORIZED
  • UNIQUE_VIOLATION
  • UNKNOWN_KEY
  • UNKNOWN_NAMESPACE
  • UNSUPPORTED
  • UNSUPPORTED_QUERY_PARAM
  • UNSUPPORTED_TRANSFORM
  • UPLOAD_SESSION_EXPIRED
  • UPLOAD_SESSION_NOT_FOUND
  • USER_ALREADY_EXISTS
  • VALIDATION_FAILED
  • VERSION_NOT_FOUND
  • VERSION_NOT_RESTORABLE
  • WRITABLE_PACKAGE_REQUIRED
  • WRONG_PASSWORD

StandardSynonymWaiver

Properties

PropertyTypeRequiredDescription
codestringThe registered extension code the waiver keeps admissible
shadowsEnum<'VALIDATION_ERROR' | 'INVALID_FIELD' | 'MISSING_REQUIRED_FIELD' | 'INVALID_FORMAT' | 'VALUE_TOO_LONG' | 'VALUE_TOO_SHORT' | 'VALUE_OUT_OF_RANGE' | … +46 more>The standard-catalog member whose condition the code re-spells
reasonstringWhy the synonym stays registered — recorded so admission is a decision, not drift

Allowed Values: StandardSynonymWaiver.shadows

  • VALIDATION_ERROR
  • INVALID_FIELD
  • MISSING_REQUIRED_FIELD
  • INVALID_FORMAT
  • VALUE_TOO_LONG
  • VALUE_TOO_SHORT
  • VALUE_OUT_OF_RANGE
  • INVALID_REFERENCE
  • DUPLICATE_VALUE
  • INVALID_QUERY
  • INVALID_FILTER
  • INVALID_SORT
  • MAX_RECORDS_EXCEEDED
  • UNAUTHENTICATED
  • INVALID_CREDENTIALS
  • EXPIRED_TOKEN
  • INVALID_TOKEN
  • SESSION_EXPIRED
  • MFA_REQUIRED
  • EMAIL_NOT_VERIFIED
  • PERMISSION_DENIED
  • INSUFFICIENT_PRIVILEGES
  • FIELD_NOT_ACCESSIBLE
  • RECORD_NOT_ACCESSIBLE
  • LICENSE_REQUIRED
  • IP_RESTRICTED
  • TIME_RESTRICTED
  • RESOURCE_NOT_FOUND
  • OBJECT_NOT_FOUND
  • RECORD_NOT_FOUND
  • FIELD_NOT_FOUND
  • ENDPOINT_NOT_FOUND
  • RESOURCE_CONFLICT
  • CONCURRENT_MODIFICATION
  • DELETE_RESTRICTED
  • DUPLICATE_RECORD
  • LOCK_CONFLICT
  • METHOD_NOT_ALLOWED
  • PRECONDITION_REQUIRED
  • RATE_LIMIT_EXCEEDED
  • QUOTA_EXCEEDED
  • CONCURRENT_LIMIT_EXCEEDED
  • INTERNAL_ERROR
  • DATABASE_ERROR
  • TIMEOUT
  • SERVICE_UNAVAILABLE
  • NOT_IMPLEMENTED
  • EXTERNAL_SERVICE_ERROR
  • INTEGRATION_ERROR
  • WEBHOOK_DELIVERY_FAILED
  • BATCH_PARTIAL_FAILURE
  • BATCH_COMPLETE_FAILURE
  • TRANSACTION_FAILED

On this page