Pricing rules primer

In development Last updated

How sellers price with rules — the structured-effects model, the narrow expression profile, and worked examples from our own offer.

In development. The model below is live, not a plan: quotes and simulations run through this engine today, and the expression profile is pinned — it carries its own profile_version in the rules document, and the configuration schema (linked from the deployment's meta.discovery) is the exact document format. The gate effect's operations — engagement and the approval chain — and escalations are live too; the NDA's signing ceremony, the human signature and payment-method verification complete when contracts and e-signature land. The worked examples below use the configuration document's exact member names and spellings; the published schema is the source of truth for the full shape.

Rules are how a seller says what may be bought, at what price, and under which conditions — without writing code, and without a sales improvising exceptions. This primer is conceptual: the vocabulary is defined in core concepts.

Data, not code#

A rule is a record with three parts: a phase, an optional condition over the offer's variables, and typed effects. Effects are data; the engine sequences them. A rule never returns a value, never calls another rule by name, and never loops — a rule reads the quote as it stands so far. Infinite recursion isn't handled; it is unexpressible.

Effects#

Effect What it declares
line A quote line: a price component or a computed charge
discount A discount, with its own attribution
surcharge An added charge
require A constraint on choices (variables, terms)
gate A gate the deal must satisfy
clause A clause added or varied in the documents
document A document added to the contract set
supersede One term replacing another
chain A linkage between terms
schedule A time-based behavior

Only four fields of a rule hold expressions — when, amount, qty, cond — and everything else is structured data. That boundary is what keeps rules diffable between configuration versions, explainable on a quote, and safe to evaluate for a buyer's agent.

The expression profile#

Expression slots use a deliberately narrow profile of CEL — an allow-list, not a subset: comparisons, boolean operators, +, -, *, ?:, in, size, and the all/exists/filter/map macros, plus house functions price, pct, min, max, sum, days_between and date literals. There are no floats (integers only), no time zones, no string functions, no indexing — quotes must come out identical on every deployment. Rounding exists at exactly one point, pct(), round-half-even. The profile carries its own version, so it can grow additively without touching the rules document.

Phases, and policies as data#

Evaluation runs in three phases — lines, then adjustments, then gates — each with its own variable scope. A rule names its phase; within a phase, rules apply in document order. Between phases, the engine applies policies, which are data too: percentage_discounts: largest_wins, for example, is a declared policy the engine applies with its own attribution — not something your rules sequence by hand.

Recurring amounts are per month#

A recurring price component — flat_recurring or per_unit — carries an amount per month, never a per-term or per-invoice amount. The price table has no period member of its own: the period lives in the line kind the rule's line effect declares — flat_monthly for a flat fee, per_seat_monthly for a per-seat price — and the buyer's billing frequency scales one monthly amount to one invoice. A $120,000/yr platform fee is a flat_recurring price of $10,000 per month; billed annually, each invoice carries 12 × $10,000. The price kinds map to line kinds: flat_recurring prices a flat_monthly line, per_unit a per_seat_monthly line, per_event and usage_metered a per_event line naming the component as its meter, one_time a one_time line.

Worked examples#

We are our own first customer: the examples below are shapes from our own offer, run through the same engine you would use. They are illustrative — alternative shapes over one meter — but every member name and spelling is the document's own.

$5 per successful deal#

A per-event price component. The meter takes its name from the component's stable key — here deal.successful — and counts the thing being priced:

"deal.successful": {
  "kind": "per_event",
  "unit": "successful deal",
  "scale": 0,
  "amounts": {"USD": 500}
}

We record usage events as the system of record and reconcile them with the billing integration.

Allowance plus overage#

The same meter, priced as a usage-metered component with an included allowance instead: the first 100 units a month are covered, and each one beyond them is billed at the component's own price (or, when an overage member names another component, at that component's price):

"deal.successful": {
  "kind": "usage_metered",
  "unit": "successful deal",
  "amounts": {"USD": 500},
  "cap": 100,
  "allowance_reset_period": "month"
}

Quantities are integers — the metering path has no floats anywhere. A meter's key is immutable once any published configuration version references it: renaming a component creates a new meter, not a renamed one.

Self-hosted discount#

A percentage discount, conditioned on a hosting-model variable:

when: hosting == 'self_hosted'
discount: 10%  (as basis points: 1000)

A give-get: logo rights#

A concession tied to a commitment, in one rule — the discount simply does not exist unless the buyer grants the logo right:

when: logo_rights_granted
discount: 5%

Give-gets are first-class: the concession and the commitment live in the same rule, so the linkage can't drift, and the quote shows the attribution.

Human signature above $15,000/yr#

Not an exception process — a gate, configured like any other, with a threshold policy. The threshold itself is a per-currency entry in the price table (never FX-derived):

gate: human_review
  human_required: above
  threshold_price: human_review_threshold

Below the threshold, a bearer credential may satisfy the gate under the seller's policy; above it, a human signs. The API enforces it and says so, with the escalation path attached.

Simulate before you publish#

Every change to your configuration is a draft until you publish it, and publishing creates a new immutable configuration version. Before you publish, run simulations: hypothetical deals against the draft, with any variable choices, seeing the resulting quote, gates and documents — with no side effects. The same inputs always produce the same quote, in simulation and in production; that determinism is the product.