guardrail-sim

UCP Types

Universal Commerce Protocol type definitions for agentic commerce

@guardrail-sim/ucp-types

Type definitions aligned with the Universal Commerce Protocol for agentic commerce integration.

Installation

npm install @guardrail-sim/ucp-types

Overview

This package provides TypeScript types aligned with UCP spec revision 2026-04-08, enabling guardrail-sim to integrate with the agentic commerce ecosystem. It includes:

  • Discount types - Error codes, allocations, applied discounts and rejection messages
  • Checkout types - Checkout sessions, line items, buyers, totals
  • Cart, Order, Catalog and Identity Linking - the remaining UCP capabilities
  • Profile / discovery - the /.well-known/ucp document and capability negotiation
  • Converters - Functions to convert between policy engine results and UCP formats

Discount Types

DiscountErrorCode

Standard UCP error codes for discount validation.

import type { DiscountErrorCode } from '@guardrail-sim/ucp-types';

const code: DiscountErrorCode = 'discount_code_invalid';

Available codes:

  • discount_code_invalid - Code doesn't exist or exceeds limits
  • discount_code_expired - Code is past validity window
  • discount_code_already_applied - Code already in use
  • discount_code_combination_disallowed - Can't combine with other codes
  • discount_code_user_not_logged_in - Requires authentication
  • discount_code_user_ineligible - User doesn't qualify

DiscountMethod

How discounts are allocated across line items.

import type { DiscountMethod } from '@guardrail-sim/ucp-types';

const method: DiscountMethod = 'across'; // proportional
// or 'each' - even distribution

AppliedDiscount

A successfully applied discount.

import type { AppliedDiscount } from '@guardrail-sim/ucp-types';

const discount: AppliedDiscount = {
  code: 'SUMMER20',
  title: 'Summer Sale 20%',
  amount: { amount: 10000, currency: 'USD' },
  method: 'across',
  priority: 1,
  allocations: [
    { target: '$.line_items[0]', amount: { amount: 6000, currency: 'USD' } },
    { target: '$.line_items[1]', amount: { amount: 4000, currency: 'USD' } },
  ],
};

RejectedDiscount

A discount that was rejected.

import type { RejectedDiscount } from '@guardrail-sim/ucp-types';

const rejected: RejectedDiscount = {
  code: 'INVALID50',
  error_code: 'discount_code_invalid',
  message: 'Discount exceeds maximum allowed',
};

DiscountValidationResult

Result of pre-checkout validation.

import type { DiscountValidationResult } from '@guardrail-sim/ucp-types';

const result: DiscountValidationResult = {
  valid: false,
  error_code: 'discount_code_user_ineligible',
  message: 'Volume tier requires quantity >= 100',
};

Checkout Types

LineItem

A priced line in a checkout response. Note id and the totals[] breakdown — a line item does not carry a subtotal: Money field.

import type { LineItem } from '@guardrail-sim/ucp-types';

const item: LineItem = {
  id: 'li-8f2a1c04',
  item: { id: 'SKU-001', title: 'Widget', price: 250000 },
  quantity: 2,
  totals: [{ type: 'subtotal', amount: 500000 }],
};

LineItemRequest is the looser shape accepted on input: just item and quantity. The item must carry an inline title and price — bare references cannot be priced.

Money and amounts

import type { Money } from '@guardrail-sim/ucp-types';

const price: Money = {
  amount: 99900, // $999.00 in cents
  currency: 'USD',
};

Most amounts are plain integers, not Money

UCP states amounts as raw integers in minor units. Money exists as a wrapper where currency context is useful, but Total.amount, AppliedDiscount.amount and DiscountAllocation.amount are all plain number.

Total and the discount sign convention

import type { Total } from '@guardrail-sim/ucp-types';

const totals: Total[] = [
  { type: 'subtotal', amount: 750000 },
  { type: 'discount', amount: -75000 }, // negative
  { type: 'total', amount: 675000 },
];

Discounts are negative in totals, positive in `applied`

As of UCP 2026-04-08, a discount entry in totals[] (and line_items[].totals[]) is negative — it states the effect on the receipt. The same discount in discounts.applied[].amount is positive — it states the discount's value. The two are deliberately opposite signs, and conflating them double-counts the discount.

CheckoutWithDiscounts

A checkout session carrying the discount extension under its namespaced key.

import type { CheckoutWithDiscounts } from '@guardrail-sim/ucp-types';

const checkout: CheckoutWithDiscounts = {
  id: 'c-1f8e...',
  status: 'ready_for_complete',
  currency: 'USD',
  line_items: [
    /* LineItem[] */
  ],
  totals: [
    { type: 'subtotal', amount: 750000 },
    { type: 'discount', amount: -75000 },
    { type: 'total', amount: 675000 },
  ],
  links: [],
  payment: { handlers: [] },
  'dev.ucp.shopping.discount': {
    codes: ['SUMMER20'],
    applied: [
      /* AppliedDiscount[] */
    ],
    messages: [
      /* rejections, as warnings */
    ],
  },
};

CheckoutStatus is one of incomplete, ready_for_complete, in_progress, completed, canceled or requires_escalation. There is no open status, and there are no applied_discounts / rejected_discounts fields — rejections travel in messages[].

Spec Version and Capabilities

