Skip to content

Commit 924e8a2

Browse files
committed
feat(spec)!: 退役 RestServerConfig.openApi31 整块 —— declared ≠ enforced,零运行时读者 (#4579)
按 2026-08-02 委托裁决(remove)整块删除 OpenApi31ExtensionsSchema / CallbackSchema / OpenApiWebhookEventSchema + RestServerConfig.openApi31 键: normalizeConfig 只读 api/crud/metadata/batch/routes,/openapi.json 由预生成 契约 enrich 而来,gen:openapi 零 webhook/callback 引用 —— 作者写进 openApi31.webhooks 的定义从未出现在任何服务出的文档里(ADR-0049 假合规)。 三仓(objectstack / cloud@5df2c699b / objectui@785b8a5d4)import 级零消费者。 - rest-server.zod.ts:三 schema 删除留指路注释;openApi31 走 retiredKey() 墓碑(非 strict schema,裸删会静默剥离 —— requireAuth #3963 同款) - conversion 评估(#4734 先例):RestServerConfig 是插件 TS 配置面, stack 树的 api 块只声明四个 scoping/auth 键,sys_metadata 不可能携带 openApi31,无源可改写 ⇒ 不登记 D2 conversion,登记 D3 semantic migration rest-server-openapi31-block-removed(validateOnly 先例) - authorable-surface:14 行随三个整 def 出账(#4650 门禁判定 def no longer emitted,输出见 PR);api/RestServerConfig:openApi31 → [RETIRED] - json-schema.manifest.json:三 def 删行(#2978 蓄意移除通道) - api-surface.json:./api 六行删除(3 const + 3 type) - 回归 pin(rest-server.test.ts):compiler-API 断言全入口不再命名六个 导出名(防空转守卫)+ 运行时命名空间一致性;墓碑 sabotage(authored openApi31 → 拒绝且文案指向本单;正常键仍过);S1 sabotage 实测两 pin 红 - changeset:@objectstack/spec major(删已发布导出名 + 键,TS2305); 同 major 内吸收 #4572 的 WebhookEvent→OpenApiWebhookEvent 改名 (webhook-dual-source-api-side.md 的 TO 指引同步更正) - 严格性台账:check:strictness-ledger 实跑 PASS,api/ 行属 coarse 汇总 层(非门禁校验面),不动 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0176qgxgCXTJCUv4YFLtusP9
1 parent 5647006 commit 924e8a2

11 files changed

Lines changed: 293 additions & 275 deletions
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
---
2+
"@objectstack/spec": major
3+
---
4+
5+
refactor(spec)!: remove the `RestServerConfig.openApi31` block — OpenAPI 3.1 webhooks/callbacks config that no runtime ever read (#4579, ADR-0049)
6+
7+
`RestServerConfig.openApi31` (typed by `OpenApi31ExtensionsSchema`, with
8+
`OpenApiWebhookEventSchema` and `CallbackSchema` under it) was authorable and
9+
inert end to end — the declared ≠ enforced shape ADR-0049 exists to close:
10+
11+
- The REST server's `normalizeConfig` (`packages/rest/src/rest-server.ts`)
12+
forwards only `api` / `crud` / `metadata` / `batch` / `routes`; `openApi31`
13+
was silently discarded.
14+
- The served `GET /openapi.json` is the pre-generated `@objectstack/spec`
15+
contract, enriched at request time with the live server URL and the
16+
runtime-registered objects — it never consulted the config.
17+
- `gen:openapi` (`scripts/build-openapi.ts`) never read a webhook or callback.
18+
19+
So a webhook an author declared under `openApi31.webhooks` **never appeared in
20+
any served OpenAPI document** — false compliance, the same class as the
21+
connector-webhook gap (#3197). Zero import-level consumers existed for all
22+
three schemas across objectstack / cloud / objectui (three-repo scan in #4579).
23+
24+
Migration (FROM → TO):
25+
26+
- `openApi31` in a `RestServerConfig` value (REST plugin constructor /
27+
`plugin-hono-server` `restConfig`) → **delete the key**. There is no
28+
replacement: nothing ever read it, so removing it changes no served
29+
document. The key is tombstoned, not silently stripped —
30+
`RestServerConfigSchema` is not `.strict()`, so a `retiredKey()` tombstone
31+
makes authoring it a `tsc` error and a parse error carrying this
32+
prescription.
33+
- `import { OpenApi31Extensions(Schema), Callback(Schema), OpenApiWebhookEvent(Schema) } from '@objectstack/spec/api'`
34+
**no replacement export** (TS2305 after upgrade). For a real outbound
35+
webhook use `Webhook` from `@objectstack/spec/automation`; for connector
36+
webhook events use `WebhookEvent` from `@objectstack/spec/integration`.
37+
(`OpenApiWebhookEvent(Schema)` was the #4572 rename of `./api`'s
38+
`WebhookEvent(Schema)`; this removal absorbs that rename — pre-16 imports of
39+
the bare name land here too.)
40+
- Config-driven OpenAPI 3.1 webhooks/callbacks documentation is a **new
41+
capability**: if it is ever needed it returns via the enforce route of
42+
ADR-0049, through a new ADR — not by re-declaring inert keys.
43+
44+
The retirement kit: `retiredKey()` tombstone on the non-strict schema (parse +
45+
`tsc` both audible); ADR-0087 D3 semantic migration
46+
`rest-server-openapi31-block-removed` (plugin TS config is never a
47+
`sys_metadata` shape — the stack tree's `api` block declares only its four
48+
scoping/auth knobs — so there is no stored row or stack source for a D2
49+
conversion to rewrite); baselines (`authorable-surface.json` [RETIRED] line,
50+
`json-schema.manifest.json` def removals, `api-surface.json`) regenerated
51+
deliberately; compiler-API export pin + sabotage-verified tombstone tests.
52+
53+
No runtime behaviour changes — that impossibility is the reason for the
54+
removal: the served `/openapi.json` is byte-identical before and after.

.changeset/webhook-dual-source-api-side.md

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -28,14 +28,19 @@ import-statement-level consumer scan: framework, cloud, objectui):
2828
`registrationEndpoint` / `enabled`).
2929
- **Renamed** `WebhookEventSchema` / `WebhookEvent` in `@objectstack/spec/api`
3030
`OpenApiWebhookEventSchema` / `OpenApiWebhookEvent` (same shape, rename
31-
only; joins the existing `OpenApi*` family). `OpenApi31ExtensionsSchema.webhooks`
32-
now references the renamed schema — its parsed/authored shape is unchanged.
31+
only; joins the existing `OpenApi*` family).
32+
**Superseded in the same major (#4579, ADR-0049):** the renamed pair was
33+
then removed outright with the whole inert `RestServerConfig.openApi31`
34+
block, so the rename never ships as a landing spot — see the
35+
`rest-server-openapi31-block-removed` changeset.
3336
- FROM `import { WebhookEvent } from '@objectstack/spec/api'`
34-
TO `import { OpenApiWebhookEvent } from '@objectstack/spec/api'` (if you
35-
meant the OpenAPI 3.1 webhook descriptor), or
36-
`import { WebhookEvent } from '@objectstack/spec/integration'` (if you
37-
meant the connector event enum — check which shape you actually consume:
38-
object vs string enum).
37+
TO: no `./api` replacement exists (the OpenAPI 3.1 webhook descriptor was
38+
removed in #4579 — nothing ever rendered it into the served
39+
`/openapi.json`). Use
40+
`import { WebhookEvent } from '@objectstack/spec/integration'` if you
41+
meant the connector event enum (check which shape you actually consume:
42+
object vs string enum), or `Webhook` from `@objectstack/spec/automation`
43+
for a real outbound webhook.
3944
- `@objectstack/spec/integration`'s `WebhookConfig(Schema)` /
4045
`WebhookEvent(Schema)` are **unchanged** and are now the sole owners of the
4146
bare names. Imports from `./integration` need no migration.

content/docs/references/api/rest-server.mdx

Lines changed: 3 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,8 @@ Architecture alignment:
3838
## TypeScript Usage
3939

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

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

6161

62-
---
63-
64-
## Callback
65-
66-
### Properties
67-
68-
| Property | Type | Required | Description |
69-
| :--- | :--- | :--- | :--- |
70-
| **name** | `string` || Callback identifier (snake_case) |
71-
| **expression** | `string` || Runtime expression (e.g., `{$request.body#/callbackUrl}`) |
72-
| **method** | `Enum<'GET' \| 'POST' \| 'PUT' \| 'DELETE' \| 'PATCH' \| 'HEAD' \| 'OPTIONS'>` || HTTP method for callback request |
73-
| **url** | `string` || Callback URL template with runtime expressions |
74-
75-
7662
---
7763

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

161147

162-
---
163-
164-
## OpenApi31Extensions
165-
166-
### Properties
167-
168-
| Property | Type | Required | Description |
169-
| :--- | :--- | :--- | :--- |
170-
| **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) |
171-
| **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) |
172-
| **jsonSchemaDialect** | `string` || JSON Schema dialect for schema definitions |
173-
| **pathItemReferences** | `boolean` || Allow $ref in path items (OpenAPI 3.1 feature) |
174-
175-
176-
---
177-
178-
## OpenApiWebhookEvent
179-
180-
### Properties
181-
182-
| Property | Type | Required | Description |
183-
| :--- | :--- | :--- | :--- |
184-
| **name** | `string` || Webhook event identifier (snake_case) |
185-
| **description** | `string` || Human-readable event description |
186-
| **method** | `Enum<'GET' \| 'POST' \| 'PUT' \| 'DELETE' \| 'PATCH' \| 'HEAD' \| 'OPTIONS'>` || HTTP method for webhook delivery |
187-
| **payloadSchema** | `string` || JSON Schema $ref for the webhook payload |
188-
| **headers** | `Record<string, string>` | optional | Custom headers to include in webhook delivery |
189-
| **security** | `Enum<'hmac_sha256' \| 'basic' \| 'bearer' \| 'api_key'>[]` || Supported authentication methods for webhook verification |
190-
191-
192148
---
193149

194150
## RestApiConfig
@@ -226,7 +182,7 @@ const result = BatchEndpointsConfigSchema.parse(data);
226182
| **metadata** | `{ prefix: string; enableCache: boolean; cacheTtl: integer; endpoints?: object }` | optional | Metadata endpoints configuration |
227183
| **batch** | `{ maxBatchSize: integer; enableBatchEndpoint: boolean; operations?: object; defaultAtomic: boolean }` | optional | Batch endpoints configuration |
228184
| **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 |
229-
| **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 |
185+
| **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`. |
230186

231187

232188
---

docs/protocol-upgrade-guide.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,8 @@ The same enforce-or-remove pass reaches the event vocabulary: `DataEventType` dr
178178

179179
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.
180180

181+
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.
182+
181183
### Mechanical (applied for you)
182184

183185
| Conversion | Surface | Change | Load window |
@@ -266,6 +268,9 @@ The object capability block closes out the same ADR-0049 pass: `enable.trash` an
266268
- **`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
267269
- 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.
268270
- 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.
271+
- **`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)
272+
- 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.
273+
- 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.
269274

270275
---
271276

packages/spec/api-surface.json

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2472,8 +2472,6 @@
24722472
"CacheInvalidationResponse (type)",
24732473
"CacheInvalidationResponseSchema (const)",
24742474
"CacheInvalidationTarget (type)",
2475-
"Callback (type)",
2476-
"CallbackSchema (const)",
24772475
"CheckPermissionRequest (type)",
24782476
"CheckPermissionRequestSchema (const)",
24792477
"CheckPermissionResponse (type)",
@@ -2927,8 +2925,6 @@
29272925
"ObjectDefinitionResponseSchema (const)",
29282926
"ObjectQLReference (type)",
29292927
"ObjectQLReferenceSchema (const)",
2930-
"OpenApi31Extensions (type)",
2931-
"OpenApi31ExtensionsSchema (const)",
29322928
"OpenApiGenerationConfig (type)",
29332929
"OpenApiGenerationConfigInput (type)",
29342930
"OpenApiGenerationConfigSchema (const)",
@@ -2938,8 +2934,6 @@
29382934
"OpenApiServerSchema (const)",
29392935
"OpenApiSpec (type)",
29402936
"OpenApiSpecSchema (const)",
2941-
"OpenApiWebhookEvent (type)",
2942-
"OpenApiWebhookEventSchema (const)",
29432937
"OperatorMapping (type)",
29442938
"OperatorMappingSchema (const)",
29452939
"PackageApiContracts (const)",

0 commit comments

Comments
 (0)