DEV Community

Cover image for Agent-Ready Commerce, Part 5: Keeping ACP, UCP, MCP, and AP2 Adapters Thin
Dimitrios S. Sfyris
Dimitrios S. Sfyris

Posted on • Edited on

Agent-Ready Commerce, Part 5: Keeping ACP, UCP, MCP, and AP2 Adapters Thin

Protocol adapters are one of the easiest places for agent-commerce architecture to drift.

An adapter begins with one responsibility: translate an external request into the platform's internal language. An MCP-style tool may ask for return terms. An ACP- or UCP-style interaction may ask whether checkout can be prepared. An AP2-related flow may carry payment-authority information. 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, an ACP or UCP 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 validation, checkout state, or payment authority. Those decisions belong inside the commerce platform, where they can be shared, evaluated, traced, and reused.

Series context

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
Enter fullscreen mode Exit fullscreen mode

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 quotability.

This article focuses on protocol adapters.

The central argument is that ACP, UCP, MCP, AP2-related flows, 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.

In this article series, projection means response shaping at a protocol boundary. Later, generated-claim projection uses the same idea more strictly: the platform does not only shape a response, it decides whether a piece of language may be said at all.

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.
Enter fullscreen mode Exit fullscreen mode

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?
Enter fullscreen mode Exit fullscreen mode

Another may ask:

Can checkout be prepared?
Enter fullscreen mode Exit fullscreen mode

A tool interface may ask:

Return the return policy for this product.
Enter fullscreen mode Exit fullscreen mode

A payment protocol may ask:

Can this agent request delegated payment for this cart?
Enter fullscreen mode Exit fullscreen mode

A feed may publish:

checkoutEligible: true
Enter fullscreen mode Exit fullscreen mode

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";
}
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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 a small file. It may parse the protocol, validate schemas, map errors, propagate idempotency, emit telemetry, extract authentication context, paginate results, shape responses, and handle compatibility mapping. “Thin” means that it does not decide commercial truth, eligibility, authority, checkout validity, or payment permission.

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 quotable. 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.
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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 quotable
EU shipping: known and potentially quotable
US shipping: unknown
Return policy for Travel Bags: missing
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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.
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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. Canonical intent is the shared internal description of what the caller wants, independent of the protocol used to ask for it.

For example, one protocol may expose a tool called:

get_return_terms
Enter fullscreen mode Exit fullscreen mode

Another may expose:

describe_policy
Enter fullscreen mode Exit fullscreen mode

A third may ask for:

product.purchase_constraints
Enter fullscreen mode Exit fullscreen mode

The platform should not implement three separate policy interpreters. It should map those requests into canonical intent:

quote_policy
Enter fullscreen mode Exit fullscreen mode

A simple internal action vocabulary might look like this:

type AgentCommerceDecisionAction =
  | "discover"
  | "compare"
  | "quote_policy"
  | "add_to_cart"
  | "prepare_checkout"
  | "delegate_payment"
  | "complete_checkout"
  | "show_generated_claim"
  | "explain";
Enter fullscreen mode Exit fullscreen mode

The adapter can map protocol-specific actions to these internal actions:

type ProtocolName =
  | "internal_feed"
  | "mcp"
  | "acp"
  | "ucp"
  | "ap2"
  | "marketplace_api"
  | "admin";

type ProtocolActionMapping = {
  protocol: ProtocolName;
  externalAction: string;
  internalAction: AgentCommerceDecisionAction;
};

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: "ucp",
    externalAction: "create_checkout",
    internalAction: "prepare_checkout"
  },
  {
    protocol: "ap2",
    externalAction: "request_delegated_payment",
    internalAction: "delegate_payment"
  }
];
Enter fullscreen mode Exit fullscreen mode

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 quotable. 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 = {
  requestedAction: AgentCommerceDecisionAction;
  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;
};
Enter fullscreen mode Exit fullscreen mode

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.