import {
  UCP_SPEC_VERSION, // '2026-04-08'
  SUPPORTED_UCP_VERSIONS, // ['2026-04-08', '2026-01-23', '2026-01-11']
  CHECKOUT_CAPABILITY,
  CART_CAPABILITY,
  CATALOG_CAPABILITY,
  ORDER_CAPABILITY,
  DISCOUNT_EXTENSION,
  FULFILLMENT_EXTENSION,
  IDENTITY_LINKING_CAPABILITY,
  GUARDRAIL_UCP_PROFILE,
  serializeProfile,
} from '@guardrail-sim/ucp-types';

// The discount extension augments both checkout and cart as of 2026-04-08.
DISCOUNT_EXTENSION.extends;
// ['dev.ucp.shopping.checkout', 'dev.ucp.shopping.cart']

GUARDRAIL_UCP_PROFILE is the /.well-known/ucp document this project advertises, built from those constants so the spec version is stated exactly once. serializeProfile() returns it exactly as the MCP server serves it.

Other Modules

ModuleContents
profile.tsUCPProfile, compareVersions, negotiateCapabilities, profileSupportsCapability
cart.tsCartResponse, CartLineItem, create/update request types
order.tsOrder, OrderFulfillment, OrderWebhookEvent, webhook signature headers
identity-linking.tsOAuth 2.0 flows: server metadata (RFC 8414), token/refresh/revocation types

Converter Functions

toUCPErrorCode

Map a policy violation to a UCP error code. Takes the whole Violation, not a rule name — it delegates to getUCPErrorCode in @guardrail-sim/policy-engine, which owns the single mapping table.

import { toUCPErrorCode } from '@guardrail-sim/ucp-types';

const code = toUCPErrorCode({ rule: 'margin_floor', message: 'below floor' });
// 'discount_code_invalid'

toDiscountValidationResult

Convert a policy evaluation into a validation result. The code argument is echoed back on the result.

import { toDiscountValidationResult } from '@guardrail-sim/ucp-types';

const evaluation = await engine.evaluate(order, 0.3);
const result = toDiscountValidationResult(evaluation, 'SUMMER30');
// {
//   code: 'SUMMER30',
//   valid: false,
//   error_code: 'discount_code_invalid',
//   message: 'Calculated margin falls below 15% floor',
//   limiting_factor: 'margin_floor'
// }

buildDiscountExtensionResponse

Build a UCP discount extension response. The amount is a plain integer in minor units.

import { buildDiscountExtensionResponse } from '@guardrail-sim/ucp-types';

const response = buildDiscountExtensionResponse(
  ['SUMMER20'], // codes
  evaluationResult, // policy result
  75000, // discount amount in cents
  'Summer Sale' // title
);

// Approved:
// { codes: ['SUMMER20'], applied: [{ code: 'SUMMER20', amount: 75000, ... }] }
//
// Rejected — note there is no `rejected` array; rejections are warnings:
// {
//   codes: ['SUMMER20'],
//   applied: [],
//   messages: [{
//     type: 'warning',
//     code: 'discount_code_invalid',
//     message: '...',
//     field: 'dev.ucp.shopping.discount.codes'
//   }]
// }

calculateAllocations

Distribute a discount across line items. across is proportional to each line's subtotal; each splits evenly. Amounts are plain integers.

import { calculateAllocations } from '@guardrail-sim/ucp-types';
import type { LineItem } from '@guardrail-sim/ucp-types';

const lineItems: LineItem[] = [
  {
    id: 'li-a',
    item: { id: 'A', title: 'Widget', price: 150000 },
    quantity: 2,
    totals: [{ type: 'subtotal', amount: 300000 }],
  },
  {
    id: 'li-b',
    item: { id: 'B', title: 'Gadget', price: 200000 },
    quantity: 1,
    totals: [{ type: 'subtotal', amount: 200000 }],
  },
];

const allocations = calculateAllocations(50000, lineItems, 'across');
// [
//   { target: '$.line_items[0]', amount: 30000 },
//   { target: '$.line_items[1]', amount: 20000 }
// ]

fromUCPLineItems

Convert UCP line items to the policy engine's Order. The option key is productMargin.

import { fromUCPLineItems, DEFAULT_PRODUCT_MARGIN } from '@guardrail-sim/ucp-types';

const order = fromUCPLineItems(lineItems, {
  productMargin: 0.4, // 40% base margin
  customerSegment: 'gold',
});
// { order_value: 500000, quantity: 3, product_margin: 0.4, customer_segment: 'gold' }

Margin is an assumption, not a measurement

UCP line items carry price but not cost, so margin cannot be derived from a cart. Omitting productMargin falls back to DEFAULT_PRODUCT_MARGIN (0.3), which is exported so you can see the number you are inheriting. Pass your real margin whenever you know it.

Usage with MCP Server

The MCP server's UCP-aligned tools use these types internally:

// validate_discount_code returns DiscountValidationResult
{
  "code": "SUMMER30",
  "valid": false,
  "error_code": "discount_code_invalid",
  "message": "Calculated margin falls below 15% floor",
  "limiting_factor": "margin_floor",
  "max_allowed": 75000
}

// simulate_checkout_discount returns DiscountExtensionResponse
// (plus `currency` and `allocations`). Rejections are messages, not a `rejected` array.
{
  "codes": ["SUMMER20"],
  "applied": [],
  "messages": [
    {
      "type": "warning",
      "code": "discount_code_invalid",
      "message": "Calculated margin falls below 15% floor",
      "field": "dev.ucp.shopping.discount.codes"
    }
  ],
  "currency": "USD"
}

See Also

On this page