Pricing rules primer
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_versionin the rules document, and the configuration schema (linked from the deployment'smeta.discovery) is the exact document format. Thegateeffect'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.