Defines the standard connector specification for external system integration.
Connectors enable ObjectStack to sync data with SaaS apps, databases, file storage,
and message queues through a unified protocol.
Positioning in the sync/integration layering — this file is now the ONLY
layer. Both layers above it were retired under ADR-0049 for the same measured
reason, that no engine ever executed them: L1 "Simple Sync"
(automation/sync.zod.ts) in #4738, and L2 "ETL Pipeline"
(automation/etl.zod.ts) in #6414. See
packages/spec/docs/SYNC_ARCHITECTURE.md:
Enterprise Connector (THIS FILE) - System integrators - Full SAP integration; connector-attached sync via syncConfig
SCOPE: Most comprehensive integration layer.
Includes authentication, webhooks, field mapping, bidirectional sync,
retry policies, and complete lifecycle management.
This protocol supports multiple authentication strategies, bidirectional sync,
field mapping, webhooks, and comprehensive retry and resilience policies.
There is no outbound rate limiting. This header used to advertise "rate
limiting" twice — once in the SCOPE line, once as "comprehensive rate limiting" —
and no engine ever backed either. connector.rateLimitConfig, and the entire
ConnectorRateLimitConfig / RateLimitStrategy shape behind it, was removed in
@objectstack/spec 17.0.0 (#4911, ADR-0049 D2), because no outbound
rate-limiting engine ever existed. The platform's only token bucket (runtime
security/rate-limit.ts) throttles INBOUND requests to us; nothing throttles
the calls a connector makes out. Do not substitute shared's
RateLimitConfig — that is the inbound limiter and would cap the wrong direction.
Until an outbound throttle exists, rate-limit at the connector provider or
upstream gateway. What L3 does declare for a rate-limited upstream is
retryConfig — whose retryableStatusCodes default [408, 429, 500, 502, 503, 504] includes 429 — and health.circuitBreaker. The full removal reasoning is
recorded at the removal site: the "REMOVED: outbound rate limiting" block in
integration/connector.zod.ts, and packages/spec/docs/SYNC_ARCHITECTURE.md.
Field mapping does not transform values. This header used to offer "field
mapping and transformations"; only the first half was ever true.
ConnectorFieldMappingSchema extends the base mapping with exactly three keys —
dataType, required and syncMode. FieldMapping.transform was removed in
@objectstack/spec 17.0.0 (#5552, ADR-0049), and the whole FieldMappingTransform
union went with it (constant / cast / lookup / javascript / map) — no
runtime ever executed any of the five. An L3 connector mapping moves a value from
source to target; it does not compute one. Value conversion belongs on a
surface that runs it: the import mapping's own mapping.fieldMapping[].transform
(data/mapping.zod.ts — a string enum,
none/constant/map/split/join/lookup, with its settings in params),
applied row by row by the REST import path — or an ETL transformation step
(L2 above). Already authored the retired key? os migrate meta --from 16 lists the mechanical edits for existing sources — the key itself is removed.
This schema serves TWO distinct consumers; do not conflate them:
Runtime registration (plugin-only). The automation engine's connector
registry — what GET /connectors lists and the connector_action flow
node dispatches — is populated exclusively by plugins calling
engine.registerConnector(def, handlers) with a handler per declared
action (ADR-0018 §Addendum). The definition is validated against this
schema at registration.
Declarative connectors: stack entries (catalog descriptors). Stack
metadata validated against this schema is registered as kind 'connector'
for discovery/documentation/marketplace purposes only — it never reaches
the runtime registry, because an action here carries no execution binding
(deliberately: ADR-0023 rejected re-inventing OpenAPI inside this schema).
The automation service warns at boot about declared entries with actions
that lack a same-name runtime registration; mark deliberate catalog-only
entries with enabled: false. Provider-bound declarative instances that
a generic executor (connector-openapi / connector-mcp) materializes at
boot are tracked in #2977 (ADR-0097).
Authentication is now imported from the canonical auth/config.zod.ts.
Building enterprise-grade connectors (e.g., Salesforce, SAP, Oracle)
Complex OAuth2/SAML authentication required
Bidirectional sync with field mapping (dataType / syncMode per field — it moves values, it does not transform them)
Webhook management required
Full CRUD operations and data synchronization
Need comprehensive retry strategies and error handling
Examples:
Full Salesforce integration with webhooks
SAP ERP connector with CDC (Change Data Capture)
Microsoft Dynamics 365 connector
When to downgrade:
Per-field value conversion on import only → the import mapping's own
transform (data/mapping.zod.ts), which the REST import path executes
row by row. (This used to point at automation/etl.zod.ts; L2 was retired
at #6414 for having no executor, so the pointer would have been a signpost
landing nowhere — the same defect class this header names below.)
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. (Both automation-side layers were themselves retired as
dead ends of the same class: L1 "Simple Sync" in #4738, L2 etl.zod.ts in
#6414. This paragraph named L2 as the transformation destination until the
second retirement; a signpost that must land somewhere enforced cannot make
an exception for itself.)
Authentication configuration (runtime shape with inline secrets — plugin-supplied at registerConnector). Authored entries must not inline secrets: use auth.credentialRef on a provider-bound instance.
provider
string
optional
Generic-executor key that materializes this declarative entry at boot (e.g. openapi/mcp/rest). Omit for a catalog-only descriptor. Unknown provider ⇒ hard boot error (ADR-0097).
providerConfig
Record<string, any>
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.
[REMOVED] connector.rateLimitConfig was removed in @objectstack/spec 17.0.0 (ADR-0049 D2) — the entire shape is gone, not just this key: ConnectorRateLimitConfig and its RateLimitStrategy enum were removed with it, because no outbound rate-limiting engine ever existed. The platform's only token bucket (runtime security/rate-limit.ts) throttles INBOUND requests to us; nothing throttled the calls a connector makes out, so every knob here was inert while reading like a configured cap. Delete the key. Do NOT substitute sharedRateLimitConfig — that is the inbound limiter and would cap the wrong direction; until an outbound throttle exists, rate-limit at the connector provider or upstream gateway. Run os migrate meta --from 16 to list the mechanical edits for existing sources; apply them by hand.
What the action does upstream: 'read' never mutates (reports acted:0); 'write' does (a successful dispatch reports acted:1). Omit when the effect is not knowable — the step is then reported as unmeasured, not as zero
[REMOVED] FieldMapping.transform — authored as connector.fieldMappings[].transform and externalLookup.fieldMappings[].transform — was removed in @objectstack/spec 17.0.0 (ADR-0049), and the whole FieldMappingTransform union went with it (constant / cast / lookup / javascript / map) — no runtime ever executed any of the five, and the javascript member advertised dialect: "js", a dialect retired. Delete the key. The transform pipeline that IS enforced is the import mapping's: mapping.fieldMapping[].transform (a string enum — none/constant/map/split/join/lookup — with its settings in params), applied by the REST import path, which rejects javascript with a 400 rather than pretending to run it. Run os migrate meta --from 16 to list the mechanical edits for existing sources; apply them by hand.
What the action does upstream: 'read' never mutates (reports acted:0); 'write' does (a successful dispatch reports acted:1). Omit when the effect is not knowable — the step is then reported as unmeasured, not as zero
What the action does upstream: 'read' never mutates (reports acted:0); 'write' does (a successful dispatch reports acted:1). Omit when the effect is not knowable — the step is then reported as unmeasured, not as zero
[REMOVED] FieldMapping.transform — authored as connector.fieldMappings[].transform and externalLookup.fieldMappings[].transform — was removed in @objectstack/spec 17.0.0 (ADR-0049), and the whole FieldMappingTransform union went with it (constant / cast / lookup / javascript / map) — no runtime ever executed any of the five, and the javascript member advertised dialect: "js", a dialect retired. Delete the key. The transform pipeline that IS enforced is the import mapping's: mapping.fieldMapping[].transform (a string enum — none/constant/map/split/join/lookup — with its settings in params), applied by the REST import path, which rejects javascript with a 400 rather than pretending to run it. Run os migrate meta --from 16 to list the mechanical edits for existing sources; apply them by hand.
Authentication configuration (runtime shape with inline secrets — plugin-supplied at registerConnector). Authored entries must not inline secrets: use auth.credentialRef on a provider-bound instance.
provider
string
optional
Generic-executor key that materializes this declarative entry at boot (e.g. openapi/mcp/rest). Omit for a catalog-only descriptor. Unknown provider ⇒ hard boot error (ADR-0097).
providerConfig
Record<string, any>
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.
[REMOVED] connector.rateLimitConfig was removed in @objectstack/spec 17.0.0 (ADR-0049 D2) — the entire shape is gone, not just this key: ConnectorRateLimitConfig and its RateLimitStrategy enum were removed with it, because no outbound rate-limiting engine ever existed. The platform's only token bucket (runtime security/rate-limit.ts) throttles INBOUND requests to us; nothing throttled the calls a connector makes out, so every knob here was inert while reading like a configured cap. Delete the key. Do NOT substitute sharedRateLimitConfig — that is the inbound limiter and would cap the wrong direction; until an outbound throttle exists, rate-limit at the connector provider or upstream gateway. Run os migrate meta --from 16 to list the mechanical edits for existing sources; apply them by hand.
What the action does upstream: 'read' never mutates (reports acted:0); 'write' does (a successful dispatch reports acted:1). Omit when the effect is not knowable — the step is then reported as unmeasured, not as zero
[REMOVED] FieldMapping.transform — authored as connector.fieldMappings[].transform and externalLookup.fieldMappings[].transform — was removed in @objectstack/spec 17.0.0 (ADR-0049), and the whole FieldMappingTransform union went with it (constant / cast / lookup / javascript / map) — no runtime ever executed any of the five, and the javascript member advertised dialect: "js", a dialect retired. Delete the key. The transform pipeline that IS enforced is the import mapping's: mapping.fieldMapping[].transform (a string enum — none/constant/map/split/join/lookup — with its settings in params), applied by the REST import path, which rejects javascript with a 400 rather than pretending to run it. Run os migrate meta --from 16 to list the mechanical edits for existing sources; apply them by hand.