Skip to content

A Rule is a stratified, verdict-producing, side-effect-free evaluation function. Rules read from facts and lower-stratum verdict outputs. They produce verdict instances. They do not modify entity state. They do not invoke operations. Rules are the decision logic of a Tenor contract.

DSL Syntax

hcl
rule <RuleId> {
  stratum: <nat>
  when:    <PredicateExpression>
  produce: verdict <VerdictName> { payload: <BaseType> = <value> }
}

Fields

FieldRequiredDescription
stratumYesNon-negative integer. Determines what the rule can reference.
whenYesA predicate expression that must be true for the rule to fire. See Symbols & Operators for the complete predicate grammar.
produceYesOne or more verdicts emitted when the rule fires. Each verdict has a name and a typed payload.

Stratification

Rules are assigned to explicit, numbered strata. A rule in stratum N may only reference outputs from rules in strata strictly below N. No same-stratum references are permitted, regardless of whether the intra-stratum dependency graph would be acyclic.

Formally: for any rule r that references the output of rule r', stratum(r') < stratum(r). Equality is not permitted.

  • Stratum 0 -- can reference only facts (ground layer)
  • Stratum 1 -- can reference facts and verdicts from stratum 0
  • Stratum N -- can reference facts and verdicts from strata 0 through N-1

Multiple rules may occupy the same stratum provided they have no dependencies on each other.

Re-expression theorem: Any acyclic same-stratum dependency graph can be mechanically transformed into strict stratification with no loss of expressive power. If you accidentally put dependent rules at the same stratum, the fix is always to move the dependent rule to a higher stratum.

Verdict Definition

Each verdict type has a name and a payload schema:

hcl
produce: verdict payment_ok { payload: Bool = true }
produce: verdict risk_score { payload: Int(min: 0, max: 100) = 85 }
produce: verdict threshold_amount { payload: Money(currency: "USD") = 10000.00 }

Verdicts form a finite tagged set. The payload type can be any BaseType. The payload value is the result produced when the rule fires.

Verdict Uniqueness (S8)

Each VerdictType name is produced by exactly one rule in the entire contract. If two or more rules (at any stratum) produce the same VerdictType name, the contract is statically rejected. This is structural check S8, enforced during Pass 5.

hcl
// WRONG -- two rules produce the same verdict name
rule check_a {
  stratum: 0
  when:    amount > 100
  produce: verdict approved { payload: Bool = true }
}

rule check_b {
  stratum: 0
  when:    status = "premium"
  produce: verdict approved { payload: Bool = true }  // ERROR: S8 violation
}

The correct pattern is distinct verdict names composed at a higher stratum:

hcl
rule check_a {
  stratum: 0
  when:    amount > 100
  produce: verdict amount_ok { payload: Bool = true }
}

rule check_b {
  stratum: 0
  when:    status = "premium"
  produce: verdict premium_status { payload: Bool = true }
}

rule final_approval {
  stratum: 1
  when:    verdict_present(amount_ok) and verdict_present(premium_status)
  produce: verdict approved { payload: Bool = true }
}

Predicate Expression Syntax

The when clause uses the predicate expression grammar. See Symbols & Operators for the complete operator reference including Unicode and ASCII forms.

Fact comparisons:

hcl
when: cargo_weight_kg <= 50000
when: risk_level = "high"
when: order_total > compliance_threshold

Logical connectives (and/, or/, not/¬):

hcl
when: delivery_confirmed = true and dispute_filed = false
when: verdict_present(amount_ok) or verdict_present(override_approved)
when: not verdict_present(blocked)

Verdict presence:

hcl
when: verdict_present(payment_verified)

Arithmetic (literal multiplication only -- no variable x variable in predicates):

hcl
when: base_amount + surcharge > threshold
when: unit_price * 100 <= budget_limit

Bounded quantification over List-typed facts (forall/, exists/):

hcl
when: forall item in line_items . item.amount > 0
when: exists doc in documents . doc.status = "verified"

Variable x variable multiplication is permitted only in Rule bodies (produce clauses), not in predicate expressions. This is the only location in the language where it is available:

hcl
rule compute_total_tax {
  stratum: 0
  when:    true
  produce: verdict total_tax {
    payload: Int(min: 0, max: 10000000) = item_count * unit_tax_amount
  }
}

