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

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

Most ecommerce 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 ecommerce 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.

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 this:

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 is one of the most important design choices in agent-ready commerce.

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 AgentCommerceAction =
  | "discover"
  | "compare"
  | "quote_policy"
  | "add_to_cart"
  | "prepare_checkout"
  | "delegate_payment";
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 act within a payment authority boundary.

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, mandate, authority       blocked
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 session and payment authority
  Result: blocked because checkout is not valid
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.

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.

A more useful eligibility decision includes the action, subject, context, result, blockers, evidence, and rule version.

type ActionEligibilityDecision = {
  subject: {
    productId: string;
    truthVersion: string;
  };
  action: AgentCommerceAction;
  context: EligibilityContext;
  result: "allowed" | "blocked" | "requires_revalidation" | "requires_review";
  blockers: EligibilityBlocker[];
  warnings: EligibilityWarning[];
  evidence: EvidenceRef[];
  ruleSet: {
    id: string;
    version: string;
  };
  evaluatedAt: string;
};

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

type EligibilityBlocker = {
  code: string;
  message: string;
  nextAction?: string;
};

type EligibilityWarning = {
  code: string;
  message: string;
};

type EvidenceRef = {
  type: "truth_fact" | "policy_fact" | "rule" | "source";
  ref: string;
};
Enter fullscreen mode Exit fullscreen mode

For the travel backpack, the checkout decision might look like this:

const decision: ActionEligibilityDecision = {
  subject: {
    productId: "bag_travel_42",
    truthVersion: "truth_2025_10_18_001"
  },
  action: "prepare_checkout",
  context: {
    region: "EU",
    currency: "EUR",
    buyerType: "consumer",
    channel: "agent",
    actorType: "agent"
  },
  result: "blocked",
  blockers: [
    {
      code: "INVENTORY_STALE",
      message: "Inventory must be revalidated before checkout.",
      nextAction: "Revalidate inventory from the warehouse source."
    },
    {
      code: "RETURN_POLICY_MISSING",
      message: "Return-policy coverage is missing for the Travel Bags category.",
      nextAction: "Attach or approve a return policy for this category."
    }
  ],
  warnings: [],
  evidence: [
    {
      type: "truth_fact",
      ref: "truth:bag_travel_42:inventory"
    },
    {
      type: "policy_fact",
      ref: "policyCoverage:travel_bags:returns"
    }
  ],
  ruleSet: {
    id: "agent_product_eligibility",
    version: "v4"
  },
  evaluatedAt: "2025-10-18T09:30:00Z"
};
Enter fullscreen mode Exit fullscreen mode

This decision can serve multiple consumers.

The agent can stop, ask for clarification, or suggest alternatives.

The checkout service can refuse to prepare a session.

The admin UI can create remediation tasks.

The audit trail can record why the product was blocked.

The feed publisher can downgrade the product’s action summary.

A structured decision is more reusable than a boolean.

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;
  action: AgentCommerceAction;
  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 ProductTruthSnapshot = {
  productId: string;
  truthVersion: 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" | "pending_review" | "conflicting";
  };
};
Enter fullscreen mode Exit fullscreen mode

Eligibility then applies action-specific requirements.

type ActionRequirement = {
  action: AgentCommerceAction;
  requiredFacts: Array<keyof ProductTruthSnapshot["facts"]>;
  blockedStatuses: Array<
    "missing" | "stale" | "conflicting" | "pending_review"
  >;
};
Enter fullscreen mode Exit fullscreen mode

Example:

