The first thing built against the mold. An adapter takes a canonical transaction, gets it authorized by one processor, and returns a canonical result — nothing more. Same contract for every processor. In the native C engine this is a function-pointer vtable (fdy_processor_adapter_vtbl_t); the Kotlin SDK exposes the same shape as an interface. This spec defines that contract and walks a worked example, then lists the processors shipped against it.
An adapter is a pure translator bounded on both sides. Canonical on the inside (the mold from doc 03), a published host spec on the outside (ISO 8583 / 20022). It never touches the kernel, never sees raw TLV, never makes an EMV decision — it formats, sends, parses, and returns.
The assembled object — amount, currency, panToken, cryptogram, aid, risk tags. Already normalized at the seam; the adapter only reads it.
Fills authResponse — response code, auth code, ARPC, network refs. The engine feeds it to the kernel via emv_completeTransaction. Adapter is done.
Foundry defines the adapter contract; each processor implements it. In the C engine that contract is fdy_processor_adapter_vtbl_t, a struct of function pointers. The engine depends only on this vtable — never on a concrete processor or its library. A single authorize() branches on the canonical transaction type (PURCHASE, REFUND, VOID, AUTH_ONLY, COMPLETION) rather than widening the vtable per operation. That dependency direction is the whole reuse guarantee.
authorize(canonical)
Canonical in → host message → send → parse response → AuthResult out. The one call the engine makes at the online-auth step. Everything else supports this.
tlvFilterPolicy()
The adapter declares which tags it needs and in what order. Foundry's getTlvView(policy) derives the processor-specific TLV set without mutating canonical. NAB's policy ≠ Santander's.
buildHostMessage(view)
Assembles the actual host request — ISO 8583 fields or ISO 20022 elements, DE 55 / ICC data built from the filtered view. This is where the processor's dialect lives.
parseHostResponse(bytes)
Reads the response back to canonical — response code, auth code, ARPC, network references. Maps the processor's codes onto Foundry's normalized result enum.
keyContext()
Supplies the processor's key/HSM specifics — zone keys, working-key rotation, MAC scheme — through the common crypto interface. The adapter provisions; it never implements primitives.
capabilities()
What this processor supports — currencies, message format, reversal/advice handling, partial-auth. Lets the engine know whether a hasReversal flag needs an action.
fdy_processor_adapter_vtbl_t contract, never a concrete adapter. A registry resolves the active adapter at runtime. So NAB's ISO 8583 build, TSYS's gateway dialect, and Santander's 0200 format never share a dependency graph — each is sealed inside its own casting. Adding a processor in the C engine is mechanical: create a src/<name>/ directory, add one line to the build PROCESSORS list, and one #include to processors.h. No engine change. That is Checkpoint A — proven across the shipped roster.
Everything universal stays in Foundry; only the processor-specific translation lives in the adapter. If a casting starts reaching back into kernel or EMV logic, the seam has leaked.
getTlvView() / buildDOL() engineemv_completeTransaction, ctls_displayOnlineAuthResulttlvFilterPolicy — which tags, what orderkeyContext()The production adapters built against fdy_processor_adapter_vtbl_t in the native C engine, each sealed in its own src/<name>/ directory with no shared dependency graph. The full roster — shipped and planned — lives on the Processor Castings page; the engine suite runs green under -std=c99 -Wall -Wextra -Wpedantic.
FDY_ERR_NOT_IMPLEMENTED pending host spec + test endpoint. USD · MXN.Each adapter carries two TODO seams — the ISO 8583 / schema build and parse functions — where live host wiring lands. These are pure functions isolated from routing and I/O, safe to upgrade without restructuring. Not yet wired: live HTTP transports, a credential/config store, and the adapter factory.
Adapter #1. What authorize(canonical) does, end to end, the first time the engine calls it at the online-auth step.
amount, currency (840/484), panToken, cryptogram, aid, tvr, atc — by name, not tag.getTlvView(santanderPolicy) to get exactly the tags Santander wants for DE 55, in their required order. Canonical stays untouched.buildHostMessage() assembles Santander's ISO 8583 0200 authorization with DE 55 ICC data, applying the MAC via keyContext(). The processor dialect lives entirely here.0210 response. Timeout / retry policy is the adapter's, declared in capabilities().parseHostResponse() extracts response code, auth code, and ARPC, maps Santander's codes onto Foundry's result enum, and returns an AuthResult. The casting's job ends here.emv_completeTransaction + ctls_displayOnlineAuthResult feed the ARPC to the kernel. Universal again — the adapter is no longer in the loop.The adapter spec exists to make this question answerable. One casting proves the interface compiles; two castings prove it's reusable. That measurement has now been run: multiple processors — NAB, TSYS, and the Santander (Mexico) casting — were built against the same fdy_processor_adapter_vtbl_t contract with zero core files changed. That validates the entire Foundry thesis in practice, not just on paper.