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
34 changes: 34 additions & 0 deletions .changeset/ledger-synonym-admission-gate.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
---
"@objectstack/spec": minor
---

feat(spec): the error-code ledger's "no synonym of the standard catalog" rule is now mechanical, with recorded waivers (#8211, option C)

The ledger header has always said: if the condition is generic, use the
standard catalog instead of registering a synonym. That rule was prose only —
the admission gate rejected a code that is *literally* a `StandardErrorCode`
member, so four semantic synonyms (`CONFLICT`, `NOT_FOUND`, `FORBIDDEN`,
`INTERNAL`) accumulated without anyone deciding to allow them.

The rule now has teeth. New exports on `@objectstack/spec`:

- `standardSynonymOf(code)` — a **closed, mechanical** detector (no NLP fuzz):
a code is a semantic synonym of a standard member when it is the
SCREAMING_SNAKE spelling of an HTTP reason phrase whose status
`HttpStatusErrorCodeMap` maps to a member (`FORBIDDEN` → 403 →
`PERMISSION_DENIED`), or when every `_`-token of the code appears in a
member's name (`CONFLICT` ⊆ `RESOURCE_CONFLICT`).
- `standardSynonymViolations(ledger, waivers)` — every unwaived synonym
registration; the admission suite asserts it is empty and pins that the same
function rejects a newly-introduced synonym (both prongs).
- `StandardSynonymWaiverSchema` / `StandardSynonymWaiver` /
`STANDARD_SYNONYM_WAIVERS` — a waiver names the member the code shadows and
carries a recorded reason, so admission is a decision on the record, never
drift. Stale waivers fail the suite.