A request or context hash proves only which input was evaluated. It does not prove that the relevant owner evaluated that input and returned allowed. The owner decision must be explicit, include its evidence and blockers, and be bound to the exact action, subject, scope, and freshness window.

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 AgentCommerceDecisionEnvelopeAuthenticator =
  | {
      kind: "digital_signature";
      algorithm: "ed25519";
      format: "detached";
      keyId: string;
      verificationKeyRef: string;
      protectedHash: string;
      value: string;
      verifiable: true;
    }
  | {
      kind: "message_authentication_code";
      algorithm: "hmac-sha256";
      format: "detached";
      keyId: string;
      verificationKeyRef: string;
      protectedHash: string;
      value: string;
      verifiable: true;
    }
  | {
      kind: "unsigned";
      algorithm: "none";
      format: "none";
      protectedHash: string;
      verifiable: false;
      warning: "missing_platform_signing_key";
    };

type AgentCommerceDecisionProjection = {
  envelopeSchemaVersion: "agent-commerce-decision-envelope-schema-v4";
  ruleSetRef: string;
  ruleSetHash: string;
  authenticator: AgentCommerceDecisionEnvelopeAuthenticator;
  decisionHash: string;
  inputDependencyHash: string;
  resultHash: string;
  requestedAction: AgentCommerceDecisionAction;
  allowed: boolean;
  status:
    | "allowed"
    | "blocked"
    | "requires_review"
    | "requires_revalidation"
    | "requires_confirmation";
  reasonCodes: readonly string[];
  freshness?: {
    evaluatedAt: string;
    validUntil: string | null;
    staleAfter: string | null;
    reasonCodes: readonly string[];
    dependencyCount: number;
    dependencyKinds: readonly string[];
  };
  nextSafeActions: readonly {
    action: string;
    owner: "system" | "operator" | "buyer" | "merchant";
    reasonCode: string;
  }[];
};
Enter fullscreen mode Exit fullscreen mode

The canonical envelope carries the full subject, actor, freshness, basis, eligibility, authority, checkout, payment, generated-claim, evidence, rule-set, hash, authenticator, and declared-surface state. A surface projection may expose only the fields it needs, but it should preserve the canonical hashes, rule-set identity, authenticator, status, reasons, freshness, and next safe actions instead of rebuilding them. The trusted boundary verifies the envelope's declared surface before producing that projection.

Before an envelope crosses an external boundary, the projection path should capture externally supplied or persisted input once into a detached, deeply frozen, JSON-compatible snapshot. Hash recomputation, reason/component reconciliation, authenticator verification, trusted-key validation, surface binding, live-request binding, freshness evaluation, and projection should all consume that same captured value. Action-bearing external surfaces must reject stale allowed output. Administrative and support projections may expose stale context for diagnosis, but they do not authorize execution. A blocked envelope may still be projected as a refusal so the recipient receives its reason codes and next safe actions.

Independently verifiable output should require Ed25519. HMAC belongs only inside an explicitly configured shared-secret trust domain, and unsigned output should remain local-development only. Before changing state, the platform should compare the decision's protected dependency hashes with current authoritative snapshots. A missing, changed, or expired dependency requires a fresh decision even when the envelope remains signed and inside its original freshness window.

For the Travel Backpack, a return-policy quotation decision might be:

const decision: AgentCommerceDecisionProjection = {
  envelopeSchemaVersion: "agent-commerce-decision-envelope-schema-v4",
  ruleSetRef: "ruleset:sha256:<sha256-hex>",
  ruleSetHash: "<sha256-hex>",
  authenticator: {
    kind: "digital_signature",
    algorithm: "ed25519",
    format: "detached",
    keyId: "platform-key:decision-envelope",
    verificationKeyRef: "public-key:decision-envelope",
    protectedHash: "<sha256-hex>",
    value: "<base64url-signature>",
    verifiable: true
  },
  decisionHash: "<sha256-hex>",
  inputDependencyHash: "<sha256-hex>",
  resultHash: "<sha256-hex>",
  requestedAction: "quote_policy",
  allowed: false,
  status: "blocked",
  reasonCodes: ["missing_return_policy"],
  freshness: {
    evaluatedAt: "2026-07-06T10:12:00.000Z",
    validUntil: null,
    staleAfter: null,
    reasonCodes: [],
    dependencyCount: 1,
    dependencyKinds: ["policy"]
  },
  nextSafeActions: [
    {
      action: "attach_return_policy_fact",
      owner: "operator",
      reasonCode: "missing_return_policy"
    }
  ]
};
Enter fullscreen mode Exit fullscreen mode

