DEV Community

Cover image for Agent-Ready Commerce, Part 3: Why “Available” Is Not Enough for AI Agents
Dimitrios S. Sfyris
Dimitrios S. Sfyris

Posted on • Edited on

Agent-Ready Commerce, Part 3: Why “Available” Is Not Enough for AI Agents

Most e-commerce platforms have some version of product availability.

A product is active. It is in stock. It has a price. It can appear on the storefront.

For a human-facing e-commerce site, that model can be good enough for a long time. The storefront can render a product page, show an “Add to cart” button, and let checkout perform final validation later.

For agent-ready commerce, that model becomes too vague.

The problem is not that available is wrong. The problem is that it hides too many different decisions behind one word.

Available for what?

Discovery?

Comparison?

Policy quotation?

Cart insertion?

Checkout preparation?

Delegated payment?

Those actions do not carry the same risk, and they should not use the same rule.

Series context

This is the third article in the Agent-Ready Commerce series.

Part 1 introduced the broader model:

Facts → Eligibility → Authority → State transition → Evidence → Audit
Enter fullscreen mode Exit fullscreen mode

Part 2 focused on the first part of that chain: commercial truth. A raw product record is not enough for agent-facing systems. The platform needs source-backed, freshness-aware product facts before software can safely act on product information.

This article focuses on the second part of the chain: eligibility.

The main idea is to answer four separate questions instead of hiding them behind available:

Commercial truth says what is known.
Eligibility says which actions are valid.
Authority says who is allowed to request them.
Execution performs the state change.
Enter fullscreen mode Exit fullscreen mode

Keeping those layers separate prevents a valid fact from being mistaken for permission, and permission from being mistaken for successful execution.

The problem with generic availability

A typical product model might contain fields like this:

type Product = {
  id: string;
  title: string;
  active: boolean;
  inventoryStatus: "in_stock" | "low_stock" | "out_of_stock";
  price: {
    amount: number;
    currency: string;
  };
};
Enter fullscreen mode Exit fullscreen mode

A simple availability check might look like this:

function isAvailable(product: Product) {
  return product.active && product.inventoryStatus !== "out_of_stock";
}
Enter fullscreen mode Exit fullscreen mode

This is not a bad function. It answers a real question.

The issue is that the question is too broad.

If isAvailable(product) returns true, what exactly did the platform allow?

Can the product appear in search?

Can an agent recommend it?

Can an agent compare it with alternatives?

Can an agent quote its return policy?

Can an agent place it in a cart?

Can an agent prepare checkout?

Can an agent trigger a delegated payment flow?

A human-facing storefront may not need to separate all of those actions immediately. A page can render the product and rely on later validation to catch problems.

An agent-facing system needs a more precise contract. If an external agent sees available: true, it may reasonably assume that the product can be used in a commercial action. That is too much meaning for one boolean.

The platform needs action-specific eligibility.

A running example

Consider a product that appears simple from the catalog perspective:

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

A storefront can render this product. It has a title, price, image, category, and inventory flag.

The commercial truth layer, however, may know more:

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

A generic availability model might still say:

available = true
Enter fullscreen mode Exit fullscreen mode

But that answer is not precise enough.

A better action-level view would be:

discover          allowed
compare           allowed
quote_policy      blocked
add_to_cart        requires_revalidation
prepare_checkout  blocked
delegate_payment  blocked
Enter fullscreen mode Exit fullscreen mode

This is the important shift.

The product is not simply available or unavailable. It has different readiness levels for different actions.

A human user may still browse the product page. An agent may still discover and compare the product. But the platform should not allow policy quotation or checkout preparation until the missing and stale facts are resolved.

Actions need to be part of the domain model

The first step is to name the actions that matter at the agent boundary.

A platform does not need to expose every internal operation, but it should define the commercial actions agents may request.

For example:

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

These actions are not interchangeable.

discover means the product may appear in an agent-facing catalog, search result, or product feed.

compare means the product may be evaluated against alternatives using selected commercial facts.

quote_policy means the agent may describe policy terms such as returns, shipping, cancellation, or warranty.

add_to_cart means the agent may place the product into a draft cart or buyer-intent context.

prepare_checkout means the agent may start or prepare a checkout session.

delegate_payment means the agent may request payment within a bounded authority scope tied to the current checkout.

complete_checkout means the platform may finalize a valid checkout through payment and order submission when all state, authority, and idempotency requirements pass.