const actionRequirements: ActionRequirement[] = [
  {
    action: "discover",
    requiredFacts: ["identity"],
    blockedStatuses: ["missing", "conflicting"]
  },
  {
    action: "compare",
    requiredFacts: ["identity", "price"],
    blockedStatuses: ["missing", "conflicting"]
  },
  {
    action: "quote_policy",
    requiredFacts: ["policyCoverage"],
    blockedStatuses: ["missing", "stale", "conflicting"]
  },
  {
    action: "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"
  | "allowed_with_warnings"
  | "requires_revalidation"
  | "requires_review"
  | "blocked";
Enter fullscreen mode Exit fullscreen mode

For example:

discover
  result: allowed_with_warnings
  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.

Rule versioning is necessary

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 version produced them.

type EligibilityRuleSet = {
  id: string;
  version: string;
  effectiveFrom: string;
};
Enter fullscreen mode Exit fullscreen mode

A decision should reference the rule set:

type EligibilityDecisionMetadata = {
  truthVersion: string;
  ruleSetId: string;
  ruleSetVersion: string;
  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 rule versioning, 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 readiness.

A feed item can include a compact action summary:

type AgentProductFeedItem = {
  id: string;
  title: string;
  price: {
    amount: number;
    currency: string;
    lastVerifiedAt: string;
  };
  actions: {
    discoverable: boolean;
    comparable: boolean;
    policyQuotable: boolean;
    checkoutEligible: boolean;
  };
  truthVersion: string;
  eligibilityVersion: string;
  publishedAt: string;
};
Enter fullscreen mode Exit fullscreen mode

For the travel backpack:

{
  "id": "bag_travel_42",
  "title": "Travel Backpack",
  "actions": {
    "discoverable": true,
    "comparable": true,
    "policyQuotable": false,
    "checkoutEligible": false
  },
  "truthVersion": "truth_2025_10_18_001",
  "eligibilityVersion": "agent_product_eligibility_v4",
  "publishedAt": "2025-10-18T09:35:00Z"
}
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" | "mcp" | "internal_feed" | "admin";
  externalAction: string;
  internalAction: AgentCommerceAction;
};
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 readiness, 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 readiness 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
        ↓
Checkout validity
        ↓
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;
  checkoutSessionValid: 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.
The checkout session is valid.
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 = {
  action: AgentCommerceAction;
  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;
  checkoutReady: 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 versions, or next actions.

If checkoutReady 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 version 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 decisions for authority.
Enter fullscreen mode Exit fullscreen mode

For example:

type ProductEligibilityProfile = {
  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 version
+ same context
+ same rule version
= 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 version 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 version
Context
Rule set version
Decision result
Blockers
Evidence references
Evaluation timestamp
Enter fullscreen mode Exit fullscreen mode

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

The point is that change should be explainable.

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 truth and eligibility versions. Otherwise, external systems may rely on stale action readiness.

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.

Testing action eligibility

Eligibility should be tested with scenario matrices, not only single-field unit tests.

A useful test combines:

Product truth
Request context
Action
Expected decision
Expected blockers
Expected operator impact
Enter fullscreen mode Exit fullscreen mode

Example scenarios:

Fresh price, fresh inventory, complete policy coverage.
Fresh price, stale inventory, complete policy coverage.
Fresh price, fresh inventory, missing return policy.
Fresh price, fresh inventory, policy conflict.
Fresh price, fresh inventory, generated claims pending review.
Fresh price, fresh inventory, complete policy coverage, unsupported region.
Enter fullscreen mode Exit fullscreen mode

Expected matrix for one scenario:

Scenario:
Fresh price, stale inventory, missing return policy.

Expected:
discover          allowed
compare           allowed
quote_policy      blocked: RETURN_POLICY_MISSING
add_to_cart        requires_revalidation: INVENTORY_STALE
prepare_checkout  blocked: INVENTORY_STALE, RETURN_POLICY_MISSING
delegate_payment  blocked: CHECKOUT_NOT_VALID
Enter fullscreen mode Exit fullscreen mode

This kind of test verifies degradation behavior.

The goal is not only to know whether the product is available. The goal is to know which actions are allowed, which are blocked, and why.

The main tradeoff

Action-specific eligibility adds complexity.

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

That is more work than a single available flag.

For a small storefront, this may be unnecessary. If the only consumer 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 version, 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. Version the rules

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.

The key takeaway

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

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