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
54 changes: 54 additions & 0 deletions .changeset/rest-server-openapi31-block-removed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
---
"@objectstack/spec": major
---

refactor(spec)!: remove the `RestServerConfig.openApi31` block — OpenAPI 3.1 webhooks/callbacks config that no runtime ever read (#4579, ADR-0049)

`RestServerConfig.openApi31` (typed by `OpenApi31ExtensionsSchema`, with
`OpenApiWebhookEventSchema` and `CallbackSchema` under it) was authorable and
inert end to end — the declared ≠ enforced shape ADR-0049 exists to close:

- The REST server's `normalizeConfig` (`packages/rest/src/rest-server.ts`)
forwards only `api` / `crud` / `metadata` / `batch` / `routes`; `openApi31`
was silently discarded.
- The served `GET /openapi.json` is the pre-generated `@objectstack/spec`
contract, enriched at request time with the live server URL and the
runtime-registered objects — it never consulted the config.
- `gen:openapi` (`scripts/build-openapi.ts`) never read a webhook or callback.

So a webhook an author declared under `openApi31.webhooks` **never appeared in
any served OpenAPI document** — false compliance, the same class as the
connector-webhook gap (#3197). Zero import-level consumers existed for all
three schemas across objectstack / cloud / objectui (three-repo scan in #4579).

Migration (FROM → TO):

- `openApi31` in a `RestServerConfig` value (REST plugin constructor /
`plugin-hono-server` `restConfig`) → **delete the key**. There is no
replacement: nothing ever read it, so removing it changes no served
document. The key is tombstoned, not silently stripped —
`RestServerConfigSchema` is not `.strict()`, so a `retiredKey()` tombstone
makes authoring it a `tsc` error and a parse error carrying this
prescription.
- `import { OpenApi31Extensions(Schema), Callback(Schema), OpenApiWebhookEvent(Schema) } from '@objectstack/spec/api'`
→ **no replacement export** (TS2305 after upgrade). For a real outbound
webhook use `Webhook` from `@objectstack/spec/automation`; for connector
webhook events use `WebhookEvent` from `@objectstack/spec/integration`.
(`OpenApiWebhookEvent(Schema)` was the #4572 rename of `./api`'s
`WebhookEvent(Schema)`; this removal absorbs that rename — pre-16 imports of
the bare name land here too.)
- Config-driven OpenAPI 3.1 webhooks/callbacks documentation is a **new
capability**: if it is ever needed it returns via the enforce route of
ADR-0049, through a new ADR — not by re-declaring inert keys.

The retirement kit: `retiredKey()` tombstone on the non-strict schema (parse +
`tsc` both audible); ADR-0087 D3 semantic migration
`rest-server-openapi31-block-removed` (plugin TS config is never a
`sys_metadata` shape — the stack tree's `api` block declares only its four
scoping/auth knobs — so there is no stored row or stack source for a D2
conversion to rewrite); baselines (`authorable-surface.json` [RETIRED] line,
`json-schema.manifest.json` def removals, `api-surface.json`) regenerated
deliberately; compiler-API export pin + sabotage-verified tombstone tests.

No runtime behaviour changes — that impossibility is the reason for the
removal: the served `/openapi.json` is byte-identical before and after.
19 changes: 12 additions & 7 deletions .changeset/webhook-dual-source-api-side.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,19 @@ import-statement-level consumer scan: framework, cloud, objectui):
`registrationEndpoint` / `enabled`).
- **Renamed** `WebhookEventSchema` / `WebhookEvent` in `@objectstack/spec/api`
→ `OpenApiWebhookEventSchema` / `OpenApiWebhookEvent` (same shape, rename
only; joins the existing `OpenApi*` family). `OpenApi31ExtensionsSchema.webhooks`
now references the renamed schema — its parsed/authored shape is unchanged.
only; joins the existing `OpenApi*` family).
**Superseded in the same major (#4579, ADR-0049):** the renamed pair was
then removed outright with the whole inert `RestServerConfig.openApi31`
block, so the rename never ships as a landing spot — see the
`rest-server-openapi31-block-removed` changeset.
- FROM `import { WebhookEvent } from '@objectstack/spec/api'` →
TO `import { OpenApiWebhookEvent } from '@objectstack/spec/api'` (if you
meant the OpenAPI 3.1 webhook descriptor), or
`import { WebhookEvent } from '@objectstack/spec/integration'` (if you
meant the connector event enum — check which shape you actually consume:
object vs string enum).
TO: no `./api` replacement exists (the OpenAPI 3.1 webhook descriptor was
removed in #4579 — nothing ever rendered it into the served
`/openapi.json`). Use
`import { WebhookEvent } from '@objectstack/spec/integration'` if you
meant the connector event enum (check which shape you actually consume:
object vs string enum), or `Webhook` from `@objectstack/spec/automation`
for a real outbound webhook.
- `@objectstack/spec/integration`'s `WebhookConfig(Schema)` /
`WebhookEvent(Schema)` are **unchanged** and are now the sole owners of the
bare names. Imports from `./integration` need no migration.
Expand Down
50 changes: 3 additions & 47 deletions content/docs/references/api/rest-server.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@ Architecture alignment:
## TypeScript Usage

```typescript
import { BatchEndpointsConfigSchema, CallbackSchema, CrudEndpointPatternSchema, CrudEndpointsConfigSchema, CrudOperation, EndpointRegistrySchema, GeneratedEndpointSchema, MetadataEndpointsConfigSchema, OpenApi31ExtensionsSchema, OpenApiWebhookEventSchema, RestApiConfigSchema, RestServerConfigSchema, RouteGenerationConfigSchema } from '@objectstack/spec/api';
import type { BatchEndpointsConfig, Callback, CrudEndpointPattern, CrudEndpointsConfig, CrudOperation, EndpointRegistry, GeneratedEndpoint, MetadataEndpointsConfig, OpenApi31Extensions, OpenApiWebhookEvent, RestApiConfig, RestServerConfig, RouteGenerationConfig } from '@objectstack/spec/api';
import { BatchEndpointsConfigSchema, CrudEndpointPatternSchema, CrudEndpointsConfigSchema, CrudOperation, EndpointRegistrySchema, GeneratedEndpointSchema, MetadataEndpointsConfigSchema, RestApiConfigSchema, RestServerConfigSchema, RouteGenerationConfigSchema } from '@objectstack/spec/api';
import type { BatchEndpointsConfig, CrudEndpointPattern, CrudEndpointsConfig, CrudOperation, EndpointRegistry, GeneratedEndpoint, MetadataEndpointsConfig, RestApiConfig, RestServerConfig, RouteGenerationConfig } from '@objectstack/spec/api';

// Validate data
const result = BatchEndpointsConfigSchema.parse(data);
Expand All @@ -59,20 +59,6 @@ const result = BatchEndpointsConfigSchema.parse(data);
| **defaultAtomic** | `boolean` | ✅ | Default atomic/transaction mode for batch operations |


---

## Callback

### Properties

| Property | Type | Required | Description |
| :--- | :--- | :--- | :--- |
| **name** | `string` | ✅ | Callback identifier (snake_case) |
| **expression** | `string` | ✅ | Runtime expression (e.g., `{$request.body#/callbackUrl}`) |
| **method** | `Enum<'GET' \| 'POST' \| 'PUT' \| 'DELETE' \| 'PATCH' \| 'HEAD' \| 'OPTIONS'>` | ✅ | HTTP method for callback request |
| **url** | `string` | ✅ | Callback URL template with runtime expressions |


---

## CrudEndpointPattern
Expand Down Expand Up @@ -159,36 +145,6 @@ const result = BatchEndpointsConfigSchema.parse(data);
| **endpoints** | `{ types: boolean; items: boolean; item: boolean; schema: boolean }` | optional | Enable/disable specific endpoints |


---

## OpenApi31Extensions

### Properties

| Property | Type | Required | Description |
| :--- | :--- | :--- | :--- |
| **webhooks** | `Record<string, { name: string; description: string; method: Enum<'GET' \| 'POST' \| 'PUT' \| 'DELETE' \| 'PATCH' \| 'HEAD' \| 'OPTIONS'>; payloadSchema: string; … }>` | optional | OpenAPI 3.1 webhooks (top-level webhook definitions) |
| **callbacks** | `Record<string, { name: string; expression: string; method: Enum<'GET' \| 'POST' \| 'PUT' \| 'DELETE' \| 'PATCH' \| 'HEAD' \| 'OPTIONS'>; url: string }[]>` | optional | OpenAPI 3.1 callbacks (async response definitions) |
| **jsonSchemaDialect** | `string` | ✅ | JSON Schema dialect for schema definitions |
| **pathItemReferences** | `boolean` | ✅ | Allow $ref in path items (OpenAPI 3.1 feature) |


---

## OpenApiWebhookEvent

### Properties

| Property | Type | Required | Description |
| :--- | :--- | :--- | :--- |
| **name** | `string` | ✅ | Webhook event identifier (snake_case) |
| **description** | `string` | ✅ | Human-readable event description |
| **method** | `Enum<'GET' \| 'POST' \| 'PUT' \| 'DELETE' \| 'PATCH' \| 'HEAD' \| 'OPTIONS'>` | ✅ | HTTP method for webhook delivery |
| **payloadSchema** | `string` | ✅ | JSON Schema $ref for the webhook payload |
| **headers** | `Record<string, string>` | optional | Custom headers to include in webhook delivery |
| **security** | `Enum<'hmac_sha256' \| 'basic' \| 'bearer' \| 'api_key'>[]` | ✅ | Supported authentication methods for webhook verification |


---

## RestApiConfig
Expand Down Expand Up @@ -226,7 +182,7 @@ const result = BatchEndpointsConfigSchema.parse(data);
| **metadata** | `{ prefix: string; enableCache: boolean; cacheTtl: integer; endpoints?: object }` | optional | Metadata endpoints configuration |
| **batch** | `{ maxBatchSize: integer; enableBatchEndpoint: boolean; operations?: object; defaultAtomic: boolean }` | optional | Batch endpoints configuration |
| **routes** | `{ includeObjects?: string[]; excludeObjects?: string[]; nameTransform: Enum<'none' \| 'plural' \| 'kebab-case' \| 'camelCase'>; overrides?: Record<string, { enabled?: boolean; basePath?: string; operations?: Record<string, boolean> }> }` | optional | Route generation configuration |
| **openApi31** | `{ webhooks?: Record<string, { name: string; description: string; method: Enum<'GET' \| 'POST' \| 'PUT' \| 'DELETE' \| 'PATCH' \| 'HEAD' \| 'OPTIONS'>; payloadSchema: string; … }>; callbacks?: Record<string, { name: string; expression: string; method: Enum<'GET' \| 'POST' \| 'PUT' \| 'DELETE' \| 'PATCH' \| 'HEAD' \| 'OPTIONS'>; url: string }[]>; jsonSchemaDialect: string; pathItemReferences: boolean }` | optional | OpenAPI 3.1 extensions configuration |
| **openApi31** | `any` | optional | [REMOVED] `RestServerConfig.openApi31` was removed in @objectstack/spec 17 (#4579, ADR-0049) — no runtime ever read it: the REST server forwards only `api`/`crud`/`metadata`/`batch`/`routes`, and the served /openapi.json is the pre-generated contract enriched with the live server URL and the registered objects, so webhook/callback definitions declared here never appeared in it. Delete the key. Config-driven OpenAPI 3.1 webhooks/callbacks documentation is a new capability and must arrive via the enforce route of ADR-0049 (a new ADR), not by re-declaring the key; for a real outbound webhook use `Webhook` from `@objectstack/spec/automation`. |


---
Expand Down
5 changes: 5 additions & 0 deletions docs/protocol-upgrade-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,8 @@ The same enforce-or-remove pass reaches the event vocabulary: `DataEventType` dr

The object capability block closes out the same ADR-0049 pass: `enable.trash` and `enable.mru` left the schema in the 16.x line (#3207, the #2377 close-out — every delete has always been a hard delete and MRU tracking was never implemented, so both default-true flags gated nothing), and the `.strict()` capabilities block rejects them with the prescription. This step registers the migration surface that removal was missing: stored 16.x rows replay clean instead of flagging `metadata_spec_invalid`, and `os migrate meta --from 16` rewrites authored sources. Soft delete stays parked at #3146; if built it returns as a live enforced flag rather than by reviving these keys.

The same enforce-or-remove pass retires the `RestServerConfig.openApi31` block (#4579): `OpenApi31ExtensionsSchema` (`webhooks` / `callbacks` / `jsonSchemaDialect` / `pathItemReferences`) with `OpenApiWebhookEventSchema` and `CallbackSchema` under it. Declared-but-unenforced end to end: the REST server's `normalizeConfig` forwards only `api`/`crud`/`metadata`/`batch`/`routes`, the served /openapi.json is the pre-generated contract enriched with the live server URL and registered objects, and `gen:openapi` never read a webhook or callback — so a definition authored under `openApi31.webhooks` never appeared in any served document, and zero import-level consumers existed across objectstack / cloud / objectui. `RestServerConfig` is plugin TS configuration (the REST plugin constructor / `plugin-hono-server` `restConfig`), never a stored metadata shape: the stack tree's own `api` block declares only its four scoping/auth knobs, so no `sys_metadata` row can carry `openApi31` and there is no source for the chain to rewrite — one semantic TODO for config authors rather than a stack conversion, the `validateOnly` shape. The key itself is tombstoned (the schema is not `.strict()`; a plain delete would strip it silently), and a config-driven webhooks/callbacks synthesis, if ever wanted, returns via the enforce route of ADR-0049 through a new ADR.

### Mechanical (applied for you)

| Conversion | Surface | Change | Load window |
Expand Down Expand Up @@ -266,6 +268,9 @@ The object capability block closes out the same ADR-0049 pass: `enable.trash` an
- **`data-engine-batch-retired`** — `contracts.IDataEngine.batch / data.DataEngineBatchRequestSchema` → `IObjectQLEngine.transaction(cb)` for in-process multi-write atomicity; the metadata protocol's `batchData` with `options.atomic: true` for a batch over one object; `POST {basePath}/batch` on the wire
- Why not automatic: `batch?` was declared on `IDataEngine` for as long as that contract existed and was never implemented by any engine: `ObjectQL` has no `batch` method and there is no other engine in the tree. It also had no caller — `DataEngineRequest` was imported by exactly one file, the contract declaring the member. Its entire specification was a three-word doc comment ("Batch Operations (Transactional)"), which settles nothing about partial failure, ordering, cross-object references, rollback scope, or what `transaction: false` was supposed to mean — the questions a batch API exists to answer. Contrast its neighbours `getDefaultDriverName?` / `getDriverByName?`, whose optionality is evidenced: each names its implementer and its probing caller. The tell that nobody ever designed against it is in the schema: `DataEngineBatchRequestSchema.requests` nested the request union RECURSIVELY, so a batch could contain batches, with no statement anywhere about what that meant for ordering or rollback. The only test was a type pin — an ad-hoc object literal carrying a `batch` property, asserting the property was defined — which could not fail while the declaration existed and would have passed unchanged for the member's whole life with no engine implementing it. What it claimed is now covered by members that are real, so the removal deletes a false affordance rather than a capability: ADR-0119 D1 made `transaction` reachable through the contract and D4 made `batchData`'s `atomic` honest, while the wire batch has always validated with `CrossObjectBatchRequestSchema` / `BatchUpdateRequestSchema` from `api/batch.zod.ts` — a different schema entirely, untouched here. TS/API surfaces only: an engine is CODE, never stack metadata, so there is no source for the chain to rewrite. Deliberately no schema tombstone either — nothing ever parsed `DataEngineBatchRequestSchema`, so a `retiredKey()` prescription would have no one to reach; its three `authorable-surface.json` baseline lines and its `json-schema.manifest.json` entry are dropped in the same change, deliberately. The enforced channel is tsc. ADR-0049 / ADR-0078, #4618.
- Done when: No code calls `engine.batch(...)` and no type references `DataEngineBatchRequest`; in-process multi-write atomicity goes through `IObjectQLEngine.transaction(cb)`, a batch over one object through `batchData` with `options.atomic: true`, and a cross-object batch over the wire through `POST {basePath}/batch`. Because no engine implemented the member, an implementation left behind still compiles and is simply never reached; a CALLER of it no longer type-checks — and there were none.
- **`rest-server-openapi31-block-removed`** — `restServer.openApi31` → (removed — no replacement key exists. Delete the key; for a real outbound webhook use `Webhook` from `@objectstack/spec/automation`. Config-driven OpenAPI 3.1 webhooks/callbacks documentation returns, if ever, via the enforce route of ADR-0049 through a new ADR)
- Why not automatic: The `openApi31` block (`webhooks` / `callbacks` / `jsonSchemaDialect` / `pathItemReferences`, typed by `OpenApi31ExtensionsSchema` with `OpenApiWebhookEventSchema` and `CallbackSchema` under it) promised OpenAPI 3.1 document synthesis nothing delivered: the REST server's `normalizeConfig` forwards only `api`/`crud`/`metadata`/`batch`/`routes`, and the served /openapi.json is the pre-generated @objectstack/spec contract enriched with the live server URL and the registered objects — a webhook declared here never appeared in any served document (ADR-0049; the #3197 connector-webhook shape one layer up). There is no behaviour to preserve and nothing stored to rewrite: `RestServerConfig` is plugin TS configuration (REST plugin constructor / `plugin-hono-server` `restConfig`), never a `sys_metadata` shape — the stack tree's `api` block declares only its four scoping/auth knobs. The three schemas are removed with the key (zero import-level consumers in objectstack / cloud / objectui); the key itself is tombstoned because the schema is not `.strict()` and a plain delete would strip it silently. #4579.
- Done when: No `RestServerConfig` value passed to the REST plugin (or `plugin-hono-server` `restConfig`) carries `openApi31` — a config that includes it now fails the parse with the retirement prescription instead of being silently stripped. No code imports `OpenApi31Extensions(Schema)`, `Callback(Schema)` or `OpenApiWebhookEvent(Schema)` from `@objectstack/spec/api` (TS2305 after upgrade). The served /openapi.json is byte-identical before and after — the block never reached it.

---

Expand Down
6 changes: 0 additions & 6 deletions packages/spec/api-surface.json
Original file line number Diff line number Diff line change
Expand Up @@ -2455,8 +2455,6 @@
"CacheInvalidationResponse (type)",
"CacheInvalidationResponseSchema (const)",
"CacheInvalidationTarget (type)",
"Callback (type)",
"CallbackSchema (const)",
"CheckPermissionRequest (type)",
"CheckPermissionRequestSchema (const)",
"CheckPermissionResponse (type)",
Expand Down Expand Up @@ -2910,8 +2908,6 @@
"ObjectDefinitionResponseSchema (const)",
"ObjectQLReference (type)",
"ObjectQLReferenceSchema (const)",
"OpenApi31Extensions (type)",
"OpenApi31ExtensionsSchema (const)",
"OpenApiGenerationConfig (type)",
"OpenApiGenerationConfigInput (type)",
"OpenApiGenerationConfigSchema (const)",
Expand All @@ -2921,8 +2917,6 @@
"OpenApiServerSchema (const)",
"OpenApiSpec (type)",
"OpenApiSpecSchema (const)",
"OpenApiWebhookEvent (type)",
"OpenApiWebhookEventSchema (const)",
"OperatorMapping (type)",
"OperatorMappingSchema (const)",
"PackageApiContracts (const)",
Expand Down
Loading
Loading