show_generated_claim means the platform may project approved generated language for the requested surface and use.

explain means the platform may return an explanation from the existing decision basis without recomputing the decision locally.

Once the actions are named, the platform can stop treating product availability as one global state.

Instead, it can ask:

Given the current product truth and request context,
is this specific action allowed?
Enter fullscreen mode Exit fullscreen mode

That is a better question.

Different actions require different facts

Action eligibility should depend on commercial truth, but not every action requires the same facts.

A simplified requirement matrix might look like this:

Action             Required truth                         Example result
--------------------------------------------------------------------------------
discover           identity, visibility, media             allowed
compare            identity, price, comparable attributes   allowed
quote_policy       applicable policy coverage              blocked
add_to_cart         price, inventory signal                 requires_revalidation
prepare_checkout   fresh price, fresh inventory, policies   blocked
delegate_payment   valid checkout state, mandate, payment authority      blocked
complete_checkout   valid checkout, payment result, order transition      blocked
show_generated_claim   approved claim, scope, surface, use, evidence       requires_review
explain            existing decision basis and safe evidence               allowed
Enter fullscreen mode Exit fullscreen mode

This matrix is more useful than a single available flag because it explains what each action depends on.

The travel backpack example becomes clearer:

discover
  Required: identity, visibility, media
  Result: allowed

compare
  Required: identity, price
  Result: allowed

quote_policy
  Required: return-policy coverage
  Result: blocked because return policy is missing for Travel Bags

add_to_cart
  Required: price, inventory signal
  Result: requires_revalidation because inventory is stale

prepare_checkout
  Required: fresh price, fresh inventory, applicable policies
  Result: blocked because inventory is stale and return policy is missing

delegate_payment
  Required: valid checkout state and payment authority
  Result: blocked because checkout is not ready, so payment is not attempted
Enter fullscreen mode Exit fullscreen mode

This does not mean the product is broken. It means different capabilities have different readiness levels.

That distinction is essential for agent-ready systems.

Eligibility decisions should be structured

A boolean return value is rarely enough because it shows the outcome but not the reason, evidence, freshness, or safe next step.

This is too weak:

const checkoutAllowed = false;
Enter fullscreen mode Exit fullscreen mode

It tells the caller the result, but it does not explain the decision or preserve enough meaning for other surfaces.

A more useful eligibility result includes action, subject, context, result, blocker codes, evidence references, and next safe actions. In a decision-spine architecture, that eligibility result becomes one section of a broader envelope rather than the whole answer.

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 AgentCommerceDecisionEnvelope = {
  contractVersion: "agent-commerce-decision-envelope-v4";
  envelopeSchemaVersion: "agent-commerce-decision-envelope-schema-v4";
  decisionId: string;
  decisionHash: string;
  inputDependencyHash: string;
  resultHash: string;
  ruleSetVersion: string;
  ruleSetRef: string;
  ruleSetHash: string;
  authenticator: AgentCommerceDecisionEnvelopeAuthenticator;
  requestedAction: AgentCommerceDecisionAction;

  basis: {
    status:
      | "allowed"
      | "blocked"
      | "requires_revalidation"
      | "requires_review"
      | "requires_confirmation";
    allowed: boolean;
    reasonCodes: readonly string[];
    components: readonly {
      code: string;
      source: "eligibility" | "authority" | "checkout" | "payment" | "generated_claim";
      field: string;
      value: string | number | boolean | null;
      contributesTo: "status" | "payment_dispatch" | "generated_claim_use";
    }[];
  };

  eligibility: {
    result:
      | "allowed"
      | "blocked"
      | "requires_revalidation"
      | "requires_review"
      | "requires_confirmation";
    blockerCodes: readonly string[];
    source: "product" | "policy" | "checkout" | "payment" | "operator" | "combined";
  };

  evidenceRefs: readonly {
    type: string;
    id: string;
    hash: string;
    hashAlgorithm: "sha256";
  }[];

  nextSafeActions: readonly {
    action: string;
    owner: "system" | "operator" | "buyer" | "merchant";
    reasonCode: string;
  }[];
};
Enter fullscreen mode Exit fullscreen mode

The full envelope also carries subject, actor, input references, freshness dependencies and horizons, authority, checkout, payment, and generated-claim projection state. inputDependencyHash covers decisionId, the requested surface and action, actor, subject, input references, rule-set identity, evaluation time, evidence pins, and freshness dependencies. resultHash covers the computed eligibility, authority, checkout, payment, generated-claim, freshness, basis, and next-action result. decisionHash binds the contract and schema to those two hashes.

