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
Seven ship in @fabricorg/ports/catalog: flags, design tokens, identity, content, payments, search
and customer records. 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:
| Port | The mistake its suite catches |
|---|---|
| Content | Inventing a default for an unauthored key, or carrying a capability reference |
| Payments | Money as a float, or an idempotency key that moves it twice |
| Search | Treating a hit as authorization rather than a pointer a governed read still resolves |
| Customer records | Redelivery duplicating a record |
| Identity | Claims 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.
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.