The product range is computed from the operand fact type ranges and must be contained within the declared verdict payload type.

Rule Evaluation

eval_strata(contract, facts) =
  verdicts = empty
  for n = 0 to max_stratum:
    stratum_rules = { r in contract.rules | r.stratum = n }
    new_verdicts  = union { eval_rule(r, facts, verdicts) | r in stratum_rules }
    verdicts      = verdicts union new_verdicts
  return resolve(verdicts)

This fold terminates because the number of strata is finite and each stratum is fully evaluated before the next. Resolution is the identity on sets -- it returns the accumulated verdict set. Each VerdictType appears at most once in a conforming contract (guaranteed by S8).

Multi-Stratum Example: Healthcare Prior Authorization

From the healthcare domain, four strata build a complete authorization decision:

Stratum 0 -- ground checks against facts:

hcl
rule all_records_complete {
  stratum: 0
  when:    forall record in medical_records . record.is_complete = true
  produce: verdict records_complete { payload: Bool = true }
}

rule diagnosis_covered {
  stratum: 0
  when:    policy_criteria.diagnosis_covered = true
  produce: verdict diagnosis_is_covered { payload: Bool = true }
}

rule treatment_in_formulary {
  stratum: 0
  when:    policy_criteria.treatment_in_formulary = true
  produce: verdict treatment_is_formulary { payload: Bool = true }
}

rule provider_network_check {
  stratum: 0
  when:    policy_criteria.provider_in_network = true
  produce: verdict provider_in_network { payload: Bool = true }
}

rule step_therapy_check {
  stratum: 0
  when:    policy_criteria.step_therapy_complete = true
  produce: verdict step_therapy_done { payload: Bool = true }
}

rule records_relevant_check {
  stratum: 0
  when:    forall record in medical_records . record.is_relevant = true
  produce: verdict records_relevant { payload: Bool = true }
}

rule clinical_criteria_check {
  stratum: 0
  when:    clinical_review_score >= 70
  produce: verdict clinical_criteria_passed { payload: Bool = true }
}

Stratum 1 -- compose ground verdicts into eligibility determinations:

hcl
rule documentation_sufficient {
  stratum: 1
  when:    verdict_present(records_complete) and verdict_present(records_relevant)
  produce: verdict documentation_ok { payload: Bool = true }
}

rule policy_criteria_satisfied {
  stratum: 1
  when:    verdict_present(diagnosis_is_covered)
       and verdict_present(treatment_is_formulary)
       and verdict_present(provider_in_network)
       and verdict_present(step_therapy_done)
  produce: verdict policy_satisfied { payload: Bool = true }
}

Stratum 2 -- final authorization decision:

hcl
rule can_approve {
  stratum: 2
  when:    verdict_present(documentation_ok)
       and verdict_present(policy_satisfied)
       and verdict_present(clinical_criteria_passed)
  produce: verdict authorization_approved { payload: Bool = true }
}

rule should_deny {
  stratum: 2
  when:    verdict_present(documentation_ok)
       and not verdict_present(clinical_criteria_passed)
  produce: verdict authorization_denied { payload: Bool = true }
}

Stratum 3 -- appeal outcome:

hcl
rule can_overturn_denial {
  stratum: 3
  when:    verdict_present(appeal_meritorious)
        or verdict_present(new_evidence_available)
  produce: verdict overturn_recommended { payload: Bool = true }
}

This contract has 13 rules across 4 strata. Each verdict traces to a single source rule. The stratification makes the data flow visible: facts feed stratum 0, stratum 0 feeds stratum 1, and so on. No cycles, no ambiguity.

Payload Types

Verdict payloads can be any BaseType:

hcl
// Bool payload
produce: verdict payment_ok { payload: Bool = true }

// Int payload
produce: verdict risk_score { payload: Int(min: 0, max: 100) = 85 }

// Money payload (from variable x variable multiplication)
produce: verdict tax_amount {
  payload: Money(currency: "USD") = item_total * tax_rate
}

// Enum payload
produce: verdict risk_category {
  payload: Enum(values: [low, medium, high]) = "high"
}

Full Working Example

A complete contract with multi-stratum rules:

hcl
// --- Personas ---
persona buyer
persona seller
persona escrow_agent
persona compliance_officer