The canonical basis keeps the result and its explanation together. Every code in basis.reasonCodes must appear in at least one basis.components[].code, and every component code must appear in the reason set. Several components may explain the same reason at different causal boundaries, but no reason can exist without a component.

The canonical result also needs an explicit precedence rule. A required hard block in authority, checkout, payment, or generated-claim use must dominate softer review, confirmation, or revalidation states. Contradictory sections—such as allowed with blocker codes, a valid checkout carrying checkout blockers, or payment dispatch attempted while payment authority is blocked—should be rejected rather than normalized into a plausible envelope.

When actor authority or checkout validity prevents payment evaluation, the payment section should remain not_evaluated with no payment-specific blockers. The upstream reasons remain in their owning sections and in the decision basis.

The authenticator states whether the envelope is protected by an Ed25519 digital signature, an HMAC-SHA-256 message-authentication code, or an explicitly unsigned local-development variant. The core eligibility idea remains simple: result, reason, evidence, and next safe action.

For the Travel Backpack, the prepare_checkout envelope can say:

basis.status = blocked
basis.reasonCodes = eligibility_blocked, invalid_checkout_state, missing_return_policy, stale_inventory
eligibility.result = blocked
checkout.validForRequestedAction = false
payment.paymentDispatchAttempted = false
nextSafeActions = revalidate inventory, attach return policy
Enter fullscreen mode Exit fullscreen mode

The envelope can still pin this result to its inputs and freshness horizon, but the reader-facing decision remains easy to follow.

That structure prevents three common mistakes.

First, the feed cannot turn active and in_stock into checkout readiness.

Second, the adapter cannot treat an authenticated actor as evidence that the action itself is valid.

Third, the payment layer cannot treat a payment artifact as permission to bypass stale inventory or missing policy coverage.

Eligibility stays explicit, but it does not pretend to answer every question by itself.

Eligibility is not authority

Eligibility and authority are often confused.

They should not be.

Eligibility answers:

Is this action valid for this product under the current commercial conditions?
Enter fullscreen mode Exit fullscreen mode

Authority answers:

Is this actor allowed to request this action?
Enter fullscreen mode Exit fullscreen mode

Execution answers:

Can the system perform the state change now?
Enter fullscreen mode Exit fullscreen mode

Those are separate checks.

For example, a product may be checkout-eligible, but a specific agent may not have permission to prepare checkout.

An agent may have a valid payment mandate, but the product may not be checkout-eligible because inventory is stale.

A checkout session may be eligible and authorized, but execution may fail because the state changed between validation and transition.

The separation looks like this:

Commercial truth
        ↓
Eligibility
        ↓
Authority
        ↓
Execution
        ↓
State transition
Enter fullscreen mode Exit fullscreen mode

A command should pass through each layer.

type CommerceCommandReadiness = {
  eligibility: ActionEligibilityDecision;
  authority: AuthorityDecision;
  execution: ExecutionReadiness;
  executable: boolean;
};

type AuthorityDecision = {
  actorId: string;
  requestedAction: AgentCommerceDecisionAction;
  allowed: boolean;
  reason?: string;
  scopes: string[];
};

type ExecutionReadiness = {
  currentState: string;
  transitionAllowed: boolean;
  reason?: string;
};
Enter fullscreen mode Exit fullscreen mode

This prevents one decision from doing too much work.

A product being eligible does not mean the actor is authorized.

An actor being authorized does not mean the product is eligible.

Both being true does not guarantee execution if the state transition is invalid.

This distinction becomes especially important for checkout and delegated payment.

Eligibility should consume commercial truth, not rebuild it

The eligibility layer should not rediscover product facts from raw catalog data. That would duplicate logic and create inconsistent behavior.

Instead, eligibility should consume a truth snapshot.

type AgentCommerceCommercialTruthSnapshot = {
  productId: string;
  truthRef: string;
  facts: {
    identity: "known" | "missing" | "stale" | "conflicting";
    price: "known" | "missing" | "stale" | "conflicting";
    inventory: "known" | "missing" | "stale" | "conflicting";
    policyCoverage: "known" | "missing" | "stale" | "conflicting";
    generatedClaims: "known" | "missing" | "REVIEW_REQUIRED" | "conflicting";
  };
};
Enter fullscreen mode Exit fullscreen mode

