From e439ab28d42c730bf6426cfb4417fff728dab42f Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 18 Jul 2026 12:44:08 +0000 Subject: [PATCH] docs(spec): annotate schema-only event/subscription/connector enums as not-yet-enforced (#3197) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Per-surface confirmation of the #3197 audit, then option (c) for every surviving row: explicit 'not yet enforced / not yet implemented' notes in the schemas' doc comments and .describe() texts, plus regenerated reference docs. No runtime behavior or schema shape changes. - GraphQLSubscriptionConfigSchema: no subscription transport; HTTP entry serves query/mutation only - websocket.zod.ts module + WebSocketMessageType: no WS server mounted (#2462); future wire contract - RealtimeEventType: zero runtime importers; engine emits data.record.* literals that don't match the enum; field.changed never emitted - connector.zod.ts webhooks/triggers: registerConnector reads only actions; events/trigger defs parse but are never dispatched or polled - trigger-registry.zod.ts ConnectorTriggerSchema/TriggerRegistrySchema: unconsumed; 'stream' exists only here - NotificationChannelSchema + NotificationChannel contract type: implemented channels are inbox/email/sms; others dead-letter; enum says 'in-app' but the registered channel id is 'inbox' SubscriptionEventType (audit row 5) was already removed by the feed-contract retirement (#1959) — nothing left to annotate. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01ToaDWi9wbS2cHNWVyqgkrL --- .changeset/annotate-schema-only-enums.md | 16 +++++++++++++ content/docs/references/api/graphql.mdx | 2 +- content/docs/references/api/realtime.mdx | 2 ++ content/docs/references/api/websocket.mdx | 10 ++++++++ .../docs/references/integration/connector.mdx | 10 ++++---- content/docs/references/integration/misc.mdx | 24 +++++++++---------- .../docs/references/system/notification.mdx | 2 ++ packages/spec/src/api/graphql.zod.ts | 13 ++++++---- packages/spec/src/api/realtime.zod.ts | 8 ++++++- packages/spec/src/api/websocket.zod.ts | 13 ++++++++-- .../src/automation/trigger-registry.zod.ts | 7 +++++- .../src/contracts/notification-service.ts | 5 ++++ .../spec/src/integration/connector.zod.ts | 19 +++++++++++---- packages/spec/src/system/notification.zod.ts | 12 ++++++++-- 14 files changed, 111 insertions(+), 32 deletions(-) create mode 100644 .changeset/annotate-schema-only-enums.md diff --git a/.changeset/annotate-schema-only-enums.md b/.changeset/annotate-schema-only-enums.md new file mode 100644 index 0000000000..ade2f65969 --- /dev/null +++ b/.changeset/annotate-schema-only-enums.md @@ -0,0 +1,16 @@ +--- +"@objectstack/spec": patch +--- + +Annotate the schema-only event/subscription/connector surfaces flagged by the #3197 audit with explicit "not yet enforced / not yet implemented" notes in their doc comments and `.describe()` texts, so authoring metadata against them is no longer silently swallowed. No runtime behavior or schema shape changes — documentation only. + +Surfaces annotated (each trace re-confirmed against the current tree before annotating): + +- `GraphQLSubscriptionConfigSchema` (`api/graphql.zod.ts`) — no subscription transport exists; the GraphQL HTTP entry serves query/mutation only. +- `WebSocketMessageType` + module header (`api/websocket.zod.ts`) — no WebSocket server is mounted (#2462); the protocol is a future wire contract. +- `RealtimeEventType` (`api/realtime.zod.ts`) — zero runtime importers; the engine emits `data.record.*` names (which don't match this enum's members) and nothing emits `field.changed`. +- Connector `webhooks`/`WebhookConfigSchema`/`WebhookEventSchema` and `triggers`/`ConnectorTriggerSchema` (`integration/connector.zod.ts`) — `AutomationEngine.registerConnector` reads only `actions`; webhook events and trigger definitions parse but are never dispatched or polled. +- Automation `ConnectorTriggerSchema`/`TriggerRegistrySchema` (`automation/trigger-registry.zod.ts`) — no runtime importer; the `stream` trigger mechanism exists only here. +- `NotificationChannelSchema` (`system/notification.zod.ts`) + the mirrored `NotificationChannel` contract type — implemented delivery channels are `inbox`/`email`/`sms`; `push`/`slack`/`teams`/`webhook` dead-letter, and the enum's `in-app` does not match the registered `inbox` channel id. + +The audit's sixth row (`SubscriptionEventType`, formerly `data/subscription.zod.ts`) needed no annotation — it was already removed outright by the feed-contract retirement (#1959). diff --git a/content/docs/references/api/graphql.mdx b/content/docs/references/api/graphql.mdx index 4cfffaeab8..d65da659a8 100644 --- a/content/docs/references/api/graphql.mdx +++ b/content/docs/references/api/graphql.mdx @@ -451,7 +451,7 @@ const result = FederationEntity.parse(data); | :--- | :--- | :--- | :--- | | **name** | `string` | ✅ | Subscription field name (camelCase recommended) | | **object** | `string` | ✅ | Source ObjectQL object name | -| **events** | `Enum<'created' \| 'updated' \| 'deleted' \| 'custom'>[]` | ✅ | Events to subscribe to | +| **events** | `Enum<'created' \| 'updated' \| 'deleted' \| 'custom'>[]` | ✅ | Events to subscribe to (not yet implemented — no subscription transport exists; see #3197) | | **description** | `string` | optional | Subscription description | | **filter** | `{ enabled: boolean; fields?: string[] }` | optional | Subscription filtering | | **payload** | `{ includeEntity: boolean; includePreviousValues: boolean; includeMeta: boolean }` | optional | Payload configuration | diff --git a/content/docs/references/api/realtime.mdx b/content/docs/references/api/realtime.mdx index 9d63132416..56e5e55089 100644 --- a/content/docs/references/api/realtime.mdx +++ b/content/docs/references/api/realtime.mdx @@ -58,6 +58,8 @@ const result = RealtimeConfig.parse(data); ## RealtimeEventType +Realtime event type (not yet enforced — the runtime emits data.record.* event names instead, and field.changed is never emitted; see #3197) + ### Allowed Values * `record.created` diff --git a/content/docs/references/api/websocket.mdx b/content/docs/references/api/websocket.mdx index 3775e75a4b..31addeaebb 100644 --- a/content/docs/references/api/websocket.mdx +++ b/content/docs/references/api/websocket.mdx @@ -13,6 +13,16 @@ Supports event subscriptions, filtering, presence tracking, and collaborative ed Industry alignment: Firebase Realtime Database, Socket.IO, Pusher +⚠️ NOT YET SERVED — this protocol is declared but no WebSocket server is + +mounted anywhere in the runtime (#2462, #3197): `IRealtimeService.handleUpgrade` + +is deliberately unimplemented and discovery advertises `websockets: false`. + +These schemas define the future wire contract; nothing consumes them at + +runtime today. + **Source:** `packages/spec/src/api/websocket.zod.ts` diff --git a/content/docs/references/integration/connector.mdx b/content/docs/references/integration/connector.mdx index 06087c43db..d7decca213 100644 --- a/content/docs/references/integration/connector.mdx +++ b/content/docs/references/integration/connector.mdx @@ -181,10 +181,10 @@ Circuit breaker configuration | **providerConfig** | `Record` | optional | Provider-specific config validated by the provider factory at boot (e.g. `{ spec, baseUrl }` for openapi, where spec is an inline document, a package-relative file path like './billing-openapi.json', or an http(s) URL). Requires `provider`. | | **auth** | `{ type: 'none' } \| { type: 'bearer'; credentialRef: string } \| { type: 'api-key'; credentialRef: string; headerName?: string; paramName?: string } \| { type: 'basic'; username: string; credentialRef: string }` | optional | Declarative instance auth — references credentials via `credentialRef` (resolved at boot), never inline secrets. Requires `provider` (ADR-0097). | | **actions** | `{ key: string; label: string; description?: string; inputSchema?: Record; … }[]` | optional | | -| **triggers** | `{ key: string; label: string; description?: string; type: Enum<'polling' \| 'webhook'>; … }[]` | optional | | +| **triggers** | `{ key: string; label: string; description?: string; type: Enum<'polling' \| 'webhook'>; … }[]` | optional | Trigger definitions (not yet enforced — never read at registration; see #3197) | | **syncConfig** | `{ strategy?: Enum<'full' \| 'incremental' \| 'upsert' \| 'append_only'>; direction?: Enum<'import' \| 'export' \| 'bidirectional'>; schedule?: string \| { dialect: Enum<'cel' \| 'js' \| 'cron' \| 'template'>; source?: string; ast?: any; meta?: object }; realtimeSync?: boolean; … }` | optional | Data sync configuration | | **fieldMappings** | `{ source: string; target: string; transform?: { type: 'constant'; value: any } \| { type: 'cast'; targetType: Enum<'string' \| 'number' \| 'boolean' \| 'date'> } \| { type: 'lookup'; table: string; keyField: string; valueField: string } \| { type: 'javascript'; expression: string \| { dialect: Enum<'cel' \| 'js' \| 'cron' \| 'template'>; source?: string; ast?: any; meta?: object } } \| { type: 'map'; mappings: Record }; defaultValue?: any; … }[]` | optional | Field mapping rules | -| **webhooks** | `{ name: string; label?: string; object?: string; triggers?: Enum<'create' \| 'update' \| 'delete'>[]; … }[]` | optional | Webhook configurations | +| **webhooks** | `{ name: string; label?: string; object?: string; triggers?: Enum<'create' \| 'update' \| 'delete'>[]; … }[]` | optional | Webhook configurations (not yet enforced — never read at registration; see #3197) | | **rateLimitConfig** | `{ strategy?: Enum<'fixed_window' \| 'sliding_window' \| 'token_bucket' \| 'leaky_bucket'>; maxRequests: number; windowSeconds: number; burstCapacity?: number; … }` | optional | Rate limiting configuration | | **retryConfig** | `{ strategy?: Enum<'exponential_backoff' \| 'linear_backoff' \| 'fixed_delay' \| 'no_retry'>; maxAttempts?: number; initialDelayMs?: number; maxDelayMs?: number; … }` | optional | Retry configuration | | **connectionTimeoutMs** | `number` | optional | Connection timeout in ms | @@ -307,10 +307,10 @@ Connector type | **providerConfig** | `Record` | optional | Provider-specific config validated by the provider factory at boot (e.g. `{ spec, baseUrl }` for openapi, where spec is an inline document, a package-relative file path like './billing-openapi.json', or an http(s) URL). Requires `provider`. | | **auth** | `{ type: 'none' } \| { type: 'bearer'; credentialRef: string } \| { type: 'api-key'; credentialRef: string; headerName?: string; paramName?: string } \| { type: 'basic'; username: string; credentialRef: string }` | optional | Declarative instance auth — references credentials via `credentialRef` (resolved at boot), never inline secrets. Requires `provider` (ADR-0097). | | **actions** | `{ key: string; label: string; description?: string; inputSchema?: Record; … }[]` | optional | | -| **triggers** | `{ key: string; label: string; description?: string; type: Enum<'polling' \| 'webhook'>; … }[]` | optional | | +| **triggers** | `{ key: string; label: string; description?: string; type: Enum<'polling' \| 'webhook'>; … }[]` | optional | Trigger definitions (not yet enforced — never read at registration; see #3197) | | **syncConfig** | `{ strategy?: Enum<'full' \| 'incremental' \| 'upsert' \| 'append_only'>; direction?: Enum<'import' \| 'export' \| 'bidirectional'>; schedule?: string \| { dialect: Enum<'cel' \| 'js' \| 'cron' \| 'template'>; source?: string; ast?: any; meta?: object }; realtimeSync?: boolean; … }` | optional | Data sync configuration | | **fieldMappings** | `{ source: string; target: string; transform?: { type: 'constant'; value: any } \| { type: 'cast'; targetType: Enum<'string' \| 'number' \| 'boolean' \| 'date'> } \| { type: 'lookup'; table: string; keyField: string; valueField: string } \| { type: 'javascript'; expression: string \| { dialect: Enum<'cel' \| 'js' \| 'cron' \| 'template'>; source?: string; ast?: any; meta?: object } } \| { type: 'map'; mappings: Record }; defaultValue?: any; … }[]` | optional | Field mapping rules | -| **webhooks** | `{ name: string; label?: string; object?: string; triggers?: Enum<'create' \| 'update' \| 'delete'>[]; … }[]` | optional | Webhook configurations | +| **webhooks** | `{ name: string; label?: string; object?: string; triggers?: Enum<'create' \| 'update' \| 'delete'>[]; … }[]` | optional | Webhook configurations (not yet enforced — never read at registration; see #3197) | | **rateLimitConfig** | `{ strategy?: Enum<'fixed_window' \| 'sliding_window' \| 'token_bucket' \| 'leaky_bucket'>; maxRequests: number; windowSeconds: number; burstCapacity?: number; … }` | optional | Rate limiting configuration | | **retryConfig** | `{ strategy?: Enum<'exponential_backoff' \| 'linear_backoff' \| 'fixed_delay' \| 'no_retry'>; maxAttempts?: number; initialDelayMs?: number; maxDelayMs?: number; … }` | optional | Retry configuration | | **connectionTimeoutMs** | `number` | optional | Connection timeout in ms | @@ -480,7 +480,7 @@ Synchronization strategy | **isActive** | `boolean` | ✅ | Whether webhook is active | | **description** | `string` | optional | Webhook description | | **tags** | `string[]` | optional | Tags for organization | -| **events** | `Enum<'record.created' \| 'record.updated' \| 'record.deleted' \| 'sync.started' \| 'sync.completed' \| 'sync.failed' \| 'auth.expired' \| 'rate_limit.exceeded'>[]` | optional | Connector events to subscribe to | +| **events** | `Enum<'record.created' \| 'record.updated' \| 'record.deleted' \| 'sync.started' \| 'sync.completed' \| 'sync.failed' \| 'auth.expired' \| 'rate_limit.exceeded'>[]` | optional | Connector events to subscribe to (not yet enforced — no runtime dispatches these; see #3197) | | **signatureAlgorithm** | `Enum<'hmac_sha256' \| 'hmac_sha512' \| 'none'>` | ✅ | Webhook signature algorithm | diff --git a/content/docs/references/integration/misc.mdx b/content/docs/references/integration/misc.mdx index 52f3a09810..4672885722 100644 --- a/content/docs/references/integration/misc.mdx +++ b/content/docs/references/integration/misc.mdx @@ -97,10 +97,10 @@ Message acknowledgment mode | **providerConfig** | `Record` | optional | Provider-specific config validated by the provider factory at boot (e.g. `{ spec, baseUrl }` for openapi, where spec is an inline document, a package-relative file path like './billing-openapi.json', or an http(s) URL). Requires `provider`. | | **auth** | `{ type: 'none' } \| { type: 'bearer'; credentialRef: string } \| { type: 'api-key'; credentialRef: string; headerName?: string; paramName?: string } \| { type: 'basic'; username: string; credentialRef: string }` | optional | Declarative instance auth — references credentials via `credentialRef` (resolved at boot), never inline secrets. Requires `provider` (ADR-0097). | | **actions** | `{ key: string; label: string; description?: string; inputSchema?: Record; … }[]` | optional | | -| **triggers** | `{ key: string; label: string; description?: string; type: Enum<'polling' \| 'webhook'>; … }[]` | optional | | +| **triggers** | `{ key: string; label: string; description?: string; type: Enum<'polling' \| 'webhook'>; … }[]` | optional | Trigger definitions (not yet enforced — never read at registration; see #3197) | | **syncConfig** | `{ strategy?: Enum<'full' \| 'incremental' \| 'upsert' \| 'append_only'>; direction?: Enum<'import' \| 'export' \| 'bidirectional'>; schedule?: string \| { dialect: Enum<'cel' \| 'js' \| 'cron' \| 'template'>; source?: string; ast?: any; meta?: object }; realtimeSync?: boolean; … }` | optional | Data sync configuration | | **fieldMappings** | `{ source: string; target: string; transform?: { type: 'constant'; value: any } \| { type: 'cast'; targetType: Enum<'string' \| 'number' \| 'boolean' \| 'date'> } \| { type: 'lookup'; table: string; keyField: string; valueField: string } \| { type: 'javascript'; expression: string \| { dialect: Enum<'cel' \| 'js' \| 'cron' \| 'template'>; source?: string; ast?: any; meta?: object } } \| { type: 'map'; mappings: Record }; defaultValue?: any; … }[]` | optional | Field mapping rules | -| **webhooks** | `{ name: string; label?: string; object?: string; triggers?: Enum<'create' \| 'update' \| 'delete'>[]; … }[]` | optional | Webhook configurations | +| **webhooks** | `{ name: string; label?: string; object?: string; triggers?: Enum<'create' \| 'update' \| 'delete'>[]; … }[]` | optional | Webhook configurations (not yet enforced — never read at registration; see #3197) | | **rateLimitConfig** | `{ strategy?: Enum<'fixed_window' \| 'sliding_window' \| 'token_bucket' \| 'leaky_bucket'>; maxRequests: number; windowSeconds: number; burstCapacity?: number; … }` | optional | Rate limiting configuration | | **retryConfig** | `{ strategy?: Enum<'exponential_backoff' \| 'linear_backoff' \| 'fixed_delay' \| 'no_retry'>; maxAttempts?: number; initialDelayMs?: number; maxDelayMs?: number; … }` | optional | Retry configuration | | **connectionTimeoutMs** | `number` | optional | Connection timeout in ms | @@ -304,10 +304,10 @@ File access pattern | **providerConfig** | `Record` | optional | Provider-specific config validated by the provider factory at boot (e.g. `{ spec, baseUrl }` for openapi, where spec is an inline document, a package-relative file path like './billing-openapi.json', or an http(s) URL). Requires `provider`. | | **auth** | `{ type: 'none' } \| { type: 'bearer'; credentialRef: string } \| { type: 'api-key'; credentialRef: string; headerName?: string; paramName?: string } \| { type: 'basic'; username: string; credentialRef: string }` | optional | Declarative instance auth — references credentials via `credentialRef` (resolved at boot), never inline secrets. Requires `provider` (ADR-0097). | | **actions** | `{ key: string; label: string; description?: string; inputSchema?: Record; … }[]` | optional | | -| **triggers** | `{ key: string; label: string; description?: string; type: Enum<'polling' \| 'webhook'>; … }[]` | optional | | +| **triggers** | `{ key: string; label: string; description?: string; type: Enum<'polling' \| 'webhook'>; … }[]` | optional | Trigger definitions (not yet enforced — never read at registration; see #3197) | | **syncConfig** | `{ strategy?: Enum<'full' \| 'incremental' \| 'upsert' \| 'append_only'>; direction?: Enum<'import' \| 'export' \| 'bidirectional'>; schedule?: string \| { dialect: Enum<'cel' \| 'js' \| 'cron' \| 'template'>; source?: string; ast?: any; meta?: object }; realtimeSync?: boolean; … }` | optional | Data sync configuration | | **fieldMappings** | `{ source: string; target: string; transform?: { type: 'constant'; value: any } \| { type: 'cast'; targetType: Enum<'string' \| 'number' \| 'boolean' \| 'date'> } \| { type: 'lookup'; table: string; keyField: string; valueField: string } \| { type: 'javascript'; expression: string \| { dialect: Enum<'cel' \| 'js' \| 'cron' \| 'template'>; source?: string; ast?: any; meta?: object } } \| { type: 'map'; mappings: Record }; defaultValue?: any; … }[]` | optional | Field mapping rules | -| **webhooks** | `{ name: string; label?: string; object?: string; triggers?: Enum<'create' \| 'update' \| 'delete'>[]; … }[]` | optional | Webhook configurations | +| **webhooks** | `{ name: string; label?: string; object?: string; triggers?: Enum<'create' \| 'update' \| 'delete'>[]; … }[]` | optional | Webhook configurations (not yet enforced — never read at registration; see #3197) | | **rateLimitConfig** | `{ strategy?: Enum<'fixed_window' \| 'sliding_window' \| 'token_bucket' \| 'leaky_bucket'>; maxRequests: number; windowSeconds: number; burstCapacity?: number; … }` | optional | Rate limiting configuration | | **retryConfig** | `{ strategy?: Enum<'exponential_backoff' \| 'linear_backoff' \| 'fixed_delay' \| 'no_retry'>; maxAttempts?: number; initialDelayMs?: number; maxDelayMs?: number; … }` | optional | Retry configuration | | **connectionTimeoutMs** | `number` | optional | Connection timeout in ms | @@ -412,10 +412,10 @@ File storage provider type | **providerConfig** | `Record` | optional | Provider-specific config validated by the provider factory at boot (e.g. `{ spec, baseUrl }` for openapi, where spec is an inline document, a package-relative file path like './billing-openapi.json', or an http(s) URL). Requires `provider`. | | **auth** | `{ type: 'none' } \| { type: 'bearer'; credentialRef: string } \| { type: 'api-key'; credentialRef: string; headerName?: string; paramName?: string } \| { type: 'basic'; username: string; credentialRef: string }` | optional | Declarative instance auth — references credentials via `credentialRef` (resolved at boot), never inline secrets. Requires `provider` (ADR-0097). | | **actions** | `{ key: string; label: string; description?: string; inputSchema?: Record; … }[]` | optional | | -| **triggers** | `{ key: string; label: string; description?: string; type: Enum<'polling' \| 'webhook'>; … }[]` | optional | | +| **triggers** | `{ key: string; label: string; description?: string; type: Enum<'polling' \| 'webhook'>; … }[]` | optional | Trigger definitions (not yet enforced — never read at registration; see #3197) | | **syncConfig** | `{ strategy?: Enum<'full' \| 'incremental' \| 'upsert' \| 'append_only'>; direction?: Enum<'import' \| 'export' \| 'bidirectional'>; schedule?: string \| { dialect: Enum<'cel' \| 'js' \| 'cron' \| 'template'>; source?: string; ast?: any; meta?: object }; realtimeSync?: boolean; … }` | optional | Data sync configuration | | **fieldMappings** | `{ source: string; target: string; transform?: { type: 'constant'; value: any } \| { type: 'cast'; targetType: Enum<'string' \| 'number' \| 'boolean' \| 'date'> } \| { type: 'lookup'; table: string; keyField: string; valueField: string } \| { type: 'javascript'; expression: string \| { dialect: Enum<'cel' \| 'js' \| 'cron' \| 'template'>; source?: string; ast?: any; meta?: object } } \| { type: 'map'; mappings: Record }; defaultValue?: any; … }[]` | optional | Field mapping rules | -| **webhooks** | `{ name: string; label?: string; object?: string; triggers?: Enum<'create' \| 'update' \| 'delete'>[]; … }[]` | optional | Webhook configurations | +| **webhooks** | `{ name: string; label?: string; object?: string; triggers?: Enum<'create' \| 'update' \| 'delete'>[]; … }[]` | optional | Webhook configurations (not yet enforced — never read at registration; see #3197) | | **rateLimitConfig** | `{ strategy?: Enum<'fixed_window' \| 'sliding_window' \| 'token_bucket' \| 'leaky_bucket'>; maxRequests: number; windowSeconds: number; burstCapacity?: number; … }` | optional | Rate limiting configuration | | **retryConfig** | `{ strategy?: Enum<'exponential_backoff' \| 'linear_backoff' \| 'fixed_delay' \| 'no_retry'>; maxAttempts?: number; initialDelayMs?: number; maxDelayMs?: number; … }` | optional | Retry configuration | | **connectionTimeoutMs** | `number` | optional | Connection timeout in ms | @@ -545,10 +545,10 @@ GitHub provider type | **providerConfig** | `Record` | optional | Provider-specific config validated by the provider factory at boot (e.g. `{ spec, baseUrl }` for openapi, where spec is an inline document, a package-relative file path like './billing-openapi.json', or an http(s) URL). Requires `provider`. | | **auth** | `{ type: 'none' } \| { type: 'bearer'; credentialRef: string } \| { type: 'api-key'; credentialRef: string; headerName?: string; paramName?: string } \| { type: 'basic'; username: string; credentialRef: string }` | optional | Declarative instance auth — references credentials via `credentialRef` (resolved at boot), never inline secrets. Requires `provider` (ADR-0097). | | **actions** | `{ key: string; label: string; description?: string; inputSchema?: Record; … }[]` | optional | | -| **triggers** | `{ key: string; label: string; description?: string; type: Enum<'polling' \| 'webhook'>; … }[]` | optional | | +| **triggers** | `{ key: string; label: string; description?: string; type: Enum<'polling' \| 'webhook'>; … }[]` | optional | Trigger definitions (not yet enforced — never read at registration; see #3197) | | **syncConfig** | `{ strategy?: Enum<'full' \| 'incremental' \| 'upsert' \| 'append_only'>; direction?: Enum<'import' \| 'export' \| 'bidirectional'>; schedule?: string \| { dialect: Enum<'cel' \| 'js' \| 'cron' \| 'template'>; source?: string; ast?: any; meta?: object }; realtimeSync?: boolean; … }` | optional | Data sync configuration | | **fieldMappings** | `{ source: string; target: string; transform?: { type: 'constant'; value: any } \| { type: 'cast'; targetType: Enum<'string' \| 'number' \| 'boolean' \| 'date'> } \| { type: 'lookup'; table: string; keyField: string; valueField: string } \| { type: 'javascript'; expression: string \| { dialect: Enum<'cel' \| 'js' \| 'cron' \| 'template'>; source?: string; ast?: any; meta?: object } } \| { type: 'map'; mappings: Record }; defaultValue?: any; … }[]` | optional | Field mapping rules | -| **webhooks** | `{ name: string; label?: string; object?: string; triggers?: Enum<'create' \| 'update' \| 'delete'>[]; … }[]` | optional | Webhook configurations | +| **webhooks** | `{ name: string; label?: string; object?: string; triggers?: Enum<'create' \| 'update' \| 'delete'>[]; … }[]` | optional | Webhook configurations (not yet enforced — never read at registration; see #3197) | | **rateLimitConfig** | `{ strategy?: Enum<'fixed_window' \| 'sliding_window' \| 'token_bucket' \| 'leaky_bucket'>; maxRequests: number; windowSeconds: number; burstCapacity?: number; … }` | optional | Rate limiting configuration | | **retryConfig** | `{ strategy?: Enum<'exponential_backoff' \| 'linear_backoff' \| 'fixed_delay' \| 'no_retry'>; maxAttempts?: number; initialDelayMs?: number; maxDelayMs?: number; … }` | optional | Retry configuration | | **connectionTimeoutMs** | `number` | optional | Connection timeout in ms | @@ -606,10 +606,10 @@ GitHub provider type | **providerConfig** | `Record` | optional | Provider-specific config validated by the provider factory at boot (e.g. `{ spec, baseUrl }` for openapi, where spec is an inline document, a package-relative file path like './billing-openapi.json', or an http(s) URL). Requires `provider`. | | **auth** | `{ type: 'none' } \| { type: 'bearer'; credentialRef: string } \| { type: 'api-key'; credentialRef: string; headerName?: string; paramName?: string } \| { type: 'basic'; username: string; credentialRef: string }` | optional | Declarative instance auth — references credentials via `credentialRef` (resolved at boot), never inline secrets. Requires `provider` (ADR-0097). | | **actions** | `{ key: string; label: string; description?: string; inputSchema?: Record; … }[]` | optional | | -| **triggers** | `{ key: string; label: string; description?: string; type: Enum<'polling' \| 'webhook'>; … }[]` | optional | | +| **triggers** | `{ key: string; label: string; description?: string; type: Enum<'polling' \| 'webhook'>; … }[]` | optional | Trigger definitions (not yet enforced — never read at registration; see #3197) | | **syncConfig** | `{ strategy?: Enum<'full' \| 'incremental' \| 'upsert' \| 'append_only'>; direction?: Enum<'import' \| 'export' \| 'bidirectional'>; schedule?: string \| { dialect: Enum<'cel' \| 'js' \| 'cron' \| 'template'>; source?: string; ast?: any; meta?: object }; realtimeSync?: boolean; … }` | optional | Data sync configuration | | **fieldMappings** | `{ source: string; target: string; transform?: { type: 'constant'; value: any } \| { type: 'cast'; targetType: Enum<'string' \| 'number' \| 'boolean' \| 'date'> } \| { type: 'lookup'; table: string; keyField: string; valueField: string } \| { type: 'javascript'; expression: string \| { dialect: Enum<'cel' \| 'js' \| 'cron' \| 'template'>; source?: string; ast?: any; meta?: object } } \| { type: 'map'; mappings: Record }; defaultValue?: any; … }[]` | optional | Field mapping rules | -| **webhooks** | `{ name: string; label?: string; object?: string; triggers?: Enum<'create' \| 'update' \| 'delete'>[]; … }[]` | optional | Webhook configurations | +| **webhooks** | `{ name: string; label?: string; object?: string; triggers?: Enum<'create' \| 'update' \| 'delete'>[]; … }[]` | optional | Webhook configurations (not yet enforced — never read at registration; see #3197) | | **rateLimitConfig** | `{ strategy?: Enum<'fixed_window' \| 'sliding_window' \| 'token_bucket' \| 'leaky_bucket'>; maxRequests: number; windowSeconds: number; burstCapacity?: number; … }` | optional | Rate limiting configuration | | **retryConfig** | `{ strategy?: Enum<'exponential_backoff' \| 'linear_backoff' \| 'fixed_delay' \| 'no_retry'>; maxAttempts?: number; initialDelayMs?: number; maxDelayMs?: number; … }` | optional | Retry configuration | | **connectionTimeoutMs** | `number` | optional | Connection timeout in ms | @@ -742,10 +742,10 @@ SaaS provider type | **providerConfig** | `Record` | optional | Provider-specific config validated by the provider factory at boot (e.g. `{ spec, baseUrl }` for openapi, where spec is an inline document, a package-relative file path like './billing-openapi.json', or an http(s) URL). Requires `provider`. | | **auth** | `{ type: 'none' } \| { type: 'bearer'; credentialRef: string } \| { type: 'api-key'; credentialRef: string; headerName?: string; paramName?: string } \| { type: 'basic'; username: string; credentialRef: string }` | optional | Declarative instance auth — references credentials via `credentialRef` (resolved at boot), never inline secrets. Requires `provider` (ADR-0097). | | **actions** | `{ key: string; label: string; description?: string; inputSchema?: Record; … }[]` | optional | | -| **triggers** | `{ key: string; label: string; description?: string; type: Enum<'polling' \| 'webhook'>; … }[]` | optional | | +| **triggers** | `{ key: string; label: string; description?: string; type: Enum<'polling' \| 'webhook'>; … }[]` | optional | Trigger definitions (not yet enforced — never read at registration; see #3197) | | **syncConfig** | `{ strategy?: Enum<'full' \| 'incremental' \| 'upsert' \| 'append_only'>; direction?: Enum<'import' \| 'export' \| 'bidirectional'>; schedule?: string \| { dialect: Enum<'cel' \| 'js' \| 'cron' \| 'template'>; source?: string; ast?: any; meta?: object }; realtimeSync?: boolean; … }` | optional | Data sync configuration | | **fieldMappings** | `{ source: string; target: string; transform?: { type: 'constant'; value: any } \| { type: 'cast'; targetType: Enum<'string' \| 'number' \| 'boolean' \| 'date'> } \| { type: 'lookup'; table: string; keyField: string; valueField: string } \| { type: 'javascript'; expression: string \| { dialect: Enum<'cel' \| 'js' \| 'cron' \| 'template'>; source?: string; ast?: any; meta?: object } } \| { type: 'map'; mappings: Record }; defaultValue?: any; … }[]` | optional | Field mapping rules | -| **webhooks** | `{ name: string; label?: string; object?: string; triggers?: Enum<'create' \| 'update' \| 'delete'>[]; … }[]` | optional | Webhook configurations | +| **webhooks** | `{ name: string; label?: string; object?: string; triggers?: Enum<'create' \| 'update' \| 'delete'>[]; … }[]` | optional | Webhook configurations (not yet enforced — never read at registration; see #3197) | | **rateLimitConfig** | `{ strategy?: Enum<'fixed_window' \| 'sliding_window' \| 'token_bucket' \| 'leaky_bucket'>; maxRequests: number; windowSeconds: number; burstCapacity?: number; … }` | optional | Rate limiting configuration | | **retryConfig** | `{ strategy?: Enum<'exponential_backoff' \| 'linear_backoff' \| 'fixed_delay' \| 'no_retry'>; maxAttempts?: number; initialDelayMs?: number; maxDelayMs?: number; … }` | optional | Retry configuration | | **connectionTimeoutMs** | `number` | optional | Connection timeout in ms | diff --git a/content/docs/references/system/notification.mdx b/content/docs/references/system/notification.mdx index f064828d25..d4c19ce264 100644 --- a/content/docs/references/system/notification.mdx +++ b/content/docs/references/system/notification.mdx @@ -93,6 +93,8 @@ const result = EmailTemplate.parse(data); ## NotificationChannel +Notification delivery channel (implemented today: inbox, email, sms — push/slack/teams/webhook are not yet implemented and dead-letter; see #3197) + ### Allowed Values * `email` diff --git a/packages/spec/src/api/graphql.zod.ts b/packages/spec/src/api/graphql.zod.ts index 4f1ab583b2..d296d4fc97 100644 --- a/packages/spec/src/api/graphql.zod.ts +++ b/packages/spec/src/api/graphql.zod.ts @@ -326,18 +326,23 @@ export type GraphQLMutationConfigInput = z.input z.object({ /** Subscription name */ name: z.string().describe('Subscription field name (camelCase recommended)'), - + /** Source Object */ object: z.string().describe('Source ObjectQL object name'), - + /** Subscription trigger events */ - events: z.array(z.enum(['created', 'updated', 'deleted', 'custom'])).describe('Events to subscribe to'), + events: z.array(z.enum(['created', 'updated', 'deleted', 'custom'])).describe('Events to subscribe to (not yet implemented — no subscription transport exists; see #3197)'), /** Description */ description: z.string().optional().describe('Subscription description'), diff --git a/packages/spec/src/api/realtime.zod.ts b/packages/spec/src/api/realtime.zod.ts index 411dacfde2..28ae8071d3 100644 --- a/packages/spec/src/api/realtime.zod.ts +++ b/packages/spec/src/api/realtime.zod.ts @@ -23,13 +23,19 @@ export type TransportProtocol = z.infer; /** * Event Type Enum * Types of realtime events that can be subscribed to + * + * ⚠️ NOT YET ENFORCED — declared but has no runtime consumer (#3197). The + * engine publishes realtime events under the `data.record.created/updated/deleted` + * names from `DataEventType` (events.zod.ts), which this enum does not match, + * and nothing ever emits `field.changed`. Subscriptions authored against these + * values are not validated or filtered by any runtime. */ export const RealtimeEventType = z.enum([ 'record.created', 'record.updated', 'record.deleted', 'field.changed', -]); +]).describe('Realtime event type (not yet enforced — the runtime emits data.record.* event names instead, and field.changed is never emitted; see #3197)'); export type RealtimeEventType = z.infer; diff --git a/packages/spec/src/api/websocket.zod.ts b/packages/spec/src/api/websocket.zod.ts index a366197926..74a1f8fc4a 100644 --- a/packages/spec/src/api/websocket.zod.ts +++ b/packages/spec/src/api/websocket.zod.ts @@ -10,11 +10,17 @@ export { PresenceStatus } from './realtime-shared.zod'; /** * WebSocket Event Protocol - * + * * Defines the schema for WebSocket-based real-time communication in ObjectStack. * Supports event subscriptions, filtering, presence tracking, and collaborative editing. - * + * * Industry alignment: Firebase Realtime Database, Socket.IO, Pusher + * + * ⚠️ NOT YET SERVED — this protocol is declared but no WebSocket server is + * mounted anywhere in the runtime (#2462, #3197): `IRealtimeService.handleUpgrade` + * is deliberately unimplemented and discovery advertises `websockets: false`. + * These schemas define the future wire contract; nothing consumes them at + * runtime today. */ // ========================================== @@ -24,6 +30,9 @@ export { PresenceStatus } from './realtime-shared.zod'; /** * WebSocket Message Type Enum * Defines the types of messages that can be sent over WebSocket + * + * ⚠️ NOT YET SERVED — no WebSocket transport is mounted, so no runtime sends + * or dispatches on these message types (see module note above; #3197). */ export const WebSocketMessageType = z.enum([ 'subscribe', // Client subscribes to events diff --git a/packages/spec/src/automation/trigger-registry.zod.ts b/packages/spec/src/automation/trigger-registry.zod.ts index 5043463f90..826ec7546d 100644 --- a/packages/spec/src/automation/trigger-registry.zod.ts +++ b/packages/spec/src/automation/trigger-registry.zod.ts @@ -340,8 +340,13 @@ export type ConnectorOperation = z.infer; /** * Connector Trigger Schema - * + * * Triggers are special operations that watch for events and initiate workflows. + * + * ⚠️ NOT YET ENFORCED — declared but has no runtime consumer (#3197). No + * runtime imports this schema (or `TriggerRegistrySchema` below); in + * particular the `stream` trigger mechanism exists only here and has no + * implementation anywhere. */ export const ConnectorTriggerSchema = lazySchema(() => z.object({ /** diff --git a/packages/spec/src/contracts/notification-service.ts b/packages/spec/src/contracts/notification-service.ts index 557426ddad..ef84edebd6 100644 --- a/packages/spec/src/contracts/notification-service.ts +++ b/packages/spec/src/contracts/notification-service.ts @@ -15,6 +15,11 @@ /** * Supported notification delivery channels + * + * ⚠️ PARTIALLY ENFORCED — mirrors `NotificationChannelSchema` + * (system/notification.zod.ts); the delivery channels actually registered by + * `service-messaging` are `inbox`, `email`, and `sms` only (#3197). Messages + * addressed to an unregistered channel are dead-lettered, not delivered. */ export type NotificationChannel = 'email' | 'sms' | 'push' | 'in-app' | 'slack' | 'teams' | 'webhook'; diff --git a/packages/spec/src/integration/connector.zod.ts b/packages/spec/src/integration/connector.zod.ts index d85d1f6a73..dde723ba26 100644 --- a/packages/spec/src/integration/connector.zod.ts +++ b/packages/spec/src/integration/connector.zod.ts @@ -259,16 +259,21 @@ export type WebhookSignatureAlgorithm = z.infer WebhookSchema.extend({ /** * Events to listen for * Connector-specific events like sync completion, auth expiry, etc. */ - events: z.array(WebhookEventSchema).optional().describe('Connector events to subscribe to'), + events: z.array(WebhookEventSchema).optional().describe('Connector events to subscribe to (not yet enforced — no runtime dispatches these; see #3197)'), /** * Signature algorithm for webhook security @@ -538,6 +543,12 @@ export const ConnectorActionSchema = lazySchema(() => z.object({ /** * Connector Trigger Definition + * + * ⚠️ NOT YET ENFORCED — declared but never read by the runtime (#3197). + * `AutomationEngine.registerConnector` ignores a connector's `triggers`; the + * only runtime touch is the authoring-time reject rule that forbids triggers + * on provider-bound declarative instances (ADR-0097 §5). No polling loop or + * webhook receiver is driven by these definitions. */ export const ConnectorTriggerSchema = lazySchema(() => z.object({ key: z.string().describe('Trigger key'), @@ -632,7 +643,7 @@ export const ConnectorSchema = lazySchema(() => z.object({ /** Zapier-style Capabilities */ actions: z.array(ConnectorActionSchema).optional(), - triggers: z.array(ConnectorTriggerSchema).optional(), + triggers: z.array(ConnectorTriggerSchema).optional().describe('Trigger definitions (not yet enforced — never read at registration; see #3197)'), /** * Data synchronization configuration @@ -648,7 +659,7 @@ export const ConnectorSchema = lazySchema(() => z.object({ /** * Webhook configuration */ - webhooks: z.array(WebhookConfigSchema).optional().describe('Webhook configurations'), + webhooks: z.array(WebhookConfigSchema).optional().describe('Webhook configurations (not yet enforced — never read at registration; see #3197)'), /** * Rate limiting configuration diff --git a/packages/spec/src/system/notification.zod.ts b/packages/spec/src/system/notification.zod.ts index a380f1fd39..9c8c6b97ec 100644 --- a/packages/spec/src/system/notification.zod.ts +++ b/packages/spec/src/system/notification.zod.ts @@ -211,8 +211,16 @@ export const InAppNotificationSchema = lazySchema(() => z.object({ /** * Notification Channel Enum - * + * * Supported notification delivery channels. + * + * ⚠️ PARTIALLY ENFORCED — the delivery channels actually registered by + * `service-messaging` are `inbox`, `email`, and `sms` (#3197). `push`, + * `slack`, `teams`, and `webhook` have no delivery implementation, and the + * dispatcher dead-letters any message addressed to an unregistered channel. + * Note also the naming drift: this enum says `in-app` while the implemented + * channel registers as `inbox` (which this enum does not contain) — + * reconcile before wiring this enum into the runtime. */ export const NotificationChannelSchema = lazySchema(() => z.enum([ 'email', @@ -222,7 +230,7 @@ export const NotificationChannelSchema = lazySchema(() => z.enum([ 'slack', 'teams', 'webhook', -])); +]).describe('Notification delivery channel (implemented today: inbox, email, sms — push/slack/teams/webhook are not yet implemented and dead-letter; see #3197)')); /** * Notification Configuration Schema