TechFabricTechFabricPlatform
Composable application standard

Integrations and ports

Defining a port for any external system, and why registering an adapter is a claim rather than evidence.

An enterprise integrates hundreds of external systems: document signing, tax calculation, credit bureaux, telematics, address validation, dealer management, a dozen internal services nobody outside the company has heard of. Fabric cannot enumerate them, and a framework that needs a pull request per integration is not a framework.

So ports are a mechanism you use, not a list you pick from.

Defining a port

import { definePort } from "@fabricorg/ports";

export const DOCUSIGN_PORT = definePort<DocuSignPort>({
  id: "acme.docusign",
  version: "1.0.0",
  description: "Sends an envelope for signature, once per envelope id.",
  checks: () => [/* the suite every adapter must pass */],
});

Define it in your own package. Fabric never learns what the system behind it is, and every mechanism here applies unchanged.

The id is namespaced because a port defined outside this repository must not collide with one defined inside it. The version is the contract's version, not any adapter's: raise it when the interface changes shape, and every adapter certified against the old contract correctly stops counting.

Registration is a claim; certification is evidence

A capability declares the ports it needs. An adapter declares the port it implements. Nothing in that exchange demonstrates the adapter works.

const certified = await certifyAdapter({
  definition: DOCUSIGN_PORT,
  fixtures,
  adapter,
  vendor: "docusign",
});

certifyAdapter runs the port's own suite and records what passed, against which contract version. Deployment can then require that evidence be present, well-formed, about this port, and issued against the current contract version:

assertPortRequirementsSatisfied({
  capability,
  adapters,
  definitions: [DOCUSIGN_PORT],
  requireCertification: true,
});

Be precise about what the gate proves. It confirms the certification names this port, carries a semver contract version matching the definition in force, lists at least one check, and parses. It cannot confirm the listed checks are the definition's suite, because the definition's checks need fixtures the gate does not have. Certification therefore keeps a careless adapter out and a stale one out; it does not keep out a deliberately forged record. Treat the certification store as trusted input, the way you treat the lockfile.

Supplying definitions alone catches a stale certification — an adapter certified against a contract version that has since moved. Pass requireCertification to also reject an adapter carrying no evidence at all. The two are separate on purpose: adopting definitions should not break a deployment that has not yet certified anything.

Prove your suite can fail

A suite whose checks pass against a deliberately broken adapter looks rigorous and certifies nothing.

await assertPortSuiteHasTeeth(DOCUSIGN_PORT, fixtures, [returnsNothing, returnsGarbage]);

It takes a set of broken adapters, one per way of being wrong, because a single stub cannot trip both a fixture-liveness guard and the checks that guard protects: the guard bites when nothing comes back, and the checks bite when something malformed does.

Write this test when you author the port, not after an adapter fails in production.

The reference ports

Eight ship in @fabricorg/ports/catalog: flags, design tokens, identity, content, payments, search, customer records, and completion ingress. They are examples of the pattern rather than the extent of it. What earns a place there is speaking an open standard — OpenFeature, W3C DTCG, OAuth2/OIDC — or having a failure mode general enough to be worth stating once.

Those failure modes are worth reading even if you use none of the seven, because each suite encodes what that class of integration actually gets wrong:

PortThe mistake its suite catches
ContentInventing a default for an unauthored key, or carrying a capability reference
PaymentsMoney as a float, or an idempotency key that moves it twice
SearchTreating a hit as authorization rather than a pointer a governed read still resolves
Customer recordsRedelivery duplicating a record
IdentityClaims whose timestamps the scope check will then reject on every request

PaymentOutcome includes ambiguous, which most payment interfaces omit and which matters most: a timeout tells you nothing about whether the charge landed. It maps onto the platform's own adapter outcomes, so an uncertain charge reconciles instead of being retried.

Completing work that finishes elsewhere

An external system that finished work the host handed it calls back. The host settles the invocation by reference through its completion ingress, but that method takes invocation, tenant, space, and provider as trusted arguments. What stands in front of it is an adapter the vertical builds, because authentication schemes and reference formats are the provider's business, and the platform owns the suite that adapter has to pass. fabric.completion-ingress proves behaviour and prescribes no mechanism: the adapter derives every scope field from server-held state and never from the payload, refuses an unverifiable credential, an unknown reference, a provider the reference is not bound to, and a reference belonging to another tenant, forwards an exact replay and lets the host absorb it, forwards changed evidence and lets the host reconcile it, leaves a secret-free audit record for every refusal, and holds up when identical callbacks arrive together.

const certified = await certifyAdapter({
  definition: COMPLETION_INGRESS_PORT,
  fixtures: { provider, validCredential, boundReference, expectedScope, secrets, /* ... */ },
  adapter: renderCallbackAdapter,
  vendor: "renderco",
});

The fixtures are yours: a credential your adapter verifies, a reference your binding resolves, a reference bound to another tenant, and the secrets that must never surface. The suite has its own teeth, proven in this repository against adapters that trust the payload's scope, accept any credential, leak the signing key, or call changed evidence a replay.

What a port never becomes

A port is reached from a capability, never the other way round. A content system decides what a screen says and never what it may reach; a search index knows an identifier exists and never that this actor may read it; a CRM is told and never asked. Keeping that direction is what stops an integration becoming a source of business truth.

On this page