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