No wire change: the existing synonyms — the four above plus `UNAUTHORIZED`
(401 reason phrase of `UNAUTHENTICATED`'s condition, surfaced by the detector)
— are grandfathered via explicit waiver entries. Consolidating any of them
onto the member it shadows (option B) is deferred until a specific code has a
measured victim. One rule, two doors: this is the admission-door half of the
family whose dispatcher-door half was ruled in #8087.
95 changes: 93 additions & 2 deletions content/docs/references/api/error-code-ledger.mdx
Original file line numberDiff line numberDiff line change
Expand Up@@ -68,6 +68,28 @@ 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.

Expand DownExpand Up@@ -103,8 +125,8 @@ SEPARATE vocabulary and do not belong here — see #3977 (ADR-0112 D6).
## TypeScript Usage

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

// Validate data
const result = ErrorCode.parse(data);
Expand DownExpand Up@@ -388,3 +410,72 @@ const result = ErrorCode.parse(data);

---

## 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_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`


---

10 changes: 5 additions & 5 deletions content/docs/references/index.mdx
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
---
title: Protocol Reference
description: Every schema published by @objectstack/spec — 1570 schemas across 14 protocol modules
description: Every schema published by @objectstack/spec — 1571 schemas across 14 protocol modules
---

{/* ⚠️ AUTO-GENERATED — DO NOT EDIT. Run build-docs.ts to regenerate. Hand-written docs live in the module folders under content/docs/. */}
Expand All@@ -20,7 +20,7 @@ counts are sums of the rows they head. Regenerate with
| Module | Pages | Schemas | Description |
| :--- | ---: | ---: | :--- |
| [AI Protocol](/docs/references/ai) | 11 | 66 | Agents, tools, skills, RAG and knowledge sources, model registry, conversations. |
| [API Protocol](/docs/references/api) | 28 | 412 | REST/GraphQL contracts, endpoints, routing, realtime, batch, discovery. |
| [API Protocol](/docs/references/api) | 28 | 413 | REST/GraphQL contracts, endpoints, routing, realtime, batch, discovery. |
| [Automation Protocol](/docs/references/automation) | 13 | 68 | Flows and their nodes, approvals, ETL pipelines, webhooks, state machines, execution records. |
| [Cloud Protocol](/docs/references/cloud) | 11 | 94 | Environments, packages and versions, marketplace, developer portal, tenancy. |
| [Data Protocol](/docs/references/data) | 29 | 163 | Objects, fields, queries, filters, datasources and drivers — the ObjectQL layer. |
Expand All@@ -33,7 +33,7 @@ counts are sums of the rows they head. Regenerate with
| [Studio Protocol](/docs/references/studio) | 3 | 35 | Studio designer metadata — the authoring surfaces for the protocols above. |
| [System Protocol](/docs/references/system) | 36 | 287 | The runtime environment — logging, jobs, cache, metrics, notifications, i18n and compliance. |
| [UI Protocol](/docs/references/ui) | 16 | 148 | Apps, pages, views, dashboards, reports, actions and themes — the ObjectUI layer. |
| **Total** | **198** | **1570** | 14 protocol modules |
| **Total** | **198** | **1571** | 14 protocol modules |

---

Expand DownExpand Up@@ -61,7 +61,7 @@ Agents, tools, skills, RAG and knowledge sources, model registry, conversations.

## API Protocol

**Source:** `packages/spec/src/api/` · **Import:** `@objectstack/spec/api` · **28 pages, 412 schemas**
**Source:** `packages/spec/src/api/` · **Import:** `@objectstack/spec/api` · **28 pages, 413 schemas**

REST/GraphQL contracts, endpoints, routing, realtime, batch, discovery.

Expand All@@ -77,7 +77,7 @@ REST/GraphQL contracts, endpoints, routing, realtime, batch, discovery.
| [`dispatcher.zod.ts`](/docs/references/api/dispatcher) | `DispatcherConfig`, `DispatcherErrorCode`, `DispatcherErrorResponse`, `DispatcherRoute` |
| [`documentation.zod.ts`](/docs/references/api/documentation) | `ApiChangelogEntry`, `ApiDocumentationConfig`, `ApiTestCollection`, `ApiTestRequest`, `ApiTestingUiConfig`, `ApiTestingUiType`, `CodeGenerationTemplate`, `GeneratedApiDocumentation`, `OpenApiSecurityScheme`, `OpenApiServer`, `OpenApiSpec` |
| [`endpoint.zod.ts`](/docs/references/api/endpoint) | `ApiEndpoint`, `ApiMapping` |
| [`error-code-ledger.zod.ts`](/docs/references/api/error-code-ledger) | `ErrorCode` |
| [`error-code-ledger.zod.ts`](/docs/references/api/error-code-ledger) | `ErrorCode`, `StandardSynonymWaiver` |
| [`errors.zod.ts`](/docs/references/api/errors) | `EnhancedApiError`, `ErrorCategory`, `ErrorResponse`, `FieldError`, `FieldErrorCode`, `RetryStrategy`, `StandardErrorCode` |
| [`events.zod.ts`](/docs/references/api/events) | `BulkDataEvent`, `BulkDataEventType`, `DataEvent`, `DataEventType`, `MetadataEvent`, `MetadataEventType` |
| [`export.zod.ts`](/docs/references/api/export) | `CreateExportJobRequest`, `CreateExportJobResponse`, `CreateImportJobRequest`, `CreateImportJobResponse`, `DeduplicationStrategy`, `ExportFormat`, `ExportImportTemplate`, `ExportJobProgress`, `ExportJobStatus`, `ExportJobSummary`, `FieldMappingEntry`, `GetExportJobDownloadRequest`, `GetExportJobDownloadResponse`, `ImportJobProgress`, `ImportJobResults`, `ImportJobStatus`, `ImportJobSummary`, `ImportMapping`, `ImportRequest`, `ImportResponse`, `ImportRowResult`, `ImportValidationConfig`, `ImportValidationMode`, `ImportValidationResult`, `ImportWriteMode`, `ListExportJobsRequest`, `ListExportJobsResponse`, `ListImportJobsRequest`, `ListImportJobsResponse`, `ScheduleExportRequest`, `ScheduleExportResponse`, `ScheduledExport`, `UndoImportJobResponse` |
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -260,7 +260,7 @@ directory rather than per file.
| Dir | Sites |
|---|---|
| `ai/` | 77 |
| `api/` | 398 |
| `api/` | 399 |
| `cloud/` | 83 |
| `identity/` | 33 |
| `integration/` | 10 |
Expand Down
6 changes: 6 additions & 0 deletions packages/spec/api-surface/api.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -834,6 +834,7 @@
"RuntimeAuthoringIssue (type)",
"RuntimeAuthoringIssueSchema (const)",
"SERVICE_SELF_INFO_KEY (const)",
"STANDARD_SYNONYM_WAIVERS (const)",
"SaveMetaItemRequest (type)",
"SaveMetaItemRequestSchema (const)",
"SaveMetaItemResponse (type)",
Expand DownExpand Up@@ -873,6 +874,9 @@
"SingleRecordResponseSchema (const)",
"StandardApiContracts (const)",
"StandardErrorCode (type)",
"StandardSynonymViolation (interface)",
"StandardSynonymWaiver (type)",
"StandardSynonymWaiverSchema (const)",
"StorageApiContracts (const)",
"SubscribeMessage (type)",
"SubscribeMessageSchema (const)",
Expand DownExpand Up@@ -998,6 +1002,8 @@
"readServiceSelfInfo (function)",
"resolveDiscoveryEnvironment (function)",
"standardErrorCodeForHttpStatus (function)",
"standardSynonymOf (function)",
"standardSynonymViolations (function)",
"validateApiEndpointDeclarations (function)",
"zodIssuesToFields (function)"
]
Expand Down
3 changes: 3 additions & 0 deletions packages/spec/authorable-surface/api.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -1538,6 +1538,9 @@
"api/SingleRecordResponse:error",
"api/SingleRecordResponse:meta",
"api/SingleRecordResponse:success",
"api/StandardSynonymWaiver:code",
"api/StandardSynonymWaiver:reason",
"api/StandardSynonymWaiver:shadows",
"api/SubscribeMessage:messageId",
"api/SubscribeMessage:subscription",
"api/SubscribeMessage:timestamp",
Expand Down
6 changes: 6 additions & 0 deletions packages/spec/export-origins/api.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -834,6 +834,7 @@
"RuntimeAuthoringIssue": "src/api/protocol.zod.ts#RuntimeAuthoringIssue (type)",
"RuntimeAuthoringIssueSchema": "src/api/protocol.zod.ts#RuntimeAuthoringIssueSchema (const)",
"SERVICE_SELF_INFO_KEY": "src/api/discovery.zod.ts#SERVICE_SELF_INFO_KEY (const)",
"STANDARD_SYNONYM_WAIVERS": "src/api/error-code-ledger.zod.ts#STANDARD_SYNONYM_WAIVERS (const)",
"SaveMetaItemRequest": "src/api/protocol.zod.ts#SaveMetaItemRequest (type)",
"SaveMetaItemRequestSchema": "src/api/protocol.zod.ts#SaveMetaItemRequestSchema (const)",
"SaveMetaItemResponse": "src/api/protocol.zod.ts#SaveMetaItemResponse (type)",
Expand DownExpand Up@@ -873,6 +874,9 @@
"SingleRecordResponseSchema": "src/api/contract.zod.ts#SingleRecordResponseSchema (const)",
"StandardApiContracts": "src/api/contract.zod.ts#StandardApiContracts (const)",
"StandardErrorCode": "src/api/errors.zod.ts#StandardErrorCode (type)",
"StandardSynonymViolation": "src/api/error-code-ledger.zod.ts#StandardSynonymViolation (interface)",
"StandardSynonymWaiver": "src/api/error-code-ledger.zod.ts#StandardSynonymWaiver (type)",
"StandardSynonymWaiverSchema": "src/api/error-code-ledger.zod.ts#StandardSynonymWaiverSchema (const)",
"StorageApiContracts": "src/api/storage.zod.ts#StorageApiContracts (const)",
"SubscribeMessage": "src/api/websocket.zod.ts#SubscribeMessage (type)",
"SubscribeMessageSchema": "src/api/websocket.zod.ts#SubscribeMessageSchema (const)",
Expand DownExpand Up@@ -998,6 +1002,8 @@
"readServiceSelfInfo": "src/api/discovery.zod.ts#readServiceSelfInfo (function)",
"resolveDiscoveryEnvironment": "src/api/discovery.zod.ts#resolveDiscoveryEnvironment (function)",
"standardErrorCodeForHttpStatus": "src/api/errors.zod.ts#standardErrorCodeForHttpStatus (function)",
"standardSynonymOf": "src/api/error-code-ledger.zod.ts#standardSynonymOf (function)",
"standardSynonymViolations": "src/api/error-code-ledger.zod.ts#standardSynonymViolations (function)",
"validateApiEndpointDeclarations": "src/api/endpoint-publish-gate.ts#validateApiEndpointDeclarations (function)",
"zodIssuesToFields": "src/api/zod-issues-to-fields.ts#zodIssuesToFields (function)"
}
Expand Down
1 change: 1 addition & 0 deletions packages/spec/json-schema.manifest/api.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -363,6 +363,7 @@
"api/SimplePresenceState",
"api/SingleRecordResponse",
"api/StandardErrorCode",
"api/StandardSynonymWaiver",
"api/SubscribeMessage",
"api/Subscription",
"api/SubscriptionEvent",
Expand Down
Loading
Loading