Eligibility then applies action-specific requirements.

type ActionRequirement = {
  requestedAction: AgentCommerceDecisionAction;
  requiredFacts: Array<keyof AgentCommerceCommercialTruthSnapshot["facts"]>;
  blockedStatuses: Array<
    "missing" | "stale" | "conflicting" | "REVIEW_REQUIRED"
  >;
};
Enter fullscreen mode Exit fullscreen mode

Example:

const actionRequirements: ActionRequirement[] = [
  {
    requestedAction: "discover",
    requiredFacts: ["identity"],
    blockedStatuses: ["missing", "conflicting"]
  },
  {
    requestedAction: "compare",
    requiredFacts: ["identity", "price"],
    blockedStatuses: ["missing", "conflicting"]
  },
  {
    requestedAction: "quote_policy",
    requiredFacts: ["policyCoverage"],
    blockedStatuses: ["missing", "stale", "conflicting"]
  },
  {
    requestedAction: "prepare_checkout",
    requiredFacts: ["price", "inventory", "policyCoverage"],
    blockedStatuses: ["missing", "stale", "conflicting"]
  }
];
Enter fullscreen mode Exit fullscreen mode

The boundary is clean:

Commercial truth says:
Inventory is stale.

Eligibility says:
Stale inventory blocks checkout preparation.

Authority says:
This actor may or may not request checkout preparation.

Execution says:
The checkout state transition can or cannot happen now.
Enter fullscreen mode Exit fullscreen mode

Each layer has a narrower responsibility.

Context matters

Product eligibility is rarely global.

A product may be eligible in one region and blocked in another. A shipping policy may exist for the EU but not for the US. A return policy may apply to consumers but not business buyers. A payment method may depend on currency. A marketplace channel may have stricter requirements than the merchant’s own storefront.

Eligibility should therefore include context.

type EligibilityContext = {
  region?: string;
  currency?: string;
  buyerType?: "consumer" | "business";
  channel?: "storefront" | "agent" | "marketplace";
  actorType?: "human" | "agent";
};
Enter fullscreen mode Exit fullscreen mode

The same product can produce different decisions:

Context: EU consumer, agent channel
discover          allowed
compare           allowed
quote_policy      blocked: return policy missing
prepare_checkout  blocked: inventory stale

Context: US consumer, agent channel
discover          allowed
compare           allowed
quote_policy      blocked: return policy missing
prepare_checkout  blocked: inventory stale, shipping policy unknown

Context: business buyer, agent channel
discover          allowed
compare           allowed
quote_policy      blocked: business return terms missing
prepare_checkout  blocked: business policy coverage incomplete
Enter fullscreen mode Exit fullscreen mode

A global available flag cannot express this.

Context-aware eligibility is more complex, but it avoids overexposing products in cases where the platform lacks the facts required for a specific buyer or market.

Eligibility should support degradation

Eligibility is not only a gate. It is also a way to degrade safely.

The product does not need to disappear from every agent surface because one high-risk action is blocked.

For the travel backpack, the platform can expose a capability profile:

discover          allowed
compare           allowed
quote_policy      blocked
add_to_cart        requires_revalidation
prepare_checkout  blocked
delegate_payment  blocked
Enter fullscreen mode Exit fullscreen mode

This tells the agent and the operator exactly where the product stands.

A product with incomplete return-policy coverage may still be discoverable. A product with stale inventory may still be comparable. A product with an unreviewed generated description may still be shown if the agent uses approved catalog copy instead.

This is better than two extreme alternatives:

Too permissive:
Show the product as fully available even though checkout is unsafe.

Too conservative:
Remove the product entirely even though low-risk actions are still valid.
Enter fullscreen mode Exit fullscreen mode

Action-specific eligibility allows the platform to preserve safe capabilities while blocking unsafe ones.

That is a more useful operational model.

Decision results should be more than allowed or blocked

Not every negative decision should be represented as blocked.

Some decisions require revalidation. Some require human review. Some require additional authority. Some are warnings rather than blockers.

A useful result type might be:

type EligibilityResult =
  | "allowed"
  | "blocked"
  | "requires_revalidation"
  | "requires_review"
  | "requires_confirmation";
Enter fullscreen mode Exit fullscreen mode

For example:

discover
  result: allowed
  warning: generated description pending review, using approved catalog summary

