Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 66 additions & 0 deletions .changeset/adr-0112-batch-2-lowercase-sweep.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
---
"@objectstack/spec": minor
"@objectstack/rest": minor
"@objectstack/runtime": minor
"@objectstack/cloud-connection": minor
"@objectstack/plugin-auth": minor
"@objectstack/plugin-approvals": minor
"@objectstack/plugin-webhooks": minor
"@objectstack/service-messaging": minor
"@objectstack/service-automation": minor
"@objectstack/metadata-protocol": minor
"@objectstack/metadata-core": minor
"@objectstack/trigger-api": minor
"@objectstack/hono": minor
---

refactor!: ADR-0112 batch 2 — sweep the lowercase error-code emitters (#4003)

Continues #3841 per ADR-0112. Batch 1 (#3988) settled the vocabulary and closed
the set; this batch moves the emitters that still spoke lowercase `snake_case`
onto it.

**Wire-visible change.** Error codes on these surfaces change spelling. Generic
conditions collapse onto the standard catalog rather than keeping a synonym:
`unauthorized`/`unauthenticated` → `UNAUTHENTICATED`, `forbidden` →
`PERMISSION_DENIED`, `not_found` → `RESOURCE_NOT_FOUND`, `internal` →
`INTERNAL_ERROR`, `unavailable` → `SERVICE_UNAVAILABLE`, `not_supported` →
`NOT_IMPLEMENTED`, `bad_request` → `INVALID_REQUEST`. Domain conditions get codes
registered in `ERROR_CODE_LEDGER` (`MARKETPLACE_STORAGE_FAILED`,
`PLUGIN_MANIFEST_INVALID`, `ITEM_LOCKED`, `DELIVERY_NOT_ELIGIBLE`, …). Swept:
`cloud-connection`, `plugin-auth`, `hono`, `metadata-protocol`, `rest`,
`service-messaging`, `service-automation`, `trigger-api`.

Branch on `error.code` values rather than pattern-matching their case: the
console's fix for the same rename (objectui#2977) reads codes case-insensitively
for exactly this reason, and that is the pattern to copy in your own consumers if
you support servers on both sides of the change.

**Four routes stop putting a code in the message slot.** The webhook redeliver
route, the API-trigger webhook, and two `rest` routes answered
`{ success: false, error: '<code>', message }` — the code occupying `error`, the
declared object envelope nowhere. They now emit `error: { code, message }`, and
three API-trigger branches gained a message they never had. Clients reading
`body.error` as a string on those routes must read `body.error.code`.

**`ConnectorErrorCategory` / `ConnectorRetryStrategy`** (ADR-0112 D9a):
`@objectstack/spec` exported two mutually incompatible `ErrorCategory` types and
two `RetryStrategy` types. The connector-side pair is renamed; importers of the
`integration` subpath update the name. Side effect: the api-side `ErrorCategory`
and `RetryStrategy` now appear in the generated API reference at all — the name
collision had been silently dropping them.

**`OAUTH_REGISTER_FAILED` replaces an unbounded code source.** The OAuth client
registration route put better-auth's arbitrary `body.error` string straight into
`error.code`. The code is now ours and the upstream discriminator moved to
`details.upstreamError`.

**Not swept, deliberately.** `sys_metadata_audit.code` keeps its lowercase values
(ADR-0112 D6b): it is persisted audit history, and the same column holds
non-error outcomes (`ok`, `lock_override`). Diagnostics records that ship inside a
200 keep theirs (D6c), as do field-level codes (D6, #3977) and the CLI's
`--json` output contract.

A `check:error-code-casing` CI guard now fails on a new lowercase literal in a
code position, since the ledger's casing rule can only police codes that someone
registers.
9 changes: 9 additions & 0 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,15 @@ jobs:
- name: Response-envelope guard
run: pnpm check:route-envelope

# Error-code casing guard (ADR-0112, #4003). The ledger's admission test
# enforces casing on every code someone REGISTERS; this catches the ones
# nobody registers — an unregistered lowercase literal in a code position
# is invisible to both the ledger and to the schema on any route that does
# not parse its own response. That is how 208 of them accumulated across
# 10 packages before batch 2. Runs its own --self-test first.
- name: Error-code casing guard
run: pnpm check:error-code-casing

# Release-notes drift guard: the platform is one version-locked train, so
# every released @objectstack/spec major must have a curated, navigable
# release page at content/docs/releases/v<major>.mdx. Catches the gap that
Expand Down
9 changes: 5 additions & 4 deletions content/docs/automation/webhooks.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -399,10 +399,11 @@ The plugin mounts exactly **one** custom endpoint:
| POST | `/api/v1/webhooks/redeliver` | `{ deliveryId }` | Re-queue a previously failed/dead delivery. |

It delegates to `messaging.redeliverHttp(deliveryId)` and is guarded by the
better-auth session (any authenticated user). Responses: `200` on success,
`401` unauthenticated, `400` missing/invalid body, `404` `not_found`, `409`
`not_eligible`. There are no `/webhooks/:id/test`, `/rotate`,
`/deliveries`, or per-delivery redeliver routes.
better-auth session (any authenticated user). Failures answer in the declared
envelope — `{ success: false, error: { code, message } }` — with `401`
`UNAUTHENTICATED`, `400` `INVALID_REQUEST` or `MISSING_REQUIRED_FIELD`, `404`
`RESOURCE_NOT_FOUND`, `409` `DELIVERY_NOT_ELIGIBLE` (ADR-0112). There are no
`/webhooks/:id/test`, `/rotate`, `/deliveries`, or per-delivery redeliver routes.

## 9. Concurrency & rate-limiting

Expand Down
6 changes: 3 additions & 3 deletions content/docs/references/api/analytics.mdx

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions content/docs/references/api/auth.mdx

Large diffs are not rendered by default.

18 changes: 9 additions & 9 deletions content/docs/references/api/automation-api.mdx

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions content/docs/references/api/batch.mdx

Large diffs are not rendered by default.

35 changes: 3 additions & 32 deletions content/docs/references/api/connector.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -12,42 +12,13 @@ description: Connector protocol schemas
## TypeScript Usage

```typescript
import { ErrorCategory, RetryStrategy, WebhookConfig, WebhookEvent } from '@objectstack/spec/api';
import type { ErrorCategory, RetryStrategy, WebhookConfig, WebhookEvent } from '@objectstack/spec/api';
import { WebhookConfig, WebhookEvent } from '@objectstack/spec/api';
import type { WebhookConfig, WebhookEvent } from '@objectstack/spec/api';

// Validate data
const result = ErrorCategory.parse(data);
const result = WebhookConfig.parse(data);
```

---

## ErrorCategory

### Allowed Values

* `validation`
* `authentication`
* `authorization`
* `not_found`
* `conflict`
* `rate_limit`
* `server`
* `external`
* `maintenance`


---

## RetryStrategy

### Allowed Values

* `no_retry`
* `retry_immediate`
* `retry_backoff`
* `retry_after`


---

## WebhookConfig
Expand Down
16 changes: 8 additions & 8 deletions content/docs/references/api/contract.mdx

Large diffs are not rendered by default.

38 changes: 38 additions & 0 deletions content/docs/references/api/error-code-ledger.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -137,13 +137,18 @@ const result = ErrorCode.parse(data);
* `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`
* `CONFLICTING_MAPPING`
* `CONNECTOR_UPSTREAM_UNAVAILABLE`
Expand All @@ -152,10 +157,18 @@ const result = ErrorCode.parse(data);
* `DATASET_INVALID`
* `DATASOURCE_ADMIN_ERROR`
* `DELEGABLE_SCOPE_FAILED`
* `DELIVERY_NOT_ELIGIBLE`
* `DESTRUCTIVE_CHANGE`
* `DEVICE_CODE_FAILED`
* `DOMAIN_VERIFICATION_DISABLED`
* `DOMAIN_VERIFICATION_FAILED`
* `DRIVER_UNAVAILABLE`
* `DUPLICATE_REQUEST`
* `EMAIL_SEND_FAILED`
* `EMAIL_SERVICE_REQUIRED`
* `ENQUEUE_FAILED`
* `ENVIRONMENT_BIND_FAILED`
* `ENVIRONMENT_NOT_FOUND`
* `ENV_ACCESS_DENIED`
* `ERR_BULK_RESULT_MISMATCH`
* `ERR_DATASOURCE_UNAVAILABLE`
Expand Down Expand Up @@ -187,22 +200,30 @@ const result = ErrorCode.parse(data);
* `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`
Expand All @@ -211,15 +232,22 @@ const result = ErrorCode.parse(data);
* `NAMESPACE_PREFIX`
* `NEEDS_PASSWORD`
* `NODE_FAILURE`
* `NOTHING_TO_PURGE`
* `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`
* `OPENAPI_UNAVAILABLE`
* `OS_PROTOCOL_INCOMPATIBLE`
* `OVERLAY_PERSISTENCE_FAILED`
* `PACKAGE_DELETE_FAILED`
* `PACKAGE_DELETE_PARTIAL`
* `PACKAGE_MANIFEST_INVALID`
Expand All @@ -231,6 +259,9 @@ const result = ErrorCode.parse(data);
* `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`
Expand All @@ -246,13 +277,16 @@ const result = ErrorCode.parse(data);
* `REPORT_SAVE_FAILED`
* `REPORT_SCHEDULE_FAILED`
* `REQUEST_NOT_FOUND`
* `RESEED_NO_ROWS`
* `RESEED_SKIPPED`
* `ROUTE_NOT_FOUND`
* `RULE_DEFINE_FAILED`
* `RULE_DELETE_FAILED`
* `RULE_EVALUATE_FAILED`
* `RULE_GET_FAILED`
* `RULE_LIST_FAILED`
* `RULE_NOT_FOUND`
* `SAML_REGISTER_FAILED`
* `SCHEDULES_LIST_FAILED`
* `SCHEDULE_DELETE_FAILED`
* `SETTINGS_ACTION_FAILED`
Expand All @@ -266,6 +300,7 @@ const result = ErrorCode.parse(data);
* `SHARE_REVOKE_FAILED`
* `SHARING_NOT_ENABLED`
* `SIGN_IN_REQUIRED`
* `SSO_REGISTER_FAILED`
* `SSO_REGISTER_FORBIDDEN`
* `SUGGESTION_CONFIRM_FAILED`
* `SUGGESTION_DISMISS_FAILED`
Expand All @@ -283,6 +318,9 @@ const result = ErrorCode.parse(data);
* `UPLOAD_SESSION_NOT_FOUND`
* `USER_ALREADY_EXISTS`
* `VALIDATION_FAILED`
* `VERSION_NOT_FOUND`
* `VERSION_NOT_RESTORABLE`
* `WRITABLE_PACKAGE_REQUIRED`
* `WRONG_PASSWORD`


Expand Down
33 changes: 31 additions & 2 deletions content/docs/references/api/errors.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ Industry alignment: Google Cloud Errors, AWS Error Codes, Stripe API Errors
## TypeScript Usage

```typescript
import { EnhancedApiError, ErrorResponse, FieldError, StandardErrorCode } from '@objectstack/spec/api';
import type { EnhancedApiError, ErrorResponse, FieldError, StandardErrorCode } from '@objectstack/spec/api';
import { EnhancedApiError, ErrorCategory, ErrorResponse, FieldError, RetryStrategy, StandardErrorCode } from '@objectstack/spec/api';
import type { EnhancedApiError, ErrorCategory, ErrorResponse, FieldError, RetryStrategy, StandardErrorCode } from '@objectstack/spec/api';

// Validate data
const result = EnhancedApiError.parse(data);
Expand Down Expand Up @@ -61,6 +61,23 @@ const result = EnhancedApiError.parse(data);
| **helpText** | `string` | optional | Suggested actions to resolve the error |


---

## ErrorCategory

### Allowed Values

* `validation`
* `authentication`
* `authorization`
* `not_found`
* `conflict`
* `rate_limit`
* `server`
* `external`
* `maintenance`


---

## ErrorResponse
Expand Down Expand Up @@ -89,6 +106,18 @@ const result = EnhancedApiError.parse(data);
| **constraint** | `any` | optional | The constraint that was violated (e.g., max length) |


---

## RetryStrategy

### Allowed Values

* `no_retry`
* `retry_immediate`
* `retry_backoff`
* `retry_after`


---

## StandardErrorCode
Expand Down
Loading
Loading