The reference architecture for agent-ready commerce has one central rule:
Every external surface should be a projection of the same commerce decision spine.
A storefront, product feed, MCP-style tool, ACP- or UCP-style interaction, AP2-related payment flow, marketplace API, admin UI, support view, and future agent interface may all need different shapes of response.
Throughout this article, projection has a broad meaning: a surface-specific view of the same decision. Generated-claim projection is the strictest version of that idea because it decides whether specific language may be used, not just how a response should be shaped.
They should not produce different commercial meanings.
If one surface says a product is checkout-ready and another blocks checkout, the architecture has drifted.
If one adapter quotes a return policy and another says return-policy coverage is missing, the architecture has drifted.
If a generated summary says a product is ready to ship while eligibility says inventory is stale, the architecture has drifted.
If a payment artifact causes payment to proceed while checkout state is not valid for payment, the architecture has failed.
Agent-ready commerce is not a chatbot attached to a store. It is not only a product feed. It is not an adapter for a protocol. It is not a payment token. It is not generated product copy.
It is a commerce platform that can expose its facts, policies, actions, state transitions, authority boundaries, generated claims, and evidence to agents without losing control of commercial meaning.
That requires a decision-centered architecture.
Series context
This is the tenth and final article in the Agent-Ready Commerce series.
Part 1 introduced the broader architecture model:
Facts → Eligibility → Authority → State transition → Evidence → Audit
Part 2 focused on commercial truth. It argued that catalog data is not enough. A platform needs source-backed, freshness-aware product facts before agents or other systems can safely rely on product information.
Part 3 focused on action eligibility. It argued that “available” is too broad. A product may be discoverable but not checkout-ready, comparable but not policy-quotable, or checkout-ready for a human flow but not eligible for delegated payment.
Part 4 focused on policy structure. It argued that agents should not interpret free-text policy pages as executable rules. Policies need structured facts with applicability, evidence, lifecycle, conflicts, and quotability.
Part 5 focused on protocol adapters. It argued that ACP, UCP, MCP, AP2-related flows, feeds, tools, and future interfaces should translate domain decisions rather than becoming separate sources of commercial meaning.
Part 6 focused on checkout state. It argued that checkout is where agent-ready commerce crosses from answering into mutating commercial state. Checkout should therefore use explicit checkout state with controlled transitions, not a loose form endpoint.
Part 7 focused on delegated payment. It argued that a payment artifact is not enough. Delegated payment requires bounded authority over a specific checkout snapshot, amount, currency, merchant, actor, buyer, time window, evidence, and audit.
Part 8 focused on generated claims. It argued that generated commerce text should be treated as a derived claim that is projectable only with source, freshness, scope, surface, use, payload integrity, direct parent-projection binding, inherited refusal state, invalidation, expiry, and audit.
Part 9 focused on evidence and audit. It argued that agent-ready commerce needs canonical decision envelopes, hash-pinned evidence references, and meaningful event history, not just logs.
This final article connects the pieces into a reference architecture.
The central argument is that agent-ready commerce should be built around a shared decision spine:
Source systems
↓
Commercial truth
↓
Policy facts
↓
Action-specific eligibility
↓
Actor authority
↓
Checkout state
↓
Payment authority
↓
Generated-claim projection state
↓
Evidence and meaningful event history
↓
Protocol-safe projections
↓
Operator remediation
Protocols and channels sit at the edges. They do not own the meaning.
The architecture is not a topology
A reference architecture is not the same thing as a deployment diagram.
The system can be implemented as:
a modular monolith
a set of services
a workflow-based platform
an event-driven architecture
a hybrid
The deployment shape matters less than the semantic boundaries.
A small team may implement commercial truth, policy facts, eligibility, checkout, generated claims, and audit as modules inside one application.
A larger organization may split them into services.
Both can work.
The important questions are different:
Can adapters bypass eligibility?
Can feeds publish readiness without decision envelopes?
Can checkout proceed without policy coverage?
Can payment proceed without valid checkout state?
Can generated claims bypass review and allowed use?
Can policy summaries become quotable without policy facts?
Can admin UI compute readiness differently from the agent feed?
Can support explain why an agent refused an action?
If the answer to those questions is yes, the architecture will drift regardless of whether it is deployed as one service or twenty.
A well-structured monolith with clear domain boundaries is safer than a distributed system with duplicated rules.
The reference architecture is about ownership of meaning.
The decision spine
The decision spine is the ordered path the platform follows to turn operational data into one explainable commercial answer.
A simplified version:
Source systems
↓
Commercial truth
↓
Policy facts
↓
Action-specific eligibility
↓
Actor authority
↓
Checkout state
↓
Payment authority
↓
Generated-claim projection state
↓
Evidence and meaningful event history
↓
Protocol-safe projections
↓
Operator remediation
Each layer answers a different question.
| Layer | Question |
|---|---|
| Source systems | What data exists in operational systems? |
| Commercial truth | Which facts can the platform currently rely on? |
| Policy facts | Which terms apply in this context, and are they quotable? |
| Action-specific eligibility | Which requested actions are valid now? |
| Actor authority | Who may request the action? |
| Checkout state | Is the requested mutation valid from the current state? |
| Payment authority | Is delegated payment inside the current mandate scope? |
| Generated-claim projection state | Which derived language is projectable for this use? |
| Evidence and events | Why was the decision made, and what meaningful change occurred? |
| Projection | What should this audience or protocol see? |
| Operator remediation | What must be fixed to unblock the path? |
The spine gives every surface the same starting point. Agents, adapters, feeds, generated text, and checkout flows may present the result differently, but they do not reinterpret the underlying commerce state.
The output of the spine is a decision envelope: the shared record of what was requested, what the platform decided, why it decided it, and what may safely happen next.
For generated claims, the envelope acts as a capability rather than a raw text field. A claim is usable only while its source, freshness, scope, surface, allowed use, payload integrity, direct dependencies, and inherited refusal state remain attached.
The practical envelope view remains simple: eligibility, authority, checkout, payment, generated-claim projection state, freshness, evidence references, and next safe actions.
The contract also carries decisionId, version and rule-set identity, freshness horizons, hash-pinned evidence references, three decision hashes, and an explicit authenticator.
-
inputDependencyHashprotects the decision identity and the inputs that were evaluated. -
resultHashprotects the conclusions, reasons, freshness result, and next actions. -
decisionHashbinds those hashes to the contract and schema.
This separation lets a recipient tell whether the inputs changed, the result changed, or the contract itself changed.
Canonical serialization means that the same normalized input produces the same bytes and therefore the same hashes. Object keys, set-like collections, and date-time values must be normalized. Every reason code must also be represented by a basis component. Hard blocks must override softer review, confirmation, or revalidation states, and contradictory sections such as allowed with blockers must be rejected.
The authenticator distinguishes a detached Ed25519 digital signature, a detached HMAC-SHA-256 message-authentication code, and visibly unsigned local-development output. At an external boundary, the projection path should capture any externally supplied or persisted envelope once into a detached, deeply frozen, JSON-compatible snapshot. Integrity verification, authenticator and trusted-key validation, surface binding, freshness evaluation, live action/subject/actor binding, 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 without authorizing execution. Before changing state, the platform should compare the decision's protected dependency hashes with current authoritative snapshots and require a fresh decision when a dependency is missing, changed, or expired.
A projection should receive the value prepared for its surface. It should not read raw claim text and decide locally whether the text is safe.
A claim refused for this use is different from a claim that never existed or was never grounded. If a new claim uses an upstream projection, it should bind that exact parent and request context. Any refusal or stale state from the parent should remain visible in the child.
The Travel Backpack end-to-end
Use the running example one final time:
Product: Travel Backpack
SKU: BAG-TRAVEL-42
Price: €129
Catalog status: active
Inventory status: in_stock
Category: Travel Bags
The commercial truth layer currently says:
Price: fresh
Inventory: stale
Return policy: missing for Travel Bags
Warranty policy: known
Shipping policy: known for EU, unknown for US
Generated description: pending review
Feed publication: last published yesterday
The action matrix is:
| Action | Result |
|---|---|
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 from decision basis |
Now suppose an agent asks:
Can you buy the Travel Backpack for the buyer if the total stays under €150?
A channel-first implementation may try to answer inside the adapter:
Product active?
Inventory says in_stock?
Price below €150?
Payment artifact present?
Proceed.
That is unsafe.
A decision-centered platform follows the spine:
Protocol request
↓
Canonical intent
↓
Commercial truth selection
↓
Policy coverage check
↓
Eligibility decision
↓
Checkout state decision
↓
Payment authority decision
↓
Evidence and audit
↓
Protocol-safe projection
↓
Operator tasks
The correct result is:
Delegated payment is blocked.
Reason:
Checkout state is not valid for payment.
Checkout blockers:
- inventory requires revalidation
- return-policy coverage is missing for Travel Bags
Payment:
not attempted
Operator tasks:
- revalidate inventory
- attach or approve return-policy coverage for Travel Bags
This answer is not produced by an adapter improvising.
It is produced by the platform’s decision path.
That is the architectural goal.
Source systems provide inputs, not truth
Source systems may include:
Catalog systems
PIM
ERP
Inventory services
Pricing systems
Promotion systems
Shipping systems
Tax systems
Policy documents
Merchant configuration
CMS
Payment providers
Order management
Identity and account systems
Support systems
Operator input
These systems contain important data. But their data is not automatically safe for agent use.
A catalog field may say the product is active. That does not mean checkout can be prepared.
An inventory source may say in_stock. That does not mean the inventory fact is fresh enough for payment or fulfillment.
A policy page may describe returns. That does not mean a quotable return-policy fact applies to this product, category, region, buyer type, and channel.
A payment provider may authorize a payment. That does not mean the order has been committed.
The ingestion layer should therefore convert source inputs into referenced facts.
type SourceRef = {
system: string;
objectType: string;
objectId: string;
sourceRef?: string;
url?: string;
sourceHash?: string;
capturedAt: string;
};
type SourceBackedFact<TValue> = {
factId: string;
domain:
| "product"
| "price"
| "inventory"
| "shipping"
| "returns"
| "warranty"
| "checkout"
| "payment";
subject: {
productId?: string;
sku?: string;
categoryId?: string;
region?: string;
buyerType?: "consumer" | "business";
};
value: TValue;
sourceRefs: SourceRef[];
freshness: {
status: "fresh" | "stale" | "expired" | "unknown";
evaluatedAt: string;
expiresAt?: string;
};
lifecycle: {
status: "active" | "superseded" | "invalidated";
validFrom: string;
validTo?: string;
};
};
This layer answers:
Where did the fact come from?
When was it captured?
Which source reference was used?
Is it still fresh?
Has it been superseded?
Is it scoped to this product, category, buyer, region, or channel?
Without this layer, every downstream decision becomes weaker.
Commercial truth is the first platform boundary
Commercial truth is the set of source-backed facts the platform can currently rely on.
It is not a copy of the catalog.
It is not the product page.
It is not generated copy.
It is a decision input.
For the Travel Backpack:
Catalog:
active
in_stock
€129
Commercial truth:
price fresh
inventory stale
return policy missing for Travel Bags
EU shipping known
US shipping unknown
generated description pending review
The difference is the architecture.
A product may be active but not checkout-ready.
A product may have a price but not a fresh enough price for payment.
A product may have a policy page but no quotable policy fact.
A product may have generated copy but no approved generated claim.
A commercial truth snapshot may look like this:
type AgentCommerceCommercialTruthSnapshot = {
snapshotId: string;
subject: {
productId?: string;
sku?: string;
categoryId?: string;
};
selectedFacts: SourceBackedFact<unknown>[];
missingFacts: Array<{
domain: string;
reason: string;
requiredFor: string[];
}>;
conflicts: Array<{
conflictId: string;
domain: string;
factIds: string[];
resolution: "unresolved" | "resolved" | "ignored";
}>;
evaluatedAt: string;
};
The snapshot may be stored, cached, or computed on demand. The important property is consistency.
Feeds, adapters, checkout, generated claims, and admin UI should not each select different truth.
Policy facts are not policy pages
Policy pages communicate terms to humans.
Policy facts support machine decisions.
A policy layer should turn returns, shipping, warranty, cancellation, regional restrictions, and business-buyer rules into structured facts with applicability and quotability.
type PolicyFact = {
policyFactId: string;
domain:
| "returns"
| "shipping"
| "warranty"
| "cancellation"
| "regional_restriction"
| "business_buyer_rule";
applicability: {
productId?: string;
categoryId?: string;
region?: string;
buyerType?: "consumer" | "business";
channel?: "storefront" | "agent" | "marketplace" | "support";
};
value: unknown;
quotability: {
status: "quotable" | "not_quotable" | "requires_review";
reason?: string;
};
sourceRefs: SourceRef[];
lifecycle: {
status: "active" | "superseded" | "invalidated";
validFrom: string;
validTo?: string;
};
freshness: {
status: "fresh" | "stale" | "expired" | "unknown";
evaluatedAt: string;
};
};
For the Travel Backpack:
Warranty policy:
known
EU shipping:
known
US shipping:
unknown
Return policy for Travel Bags:
missing
A generated summary cannot fill that gap.
An adapter cannot quote a generic return page and treat it as applicable.
A checkout flow cannot assume return terms are available because the category has a public policy page somewhere.
The policy layer owns applicability and quotability.
That ownership prevents agents from over-quoting policy language.
Eligibility is action-specific
Eligibility prevents available from becoming an unsafe boolean.
The platform should evaluate actions separately:
discover
compare
quote_policy
add_to_cart
prepare_checkout
delegate_payment
complete_checkout
show_generated_claim
explain
Each action has different requirements.
type AgentCommerceDecisionAction =
| "discover"
| "compare"
| "quote_policy"
| "add_to_cart"
| "prepare_checkout"
| "delegate_payment"
| "complete_checkout"
| "show_generated_claim"
| "explain";
type AgentCommerceDecisionEligibilitySection = {
result:
| "allowed"
| "blocked"
| "requires_review"
| "requires_revalidation"
| "requires_confirmation";
blockerCodes: readonly string[];
source: "product" | "policy" | "checkout" | "payment" | "operator" | "combined";
};
The canonical envelope carries this eligibility section beside actor authority, checkout state, payment authority, generated-claim projection state, freshness, the action-aware decision basis, rule-set identity, hashes, authenticator metadata, evidence references, and next safe actions.
For the Travel Backpack:
discover:
allowed
compare:
allowed
quote_policy:
blocked because return-policy coverage is missing
add_to_cart:
requires_revalidation because inventory is stale
prepare_checkout:
blocked because inventory is stale and return-policy coverage is missing
delegate_payment:
blocked because checkout state is not valid for payment
show_generated_claim:
action result requires_review; canonical claim status inherited_refusal
stale and review details remain visible in axes and blockers
explain:
allowed from the action-aware decision basis
This layer should be shared by every surface.
The feed should not compute action eligibility separately.
The adapter should not infer checkout readiness from product status.
The admin UI should not show a different readiness result from the agent feed.
Eligibility is the action boundary.
Authority is separate from eligibility
Eligibility answers:
Is this action valid in this commercial context?
Authority answers:
May this actor request this action?
Those are different questions.
A product may be eligible for checkout, but an agent may not be authorized to prepare checkout for a buyer.
A buyer may authorize an agent to buy an item, but checkout validation may still fail.
A payment artifact may exist, but mandate scope may not match the cart snapshot.
A support operator may view blockers but not request payment.
Authority belongs in its own layer.
type ActorContext = {
actorId: string;
actorType: "human" | "agent" | "system";
buyerId?: string;
buyerType?: "consumer" | "business";
};
type AuthorityDecision = {
authorityDecisionId: string;
actor: ActorContext;
requestedAction:
| "add_to_cart"
| "prepare_checkout"
| "delegate_payment"
| "complete_checkout"
| "create_payment_mandate"
| "consume_payment_mandate"
| "revoke_payment_mandate"
| "complete_delegated_payment";
target: {
cartId?: string;
checkoutId?: string;
cartSnapshotId?: string;
mandateId?: string;
};
result:
| "allowed"
| "blocked"
| "requires_confirmation"
| "expired"
| "revoked";
blockers: Array<{
code: string;
message: string;
nextAction?: string;
}>;
evidenceRefs: Array<{ type: string; id: string; hash: string; hashAlgorithm: "sha256" }>;
evaluatedAt: string;
};
Part 7 introduced the delegated payment authority question:
May this actor request this payment action for this buyer, merchant, checkout snapshot, amount, currency, and time window?
That question should not be answered inside a payment adapter.
It should be answered by the platform authority layer.
Checkout is the mutation boundary
Checkout is where the platform crosses from answering into mutating commercial state.
The reference architecture should treat checkout as explicit checkout state with controlled transitions, not as a form endpoint.
type AgentCommerceCheckoutLifecycleState =
| "created"
| "empty"
| "requires_address"
| "requires_shipping_option"
| "requires_payment"
| "requires_revalidation"
| "blocked"
| "payment_authorized"
| "order_submitted"
| "completed"
| "declined"
| "cancelled"
| "expired"
| "failed";
type AgentCommerceCheckoutOperation =
| "create"
| "read"
| "update"
| "confirm"
| "complete"
| "cancel"
| "expire";
Adapters should not perform hidden checkout mutations.
They should map external requests into commands.
async function protocolAdapterHandler(req: ProtocolRequest) {
const operation = mapToCheckoutOperation(req);
const result = await checkout.handleOperation(operation);
return projectCheckoutOperationResult(result);
}
The checkout transition model coordinates commercial truth, policy facts, eligibility, authority, cart snapshots, idempotency, expiry, payment state, evidence, and audit.
Each command should have one authoritative replay owner. A state-changing operation should hold a command fence through the business write, bind the exact committed response hash, and require the canonical checkout record to verify the same fence. If the business mutation commits but canonical persistence fails, the command remains reconciliation-required instead of becoming safe to replay.
It should not duplicate all of those domains internally.
For the Travel Backpack:
update:
requires revalidation
prepare_checkout:
blocked
delegate_payment:
blocked because checkout state is not valid for payment
This prevents payment or order logic from discovering too late that the cart was never eligible.
Payment authority binds to checkout snapshots
Payment authority should not be modeled as a token check.
It should be modeled as bounded authority over a specific checkout snapshot.
type AgentCommerceMandateRecord = {
mandateId: string;
status:
| "ACTIVE"
| "ACCEPTED"
| "CONSUMED"
| "REVOKED"
| "EXPIRED";
buyerId: string;
actor: {
actorId: string;
actorType: "human" | "agent" | "system";
};
scope: {
merchantId: string;
cartSnapshotId: string;
maxAmount: number;
currency: string;
allowedActions: Array<
| "create_payment_mandate"
| "consume_payment_mandate"
| "revoke_payment_mandate"
| "complete_delegated_payment"
>;
};
confirmation: {
required: boolean;
satisfied: boolean;
confirmationRef?: string;
};
lifecycle: {
createdAt: string;
expiresAt: string;
revokedAt?: string;
usedAt?: string;
};
evidenceRefs: Array<{ type: string; id: string; hash: string; hashAlgorithm: "sha256" }>;
};
Before payment execution, the platform checks:
checkout state
cart snapshot match
amount
currency
merchant
expiry
revocation
confirmation
actor authority
idempotency
payment attempt state
Immediately before the provider call, the platform should persist a dispatch intent bound to the execution, provider idempotency identity, current lease or fence, and canonical provider-command hash. An expired active execution after a timeout or crash requires reconciliation through provider lookup or a verified webhook; expiry alone is not permission to dispatch again.
For the Travel Backpack, delegated payment is blocked before provider execution because checkout state is not valid for payment. Payment authority remains not_evaluated, and payment dispatch remains false.
That causal chain is important:
delegate_payment blocked
↓
checkout state is not valid for payment
↓
prepare_checkout blocked
↓
inventory stale
return-policy coverage missing
Payment authority cannot make checkout state valid.
It can only authorize payment if checkout state is valid for payment and the request is inside the mandate scope.
Generated claims are derived projections
Generated text should not become a parallel truth layer.
Generated claims should be stored with dependencies, scope, review status, projection axes, invalidation, expiry, and audit.
type AxisStatus = "passed" | "failed" | "not_evaluated";
type GeneratedClaimAxis = {
status: AxisStatus;
blockerCodes: string[];
};
type GeneratedClaimStatus =
| "allowed"
| "requires_review"
| "refused_here"
| "inherited_refusal"
| "stale"
| "out_of_scope"
| "absent";
type GeneratedCommerceClaimSummary = {
claimId: string;
kind: GeneratedClaimKind;
status: GeneratedClaimStatus;
scope: GeneratedClaimScope;
allowedUses: GeneratedClaimUse[];
axes: Record<
"source" | "freshness" | "scope" | "surface" | "use" | "payload" | "taint",
GeneratedClaimAxis
>;
dependencies: Array<{
type: string;
ref: string;
hash: string;
hashAlgorithm: "sha256";
}>;
inheritedRefusalCount: number;
expiresAt?: string;
auditRefs: string[];
};
The compact form keeps the article readable while preserving dependency pins, allowed uses, expiry, projection axes, and inherited refusal state. An allowed claim must declare at least one allowed use, and the projection gate should bind the exact claim ID, requested use, requested surface, required value hash, and observed projected value.
When a claim identity exists and several non-allowed conditions apply, the envelope derives one overall claim status in this order: inherited_refusal, refused_here, stale, out_of_scope, requires_review, then allowed. The status absent applies only when no claim identity exists. A passed or not_evaluated axis carries no blockers, while a failed axis carries at least one blocker.
When one generated claim is derived from another projected claim, the owning record should also retain canonical dependencyRefs and a child provenance hash. Each dependency reference should bind the parent projection, its source-envelope and evidence-pin hashes, source record, request context, status, requested surface and use, and refusal kind. After projection-hash verification and deterministic ordering, the child hash should cover every direct parent actually used, including usable parents.
For the Travel Backpack:
“Ready to ship and easy to return”
should be blocked.
Reason:
inventory is stale and return-policy coverage is missing.
A different generated sentence may be safe:
This product is discoverable and comparable, but checkout is blocked because inventory requires revalidation and return-policy coverage is missing.
That sentence is safe only if it is derived from the current eligibility decisions and checkout state and approved for that use. It must also be projected with fresh source pins, bound to the direct projections actually used, and free of inherited refusal that blocks this surface.
Generated claims are useful. They are not authoritative by themselves. When a dependency invalidates a claim, the prior authority descriptor should remain available for audit. Repairing the exact parent may make the claim ready for reassessment, but authority should return only after a fresh approved value, scope, evidence, allowed use, and owner decision.
A compact generated-claim section in the decision envelope can preserve the state without exposing raw claim text:
type AgentCommerceGeneratedClaimsSection = {
allowed: boolean;
status:
| "allowed"
| "requires_review"
| "refused_here"
| "inherited_refusal"
| "stale"
| "out_of_scope"
| "absent";
claimIds: string[];
sourceFactRefs: string[];
derivedFactRefs: string[];
allowedUses: string[];
axes: Record<
| "source"
| "freshness"
| "scope"
| "surface"
| "use"
| "payload"
| "taint",
{
status: "passed" | "failed" | "not_evaluated";
blockerCodes: string[];
}
>;
blockerCodes: string[];
inheritedRefusalCount: number;
};
The envelope carries derived references and inheritedRefusalCount directly. Approved-value, claim-text, and quote-text hashes may be represented through derived references and exposed by surface projections. Expiry is governed through the envelope’s freshness dependencies and horizons rather than through a separate generated-claim expiry field.
The main decision basis should remain action-aware. If delegate_payment is blocked because checkout is invalid, a pending generated description may remain visible in generatedClaims, but it should not become the reason for the payment decision unless the requested action depends on using that claim.
Evidence and audit are product infrastructure
Part 9 argued that agent-ready commerce needs evidence records and audit timelines.
The reference architecture should treat decision envelopes, evidence references, projections, invalidation events, and audit timelines as first-class product infrastructure.
A decision envelope should answer:
What was requested?
Who requested it?
What was targeted?
Which context applied?
What was decided?
Which blockers were returned?
Which evidence supported the result?
Which projection was sent?
Which audit event recorded it?
The canonical envelope carries a compact evidence reference:
type EvidenceRef = {
type: EvidenceKind;
id: string;
hash: string;
hashAlgorithm: "sha256";
};
A separately retrievable evidence record may carry version, visibility, capture time, source location, and access policy. Keeping those details outside the canonical reference avoids copying sensitive material into every projection while preserving a content or canonical-snapshot hash for verification.
Freshness dependencies have a different job. They identify product, price, inventory, policy, checkout, mandate, generated-claim, authority, payment, or evidence inputs whose change or expiry can invalidate the decision. A dependency need not be duplicated as evidence unless it points to a substantiating or replayable artifact.
Evidence should support:
buyer-safe explanations
agent-facing blocker codes
operator remediation
support timelines
audit review
scenario testing
adapter drift detection
claim invalidation
feed republishing
Without this layer, the platform may still respond. It cannot reliably explain why.
Adapters are projection boundaries
Protocol adapters should remain thin in the sense defined in Part 5.
They may own:
protocol parsing
schema validation
authentication context extraction
request normalization
canonical intent mapping
idempotency propagation
response projection
protocol-safe error mapping
They should not own:
product truth
policy interpretation
eligibility rules
checkout state and validation
payment authority
generated commercial claims
evidence decisions
operator remediation logic
A canonical intent helps preserve the boundary:
type CommerceIntent = {
action:
| "discover"
| "compare"
| "quote_policy"
| "add_to_cart"
| "prepare_checkout"
| "delegate_payment"
| "complete_checkout"
| "show_generated_claim"
| "explain";
target: {
productId?: string;
sku?: string;
cartId?: string;
checkoutId?: string;
};
context: {
region?: string;
buyerType?: "consumer" | "business";
channel: "storefront" | "agent" | "marketplace" | "support";
currency?: string;
};
actor: ActorContext;
idempotencyKey?: string;
correlationId?: string;
sourceProtocol?:
| "internal_feed"
| "mcp"
| "acp"
| "ucp"
| "ap2"
| "marketplace_api"
| "admin";
requestedAt: string;
};
The adapter maps external protocol language into this intent.
The domain evaluates the intent.
Before external projection, the boundary recomputes the canonical hashes, reconciles reasons and components, verifies the authenticator and trusted key, confirms that the envelope surface matches the target surface, and compares the protected requested action, subject, and actor with the live request. Action-bearing external surfaces reject stale allowed output. Administrative and support projections may expose stale context for diagnosis without authorizing execution. A stale blocked envelope may still be projected as a refusal so the recipient receives its explanation and remediation context.
Independently verifiable output should use Ed25519. HMAC is limited to an explicitly configured shared-secret boundary, and unsigned output is local-development only. Before changing state, the platform must also verify the latest checkout revision, mandate state, product decision, or another authoritative record used to approve the operation. A signed envelope can still be within its freshness window while referring to state that has already changed.
The adapter then projects the verified result back out.
This prevents semantic drift.
Feeds are projections too
Agent-facing feeds are not neutral exports.
They are projections of commercial truth, eligibility, generated claims, and allowed actions.
A feed item should not publish optimistic readiness.
For the Travel Backpack, an unsafe feed item would say:
Product active.
Price €129.
Inventory in_stock.
Checkout ready.
A safer feed item would say:
{
"sku": "BAG-TRAVEL-42",
"title": "Travel Backpack",
"price": {
"amount": 129,
"currency": "EUR",
"freshness": "fresh"
},
"actions": {
"discover": "allowed",
"compare": "allowed",
"quotePolicy": "blocked",
"addToCart": "requires_revalidation",
"prepareCheckout": "blocked",
"delegatePayment": "blocked",
"showGeneratedClaim": "requires_review",
"explain": "allowed"
},
"blockers": [
"stale_inventory",
"missing_return_policy"
],
"generatedClaims": {
"status": "inherited_refusal",
"claimIds": [
"generated_claim:cabin-size-compatible",
"generated_claim:returns-comparison-paragraph"
],
"allowedUses": [],
"axes": {
"source": { "status": "passed", "blockerCodes": [] },
"freshness": {
"status": "failed",
"blockerCodes": ["stale_inventory"]
},
"scope": { "status": "passed", "blockerCodes": [] },
"surface": { "status": "passed", "blockerCodes": [] },
"use": {
"status": "failed",
"blockerCodes": ["generated_claim_requires_review"]
},
"payload": {
"status": "not_evaluated",
"blockerCodes": []
},
"taint": {
"status": "failed",
"blockerCodes": ["inherited_refusal"]
}
},
"blockerCodes": [
"generated_claim_requires_review",
"inherited_refusal"
],
"inheritedRefusalCount": 1
},
"publishedAt": "2026-06-28T09:00:00Z"
}
The feed is compact, but it preserves the decision model.
It should be refreshed or invalidated when dependencies change.
Otherwise, downstream agents may act on stale projections.
Operator workflows close the loop
Agent-readiness is not only a runtime problem.
Many blockers are operational:
missing policy coverage
stale inventory
unreviewed generated claims
conflicting policy facts
unsupported region
expired feed projection
invalidated checkout explanation
missing payment configuration
unresolved authority workflow
The architecture should include operator tasks as part of the system.
For the Travel Backpack:
Task:
Revalidate inventory for BAG-TRAVEL-42.
Task:
Attach or approve return-policy coverage for Travel Bags.
Task:
Review generated product description.
Impact:
Agents can discover and compare the product, but cannot quote return terms, prepare checkout, or proceed to delegated payment.
Operator workflows should connect to evidence.
One durable remediation stream can move through open, acknowledged, resolved, and reopened. Repeated observations should not create duplicate work, and a blocker that returns after resolution should reopen the same stream.
When an operator resolves a blocker, the platform should refresh affected decisions, invalidate old generated claims, republish feeds, and update audit timelines.
The loop is:
Decision blocked
↓
Evidence recorded
↓
Operator task created
↓
Operator resolves source issue
↓
Fact or policy updated
↓
Dependent decisions invalidated or recomputed
↓
Generated claims refreshed
↓
Feeds republished
↓
Audit timeline updated
This is how agent-ready commerce stays current.
Runtime path: policy question
Consider a request:
What is the return policy for the Travel Backpack?
A safe runtime path:
Adapter maps request to quote_policy intent.
↓
Commercial truth selects product and category.
↓
Policy layer checks returns applicability for Travel Bags.
↓
Return-policy fact is missing.
↓
Eligibility returns blocked.
↓
Generated policy summary is not used.
↓
Evidence records missing policy coverage.
↓
Protocol projection returns a safe blocked answer.
↓
Operator task is created or linked.
The response might be:
{
"result": "blocked",
"reason": "missing_return_policy",
"message": "Return-policy terms are not available for this product category.",
"nextAction": "operator_review_required"
}
The platform does not summarize a policy page.
It does not generate plausible return terms.
It does not quote a generic policy.
It preserves the policy boundary.
Runtime path: checkout request
Now consider:
Prepare checkout for the Travel Backpack for an EU consumer buyer.
A safe runtime path:
Adapter maps request to prepare_checkout intent.
↓
Commercial truth snapshot selected.
↓
Inventory freshness checked.
↓
Policy coverage checked.
↓
Eligibility evaluates prepare_checkout.
↓
Checkout transition model attempts the transition.
↓
Transition blocked.
↓
Evidence and audit recorded.
↓
Decision-derived explanation generated or selected.
↓
Protocol response projected.
↓
Operator tasks updated.
The result:
{
"result": "blocked",
"requestedAction": "prepare_checkout",
"blockers": [
{
"code": "stale_inventory",
"nextAction": "request_inventory_revalidation"
},
{
"code": "missing_return_policy",
"nextAction": "operator_policy_review_required"
}
]
}
The checkout service does not discover the missing policy by accident near payment.
It uses the same decision model used by feeds, adapters, support, and admin.
Runtime path: delegated payment
Now consider:
Buy the Travel Backpack if the total stays under €150.
A safe runtime path:
Adapter maps request to delegate_payment intent.
↓
Platform checks checkout state.
↓
Checkout state is not valid for payment.
↓
Payment authority remains not_evaluated.
↓
Payment dispatch remains false, and the provider is not called.
↓
Evidence records checkout dependency.
↓
Protocol response explains blocked delegated payment.
↓
Operator tasks point to checkout blockers.
The response:
{
"result": "blocked",
"requestedAction": "delegate_payment",
"reason": "invalid_checkout_state",
"causedBy": [
"stale_inventory",
"missing_return_policy"
],
"paymentAttempted": false
}
This preserves the causal chain.
Payment is not attempted because checkout state is not valid for payment.
The payment artifact does not bypass checkout.
Runtime path: generated claim publication
Consider a generated description:
This backpack is ready to ship and easy to return.
A safe runtime path:
Generated claim is created.
↓
Claim is segmented.
↓
Inventory clause depends on inventory freshness.
↓
Return clause depends on return-policy coverage.
↓
Inventory is stale.
↓
Return policy is missing.
↓
Claim is rejected or remains pending review.
↓
Audit records unsupported segments.
↓
Operator tasks link to inventory and policy blockers.
The claim should not enter the agent feed.
A safer generated explanation may be allowed:
This product is discoverable and comparable, but checkout is blocked because inventory requires revalidation and return-policy coverage is missing.
Only if it is derived from current eligibility decisions and checkout state.
This preserves the generated-claim boundary.
Cross-cutting invalidation
The reference architecture needs invalidation as a cross-cutting mechanism.
When a dependency changes, projections and claims may need refresh. If a claim was derived from another projected claim, changing the parent projection, evidence pin, or request context should change the child's provenance hash. Upstream refusal or stale-source state should also continue to affect the child.
Examples:
Price changes:
Invalidate price claims, feed projections, checkout snapshots, and affected mandates.
Inventory becomes stale:
Invalidate checkout readiness and availability claims.
Return-policy fact is approved:
Supersede missing_return_policy blockers and refresh quote_policy decisions.
Generated claim is rejected:
Remove it from feeds, block agent quotation, and mark dependent projections as carrying an inherited refusal.
Checkout session expires:
Invalidate payment authority decisions tied to that checkout.
Mandate is revoked:
Block payment execution and audit the revocation.
Adapter projection bug is fixed:
Republish affected feed items or protocol projections if necessary.
A simple invalidation event:
type InvalidationEvent = {
invalidationId: string;
targetRef: {
kind: string;
id: string;
};
reason:
| "source_fact_changed"
| "source_fact_expired"
| "policy_fact_changed"
| "policy_conflict_detected"
| "eligibility_rule_changed"
| "checkout_state_changed"
| "authority_revoked"
| "generated_claim_invalidated"
| "projection_superseded"
| "manual_review";
causedBy: {
kind: string;
id: string;
};
occurredAt: string;
};
Invalidation prevents stale truth from continuing to act as current truth.
It also lets the platform explain why an answer changed.
Component responsibility map
A reference architecture should make ownership explicit.
| Component | Owns | Does not own |
|---|---|---|
| Source systems | Raw operational data | Agent-facing decisions |
| Commercial truth | Selected reliable facts, source refs, freshness | Action eligibility |
| Policy facts | Applicability, quotability, conflicts | Generated summaries as authority |
| Eligibility | Action-specific validity | Actor permission |
| Delegated authority | Mandate scope, confirmation, consumption, revocation | Checkout validity |
| Checkout application owner | Human and agent checkout behavior, current state, events | Payment authority as a shortcut |
| Payment | Payment attempts and provider outcomes | Order commitment without checks |
| Order | Order state and commitment | Payment-provider interpretation |
| Transaction coordinator | Cross-aggregate correlation and meaningful events | Copies of checkout, mandate, payment, or order state |
| Generated claims | Derived claim lifecycle and projection gate | Source truth |
| Decision envelope | Canonical result, reasons, freshness, hashes, evidence | Business-rule invention |
| Protocol and surface adapters | Context mapping and projection | Commercial meaning |
| Admin and support surfaces | Merchant tasks and audience-safe explanations | Separate decision logic |
This table is not a deployment prescription.
It prevents drift by giving each kind of commercial meaning one owner.
Architecture invariants
The architecture should enforce a small set of invariants.
These are more important than the deployment shape.
1. Source data is not automatically truth
Every source fact needs source references, freshness, scope, lifecycle, and conflict handling.
2. Product action status is action-specific
available: true is not enough. The platform must distinguish discovery, comparison, policy quotation, cart mutation, checkout preparation, delegated payment, checkout completion, generated-claim use, and explanation.
3. Policies must be applicable and quotable
Policy pages do not automatically become agent-quotable terms.
4. Eligibility and authority are separate
A valid action is not necessarily an authorized action.
An authorized actor does not make an invalid action valid.
5. Mutations go through state transitions
Cart, checkout, payment, and order changes should not be hidden inside adapters or request handlers. One replay owner and a monotonic command fence should prevent a committed mutation from being repeated when canonical persistence or the response path fails.
6. Payment authority is bounded
A token or artifact is evidence, not permission by itself.
Payment authority must match actor, buyer, merchant, amount, currency, checkout snapshot, lifecycle, confirmation, and state. Ambiguous provider execution must reconcile before redispatch, and confirmed capture must not be repeated when only order finalization remains.
7. Generated claims remain derived
Generated text should never become source truth, policy authority, checkout validation, or payment authority. A derived claim should bind every direct parent projection actually used, not only refused parents. It should reject any parent whose stored projection hash no longer matches its semantics. Parent repair may permit reassessment, but it must not restore authority automatically.
8. Decisions need evidence
Allowed and blocked decisions should reference the facts, policies, rules, states, authority records, claims, and projections that justify them. Canonical evidence references use explicit SHA-256 hashes, while freshness dependencies identify invalidation inputs and horizons.
9. Projections must preserve meaning
Adapters and feeds may change response shape. They should not change the domain decision. Canonical reasons must reconcile with their basis components, and an external projection should verify the hashes, authenticator, trusted key, surface, and freshness before returning the result.
10. Blockers should create remediation paths
Missing facts, stale facts, policy gaps, invalidated claims, and authority blockers should produce operator-visible work when actionable. Repeated observations should converge on one lifecycle, and resolved blockers should reopen when the same condition returns.
11. Verification and use must share one captured state
An externally supplied or persisted envelope should be captured once into a detached, deeply frozen, JSON-compatible snapshot. Integrity verification, request binding, freshness evaluation, projection, and execution evaluation should consume that same value rather than re-read caller-controlled state.
These invariants are the real architecture.
Where the architecture can go wrong
A reference architecture reduces drift, but it can still fail.
The architecture becomes a diagram only
If adapters still implement local readiness checks, the diagram does not matter. The domain decision path must be the easiest path to use.
Commercial truth becomes catalog copy
If truth does not include freshness, source references, missing facts, conflicts, and lifecycle, the platform will overtrust raw data.
Policy facts are skipped
If agents or adapters summarize policy pages directly, policy applicability and quotability are bypassed.
Eligibility becomes a boolean
If the platform exposes only available: true, it loses action-specific readiness.
Authority is treated as authentication
Knowing who the actor is does not mean the actor may request checkout mutation, payment authority, or buyer delegation.
Checkout remains endpoint-driven
If checkout mutations happen inside request handlers, the platform cannot reliably enforce state, idempotency, snapshots, expiry, or audit.
Payment artifacts become permission
If a token is treated as delegated authority, the platform may bypass mandate scope, checkout state, and cart snapshot matching.
Generated claims become truth
If generated text is read back as product or policy truth, the dependency direction has inverted. Recording direct parent projections only as descriptive IDs creates another failure: a changed usable parent or request context can leave a derived claim looking falsely unchanged.
Evidence is optional
If decisions can be made without evidence references, audit will be incomplete exactly when it matters.
Operator workflow is disconnected
If blockers do not create actionable tasks, agent-readiness becomes a runtime failure instead of an operational loop.
Feeds are optimistic
If feeds publish what the storefront hopes is true rather than what the decision layer allows, agents will act on stale or unsafe projections.
Verification and use read different state
If a boundary verifies one representation but later projects or acts on a mutable caller-controlled value, the authenticated state and the state actually used can diverge. The recipient should capture once and use the same immutable value throughout the trusted path.
Scenario tests for the reference architecture
The reference architecture should be tested with end-to-end scenarios, with component tests used to keep individual decisions precise.
A useful architecture test includes:
Input source facts
Policy coverage
Generated claim state
Actor context
Buyer context
Requested action
Expected eligibility decision
Expected authority decision
Expected state transition
Expected generated claim use
Expected generated-claim dependency refs and provenance hash
Expected evidence refs
Expected contract and schema versions
Expected rule-set hash and authenticator result
Expected protocol projection
Expected operator work item
Expected invalidation behavior
For the Travel Backpack:
| Scenario | Expected architecture behavior |
|---|---|
| Agent asks to discover product | Allowed; product facts referenced |
| Agent asks to compare product | Allowed only on known facts |
| Agent asks for return policy | Blocked; missing Travel Bags return-policy fact |
| Agent asks to add to cart | Requires inventory revalidation |
| Agent asks to prepare checkout | Blocked; stale inventory and missing return policy |
| Agent asks delegated payment | Blocked; checkout state not valid; payment authority not_evaluated; payment not attempted |
| Generated claim says “ready to ship” | Blocked or pending because inventory is stale |
| EU shipping claim requested for US buyer | Blocked as out of scope |
| Same direct parent projections arrive in a different order | Derived dependency refs and provenance hash remain identical |
| Usable parent, evidence pin, or request context changes | Derived provenance hash changes even if the wording does not |
| Parent projection is altered without updating its hash | Derivation is rejected |
| Unrelated refused projection is not a dependency | The derived claim remains untainted |
| Operator approves return-policy coverage | Policy fact becomes active; affected decisions refreshed |
| Inventory revalidated | Inventory blocker superseded |
| Feed republished | Feed projects updated eligibility decisions |
| Adapter projects blocked checkout as success | Semantic test catches drift |
| Same normalized envelope input is repeated | The three canonical hashes remain identical |
decisionId is modified after authentication |
Integrity verification rejects the envelope |
| Soft eligibility result is combined with a hard authority, checkout, or payment block | Canonical result remains blocked |
| Business mutation commits but canonical checkout persistence fails | Command remains reconciliation-required; mutation is not replayed |
| Active payment execution expires after an ambiguous provider call | Reconciliation is required; payment is not redispatched |
| Parent policy or claim is repaired after dependent invalidation | Dependent becomes ready for reassessment; authority is not restored automatically |
| Surface, freshness, hash, or authenticator is modified | External projection is rejected |
| Live requested action, subject, or actor does not match the envelope | External projection is rejected |
| Caller-controlled envelope changes after its first read | Boundary uses one detached immutable snapshot; the verified result does not drift |
| Older signed envelope is presented after checkout, mandate, or product-decision state changes | Mutation is rejected because the envelope no longer matches the latest state that controls the operation |
| Buyer-safe explanation requested | Internal evidence redacted, blocker meaning and projection axes preserved |
| Audit reconstruction requested | Decision inputs, evidence refs, rule identity, and projections available |
A simplified scenario type:
type ReferenceArchitectureScenario = {
name: string;
sourceFacts: string[];
policyFacts: string[];
generatedClaims: string[];
intent: CommerceIntent;
expectedEligibility: EligibilityDecision["result"];
expectedAuthority?: AuthorityDecision["result"];
expectedCheckoutState?: AgentCommerceCheckoutLifecycleState;
expectedBlockerCodes: string[];
expectedEvidenceKinds: string[];
expectedEnvelopeSchemaVersion: "agent-commerce-decision-envelope-schema-v4";
expectedReasonComponentReconciliation: boolean;
expectedAuthenticatorResult: "valid" | "invalid";
expectedProjection: string;
expectedOperatorWorkItems: string[];
};
The goal is to test whether the architecture preserves meaning across layers.
The same scenario should not produce one answer in the feed, another in the tool adapter, another in checkout, and another in admin.
A practical implementation roadmap
A platform does not need to implement the full architecture at once.
A practical roadmap:
Define the action vocabulary.
Start withdiscover,compare,quote_policy,add_to_cart,prepare_checkout,delegate_payment,complete_checkout,show_generated_claim, andexplain.Build commercial truth snapshots.
Add source references, freshness, missing facts, conflicts, and lifecycle.Structure policy facts.
Model returns, shipping, warranty, cancellation, regional restrictions, and business-buyer rules with applicability and quotability.Add eligibility decisions.
Produce action-specificallowed,blocked,requires_review,requires_revalidation, andrequires_confirmationdecisions.Keep adapters thin.
Map external protocol requests into canonical intents. Capture externally supplied or persisted envelopes once into a detached, deeply frozen, JSON-compatible snapshot, then use that same value for hash verification, reason/component reconciliation, authenticator and trusted-key validation, target-surface binding, live action/subject/actor binding, freshness evaluation, and projection. Before changing state, compare protected dependency hashes with current authoritative snapshots and require a fresh decision when a dependency is missing, changed, or expired.Move checkout behind commands.
Introduce explicit checkout states, cart snapshots, idempotency, expiry, and transition audit.Model payment authority.
Bind mandates to actor, buyer, merchant, amount, currency, cart snapshot, expiry, revocation, and confirmation.Store generated claims as records.
Attach dependencies, scope, review status, allowed use, projection axes, inherited refusal state, value hashes, invalidation, expiry, and audit. For claims derived from projected claims, verify and bind every direct parent projection, including usable parents, into the child’s canonical provenance hash.Add decision evidence.
Attach explicit SHA-256 content or canonical-snapshot evidence references to allowed and blocked decisions. Keep invalidation dependencies and their horizons distinct from replayable evidence.Build operator remediation.
Create tasks from missing facts, stale facts, policy gaps, generated-claim review, checkout blockers, and authority blockers.Publish feeds from decisions.
Do not publish readiness from catalog flags alone.Add invalidation.
Refresh decisions, claims, feeds, and tasks when dependencies change.Keep one coherent cross-surface semantic test suite.
Use the same scenario set across feeds, tools, adapters, checkout, payment, admin, and support. Test semantic parity, reason/component reconciliation, deterministic hashes, direct dependency-projection binding, verified-state identity, execution-time dependency revalidation, and external-boundary refusal without relying on broad response snapshots or route counts.
This roadmap builds the architecture in layers while reducing the highest-risk drift first. It should not turn every concept into a permanent subsystem.
The tradeoff
The reference architecture adds structure.
It requires facts, policy modeling, eligibility decisions, authority checks, checkout transition models, generated-claim lifecycle, evidence, audit, projections, invalidation, and operator workflows.
That is more work than adding an agent endpoint to an existing storefront.
For simple read-only product discovery, a lighter approach may be enough for a while.
Agent-ready commerce changes the threshold when agents can quote policies, compare products, prepare checkout, operate under buyer authority, explain payment refusal, or act across multiple protocols.
The tradeoff is between local integration speed and platform consistency.
A shortcut can make one adapter work.
A reference architecture keeps the platform from developing several incompatible versions of commercial truth.
The goal is not to make commerce rigid.
The goal is to make commercial decisions explicit enough that agents can use them safely.
The series in one model
The full series can be reduced to one chain:
Facts
↓
Commercial truth
↓
Policy facts
↓
Eligibility
↓
Authority
↓
State transition
↓
Generated claims
↓
Evidence
↓
Audit
↓
Adapters and projections
↓
Operator remediation
Each article strengthened one link.
Part 1 introduced the model.
Part 2 made facts source-backed and freshness-aware.
Part 3 made readiness action-specific.
Part 4 made policies machine-readable and quotability-aware.
Part 5 kept protocol adapters from owning commercial meaning.
Part 6 turned checkout into explicit state transitions.
Part 7 treated delegated payment as bounded authority over a checkout snapshot.
Part 8 treated generated text as derived claims with lifecycle, scoped projection, and direct parent-projection binding.
Part 9 made evidence and audit part of the product.
Part 10 connects those ideas into a complete reference architecture.
The architecture is not about letting agents do everything.
It is about making clear what agents may know, say, request, mutate, and pay for, and why.
Conclusion
Agent-ready commerce is a platform capability.
It is not a product feed alone. It is not a chatbot. It is not a protocol adapter. It is not a payment token. It is not generated product copy. It is not a single admin toggle.
It is a commerce architecture where facts, policies, eligibility, authority, checkout state, payment boundaries, generated claims, evidence, audit, projections, and operator workflows are connected.
For the Travel Backpack, the architecture gives a precise answer:
Discover:
allowed
Compare:
allowed on known facts
Quote return policy:
blocked because return-policy coverage is missing for Travel Bags
Add to cart:
requires inventory revalidation
Prepare checkout:
blocked because inventory is stale and return-policy coverage is missing
Delegate payment:
blocked because checkout state is not valid for payment
payment authority: not_evaluated
payment dispatch: not attempted
Generated claim:
show_generated_claim action: requires_review
canonical claim status: inherited_refusal; inheritedRefusalCount: 1
Operator tasks:
revalidate inventory
approve return-policy coverage
review generated claim
That is the difference between catalog availability and agent-ready commerce.
The platform does not need to guess.
The adapter does not need to invent rules.
The generated text does not become truth.
The payment artifact does not bypass checkout.
The audit trail can explain the decision.
That is the reference architecture this series has been building toward.
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 (13)
The framing of every surface as a projection of one decision spine is the part that's going to stick w/ me, and the eligibility vs authority split even more, people collapse those two into "authz" constantly and then act surprised when a perfectly authenticated actor walks an invalid action straight through (same energy in the payment-artifact-is-evidence-not-permission point, that reframe alone kills a whole category of bugs). Where i'd push is the line you almost throw away, that the domain decision path has to be the easiest path to use. imo that's the actual ballgame and it's more of an org problem than an architecture one. Every anti-pattern you list, adapter sneaking in a local in_stock && price<150 check, generated text quietly becoming truth, feed going optimistic, is just what happens under a deadline when the shortcut is one line and the spine is a call through three layers. The invariants are right but nothing structural stops a dev bypassing them at 5pm on a friday, so the only teeth i've seen actually hold is the cross-surface contract test you gesture at, i.e. run the same Travel Backpack scenario through the feed, the tool adapter, checkout and admin and fail CI when they disagree on even one blocker code. w/o that the drift isn't a risk, it's a certainty on a delay.
fwiw this maps almost 1:1 onto grounding an LLM, which is where i spend most of my time. "Source data is not automatically truth, it becomes a referenced fact" and "generated claims are derived, not authoritative" is the same discipline as not letting a model assert anything it can't cite back to a verified source. Different domain, identical spine.
Thank you very much for the thoughtful review and I'm glad the architecture resonated! I'm taking your points into consideration and I'm going to refine both the article and the reference implementation. The architecture already assumes one decision spine, with eligibility, authority, checkout state, payment evidence, policy grounding, and generated claims kept separate. What I'd make more explicit is that the domain decision path also has to be the easiest path for adapters to use. Shared route and public type ownership have already been consolidated across API/client/protocol surfaces, command dispatch has been simplified, and the next source step is a canonical decision envelope that carries eligibility, authority, blocker codes, evidence references, checkout state, payment authority, generated-claim status, and next safe actions in one shape.
One clarification, the Travel Backpack scenario is meant as a small semantic drift check across feed, tool adapter, checkout, and admin, not a big contract-test framework. It simply confirms that the same blocker codes and causal meaning survive across surfaces.
totally fair, and the small-drift-check framing is actually the stronger version, i was overloading it. my point wasn't really about size though, it was about where it's wired. a small semantic drift check that runs as a required check on merge is worth more than a full contract-test framework that's optional, because the thing that kills you isn't the check being too thin, it's it being skippable at 5pm on a friday. so keep it small, just make it non-optional. same blocker codes surviving across feed/adapter/checkout/admin is exactly the right assertion, and if that one check red-blocks the merge, the drift you're worried about can't land quietly.
the canonical decision envelope is the part i'd be most excited to see, especially carrying generated-claim status as a first-class field. that's the same move as making an LLM cite back to a verified source before it's allowed to assert: generated is derived until something grounds it. once that status rides in the envelope instead of living in each adapter's head, the "generated text quietly becoming truth" failure basically can't express itself. different domain, identical spine, like i said.
Updated. Implemented the small non-optional drift check, one canonical envelope, and thin projections.
The implementation now centers on one canonical decision envelope carrying eligibility, actor authority, checkout state, payment authority, generated-claim status, evidence references, and next safe actions. Each surface projects from that envelope instead of recreating meaning locally.
Generated-claim status is first-class in the envelope, so generated text remains derived unless it is grounded, scoped, reviewed, and allowed for that use.
love that you shipped it, and putting eligibility, authority, checkout, payment and generated-claim status in the one envelope is exactly the move. now that it's real, the next thing that can still slip is scope on the grounding.
"grounded, scoped, reviewed, allowed" is a status, but grounded is never global, it's grounded FOR something. the failure that can still express itself is a claim that got grounded and allowed for one surface getting projected into another where the scope doesn't hold, and because the envelope says allowed, the second surface trusts it. it's the same as a citation licensing the exact claim it grounds and not every paraphrase downstream of it. so i'd have the envelope carry what the allowance is scoped TO, and let the projections check allowed-for-this-use, not just allowed. thin projection means pure read, and the one thing it must never do is widen its own scope.
the other one is decay. grounding is only as true as the evidence it pinned to, so if the reference isn't pinned to a version or a hash, the source can move and "grounded" quietly goes stale with no status ever changing. pin the grounding to the evidence version and the check can catch a claim still riding as grounded against evidence that shifted under it.
and both of those are things your small non-optional check can assert, not just the blocker codes surviving: no projection re-derives status locally, and no grounded claim outlives the evidence version it was grounded against. keep it small, keep it non-optional, just let it guard the envelope's meaning and not only its shape.
Really appreciate you catching that! I'll make the grounded/allowed status scoped to a requested use and pinned to source evidence.
The current baseline has hashes, source versions, and use-specific policy helpers, but the shared envelope does not force the "allowed for this use against this evidence pin" decision.
I'm adding that as a compact domain primitive, so the envelope carries
allowedForandevidencePin, and projections can only use a grounded claim after calling the domain helper for the exact requested use/surface/scope. The generated policy-claim projection should use that helper, so it does not widen scope locally or keep trusting a claim if the pinned evidence changes.that's exactly it, allowedFor plus evidencePin as one primitive is the compact version of the whole thing. the only piece i'd harden further is the part where projections "should" call the helper. should is the word that fails at 5pm on a friday.
right now the guarantee holds as long as every projection remembers to go through the helper, but the raw grounded claim is still sitting there as a readable field, so a new projection can read it directly and skip the gate with nobody noticing. the fix is to make that impossible instead of discouraged: the usable value should only exist as the helper's return, so the grounded claim isn't reachable except through allowedFor plus evidencePin. then "forgot to call the helper" can't be written, not just shouldn't be. and that's the exact thing your non-optional check asserts: no projection reads the claim field directly, only the helper's output.
one more on the negative path. when the helper says no, either not allowed for this use or the evidence pin moved, that result has to be a distinct state the surface handles, not an empty. if a blocked claim comes back as null and null already means "nothing to show", a revoked claim and an absent claim look identical, and the interesting failure hides in that collision. make refused-here and never-grounded two different answers, so a projection can't quietly treat "you lost the right to say this" as "there was nothing to say".
different domain, same spine, still.
I tightened that last part too.
The raw projected value is no longer exposed from the envelope. The usable claim text only comes back from the projection helper after the requested use, requested surface, scope, and current evidence pin all pass.
I also separated the negative path. A claim that exists but is refused for this surface returns a refused-here state. A claim that was never grounded, lost its projectable value, or no longer matches its pinned value returns a never-grounded state. So revoked, stale, out-of-scope, and absent no longer collapse into the same empty result.
One extra tightening I added was value drift protection. The helper-owned projectable value is now cloned and frozen when the envelope is built, and the helper re-checks its hash before returning it as usable. I believe that fixes the bypass concern and keeps the gate about meaning, not just shape.
Thank you for pushing on this!
the clone-and-freeze with the hash recheck is the right final piece, and i like that you got there on your own, because it closes a gap i didn't even name: even with the helper as the only door, a value that stays mutable behind the door can still drift after the check. freezing it at build time and re-verifying the hash on the way out makes the helper's yes actually mean something, the thing it approved is the thing it hands back.
what you've ended up with is basically a capability for generated claims. the grounded text isn't a field you read, it's a right you exercise, and only through a gate that checks use, surface, scope and evidence and then hands back something tamper-evident. refused-here vs never-grounded keeps the gate honest about why it said no. that's a real security model, not just a status flag, and it's the version of this i'd reach for in any domain where generated output can quietly become an authority.
genuinely enjoyed this one. different domain, same spine, and now it has teeth.
Thanks, Vinicius. I'd be happy to continue the conversation beyond the comment thread. Is there a good way to contact you directly, especially around possible collaboration or freelance work?
for sure, dimitrios, i'd be up for that. this thread was a good one.
easiest is email: vinimabreu@yahoo.com.br, or i'm vinimabreu on github. drop me a line whenever and we can pick any of these back up, or get into the freelance side properly. either way, glad we dug into it.
Came for the architecture, stayed for the thread — you two independently rebuilt something I've been calling a typed provenance vector from the commerce side, and the convergence is the interesting part.
Your decision spine is a provenance chain: every projection is only as trustworthy as the facts it pins to, and evidencePin is exactly a freshness axis — "grounded" quietly goes stale when the source moves unless the claim is pinned to a version. The move Vinicius pushed you toward — grounded text isn't a field you read, it's a right you exercise through a gate that checks use/surface/scope/evidence and hands back something tamper-evident — is capability-based security applied to generated claims. Same thing I landed on for agent side effects: make the unsafe path unrepresentable, so "forgot to call the helper" can't be written, not just shouldn't be.
Two things from the provenance-chain angle that might transfer:
1/ refused-here vs never-grounded is the start of a taint distinction, and it wants to propagate. If a claim refused for surface A feeds a derived claim that then gets projected to surface B, the derivation can launder the refusal — B sees a fresh-looking claim and never learns its input was refused upstream. The scope check has to taint downstream, not just gate at the leaf, or the exact failure you closed re-enters one level up.
2/
allowedForscoped to a use is really one axis of several. Freshness (evidencePin) and scope (allowedFor) degrade independently — a claim can be perfectly fresh but out-of-scope, or in-scope but stale. Collapsing them into one allowed/refused bit works until a consumer cares about one axis and not the other. At that point the envelope wants to carry the axes separately and let each projection apply its own floor.Genuinely one of the best comment threads I've read here — the "different domain, same spine" line is doing a lot of work, and it's correct.
Hi Sergei! Agreed. Your framing helped me make the model more precise: not just "a claim is grounded" but "a claim is projectable only with its provenance state carried through".
I've now applied that in the implementation. Derived claims now carry upstream projection state, so a refused input cannot become clean just because it was reworded or composed into another claim. I also kept the projection reasons separated (source, freshness, scope, surface, use, payload, and inherited refusal) so each surface can apply its own floor instead of reading one flattened allowed/refused bit.
That connects directly with Vinicius capability-gate point. Thanks for pushing on this.