add_to_cart
  result: requires_revalidation
  reason: inventory signal is stale

quote_policy
  result: blocked
  reason: return-policy coverage is missing

prepare_checkout
  result: blocked
  reason: inventory stale and policy coverage missing
Enter fullscreen mode Exit fullscreen mode

This makes the system more expressive.

A product does not have to move directly from allowed to blocked. It can require revalidation, review, or warning-aware handling depending on the action.

Those result labels should stay attached to the action and context that produced them. A warning on discovery is not the same as a warning on checkout.

Rule identity should be explicit

Eligibility rules change.

A platform may decide that stale inventory is acceptable for cart insertion but not checkout. Later, high-demand categories may require fresh inventory even before cart insertion. A policy team may require return-policy coverage before comparison in certain regions. A marketplace channel may require stricter policy completeness than the storefront.

If eligibility decisions are stored, cached, published, or audited, the platform needs to know which rule set produced them.

type EligibilityRuleSetRef = {
  ruleSetVersion: string;
  ruleSetRef: `ruleset:sha256:${string}`;
  ruleSetHash: string;
  effectiveFrom: string;
  changeRef?: string;
};
Enter fullscreen mode Exit fullscreen mode

A decision should reference both the human-readable rule-set version and the content-addressed identity:

type EligibilityDecisionMetadata = {
  truthRef: string;
  ruleSet: EligibilityRuleSetRef;
  evaluatedAt: string;
};
Enter fullscreen mode Exit fullscreen mode

This helps distinguish two cases:

Case 1:
The product became blocked because inventory changed from fresh to stale.

Case 2:
The product became blocked because the eligibility rule changed.
Enter fullscreen mode Exit fullscreen mode

Both are valid, but they mean different things operationally.

The first case may require inventory revalidation. The second may require merchant remediation, policy updates, or feed republishing.

Without traceable decisions, these cases become harder to explain.

Eligibility and feeds

An agent-facing product feed should not publish raw catalog availability as if it were action eligibility.

A feed item can include a compact action summary:

type AgentCommerceProductRecord = {
  id: string;
  title: string;
  price: {
    amount: number;
    currency: string;
    lastVerifiedAt: string;
  };
  actions: Partial<Record<
    AgentCommerceDecisionAction,
    "allowed" | "blocked" | "requires_revalidation" | "requires_review" | "requires_confirmation"
  >>;
  truthRef: string;
  decisionHash: string;
  validUntil: string | null;
  publishedAt: string;
};
Enter fullscreen mode Exit fullscreen mode

For the travel backpack:

{
  "id": "bag_travel_42",
  "title": "Travel Backpack",
  "price": {
    "amount": 129,
    "currency": "EUR",
    "lastVerifiedAt": "2026-07-06T10:12:00.000Z"
  },
  "actions": {
    "discover": "allowed",
    "compare": "allowed",
    "quote_policy": "blocked",
    "add_to_cart": "requires_revalidation",
    "prepare_checkout": "blocked",
    "delegate_payment": "blocked",
    "show_generated_claim": "requires_review",
    "explain": "allowed"
  },
  "truthRef": "truth:travel-backpack:2026-07-06",
  "decisionHash": "1111111111111111111111111111111111111111111111111111111111111111",
  "validUntil": "2026-07-06T10:15:00.000Z",
  "publishedAt": "2026-07-06T10:12:00.000Z"
}
Enter fullscreen mode Exit fullscreen mode

The feed does not need to expose every internal rule. It should expose enough for external systems to avoid unsafe assumptions.

The full blocker details can live behind a product-detail endpoint or eligibility endpoint.

This keeps the feed compact while preserving explainability.

Eligibility and protocol adapters

Protocol adapters should not invent eligibility rules.

This is a common source of drift.

One adapter calculates product visibility from catalog status. Another adapter calculates checkout readiness from inventory. The feed publisher uses a different rule. The admin UI shows a different status. The checkout service performs final validation again.

The result is inconsistent behavior.

A safer structure is:

Protocol request
        ↓
Protocol adapter
        ↓
Internal action mapping
        ↓
Eligibility service
        ↓
Domain decision
        ↓
Protocol-specific response
Enter fullscreen mode Exit fullscreen mode

The adapter maps the external request to an internal action.

