Connectors

Data Mapping

Mappers that turn provider payloads into schema-versioned normalized models, plus field catalogs, mapping profiles, and the connector function runtime.

Provider payloads are all shaped differently — HubSpot's contact JSON looks nothing like Salesforce's. Data mappers convert those provider-shaped payloads into schema-versioned, normalized connector models before the data enters workflow inputs, sync-record lookups, or routing strategy features. Downstream steps then read a stable canonical result (canonical.contact) instead of provider-specific JSON.

What a mapper is

A mapper is a deterministic, registered transform from a provider payload to a normalized model. The runtime executes declarative, CEL, and Go-backed mappers only (docs/connectors/data-mappers.md). Imported template mappers that would require TypeScript execution stay catalog-visible as metadata-only specs with an explicit runtime reason — they are never silently skipped.

Inspect and exercise the catalog through the connector API:

  • ListConnectorModels / GetConnectorModel — the normalized models a provider exposes.
  • ListConnectorMappers / GetConnectorMapper — the registered mapper specs.
  • ValidateConnectorMapper — check a mapper without running it.
  • PreviewConnectorMapper — run a registered mapper against a sample payload; returns normalized bytes plus a metadata-only mapper_execution summary (mapper key/version, source kind/id, output model key/external id, status, bounded error code). It never carries raw provider input.

Where mapping runs

Mapping is bound at three points, all going through the same executor:

  • Action output. An ActionSpec can declare output_mapper_key and default_mapper_output_mode; a caller can override per execution with mapper_key and mapper_output_mode (raw keeps provider bytes and skips the mapper, normalized returns mapper output, both returns provider bytes plus normalized_output). Responses also carry normalized_model_key, normalized_external_id, and normalized_schema_hash.
  • Sync records. A sync execution strategy binds a mapper_key per model plan; each mapped record is stored as a SyncRecord with model, external_id, content_hash, and normalized data.
  • Webhook/lifecycle payloads. A TriggerSpec can declare payload_mapper_key; ingress verifies and dedupes the delivery, then runs the mapper before starting the workflow, so the canonical event envelope arrives with raw_payload and normalized_payload side by side. See Triggers & Polling.

Whichever binding point invoked it, the output mode decides what the caller actually receives — and raw is the one mode that skips the mapper entirely:

raw normalized both provider result output mode provider bytes, mapper skipped run mapper normalized only run mapper bytes + normalized_output

Mapper bindings are a routing data contract

Mapper bindings are part of the function manifest hash. Changing an output_mapper_key, payload_mapper_key, or default mapper output mode changes the manifest/schema hash and surfaces in DiffConnectorDeployment as a function-level schema_changed / manifest_changed reason. Treat those diffs as data-contract changes, not cosmetic version bumps — see Marketplace & Deployments.

Field catalogs and mapping profiles

Where mappers transform payloads, field catalogs and mapping profiles (docs/connectors/field-catalogs-mapping-profiles.md) make the tenant's provider-schema choices durable and auditable instead of hiding them in generic connection metadata.

  • A field catalog is a scoped provider-schema snapshot for a tenant. Object catalogs track provider objects (contacts, tickets, boards, pipelines, custom objects); field catalogs track object fields, provider/normalized types, read/write mode, required/nullability flags, PII class, enum options, relationships, constraints, schema hashes, and drift state. Refresh through RefreshConnectorFieldCatalog; unsupported providers return an explicit unsupported / metadata_only status rather than pretending discovery is complete.
  • Dynamic property resolvers answer the interactive question "what can a user pick right now for this form field?" They can feed discovery, but they are not the durable source of truth — the catalog is.
  • A mapping profile is a versioned tenant field contract mapping an internal model field path to a provider object field path (read / write / bidirectional), with required/null policy, defaults, transform/mapper/dataflow refs, a source catalog hash, and a stable profile hash. Create a draft, validate it against the current catalog, preview with redacted samples, then promote to active.

Promotion fails closed on breaking drift

Profile promotion is rejected when the source catalog is stale or breaking, required fields are unmapped, enum defaults are invalid, dependency refs are missing, policy/entitlement checks fail, or identity locks disagree. Breaking catalog drift blocks promotion outright; a stale non-breaking active profile can continue only when its governing policy allows it, and runtime paths never mutate a profile definition.

Sync runs, sync records, mapper executions, and provider-event envelopes all record the profile id, version, and hash that shaped their payload — so replay and promotion evidence can freeze the exact contract that produced a record.

The connector function runtime

A handful of imported template mappers, actions, syncs, and webhooks are authored in TypeScript. Those run on the connector function runtime (docs/connectors/function-runtime.md) — a governed TypeScript execution surface that is default-off and must run through the exec-plane sandbox isolation boundary. TypeScript function code is never executed in the Go process; in_process is explicitly not an isolation boundary and startup fails closed if the runtime is enabled without subprocess or remote executor isolation.

Its prototype runner (tools/connector-function-runner) reads an ExecSpec from stdin and writes an ExecResult to stdout, and enforces hard invariants: no raw secrets passed to the runner, no mutable source loaded from local checkouts at execution, no network package installs, direct network fetch reported as direct_fetch_blocked, and no raw provider payloads/PII in dry-run evidence. There is intentionally no production-execute MCP tool yet — only status, describe, and dry-run.

Experimental

The connector function runtime is an experimental, default-off surface. Mappers should prefer the declarative, CEL, or Go path; a TypeScript mapper stays metadata-only until the runtime is deliberately enabled and wired.

Where to go next