Error Code Ledger
Error Code Ledger protocol schemas
Error-Code Ledger (ADR-0112 D3).
The top-level error.code vocabulary is two-tier:
- Standard catalog —
StandardErrorCode(errors.zod.ts): a small, closed set with platform-wide HTTP semantics. It does NOT grow when a service invents a code. - 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:
- shape —
envelopeViolations(body)(contract.zod.ts), and - vocabulary —
code ∈ StandardErrorCode ∪ <its own ledger>, whichmakeApiErrorSchema(<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_ERRORINVALID_FIELDMISSING_REQUIRED_FIELDINVALID_FORMATVALUE_TOO_LONGVALUE_TOO_SHORTVALUE_OUT_OF_RANGEINVALID_REFERENCEDUPLICATE_VALUEINVALID_QUERYINVALID_FILTERINVALID_SORTMAX_RECORDS_EXCEEDEDUNAUTHENTICATEDINVALID_CREDENTIALSEXPIRED_TOKENINVALID_TOKENSESSION_EXPIREDMFA_REQUIREDEMAIL_NOT_VERIFIEDPERMISSION_DENIEDINSUFFICIENT_PRIVILEGESFIELD_NOT_ACCESSIBLERECORD_NOT_ACCESSIBLELICENSE_REQUIREDIP_RESTRICTEDTIME_RESTRICTEDRESOURCE_NOT_FOUNDOBJECT_NOT_FOUNDRECORD_NOT_FOUNDFIELD_NOT_FOUNDENDPOINT_NOT_FOUNDRESOURCE_CONFLICTCONCURRENT_MODIFICATIONDELETE_RESTRICTEDDUPLICATE_RECORDLOCK_CONFLICTMETHOD_NOT_ALLOWEDPRECONDITION_REQUIREDRATE_LIMIT_EXCEEDEDQUOTA_EXCEEDEDCONCURRENT_LIMIT_EXCEEDEDINTERNAL_ERRORDATABASE_ERRORTIMEOUTSERVICE_UNAVAILABLENOT_IMPLEMENTEDEXTERNAL_SERVICE_ERRORINTEGRATION_ERRORWEBHOOK_DELIVERY_FAILEDBATCH_PARTIAL_FAILUREBATCH_COMPLETE_FAILURETRANSACTION_FAILEDACCOUNT_LOCKEDALREADY_REVERTEDAMBIGUOUS_MATCHANALYTICS_QUERY_FAILEDAPPROVAL_ACTIONS_FAILEDAPPROVAL_RECALL_FAILEDAPPROVAL_REQUEST_GET_FAILEDAPPROVAL_REQUEST_LIST_FAILEDASYNC_NOT_SUPPORTEDATTACHMENT_DELETE_DENIEDATTACHMENT_DOWNLOAD_DENIEDATTACHMENT_PARENT_ACCESSAUDIENCE_NOT_ALLOWEDAUTH_CONFIG_ERRORAUTH_REQUIREDAUTOMATION_UNSCOPED_RUN_DATA_ACCESSBATCH_ABORTEDBATCH_NOT_ATOMICBATCH_TOO_LARGEBATCH_UNRESOLVED_REFBLANK_MATCH_KEYCLONE_DISABLEDCLOUD_FETCH_FAILEDCLOUD_UNCONFIGUREDCOMMIT_NOT_FOUNDCONCURRENT_UPDATECONFLICTCONFLICTING_MAPPINGCONNECTOR_UPSTREAM_UNAVAILABLECREATE_FAILEDCUBE_NOT_FOUNDDATASET_INVALIDDATASOURCE_ADMIN_ERRORDELEGABLE_SCOPE_FAILEDDELIVERY_NEVER_SENTDELIVERY_NOT_ELIGIBLEDESTRUCTIVE_CHANGEDEVICE_CODE_FAILEDDOMAIN_VERIFICATION_DISABLEDDOMAIN_VERIFICATION_FAILEDDRIVER_UNAVAILABLEDUPLICATE_REQUESTELIGIBILITY_UNEVALUABLEEMAIL_SEND_FAILEDEMAIL_SERVICE_REQUIREDENQUEUE_FAILEDENVIRONMENT_BIND_FAILEDENVIRONMENT_NOT_FOUNDENV_ACCESS_DENIEDERR_BULK_RESULT_MISMATCHERR_DATASOURCE_UNAVAILABLEERR_DRIVER_CONNECTERR_FILE_CONSTRAINTERR_FILE_REFERENCE_COPYERR_READONLY_FIELD_REJECTEDERR_SUMMARY_RECOMPUTEEXECUTION_ERROREXPIRED_OR_REVOKEDEXPIRY_IN_PASTEXPIRY_TOO_LONGEXPLAIN_FAILEDEXPORT_NOT_PERMITTEDEXTERNAL_DATASOURCE_ERROREXTERNAL_IMPORT_ERROREXTERNAL_SCHEMA_MISMATCHEXTERNAL_SCHEMA_MODE_VIOLATIONEXTERNAL_WRITE_FORBIDDENFEEDS_DISABLEDFILES_DISABLEDFILE_DOWNLOAD_DENIEDFILE_FIELD_BULK_WRITE_REFUSEDFILE_NOT_FOUNDFILTER_TOKEN_UNKNOWNFILTER_TOKEN_UNRESOLVEDFORBIDDENFORM_NOT_FOUNDFORM_RESOLVE_FAILEDIMPORT_JOB_CREATE_FAILEDIMPORT_ROW_FAILEDINTERNALINVALID_EMAILINVALID_EXPIRYINVALID_METADATAINVALID_OR_EXPIREDINVALID_PHONEINVALID_REQUESTINVALID_RESUME_TOKENINVALID_SIGNALINVALID_SIGNATUREINVALID_STATEINVITE_EMAIL_FAILEDINVITE_REQUIRES_EMAILINVITE_SMS_FAILEDIP_NOT_ALLOWEDITEM_LOCKEDLAST_LOCAL_CREDENTIALLOOKUP_NOT_PUBLICLOOKUP_TARGET_MISSINGMANIFEST_CONFLICTMAPPING_FORMAT_MISMATCHMAPPING_FORMAT_UNSUPPORTEDMAPPING_NOT_FOUNDMAPPING_TARGET_MISMATCHMARKETPLACE_PROXY_FAILEDMARKETPLACE_STORAGE_FAILEDMARKETPLACE_UNAVAILABLEMETADATA_BRANCHMETADATA_CONFLICTMETADATA_NOT_FOUNDMETADATA_SCHEMA_INVALIDNAMESPACE_PREFIXNEEDS_PASSWORDNODE_FAILURENOTHING_TO_PURGENOT_ATTEMPTEDNOT_CREATABLENOT_FOUNDNOT_OVERRIDABLENOT_UNDOABLENO_DRAFTNO_EXECUTORNO_IDENTITYNO_MATCHNO_PENDING_VERIFICATIONOAUTH_REGISTER_FAILEDOBJECT_API_DISABLEDOBJECT_API_METHOD_NOT_ALLOWEDOBJECT_OVERLAY_PACKAGE_MISMATCHOBJECT_PACKAGE_DISABLEDOPENAPI_UNAVAILABLEOS_PROTOCOL_INCOMPATIBLEPACKAGE_DELETE_FAILEDPACKAGE_DELETE_PARTIALPACKAGE_MANIFEST_INVALIDPACKAGE_PUBLISH_FAILEDPASSWORD_ALREADY_SETPASSWORD_EXPIREDPASSWORD_POLICY_VIOLATIONPASSWORD_REUSEPAYLOAD_TOO_LARGEPERMISSION_NOT_ALLOWEDPHONE_NOT_ENABLEDPLUGIN_INSTALL_FAILEDPLUGIN_MANIFEST_INVALIDPLUGIN_REGISTER_FAILEDPROJECT_MEMBERSHIP_REQUIREDPROJECT_NOT_FOUNDPROJECT_PROVISIONINGPROJECT_PROVISIONING_FAILEDRAW_SQL_UNSUPPORTEDREAD_SCOPE_COMPILE_FAILEDRECORD_GONERECORD_LOCKEDRECORD_NOT_ELIGIBLEREPORTS_LIST_FAILEDREPORT_DELETE_FAILEDREPORT_GET_FAILEDREPORT_NOT_FOUNDREPORT_RUN_FAILEDREPORT_SAVE_FAILEDREPORT_SCHEDULE_FAILEDREQUEST_NOT_FOUNDRESEED_NO_ROWSRESEED_SKIPPEDRESUME_FAILEDRESUME_IN_PROGRESSRESUME_TARGET_LOSTROLLED_BACKROUTE_NOT_FOUNDRULE_DEFINE_FAILEDRULE_DELETE_FAILEDRULE_EVALUATE_FAILEDRULE_GET_FAILEDRULE_LIST_FAILEDRULE_NOT_FOUNDRUN_NOT_FOUNDSAML_REGISTER_FAILEDSCHEDULES_LIST_FAILEDSCHEDULE_DELETE_FAILEDSETTINGS_ACTION_FAILEDSETTINGS_CRYPTO_UNAVAILABLESETTINGS_FORBIDDENSETTINGS_LOCKEDSETTINGS_UNKNOWN_KEYSETTINGS_UNKNOWN_NAMESPACESETTINGS_VALIDATIONSHARES_LIST_FAILEDSHARE_GRANT_FAILEDSHARE_REVOKE_FAILEDSHARING_NOT_ENABLEDSIGN_IN_REQUIREDSSO_REGISTER_FAILEDSSO_REGISTER_FORBIDDENSTORE_UNAVAILABLESUGGESTION_CONFIRM_FAILEDSUGGESTION_DISMISS_FAILEDSUGGESTION_LIST_FAILEDSUGGESTION_NOT_FOUNDSUGGESTION_STATESUMMARY_RECOMPUTE_FAILEDTENANT_SCOPE_REQUIREDUNAUTHORIZEDUNIQUE_VIOLATIONUNKNOWN_KEYUNKNOWN_NAMESPACEUNSUPPORTEDUNSUPPORTED_QUERY_PARAMUNSUPPORTED_TRANSFORMUPLOAD_SESSION_EXPIREDUPLOAD_SESSION_NOT_FOUNDUSER_ALREADY_EXISTSVALIDATION_FAILEDVERSION_NOT_FOUNDVERSION_NOT_RESTORABLEWRITABLE_PACKAGE_REQUIREDWRONG_PASSWORD
StandardSynonymWaiver
Properties
| Property | Type | Required | Description |
|---|---|---|---|
| code | string | ✅ | The registered extension code the waiver keeps admissible |
| shadows | Enum<'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 |
| reason | string | ✅ | Why the synonym stays registered — recorded so admission is a decision, not drift |
Allowed Values: StandardSynonymWaiver.shadows
VALIDATION_ERRORINVALID_FIELDMISSING_REQUIRED_FIELDINVALID_FORMATVALUE_TOO_LONGVALUE_TOO_SHORTVALUE_OUT_OF_RANGEINVALID_REFERENCEDUPLICATE_VALUEINVALID_QUERYINVALID_FILTERINVALID_SORTMAX_RECORDS_EXCEEDEDUNAUTHENTICATEDINVALID_CREDENTIALSEXPIRED_TOKENINVALID_TOKENSESSION_EXPIREDMFA_REQUIREDEMAIL_NOT_VERIFIEDPERMISSION_DENIEDINSUFFICIENT_PRIVILEGESFIELD_NOT_ACCESSIBLERECORD_NOT_ACCESSIBLELICENSE_REQUIREDIP_RESTRICTEDTIME_RESTRICTEDRESOURCE_NOT_FOUNDOBJECT_NOT_FOUNDRECORD_NOT_FOUNDFIELD_NOT_FOUNDENDPOINT_NOT_FOUNDRESOURCE_CONFLICTCONCURRENT_MODIFICATIONDELETE_RESTRICTEDDUPLICATE_RECORDLOCK_CONFLICTMETHOD_NOT_ALLOWEDPRECONDITION_REQUIREDRATE_LIMIT_EXCEEDEDQUOTA_EXCEEDEDCONCURRENT_LIMIT_EXCEEDEDINTERNAL_ERRORDATABASE_ERRORTIMEOUTSERVICE_UNAVAILABLENOT_IMPLEMENTEDEXTERNAL_SERVICE_ERRORINTEGRATION_ERRORWEBHOOK_DELIVERY_FAILEDBATCH_PARTIAL_FAILUREBATCH_COMPLETE_FAILURETRANSACTION_FAILED