type ProtocolActionMapping = {
  protocol: "acp" | "ucp" | "mcp" | "ap2" | "internal_feed" | "admin";
  externalAction: string;
  internalAction: AgentCommerceDecisionAction;
};
Enter fullscreen mode Exit fullscreen mode

The internal eligibility service owns the decision.

The adapter only translates the result into the protocol-specific shape.

This matters because different protocols may ask similar questions with different language:

Can this product be purchased?
Can checkout be prepared?
Is this item eligible for agent checkout?
Can this offer be fulfilled?
Is delegated payment supported?
Enter fullscreen mode Exit fullscreen mode

The platform should not answer each of those questions with separate business logic.

It should map them to internal actions and evaluate those actions consistently.

Eligibility and checkout

Checkout should revalidate product action status, but it should not be the first place where readiness is understood.

If eligibility exists only inside checkout, then discovery, comparison, feeds, protocol adapters, and admin workflows cannot explain product action status before checkout starts.

A better structure is:

Commercial truth
        ↓
Action eligibility
        ↓
Checkout preparation
        ↓
Checkout state transition
Enter fullscreen mode Exit fullscreen mode

Checkout preparation can consume eligibility decisions:

type CheckoutPreparationInput = {
  cartId: string;
  productDecisions: ActionEligibilityDecision[];
};
Enter fullscreen mode Exit fullscreen mode

If any product is not checkout-eligible, checkout preparation can return structured blockers:

type CheckoutPreparationBlocker = {
  productId: string;
  code: string;
  message: string;
  nextAction?: string;
};
Enter fullscreen mode Exit fullscreen mode

This does not remove the need for final checkout validation. Prices and inventory should still be revalidated at the transition boundary.

But it prevents checkout from becoming the only part of the system that knows whether a product is commercially ready.

Eligibility gives the rest of the platform a way to reason before checkout begins.

Eligibility and delegated payment

Delegated payment is not just a higher level of product availability.

It depends on several layers.

Commercial truth
        ↓
Product eligibility
        ↓
Valid checkout state
        ↓
Payment authority
        ↓
Payment execution
Enter fullscreen mode Exit fullscreen mode

A product being checkout-eligible does not mean an agent can trigger payment.

Delegated payment also requires mandate boundaries, amount limits, currency match, merchant binding, cart snapshot integrity, expiry, revocation checks, and possibly human confirmation.

A simplified readiness model might look like this:

type DelegatedPaymentReadiness = {
  productEligibilityPassed: boolean;
  checkoutAuthorityEstablished: boolean;
  paymentMandatePresent: boolean;
  cartSnapshotMatches: boolean;
  amountWithinMandate: boolean;
  humanConfirmationSatisfied: boolean;
};
Enter fullscreen mode Exit fullscreen mode

This prevents a dangerous shortcut:

Product is available → agent can pay
Enter fullscreen mode Exit fullscreen mode

That shortcut should not exist.

The correct chain is narrower and safer:

Product facts are valid.
The product is eligible for checkout.
Checkout validation passes.
The actor has payment authority.
The payment command is executable.
Enter fullscreen mode Exit fullscreen mode

Each statement belongs to a different layer.

Eligibility should produce operator tasks

Eligibility decisions should not only serve agents. They should also serve operators.

When many products fail the same eligibility check, the platform should turn those failures into grouped remediation tasks.

For example:

type EligibilityIssueGroup = {
  requestedAction: AgentCommerceDecisionAction;
  blockerCode: string;
  productIds: string[];
  impact: string;
  nextAction: string;
};
Enter fullscreen mode Exit fullscreen mode

Operator-facing examples:

18 products are discoverable but not checkout-ready.

Reason:
Inventory freshness is stale.

Impact:
Agents can show these products, but checkout preparation is blocked.

Next action:
Revalidate inventory from the warehouse source.
Enter fullscreen mode Exit fullscreen mode

Another example:

7 products are blocked from policy quotation.

Reason:
Return-policy coverage is missing.

Impact:
Agents should not quote return terms for these products.

Next action:
Attach or approve a return policy for the Travel Bags category.
Enter fullscreen mode Exit fullscreen mode

This is where eligibility becomes operationally useful.

A generic validation error says something is wrong. An eligibility issue explains which commercial capability is affected.

That is the difference between debug output and an operator workflow.

Avoid boolean explosion

A common response to weak availability modeling is to add more booleans.

