Skip to content

Commit 4c9222a

Browse files
committed
feat(spec)!: resolve the WebhookConfig/WebhookEvent dual source — ./api pair removed/renamed, ./integration keeps the bare names (#4572)
The four #4535-C1 baseline rows were the #4411 trap in cross-form: ./api's WebhookEventSchema was a z.object (OpenAPI 3.1 webhook definition) while ./integration's is a z.enum of connector event types — same names, two concepts, and which one you got depended only on the import path. Import-statement-level scan across framework, cloud and objectui: zero external consumers on either side (each pair's only importer is its own unit test; cloud and objectui reference neither name). - api WebhookConfig(Schema): DEAD — wired into nothing, not even RestServerConfigSchema; no runtime reads a REST webhook config. Deleted (major window). Its authorable-surface lines deleted by hand per the #4458/#4568 precedent (plugin-config type, not authorable metadata — no tombstone, no D2 conversion). - api WebhookEvent(Schema): renamed OpenApiWebhookEvent(Schema) — it is the OpenAPI 3.1 top-level `webhooks` descriptor and now sits in the existing OpenApi* family; OpenApi31ExtensionsSchema wiring updated, authored shape unchanged. - integration WebhookConfig/WebhookEvent: untouched, now sole owners of the bare names, so the pair stays one coherent family in one domain. dual-source-exports.baseline.json: exactly the 4 named rows removed (35 → 31). json-schema.manifest: api/WebhookConfig retired, api/WebhookEvent → api/OpenApiWebhookEvent (deliberate rename, not a silent drop). Changeset: @objectstack/spec major with FROM → TO lines. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01M9uWvoEp9CoLzYjNExj9sL
1 parent dab35e0 commit 4c9222a

6 files changed

Lines changed: 80 additions & 84 deletions

File tree

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
---
2+
"@objectstack/spec": major
3+
---
4+
5+
feat(spec)!: `@objectstack/spec/api` no longer exports the bare names `WebhookConfig` / `WebhookEvent` — they belong to `./integration` alone (#4572)
6+
7+
The names `WebhookConfig(Schema)` / `WebhookEvent(Schema)` resolved to **two
8+
different declarations** depending on the import path (`./api` vs
9+
`./integration`) — the #4411 dual-source trap, and a cross-form one:
10+
`./api`'s `WebhookEventSchema` was a `z.object` (an OpenAPI 3.1 webhook
11+
*definition* descriptor: `name`/`description`/`method`/`payloadSchema`/
12+
`security`) while `./integration`'s is a `z.enum` of connector event types
13+
(`'record.created'``'rate_limit.exceeded'`). Auto-importing the wrong side
14+
compiled and validated the wrong contract. Resolution (three-repo,
15+
import-statement-level consumer scan: framework, cloud, objectui):
16+
17+
- **Removed** `WebhookConfigSchema` / `WebhookConfig` from
18+
`@objectstack/spec/api`. This pair was dead: wired into nothing — not even
19+
`RestServerConfigSchema` — with zero import-level consumers in all three
20+
repos, and no runtime ever read a REST-server webhook config.
21+
- FROM `import { WebhookConfig } from '@objectstack/spec/api'`
22+
TO: no replacement exists for a REST-server webhook config (it never had a
23+
runtime). For a real outbound webhook use `Webhook` from
24+
`@objectstack/spec/automation`; for a connector webhook use
25+
`WebhookConfig` from `@objectstack/spec/integration` (a **different
26+
shape**: it extends the canonical automation `WebhookSchema` with
27+
`events` / `signatureAlgorithm`, and has no `deliveryConfig` /
28+
`registrationEndpoint` / `enabled`).
29+
- **Renamed** `WebhookEventSchema` / `WebhookEvent` in `@objectstack/spec/api`
30+
`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.
33+
- 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).
39+
- `@objectstack/spec/integration`'s `WebhookConfig(Schema)` /
40+
`WebhookEvent(Schema)` are **unchanged** and are now the sole owners of the
41+
bare names. Imports from `./integration` need no migration.
42+
43+
`dual-source-exports.baseline.json` shrinks by exactly these 4 rows (35 → 31,
44+
#4535 C1).

packages/spec/authorable-surface.json

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1625,6 +1625,12 @@
16251625
"api/OpenApiSpec:security",
16261626
"api/OpenApiSpec:servers",
16271627
"api/OpenApiSpec:tags",
1628+
"api/OpenApiWebhookEvent:description",
1629+
"api/OpenApiWebhookEvent:headers",
1630+
"api/OpenApiWebhookEvent:method",
1631+
"api/OpenApiWebhookEvent:name",
1632+
"api/OpenApiWebhookEvent:payloadSchema",
1633+
"api/OpenApiWebhookEvent:security",
16281634
"api/OperatorMapping:odata",
16291635
"api/OperatorMapping:operator",
16301636
"api/OperatorMapping:rest",
@@ -2098,16 +2104,6 @@
20982104
"api/WebSocketServerConfig:path",
20992105
"api/WebSocketServerConfig:presence",
21002106
"api/WebSocketServerConfig:reconnectAttempts",
2101-
"api/WebhookConfig:deliveryConfig",
2102-
"api/WebhookConfig:enabled",
2103-
"api/WebhookConfig:events",
2104-
"api/WebhookConfig:registrationEndpoint",
2105-
"api/WebhookEvent:description",
2106-
"api/WebhookEvent:headers",
2107-
"api/WebhookEvent:method",
2108-
"api/WebhookEvent:name",
2109-
"api/WebhookEvent:payloadSchema",
2110-
"api/WebhookEvent:security",
21112107
"api/WellKnownCapabilities:automation",
21122108
"api/WellKnownCapabilities:chunkedUpload",
21132109
"api/WellKnownCapabilities:comments",

packages/spec/dual-source-exports.baseline.json

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,6 @@
3131
"Session — [./api (type)] ≠ [./identity (type)]",
3232
"SessionSchema — [./api (const)] ≠ [./identity (const)]",
3333
"TenantPlan — [./cloud (type)] ≠ [./system (type)]",
34-
"TenantPlanSchema — [./cloud (const)] ≠ [./system (const)]",
35-
"WebhookConfig — [./api (type)] ≠ [./integration (type)]",
36-
"WebhookConfigSchema — [./api (const)] ≠ [./integration (const)]",
37-
"WebhookEvent — [./api (type)] ≠ [./integration (type)]",
38-
"WebhookEventSchema — [./api (const)] ≠ [./integration (const)]"
34+
"TenantPlanSchema — [./cloud (const)] ≠ [./system (const)]"
3935
]
4036
}

packages/spec/json-schema.manifest.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -366,6 +366,7 @@
366366
"api/OpenApiSecurityScheme",
367367
"api/OpenApiServer",
368368
"api/OpenApiSpec",
369+
"api/OpenApiWebhookEvent",
369370
"api/OperatorMapping",
370371
"api/PackageApiErrorCode",
371372
"api/PackageInstallRequest",
@@ -492,8 +493,6 @@
492493
"api/WebSocketMessageType",
493494
"api/WebSocketPresenceStatus",
494495
"api/WebSocketServerConfig",
495-
"api/WebhookConfig",
496-
"api/WebhookEvent",
497496
"api/WellKnownCapabilities",
498497
"automation/ActionCategory",
499498
"automation/ActionDescriptor",

packages/spec/src/api/rest-server.test.ts

Lines changed: 17 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,7 @@ import {
1212
EndpointRegistrySchema,
1313
RestApiConfig,
1414
RestServerConfig,
15-
WebhookEventSchema,
16-
WebhookConfigSchema,
15+
OpenApiWebhookEventSchema,
1716
CallbackSchema,
1817
OpenApi31ExtensionsSchema,
1918
type RestApiConfig as RestApiConfigType,
@@ -656,9 +655,9 @@ describe('Integration Tests', () => {
656655
// OpenAPI 3.1 Webhooks & Callbacks Tests
657656
// ==========================================
658657

659-
describe('WebhookEventSchema', () => {
658+
describe('OpenApiWebhookEventSchema', () => {
660659
it('should accept valid webhook event', () => {
661-
const event = WebhookEventSchema.parse({
660+
const event = OpenApiWebhookEventSchema.parse({
662661
name: 'record_created',
663662
description: 'Fired when a record is created',
664663
payloadSchema: '#/components/schemas/RecordCreated',
@@ -671,7 +670,7 @@ describe('WebhookEventSchema', () => {
671670
});
672671

673672
it('should enforce snake_case name', () => {
674-
expect(() => WebhookEventSchema.parse({
673+
expect(() => OpenApiWebhookEventSchema.parse({
675674
name: 'RecordCreated',
676675
description: 'Bad name',
677676
payloadSchema: '#/ref',
@@ -680,7 +679,7 @@ describe('WebhookEventSchema', () => {
680679
});
681680

682681
it('should accept event with custom headers', () => {
683-
const event = WebhookEventSchema.parse({
682+
const event = OpenApiWebhookEventSchema.parse({
684683
name: 'sync_completed',
685684
description: 'Sync finished',
686685
payloadSchema: '#/ref',
@@ -693,52 +692,26 @@ describe('WebhookEventSchema', () => {
693692

694693
it('should accept all security methods', () => {
695694
const methods = ['hmac_sha256', 'basic', 'bearer', 'api_key'] as const;
696-
const event = WebhookEventSchema.parse({
695+
const event = OpenApiWebhookEventSchema.parse({
697696
name: 'test_event',
698697
description: 'Test',
699698
payloadSchema: '#/ref',
700699
security: [...methods],
701700
});
702701
expect(event.security).toHaveLength(4);
703702
});
704-
});
705-
706-
describe('WebhookConfigSchema', () => {
707-
it('should accept config with defaults', () => {
708-
const config = WebhookConfigSchema.parse({
709-
events: [
710-
{
711-
name: 'record_created',
712-
description: 'Record created',
713-
payloadSchema: '#/ref',
714-
security: ['hmac_sha256'],
715-
},
716-
],
717-
deliveryConfig: {},
718-
});
719-
720-
expect(config.enabled).toBe(false);
721-
expect(config.deliveryConfig.maxRetries).toBe(3);
722-
expect(config.deliveryConfig.timeoutMs).toBe(30000);
723-
expect(config.deliveryConfig.signatureHeader).toBe('X-Signature-256');
724-
expect(config.registrationEndpoint).toBe('/webhooks');
725-
});
726-
727-
it('should accept full delivery config', () => {
728-
const config = WebhookConfigSchema.parse({
729-
enabled: true,
730-
events: [],
731-
deliveryConfig: {
732-
maxRetries: 5,
733-
retryIntervalMs: 10000,
734-
timeoutMs: 60000,
735-
signatureHeader: 'X-Hub-Signature',
736-
},
737-
registrationEndpoint: '/hooks',
738-
});
739703

740-
expect(config.deliveryConfig.maxRetries).toBe(5);
741-
expect(config.registrationEndpoint).toBe('/hooks');
704+
// v17 dual-source cleanup (#4572): the bare names WebhookEvent(Schema) /
705+
// WebhookConfig(Schema) now belong to @objectstack/spec/integration alone
706+
// (connector event enum + connector webhook config). The ./api pair was the
707+
// #4411-style trap: same names, different concepts, different forms
708+
// (z.object here vs z.enum there). WebhookConfig(Schema) on ./api was dead
709+
// — wired into nothing (not even RestServerConfigSchema) — and was removed
710+
// rather than renamed. Pin: ./api no longer exports the bare names.
711+
it('does not re-expose the bare WebhookEvent/WebhookConfig names from ./api', async () => {
712+
const api = await import('./index');
713+
expect('WebhookEventSchema' in api).toBe(false);
714+
expect('WebhookConfigSchema' in api).toBe(false);
742715
});
743716
});
744717

packages/spec/src/api/rest-server.zod.ts

Lines changed: 11 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -399,10 +399,16 @@ export type RouteGenerationConfigInput = z.input<typeof RouteGenerationConfigSch
399399
// ==========================================
400400

401401
/**
402-
* Webhook Event Schema
403-
* Defines an event that can trigger a webhook delivery
402+
* OpenAPI Webhook Event Schema
403+
* Defines a webhook event as declared in an OpenAPI 3.1 document's top-level
404+
* `webhooks` section — an API-documentation descriptor, NOT a runtime
405+
* subscription. (Renamed from `WebhookEventSchema` in v17 — that bare name
406+
* collided with the connector event enum in `@objectstack/spec/integration`,
407+
* a different concept AND a different form: z.object here vs z.enum there.
408+
* The outbound webhook an author configures is `WebhookSchema` in
409+
* `@objectstack/spec/automation`.)
404410
*/
405-
export const WebhookEventSchema = lazySchema(() => z.object({
411+
export const OpenApiWebhookEventSchema = lazySchema(() => z.object({
406412
name: z.string().regex(/^[a-z_][a-z0-9_]*$/).describe('Webhook event identifier (snake_case)'),
407413
description: z.string().describe('Human-readable event description'),
408414
method: HttpMethod.default('POST').describe('HTTP method for webhook delivery'),
@@ -413,25 +419,7 @@ export const WebhookEventSchema = lazySchema(() => z.object({
413419
).describe('Supported authentication methods for webhook verification'),
414420
}));
415421

416-
export type WebhookEvent = z.infer<typeof WebhookEventSchema>;
417-
418-
/**
419-
* Webhook Configuration Schema
420-
* Top-level webhook configuration for the REST API
421-
*/
422-
export const WebhookConfigSchema = lazySchema(() => z.object({
423-
enabled: z.boolean().default(false).describe('Enable webhook support'),
424-
events: z.array(WebhookEventSchema).describe('Registered webhook events'),
425-
deliveryConfig: z.object({
426-
maxRetries: z.number().int().default(3).describe('Maximum delivery retry attempts'),
427-
retryIntervalMs: z.number().int().default(5000).describe('Milliseconds between retry attempts'),
428-
timeoutMs: z.number().int().default(30000).describe('Delivery request timeout in milliseconds'),
429-
signatureHeader: z.string().default('X-Signature-256').describe('Header name for webhook signature'),
430-
}).describe('Webhook delivery configuration'),
431-
registrationEndpoint: z.string().default('/webhooks').describe('URL path for webhook registration'),
432-
}));
433-
434-
export type WebhookConfig = z.infer<typeof WebhookConfigSchema>;
422+
export type OpenApiWebhookEvent = z.infer<typeof OpenApiWebhookEventSchema>;
435423

436424
/**
437425
* Callback Schema
@@ -451,7 +439,7 @@ export type Callback = z.infer<typeof CallbackSchema>;
451439
* Extensions specific to OpenAPI 3.1 specification
452440
*/
453441
export const OpenApi31ExtensionsSchema = lazySchema(() => z.object({
454-
webhooks: z.record(z.string(), WebhookEventSchema).optional()
442+
webhooks: z.record(z.string(), OpenApiWebhookEventSchema).optional()
455443
.describe('OpenAPI 3.1 webhooks (top-level webhook definitions)'),
456444
callbacks: z.record(z.string(), z.array(CallbackSchema)).optional()
457445
.describe('OpenAPI 3.1 callbacks (async response definitions)'),

0 commit comments

Comments
 (0)