The MCP-style tool adapter may project that into a tool result. An ACP- or UCP-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.

Thin projection has one job it cannot bend. It preserves scope. If a generated policy claim is safe for feed discovery, the adapter should not reuse it as checkout or support language. It should use the value returned for that surface, not reach back into the raw claim and reuse the language.

Capability is not permission

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?
Enter fullscreen mode Exit fullscreen mode

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 quotable 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 action status.
const response = {
  canQuotePolicy: true,
  canPrepareCheckout: true,
  canDelegatePayment: true
};
Enter fullscreen mode Exit fullscreen mode

A better model separates the layers:

type ProtocolCapability = {
  requestedAction: AgentCommerceDecisionAction;
  expressibleByProtocol: boolean;
};

type PlatformCapability = {
  requestedAction: AgentCommerceDecisionAction;
  supportedByPlatform: boolean;
};

type ContextualReadiness = {
  requestedAction: AgentCommerceDecisionAction;
  result: DecisionResult;
  blockers: DecisionBlocker[];
};
Enter fullscreen mode Exit fullscreen mode

For the Travel Backpack:

Protocol can express checkout preparation.
The platform supports checkout preparation.
This product and context are not checkout-ready.
Enter fullscreen mode Exit fullscreen mode

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)
  };
}
Enter fullscreen mode Exit fullscreen mode

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);
}
Enter fullscreen mode Exit fullscreen mode

That code is shorter, but the point is not shortness. The point is that the adapter no longer owns checkout validation.

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 and ergonomic:

Protocol request
      ↓
Adapter maps request to canonical intent
      ↓
Existing product, policy, checkout, payment, and authority decisions are loaded or evaluated
      ↓
Decision spine returns one envelope
      ↓
Adapter projects the envelope to protocol shape
Enter fullscreen mode Exit fullscreen mode

The adapter should not need to know how stale inventory, missing policy coverage, checkout lifecycle, mandate scope, generated-claim status, and operator next actions all interact.

It should ask the platform for the shared decision envelope.

A simplified envelope contains:

eligibility
actor authority
checkout state
payment authority
generated-claim status
evidence references
next safe actions
Enter fullscreen mode Exit fullscreen mode

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.

The anti-pattern is predictable:

if (product.inStock && product.price.amount < 150 && paymentArtifact.present) {
  return { status: "ready_for_payment" };
}
Enter fullscreen mode Exit fullscreen mode

That code is not malicious. It is what developers may be tempted to write when the local shortcut is easier than the shared domain path.

The reference architecture works only when the spine is the shortest safe path.

Correct producer path:

const envelope =
  buildAgentCommerceDecisionEnvelope(input);
Enter fullscreen mode Exit fullscreen mode

The decision spine builds the canonical envelope. Surface adapters do not reconstruct the commercial decision.

Correct trusted-boundary path:

const liveRequest = {
  requestedAction: intent.requestedAction,
  subject: intent.subject,
  actor: authenticatedActor
};

const envelope =
  captureAgentCommerceDecisionEnvelope(externalEnvelope);

return projectTrustedAgentCommerceDecisionEnvelope(
  envelope,
  "tool",
  {
    ...trustContext,
    expectedRequest: liveRequest,
    now
  }
);
Enter fullscreen mode Exit fullscreen mode

liveRequest is the normalized request context derived from the current protocol call and authenticated actor context. It contains the requested action, subject, and actor that the recipient expects to process. It must be constructed independently of the supplied envelope so the trusted boundary can detect replay, substitution, or context mismatch.

trustContext contains recipient-controlled verification material, trusted key identity, and authenticator policy. It is local configuration and must not be accepted from the supplied envelope.

For a state-changing operation:

const liveRequest = {
  requestedAction: intent.requestedAction,
  subject: intent.subject,
  actor: authenticatedActor
};

const envelope =
  captureAgentCommerceDecisionEnvelope(externalEnvelope);

const projection =
  projectTrustedAgentCommerceDecisionEnvelope(
    envelope,
    "checkout",
    {
      ...trustContext,
      expectedRequest: liveRequest,
      now
    }
  );

