@abloatai/ablo 0.6.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +77 -0
- package/README.md +95 -57
- package/dist/BaseSyncedStore.d.ts +1 -1
- package/dist/BaseSyncedStore.js +8 -4
- package/dist/SyncEngineContext.d.ts +2 -1
- package/dist/SyncEngineContext.js +5 -3
- package/dist/agent/session.js +3 -2
- package/dist/auth/index.js +39 -11
- package/dist/client/Ablo.d.ts +112 -3
- package/dist/client/Ablo.js +144 -10
- package/dist/client/ApiClient.d.ts +32 -0
- package/dist/client/ApiClient.js +76 -44
- package/dist/client/auth.d.ts +11 -1
- package/dist/client/auth.js +21 -2
- package/dist/client/createModelProxy.d.ts +120 -53
- package/dist/client/createModelProxy.js +66 -31
- package/dist/client/identity.js +14 -0
- package/dist/client/registerDataSource.d.ts +19 -0
- package/dist/client/registerDataSource.js +57 -0
- package/dist/client/validateAbloOptions.d.ts +2 -1
- package/dist/client/validateAbloOptions.js +8 -7
- package/dist/coordination/index.d.ts +6 -0
- package/dist/coordination/index.js +6 -0
- package/dist/coordination/schema.d.ts +329 -0
- package/dist/coordination/schema.js +209 -0
- package/dist/core/QueryView.d.ts +4 -1
- package/dist/core/QueryView.js +1 -1
- package/dist/core/query-utils.d.ts +7 -10
- package/dist/core/query-utils.js +2 -3
- package/dist/errorCodes.d.ts +286 -0
- package/dist/errorCodes.js +284 -0
- package/dist/errors.d.ts +103 -7
- package/dist/errors.js +192 -41
- package/dist/index.d.ts +11 -6
- package/dist/index.js +10 -6
- package/dist/keys/index.d.ts +61 -0
- package/dist/keys/index.js +151 -0
- package/dist/policy/index.d.ts +1 -1
- package/dist/policy/index.js +1 -1
- package/dist/policy/types.d.ts +31 -0
- package/dist/policy/types.js +15 -0
- package/dist/query/client.js +19 -8
- package/dist/react/AbloProvider.d.ts +37 -0
- package/dist/react/AbloProvider.js +107 -4
- package/dist/react/ClientSideSuspense.d.ts +1 -1
- package/dist/react/DefaultFallback.d.ts +1 -1
- package/dist/react/SyncGroupProvider.d.ts +1 -1
- package/dist/react/index.d.ts +3 -2
- package/dist/react/index.js +3 -2
- package/dist/react/useAblo.d.ts +4 -4
- package/dist/react/useAblo.js +10 -5
- package/dist/react/useReactive.js +16 -3
- package/dist/schema/ddl.d.ts +62 -0
- package/dist/schema/ddl.js +317 -0
- package/dist/schema/diff.d.ts +6 -0
- package/dist/schema/diff.js +21 -3
- package/dist/schema/field.d.ts +16 -19
- package/dist/schema/field.js +30 -17
- package/dist/schema/index.d.ts +7 -4
- package/dist/schema/index.js +9 -3
- package/dist/schema/model.d.ts +87 -25
- package/dist/schema/model.js +33 -3
- package/dist/schema/relation.d.ts +17 -0
- package/dist/schema/roles.d.ts +148 -0
- package/dist/schema/roles.js +149 -0
- package/dist/schema/schema.d.ts +2 -112
- package/dist/schema/schema.js +50 -62
- package/dist/schema/select.d.ts +25 -0
- package/dist/schema/select.js +55 -0
- package/dist/schema/serialize.d.ts +16 -12
- package/dist/schema/serialize.js +16 -12
- package/dist/schema/sugar.d.ts +20 -3
- package/dist/schema/sugar.js +5 -1
- package/dist/schema/tenancy.d.ts +66 -0
- package/dist/schema/tenancy.js +58 -0
- package/dist/sync/BootstrapHelper.js +46 -27
- package/dist/sync/ConnectionManager.d.ts +3 -1
- package/dist/sync/ConnectionManager.js +37 -1
- package/dist/sync/HydrationCoordinator.d.ts +2 -0
- package/dist/sync/HydrationCoordinator.js +26 -19
- package/dist/sync/NetworkProbe.d.ts +8 -0
- package/dist/sync/NetworkProbe.js +24 -2
- package/dist/sync/SyncWebSocket.d.ts +1 -1
- package/dist/sync/SyncWebSocket.js +43 -53
- package/dist/sync/createIntentStream.d.ts +2 -1
- package/dist/sync/createIntentStream.js +46 -1
- package/dist/sync/participants.js +10 -16
- package/dist/transactions/TransactionQueue.js +13 -1
- package/dist/types/streams.d.ts +53 -33
- package/docs/api-keys.md +47 -3
- package/docs/api.md +103 -57
- package/docs/audit.md +16 -9
- package/docs/cli.md +222 -0
- package/docs/client-behavior.md +35 -21
- package/docs/coordination.md +74 -36
- package/docs/data-sources.md +23 -21
- package/docs/examples/agent-human.md +72 -28
- package/docs/examples/ai-sdk-tool.md +14 -11
- package/docs/examples/existing-python-backend.md +30 -19
- package/docs/examples/nextjs.md +21 -8
- package/docs/examples/scoped-agent.md +93 -0
- package/docs/examples/server-agent.md +27 -5
- package/docs/guarantees.md +29 -17
- package/docs/identity.md +198 -121
- package/docs/index.md +35 -18
- package/docs/integration-guide.md +79 -83
- package/docs/interaction-model.md +40 -25
- package/docs/mcp/claude-code.md +9 -17
- package/docs/mcp/cursor.md +6 -24
- package/docs/mcp/windsurf.md +6 -19
- package/docs/mcp.md +103 -26
- package/docs/quickstart.md +31 -39
- package/docs/react.md +18 -14
- package/docs/roadmap.md +15 -3
- package/docs/schema-contract.md +109 -0
- package/examples/README.md +8 -4
- package/examples/data-source/README.md +6 -2
- package/examples/data-source/run.ts +4 -3
- package/examples/quickstart.ts +1 -1
- package/llms.txt +27 -16
- package/package.json +13 -1
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The canonical Ablo error-code registry — the **`code` tier** of the
|
|
3
|
+
* Stripe-style two-tier error model.
|
|
4
|
+
*
|
|
5
|
+
* ### The two tiers (mirrors Stripe)
|
|
6
|
+
*
|
|
7
|
+
* - **`type`** — coarse category, 1:1 with an {@link AbloError} subclass
|
|
8
|
+
* (`AbloPermissionError`, `AbloValidationError`, …). "Catch by
|
|
9
|
+
* `instanceof`" ≡ "switch on `e.type`". This tier lives in `errors.ts`.
|
|
10
|
+
* - **`code`** — the fine-grained, machine-readable identifier in this
|
|
11
|
+
* file. `snake_case`, ordered noun→state (`entity_claimed`) or
|
|
12
|
+
* condition→constraint (`queue_too_deep`). This is what callers
|
|
13
|
+
* `switch` on for specific handling, and what `doc_url` is derived from.
|
|
14
|
+
*
|
|
15
|
+
* Because sync-engine ↔ sync-server ↔ MCP all speak this code vocabulary,
|
|
16
|
+
* the registry **is** the wire contract. Two consequences:
|
|
17
|
+
*
|
|
18
|
+
* 1. `ErrorCode` is a *closed* union (plus the `policy:${string}`
|
|
19
|
+
* dynamic family). Producing an unregistered code is a compile error
|
|
20
|
+
* — that's the whole point. {@link AbloError}'s constructor param is
|
|
21
|
+
* narrowed to `ErrorCode`; only the wire-parse boundary
|
|
22
|
+
* (`translateHttpError`, frame deserialization) casts an incoming
|
|
23
|
+
* string to `ErrorCode`, so an *older* SDK still tolerates a *newer*
|
|
24
|
+
* server's code (forward compat) while internal producers stay checked.
|
|
25
|
+
* 2. The `surface: 'wire'` subset is what an HTTP/MCP boundary maps from
|
|
26
|
+
* and what the public error docs are generated from. `surface:
|
|
27
|
+
* 'client'` codes are local SDK invariants (you forgot to open the DB,
|
|
28
|
+
* a model isn't registered) — never sent over the network, so they
|
|
29
|
+
* carry no `httpStatus`, exactly as Stripe omits client-side
|
|
30
|
+
* programmer errors from its published code list.
|
|
31
|
+
*/
|
|
32
|
+
/**
|
|
33
|
+
* Version of the error contract — the envelope shape + the set of codes and
|
|
34
|
+
* their semantics. Date-based, like Stripe's API versions. Bump it (and only
|
|
35
|
+
* it) when the contract changes in a way consumers can observe: a new/removed
|
|
36
|
+
* code, a changed HTTP status, an envelope field. Emitted in `errors.json`
|
|
37
|
+
* and on the `Ablo-Version` response header so a consumer can detect drift.
|
|
38
|
+
*/
|
|
39
|
+
export declare const ERROR_CONTRACT_VERSION = "2026-05-29";
|
|
40
|
+
/** Coarse grouping for metrics dashboards and docs sectioning. */
|
|
41
|
+
export type ErrorCategory = 'auth' | 'permission' | 'capability' | 'claim' | 'conflict' | 'validation' | 'not_found' | 'tenant' | 'schema' | 'intent' | 'bootstrap' | 'transport' | 'rate_limit' | 'server' | 'client';
|
|
42
|
+
/** One registry entry. `httpStatus` is present only for `surface: 'wire'`
|
|
43
|
+
* codes — status is a property of the wire boundary, never of a
|
|
44
|
+
* purely-local client invariant. */
|
|
45
|
+
export interface ErrorCodeSpec {
|
|
46
|
+
readonly category: ErrorCategory;
|
|
47
|
+
/** `'wire'` = crosses the network and is part of the API/MCP contract;
|
|
48
|
+
* `'client'` = local SDK invariant, never serialized. */
|
|
49
|
+
readonly surface: 'wire' | 'client';
|
|
50
|
+
/** Canonical HTTP status for the wire boundary. Omitted for client codes. */
|
|
51
|
+
readonly httpStatus?: number;
|
|
52
|
+
/** Whether the same request can succeed on a later retry without the
|
|
53
|
+
* caller changing anything. `false` for permission / validation /
|
|
54
|
+
* not-found; `true` for transient transport / lease contention. */
|
|
55
|
+
readonly retryable: boolean;
|
|
56
|
+
/** One-line human description — the source text for the `doc_url` page. */
|
|
57
|
+
readonly message: string;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* The closed set of stable error codes. Add a code here BEFORE throwing it
|
|
61
|
+
* — the narrowed {@link AbloError} constructor param enforces this.
|
|
62
|
+
*/
|
|
63
|
+
export declare const ERROR_CODES: {
|
|
64
|
+
readonly apikey_invalid: ErrorCodeSpec;
|
|
65
|
+
readonly apikey_revoked: ErrorCodeSpec;
|
|
66
|
+
readonly apikey_expired: ErrorCodeSpec;
|
|
67
|
+
readonly apikey_missing: ErrorCodeSpec;
|
|
68
|
+
readonly api_key_required: ErrorCodeSpec;
|
|
69
|
+
readonly capability_id_missing: ErrorCodeSpec;
|
|
70
|
+
readonly exchange_failed: ErrorCodeSpec;
|
|
71
|
+
readonly identity_resolve_failed: ErrorCodeSpec;
|
|
72
|
+
readonly auth_no_credentials: ErrorCodeSpec;
|
|
73
|
+
readonly identity_missing_organization: ErrorCodeSpec;
|
|
74
|
+
readonly session_expired: ErrorCodeSpec;
|
|
75
|
+
readonly jwt_invalid: ErrorCodeSpec;
|
|
76
|
+
readonly jwt_malformed: ErrorCodeSpec;
|
|
77
|
+
readonly jwt_missing_issuer: ErrorCodeSpec;
|
|
78
|
+
readonly jwt_issuer_untrusted: ErrorCodeSpec;
|
|
79
|
+
readonly jwt_signature_invalid: ErrorCodeSpec;
|
|
80
|
+
readonly jwt_audience_mismatch: ErrorCodeSpec;
|
|
81
|
+
readonly jwt_missing_subject: ErrorCodeSpec;
|
|
82
|
+
readonly jwt_missing_organization: ErrorCodeSpec;
|
|
83
|
+
readonly jwt_expired: ErrorCodeSpec;
|
|
84
|
+
readonly jwt_org_membership_denied: ErrorCodeSpec;
|
|
85
|
+
readonly file_upload_auth_required: ErrorCodeSpec;
|
|
86
|
+
readonly browser_apikey_blocked: ErrorCodeSpec;
|
|
87
|
+
readonly browser_database_url_blocked: ErrorCodeSpec;
|
|
88
|
+
readonly datasource_registration_failed: ErrorCodeSpec;
|
|
89
|
+
readonly capability_scope_denied: ErrorCodeSpec;
|
|
90
|
+
readonly issuer_register_forbidden: ErrorCodeSpec;
|
|
91
|
+
readonly capability_invalid: ErrorCodeSpec;
|
|
92
|
+
readonly byo_role_cannot_enforce_rls: ErrorCodeSpec;
|
|
93
|
+
readonly byo_role_unreadable: ErrorCodeSpec;
|
|
94
|
+
readonly byo_tenant_tables_unforced_rls: ErrorCodeSpec;
|
|
95
|
+
readonly claim_conflict: ErrorCodeSpec;
|
|
96
|
+
readonly claim_lost: ErrorCodeSpec;
|
|
97
|
+
readonly entity_claimed: ErrorCodeSpec;
|
|
98
|
+
readonly intent_conflict: ErrorCodeSpec;
|
|
99
|
+
readonly malformed_claim: ErrorCodeSpec;
|
|
100
|
+
readonly model_claimed: ErrorCodeSpec;
|
|
101
|
+
readonly model_claimed_timeout: ErrorCodeSpec;
|
|
102
|
+
readonly model_claim_not_configured: ErrorCodeSpec;
|
|
103
|
+
readonly stale_context: ErrorCodeSpec;
|
|
104
|
+
readonly idempotency_conflict: ErrorCodeSpec;
|
|
105
|
+
readonly idempotency_key_too_long: ErrorCodeSpec;
|
|
106
|
+
readonly turn_validation_failed: ErrorCodeSpec;
|
|
107
|
+
readonly commit_operation_required: ErrorCodeSpec;
|
|
108
|
+
readonly commit_operation_model_required: ErrorCodeSpec;
|
|
109
|
+
readonly commit_operations_ambiguous: ErrorCodeSpec;
|
|
110
|
+
readonly commit_too_many_operations: ErrorCodeSpec;
|
|
111
|
+
readonly model_required_field_missing: ErrorCodeSpec;
|
|
112
|
+
readonly model_identifier_missing: ErrorCodeSpec;
|
|
113
|
+
readonly snapshot_reserved_key: ErrorCodeSpec;
|
|
114
|
+
readonly mesh_message_invalid_input: ErrorCodeSpec;
|
|
115
|
+
readonly mesh_message_from_id_spoof: ErrorCodeSpec;
|
|
116
|
+
readonly mesh_message_from_kind_mismatch: ErrorCodeSpec;
|
|
117
|
+
readonly agent_perception_missing_context: ErrorCodeSpec;
|
|
118
|
+
readonly entity_not_found: ErrorCodeSpec;
|
|
119
|
+
readonly model_not_found: ErrorCodeSpec;
|
|
120
|
+
readonly mutate_update_entity_not_found: ErrorCodeSpec;
|
|
121
|
+
readonly task_id_missing: ErrorCodeSpec;
|
|
122
|
+
readonly not_null_violation: ErrorCodeSpec;
|
|
123
|
+
readonly foreign_key_violation: ErrorCodeSpec;
|
|
124
|
+
readonly unique_violation: ErrorCodeSpec;
|
|
125
|
+
readonly check_violation: ErrorCodeSpec;
|
|
126
|
+
readonly constraint_violation: ErrorCodeSpec;
|
|
127
|
+
readonly server_execute_unknown_model: ErrorCodeSpec;
|
|
128
|
+
readonly mutate_create_unknown_model: ErrorCodeSpec;
|
|
129
|
+
readonly tenant_model_columns_unknown: ErrorCodeSpec;
|
|
130
|
+
readonly tenant_model_missing_organization_id: ErrorCodeSpec;
|
|
131
|
+
readonly schema_mutable_missing_meta: ErrorCodeSpec;
|
|
132
|
+
readonly schema_scope_kind_invalid: ErrorCodeSpec;
|
|
133
|
+
readonly schema_field_not_camelcase: ErrorCodeSpec;
|
|
134
|
+
readonly schema_field_consecutive_caps: ErrorCodeSpec;
|
|
135
|
+
readonly schema_grants_shape_invalid: ErrorCodeSpec;
|
|
136
|
+
readonly schema_grants_identifier_unsafe: ErrorCodeSpec;
|
|
137
|
+
readonly schema_grants_relation_kind: ErrorCodeSpec;
|
|
138
|
+
readonly schema_grants_relation_missing: ErrorCodeSpec;
|
|
139
|
+
readonly schema_grants_target_not_scope_root: ErrorCodeSpec;
|
|
140
|
+
readonly drop_field: ErrorCodeSpec;
|
|
141
|
+
readonly drop_model: ErrorCodeSpec;
|
|
142
|
+
readonly lossy_recreate: ErrorCodeSpec;
|
|
143
|
+
readonly made_required: ErrorCodeSpec;
|
|
144
|
+
readonly required_field_added: ErrorCodeSpec;
|
|
145
|
+
readonly enum_value_removed: ErrorCodeSpec;
|
|
146
|
+
readonly risky_cast: ErrorCodeSpec;
|
|
147
|
+
readonly intent_lease_unavailable: ErrorCodeSpec;
|
|
148
|
+
readonly intent_not_wired: ErrorCodeSpec;
|
|
149
|
+
readonly intent_queued: ErrorCodeSpec;
|
|
150
|
+
readonly intent_wait_aborted: ErrorCodeSpec;
|
|
151
|
+
readonly intent_wait_poll_interval_required: ErrorCodeSpec;
|
|
152
|
+
readonly grant_timeout: ErrorCodeSpec;
|
|
153
|
+
readonly slide_intent_missing_deck_id: ErrorCodeSpec;
|
|
154
|
+
readonly slide_intent_unknown_sibling: ErrorCodeSpec;
|
|
155
|
+
readonly bootstrap_fetch_timeout: ErrorCodeSpec;
|
|
156
|
+
readonly bootstrap_offline: ErrorCodeSpec;
|
|
157
|
+
readonly bootstrap_offline_no_cache: ErrorCodeSpec;
|
|
158
|
+
readonly bootstrap_response_invalid: ErrorCodeSpec;
|
|
159
|
+
readonly bootstrap_response_schema_invalid: ErrorCodeSpec;
|
|
160
|
+
readonly exchange_malformed_response: ErrorCodeSpec;
|
|
161
|
+
readonly exchange_network_error: ErrorCodeSpec;
|
|
162
|
+
readonly source_network_error: ErrorCodeSpec;
|
|
163
|
+
readonly identity_network_error: ErrorCodeSpec;
|
|
164
|
+
readonly commit_no_result: ErrorCodeSpec;
|
|
165
|
+
readonly commit_failed: ErrorCodeSpec;
|
|
166
|
+
readonly commit_offline_grace_expired: ErrorCodeSpec;
|
|
167
|
+
readonly queue_too_deep: ErrorCodeSpec;
|
|
168
|
+
readonly flush_timeout: ErrorCodeSpec;
|
|
169
|
+
readonly wait_for_timeout: ErrorCodeSpec;
|
|
170
|
+
readonly fetch_unavailable: ErrorCodeSpec;
|
|
171
|
+
readonly base_url_missing: ErrorCodeSpec;
|
|
172
|
+
readonly sync_not_ready: ErrorCodeSpec;
|
|
173
|
+
readonly ws_not_ready: ErrorCodeSpec;
|
|
174
|
+
readonly quota_exceeded: ErrorCodeSpec;
|
|
175
|
+
readonly internal_error: ErrorCodeSpec;
|
|
176
|
+
readonly quota_lookup_failed: ErrorCodeSpec;
|
|
177
|
+
readonly turn_open_failed: ErrorCodeSpec;
|
|
178
|
+
readonly turn_close_failed: ErrorCodeSpec;
|
|
179
|
+
readonly invalid_options: ErrorCodeSpec;
|
|
180
|
+
readonly no_ablo_provider: ErrorCodeSpec;
|
|
181
|
+
readonly no_sync_group_provider: ErrorCodeSpec;
|
|
182
|
+
readonly sync_context_missing_provider: ErrorCodeSpec;
|
|
183
|
+
readonly db_not_opened: ErrorCodeSpec;
|
|
184
|
+
readonly db_store_not_found: ErrorCodeSpec;
|
|
185
|
+
readonly db_unknown_action_type: ErrorCodeSpec;
|
|
186
|
+
readonly idb_unavailable: ErrorCodeSpec;
|
|
187
|
+
readonly meta_db_not_initialized: ErrorCodeSpec;
|
|
188
|
+
readonly sync_client_db_missing: ErrorCodeSpec;
|
|
189
|
+
readonly lazy_ref_db_missing: ErrorCodeSpec;
|
|
190
|
+
readonly lazy_ref_pool_missing: ErrorCodeSpec;
|
|
191
|
+
readonly model_class_not_registered: ErrorCodeSpec;
|
|
192
|
+
readonly model_not_registered: ErrorCodeSpec;
|
|
193
|
+
readonly model_disposed: ErrorCodeSpec;
|
|
194
|
+
readonly pool_model_class_not_registered: ErrorCodeSpec;
|
|
195
|
+
readonly pool_registry_missing: ErrorCodeSpec;
|
|
196
|
+
readonly pool_subscribe_unregistered: ErrorCodeSpec;
|
|
197
|
+
readonly registry_invalid_constructor: ErrorCodeSpec;
|
|
198
|
+
readonly registry_not_initialized: ErrorCodeSpec;
|
|
199
|
+
readonly registry_property_conflict: ErrorCodeSpec;
|
|
200
|
+
readonly registry_reference_unknown_target: ErrorCodeSpec;
|
|
201
|
+
readonly registry_reference_unresolved: ErrorCodeSpec;
|
|
202
|
+
readonly registry_unknown_model: ErrorCodeSpec;
|
|
203
|
+
readonly query_returns_unknown_model: ErrorCodeSpec;
|
|
204
|
+
readonly store_create_schema_missing: ErrorCodeSpec;
|
|
205
|
+
readonly store_manager_unknown_model: ErrorCodeSpec;
|
|
206
|
+
readonly store_query_schema_missing: ErrorCodeSpec;
|
|
207
|
+
readonly store_query_unknown_model: ErrorCodeSpec;
|
|
208
|
+
readonly transaction_mutate_unknown_model: ErrorCodeSpec;
|
|
209
|
+
readonly transaction_read_unknown_model: ErrorCodeSpec;
|
|
210
|
+
readonly mutator_registry_duplicate: ErrorCodeSpec;
|
|
211
|
+
readonly mutator_registry_unnamed_def: ErrorCodeSpec;
|
|
212
|
+
readonly mutators_schema_missing: ErrorCodeSpec;
|
|
213
|
+
readonly undo_scope_schema_missing: ErrorCodeSpec;
|
|
214
|
+
readonly mock_mutation_failed: ErrorCodeSpec;
|
|
215
|
+
readonly mock_unsupported_operation: ErrorCodeSpec;
|
|
216
|
+
readonly invalid_body: ErrorCodeSpec;
|
|
217
|
+
readonly invalid_json: ErrorCodeSpec;
|
|
218
|
+
readonly capability_id_required: ErrorCodeSpec;
|
|
219
|
+
readonly organization_mismatch: ErrorCodeSpec;
|
|
220
|
+
readonly forbidden: ErrorCodeSpec;
|
|
221
|
+
readonly source_api_key_unresolved: ErrorCodeSpec;
|
|
222
|
+
readonly capability_auth_disabled: ErrorCodeSpec;
|
|
223
|
+
readonly provisioner_unavailable: ErrorCodeSpec;
|
|
224
|
+
readonly invalid_model: ErrorCodeSpec;
|
|
225
|
+
readonly invalid_id: ErrorCodeSpec;
|
|
226
|
+
readonly unknown_model: ErrorCodeSpec;
|
|
227
|
+
readonly model_not_tenant_scoped: ErrorCodeSpec;
|
|
228
|
+
readonly schema_table_invalid: ErrorCodeSpec;
|
|
229
|
+
readonly schema_scope_invalid: ErrorCodeSpec;
|
|
230
|
+
readonly entity_fetch_failed: ErrorCodeSpec;
|
|
231
|
+
readonly events_required: ErrorCodeSpec;
|
|
232
|
+
readonly ingest_failed: ErrorCodeSpec;
|
|
233
|
+
readonly migration_failed: ErrorCodeSpec;
|
|
234
|
+
readonly model_query_failed: ErrorCodeSpec;
|
|
235
|
+
readonly queries_required: ErrorCodeSpec;
|
|
236
|
+
readonly query_unsupported_operator: ErrorCodeSpec;
|
|
237
|
+
readonly query_unknown_relation: ErrorCodeSpec;
|
|
238
|
+
readonly query_relation_target_unknown: ErrorCodeSpec;
|
|
239
|
+
readonly query_invalid_identifier: ErrorCodeSpec;
|
|
240
|
+
readonly org_id_required: ErrorCodeSpec;
|
|
241
|
+
readonly presence_identity_required: ErrorCodeSpec;
|
|
242
|
+
readonly upload_fields_required: ErrorCodeSpec;
|
|
243
|
+
readonly upload_items_required: ErrorCodeSpec;
|
|
244
|
+
readonly presigned_url_failed: ErrorCodeSpec;
|
|
245
|
+
readonly task_id_required: ErrorCodeSpec;
|
|
246
|
+
readonly intent_id_required: ErrorCodeSpec;
|
|
247
|
+
readonly commit_operation_action_required: ErrorCodeSpec;
|
|
248
|
+
readonly commit_operation_unsupported: ErrorCodeSpec;
|
|
249
|
+
readonly usage_invalid: ErrorCodeSpec;
|
|
250
|
+
readonly invalid_request: ErrorCodeSpec;
|
|
251
|
+
readonly capability_not_found: ErrorCodeSpec;
|
|
252
|
+
readonly invalid_participant_kind: ErrorCodeSpec;
|
|
253
|
+
readonly narrow_scope_required: ErrorCodeSpec;
|
|
254
|
+
readonly wide_scope_forbidden: ErrorCodeSpec;
|
|
255
|
+
readonly capability_required: ErrorCodeSpec;
|
|
256
|
+
readonly parent_turn_not_found: ErrorCodeSpec;
|
|
257
|
+
readonly parent_turn_foreign_agent: ErrorCodeSpec;
|
|
258
|
+
readonly turn_not_found: ErrorCodeSpec;
|
|
259
|
+
readonly turn_foreign_agent: ErrorCodeSpec;
|
|
260
|
+
readonly invalid_intent: ErrorCodeSpec;
|
|
261
|
+
readonly schema_too_large: ErrorCodeSpec;
|
|
262
|
+
readonly invalid_schema: ErrorCodeSpec;
|
|
263
|
+
readonly incompatible_change: ErrorCodeSpec;
|
|
264
|
+
};
|
|
265
|
+
/**
|
|
266
|
+
* The closed set of registered codes, plus the `policy:${reason}` dynamic
|
|
267
|
+
* family (conflict-policy rejections name their reason inline, the same way
|
|
268
|
+
* Stripe carries a `decline_code` sub-detail). The constructor of
|
|
269
|
+
* {@link AbloError} narrows its `code` option to this type, so a typo or an
|
|
270
|
+
* unregistered code is a compile error. The wire-parse boundary casts
|
|
271
|
+
* incoming strings to this type to preserve forward compatibility.
|
|
272
|
+
*/
|
|
273
|
+
export type ErrorCode = keyof typeof ERROR_CODES | `policy:${string}`;
|
|
274
|
+
/** The subset of codes that cross the network — the actual API/MCP wire
|
|
275
|
+
* contract. HTTP/MCP boundaries map from this set; docs are generated
|
|
276
|
+
* from it. */
|
|
277
|
+
export type WireErrorCode = {
|
|
278
|
+
[K in keyof typeof ERROR_CODES]: (typeof ERROR_CODES)[K]['surface'] extends 'wire' ? K : never;
|
|
279
|
+
}[keyof typeof ERROR_CODES];
|
|
280
|
+
/** Look up an error code's spec. Returns `undefined` for the dynamic
|
|
281
|
+
* `policy:*` family and for any forward-compat code an older SDK doesn't
|
|
282
|
+
* yet know. */
|
|
283
|
+
export declare function errorCodeSpec(code: string): ErrorCodeSpec | undefined;
|
|
284
|
+
/** Whether a code's spec marks it retryable. Unknown / dynamic codes
|
|
285
|
+
* default to non-retryable (safe default — don't auto-retry the unknown). */
|
|
286
|
+
export declare function isRetryableCode(code: string): boolean;
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The canonical Ablo error-code registry — the **`code` tier** of the
|
|
3
|
+
* Stripe-style two-tier error model.
|
|
4
|
+
*
|
|
5
|
+
* ### The two tiers (mirrors Stripe)
|
|
6
|
+
*
|
|
7
|
+
* - **`type`** — coarse category, 1:1 with an {@link AbloError} subclass
|
|
8
|
+
* (`AbloPermissionError`, `AbloValidationError`, …). "Catch by
|
|
9
|
+
* `instanceof`" ≡ "switch on `e.type`". This tier lives in `errors.ts`.
|
|
10
|
+
* - **`code`** — the fine-grained, machine-readable identifier in this
|
|
11
|
+
* file. `snake_case`, ordered noun→state (`entity_claimed`) or
|
|
12
|
+
* condition→constraint (`queue_too_deep`). This is what callers
|
|
13
|
+
* `switch` on for specific handling, and what `doc_url` is derived from.
|
|
14
|
+
*
|
|
15
|
+
* Because sync-engine ↔ sync-server ↔ MCP all speak this code vocabulary,
|
|
16
|
+
* the registry **is** the wire contract. Two consequences:
|
|
17
|
+
*
|
|
18
|
+
* 1. `ErrorCode` is a *closed* union (plus the `policy:${string}`
|
|
19
|
+
* dynamic family). Producing an unregistered code is a compile error
|
|
20
|
+
* — that's the whole point. {@link AbloError}'s constructor param is
|
|
21
|
+
* narrowed to `ErrorCode`; only the wire-parse boundary
|
|
22
|
+
* (`translateHttpError`, frame deserialization) casts an incoming
|
|
23
|
+
* string to `ErrorCode`, so an *older* SDK still tolerates a *newer*
|
|
24
|
+
* server's code (forward compat) while internal producers stay checked.
|
|
25
|
+
* 2. The `surface: 'wire'` subset is what an HTTP/MCP boundary maps from
|
|
26
|
+
* and what the public error docs are generated from. `surface:
|
|
27
|
+
* 'client'` codes are local SDK invariants (you forgot to open the DB,
|
|
28
|
+
* a model isn't registered) — never sent over the network, so they
|
|
29
|
+
* carry no `httpStatus`, exactly as Stripe omits client-side
|
|
30
|
+
* programmer errors from its published code list.
|
|
31
|
+
*/
|
|
32
|
+
/**
|
|
33
|
+
* Version of the error contract — the envelope shape + the set of codes and
|
|
34
|
+
* their semantics. Date-based, like Stripe's API versions. Bump it (and only
|
|
35
|
+
* it) when the contract changes in a way consumers can observe: a new/removed
|
|
36
|
+
* code, a changed HTTP status, an envelope field. Emitted in `errors.json`
|
|
37
|
+
* and on the `Ablo-Version` response header so a consumer can detect drift.
|
|
38
|
+
*/
|
|
39
|
+
export const ERROR_CONTRACT_VERSION = '2026-05-29';
|
|
40
|
+
const wire = (category, httpStatus, retryable, message) => ({ category, surface: 'wire', httpStatus, retryable, message });
|
|
41
|
+
const client = (category, message) => ({ category, surface: 'client', retryable: false, message });
|
|
42
|
+
/**
|
|
43
|
+
* The closed set of stable error codes. Add a code here BEFORE throwing it
|
|
44
|
+
* — the narrowed {@link AbloError} constructor param enforces this.
|
|
45
|
+
*/
|
|
46
|
+
export const ERROR_CODES = {
|
|
47
|
+
// ── auth (401) ─────────────────────────────────────────────────────
|
|
48
|
+
apikey_invalid: wire('auth', 401, false, 'API key is unknown or malformed.'),
|
|
49
|
+
apikey_revoked: wire('auth', 401, false, 'API key has been revoked.'),
|
|
50
|
+
apikey_expired: wire('auth', 401, false, 'API key has expired.'),
|
|
51
|
+
apikey_missing: wire('auth', 401, false, 'No API key was supplied on the request.'),
|
|
52
|
+
api_key_required: wire('auth', 401, false, 'This operation requires an API key.'),
|
|
53
|
+
capability_id_missing: wire('auth', 401, false, 'A capability id was expected but not provided.'),
|
|
54
|
+
exchange_failed: wire('auth', 401, false, 'The API-key credential exchange was rejected.'),
|
|
55
|
+
identity_resolve_failed: wire('auth', 401, false, 'Identity resolution was rejected.'),
|
|
56
|
+
auth_no_credentials: wire('auth', 401, false, 'No recognized authentication credential was presented — no API key and no bearer JWT. Send `Authorization: Bearer <token>`.'),
|
|
57
|
+
identity_missing_organization: wire('auth', 401, false, 'Authentication succeeded but resolved to no organization context.'),
|
|
58
|
+
session_expired: wire('auth', 401, false, 'The session is invalid or expired; re-authenticate.'),
|
|
59
|
+
// `jwt_invalid` is the residual fallback; the codes below split out the
|
|
60
|
+
// specific failure modes so an integrating customer can tell "I registered
|
|
61
|
+
// the wrong JWKS" from "my token has no org claim" from "wrong audience"
|
|
62
|
+
// rather than getting one opaque code for all of them.
|
|
63
|
+
jwt_invalid: wire('auth', 401, false, 'The bearer JWT could not be validated (unclassified).'),
|
|
64
|
+
jwt_malformed: wire('auth', 401, false, 'The bearer JWT is not a well-formed JWT and could not be decoded.'),
|
|
65
|
+
jwt_missing_issuer: wire('auth', 401, false, 'The bearer JWT has no `iss` (issuer) claim, so it cannot be routed to a trusted issuer.'),
|
|
66
|
+
jwt_issuer_untrusted: wire('auth', 401, false, "The bearer JWT's `iss` is not a registered trusted issuer. Register it via POST /v1/trusted-issuers, or check the token's issuer claim."),
|
|
67
|
+
jwt_signature_invalid: wire('auth', 401, false, "The bearer JWT's signature could not be verified against the issuer's JWKS (wrong key, rotated key, or forged token)."),
|
|
68
|
+
jwt_audience_mismatch: wire('auth', 401, false, "The bearer JWT's `aud` (audience) claim does not match the audience this issuer is registered with."),
|
|
69
|
+
jwt_missing_subject: wire('auth', 401, false, 'The bearer JWT has no `sub` (subject) claim to identify the user.'),
|
|
70
|
+
jwt_missing_organization: wire('auth', 401, false, 'The bearer JWT carries no organization context — neither a fixed org for the issuer nor the configured organization claim.'),
|
|
71
|
+
jwt_expired: wire('auth', 401, false, 'The bearer JWT has expired; obtain a fresh token.'),
|
|
72
|
+
jwt_org_membership_denied: wire('auth', 403, false, "The bearer JWT's subject is not an active member of the organization in its `org_id` claim (removed, suspended, or the claim does not match a membership)."),
|
|
73
|
+
file_upload_auth_required: wire('auth', 401, false, 'File upload requires an authenticated session.'),
|
|
74
|
+
browser_apikey_blocked: client('auth', 'Raw API keys must not be used from a browser context.'),
|
|
75
|
+
browser_database_url_blocked: client('auth', 'A database connection string must not be used from a browser context — it carries DB credentials.'),
|
|
76
|
+
datasource_registration_failed: client('auth', 'Failed to register the provided databaseUrl as this project data source.'),
|
|
77
|
+
// ── permission / capability (403) ──────────────────────────────────
|
|
78
|
+
capability_scope_denied: wire('capability', 403, false, "The connection's resolved scope does not cover the attempted action."),
|
|
79
|
+
issuer_register_forbidden: wire('permission', 403, false, 'Registering a trusted issuer requires a secret (sk_) API key.'),
|
|
80
|
+
capability_invalid: wire('capability', 403, false, 'The capability is unknown, revoked, or expired.'),
|
|
81
|
+
byo_role_cannot_enforce_rls: wire('permission', 403, false, 'The bring-your-own DB role cannot enforce row-level security.'),
|
|
82
|
+
byo_role_unreadable: wire('permission', 403, false, 'The bring-your-own DB role could not be introspected.'),
|
|
83
|
+
byo_tenant_tables_unforced_rls: wire('permission', 403, false, 'Tenant tables do not have RLS forced under the BYO role.'),
|
|
84
|
+
// ── claim / intent conflict (409) ──────────────────────────────────
|
|
85
|
+
claim_conflict: wire('claim', 409, true, 'The target entity is claimed by another participant.'),
|
|
86
|
+
claim_lost: wire('claim', 409, true, 'A previously held claim was lost before the write applied.'),
|
|
87
|
+
entity_claimed: wire('claim', 409, true, 'The target entity is currently claimed; write was blocked.'),
|
|
88
|
+
intent_conflict: wire('claim', 409, true, 'An intent on the target conflicts with an active intent (server-internal alias of claim_conflict).'),
|
|
89
|
+
malformed_claim: wire('claim', 400, false, 'The claim payload was malformed.'),
|
|
90
|
+
model_claimed: wire('claim', 409, true, 'The model instance is claimed by another participant.'),
|
|
91
|
+
model_claimed_timeout: wire('claim', 409, true, 'Timed out waiting for a model claim to clear.'),
|
|
92
|
+
model_claim_not_configured: client('claim', 'Claiming was requested on a model that has no claim configuration.'),
|
|
93
|
+
// ── stale context / idempotency (409) ──────────────────────────────
|
|
94
|
+
stale_context: wire('conflict', 409, true, 'The write carried a readAt watermark that is now stale; re-read and retry.'),
|
|
95
|
+
idempotency_conflict: wire('conflict', 409, false, 'The same Idempotency-Key was reused with a different request body.'),
|
|
96
|
+
idempotency_key_too_long: wire('validation', 400, false, 'The supplied Idempotency-Key exceeds the maximum length.'),
|
|
97
|
+
// ── validation (400 / 422) ─────────────────────────────────────────
|
|
98
|
+
turn_validation_failed: wire('validation', 422, false, 'The agent turn failed server-side validation.'),
|
|
99
|
+
commit_operation_required: wire('validation', 400, false, 'A commit must carry `operation` or `operations`.'),
|
|
100
|
+
commit_operation_model_required: wire('validation', 400, false, 'A commit operation is missing its `model`.'),
|
|
101
|
+
commit_operations_ambiguous: wire('validation', 400, false, 'A commit supplied both `operation` and `operations`.'),
|
|
102
|
+
commit_too_many_operations: wire('validation', 400, false, 'A commit exceeded the per-commit operation limit; split it into smaller batches.'),
|
|
103
|
+
model_required_field_missing: wire('validation', 400, false, 'A required field was absent from the model payload.'),
|
|
104
|
+
model_identifier_missing: wire('validation', 400, false, 'The model payload is missing its identifier.'),
|
|
105
|
+
snapshot_reserved_key: wire('validation', 400, false, 'A snapshot used a reserved key name.'),
|
|
106
|
+
mesh_message_invalid_input: wire('validation', 400, false, 'The mesh message failed input validation.'),
|
|
107
|
+
mesh_message_from_id_spoof: wire('validation', 403, false, 'The mesh message `from` id does not match the authenticated sender.'),
|
|
108
|
+
mesh_message_from_kind_mismatch: wire('validation', 403, false, 'The mesh message `from` kind does not match the sender.'),
|
|
109
|
+
agent_perception_missing_context: wire('validation', 422, false, 'The agent perception request lacked required context.'),
|
|
110
|
+
// ── not found (404) ────────────────────────────────────────────────
|
|
111
|
+
entity_not_found: wire('not_found', 404, false, 'The referenced entity does not exist.'),
|
|
112
|
+
model_not_found: wire('not_found', 404, false, 'The referenced model row does not exist.'),
|
|
113
|
+
mutate_update_entity_not_found: wire('not_found', 404, false, 'The entity targeted by an update does not exist.'),
|
|
114
|
+
task_id_missing: wire('server', 502, true, 'The task-create response did not include an id.'),
|
|
115
|
+
// ── data integrity / DB constraints ────────────────────────────────
|
|
116
|
+
// Emitted when a write is rejected by a database integrity constraint
|
|
117
|
+
// (Postgres class-23). All NON-retryable: the same payload re-sent
|
|
118
|
+
// unchanged will fail identically, so the client must roll back, not
|
|
119
|
+
// retry. The server normalizer maps SQLSTATE → these codes and tucks the
|
|
120
|
+
// raw constraint/column/table detail into `details` rather than leaking
|
|
121
|
+
// the driver's message text onto the wire.
|
|
122
|
+
not_null_violation: wire('validation', 400, false, 'A required field was missing (database not-null constraint).'),
|
|
123
|
+
foreign_key_violation: wire('conflict', 409, false, 'A referenced entity does not exist, or is still referenced (database foreign-key constraint).'),
|
|
124
|
+
unique_violation: wire('conflict', 409, false, 'A value violates a uniqueness constraint.'),
|
|
125
|
+
check_violation: wire('validation', 400, false, 'A value violates a database check constraint.'),
|
|
126
|
+
constraint_violation: wire('validation', 400, false, 'A database integrity constraint was violated.'),
|
|
127
|
+
// ── tenant / unknown model (400) ───────────────────────────────────
|
|
128
|
+
server_execute_unknown_model: wire('tenant', 400, false, 'The server-execute request named a model not in the tenant schema.'),
|
|
129
|
+
mutate_create_unknown_model: wire('tenant', 400, false, 'A create targeted a model not in the tenant schema.'),
|
|
130
|
+
tenant_model_columns_unknown: wire('tenant', 400, false, "The tenant model's columns could not be resolved."),
|
|
131
|
+
tenant_model_missing_organization_id: wire('tenant', 400, false, 'The tenant model is missing the organization_id column required for isolation.'),
|
|
132
|
+
// ── schema migration / declaration (validation) ────────────────────
|
|
133
|
+
schema_mutable_missing_meta: wire('schema', 400, false, 'A mutable schema is missing its required meta block.'),
|
|
134
|
+
schema_scope_kind_invalid: wire('schema', 400, false, 'A scope kind in the schema is invalid.'),
|
|
135
|
+
schema_field_not_camelcase: wire('schema', 400, false, 'A schema field name is not camelCase.'),
|
|
136
|
+
schema_field_consecutive_caps: wire('schema', 400, false, 'A schema field name has consecutive capital letters.'),
|
|
137
|
+
schema_grants_shape_invalid: wire('schema', 400, false, 'A grants declaration has an invalid shape.'),
|
|
138
|
+
schema_grants_identifier_unsafe: wire('schema', 400, false, 'A grants declaration referenced an unsafe identifier.'),
|
|
139
|
+
schema_grants_relation_kind: wire('schema', 400, false, 'A grants relation referenced an invalid kind.'),
|
|
140
|
+
schema_grants_relation_missing: wire('schema', 400, false, 'A grants declaration referenced a missing relation.'),
|
|
141
|
+
schema_grants_target_not_scope_root: wire('schema', 400, false, 'A grants target is not a scope root.'),
|
|
142
|
+
drop_field: client('schema', 'Migration would drop a field (destructive classification).'),
|
|
143
|
+
drop_model: client('schema', 'Migration would drop a model (destructive classification).'),
|
|
144
|
+
lossy_recreate: client('schema', 'Migration would require a lossy table recreate.'),
|
|
145
|
+
made_required: client('schema', 'Migration would make an existing field required.'),
|
|
146
|
+
required_field_added: client('schema', 'Migration adds a new required field.'),
|
|
147
|
+
enum_value_removed: client('schema', 'Migration removes an enum value (destructive classification).'),
|
|
148
|
+
risky_cast: client('schema', 'Migration would perform a risky column type cast.'),
|
|
149
|
+
// ── intent / lease (409 / transport) ───────────────────────────────
|
|
150
|
+
intent_lease_unavailable: wire('intent', 503, true, 'The intent-lease coordination subsystem is unavailable; retry.'),
|
|
151
|
+
intent_not_wired: client('intent', 'Intent support was used but is not wired in this runtime.'),
|
|
152
|
+
intent_queued: wire('intent', 409, true, 'The intent was queued behind an active lease holder.'),
|
|
153
|
+
intent_wait_aborted: wire('intent', 409, true, 'Waiting for the intent lease was aborted.'),
|
|
154
|
+
intent_wait_poll_interval_required: client('intent', 'A poll interval is required when waiting on an intent.'),
|
|
155
|
+
grant_timeout: wire('intent', 504, true, 'Timed out waiting for a capability grant.'),
|
|
156
|
+
slide_intent_missing_deck_id: wire('intent', 400, false, 'A slide intent was missing its deck id.'),
|
|
157
|
+
slide_intent_unknown_sibling: wire('intent', 400, false, 'A slide intent referenced an unknown sibling slide.'),
|
|
158
|
+
// ── bootstrap (transport) ──────────────────────────────────────────
|
|
159
|
+
bootstrap_fetch_timeout: wire('bootstrap', 504, true, 'The bootstrap fetch timed out.'),
|
|
160
|
+
bootstrap_offline: wire('bootstrap', 503, true, 'Bootstrap could not run because the client is offline.'),
|
|
161
|
+
bootstrap_offline_no_cache: wire('bootstrap', 503, false, 'Bootstrap is offline and no cached snapshot is available.'),
|
|
162
|
+
bootstrap_response_invalid: wire('bootstrap', 502, true, 'The bootstrap response was malformed.'),
|
|
163
|
+
bootstrap_response_schema_invalid: wire('bootstrap', 502, true, 'The bootstrap response failed schema validation.'),
|
|
164
|
+
// ── transport / connection ─────────────────────────────────────────
|
|
165
|
+
exchange_malformed_response: wire('transport', 502, true, 'The credential exchange returned a malformed response.'),
|
|
166
|
+
exchange_network_error: wire('transport', 503, true, 'A network error occurred during credential exchange.'),
|
|
167
|
+
source_network_error: wire('transport', 503, true, 'A network error occurred talking to the source.'),
|
|
168
|
+
identity_network_error: wire('transport', 503, true, 'A network error occurred resolving identity.'),
|
|
169
|
+
commit_no_result: wire('transport', 504, true, 'The commit was sent but no result frame arrived.'),
|
|
170
|
+
commit_failed: wire('transport', 500, true, 'The commit failed to apply.'),
|
|
171
|
+
commit_offline_grace_expired: wire('transport', 503, false, "The offline grace window expired before the commit could be sent."),
|
|
172
|
+
queue_too_deep: wire('transport', 503, true, 'The transaction queue exceeded its depth limit.'),
|
|
173
|
+
flush_timeout: wire('transport', 504, true, 'Timed out flushing the transaction queue.'),
|
|
174
|
+
wait_for_timeout: wire('transport', 504, true, 'A wait-for condition timed out.'),
|
|
175
|
+
fetch_unavailable: client('transport', 'No fetch implementation is available in this environment.'),
|
|
176
|
+
base_url_missing: client('transport', 'No base URL was configured for the client.'),
|
|
177
|
+
sync_not_ready: client('transport', 'A sync operation was attempted before the client was ready.'),
|
|
178
|
+
ws_not_ready: client('transport', 'A frame was sent before the WebSocket was connected.'),
|
|
179
|
+
// ── quota / rate limit (429) ──────────────────────────────────────
|
|
180
|
+
quota_exceeded: wire('rate_limit', 429, true, 'The organization exceeded its configured usage quota.'),
|
|
181
|
+
// ── server (5xx) ───────────────────────────────────────────────────
|
|
182
|
+
internal_error: wire('server', 500, true, 'An unexpected server error occurred.'),
|
|
183
|
+
quota_lookup_failed: wire('server', 503, true, 'The quota decision could not be loaded.'),
|
|
184
|
+
turn_open_failed: wire('server', 500, true, 'The agent turn failed to open.'),
|
|
185
|
+
turn_close_failed: wire('server', 500, true, 'The agent turn failed to close cleanly.'),
|
|
186
|
+
// ── client-only invariants (never serialized) ──────────────────────
|
|
187
|
+
invalid_options: client('client', 'The Ablo client was constructed with invalid or incomplete options.'),
|
|
188
|
+
no_ablo_provider: client('client', 'An Ablo hook was used outside of an Ablo provider.'),
|
|
189
|
+
no_sync_group_provider: client('client', 'A sync-group hook was used outside of its provider.'),
|
|
190
|
+
sync_context_missing_provider: client('client', 'Sync context was read outside of its provider.'),
|
|
191
|
+
db_not_opened: client('client', 'The local database was accessed before it was opened.'),
|
|
192
|
+
db_store_not_found: client('client', 'The requested IndexedDB object store does not exist.'),
|
|
193
|
+
db_unknown_action_type: client('client', 'An unknown database action type was dispatched.'),
|
|
194
|
+
idb_unavailable: client('client', 'IndexedDB is unavailable in this environment.'),
|
|
195
|
+
meta_db_not_initialized: client('client', 'The meta database was accessed before initialization.'),
|
|
196
|
+
sync_client_db_missing: client('client', 'The sync client has no database handle.'),
|
|
197
|
+
lazy_ref_db_missing: client('client', 'A lazy reference was resolved without a database handle.'),
|
|
198
|
+
lazy_ref_pool_missing: client('client', 'A lazy reference was resolved without a model pool.'),
|
|
199
|
+
model_class_not_registered: client('client', 'The model class is not registered with the store.'),
|
|
200
|
+
model_not_registered: client('client', 'The model is not registered with the store.'),
|
|
201
|
+
model_disposed: client('client', 'The model instance has been disposed.'),
|
|
202
|
+
pool_model_class_not_registered: client('client', 'The model class is not registered with the pool.'),
|
|
203
|
+
pool_registry_missing: client('client', 'The model pool registry is not initialized.'),
|
|
204
|
+
pool_subscribe_unregistered: client('client', 'Subscribed to a model that is not registered with the pool.'),
|
|
205
|
+
registry_invalid_constructor: client('client', 'A model was registered with an invalid constructor.'),
|
|
206
|
+
registry_not_initialized: client('client', 'The registry was used before initialization.'),
|
|
207
|
+
registry_property_conflict: client('client', 'Two registered models declared a conflicting property.'),
|
|
208
|
+
registry_reference_unknown_target: client('client', 'A relation referenced an unknown target model.'),
|
|
209
|
+
registry_reference_unresolved: client('client', 'A relation reference could not be resolved.'),
|
|
210
|
+
registry_unknown_model: client('client', 'The registry has no entry for the requested model.'),
|
|
211
|
+
query_returns_unknown_model: client('client', 'A query returned a model the registry does not know.'),
|
|
212
|
+
store_create_schema_missing: client('client', 'Store.create was called without a schema.'),
|
|
213
|
+
store_manager_unknown_model: client('client', 'The store manager has no entry for the requested model.'),
|
|
214
|
+
store_query_schema_missing: client('client', 'Store.query was called without a schema.'),
|
|
215
|
+
store_query_unknown_model: client('client', 'Store.query named a model the store does not know.'),
|
|
216
|
+
transaction_mutate_unknown_model: client('client', 'A transaction mutated a model the registry does not know.'),
|
|
217
|
+
transaction_read_unknown_model: client('client', 'A transaction read a model the registry does not know.'),
|
|
218
|
+
mutator_registry_duplicate: client('client', 'Two mutator definitions registered under the same name.'),
|
|
219
|
+
mutator_registry_unnamed_def: client('client', 'A mutator definition was registered without a name.'),
|
|
220
|
+
mutators_schema_missing: client('client', 'Mutators were registered without a schema.'),
|
|
221
|
+
undo_scope_schema_missing: client('client', 'An undo scope was opened without a schema.'),
|
|
222
|
+
mock_mutation_failed: client('client', 'A mock mutation adapter was configured to fail.'),
|
|
223
|
+
mock_unsupported_operation: client('client', 'A mock adapter received an unsupported operation.'),
|
|
224
|
+
// ── HTTP route edge codes (egress through app.onError) ─────────────
|
|
225
|
+
invalid_body: wire('validation', 400, false, 'The request body was missing, unparseable, or the wrong shape.'),
|
|
226
|
+
invalid_json: wire('validation', 400, false, 'The request body was not valid JSON.'),
|
|
227
|
+
capability_id_required: wire('validation', 400, false, 'A capability id is required for this request.'),
|
|
228
|
+
organization_mismatch: wire('permission', 403, false, 'The request targeted an organization the caller is not scoped to.'),
|
|
229
|
+
forbidden: wire('permission', 403, false, 'The caller lacks permission for this operation.'),
|
|
230
|
+
source_api_key_unresolved: wire('auth', 401, false, 'The source API key could not be resolved.'),
|
|
231
|
+
capability_auth_disabled: wire('server', 503, false, 'Capability authentication is disabled on this server.'),
|
|
232
|
+
provisioner_unavailable: wire('server', 503, false, 'No database provisioner is configured.'),
|
|
233
|
+
invalid_model: wire('validation', 400, false, 'The request named an invalid model.'),
|
|
234
|
+
invalid_id: wire('validation', 400, false, 'The request carried an invalid id.'),
|
|
235
|
+
unknown_model: wire('tenant', 400, false, 'The request named a model not in the tenant schema.'),
|
|
236
|
+
model_not_tenant_scoped: wire('tenant', 400, false, 'The model is not tenant-scoped and cannot be queried this way.'),
|
|
237
|
+
schema_table_invalid: wire('schema', 500, false, "The model's table identifier is invalid."),
|
|
238
|
+
schema_scope_invalid: wire('schema', 500, false, "The model's scope predicate could not be built."),
|
|
239
|
+
entity_fetch_failed: wire('server', 500, true, 'The entity fetch failed.'),
|
|
240
|
+
events_required: wire('validation', 400, false, 'The request must include a non-empty events array.'),
|
|
241
|
+
ingest_failed: wire('validation', 400, false, 'The source-event ingest failed.'),
|
|
242
|
+
migration_failed: wire('server', 500, false, 'The schema migration failed to apply.'),
|
|
243
|
+
model_query_failed: wire('validation', 400, false, 'The model query failed.'),
|
|
244
|
+
queries_required: wire('validation', 400, false, 'The request must include a non-empty queries array.'),
|
|
245
|
+
query_unsupported_operator: wire('validation', 400, false, 'The query used an unsupported operator.'),
|
|
246
|
+
query_unknown_relation: wire('validation', 400, false, 'The query referenced an unknown relation.'),
|
|
247
|
+
query_relation_target_unknown: wire('schema', 500, false, 'A relation targets a model the schema does not define.'),
|
|
248
|
+
query_invalid_identifier: wire('validation', 400, false, 'The query contained an invalid identifier.'),
|
|
249
|
+
org_id_required: wire('validation', 400, false, 'An organization id is required for this request.'),
|
|
250
|
+
presence_identity_required: wire('validation', 400, false, 'Both userId and organizationId are required.'),
|
|
251
|
+
upload_fields_required: wire('validation', 400, false, 'A required upload field was missing.'),
|
|
252
|
+
upload_items_required: wire('validation', 400, false, 'The request must include a non-empty items array.'),
|
|
253
|
+
presigned_url_failed: wire('server', 500, true, 'Failed to generate a presigned upload URL.'),
|
|
254
|
+
task_id_required: wire('validation', 400, false, 'A task id is required for this request.'),
|
|
255
|
+
intent_id_required: wire('validation', 400, false, 'An intent id is required for this request.'),
|
|
256
|
+
commit_operation_action_required: wire('validation', 400, false, 'A commit operation is missing its `action`.'),
|
|
257
|
+
commit_operation_unsupported: wire('validation', 400, false, 'A commit operation used an unsupported `action`.'),
|
|
258
|
+
usage_invalid: wire('validation', 400, false, 'The usage request was invalid.'),
|
|
259
|
+
invalid_request: wire('validation', 400, false, 'The request parameters were invalid.'),
|
|
260
|
+
capability_not_found: wire('not_found', 404, false, 'No capability exists with the given id.'),
|
|
261
|
+
invalid_participant_kind: wire('validation', 400, false, 'The participant kind is invalid.'),
|
|
262
|
+
narrow_scope_required: wire('validation', 400, false, 'A narrowed scope is required for this request.'),
|
|
263
|
+
wide_scope_forbidden: wire('permission', 403, false, 'A wide scope is not permitted for this caller.'),
|
|
264
|
+
capability_required: wire('auth', 401, false, 'This operation requires a capability.'),
|
|
265
|
+
parent_turn_not_found: wire('not_found', 404, false, 'The referenced parent turn does not exist.'),
|
|
266
|
+
parent_turn_foreign_agent: wire('permission', 403, false, 'The parent turn belongs to a different agent.'),
|
|
267
|
+
turn_not_found: wire('not_found', 404, false, 'The referenced turn does not exist.'),
|
|
268
|
+
turn_foreign_agent: wire('permission', 403, false, 'The turn belongs to a different agent.'),
|
|
269
|
+
invalid_intent: wire('validation', 400, false, 'The intent request was invalid.'),
|
|
270
|
+
schema_too_large: wire('validation', 413, false, 'The submitted schema exceeds the maximum size.'),
|
|
271
|
+
invalid_schema: wire('validation', 400, false, 'The submitted schema could not be parsed.'),
|
|
272
|
+
incompatible_change: wire('conflict', 409, false, 'The schema change is incompatible with the current schema.'),
|
|
273
|
+
};
|
|
274
|
+
/** Look up an error code's spec. Returns `undefined` for the dynamic
|
|
275
|
+
* `policy:*` family and for any forward-compat code an older SDK doesn't
|
|
276
|
+
* yet know. */
|
|
277
|
+
export function errorCodeSpec(code) {
|
|
278
|
+
return ERROR_CODES[code];
|
|
279
|
+
}
|
|
280
|
+
/** Whether a code's spec marks it retryable. Unknown / dynamic codes
|
|
281
|
+
* default to non-retryable (safe default — don't auto-retry the unknown). */
|
|
282
|
+
export function isRetryableCode(code) {
|
|
283
|
+
return errorCodeSpec(code)?.retryable ?? false;
|
|
284
|
+
}
|