diff --git a/.changeset/trigger-registry-connector-cluster-removed.md b/.changeset/trigger-registry-connector-cluster-removed.md new file mode 100644 index 0000000000..5b77d57d5f --- /dev/null +++ b/.changeset/trigger-registry-connector-cluster-removed.md @@ -0,0 +1,45 @@ +--- +'@objectstack/spec': major +--- + +The `trigger-registry.zod.ts` Connector cluster is removed (#4499) + +`@objectstack/spec/automation` no longer exports the third declaration of the +connector vocabulary: `ConnectorSchema`, `ConnectorInstanceSchema`, +`ConnectorOperationSchema`, `ConnectorTriggerSchema`, `ConnectorCategorySchema`, +`AuthenticationSchema` / `AuthenticationTypeSchema` / `AuthFieldSchema` / +`OAuth2ConfigSchema`, `OperationTypeSchema` / `OperationParameterSchema`, their +inferred types, and the `Connector.apiKey()` / `Connector.oauth2()` factory +helpers — 630 lines, all of `automation/trigger-registry.zod.ts`. + +Despite the filename, the file contained no trigger registry. Every export was +connector vocabulary, self-contained and read by nothing: + +- the automation engine registers and validates connectors against + `ConnectorSchema` from `integration/connector.zod.ts` (ADR-0097) — it never + imported this one; +- the stack `connectors:` collection parses `DeclarativeConnectorEntrySchema`; +- outside the spec package, the only references anywhere in the monorepo were + the two documentation generators that published it. + +This closes the connector triple-declaration: `integration/connector.zod.ts` +is the one live contract (ADR-0097), the six per-provider "templates" fell in +#4480, and this cluster is the last copy (Prime Directive #12 — one +capability, one contract). + +**Migration.** If you imported any of these names from +`@objectstack/spec/automation`, there is nothing to migrate *to* on that +module: nothing ever consumed what you built against them. Declare real +connector instances with `defineConnector` / the stack `connectors:` collection +(`DeclarativeConnectorEntrySchema`), or materialize them from a provider +document via connector-openapi / connector-mcp. Note the name collision when +migrating types: the live `integration/connector.zod.ts` also exports a +`ConnectorTriggerSchema` and a `Connector` type with *different shapes* — a +find-and-replace of the import path is not a migration. + +The removal also deletes the "When to use Integration Connector vs. Trigger +Registry?" comparison from `integration/connector.zod.ts`'s header, which +steered "lightweight" cases to the dead file with the platform's authority — +the same defect class as the `capabilities.readOnly` prescription #4487 +corrected. No D2 conversion: none of this was storable stack metadata, so +there is no source for `os migrate meta` to rewrite. diff --git a/content/docs/getting-started/quick-reference.mdx b/content/docs/getting-started/quick-reference.mdx index 603d216b20..de9833bb52 100644 --- a/content/docs/getting-started/quick-reference.mdx +++ b/content/docs/getting-started/quick-reference.mdx @@ -157,7 +157,6 @@ Flows, state machines, approvals, and integrations. | **[State Machine](/docs/references/automation/state-machine)** | `state-machine.zod.ts` | StateMachine | State machine definitions | | **[Webhook](/docs/references/automation/webhook)** | `webhook.zod.ts` | Webhook | Outbound webhooks | | **[ETL](/docs/references/automation/etl)** | `etl.zod.ts` | ETLPipeline | Data transformation pipelines | -| **[Trigger Registry](/docs/references/automation/trigger-registry)** | `trigger-registry.zod.ts` | TriggerRegistry | Event-driven triggers | | **[Sync](/docs/references/automation/sync)** | `sync.zod.ts` | DataSyncConfig, SyncMode | Bi-directional data sync | ## Security Protocol (3 schemas) diff --git a/content/docs/references/automation/connector.mdx b/content/docs/references/automation/connector.mdx index c86ead52b8..bfa05440a7 100644 --- a/content/docs/references/automation/connector.mdx +++ b/content/docs/references/automation/connector.mdx @@ -8,58 +8,13 @@ description: Connector protocol schemas ## TypeScript Usage ```typescript -import { Connector, ConnectorTrigger, DataSyncConfig } from '@objectstack/spec/automation'; -import type { Connector, ConnectorTrigger, DataSyncConfig } from '@objectstack/spec/automation'; +import { DataSyncConfig } from '@objectstack/spec/automation'; +import type { DataSyncConfig } from '@objectstack/spec/automation'; // Validate data -const result = Connector.parse(data); +const result = DataSyncConfig.parse(data); ``` ---- - -## Connector - -### Properties - -| Property | Type | Required | Description | -| :--- | :--- | :--- | :--- | -| **id** | `string` | ✅ | Connector ID (snake_case) | -| **name** | `string` | ✅ | Connector name | -| **description** | `string` | optional | Connector description | -| **version** | `string` | optional | Connector version | -| **icon** | `string` | optional | Connector icon | -| **category** | `Enum<'crm' \| 'payment' \| 'communication' \| 'storage' \| 'analytics' \| 'database' \| 'marketing' \| 'accounting' \| 'hr' \| 'productivity' \| 'ecommerce' \| 'support' \| 'devtools' \| 'social' \| 'other'>` | ✅ | Connector category | -| **baseUrl** | `string` | optional | API base URL | -| **authentication** | `{ type: Enum<'none' \| 'apiKey' \| 'basic' \| 'bearer' \| 'oauth1' \| 'oauth2' \| 'custom'>; fields?: { name: string; label: string; type: Enum<'text' \| 'password' \| 'url' \| 'select'>; description?: string; … }[]; oauth2?: object; test?: object }` | ✅ | Authentication config | -| **operations** | `{ id: string; name: string; description?: string; type: Enum<'read' \| 'write' \| 'delete' \| 'search' \| 'trigger' \| 'action'>; … }[]` | optional | Connector operations | -| **triggers** | `{ id: string; name: string; description?: string; type: Enum<'webhook' \| 'polling' \| 'stream'>; … }[]` | optional | Connector triggers | -| **rateLimit** | `{ requestsPerSecond?: number; requestsPerMinute?: number; requestsPerHour?: number }` | optional | Rate limiting | -| **author** | `string` | optional | Connector author | -| **documentation** | `string` | optional | Documentation URL | -| **homepage** | `string` | optional | Homepage URL | -| **license** | `string` | optional | License (SPDX identifier) | -| **tags** | `string[]` | optional | Connector tags | -| **verified** | `boolean` | ✅ | Verified connector | -| **metadata** | `Record` | optional | Custom metadata | - - ---- - -## ConnectorTrigger - -### Properties - -| Property | Type | Required | Description | -| :--- | :--- | :--- | :--- | -| **id** | `string` | ✅ | Trigger ID (snake_case) | -| **name** | `string` | ✅ | Trigger name | -| **description** | `string` | optional | Trigger description | -| **type** | `Enum<'webhook' \| 'polling' \| 'stream'>` | ✅ | Trigger mechanism | -| **config** | `Record` | optional | Trigger configuration | -| **outputSchema** | `Record` | optional | Event payload schema | -| **pollingIntervalMs** | `integer` | optional | Polling interval in ms | - - --- ## DataSyncConfig diff --git a/content/docs/references/automation/index.mdx b/content/docs/references/automation/index.mdx index fe6cef7bf8..604be92f4b 100644 --- a/content/docs/references/automation/index.mdx +++ b/content/docs/references/automation/index.mdx @@ -20,6 +20,5 @@ This section contains all protocol schemas for the automation layer of ObjectSta - diff --git a/content/docs/references/automation/meta.json b/content/docs/references/automation/meta.json index 63900061b1..1021c8f5b5 100644 --- a/content/docs/references/automation/meta.json +++ b/content/docs/references/automation/meta.json @@ -8,7 +8,6 @@ "node-executor", "state-machine", "time-relative-trigger", - "trigger-registry", "---Integration & Data---", "bpmn-interop", "connector", diff --git a/content/docs/references/automation/trigger-registry.mdx b/content/docs/references/automation/trigger-registry.mdx deleted file mode 100644 index f8230c8d24..0000000000 --- a/content/docs/references/automation/trigger-registry.mdx +++ /dev/null @@ -1,273 +0,0 @@ ---- -title: Trigger Registry -description: Trigger Registry protocol schemas ---- - -{/* ⚠️ AUTO-GENERATED — DO NOT EDIT. Run build-docs.ts to regenerate. Hand-written docs live in the module folders under content/docs/. */} - -Trigger Registry Protocol - -Lightweight automation triggers for simple integrations. - -Inspired by Zapier, n8n, and Workato connector architectures. - -## When to use Trigger Registry vs. Integration Connector? - -**Use `[automation/trigger-registry.zod.ts](/docs/references/automation/trigger-registry)` when:** - -- Building simple automation triggers (e.g., "when Slack message received, create task") - -- No complex authentication needed (simple API keys, basic auth) - -- Lightweight, single-purpose integrations - -- Quick setup with minimal configuration - -- Webhook-based or polling triggers for automation workflows - -**Use `[integration/connector.zod.ts](/docs/references/integration/connector)` when:** - -- Building enterprise-grade connectors (e.g., Salesforce, SAP, Oracle) - -- Complex OAuth2/SAML authentication required - -- Bidirectional sync with field mapping and transformations - -- Webhook management and rate limiting required - -- Full CRUD operations and data synchronization - -## Use Cases - -1. **Simple Automation Triggers** - -- Slack notifications on record updates - -- Twilio SMS on workflow events - -- SendGrid email templates - -2. **Lightweight Operations** - -- Single-action integrations (send, notify, log) - -- No bidirectional sync required - -- Webhook receivers for incoming events - -3. **Quick Integrations** - -- Payment webhooks (Stripe, PayPal) - -- Communication triggers (Twilio, SendGrid, Slack) - -- Simple API calls to third-party services - -See also: https://zapier.com/developer/documentation/v2/ - -See also: https://docs.n8n.io/integrations/creating-nodes/ - -See also: ../../[integration/connector.zod.ts](/docs/references/integration/connector) for enterprise connectors - -@example - -```typescript - -const slackNotifier: Connector = \{ - -id: 'slack_notify', - -name: 'Slack Notification', - -category: 'communication', - -authentication: \{ - -type: 'apiKey', - -fields: [\{ name: 'webhook_url', label: 'Webhook URL', type: 'url' \}] - -\}, - -operations: [ - -\{ id: 'send_message', name: 'Send Message', type: 'action' \} - -] - -\} - -``` - - -**Source:** `packages/spec/src/automation/trigger-registry.zod.ts` - - -## TypeScript Usage - -```typescript -import { AuthField, Authentication, AuthenticationType, ConnectorCategory, ConnectorInstance, ConnectorOperation, OAuth2Config, OperationParameter, OperationType } from '@objectstack/spec/automation'; -import type { AuthField, Authentication, AuthenticationType, ConnectorCategory, ConnectorInstance, ConnectorOperation, OAuth2Config, OperationParameter, OperationType } from '@objectstack/spec/automation'; - -// Validate data -const result = AuthField.parse(data); -``` - ---- - -## AuthField - -### Properties - -| Property | Type | Required | Description | -| :--- | :--- | :--- | :--- | -| **name** | `string` | ✅ | Field name (snake_case) | -| **label** | `string` | ✅ | Field label | -| **type** | `Enum<'text' \| 'password' \| 'url' \| 'select'>` | ✅ | Field type | -| **description** | `string` | optional | Field description | -| **required** | `boolean` | ✅ | Required field | -| **default** | `string` | optional | Default value | -| **options** | `{ label: string; value: string }[]` | optional | Select field options | -| **placeholder** | `string` | optional | Placeholder text | - - ---- - -## Authentication - -### Properties - -| Property | Type | Required | Description | -| :--- | :--- | :--- | :--- | -| **type** | `Enum<'none' \| 'apiKey' \| 'basic' \| 'bearer' \| 'oauth1' \| 'oauth2' \| 'custom'>` | ✅ | Authentication type | -| **fields** | `{ name: string; label: string; type: Enum<'text' \| 'password' \| 'url' \| 'select'>; description?: string; … }[]` | optional | Authentication fields | -| **oauth2** | `{ authorizationUrl: string; tokenUrl: string; scopes?: string[]; clientIdField: string; … }` | optional | OAuth 2.0 configuration | -| **test** | `{ url?: string; method: Enum<'GET' \| 'POST' \| 'PUT' \| 'DELETE'> }` | optional | Authentication test configuration | - - ---- - -## AuthenticationType - -### Allowed Values - -* `none` -* `apiKey` -* `basic` -* `bearer` -* `oauth1` -* `oauth2` -* `custom` - - ---- - -## ConnectorCategory - -### Allowed Values - -* `crm` -* `payment` -* `communication` -* `storage` -* `analytics` -* `database` -* `marketing` -* `accounting` -* `hr` -* `productivity` -* `ecommerce` -* `support` -* `devtools` -* `social` -* `other` - - ---- - -## ConnectorInstance - -### Properties - -| Property | Type | Required | Description | -| :--- | :--- | :--- | :--- | -| **id** | `string` | ✅ | Instance ID | -| **connectorId** | `string` | ✅ | Connector ID | -| **name** | `string` | ✅ | Instance name | -| **description** | `string` | optional | Instance description | -| **credentials** | `Record` | ✅ | Encrypted credentials | -| **config** | `Record` | optional | Additional config | -| **active** | `boolean` | ✅ | Instance active status | -| **createdAt** | `string` | optional | Creation time | -| **lastTestedAt** | `string` | optional | Last test time | -| **testStatus** | `Enum<'unknown' \| 'success' \| 'failed'>` | ✅ | Connection test status | - - ---- - -## ConnectorOperation - -### Properties - -| Property | Type | Required | Description | -| :--- | :--- | :--- | :--- | -| **id** | `string` | ✅ | Operation ID (snake_case) | -| **name** | `string` | ✅ | Operation name | -| **description** | `string` | optional | Operation description | -| **type** | `Enum<'read' \| 'write' \| 'delete' \| 'search' \| 'trigger' \| 'action'>` | ✅ | Operation type | -| **inputSchema** | `{ name: string; label: string; description?: string; type: Enum<'string' \| 'number' \| 'boolean' \| 'array' \| 'object' \| 'date' \| 'file'>; … }[]` | optional | Input parameters | -| **outputSchema** | `Record` | optional | Output schema | -| **sampleOutput** | `any` | optional | Sample output | -| **supportsPagination** | `boolean` | ✅ | Supports pagination | -| **supportsFiltering** | `boolean` | ✅ | Supports filtering | - - ---- - -## OAuth2Config - -### Properties - -| Property | Type | Required | Description | -| :--- | :--- | :--- | :--- | -| **authorizationUrl** | `string` | ✅ | Authorization endpoint URL | -| **tokenUrl** | `string` | ✅ | Token endpoint URL | -| **scopes** | `string[]` | optional | OAuth scopes | -| **clientIdField** | `string` | ✅ | Client ID field name | -| **clientSecretField** | `string` | ✅ | Client secret field name | - - ---- - -## OperationParameter - -### Properties - -| Property | Type | Required | Description | -| :--- | :--- | :--- | :--- | -| **name** | `string` | ✅ | Parameter name | -| **label** | `string` | ✅ | Parameter label | -| **description** | `string` | optional | Parameter description | -| **type** | `Enum<'string' \| 'number' \| 'boolean' \| 'array' \| 'object' \| 'date' \| 'file'>` | ✅ | Parameter type | -| **required** | `boolean` | ✅ | Required parameter | -| **default** | `any` | optional | Default value | -| **validation** | `Record` | optional | Validation rules | -| **dynamicOptions** | `string` | optional | Function to load dynamic options | - - ---- - -## OperationType - -### Allowed Values - -* `read` -* `write` -* `delete` -* `search` -* `trigger` -* `action` - - ---- - diff --git a/content/docs/references/integration/connector.mdx b/content/docs/references/integration/connector.mdx index 7033d9ebec..e9505e0a0f 100644 --- a/content/docs/references/integration/connector.mdx +++ b/content/docs/references/integration/connector.mdx @@ -103,33 +103,27 @@ See also: [../[automation/sync.zod.ts](/docs/references/automation/sync)](/docs/ See also: [../[automation/etl.zod.ts](/docs/references/automation/etl)](/docs/references/automation/etl) for Level 2 (data engineering) -## When to use Integration Connector vs. Trigger Registry? +## There is no "Trigger Registry" alternative -**Use `[integration/connector.zod.ts](/docs/references/integration/connector)` when:** +This header used to carry a "When to use Integration Connector vs. Trigger -- Building enterprise-grade connectors (e.g., Salesforce, SAP, Oracle) +Registry?" comparison, steering "lightweight" cases to -- Complex OAuth2/SAML authentication required +`[automation/trigger-registry.zod.ts](/docs/references/automation/trigger-registry)`. That file was a third declaration of -- Bidirectional sync with field mapping and transformations - -- Webhook management and rate limiting required - -- Full CRUD operations and data synchronization - -- Need comprehensive retry strategies and error handling +the connector vocabulary with zero consumers — nothing registered, validated -**Use `[automation/trigger-registry.zod.ts](/docs/references/automation/trigger-registry)` when:** +or executed against it — so the guidance pointed authors, with the -- Building simple automation triggers (e.g., "when Slack message received, create task") +platform's authority, at a dead end (#4499; removed alongside the #4480 -- No complex authentication needed (simple API keys, basic auth) +per-provider template cluster). The same defect class as the -- Lightweight, single-purpose integrations +`capabilities.readOnly` prescription #4487 corrected: a signpost must land -- Quick setup with minimal configuration +somewhere enforced. Lightweight cases are served HERE — a connector instance -See also: ../../[automation/trigger-registry.zod.ts](/docs/references/automation/trigger-registry) for lightweight automation triggers +with simple `auth` — or by `[automation/sync.zod.ts](/docs/references/automation/sync)` / `etl.zod.ts` below. **Source:** `packages/spec/src/integration/connector.zod.ts` diff --git a/content/docs/releases/v17.mdx b/content/docs/releases/v17.mdx index 6b4a59e7a2..1734ecf7ee 100644 --- a/content/docs/releases/v17.mdx +++ b/content/docs/releases/v17.mdx @@ -1025,6 +1025,7 @@ import or the authored key. | The `workflow` service slot — `CoreServiceName 'workflow'`, `IWorkflowService`, `WorkflowProtocol`, the `Get/WorkflowState/Config/Transition` schema cluster, discovery `routes.workflow` / `services.workflow` / `features.workflow`, the `RestApiRouteCategory 'workflow'` member and the stray `graphql` provider entry | declared end to end and implemented nowhere: nothing ever registered or resolved the slot (ADR-0115 Evidence 5), no method of `WorkflowProtocol` was ever implemented, no host ever mounted `/api/v1/workflow`. State machines are `state_machine` validation rules; approvals are flow nodes (ADR-0019); record-triggered automation is hooks + `record_change` flows (#4451) | | `datasource.readReplicas` | replica connections nothing ever opened — no driver reads the key and no query path splits reads from writes, so every statement went to the primary. #4410 had just taught the schema to validate each entry against the declared driver's contract, which made a dead slot look rigorously alive (#4468) | | The per-provider connector "template" cluster (`@objectstack/spec/integration` — `DatabaseConnectorSchema`, `FileStorageConnectorSchema`, `GitHubConnectorSchema`, `MessageQueueConnectorSchema`, `SaasConnectorSchema`, `VercelConnectorSchema`, their ~100 sub-schema/type/example exports, and the six generated reference pages) | the losing side of a decided architecture fight, left standing: ADR-0023 rejected hand-modelling each external system's shape inside the spec, and the live ADR-0097 protocol gets provider shapes from the provider itself (connector-openapi / connector-mcp materialize at boot). Zero consumers — `engine.registerConnector()` validates against `ConnectorSchema` from `connector.zod.ts` alone, and nothing referenced the six files, not even their own module's live half. `DatabaseConnectorSchema` also declared read-replica routing a *second* time (`readReplicaConfig`, see the row above), down to a `weight` field for a load balancer that does not exist (#4480) | +| The `trigger-registry.zod.ts` Connector cluster (`@objectstack/spec/automation` — `ConnectorSchema`, `ConnectorInstanceSchema`, `ConnectorOperationSchema`, `ConnectorTriggerSchema`, the `Authentication*`/`OAuth2Config`/`Operation*` vocabulary, the `Connector.apiKey()`/`.oauth2()` factory helpers, and the generated reference page) | the *third* declaration of the same business need, and the file never contained what its name promises — no trigger registry, 630 lines of connector vocabulary with zero consumers. The automation engine registers connectors against `integration/connector.zod.ts` (ADR-0097) and the stack `connectors:` collection parses `DeclarativeConnectorEntrySchema`; nothing registered, validated or executed against this copy. Its header even carried a "When to use" comparison steering lightweight cases here — a signpost to a dead end, removed with it (#4499) | | The `kernel` metadata-loader envelope family — `MetadataFormat`, `MetadataStats`, `MetadataLoadOptions`, `MetadataSaveOptions`, `MetadataExportOptions`, `MetadataImportOptions`, `MetadataLoadResult`, `MetadataSaveResult`, `MetadataWatchEvent`, `MetadataCollectionInfo`, `MetadataLoaderContract` (`@objectstack/spec/kernel`) | eleven names that each existed **twice**, with a different shape, on `./kernel` and `./system` — so which type you got depended on your import path. Every consumer imported the `./system` copy; the `./kernel` copies had zero consumers. Import them from `@objectstack/spec/system` (#4411, ADR-0049). `MetadataManagerConfig` / `MetadataFallbackStrategy` are unaffected and still ship from both entries | The Console side follows: `@object-ui/types` drops its diff --git a/docs/audits/2026-07-unknown-key-strictness-ledger.md b/docs/audits/2026-07-unknown-key-strictness-ledger.md index e08d8f5558..d8104abe38 100644 --- a/docs/audits/2026-07-unknown-key-strictness-ledger.md +++ b/docs/audits/2026-07-unknown-key-strictness-ledger.md @@ -194,13 +194,12 @@ tightening (the #4001 "sharing-rule lesson": candidates, not verdicts). | `external-catalog.zod.ts` | 4 | wire (p) | | | `field-value.zod.ts` / `seed.zod.ts` / `validation.zod.ts` | 1 ea | mixed (p) | | -### `automation/` — 99 sites +### `automation/` — 88 sites | File | Sites | Class | Note | |---|---|---|---| | `flow.zod.ts` | 11 | authorable | **strict as of #4001** (4 schemas; `FlowVersionHistorySchema` is runtime — stays tolerant) | | `sync.zod.ts` / `etl.zod.ts` | 12+10 | authorable (p) | authored pipelines — **candidates** | -| `trigger-registry.zod.ts` | 11 | mixed | descriptors are code-registered (wire-ish); bindings authored | | `execution.zod.ts` | 13 | wire | run-state envelopes — never strict. +5 at #4354 (the run-summary family: step metrics / skip reason / per-node / per-gate / the summary itself) — engine-emitted telemetry read by the Console and by operator queries, nobody authors them, so the `wire` verdict covers them unchanged | | `state-machine.zod.ts` | 7 | authorable (p) | | | `control-flow.zod.ts` | 6 | authorable (p) | validated structurally by `validateControlFlow` | @@ -213,6 +212,8 @@ tightening (the #4001 "sharing-rule lesson": candidates, not verdicts). | `webhook.zod.ts` | 1 | authorable (p) | spec-only (#3461) | | `flow-function.zod.ts` | 1 | authorable | `FlowFunctionDeclarationSchema` (#4396) — the `{ handler, effect }` form of a `defineStack({ functions })` entry. Authored, but note what an undeclared key here would be: a sibling of a **live function**, not data. `defineStack`'s union already rejects a record whose `handler` is not callable, and the boot-path reader is the hand-written `normalizeFlowFunctionEntry` rather than a `.parse()` (re-validating a live handler every boot buys nothing), so strictness would bind at authoring only. Candidate on the same verify-first rule as its `*-node-config` neighbours | +`trigger-registry.zod.ts` had a row here (11 sites, "mixed — descriptors are code-registered (wire-ish); bindings authored") until #4499 deleted the file: all 11 sites were the third connector-vocabulary declaration (`ConnectorSchema` / `Authentication*` / `Operation*` / `ConnectorInstance`), and the old row's classification was optimistic twice over — nothing was ever code-registered against these descriptors and no binding was ever authored. The engine registers against `integration/connector.zod.ts` (ADR-0097), which keeps its own row. + ### `security/` — 20 sites | File | Sites | Class | Note | diff --git a/packages/spec/PROTOCOL_MAP.md b/packages/spec/PROTOCOL_MAP.md index 682dff19db..edffaf5066 100644 --- a/packages/spec/PROTOCOL_MAP.md +++ b/packages/spec/PROTOCOL_MAP.md @@ -70,7 +70,6 @@ This document serves as the **Grand Map** of the ObjectStack specification. It l | [`flow.zod.ts`](src/automation/flow.zod.ts) | ⭐ | **Visual Flow**. Complex orchestration logic (decisions, loops, CRUD). | | [`approval.zod.ts`](src/automation/approval.zod.ts) | ⭐ | **Approval Node**. Flow node config for human approval pauses. | | [`webhook.zod.ts`](src/automation/webhook.zod.ts) | ⭐ | **Webhooks**. Outbound HTTP notification configuration. | -| [`trigger-registry.zod.ts`](src/automation/trigger-registry.zod.ts) | | **Trigger Registry**. Central registry for all automation triggers. | | [`etl.zod.ts`](src/automation/etl.zod.ts) | | **ETL Jobs**. Extract-Transform-Load definitions. | | [`sync.zod.ts`](src/automation/sync.zod.ts) | | **Data Sync**. Bi-directional synchronization rules. | diff --git a/packages/spec/api-surface.json b/packages/spec/api-surface.json index 4e78b190ca..7fa19d88c2 100644 --- a/packages/spec/api-surface.json +++ b/packages/spec/api-surface.json @@ -2081,12 +2081,6 @@ "ApproverOrgSymbol (type)", "ApproverType (const)", "ApproverValueBinding (type)", - "AuthField (type)", - "AuthFieldSchema (const)", - "Authentication (type)", - "AuthenticationSchema (const)", - "AuthenticationType (type)", - "AuthenticationTypeSchema (const)", "BPMN_BOUNDARY_EVENT (const)", "BPMN_JOIN_GATEWAY (const)", "BPMN_PARALLEL_GATEWAY (const)", @@ -2115,16 +2109,6 @@ "ConcurrencyPolicySchema (const)", "ConflictResolution (type)", "ConflictResolutionSchema (const)", - "Connector (type)", - "ConnectorCategory (type)", - "ConnectorCategorySchema (const)", - "ConnectorInstance (type)", - "ConnectorInstanceSchema (const)", - "ConnectorOperation (type)", - "ConnectorOperationSchema (const)", - "ConnectorSchema (const)", - "ConnectorTrigger (type)", - "ConnectorTriggerSchema (const)", "CreateRecordConfig (type)", "CreateRecordConfigParsed (type)", "CreateRecordConfigSchema (const)", @@ -2247,14 +2231,8 @@ "NotifyConfig (type)", "NotifyConfigParsed (type)", "NotifyConfigSchema (const)", - "OAuth2Config (type)", - "OAuth2ConfigSchema (const)", "ORG_MEMBERSHIP_LEVELS (const)", "OS_CONSTRUCT_EXT (const)", - "OperationParameter (type)", - "OperationParameterSchema (const)", - "OperationType (type)", - "OperationTypeSchema (const)", "PARALLEL_NODE_TYPE (const)", "ParallelBranch (type)", "ParallelBranchSchema (const)", diff --git a/packages/spec/authorable-surface.json b/packages/spec/authorable-surface.json index c8f296470a..9ce4ea628e 100644 --- a/packages/spec/authorable-surface.json +++ b/packages/spec/authorable-surface.json @@ -2150,18 +2150,6 @@ "automation/ApprovalNodeConfig:maxRevisions", "automation/ApprovalNodeConfig:minApprovals", "automation/ApprovalNodeConfig:onEmptyApprovers", - "automation/AuthField:default", - "automation/AuthField:description", - "automation/AuthField:label", - "automation/AuthField:name", - "automation/AuthField:options", - "automation/AuthField:placeholder", - "automation/AuthField:required", - "automation/AuthField:type", - "automation/Authentication:fields", - "automation/Authentication:oauth2", - "automation/Authentication:test", - "automation/Authentication:type", "automation/BpmnDiagnostic:bpmnElementId", "automation/BpmnDiagnostic:message", "automation/BpmnDiagnostic:nodeId", @@ -2199,50 +2187,6 @@ "automation/ConcurrencyPolicy:maxConcurrent", "automation/ConcurrencyPolicy:onConflict", "automation/ConcurrencyPolicy:queueTimeoutMs", - "automation/Connector:authentication", - "automation/Connector:author", - "automation/Connector:baseUrl", - "automation/Connector:category", - "automation/Connector:description", - "automation/Connector:documentation", - "automation/Connector:homepage", - "automation/Connector:icon", - "automation/Connector:id", - "automation/Connector:license", - "automation/Connector:metadata", - "automation/Connector:name", - "automation/Connector:operations", - "automation/Connector:rateLimit", - "automation/Connector:tags", - "automation/Connector:triggers", - "automation/Connector:verified", - "automation/Connector:version", - "automation/ConnectorInstance:active", - "automation/ConnectorInstance:config", - "automation/ConnectorInstance:connectorId", - "automation/ConnectorInstance:createdAt", - "automation/ConnectorInstance:credentials", - "automation/ConnectorInstance:description", - "automation/ConnectorInstance:id", - "automation/ConnectorInstance:lastTestedAt", - "automation/ConnectorInstance:name", - "automation/ConnectorInstance:testStatus", - "automation/ConnectorOperation:description", - "automation/ConnectorOperation:id", - "automation/ConnectorOperation:inputSchema", - "automation/ConnectorOperation:name", - "automation/ConnectorOperation:outputSchema", - "automation/ConnectorOperation:sampleOutput", - "automation/ConnectorOperation:supportsFiltering", - "automation/ConnectorOperation:supportsPagination", - "automation/ConnectorOperation:type", - "automation/ConnectorTrigger:config", - "automation/ConnectorTrigger:description", - "automation/ConnectorTrigger:id", - "automation/ConnectorTrigger:name", - "automation/ConnectorTrigger:outputSchema", - "automation/ConnectorTrigger:pollingIntervalMs", - "automation/ConnectorTrigger:type", "automation/CreateRecordConfig:fields", "automation/CreateRecordConfig:objectName", "automation/CreateRecordConfig:outputVariable", @@ -2488,19 +2432,6 @@ "automation/NotifyConfig:sourceObject", "automation/NotifyConfig:title", "automation/NotifyConfig:topic", - "automation/OAuth2Config:authorizationUrl", - "automation/OAuth2Config:clientIdField", - "automation/OAuth2Config:clientSecretField", - "automation/OAuth2Config:scopes", - "automation/OAuth2Config:tokenUrl", - "automation/OperationParameter:default", - "automation/OperationParameter:description", - "automation/OperationParameter:dynamicOptions", - "automation/OperationParameter:label", - "automation/OperationParameter:name", - "automation/OperationParameter:required", - "automation/OperationParameter:type", - "automation/OperationParameter:validation", "automation/ParallelBranch:edges", "automation/ParallelBranch:name", "automation/ParallelBranch:nodes", diff --git a/packages/spec/json-schema.manifest.json b/packages/spec/json-schema.manifest.json index 5f3fbb5f60..10bfe14e11 100644 --- a/packages/spec/json-schema.manifest.json +++ b/packages/spec/json-schema.manifest.json @@ -504,9 +504,6 @@ "automation/ApprovalNodeApprover", "automation/ApprovalNodeConfig", "automation/ApproverType", - "automation/AuthField", - "automation/Authentication", - "automation/AuthenticationType", "automation/BpmnDiagnostic", "automation/BpmnElementMapping", "automation/BpmnExportOptions", @@ -517,11 +514,6 @@ "automation/Checkpoint", "automation/ConcurrencyPolicy", "automation/ConflictResolution", - "automation/Connector", - "automation/ConnectorCategory", - "automation/ConnectorInstance", - "automation/ConnectorOperation", - "automation/ConnectorTrigger", "automation/CreateRecordConfig", "automation/DataDestinationConfig", "automation/DataSourceConfig", @@ -565,9 +557,6 @@ "automation/MapConfig", "automation/NodeExecutorDescriptor", "automation/NotifyConfig", - "automation/OAuth2Config", - "automation/OperationParameter", - "automation/OperationType", "automation/ParallelBranch", "automation/ParallelConfig", "automation/RetryPolicy", diff --git a/packages/spec/scripts/build-docs.ts b/packages/spec/scripts/build-docs.ts index bfc50b8e1b..033b7097da 100644 --- a/packages/spec/scripts/build-docs.ts +++ b/packages/spec/scripts/build-docs.ts @@ -506,7 +506,7 @@ const SECTION_GROUPS: Record { section: 'Service APIs', pages: ['core-services', 'auth', 'auth-endpoints', 'identity', 'metadata', 'metadata-plugin', 'automation-api', 'analytics', 'export', 'storage', 'notification', 'events', 'connector', 'package-api', 'package-registry', 'plugin-rest-api'] }, ], automation: [ - { section: 'Flow & Execution', pages: ['flow', 'control-flow', 'execution', 'node-executor', 'state-machine', 'trigger-registry', 'time-relative-trigger'] }, + { section: 'Flow & Execution', pages: ['flow', 'control-flow', 'execution', 'node-executor', 'state-machine', 'time-relative-trigger'] }, { section: 'Integration & Data', pages: ['sync', 'etl', 'connector', 'webhook', 'bpmn-interop', 'offline'] }, { section: 'Approvals & Jobs', pages: ['approval', 'job'] }, ], diff --git a/packages/spec/scripts/build-skill-references.ts b/packages/spec/scripts/build-skill-references.ts index ba78360474..429ca9a9f1 100644 --- a/packages/spec/scripts/build-skill-references.ts +++ b/packages/spec/scripts/build-skill-references.ts @@ -79,7 +79,6 @@ const SKILL_MAP: Record = { ], 'objectstack-automation': [ 'automation/flow.zod.ts', - 'automation/trigger-registry.zod.ts', 'automation/time-relative-trigger.zod.ts', 'automation/approval.zod.ts', 'automation/state-machine.zod.ts', diff --git a/packages/spec/src/automation/index.ts b/packages/spec/src/automation/index.ts index 7abfa49c33..f44fe4518a 100644 --- a/packages/spec/src/automation/index.ts +++ b/packages/spec/src/automation/index.ts @@ -13,7 +13,14 @@ export * from './execution.zod'; export * from './webhook.zod'; export * from './approval.zod'; export * from './etl.zod'; -export * from './trigger-registry.zod'; +// `trigger-registry.zod` was removed here (#4499). Despite the filename it +// contained no trigger registry — all 630 lines were a third declaration of +// the connector vocabulary (ConnectorSchema, Authentication*, Operation*, +// ConnectorInstance…), self-contained and read by nothing: the automation +// engine registers connectors against `integration/connector.zod.ts` +// (ADR-0097), and the stack `connectors:` collection parses +// DeclarativeConnectorEntrySchema. One capability, one contract +// (Prime Directive #12); the #4480 template cluster fell the same way. export * from './time-relative-trigger.zod'; export * from './sync.zod'; export * from './state-machine.zod'; diff --git a/packages/spec/src/automation/trigger-registry.test.ts b/packages/spec/src/automation/trigger-registry.test.ts deleted file mode 100644 index adab8d27f2..0000000000 --- a/packages/spec/src/automation/trigger-registry.test.ts +++ /dev/null @@ -1,382 +0,0 @@ -import { describe, it, expect } from 'vitest'; -import { - ConnectorCategorySchema, - AuthenticationTypeSchema, - AuthFieldSchema, - OAuth2ConfigSchema, - AuthenticationSchema, - OperationTypeSchema, - OperationParameterSchema, - ConnectorOperationSchema, - ConnectorTriggerSchema, - ConnectorSchema, - ConnectorInstanceSchema, - Connector, -} from './trigger-registry.zod'; - -describe('ConnectorCategorySchema', () => { - it('should accept all valid categories', () => { - const categories = [ - 'crm', 'payment', 'communication', 'storage', 'analytics', - 'database', 'marketing', 'accounting', 'hr', 'productivity', - 'ecommerce', 'support', 'devtools', 'social', 'other', - ]; - categories.forEach(c => { - expect(() => ConnectorCategorySchema.parse(c)).not.toThrow(); - }); - }); - - it('should reject invalid category', () => { - expect(() => ConnectorCategorySchema.parse('invalid')).toThrow(); - }); -}); - -describe('AuthenticationTypeSchema', () => { - it('should accept all valid auth types', () => { - const types = ['none', 'apiKey', 'basic', 'bearer', 'oauth1', 'oauth2', 'custom']; - types.forEach(t => { - expect(() => AuthenticationTypeSchema.parse(t)).not.toThrow(); - }); - }); - - it('should reject invalid auth type', () => { - expect(() => AuthenticationTypeSchema.parse('saml')).toThrow(); - }); -}); - -describe('AuthFieldSchema', () => { - it('should accept valid auth field with defaults', () => { - const result = AuthFieldSchema.parse({ - name: 'api_key', - label: 'API Key', - }); - expect(result.type).toBe('text'); - expect(result.required).toBe(true); - }); - - it('should accept full auth field', () => { - const field = { - name: 'region', - label: 'Region', - type: 'select' as const, - description: 'Cloud region', - required: false, - default: 'us-east-1', - options: [ - { label: 'US East', value: 'us-east-1' }, - { label: 'EU West', value: 'eu-west-1' }, - ], - placeholder: 'Select a region', - }; - expect(() => AuthFieldSchema.parse(field)).not.toThrow(); - }); - - it('should reject invalid name (not snake_case)', () => { - expect(() => AuthFieldSchema.parse({ - name: 'ApiKey', - label: 'API Key', - })).toThrow(); - }); - - it('should reject missing label', () => { - expect(() => AuthFieldSchema.parse({ - name: 'api_key', - })).toThrow(); - }); -}); - -describe('OAuth2ConfigSchema', () => { - it('should accept valid config with defaults', () => { - const result = OAuth2ConfigSchema.parse({ - authorizationUrl: 'https://example.com/auth', - tokenUrl: 'https://example.com/token', - }); - expect(result.clientIdField).toBe('client_id'); - expect(result.clientSecretField).toBe('client_secret'); - }); - - it('should accept full config', () => { - expect(() => OAuth2ConfigSchema.parse({ - authorizationUrl: 'https://example.com/auth', - tokenUrl: 'https://example.com/token', - scopes: ['read', 'write'], - clientIdField: 'my_client_id', - clientSecretField: 'my_secret', - })).not.toThrow(); - }); - - it('should reject invalid URLs', () => { - expect(() => OAuth2ConfigSchema.parse({ - authorizationUrl: 'not-a-url', - tokenUrl: 'https://example.com/token', - })).toThrow(); - }); -}); - -describe('AuthenticationSchema', () => { - it('should accept minimal auth config', () => { - expect(() => AuthenticationSchema.parse({ - type: 'none', - })).not.toThrow(); - }); - - it('should accept auth with fields and test', () => { - const result = AuthenticationSchema.parse({ - type: 'apiKey', - fields: [{ name: 'api_key', label: 'API Key', type: 'password' }], - test: { url: 'https://api.example.com/me' }, - }); - expect(result.test?.method).toBe('GET'); - }); - - it('should accept oauth2 with config', () => { - expect(() => AuthenticationSchema.parse({ - type: 'oauth2', - oauth2: { - authorizationUrl: 'https://example.com/auth', - tokenUrl: 'https://example.com/token', - }, - })).not.toThrow(); - }); - - it('should reject missing type', () => { - expect(() => AuthenticationSchema.parse({})).toThrow(); - }); -}); - -describe('OperationTypeSchema', () => { - it('should accept all valid types', () => { - const types = ['read', 'write', 'delete', 'search', 'trigger', 'action']; - types.forEach(t => { - expect(() => OperationTypeSchema.parse(t)).not.toThrow(); - }); - }); - - it('should reject invalid type', () => { - expect(() => OperationTypeSchema.parse('execute')).toThrow(); - }); -}); - -describe('OperationParameterSchema', () => { - it('should accept valid param with defaults', () => { - const result = OperationParameterSchema.parse({ - name: 'channel', - label: 'Channel', - type: 'string', - }); - expect(result.required).toBe(false); - }); - - it('should accept full param', () => { - expect(() => OperationParameterSchema.parse({ - name: 'channel', - label: 'Channel', - description: 'Slack channel', - type: 'string', - required: true, - default: '#general', - validation: { pattern: '^#' }, - dynamicOptions: 'loadChannels', - })).not.toThrow(); - }); - - it('should reject missing type', () => { - expect(() => OperationParameterSchema.parse({ - name: 'channel', - label: 'Channel', - })).toThrow(); - }); -}); - -describe('ConnectorOperationSchema', () => { - it('should accept valid operation with defaults', () => { - const result = ConnectorOperationSchema.parse({ - id: 'send_message', - name: 'Send Message', - type: 'action', - }); - expect(result.supportsPagination).toBe(false); - expect(result.supportsFiltering).toBe(false); - }); - - it('should accept full operation', () => { - expect(() => ConnectorOperationSchema.parse({ - id: 'list_contacts', - name: 'List Contacts', - description: 'List all contacts', - type: 'read', - inputSchema: [{ name: 'limit', label: 'Limit', type: 'number' }], - outputSchema: { type: 'array' }, - sampleOutput: [{ name: 'John' }], - supportsPagination: true, - supportsFiltering: true, - })).not.toThrow(); - }); - - it('should reject invalid id (not snake_case)', () => { - expect(() => ConnectorOperationSchema.parse({ - id: 'SendMessage', - name: 'Send Message', - type: 'action', - })).toThrow(); - }); -}); - -describe('ConnectorTriggerSchema', () => { - it('should accept valid webhook trigger', () => { - expect(() => ConnectorTriggerSchema.parse({ - id: 'new_message', - name: 'New Message', - type: 'webhook', - })).not.toThrow(); - }); - - it('should accept polling trigger with interval', () => { - expect(() => ConnectorTriggerSchema.parse({ - id: 'new_record', - name: 'New Record', - type: 'polling', - pollingIntervalMs: 5000, - config: { resource: 'contacts' }, - outputSchema: { type: 'object' }, - })).not.toThrow(); - }); - - it('should reject polling interval below minimum', () => { - expect(() => ConnectorTriggerSchema.parse({ - id: 'fast_poll', - name: 'Fast Poll', - type: 'polling', - pollingIntervalMs: 500, - })).toThrow(); - }); - - it('should reject invalid trigger type', () => { - expect(() => ConnectorTriggerSchema.parse({ - id: 'test', - name: 'Test', - type: 'invalid', - })).toThrow(); - }); -}); - -describe('ConnectorSchema', () => { - const minimalConnector = { - id: 'slack', - name: 'Slack', - category: 'communication', - authentication: { type: 'apiKey' }, - }; - - it('should accept minimal connector with defaults', () => { - const result = ConnectorSchema.parse(minimalConnector); - expect(result.verified).toBe(false); - }); - - it('should accept full connector', () => { - expect(() => ConnectorSchema.parse({ - ...minimalConnector, - description: 'Slack integration', - version: '1.0.0', - icon: 'slack-icon', - baseUrl: 'https://slack.com/api', - operations: [{ id: 'send_message', name: 'Send Message', type: 'action' }], - triggers: [{ id: 'new_message', name: 'New Message', type: 'webhook' }], - rateLimit: { requestsPerSecond: 10, requestsPerMinute: 100 }, - author: 'ObjectStack', - documentation: 'https://docs.example.com', - homepage: 'https://example.com', - license: 'MIT', - tags: ['chat', 'messaging'], - verified: true, - metadata: { tier: 'premium' }, - })).not.toThrow(); - }); - - it('should reject missing required fields', () => { - expect(() => ConnectorSchema.parse({})).toThrow(); - expect(() => ConnectorSchema.parse({ id: 'test' })).toThrow(); - }); - - it('should reject invalid id format', () => { - expect(() => ConnectorSchema.parse({ - ...minimalConnector, - id: 'My-Connector', - })).toThrow(); - }); -}); - -describe('ConnectorInstanceSchema', () => { - it('should accept valid instance with defaults', () => { - const result = ConnectorInstanceSchema.parse({ - id: 'inst-123', - connectorId: 'slack', - name: 'Slack Production', - credentials: { api_key: 'encrypted-value' }, - }); - expect(result.active).toBe(true); - expect(result.testStatus).toBe('unknown'); - }); - - it('should accept full instance', () => { - expect(() => ConnectorInstanceSchema.parse({ - id: 'inst-456', - connectorId: 'slack', - name: 'Slack Dev', - description: 'Development instance', - credentials: { api_key: 'encrypted' }, - config: { workspace: 'dev' }, - active: false, - createdAt: '2024-01-01T00:00:00Z', - lastTestedAt: '2024-01-02T00:00:00Z', - testStatus: 'success', - })).not.toThrow(); - }); - - it('should reject missing credentials', () => { - expect(() => ConnectorInstanceSchema.parse({ - id: 'inst-789', - connectorId: 'slack', - name: 'Slack', - })).toThrow(); - }); - - it('should reject invalid datetime', () => { - expect(() => ConnectorInstanceSchema.parse({ - id: 'inst-789', - connectorId: 'slack', - name: 'Slack', - credentials: {}, - createdAt: 'not-a-date', - })).toThrow(); - }); -}); - -describe('Connector factory', () => { - it('should create an API key connector', () => { - const connector = Connector.apiKey({ - id: 'twilio', - name: 'Twilio', - category: 'communication', - baseUrl: 'https://api.twilio.com', - }); - expect(connector.authentication.type).toBe('apiKey'); - expect(connector.verified).toBe(false); - expect(() => ConnectorSchema.parse(connector)).not.toThrow(); - }); - - it('should create an OAuth2 connector', () => { - const connector = Connector.oauth2({ - id: 'salesforce', - name: 'Salesforce', - category: 'crm', - baseUrl: 'https://login.salesforce.com', - authUrl: 'https://login.salesforce.com/services/oauth2/authorize', - tokenUrl: 'https://login.salesforce.com/services/oauth2/token', - scopes: ['api', 'refresh_token'], - }); - expect(connector.authentication.type).toBe('oauth2'); - expect(connector.authentication.oauth2?.scopes).toEqual(['api', 'refresh_token']); - expect(() => ConnectorSchema.parse(connector)).not.toThrow(); - }); -}); diff --git a/packages/spec/src/automation/trigger-registry.zod.ts b/packages/spec/src/automation/trigger-registry.zod.ts deleted file mode 100644 index 826ec7546d..0000000000 --- a/packages/spec/src/automation/trigger-registry.zod.ts +++ /dev/null @@ -1,630 +0,0 @@ -// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license. - -import { z } from 'zod'; - -/** - * Trigger Registry Protocol - * - * Lightweight automation triggers for simple integrations. - * Inspired by Zapier, n8n, and Workato connector architectures. - * - * ## When to use Trigger Registry vs. Integration Connector? - * - * **Use `automation/trigger-registry.zod.ts` when:** - * - Building simple automation triggers (e.g., "when Slack message received, create task") - * - No complex authentication needed (simple API keys, basic auth) - * - Lightweight, single-purpose integrations - * - Quick setup with minimal configuration - * - Webhook-based or polling triggers for automation workflows - * - * **Use `integration/connector.zod.ts` when:** - * - Building enterprise-grade connectors (e.g., Salesforce, SAP, Oracle) - * - Complex OAuth2/SAML authentication required - * - Bidirectional sync with field mapping and transformations - * - Webhook management and rate limiting required - * - Full CRUD operations and data synchronization - * - * ## Use Cases - * - * 1. **Simple Automation Triggers** - * - Slack notifications on record updates - * - Twilio SMS on workflow events - * - SendGrid email templates - * - * 2. **Lightweight Operations** - * - Single-action integrations (send, notify, log) - * - No bidirectional sync required - * - Webhook receivers for incoming events - * - * 3. **Quick Integrations** - * - Payment webhooks (Stripe, PayPal) - * - Communication triggers (Twilio, SendGrid, Slack) - * - Simple API calls to third-party services - * - * @see https://zapier.com/developer/documentation/v2/ - * @see https://docs.n8n.io/integrations/creating-nodes/ - * @see ../../integration/connector.zod.ts for enterprise connectors - * - * @example - * ```typescript - * const slackNotifier: Connector = { - * id: 'slack_notify', - * name: 'Slack Notification', - * category: 'communication', - * authentication: { - * type: 'apiKey', - * fields: [{ name: 'webhook_url', label: 'Webhook URL', type: 'url' }] - * }, - * operations: [ - * { id: 'send_message', name: 'Send Message', type: 'action' } - * ] - * } - * ``` - */ - -/** - * Connector Category - */ -import { lazySchema } from '../shared/lazy-schema'; -export const ConnectorCategorySchema = lazySchema(() => z.enum([ - 'crm', // Customer Relationship Management - 'payment', // Payment processors - 'communication', // Email, SMS, Chat - 'storage', // File storage - 'analytics', // Analytics platforms - 'database', // Databases - 'marketing', // Marketing automation - 'accounting', // Accounting software - 'hr', // Human resources - 'productivity', // Productivity tools - 'ecommerce', // E-commerce platforms - 'support', // Customer support - 'devtools', // Developer tools - 'social', // Social media - 'other', // Other category -])); - -export type ConnectorCategory = z.infer; - -/** - * Authentication Type - */ -export const AuthenticationTypeSchema = lazySchema(() => z.enum([ - 'none', // No authentication - 'apiKey', // API key - 'basic', // Basic auth (username/password) - 'bearer', // Bearer token - 'oauth1', // OAuth 1.0 - 'oauth2', // OAuth 2.0 - 'custom', // Custom authentication -])); - -export type AuthenticationType = z.infer; - -/** - * Authentication Field Schema - */ -export const AuthFieldSchema = lazySchema(() => z.object({ - /** - * Field name (machine name) - */ - name: z.string() - .regex(/^[a-z_][a-z0-9_]*$/) - .describe('Field name (snake_case)'), - - /** - * Field label - */ - label: z.string().describe('Field label'), - - /** - * Field type - */ - type: z.enum(['text', 'password', 'url', 'select']) - .default('text') - .describe('Field type'), - - /** - * Field description - */ - description: z.string().optional().describe('Field description'), - - /** - * Whether field is required - */ - required: z.boolean().default(true).describe('Required field'), - - /** - * Default value - */ - default: z.string().optional().describe('Default value'), - - /** - * Options for select fields - */ - options: z.array(z.object({ - label: z.string(), - value: z.string(), - })).optional().describe('Select field options'), - - /** - * Placeholder text - */ - placeholder: z.string().optional().describe('Placeholder text'), -})); - -export type AuthField = z.infer; - -/** - * OAuth 2.0 Configuration - */ -export const OAuth2ConfigSchema = lazySchema(() => z.object({ - /** - * Authorization URL - */ - authorizationUrl: z.string().url().describe('Authorization endpoint URL'), - - /** - * Token URL - */ - tokenUrl: z.string().url().describe('Token endpoint URL'), - - /** - * Scopes to request - */ - scopes: z.array(z.string()).optional().describe('OAuth scopes'), - - /** - * Client ID field name - */ - clientIdField: z.string().default('client_id').describe('Client ID field name'), - - /** - * Client secret field name - */ - clientSecretField: z.string().default('client_secret').describe('Client secret field name'), -})); - -export type OAuth2Config = z.infer; - -/** - * Authentication Configuration - */ -export const AuthenticationSchema = lazySchema(() => z.object({ - /** - * Authentication type - */ - type: AuthenticationTypeSchema.describe('Authentication type'), - - /** - * Authentication fields - * Configuration fields needed for this auth type - */ - fields: z.array(AuthFieldSchema).optional().describe('Authentication fields'), - - /** - * OAuth 2.0 configuration (when type is oauth2) - */ - oauth2: OAuth2ConfigSchema.optional().describe('OAuth 2.0 configuration'), - - /** - * Test authentication instructions - */ - test: z.object({ - url: z.string().optional().describe('Test endpoint URL'), - method: z.enum(['GET', 'POST', 'PUT', 'DELETE']).default('GET').describe('HTTP method'), - }).optional().describe('Authentication test configuration'), -})); - -export type Authentication = z.infer; - -/** - * Connector Operation Type - */ -export const OperationTypeSchema = lazySchema(() => z.enum([ - 'read', // Read/query data - 'write', // Create/update data - 'delete', // Delete data - 'search', // Search operation - 'trigger', // Webhook/polling trigger - 'action', // Custom action -])); - -export type OperationType = z.infer; - -/** - * Operation Parameter Schema - */ -export const OperationParameterSchema = lazySchema(() => z.object({ - /** - * Parameter name - */ - name: z.string().describe('Parameter name'), - - /** - * Parameter label - */ - label: z.string().describe('Parameter label'), - - /** - * Parameter description - */ - description: z.string().optional().describe('Parameter description'), - - /** - * Parameter type - */ - type: z.enum(['string', 'number', 'boolean', 'array', 'object', 'date', 'file']) - .describe('Parameter type'), - - /** - * Whether parameter is required - */ - required: z.boolean().default(false).describe('Required parameter'), - - /** - * Default value - */ - default: z.unknown().optional().describe('Default value'), - - /** - * Validation schema - */ - validation: z.record(z.string(), z.unknown()).optional().describe('Validation rules'), - - /** - * Dynamic options function - */ - dynamicOptions: z.string().optional().describe('Function to load dynamic options'), -})); - -export type OperationParameter = z.infer; - -/** - * Connector Operation Schema - */ -export const ConnectorOperationSchema = lazySchema(() => z.object({ - /** - * Operation identifier - */ - id: z.string() - .regex(/^[a-z_][a-z0-9_]*$/) - .describe('Operation ID (snake_case)'), - - /** - * Operation name - */ - name: z.string().describe('Operation name'), - - /** - * Operation description - */ - description: z.string().optional().describe('Operation description'), - - /** - * Operation type - */ - type: OperationTypeSchema.describe('Operation type'), - - /** - * Input parameters - */ - inputSchema: z.array(OperationParameterSchema) - .optional() - .describe('Input parameters'), - - /** - * Output schema - */ - outputSchema: z.record(z.string(), z.unknown()) - .optional() - .describe('Output schema'), - - /** - * Sample output for documentation - */ - sampleOutput: z.unknown().optional().describe('Sample output'), - - /** - * Whether operation supports pagination - */ - supportsPagination: z.boolean().default(false).describe('Supports pagination'), - - /** - * Whether operation supports filtering - */ - supportsFiltering: z.boolean().default(false).describe('Supports filtering'), -})); - -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({ - /** - * Trigger identifier - */ - id: z.string() - .regex(/^[a-z_][a-z0-9_]*$/) - .describe('Trigger ID (snake_case)'), - - /** - * Trigger name - */ - name: z.string().describe('Trigger name'), - - /** - * Trigger description - */ - description: z.string().optional().describe('Trigger description'), - - /** - * Trigger type - */ - type: z.enum(['webhook', 'polling', 'stream']) - .describe('Trigger mechanism'), - - /** - * Trigger configuration - */ - config: z.record(z.string(), z.unknown()) - .optional() - .describe('Trigger configuration'), - - /** - * Output schema - */ - outputSchema: z.record(z.string(), z.unknown()) - .optional() - .describe('Event payload schema'), - - /** - * Polling interval (for polling triggers) - * In milliseconds - */ - pollingIntervalMs: z.number().int().min(1000) - .optional() - .describe('Polling interval in ms'), -})); - -export type ConnectorTrigger = z.infer; - -/** - * Connector Schema - * - * Complete definition of a connector to an external system. - */ -export const ConnectorSchema = lazySchema(() => z.object({ - /** - * Connector identifier - * Must be globally unique - */ - id: z.string() - .regex(/^[a-z_][a-z0-9_]*$/) - .describe('Connector ID (snake_case)'), - - /** - * Connector name - */ - name: z.string().describe('Connector name'), - - /** - * Connector description - */ - description: z.string().optional().describe('Connector description'), - - /** - * Connector version (semver) - */ - version: z.string().optional().describe('Connector version'), - - /** - * Connector icon URL or name - */ - icon: z.string().optional().describe('Connector icon'), - - /** - * Connector category - */ - category: ConnectorCategorySchema.describe('Connector category'), - - /** - * Base URL for API calls - */ - baseUrl: z.string().url().optional().describe('API base URL'), - - /** - * Authentication configuration - */ - authentication: AuthenticationSchema.describe('Authentication config'), - - /** - * Available operations - */ - operations: z.array(ConnectorOperationSchema) - .optional() - .describe('Connector operations'), - - /** - * Available triggers - */ - triggers: z.array(ConnectorTriggerSchema) - .optional() - .describe('Connector triggers'), - - /** - * Rate limiting information - */ - rateLimit: z.object({ - requestsPerSecond: z.number().optional().describe('Max requests per second'), - requestsPerMinute: z.number().optional().describe('Max requests per minute'), - requestsPerHour: z.number().optional().describe('Max requests per hour'), - }).optional().describe('Rate limiting'), - - /** - * Connector author - */ - author: z.string().optional().describe('Connector author'), - - /** - * Documentation URL - */ - documentation: z.string().url().optional().describe('Documentation URL'), - - /** - * Homepage URL - */ - homepage: z.string().url().optional().describe('Homepage URL'), - - /** - * License - */ - license: z.string().optional().describe('License (SPDX identifier)'), - - /** - * Tags for discovery - */ - tags: z.array(z.string()).optional().describe('Connector tags'), - - /** - * Whether connector is verified/certified - */ - verified: z.boolean().default(false).describe('Verified connector'), - - /** - * Custom metadata - */ - metadata: z.record(z.string(), z.unknown()).optional().describe('Custom metadata'), -})); - -export type Connector = z.infer; - -/** - * Connector Instance Schema - * - * A configured instance of a connector with credentials. - */ -export const ConnectorInstanceSchema = lazySchema(() => z.object({ - /** - * Instance ID - */ - id: z.string().describe('Instance ID'), - - /** - * Connector ID this instance uses - */ - connectorId: z.string().describe('Connector ID'), - - /** - * Instance name - */ - name: z.string().describe('Instance name'), - - /** - * Instance description - */ - description: z.string().optional().describe('Instance description'), - - /** - * Authentication credentials (encrypted) - */ - credentials: z.record(z.string(), z.unknown()).describe('Encrypted credentials'), - - /** - * Additional configuration - */ - config: z.record(z.string(), z.unknown()).optional().describe('Additional config'), - - /** - * Whether instance is active - */ - active: z.boolean().default(true).describe('Instance active status'), - - /** - * Created timestamp - */ - createdAt: z.string().datetime().optional().describe('Creation time'), - - /** - * Last tested timestamp - */ - lastTestedAt: z.string().datetime().optional().describe('Last test time'), - - /** - * Test status - */ - testStatus: z.enum(['unknown', 'success', 'failed']) - .default('unknown') - .describe('Connection test status'), -})); - -export type ConnectorInstance = z.infer; - -/** - * Helper factory for creating connectors - */ -export const Connector = { - /** - * Create a basic API key connector - */ - apiKey: (params: { - id: string; - name: string; - category: ConnectorCategory; - baseUrl: string; - }): Connector => ({ - id: params.id, - name: params.name, - category: params.category, - baseUrl: params.baseUrl, - authentication: { - type: 'apiKey', - fields: [ - { - name: 'api_key', - label: 'API Key', - type: 'password', - required: true, - }, - ], - }, - verified: false, - }), - - /** - * Create an OAuth 2.0 connector - */ - oauth2: (params: { - id: string; - name: string; - category: ConnectorCategory; - baseUrl: string; - authUrl: string; - tokenUrl: string; - scopes?: string[]; - }): Connector => ({ - id: params.id, - name: params.name, - category: params.category, - baseUrl: params.baseUrl, - authentication: { - type: 'oauth2', - oauth2: { - authorizationUrl: params.authUrl, - tokenUrl: params.tokenUrl, - clientIdField: 'client_id', - clientSecretField: 'client_secret', - scopes: params.scopes, - }, - }, - verified: false, - }), -} as const; diff --git a/packages/spec/src/integration/connector.zod.ts b/packages/spec/src/integration/connector.zod.ts index 2e5821c6a1..988ce9124d 100644 --- a/packages/spec/src/integration/connector.zod.ts +++ b/packages/spec/src/integration/connector.zod.ts @@ -70,23 +70,18 @@ import { FieldMappingSchema as BaseFieldMappingSchema } from '../shared/mapping. * @see {@link file://../automation/sync.zod.ts} for Level 1 (simple sync) * @see {@link file://../automation/etl.zod.ts} for Level 2 (data engineering) * - * ## When to use Integration Connector vs. Trigger Registry? - * - * **Use `integration/connector.zod.ts` when:** - * - Building enterprise-grade connectors (e.g., Salesforce, SAP, Oracle) - * - Complex OAuth2/SAML authentication required - * - Bidirectional sync with field mapping and transformations - * - Webhook management and rate limiting required - * - Full CRUD operations and data synchronization - * - Need comprehensive retry strategies and error handling - * - * **Use `automation/trigger-registry.zod.ts` when:** - * - Building simple automation triggers (e.g., "when Slack message received, create task") - * - No complex authentication needed (simple API keys, basic auth) - * - Lightweight, single-purpose integrations - * - Quick setup with minimal configuration - * - * @see ../../automation/trigger-registry.zod.ts for lightweight automation triggers + * ## There is no "Trigger Registry" alternative + * + * This header used to carry a "When to use Integration Connector vs. Trigger + * Registry?" comparison, steering "lightweight" cases to + * `automation/trigger-registry.zod.ts`. That file was a third declaration of + * the connector vocabulary with zero consumers — nothing registered, validated + * or executed against it — so the guidance pointed authors, with the + * platform's authority, at a dead end (#4499; removed alongside the #4480 + * per-provider template cluster). The same defect class as the + * `capabilities.readOnly` prescription #4487 corrected: a signpost must land + * somewhere enforced. Lightweight cases are served HERE — a connector instance + * with simple `auth` — or by `automation/sync.zod.ts` / `etl.zod.ts` below. */ // ============================================================================ diff --git a/skills/objectstack-automation/references/_index.md b/skills/objectstack-automation/references/_index.md index 5e85df2ae6..51feaf5ee7 100644 --- a/skills/objectstack-automation/references/_index.md +++ b/skills/objectstack-automation/references/_index.md @@ -15,7 +15,6 @@ from `node_modules` — there is no local copy in the skill bundle. - `node_modules/@objectstack/spec/src/automation/node-executor.zod.ts` — Node Executor Plugin Protocol — Wait Node Pause/Resume - `node_modules/@objectstack/spec/src/automation/state-machine.zod.ts` — XState-inspired State Machine Protocol - `node_modules/@objectstack/spec/src/automation/time-relative-trigger.zod.ts` — Time-Relative Trigger Protocol -- `node_modules/@objectstack/spec/src/automation/trigger-registry.zod.ts` — Trigger Registry Protocol - `node_modules/@objectstack/spec/src/automation/webhook.zod.ts` — Webhook Trigger Event - `node_modules/@objectstack/spec/src/data/validation.zod.ts` — ObjectStack Validation Protocol