const execution =
  evaluateAgentCommerceDecisionExecution({
    envelope,
    currentDependencies,
    now
  });

if (!execution.permitted) {
  return execution;
}

return projection;
Enter fullscreen mode Exit fullscreen mode

currentDependencies must come from the current authoritative domain owners rather than from the supplied envelope. This lets the application detect a price, inventory, policy, checkout, mandate, authority, payment, generated-claim, or evidence change that occurred after the decision was created.

The adapter does not decide whether stale inventory blocks payment. The feed does not infer policy quotability. Checkout does not repair missing commercial facts. Admin does not invent a different blocker taxonomy. The decision spine produces the canonical commercial decision, and each recipient projects that decision through the appropriate trusted boundary. Before a state-changing operation proceeds, the owning application also compares the protected dependencies with current authoritative state.

The adapter remains protocol-shaped. The commercial meaning remains domain-owned.

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.
Enter fullscreen mode Exit fullscreen mode

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 quotable return terms.

A safer tool description is:

Use this tool to request quotable 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 quotable.
Enter fullscreen mode Exit fullscreen mode

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 checkout state valid for this action?
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?
Enter fullscreen mode Exit fullscreen mode

A simplified readiness model might be:

type DelegatedPaymentReadiness = {
  checkoutAuthorityEstablished: boolean;
  authorityPresent: boolean;
  mandateWithinScope: boolean;
  cartSnapshotMatches: boolean;
  amountWithinLimit: boolean;
  currencyMatches: boolean;
  merchantMatches: boolean;
  notExpired: boolean;
  notRevoked: boolean;
  humanConfirmationSatisfied: boolean;
};
Enter fullscreen mode Exit fullscreen mode

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;
};
Enter fullscreen mode Exit fullscreen mode

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.
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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"
}
Enter fullscreen mode Exit fullscreen mode

A better response is:

{
  "error": "failed_precondition",
  "reason": "missing_return_policy",
  "message": "Return-policy terms cannot be quoted for this product category."
}
Enter fullscreen mode Exit fullscreen mode

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.
Enter fullscreen mode Exit fullscreen mode

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 quotable return-policy fact applies to Travel Bags in this buyer context.
Enter fullscreen mode Exit fullscreen mode

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 quotability 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 AgentCommerceProductRecord = {
  productId: string;
  title: string;
  price: {
    amount: number;
    currency: string;
  };
  actions: {
    discover: DecisionResult;
    compare: DecisionResult;
    quotePolicy: DecisionResult;
    addToCart: DecisionResult;
    prepareCheckout: DecisionResult;
    delegatePayment: DecisionResult;
    showGeneratedClaim: DecisionResult;
    explain: DecisionResult;
  };
  generatedClaims: {
    status:
      | "allowed"
      | "requires_review"
      | "refused_here"
      | "inherited_refusal"
      | "stale"
      | "out_of_scope"
      | "absent";
  };
  publishedAt: string;
};
Enter fullscreen mode Exit fullscreen mode

For the Travel Backpack:

{
  "productId": "BAG-TRAVEL-42",
  "actions": {
    "discover": "allowed",
    "compare": "allowed",
    "quotePolicy": "blocked",
    "addToCart": "requires_revalidation",
    "prepareCheckout": "blocked",
    "delegatePayment": "blocked",
    "showGeneratedClaim": "requires_review",
    "explain": "allowed"
  },
  "generatedClaims": {
    "status": "inherited_refusal"
  }
}
Enter fullscreen mode Exit fullscreen mode

The show_generated_claim action result is requires_review, while the canonical generated-claim status is inherited_refusal. This feed does not expose every internal detail, but it preserves 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
Enter fullscreen mode Exit fullscreen mode

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 ProtocolShapedProductStatus = {
  acpPurchasable: boolean;
  mcpReturnToolEnabled: boolean;
  ap2PaymentReady: boolean;
  feedVisible: boolean;
};
Enter fullscreen mode Exit fullscreen mode

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 AgentCommerceProductActionSnapshot = {
  productId: string;
  decisions: CommerceDecision[];
};
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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.
Enter fullscreen mode Exit fullscreen mode