// --- Entity ---
entity EscrowAccount {
  states:  [held, released, refunded, disputed]
  initial: held
  transitions: [
    (held, released),
    (held, refunded),
    (held, disputed),
    (disputed, released),
    (disputed, refunded)
  ]
}

// --- Facts ---
fact escrow_amount {
  type:   Money(currency: "USD")
  source: "escrow_service.balance"
}

fact compliance_threshold {
  type:    Money(currency: "USD")
  source:  "compliance_db.threshold"
  default: 10000.00
}

fact delivery_confirmed {
  type:    Bool
  source:  "tracking_service.delivered"
  default: false
}

fact buyer_requested_refund {
  type:    Bool
  source:  "buyer_portal.refund_requested"
  default: false
}

// --- Rules (stratum 0: ground checks) ---
rule threshold_check {
  stratum: 0
  when:    escrow_amount <= compliance_threshold
  produce: verdict within_threshold { payload: Bool = true }
}

rule delivery_check {
  stratum: 0
  when:    delivery_confirmed = true
  produce: verdict delivery_ok { payload: Bool = true }
}

// --- Rules (stratum 1: compose ground verdicts) ---
rule auto_release_eligible {
  stratum: 1
  when:    verdict_present(within_threshold) and verdict_present(delivery_ok)
  produce: verdict can_auto_release { payload: Bool = true }
}

rule compliance_release_eligible {
  stratum: 1
  when:    not verdict_present(within_threshold) and verdict_present(delivery_ok)
  produce: verdict needs_compliance_release { payload: Bool = true }
}

// --- Operations ---
operation release_escrow {
  personas: [escrow_agent]
  require:  verdict_present(can_auto_release)
  effects:  [EscrowAccount: held -> released]
  outcomes: [released]
}

operation release_with_compliance {
  personas: [compliance_officer]
  require:  verdict_present(needs_compliance_release)
  effects:  [EscrowAccount: held -> released]
  outcomes: [released]
}

operation refund_buyer {
  personas: [escrow_agent]
  require:  buyer_requested_refund = true
  effects:  [EscrowAccount: held -> refunded]
  outcomes: [refunded]
}

Constraints

  • Stratum must be a non-negative integer. Violations: "stratum must be a non-negative integer; got <value>".
  • All verdict references must resolve to produced verdicts. Violations: "unresolved VerdictType reference: '<id>'".
  • Stratum ordering: a rule at stratum N referencing a verdict from stratum N is a Pass 5 error: "stratum violation: rule at stratum N references verdict from stratum N".
  • S8 -- Verdict uniqueness: no two rules may produce the same VerdictType name. Duplicate verdicts are elaboration errors.
  • Variable x variable multiplication in Rule bodies requires Int-typed operands, and the product range must be contained in the declared payload type. Overflow: "type error: product range Int(...) is not contained in declared verdict payload type Int(...)".

Common Mistakes

Same-stratum verdict references. A stratum 0 rule cannot reference verdicts from another stratum 0 rule. Move the dependent rule to stratum 1.

Duplicate verdict names. Two rules producing the same verdict name fails S8. Use distinct names and compose at a higher stratum.

Variable x variable multiplication in predicates. fact_a * fact_b is forbidden in when clauses. Only fact * literal is allowed in predicates. Variable x variable is allowed only in produce clauses.

Assuming verdict re-evaluation in flows. Verdicts are frozen at flow initiation. If facts change mid-flow, verdicts are NOT recomputed. The flow uses its snapshot.

Non-deterministic logic. Rules cannot reference timestamps, random values, or ambient state. All time-varying values must enter as facts.

How Rules Connect to Other Constructs

  • Facts are the sole input to stratum 0 rules.
  • Operations reference verdicts in their preconditions via verdict_present(name).
  • Flows use the frozen verdict set (snapshotted at initiation) for BranchStep conditions and OperationStep preconditions.
  • Personas do NOT affect rule evaluation. Verdict production is persona-independent.
  • Entities are NOT referenced by rules. Entity state is not a predicate term.

Static Analysis Properties

  • S5 (Verdict and outcome space) enumerates all possible verdict types the contract can produce.
  • S7 (Evaluation complexity bounds) computes bounds for each predicate expression tree.
  • S8 (Verdict uniqueness) guarantees each verdict has a single producing rule -- enabling unambiguous provenance.
  • Stratification ensures evaluation order is well-defined and terminates in finite steps.