Language

Language tour

µLang v1 is small enough to see whole. This page walks through it using the deploy contract from the tutorial. The specification has the precise rules.

Contracts

A source file holds one or more contracts. A contract has up to six sections, always in this order, each at most once:

SectionHoldsRequired
ENFORCEMENT_MODEenforcedyes
CONTEXTthe facts entity, then constantsyes
TRIGGERthe actions the contract governsyes
GUARANTEEENSURE clauses: what must holdno
PROHIBITIONFORBID clauses: what must not holdno
OBSERVABILITYLOG lines for callersno
excerpt of a checked contracttut-final
CONTRACT ProductionDeploy {
  ENFORCEMENT_MODE: enforced

Names of contracts, fields, actions and constants are [A-Za-z_][A-Za-z0-9_]*. Comments start with // or # and run to the end of the line. ENFORCEMENT_MODE: enforced is required. advisory and audit parse, but ulang-lite refuses them by default, and the mode never changes a decision.

Facts

excerpt of a checked contracttut-final
    ENTITIES DeployRequest {
      environment: string
      tests_passed: bool
      approvals: int
      author_approved_own_change: bool
      error_rate_pct: float
      change_freeze: bool
    }

A contract has exactly one entity, and its fields are typed:

TypeIn the facts JSONCompared
intan integer, at most 2^53−1 in magnitudeexactly
floata finite numberas IEEE-754 doubles
booltrue or falseby value
stringa stringexactly, by code point

The application sends facts as {"DeployRequest": {…}}, with every field present and no others. ulang-lite checks that before deciding.

Actions

excerpt of a checked contracttut-final
  TRIGGER {
    deploy()
    rollback()
  }

Each governed action is listed once, as name(), and must have at least one rule. Asking a contract about an action it does not list is a mistake in the caller, not a DENY.

Rules

excerpt of a checked contracttut-final
  GUARANTEE {
    ENSURE deploy() WHEN DeployRequest.tests_passed == true [SOURCE:tests_green]
    ENSURE deploy() WHEN DeployRequest.approvals >= min_approvals [SOURCE:two_approvals]
  }
excerpt of a checked contracttut-final
  PROHIBITION {
    FORBID deploy WHEN DeployRequest.change_freeze == true AND DeployRequest.environment == "production" [SOURCE:change_freeze]
    FORBID deploy WHEN DeployRequest.author_approved_own_change == true [SOURCE:no_self_approval]
    FORBID deploy WHEN DeployRequest.error_rate_pct > max_error_rate_pct [SOURCE:error_budget]
    FORBID rollback WHEN DeployRequest.environment != "production" AND DeployRequest.environment != "staging" [SOURCE:known_environments]
  }

A decision checks the action's FORBID clauses first, then its ENSURE clauses, each in source order. It names the first clause that denies. If none does, the action is permitted as ProductionDeploy.deploy.permit. See Evaluation.

Conditions

A condition is comparisons joined by AND and OR. AND binds tighter than OR; parentheses group.

ComparingOperatorsExample
numbers: int, float, or one of each== != < <= > >=DeployRequest.error_rate_pct > max_error_rate_pct
a string field with a string== !=DeployRequest.environment == "production"
a bool field with true or false== !=DeployRequest.tests_passed == true

Operands are facts (Entity.field), constants and literals. Two numeric facts can be compared with each other; strings and booleans are always written field op literal.

<, <=, > and >= are exact. == and != tolerate last-bit floating-point noise, so a fact computed as 0.1 + 0.2 equals the literal 0.3. Use <= and >= for thresholds.

Not in the language, on purpose: NOT, arithmetic, function calls, lists and string escapes. Patterns shows how to express the usual needs without them.

Constants

excerpt of a checked contracttut-final
    CONSTANTS {
      min_approvals: int == 2 [SOURCE:ENG-HANDBOOK-7.1]
      max_error_rate_pct: float == 1.5 [SOURCE:SRE-SLO-3]
    }

Constants are typed int (0 to 2^53−1) or float, written with ==, and carry a source. They come after the entity in CONTEXT. Compare them with facts: a constant compared with a literal is refused, because it decides nothing.

Sources and clause ids

Every clause and every constant carries [SOURCE:id]. On a clause, the id is the clause's public name: a decision reads ProductionDeploy.change_freeze. Clause ids are [A-Za-z0-9_]{1,80} and unique within a contract. On a constant, the source is free text that says where the value comes from.

Observability

excerpt of a checked contracttut-final
  OBSERVABILITY {
    LOG "deploy decision" ON decision
  }

LOG lines tell callers what to record. They are part of the contract's digest and play no part in decisions.

Canonical form and digest

Every valid contract has one canonical text: a fixed layout, no comments, normalised numbers. Its SHA-256 is the contract's digest. Layout and comments never change the digest; anything that could change a decision does. Record the digest with each decision and you can later prove which contract made it (see Audit trail).

example projectreal output
$ ./ulang-lite format policy/deploy.ul
CONTRACT ProductionDeploy {
  ENFORCEMENT_MODE: enforced
  CONTEXT {
    ENTITIES DeployRequest {
      environment: string
      tests_passed: bool
      approvals: int
      author_approved_own_change: bool
      error_rate_pct: float
      change_freeze: bool
    }
    CONSTANTS {
      min_approvals: int == 2 [SOURCE:ENG-HANDBOOK-7.1]
      max_error_rate_pct: float == 1.5 [SOURCE:SRE-SLO-3]
    }
  }
  TRIGGER {
    deploy()
    rollback()
  }
  GUARANTEE {
    ENSURE deploy() WHEN DeployRequest.tests_passed == true [SOURCE:tests_green]
    ENSURE deploy() WHEN DeployRequest.approvals >= min_approvals [SOURCE:two_approvals]
  }
  PROHIBITION {
    FORBID deploy WHEN DeployRequest.change_freeze == true AND DeployRequest.environment == "production" [SOURCE:change_freeze]
    FORBID deploy WHEN DeployRequest.author_approved_own_change == true [SOURCE:no_self_approval]
    FORBID deploy WHEN DeployRequest.error_rate_pct > max_error_rate_pct [SOURCE:error_budget]
    FORBID rollback WHEN DeployRequest.environment != "production" AND DeployRequest.environment != "staging" [SOURCE:known_environments]
  }
  OBSERVABILITY {
    LOG "deploy decision" ON decision
  }
}