type Product = {
  active: boolean;
  inStock: boolean;
  agentVisible: boolean;
  comparable: boolean;
  policyQuotable: boolean;
  eligibleForCheckoutStart: boolean;
  paymentReady: boolean;
};
Enter fullscreen mode Exit fullscreen mode

This is better than one boolean, but it still has problems.

Booleans do not explain themselves. They do not include blockers, context, evidence, rule identities, or next actions.

If eligibleForCheckoutStart is false, the platform still needs to answer:

Is the price stale?
Is inventory stale?
Is policy coverage missing?
Is the product blocked in this region?
Is generated content pending review?
Did the rule identity change?
Is checkout blocked only for agents or also for humans?
Enter fullscreen mode Exit fullscreen mode

Booleans are useful as summaries, especially in feeds and dashboards. They should not be the authoritative model.

A better rule is:

Use booleans for projections.
Use shared decision envelopes for commercial meaning.
Enter fullscreen mode Exit fullscreen mode

For example:

type AgentCommerceProductActionSnapshot = {
  productId: string;
  decisions: ActionEligibilityDecision[];
  summary: {
    discoverable: boolean;
    comparable: boolean;
    policyQuotable: boolean;
    checkoutEligible: boolean;
  };
};
Enter fullscreen mode Exit fullscreen mode

The summary is convenient. The decisions are authoritative.

Determinism matters

Eligibility decisions should be deterministic for the same inputs.

Same truth reference
+ same context
+ same rule identity
= same eligibility decision
Enter fullscreen mode Exit fullscreen mode

This matters for caching, debugging, audit trails, feed publication, and agent interactions.

If an agent sees a product as checkout-eligible in a feed, then another endpoint says it is blocked, the platform needs to explain why. A valid explanation might be that a new truth reference was generated or a rule changed. An invalid explanation is that different services used different hidden logic.

A deterministic eligibility decision should record:

Product ID
Truth reference
Context
Rule set reference
Decision result
Blockers
Evidence references
Evaluation timestamp
Enter fullscreen mode Exit fullscreen mode

At the envelope boundary, deterministic behavior is stronger than repeating the same business result. The same normalized input, including the same evaluatedAt, should produce identical inputDependencyHash, resultHash, and decisionHash values. Object keys and set-like collections such as blocker codes, evidence references, freshness dependencies, allowed uses, and next safe actions should be normalized before hashing so ordering alone does not create drift. Valid date-time inputs should be normalized to one UTC representation before they enter the hash.

This does not mean decisions never change. They should change when facts change, context changes, rules change, or the evaluation time intentionally changes.

The point is that change should be explainable and reproducible.

Where this model can go wrong

Action eligibility improves precision, but it introduces its own risks.

1. Eligibility can become a god service

If every product, policy, checkout, payment, protocol, and admin rule gets pushed into one eligibility service, the service becomes too broad. It starts owning decisions that belong elsewhere.

A useful boundary is:

Eligibility decides whether a product/action is valid.
Authority decides whether an actor may request it.
Checkout decides whether a state transition can occur.
Payment decides whether financial authority exists.
Enter fullscreen mode Exit fullscreen mode

2. Too many blocker codes can create noise

Structured blockers are useful, but a large uncontrolled list becomes hard to operate. Blocker codes should be stable, documented, and grouped by business impact.

3. Context can make caching harder

Context-aware decisions are more correct, but they are harder to cache. A decision for an EU consumer may not apply to a US buyer or business account.

4. Feed summaries can become stale

If action summaries are published to a feed, they need to reference the truth and eligibility decisions they came from. Otherwise, external systems may rely on stale action eligibility.

5. Protocol mappings can drift

If external protocol actions are mapped inconsistently to internal actions, eligibility decisions may appear inconsistent even if the domain service is correct.

6. The model can become too conservative

If every incomplete fact blocks every action, the platform may remove useful low-risk capabilities. Discovery and comparison should not always require the same evidence as checkout or payment.

7. The model can become too permissive

If high-risk actions reuse low-risk rules, agents may be allowed to proceed without enough evidence. Checkout and delegated payment should have stricter requirements than discovery.

These failure modes do not make action eligibility a bad idea. They define the engineering constraints around it.

Practical tests for action eligibility

Eligibility should be tested through a small number of semantic scenarios, with response-shape assertions used only where they clarify the decision.

The Travel Backpack scenario is enough to catch the most dangerous drift:

Product: Travel Backpack
Price: fresh
Inventory: stale
Return policy: missing
Payment artifact: present
Enter fullscreen mode Exit fullscreen mode