When those responsibilities are reversed, either the adapter becomes a commerce engine or the domain becomes protocol-shaped.

Practical adapter tests should focus on drift

Adapter tests should focus on the failure that matters most: the same commercial scenario producing different meaning across surfaces.

The useful scenario is the Travel Backpack:

Price is fresh.
Inventory is stale.
Return-policy coverage is missing.
A payment artifact is present.
Enter fullscreen mode Exit fullscreen mode

Expected meaning across feed, tool adapter, checkout, and admin:

Delegated payment is blocked.
Blocker codes include stale inventory and missing return policy.
Checkout state is not valid for payment for the requested action.
Payment authority: not_evaluated; no payment-specific blocker.
Payment dispatch: not attempted.
show_generated_claim action: requires_review; canonical claim status: inherited_refusal.
Admin next actions include revalidate inventory and attach or approve return-policy coverage.
Enter fullscreen mode Exit fullscreen mode

The test should ignore generated copy, exact table ordering, route counts, and response snapshots. It should assert semantic agreement: blocker codes, causal meaning, scoped use, decision references, and blocked payment dispatch.

Boundary tests should reject a context hash presented as an owner verdict and a verdict bound to the wrong action, subject, actor, or scope. They should also reject a changed surface, a modified hash or authenticator, stale allowed output, unsigned external output, and HMAC output when no shared-secret trust domain is configured. A stale blocked projection may still carry the canonical refusal and remediation context, but it must not become allowed. Mutation tests should confirm that an older envelope cannot bypass the latest checkout revision, mandate state, or product decision merely because it is still signed and within its freshness window.

The goal is to stop a Friday-afternoon adapter shortcut from landing quietly.

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"
Enter fullscreen mode Exit fullscreen mode

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.

Projection widens a claim

A claim supported for one use can look reusable elsewhere because the language sounds close. That is still drift. Projection should preserve the scoped meaning it was given. If the claim is refused for this surface, that refusal should stay visible instead of disappearing as an empty answer.

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 action status dashboard.

The tradeoff is between local speed and system consistency.

Adapter-local logic is faster to add.

Domain-owned decisions are easier to test, reuse, trace, 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 through the trusted boundary
Enter fullscreen mode Exit fullscreen mode

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:

  1. Define a small internal action vocabulary.
    Use the shared v4 actions from Part 3: discover, compare, quote_policy, add_to_cart, prepare_checkout, delegate_payment, complete_checkout, show_generated_claim, and explain.

  2. 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.

  3. Define the shared decision envelope.
    Return eligibility, authority, checkout state, payment authority, generated-claim status, evidence references, and next safe actions in one shape.

  4. Add one trusted projection boundary.
    External or persisted envelopes should pass through projectTrustedAgentCommerceDecisionEnvelope, or an equivalent recipient-owned boundary, before a surface response is returned. Low-level feed, tool, checkout, admin, and support mappings should remain internal and project from the captured envelope instead of recalculating commercial meaning.

  5. Keep commercial truth, policy facts, eligibility, authority, checkout, and payment decisions outside adapters.
    Adapters call domain or application use cases. They do not reimplement commerce rules.

  6. Preserve blocker meaning during projection.
    Protocol responses may be simplified, but blocker codes and causal meaning should not disappear.

  7. Separate global capability from contextual permission.
    Expose global support separately from contextual product or cart permission.

  8. Propagate idempotency.
    Adapters should pass idempotency and correlation information to state-owning services.

  9. Test negative paths.
    Missing policy, stale inventory, unsupported region, conflicting policy, pending generated claims, expired authority, and duplicate requests should all have expected behavior.

  10. Add cross-adapter consistency tests.
    The same product, action, and context should produce compatible decisions across ACP, UCP, MCP, AP2-related flows, feeds, marketplace APIs, and admin surfaces.

This path prevents the most damaging boundary mistakes without requiring a perfect protocol architecture upfront.

The boundary in one sentence

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.
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

The protocol response can differ. The commercial decision should not.

That is the purpose of thin adapters in agent-ready commerce. They prevent ACP, UCP, MCP, AP2-related flows, feeds, tools, and future interfaces from becoming separate versions of product truth, policy interpretation, eligibility, checkout, and payment authority.

Next in the series

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)