Quote Integrity & Price Lock
A signed, TTL'd price commitment binding variant + quantity + buyer-context hash + resolved price to a checkout window. Guarantees that the price the agent quoted is the price charged.
Why this spec exists
An AI shopping agent recommends Product A at $310.00. The buyer says “add it to cart.” At checkout, the price shows $349.00. This is the single fastest way to destroy buyer trust in an agent-powered commerce channel.
RAOS-0007 defines a QuoteToken — a signed object that binds a price to a specific buyer context, variant, and quantity, valid for a merchant-declared TTL. The checkout endpoint validates the token before processing. If the token is expired, the context changed, stock is gone, or the price drifted, the validation result tells the agent exactly why — and what to do.
Checkout Validation — 5-step order
The check order is non-negotiable — forgery detection must run before business-logic checks.
HonorPolicy — merchant-declared
Merchants declare their honor policy per-variant in the catalog. The policy is snapshotted into every issued token so validation always uses the policy that was in effect at issue time (not a live merchant lookup).
B2B context: predictable pricing, no surprises at PO time. Any expiry or stock loss requires a new quote.
Consumer grocery: honor within 2-minute grace window. Partial fulfillment rather than hard reject when stock runs short.
Reason Code Registry
All RAOS-0007 codes are synthetic-only — they require issueQuote + validateQuote with injected timestamps and cannot appear in the static catalog fixture grid. All are exercised in src/lib/rules/__tests__/quote.test.ts.
QUOTE_ISSUEDINFOsynthetic-onlyA QuoteToken was successfully issued. Emitted by the QUOTE-stage evaluator after all BLOCK checks pass and a signed token is produced. This is the affirmative signal that the agent's quoted price is locked.
QUOTE_EXPIREDBLOCKsynthetic-onlyThe quote has passed its TTL. Resolution depends on honorPolicy.onExpiry: 'requote' → REQUOTE_REQUIRED; 'honor_grace' + within grace → continue; 'honor_grace' + past grace → REQUOTE_REQUIRED; 'reject' → REJECTED.
Resolution: Issue a fresh quote. The buyer must re-confirm the price.
QUOTE_CONTEXT_CHANGEDBLOCKsynthetic-onlyEither (a) the buyer's context hash differs between issue and validate time (tier downgraded, trust mode changed), or (b) the recomputed price at checkout time differs from the locked price (promo ended, price tier changed). Always REQUOTE_REQUIRED.
Resolution: Issue a fresh quote with the current buyer context and current price.
QUOTE_STOCK_LOSTBLOCKsynthetic-onlyStock is now insufficient for the quoted quantity. For onStockLoss='reject': REJECTED. For onStockLoss='partial' with zero available: also REJECTED.
QUOTE_PARTIALLY_HONOREDCONDITIONsynthetic-onlyStock is short but the merchant's policy is onStockLoss='partial'. The quote is honored for the available quantity. honoredQuantity is set in QuoteValidationResult. The agent must surface the partial fulfillment to the buyer before checkout.
Resolution: Present the buyer with the partial quantity and new total. Allow them to accept or cancel.
QUOTE_FORGEDBLOCKsynthetic-onlyThe RAOS-0008 signature verification failed (excluding DATA_STALE which falls through to the TTL check). The token payload was tampered with after signing. REJECTED — do not proceed to checkout.
QUOTE_HONORED_GRACEINFOsynthetic-onlyThe token is expired but still within the merchant's declared grace window (onExpiry='honor_grace'). The quote is honored; this INFO code is prepended to the result reasons so the agent knows it is operating in grace. All remaining checks (steps 3–5) still run.
Open Questions
These are unresolved design decisions. Feedback welcome — open an issue or start a discussion.
The contextHash currently binds the buyer's privilege fields (tier, trust mode, region, etc.). If a promotional offer was active at issue time but ended before validation, the price recomputation (step 5) catches it. However, if the offer list changes without changing the recomputed price (e.g. a stackable offer is removed but a replacement promo produces the same total), step 5 would not detect it.
Proposed direction: Leaning: keep appliedOffers out of the hash; price equality is the true invariant. An alternative is to hash appliedOffers ids — adds coverage but makes the hash context-sensitive to promo identifiers that may not be stable.
The spec mandates onContextChange: 'requote' always. A merchant may argue that a minimal context change (e.g. shipping vs pickup mode but same price) should not invalidate a quote. This creates a class of 'quote-preserving context changes'.
Proposed direction: Defer to the first merchant integration that hits this. The current rule is the safe default. Relaxation requires a clear taxonomy of price-neutral context changes.
The current model issues one QuoteToken per (variant, quantity, context). A B2B purchase order with 50 line items would require 50 tokens. A cart-level QuoteToken covering all items is more efficient but requires the hash to cover all line items. Multi-item quote semantics are not defined yet.
Proposed direction: Per-item is the safe v1 model. Cart-level quote is a natural WP-12 (Cart & Checkout) concern. Keep them separate.
When QUOTE_PARTIALLY_HONORED fires, the agent must surface the partial fulfillment to the buyer. What must the agent say? What must the merchant provide? Is the new total automatically computed, or does the agent need to call getApplicablePrice again for the reduced quantity?
Proposed direction: The agent should compute the new total as honoredQuantity × unitPrice (the locked unit price). The spec should mandate that the agent presents this before proceeding and requires an explicit buyer accept.
The current spec is silent on storage. An agent could store the QuoteToken in local state, a session store, or pass it opaquely to the merchant's cart/checkout endpoint. The merchant's checkout endpoint must be able to re-validate the token at checkout time.
Proposed direction: Out of scope for RAOS-0007 (a storage and transport concern). RAOS-0010 (Cart & Checkout Handoff) should define the token transport contract.
When verifyEnvelope returns DATA_STALE (step 1), validateQuote falls through to the TTL/grace check (step 2). If the token is within its grace window, DATA_STALE would be silently absorbed. Should QUOTE_HONORED_GRACE always co-emit with DATA_STALE when the token envelope is stale?
Proposed direction: Leaning: yes. Emit both. QUOTE_HONORED_GRACE documents the grace path; DATA_STALE on the inner envelope signals the signing key age. They are additive, not exclusive.