Expected meaning:

discover          allowed
compare           allowed
quote_policy      blocked: missing_return_policy
add_to_cart        requires_revalidation: stale_inventory
prepare_checkout  blocked: stale_inventory, missing_return_policy
delegate_payment     blocked because checkout is not valid; upstream blockers remain visible in checkout and the decision basis
payment authority    not_evaluated; no payment-specific blockers
payment dispatch     false
show_generated_claim action result requires_review; canonical claim status inherited_refusal; inheritedRefusalCount: 1
explain           allowed from the decision basis
Enter fullscreen mode Exit fullscreen mode

The same blocker-code meaning should survive projection into the feed, tool adapter, checkout path, and admin or support view. The tests should also assert exact reason/component reconciliation and repeat the same normalized input to confirm stable hashes. They should mutate decisionId to confirm identity tampering is detected. They should also reject contradictory allowed sections and any soft eligibility result that would otherwise hide a hard authority, checkout, or payment block.

The goal is to make one important semantic test hard to bypass: an adapter should not be able to quietly convert a blocked decision into success.

The main tradeoff

Action-specific eligibility adds complexity.

It introduces action vocabularies, rule sets, context objects, decision envelopes, blocker codes, evidence references, rule identities, and tests.

That is more work than a single available flag.

For a small storefront, this may be unnecessary. If the only user is a human browsing a simple catalog, a basic availability model may be enough.

Agent-ready commerce changes the threshold.

Once external agents can discover products, compare options, quote policies, prepare checkout, or act within delegated authority, raw availability becomes too weak as the platform contract.

The tradeoff is between simplicity and precision.

A single availability flag is simpler, but it hides risk.

Action-specific eligibility is more complex, but it makes the system explainable, safer, and more useful to agents, protocol adapters, checkout services, payment flows, and operators.

Design principles

A practical action eligibility model should follow several principles.

1. Name the actions

Do not rely on generic availability. Define the actions agents may perform.

2. Separate low-risk and high-risk actions

Discovery, comparison, policy quotation, checkout, and delegated payment should not share one rule.

3. Base eligibility on commercial truth

Eligibility should consume source-backed facts. It should not rediscover product state independently.

4. Return structured decisions

A decision should include result, blockers, warnings, evidence, context, rule identity, and evaluation time.

5. Keep eligibility separate from authority

Eligibility says whether the product/action is valid. Authority says whether the actor may request it.

6. Keep eligibility separate from execution

A product may be eligible, but the requested state transition may still be invalid at execution time.

7. Support context

Region, buyer type, channel, currency, and actor type can all affect eligibility.

8. Name the rule set

Eligibility decisions should record which rule set produced the result.

9. Use booleans only as projections

Boolean flags are useful for summaries, but decision objects should be authoritative.

10. Feed operator workflows

Blocked decisions should produce actionable tasks with business impact and next actions.

Conclusion

“Available” is too broad for agent-ready commerce.

A product can be visible but not comparable. Comparable but not policy-quotable. Policy-quotable but not checkout-ready. Checkout-ready but not eligible for delegated payment.

Those distinctions matter because agents need explicit action boundaries.

The platform should therefore expose action-specific eligibility decisions instead of relying on one product availability flag.

Commercial truth says what is known.
Eligibility says which actions are valid.
Authority says who may request them.
Execution performs the state change.
Enter fullscreen mode Exit fullscreen mode

This separation keeps the system easier to reason about. It also creates better interfaces for agents, protocol adapters, feeds, checkout services, payment flows, and operator tools.

Next in the series

Part 4 will move from product actions to policy structure:

Agent-Ready Commerce, Part 4: Making Policies Machine-Readable

That article will examine how returns, shipping, warranty, cancellation, regional restrictions, and policy conflicts can be modeled as structured facts instead of free-text pages that agents are expected to interpret.


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 (2)

Collapse
 
scott_morrison_39a1124d85 profile image
Knowband

Excellent perspective. I especially like the distinction between commercial truth, eligibility, authority, and execution. Replacing a single available flag with action-specific eligibility makes AI agent interactions far more reliable, explainable, and safer, particularly for checkout and delegated payment workflows. Great architectural insight.

Collapse
 
dmsfiris profile image
Dimitrios S. Sfyris

Glad it resonated. Appreciate you taking the time to read it thoughtfully, and for the generous feedback.