Protocol adapters are one of the easiest places for agent-commerce architecture to drift.
An adapter begins with the narrow responsibility of translating an external protocol request into something the commerce platform understands. For example, an MCP-style tool may ask for return terms, an ACP-style interaction may ask whether checkout can be prepared, an AP2-related flow may carry payment authority information, and an internal feed may publish product capabilities.
Those are adapter concerns at the boundary.
The problem starts when the adapter does more than translate. It checks product availability from catalog fields. It interprets policy text. It decides whether checkout is ready. It treats a payment artifact as authority. It turns a domain blocker into a softer protocol response.
Each shortcut may solve an integration problem locally, but it also creates a second place where commercial meaning is decided.
When several adapters exist, those local decisions begin to diverge. The MCP tool may block return-policy quotation, the ACP adapter may expose the product as purchasable, the feed may publish it as checkout-ready, and the AP2-related flow may reject delegated payment.
At that point, the platform does not only have multiple integrations. It has multiple interpretations of the same commercial state.
This is the adapter problem in agent-ready commerce: semantic drift at the protocol boundary.
The adapter should know how to speak the protocol. It should not decide product truth, policy meaning, eligibility, checkout validity, or payment authority. Those decisions belong inside the commerce platform, where they can be shared, tested, evidenced, and audited.
This is the fifth article in the Agent-Ready Commerce series.
Part 1 introduced the broader architecture model:
Facts → Eligibility → Authority → State transition → Evidence → Audit
Part 2 focused on commercial truth. It argued that catalog data is not enough. A platform needs source-backed, freshness-aware product facts before agents or other systems can safely rely on product information.
Part 3 focused on action eligibility. It argued that “available” is too broad. A product may be discoverable but not checkout-ready, comparable but not policy-quotable, or checkout-ready for a human flow but not eligible for delegated payment.
Part 4 focused on policy structure. It argued that agents should not interpret free-text policy pages as executable rules. Returns, shipping, warranty, cancellation, regional restrictions, and business-buyer rules need structured policy facts with applicability, evidence, lifecycle, conflicts, and quoteability.
This article focuses on protocol adapters.
The central argument is that ACP, MCP, AP2, internal feeds, marketplace APIs, and future agent-facing interfaces should not become separate commerce engines. They should translate external protocol language into canonical platform intent, call the same domain decision layer, and project the result back into protocol-specific shape.
The names of the protocols are less important than the boundary they create. Whether the surface is a tool interface, an agent-commerce protocol, a payment protocol, a marketplace API, or an internal feed, the same design rule applies:
The adapter may translate the decision.
It must not become the source of the decision.
The failure mode: semantic drift
Semantic drift happens when two parts of the system use the same words but no longer mean the same thing.
In a traditional commerce platform, this already happens with terms like active, available, in_stock, or sellable. One service interprets available as “visible on the storefront.” Another interprets it as “can be added to cart.” Another interprets it as “can be paid for.” The result is inconsistent behavior.
Agent-facing protocols make this risk more explicit because they introduce more external vocabularies.
One protocol may ask:
Can this product be purchased?
Another may ask:
Can checkout be prepared?
A tool interface may ask:
Return the return policy for this product.
A payment protocol may ask:
Can this agent request delegated payment for this cart?
A feed may publish:
checkoutEligible: true
Those requests may sound related, but they are not identical. If each adapter decides what the request means on its own, the platform accumulates slightly different interpretations of the same commercial reality.
For agent-ready commerce, that is the core adapter risk.
The adapter becomes a place where protocol vocabulary is translated directly into local business rules:
// Bad: the adapter invents purchase readiness.
function canPurchaseFromProtocolRequest(product: Product) {
return product.active && product.inventoryStatus === "in_stock";
}
That code may be understandable inside one adapter. It is also incomplete. It ignores policy coverage, fact freshness, generated-claim review, regional restrictions, buyer type, checkout state, payment authority, and other decision inputs discussed in Parts 2–4.
Worse, another adapter may implement a different shortcut:
// Also bad: a second adapter creates a different meaning.
function canPrepareCheckoutForTool(product: Product) {
return product.active && product.price.amount > 0;
}
Now the platform has two meanings for a checkout-related action. Neither may match the domain.
This is how semantic drift enters the system.
Thin adapters are about authority, not code size
The phrase “thin adapter” can be misleading.
A thin adapter is not necessarily small. It may contain protocol parsing, schema validation, capability projection, error mapping, idempotency propagation, observability, authentication context extraction, pagination, response shaping, and compatibility logic.
That is real work.
Thinness is not measured by line count. It is measured by authority.
A thin adapter does not own commercial meaning.
It does not decide whether a product fact is reliable. It does not interpret return policy text. It does not decide whether a policy claim is quoteable. It does not determine whether checkout can advance. It does not infer payment authority from a protocol artifact. It does not turn a blocked domain decision into a softer answer because the protocol expects helpful output.
A thin adapter is allowed to know the protocol. It is not allowed to become the platform’s second commerce brain.
A better definition is:
A thin adapter owns protocol translation.
The domain owns commercial decisions.
That distinction allows the adapter to be substantial without making it dangerous.
The running example
Continue with the Travel Backpack example from Parts 2, 3, and 4:
Product: Travel Backpack
SKU: BAG-TRAVEL-42
Price: €129
Catalog status: active
Inventory status: in_stock
Category: Travel Bags
The commercial truth layer currently says:
Price: fresh
Inventory: stale
Return policy: missing for Travel Bags
Warranty policy: known
Shipping policy: known for EU, unknown for US
Generated description: pending review
Feed publication: last published yesterday
From Part 3, the action matrix was:
| Action | Result |
|---|---|
discover |
allowed |
compare |
allowed |
quote_policy |
blocked |
add_to_cart |
requires_revalidation |
prepare_checkout |
blocked |
delegate_payment |
blocked |
Part 4 added detail to the policy side:
Warranty policy: known and potentially quoteable
EU shipping: known and potentially quoteable
US shipping: unknown
Return policy for Travel Bags: missing
Now imagine five different external surfaces:
Internal agent product feed
MCP-style tool endpoint
ACP-style commerce interaction
AP2-style payment-related flow
Admin UI for operator remediation
Each surface may need a different response shape. That is normal.
The feed may need compact capability flags. The tool endpoint may need a structured answer for policy quotation. The commerce interaction may need a checkout-preparation response. The payment flow may need to know whether delegated payment can proceed. The admin UI may need blockers and remediation tasks.
The response shape can differ.
The underlying domain decision should not.
For the Travel Backpack, every surface should converge on the same commercial facts:
The product can be discovered.
The product can be compared on known facts.
Return-policy quotation is blocked.
US shipping quotation is blocked.
Checkout preparation is blocked.
Delegated payment is blocked.
If the product feed says checkout is available, the tool endpoint says return terms are unavailable, and the payment flow blocks delegated payment, the platform needs to explain why. If it cannot, the adapters are probably making decisions independently.
The adapter boundary
A useful adapter boundary has five steps:
External protocol request
↓
Protocol adapter
↓
Canonical intent
↓
Domain decision
↓
Protocol projection
Each step has a different responsibility.
The external protocol request is shaped by the protocol. It may contain protocol-specific fields, message envelopes, tool arguments, payment artifacts, request identifiers, actor claims, or conversation context.
The protocol adapter understands that external shape and maps it into the platform’s internal vocabulary.
The canonical intent describes what the caller is asking the commerce platform to evaluate or perform.
The domain decision evaluates commercial truth, policy facts, eligibility, authority, checkout state, and evidence.
The protocol projection converts the domain decision back into the external response format.
This is the spine of the article.
If the adapter skips canonical intent and calls random services directly, the protocol starts shaping the domain.
If the adapter creates the decision itself, the platform develops parallel business logic.
If the adapter hides the domain decision during projection, the external surface becomes difficult to debug and audit.
Canonical intent is the missing layer
A protocol action is not automatically a domain action.
Protocol actions are external language. The platform needs internal intent.
For example, one protocol may expose a tool called:
get_return_terms
Another may expose:
describe_policy
A third may ask for:
product.purchase_constraints
The platform should not implement three separate policy interpreters. It should map those requests into canonical intent:
quote_policy
A simple internal action vocabulary might look like this:
type CommerceAction =
| "discover"
| "compare"
| "quote_policy"
| "add_to_cart"
| "prepare_checkout"
| "delegate_payment";
The adapter can map protocol-specific actions to these internal actions:
type ProtocolName =
| "internal_feed"
| "mcp"
| "acp"
| "ap2"
| "marketplace_api"
| "admin";
type ProtocolActionMapping = {
protocol: ProtocolName;
externalAction: string;
internalAction: CommerceAction;
};
const mappings: ProtocolActionMapping[] = [
{
protocol: "internal_feed",
externalAction: "publish_product",
internalAction: "discover"
},
{
protocol: "mcp",
externalAction: "get_return_terms",
internalAction: "quote_policy"
},
{
protocol: "acp",
externalAction: "prepare_purchase",
internalAction: "prepare_checkout"
},
{
protocol: "ap2",
externalAction: "request_delegated_payment",
internalAction: "delegate_payment"
}
];
This mapping does not solve the whole problem, but it prevents an important failure mode. The protocol’s vocabulary does not become the platform’s business model by accident.
Canonical intent needs context
An action name alone is not enough.
The platform needs context to evaluate the action correctly.
For the Travel Backpack, quote_policy depends on policy domain, region, buyer type, and channel. EU shipping may be quoteable. US shipping is unknown. Consumer terms may differ from business-buyer terms. Agent-channel quotation may be stricter than a human-readable storefront link.
A canonical request should therefore carry normalized context:
type BuyerType = "consumer" | "business";
type Channel = "storefront" | "agent" | "marketplace" | "support";
type ActorContext = {
actorId: string;
actorType: "human" | "agent" | "system";
buyerId?: string;
buyerType?: BuyerType;
authorityRef?: string;
};
type CommerceIntent = {
action: CommerceAction;
productId?: string;
sku?: string;
cartId?: string;
policyDomain?: "returns" | "shipping" | "warranty" | "cancellation";
context: {
region?: string;
currency?: string;
buyerType?: BuyerType;
channel: Channel;
};
actor: ActorContext;
idempotencyKey?: string;
correlationId?: string;
requestedAt: string;
};
The adapter’s job is to build this object from protocol-specific input. The domain’s job is to evaluate it.
This is an important boundary. The adapter may normalize country = DE into region = EU if that mapping is part of platform infrastructure. It may extract actor identity. It may pass an authority reference. It may include an idempotency key. But it should not decide that the product is checkout-ready because the request contains a supported country and a valid-looking cart.
Context is input. It is not a decision.
Projection is not decision-making
After the domain evaluates an intent, the adapter has to return a protocol-specific response.
That response may look different for each protocol.
One protocol may need an error code. Another may need a tool result. Another may need a list of supported actions. Another may need a payment-related refusal. Another may need a feed item.
That is projection.
Projection is allowed.
Decision-making is not.
A canonical domain decision might look like this:
type DecisionResult =
| "allowed"
| "blocked"
| "requires_review"
| "requires_revalidation";
type DecisionBlocker = {
code: string;
message: string;
nextAction?: string;
};
type EvidenceRef = {
type:
| "commercial_truth"
| "policy_fact"
| "eligibility_rule"
| "authority_record"
| "checkout_state"
| "audit_event";
id: string;
};
type CommerceDecision = {
action: CommerceAction;
result: DecisionResult;
allowed: boolean;
blockers: DecisionBlocker[];
warnings: DecisionBlocker[];
evidenceRefs: EvidenceRef[];
evaluatedAt: string;
};
For the Travel Backpack, a return-policy quotation decision might be:
const decision: CommerceDecision = {
action: "quote_policy",
result: "blocked",
allowed: false,
blockers: [
{
code: "RETURN_POLICY_MISSING",
message:
"No active quoteable return-policy fact applies to Travel Bags for this buyer context.",
nextAction:
"Attach or approve return-policy coverage for the Travel Bags category."
}
],
warnings: [],
evidenceRefs: [],
evaluatedAt: "2026-06-28T09:00:00Z"
};
The MCP-style tool adapter may project that into a tool result. An ACP-style adapter may project it into a commerce response. An internal feed may project it into quotePolicy: "blocked". An admin UI may project it into a remediation task.
Those projections may differ.
The domain decision should remain the same.
Capability is not readiness
Agent-facing protocols often need some form of capability discovery. A caller may ask what operations the platform supports.
This creates another common source of drift.
There are at least three different concepts that are often collapsed into one field:
Protocol capability:
Can this interface express the operation?
Platform capability:
Does the platform support the operation at all?
Contextual readiness:
Is the operation currently valid for this product, buyer, region, channel, and actor?
These are not the same.
A platform may support delegated payment in general, but not for the current cart. A protocol may support policy quotation, but the product may not have quoteable return terms. A feed may support checkout eligibility flags, but this product may be blocked because inventory is stale.
A bad adapter exposes global support as contextual readiness:
// Bad: global capability is treated as product readiness.
const response = {
canQuotePolicy: true,
canPrepareCheckout: true,
canDelegatePayment: true
};
A better model separates the layers:
type ProtocolCapability = {
action: CommerceAction;
expressibleByProtocol: boolean;
};
type PlatformCapability = {
action: CommerceAction;
supportedByPlatform: boolean;
};
type ContextualReadiness = {
action: CommerceAction;
result: DecisionResult;
blockers: DecisionBlocker[];
};
For the Travel Backpack:
Protocol can express checkout preparation.
The platform supports checkout preparation.
This product and context are not checkout-ready.
That distinction is essential.
If a protocol adapter exposes only canPrepareCheckout: true, the external agent may interpret it as readiness. If it exposes the contextual decision, the agent can behave correctly.
A bad adapter hides the actual failure
Consider this simplified adapter implementation:
async function preparePurchaseAdapter(request: ProtocolRequest) {
const product = await catalog.getProduct(request.sku);
if (!product.active || product.inventoryStatus === "out_of_stock") {
return {
status: "blocked",
reason: "not_available"
};
}
return {
status: "ok",
checkoutUrl: await checkout.createSession(product.id)
};
}
This looks practical, but it has several problems.
It reads catalog state directly instead of commercial truth. It treats stock status as checkout readiness. It does not evaluate policy coverage. It does not check whether generated claims are approved. It does not consider buyer region, buyer type, or channel. It does not check authority. It mutates checkout state directly from the adapter. It reduces all blockers to not_available.
A better adapter does less commercial work:
async function preparePurchaseAdapter(request: ProtocolRequest) {
const intent = mapProtocolRequestToIntent(request);
const decision = await commerce.evaluateIntent(intent);
return projectDecisionToProtocolResponse(decision);
}
That code is shorter, but the point is not shortness. The point is that the adapter no longer owns checkout readiness.
The domain can now evaluate the same intent consistently across protocols.
A better domain path
For a state-changing operation such as checkout preparation, the domain path should be explicit:
Protocol request
↓
Adapter maps request to canonical intent
↓
Commercial truth is selected
↓
Policy coverage is evaluated
↓
Eligibility decision is produced
↓
Authority is checked
↓
Checkout service validates current state
↓
State transition is attempted
↓
Evidence and audit are recorded
↓
Adapter projects the result
This is more work than reading product.active.
It is also the difference between a protocol integration and a commerce platform.
For the Travel Backpack, the flow should block checkout because the required policy coverage is incomplete and inventory is stale. That decision should not depend on whether the request arrived through MCP, ACP, a feed-driven agent, or a future protocol.
Tool descriptions are not commercial authority
Tool-based integrations introduce another subtle problem.
A tool may be described in natural language:
Use this tool to get the return policy for a product.
That description is useful for the agent. It is not a commercial guarantee.
If the platform cannot quote return policy for the Travel Backpack, the tool must be allowed to return a blocked result. The description should not imply that every product has quoteable return terms.
A safer tool description is:
Use this tool to request quoteable return-policy information for a product and buyer context. The tool may return a blocked result when active, source-backed policy coverage is missing, stale, conflicting, or not quoteable.
This is less convenient, but it matches the system boundary.
The tool tells the agent what it can ask for. The domain decides what can be answered.
Without that distinction, tool descriptions become a soft form of policy. They promise behavior that the platform may not be able to support.
Payment artifacts are inputs, not authority
Payment-related protocols create higher-risk boundary problems because the adapter may receive artifacts that look authoritative: mandates, signed messages, payment instructions, buyer constraints, merchant identifiers, amount limits, or cart references.
Those artifacts matter. The adapter should preserve them and pass them into the platform.
But the adapter should not decide what they authorize.
A payment artifact is an input to authority evaluation. It is not authority by itself unless the authority domain says so.
For delegated payment, the platform may need to evaluate:
Is the checkout session valid?
Does the cart snapshot match the authorized cart?
Is the amount within the allowed limit?
Does the currency match?
Is the merchant within scope?
Has the authority expired?
Has it been revoked?
Is human confirmation required?
Is the actor allowed to request this transition?
A simplified readiness model might be:
type DelegatedPaymentReadiness = {
checkoutSessionValid: boolean;
authorityPresent: boolean;
mandateWithinScope: boolean;
cartSnapshotMatches: boolean;
amountWithinLimit: boolean;
currencyMatches: boolean;
merchantMatches: boolean;
notExpired: boolean;
notRevoked: boolean;
humanConfirmationSatisfied: boolean;
};
The adapter may validate that the protocol message is well-formed. It may verify envelope requirements if that belongs at the protocol edge. It may extract the authority reference. But it should not conclude that delegated payment is allowed merely because the artifact exists.
Protocol validity and commercial authority are different decisions.
This is one of the most important boundaries in the series.
Idempotency belongs where state changes
Agent-facing integrations will retry.
A tool call may be repeated. A network response may be lost. A checkout preparation request may time out after the checkout session is created. A payment attempt may return an ambiguous result. An agent may call the same operation again after receiving no response.
The adapter should propagate idempotency and correlation information, but state-changing idempotency belongs at the application service or state-transition boundary.
The adapter can build:
type IdempotencyContext = {
idempotencyKey: string;
correlationId: string;
protocol: ProtocolName;
externalRequestId?: string;
};
But it should not independently decide whether the mutation already happened.
Only the state-owning service can know whether a checkout session was created, whether inventory was reserved, whether payment was attempted, or whether a previous transition completed after the adapter lost the response.
The correct boundary is:
Adapter:
Extracts or creates idempotency context.
Application service:
Uses idempotency context when performing state transitions.
Audit:
Records the request, decision, and transition result.
If idempotency exists only in the adapter, retries can still create duplicate carts, duplicate checkout sessions, or inconsistent payment attempts through other surfaces.
Protocol errors must preserve domain meaning
Protocols often require simplified error categories.
Examples:
invalid_request
not_found
not_allowed
failed_precondition
conflict
internal_error
Those categories are useful, but they are not sufficient for commerce operations.
If the domain blocks checkout because return-policy coverage is missing, that reason should survive projection.
A weak adapter response is:
{
"error": "not_allowed"
}
A better response is:
{
"error": "failed_precondition",
"reason": "RETURN_POLICY_MISSING",
"message": "Return-policy terms cannot be quoted for this product category."
}
The external response does not need to expose every internal evidence reference. Some details may be internal-only. But the adapter should not erase the blocker code.
Blocker codes are useful to agents, operators, logs, tests, and audits.
A protocol-level error says what happened at the interface. A domain blocker says why the commerce platform refused the action.
Both are needed.
Adapters should not convert blockers into fallback claims
Agent-facing systems create pressure to be helpful.
If return terms are missing, an adapter may be tempted to return:
The standard return policy is usually 30 days, but please check the merchant site.
That is not a translation. It is a new policy claim.
If US shipping is unknown, the adapter may be tempted to return EU shipping terms with a warning. If checkout is blocked, it may create a session and mark it as requiring review. If payment authority is incomplete, it may continue with a reduced scope.
Those behaviors may be valid in some business contexts, but they are domain decisions. They should be modeled explicitly and evaluated by the same decision layer.
A blocker should not be softened at the adapter because the adapter is closer to the agent.
For the Travel Backpack, if the return policy is missing for Travel Bags, the adapter should preserve that outcome:
Return-policy quotation is blocked because no active quoteable return-policy fact applies to Travel Bags in this buyer context.
It may provide a next action. It may provide a source link if allowed. It may suggest contacting support. But it should not invent the return terms.
Internal feeds are adapters too
A feed may not feel like a protocol adapter because it is not interactive. It may simply publish products for downstream agents or search systems.
But a feed is still an external projection of domain state. It can drift.
If a feed publishes the Travel Backpack as checkout-ready while the checkout service would block it, downstream systems may make incorrect assumptions. If the feed includes generated descriptions that are pending review, agents may quote unapproved claims. If the feed includes a policy summary but policy quoteability is blocked, the platform has exposed a claim it cannot support.
A feed item should therefore be a projection of domain decisions, not a shortcut around them.
A compact feed shape might be:
type AgentProductFeedItem = {
productId: string;
title: string;
price: {
amount: number;
currency: string;
};
actions: {
discover: DecisionResult;
compare: DecisionResult;
quotePolicy: DecisionResult;
prepareCheckout: DecisionResult;
delegatePayment: DecisionResult;
};
generatedClaimsStatus: "approved" | "pending_review" | "blocked";
publishedAt: string;
};
For the Travel Backpack:
{
"productId": "BAG-TRAVEL-42",
"actions": {
"discover": "allowed",
"compare": "allowed",
"quotePolicy": "blocked",
"prepareCheckout": "blocked",
"delegatePayment": "blocked"
},
"generatedClaimsStatus": "pending_review"
}
This feed does not expose every internal detail. It does preserve the important decision boundaries.
A feed can be compact without being optimistic.
Admin surfaces should use the same decisions
The admin UI is another place where drift can occur.
Operators may see one status while agents receive another. The admin UI may say a product is “agent-ready,” while the policy adapter blocks quotation and the checkout adapter blocks preparation. That creates operational confusion.
Admin surfaces should use the same domain decisions as protocol adapters, but project them differently.
For the Travel Backpack, an admin view might show:
Agent readiness:
Partially ready
Allowed:
- discover
- compare
Blocked:
- quote return policy
- quote US shipping
- prepare checkout
- delegated payment
Reasons:
- return-policy coverage missing for Travel Bags
- US shipping policy unknown
- inventory stale
- generated description pending review
Suggested actions:
- approve return-policy coverage for Travel Bags
- add or explicitly block US shipping coverage
- revalidate inventory
- review generated product description
That is the same underlying decision model as the adapters. The projection is different because the user is different.
Agents need safe responses.
Operators need remediation.
Auditors need evidence.
The domain decision should support all three.
The domain should not become protocol-shaped
Keeping adapters thin also protects the domain from the opposite failure: becoming shaped by the first protocol the platform supports.
A bad internal model looks like this:
type ProductAgentStatus = {
acpPurchasable: boolean;
mcpReturnToolEnabled: boolean;
ap2PaymentReady: boolean;
feedVisible: boolean;
};
This may seem convenient, but it couples the domain to external surfaces. The next protocol creates another field. A renamed tool requires domain changes. Protocol-specific concerns leak into core commerce logic.
A better internal model is protocol-neutral:
type ProductActionProfile = {
productId: string;
decisions: CommerceDecision[];
};
Adapters can project this profile into protocol-specific fields.
The domain should understand commercial actions:
discover
compare
quote_policy
add_to_cart
prepare_checkout
delegate_payment
It should not need to know every protocol’s field names, tool names, or response conventions.
This is the anti-corruption layer pattern applied to agent-ready commerce.
The adapter should not know too little
There is also an opposite mistake: making the adapter so thin that it becomes a blind pass-through.
A useful adapter must still understand the protocol enough to protect the domain from malformed or misleading input.
It should validate protocol schema. It should reject impossible shapes. It should normalize context. It should preserve request identifiers. It should avoid leaking internal-only evidence into public responses. It should translate domain blockers into protocol-safe language.
Thin does not mean ignorant.
The adapter must know enough to translate correctly. It must not know so much that it becomes the owner of commercial truth.
A useful rule is:
The adapter should know protocol semantics.
The domain should know commerce semantics.
When those responsibilities are reversed, either the adapter becomes a commerce engine or the domain becomes protocol-shaped.
Testing should focus on drift
Adapter tests often focus on happy-path schema behavior:
Given a valid request, return a valid response.
That is necessary, but not enough.
For agent-ready commerce, adapter tests should focus on preventing semantic drift.
Mapping tests
Verify that external protocol actions map to the correct internal actions.
External action:
get_return_terms
Expected internal action:
quote_policy
Context normalization tests
Verify that protocol-specific context is normalized correctly.
Input:
country = DE
buyer_type = consumer
surface = agent
Expected context:
region = EU
buyerType = consumer
channel = agent
Decision preservation tests
Verify that domain blockers survive projection.
Given:
Domain returns RETURN_POLICY_MISSING.
Expected:
Adapter response is blocked and includes RETURN_POLICY_MISSING or a protocol-safe equivalent.
No-fallback tests
Verify that the adapter does not invent policy, checkout, or payment answers when the domain blocks.
Given:
Return-policy quotation is blocked.
Expected:
Adapter does not return generic 30-day return language.
Idempotency propagation tests
Verify that retry identifiers reach the state-owning service.
Input:
externalRequestId = req_123
Expected:
CommerceIntent includes idempotency or correlation context derived from req_123.
Capability projection tests
Verify that global platform support is not exposed as contextual readiness.
Given:
Platform supports prepare_checkout.
Product decision is prepare_checkout = blocked.
Expected:
Adapter projects blocked readiness for this product/context.
Cross-adapter consistency tests
This is the most important class of tests.
For the same product, action, and context, every adapter should expose compatible decisions.
Product:
BAG-TRAVEL-42
Context:
EU consumer, agent channel
Expected across feed, tool, commerce, payment, and admin surfaces:
discover = allowed
compare = allowed
quote_policy: returns = blocked
prepare_checkout = blocked
delegate_payment = blocked
If one adapter exposes checkout readiness and another blocks checkout for the same context, the test should fail.
The goal is not identical JSON. The goal is consistent commercial meaning.
Where this model can go wrong
Thin adapter architecture reduces drift, but it introduces its own failure modes.
The canonical intent becomes too generic
If the internal intent model is too vague, adapters will still need local interpretation.
For example:
action = "handle_request"
is not useful. The domain cannot evaluate meaningful eligibility from it.
The internal action vocabulary should be small, but not ambiguous.
The canonical intent becomes too protocol-specific
The opposite failure is copying protocol vocabulary into the domain.
If internal actions are named after protocol tools, the platform becomes coupled to the current integration surface. A second protocol will either distort the model or add duplicates.
Internal actions should be based on commercial meaning, not protocol naming.
The adapter hides too much information
Some adapters remove context to simplify the domain call. That can produce unsafe decisions.
If buyer type, region, channel, or authority references are missing, the domain may evaluate the action under defaults. Defaults may be wrong.
The adapter should normalize context, not discard it.
Domain blockers are treated as user-facing copy
Blocker messages are useful, but they may not be appropriate for every external response. Some evidence may be internal. Some messages may need protocol-safe wording.
The adapter may rewrite presentation. It should preserve meaning.
The decision layer becomes a bottleneck
If every adapter calls the same decision layer, that layer must be reliable, observable, and performant. Otherwise all external surfaces suffer.
This is not a reason to duplicate logic in adapters. It is a reason to design the decision layer as platform infrastructure.
Protocol-specific security is ignored
Keeping commercial decisions in the domain does not mean the adapter skips protocol-level security.
The adapter may still need to validate signatures, request envelopes, authentication, replay constraints, or protocol-specific requirements before building canonical intent. Those checks are protocol boundary checks, not commercial decisions.
Payment artifacts are normalized too aggressively
A payment-related protocol may contain important details. If the adapter reduces the artifact to a boolean such as hasMandate: true, the authority service loses necessary information.
Normalize the artifact enough for the domain to consume it, but do not erase its constraints.
Admin tools become separate decision systems
Operators may need additional views, filters, and remediation actions. That does not mean the admin UI should compute agent-readiness independently.
Admin tools should project the same decisions with more operational detail.
Tests only cover one adapter
A single adapter can be correct in isolation while the platform is inconsistent across adapters.
Cross-adapter tests are necessary because semantic drift is a system property.
The tradeoff
Keeping adapters thin requires discipline.
It is often faster to implement a rule at the edge. The adapter has the request, the protocol schema, and the immediate integration pressure. A local fix can make the demo work.
The cost appears later.
A policy rule is duplicated. A checkout condition differs between surfaces. A payment artifact is treated as authority in one flow but only as evidence in another. A feed exposes capabilities that the checkout service would block. The admin UI cannot explain why agents behave differently from the product readiness dashboard.
The tradeoff is between local speed and system consistency.
Adapter-local logic is faster to add.
Domain-owned decisions are easier to test, audit, reuse, and explain.
For ordinary integrations, adapter drift may be tolerable for a while. For agent-ready commerce, the threshold is lower because external agents can turn inconsistent responses into buyer-facing claims or attempted actions.
The platform should therefore make the correct path easier than the shortcut:
Map protocol request → build canonical intent → evaluate domain decision → project response
If that path is hard, engineers will put logic in the adapter. The architecture should make the safe boundary convenient.
A practical implementation path
A platform does not need to solve every protocol problem at once.
A practical path is:
Define a small internal action vocabulary.
Start with actions from Part 3:discover,compare,quote_policy,add_to_cart,prepare_checkout, anddelegate_payment.Define canonical intent.
Include product or cart identifiers, policy domain when relevant, region, currency, buyer type, channel, actor context, authority references, idempotency, and correlation metadata.Define canonical decision output.
Returnallowed,blocked,requires_review, orrequires_revalidation, with blocker codes, warnings, evidence references, and evaluation time.Build protocol action mappings.
Each adapter should map external actions to internal actions explicitly.Keep commercial truth, policy facts, eligibility, authority, checkout, and payment decisions outside adapters.
Adapters call domain services. They do not reimplement them.Preserve blocker meaning during projection.
Protocol responses may be simplified, but domain blocker codes should not disappear.Separate capability from readiness.
Expose global support separately from contextual product or cart readiness.Propagate idempotency.
Adapters should pass idempotency and correlation information to state-owning services.Test negative paths.
Missing policy, stale inventory, unsupported region, conflicting policy, pending generated claims, expired authority, and duplicate requests should all have expected behavior.Add cross-adapter consistency tests.
The same product, action, and context should produce compatible decisions across ACP, MCP, AP2, feeds, marketplace APIs, and admin surfaces.
This path prevents the most damaging boundary mistakes without requiring a perfect protocol architecture upfront.
The adapter boundary
Agent-ready commerce will likely involve multiple external surfaces. Some will look like tool interfaces. Some will look like commerce protocols. Some will involve payment delegation. Some will be feeds. Some will be marketplace integrations. Some will be administrative views.
The platform should not let each surface become its own interpreter of commerce.
The risk is not that adapters are large. The risk is that they become authoritative.
A stable design keeps the decision chain inside the platform:
Commercial truth decides what is known.
Policy facts decide what terms apply.
Eligibility decides which actions are valid.
Authority decides who may request them.
Checkout services perform state transitions.
Evidence explains why the platform trusted the decision.
Audit records what happened.
Adapters translate those decisions into external protocol language.
For the Travel Backpack, every adapter should converge on the same meaning:
Discover: allowed
Compare: allowed
Return-policy quotation: blocked
US shipping quotation: blocked
Checkout preparation: blocked
Delegated payment: blocked
The protocol response can differ. The commercial decision should not.
That is the purpose of thin adapters in agent-ready commerce. They prevent ACP, MCP, AP2, feeds, tools, and future interfaces from becoming separate versions of product truth, policy interpretation, eligibility, checkout, and payment authority.
Part 6 will move from integration boundaries to state transitions:
Agent-Ready Commerce, Part 6: Checkout Is a State Machine, Not a Form
That article will examine why checkout preparation, cart mutation, inventory validation, policy coverage, payment authority, retries, and audit need explicit state transitions rather than loosely coupled request handlers.
About the author
Written by Dimitrios S. Sfyris, Founder & Software Architect at AspectSoft.
AspectSoft designs and develops custom software platforms, e-commerce systems, SaaS infrastructure, integrations, analytics tools, and practical digital products.
You can also follow the AspectSoft LinkedIn page for updates on software platforms, commerce systems, AI tooling, and developer-focused products.
Top comments (0)