Engineering · · 8 min read
How we built an integration factory
How Deixic uses typed provider contracts, a shared Rust runtime, and executable verification to expand integrations without rebuilding the plumbing for each API.
A company shouldn't have to plan its security work around the order in which a vendor builds integrations.
An employee leaves. Their access spans an identity provider, a source code host, an HR system, and a SaaS application the security team adopted last month. The work crosses those systems whether the automation platform supports them or not. A missing integration sends part of the job back to a person.
That makes integration development a product constraint. Deixic's compliance and security agents need to work in the systems a company actually uses. We built an integration factory so that expanding that reach uses the compiler, execution machinery, and tests we've already built.
The architectural choice behind it: provider differences live in typed contracts. Authentication, pagination, validation, and durable execution run through shared machinery. That is how we've been able to expand our integrations without starting another software project for each API.
Stop rebuilding the execution path#
Consider what happens when each integration gets its own implementation. Someone writes the authentication code, then a pagination loop, then response mapping. The integration needs retries, credential handling, scheduling, and a way to recover after a worker stops. The next integration needs those things again.
Even with AI writing the code, separate implementations leave separate places to fix the same defect.
Our integration service is written in Rust. Providers are declared in TOML and compiled into typed contracts. Shared interpreters use those contracts to construct requests, resolve credentials, validate responses, map records, and traverse resources. Tenant-scoped connections and sync progress live in PostgreSQL.
The split looks like this:
| Provider-specific facts | Shared implementation |
|---|---|
| Where a credential goes and which origins may receive it | Credential resolution and request authentication |
| Endpoint, method, headers, and request body | Request construction and execution |
| Record location, primary key, and field types | Response validation and record mapping |
| Cursor location and continuation rules | Pagination and continuation checks |
| Resource relationships | Parent-to-child traversal |
| Declared sync resources | Queue admission, leases, persisted progress, and delivery |
A new provider that fits these mechanisms can be added through its manifest and fixtures. A provider that needs a new mechanism requires a schema, validator, and interpreter change. Subsequent providers can then declare that behavior through the same contract.
The details still matter: Linear pagination#
Linear is a useful example because a generic “call a REST endpoint and follow a next-page URL” adapter would miss its behavior.
Linear's API uses GraphQL. Our teams stream sends a POST to /graphql; records come from data.teams.nodes, and the next cursor goes into variables.after in the JSON request body. Here is the relevant excerpt from the provider manifest:
[[runtime.families]]
id = "teams"
display_name = "Teams"
path = "/graphql"
event_kind = "linear.team"
auth_credential_fields = ["access_token"]
record_selector = "data.teams.nodes"
primary_key = ["id"]
[runtime.families.pagination]
kind = "body_cursor"
cursor_param = "variables.after"
cursor_response_path = "data.teams.pageInfo.endCursor"
The request template supplies the GraphQL query. The response contract supplies the field types. The shared runtime handles continuation, including the case where the last page still has an endCursor: it checks the sibling hasNextPage value before continuing. That follows Linear's documented pagination contract.
There is provider-specific knowledge here. We've given that knowledge an explicit representation that the compiler can check and the runtime can execute. The next API using the same pagination mechanism reuses the implementation.
Compile from sources we can reproduce#
First-party API specifications give us a starting point for authoring. Our batch compiler accepts reviewed OpenAPI and Postman inputs and produces typed provider manifests. Hand-authored manifests use the same provider type and validation path.
An API specification does not settle every integration decision. We still need to establish which operations are useful, where credentials belong, how records are identified, and how collections continue. Reviewed overlays supply those choices explicitly. In our Clerk source batch, for example, the overlay selects session, user, and organization list operations from Clerk's first-party OpenAPI specifications.
We retain the source document with its digest, upstream revision or snapshot identity, retrieval information, and license evidence. Compilation reads those retained bytes. Fetching a newer specification is a separate operation.
That separation makes a review reproducible. A reviewer can inspect the same source that produced the manifest, even after the vendor updates its documentation.
The batch compiler also handles dependencies. It compiles nodes in a stable dependency order and writes a generation lock containing the input and output digests. A dependency change invalidates the affected descendants. Cached output is reused only when its recorded identities and actual bytes match.
Independent nodes can compile concurrently. The new output directory replaces the previous generation only after the batch succeeds, so a failed import cannot leave half a generation installed. Check mode recompiles and rejects changed, missing, or extra artifacts.
Give coding agents a specific job#
The factory also makes integration work concrete enough for coding agents to carry out.
Our capability-gap compiler compares provider definitions with retained source and verification evidence. It produces work packets with exact provider and capability identities, prerequisites, missing evidence, and acceptance commands. An unresearched operation remains a research task; an absent fixture remains a testing task.
That gives an agent a more useful assignment than “add support for this vendor.” The assignment can name one resource stream, the official contract to review, the request and response behavior to encode, and the tests that must pass.
The authoring loop has scaffold, dry-run, and verification commands. Agents can inspect an existing executable stream, propose the corresponding manifest changes, add scrubbed fixtures, and regenerate the derived artifacts. The compiler and tests report what the proposal is missing.
This is where the shared architecture pays off in agent-assisted development: the agent has fewer implementation choices to invent. It works within a provider schema and an existing execution path. New runtime behavior remains a separate engineering change with its own validation.
Test the path that will run the integration#
A plausible request is only one part of an integration. It can authenticate successfully and still select the wrong records, stop after the first page, lose progress, or fail to deliver its results.
Fixture expectations therefore need independent review against the vendor contract. Generating the expected response from the same manifest being tested would let both sides agree on the same mistake.
Our PostgreSQL provider-outcome harness exercises the production queue, leases, runner, durable events, acknowledged export, and replay. It checks the delivery path as well as decoding. A skipped database test cannot supply that evidence.
We also generate and execute adversarial scenarios for failure classes such as malformed responses, repeated cursors, missing primary keys, credential leakage, retry responses, and excessive traversal fan-out. The evidence binds to the compiled stream, so changing the contract invalidates evidence for the previous version.
Review can focus on the provider's contract and observed outcomes while the common harness exercises the execution machinery.
We keep the resulting states separate. A definition describes an integration. Fixture execution tests it against retained responses. Catalog publication makes a reviewed revision available. Customer credentials and a first live sync establish what works for that connection. An API version, an authentication variant, or another endpoint does not become another integration in our count.
Maintenance goes through the same compiler#
An integration factory also needs a way to handle the vendor's next API change.
Our source-refresh tooling stages a new specification and compiles both the old and new versions. It produces a review showing changes to the typed representation and generated manifest, along with coverage, conformance, and breaking-change information. Refreshable sources can open a pull request through that process.
The checked-in source stays unchanged until the update is reviewed. Changing a source or overlay also changes the relevant generation identity; changing shared runtime inputs expires verification receipts that depend on them.
That gives maintenance the same source-to-contract-to-execution path as initial development. The team can review what changed and rerun the affected checks without rediscovering how the integration was assembled.
Weeks-long waits deserve an architectural explanation#
There are legitimate external delays: partner approval, inaccessible APIs, missing permissions, and business semantics that need a customer's decision. A compiler cannot grant access to an API the vendor will not expose.
But when a documented API fits mechanisms the platform already implements, rebuilding authentication, pagination, worker execution, and delivery for it is a cost imposed by the platform's architecture.
If you're waiting weeks for an ordinary integration, you're probably encountering a structural limitation of the platform you're working with. Ask which parts of the integration reuse tested machinery, which behavior must be implemented from scratch, and how the platform will maintain it after the API changes.
We think companies should be moving toward this model: provider contracts that machines can inspect, shared execution that each integration inherits, and tests that check the resulting behavior. AI-assisted authoring makes that architecture especially useful because agents can work against explicit inputs and acceptance criteria.
For a team doing security and compliance work, the practical consequence is straightforward: adopting another system should not automatically create another multi-week integration project. Bring the system and the operations your workflow needs to a conversation with us. We can start with the API and permission requirements before asking for production access.