Specification · v1

Validation and diagnostics

A source is accepted or rejected as a whole. A rejection is one diagnostic, the first problem found in the fixed order below. A conforming implementation reports the same kind and code at the same position as ulang-lite for every input. Message wording is not part of the contract.

1. Diagnostic shape

interface Diagnostic {
  kind: 'syntax' | 'limit' | 'unsupported' | 'semantic';
  code: string;      // stable, machine-readable (tables below)
  message: string;   // human-readable; wording may change
  offset: number;    // UTF-16 offset into the source
  line: number;      // 1-based
  column: number;    // 1-based, counted in Unicode code points
}

The command line prints a diagnostic as file:line:column: kind[code]: message.

KindMeaning
syntaxthe text is not v1 contract syntax
limita resource bound was exceeded: size, nesting, tokens, literal range, condition depth
unsupportedthe construct parses, but is outside the executable subset
semanticthe contract is well formed but inconsistent: types, names, clause ids, actions, mode

2. Order of checks

  1. Bounds scan (linear, before parsing): source size; NOT; arithmetic characters; AND/OR count; nesting and word count. See Limits.
  2. Syntax: the parser stops at the first error.
  3. Executable subset, contract by contract, in source pre-order (a construct is reported before anything nested inside it): field types; then constants (named block, source tag, type, literal range); trigger forms; ENSURE forms and their conditions; FORBID forms and their conditions.
  4. Structure: no contract has an empty GUARANTEE block.
  5. Each contract in source order: duplicate contract name, then mode, CONTEXT, entity, fields, constants, constant/field collisions, TRIGGER, then every clause in source order (guarantees first, then prohibitions): its source tag, id and action, then whether it has a condition, then the condition's types; and finally whether every action is governed.

Because the order is fixed, one source produces one diagnostic in every conforming implementation. Fix it and the next problem, if any, surfaces.

3. Codes

KindCodeRaised when
syntaxsyntaxthe text does not match the grammar, including an unterminated string
limitsource-sizethe source is empty or larger than maxSourceBytes
limitnesting-or-tokens{ ( [ nest deeper than maxNesting, or there are more than maxTokens words
limitboolean-tokensmore than maxBooleanOperators occurrences of AND/OR
limitinteger-rangean integer literal outside ±(2^53-1)
limitfloat-rangea float literal that is not finite or exceeds ±(2^53-1)
limitcondition-deptha condition tree deeper than maxConditionDepth
unsupportednot-operatorNOT appears outside strings, comments and source tags
unsupportedarithmetic+ * / ^, or - not followed by a digit
unsupportedtype-unsupporteda field or constant type that is not int, float, bool or string
unsupportedconstants-namedCONSTANTS name { … }
unsupportedconstant-forma constant without [SOURCE:id]
unsupportedconstant-typea non-primitive constant type, if it reached validation
unsupportedconstant-invalida soft (=), duplicate or badly named constant, or a value of the wrong kind or out of range
unsupportedtrigger-forma trigger without parentheses, or with a WHEN guard
unsupportedtrigger-paramsa trigger with parameters
unsupportedensure-forman ENSURE without (), with arguments, without WHEN, or without a source tag
unsupportedforbid-forma FORBID with arguments, without WHEN, or without a source tag
unsupportedcall-or-arithmetica call or a subtraction in a condition or constant
unsupportedcondition-forma condition that is a bare operand or an assignment
unsupportedliteral-comparisonconstant op literal
unsupportedunknown-fieldEntity.field where the field is not declared
unsupportedunknown-namea name that is not a declared constant
unsupportedforeign-referencea path on another entity, or a parenthesised value
unsupportedkind-mismatchcomparing different kinds, or ordering strings or booleans
unsupportednonnumeric-comparisona string or boolean comparison not shaped Entity.field op literal
semanticempty-guaranteeGUARANTEE { }
semanticenforcement-modethe mode is not enforced (when required)
semanticcontext-requiredno CONTEXT
semanticentitynot exactly one named ENTITIES block, or not the expected entity
semanticfieldsduplicate fields, or fields that differ from the expected schema
semanticconstant-collidesa constant named like a field
semantictrigger-requiredno TRIGGER
semantictrigger-entrya duplicate, guarded, bare or unexpected trigger action
semantictrigger-incompleteTRIGGER does not list exactly the expected actions
semanticclause-sourcean executable clause without a source tag
semanticclause-idan invalid or duplicate clause id, [ SOURCE: with a space, or an action not in TRIGGER
semanticclause-predicatea clause without WHEN
semanticungoverned-actiona TRIGGER action with no clause
semanticduplicate-contracttwo contracts with the same name in one source

expected entity, expected fields and expected actions refer to validation options, which an application uses to pin a contract to its facts schema.

4. Examples

Each of these is checked by the released engine when this site is built.

Sections out of orderdiag-section-order
CONTRACT Claim {
  ENFORCEMENT_MODE: enforced
  TRIGGER {
    grant()
  }
  CONTEXT {
    ENTITIES Request {
      count: int
      region: string
      ready: bool
      ratio: float
    }
    CONSTANTS {
      zero: int == 0 [SOURCE:POLICY]
      cap: int == 25 [SOURCE:POLICY]
    }
  }
  GUARANTEE {
    ENSURE grant() WHEN Request.ready == true [SOURCE:must_be_ready]
  }
  PROHIBITION {
    FORBID grant WHEN Request.count > cap [SOURCE:over_cap]
  }
}

rejected syntax[syntax] at 6:3 expected '}' closing the contract (sections must appear in the order ENFORCEMENT_MODE, CONTEXT, TRIGGER, GUARANTEE, PROHIBITION, OBSERVABILITY, each at most once), found 'CONTEXT'

A soft constant (=) is refuseddiag-soft-constant
CONTRACT Claim {
  ENFORCEMENT_MODE: enforced
  CONTEXT {
    ENTITIES Request {
      count: int
      region: string
      ready: bool
      ratio: float
    }
    CONSTANTS {
      zero: int == 0 [SOURCE:POLICY]
      cap: int = 25 [SOURCE:POLICY]
    }
  }
  TRIGGER {
    grant()
  }
  GUARANTEE {
    ENSURE grant() WHEN Request.ready == true [SOURCE:must_be_ready]
  }
  PROHIBITION {
    FORBID grant WHEN Request.count > cap [SOURCE:over_cap]
  }
}

rejected unsupported[constant-invalid] at 12:7 duplicate/invalid/non-hard constant

Strings cannot be ordereddiag-string-order
CONTRACT Claim {
  ENFORCEMENT_MODE: enforced
  CONTEXT {
    ENTITIES Request {
      count: int
      region: string
      ready: bool
      ratio: float
    }
    CONSTANTS {
      zero: int == 0 [SOURCE:POLICY]
      cap: int == 25 [SOURCE:POLICY]
    }
  }
  TRIGGER {
    grant()
  }
  GUARANTEE {
    ENSURE grant() WHEN Request.ready == true [SOURCE:must_be_ready]
  }
  PROHIBITION {
    FORBID grant WHEN Request.region > "eu" [SOURCE:region]
  }
}

rejected unsupported[kind-mismatch] at 22:23 comparison type mismatch or ordering of nonnumbers

Every triggered action needs a clausediag-ungoverned
CONTRACT Claim {
  ENFORCEMENT_MODE: enforced
  CONTEXT {
    ENTITIES Request {
      count: int
      region: string
      ready: bool
      ratio: float
    }
    CONSTANTS {
      zero: int == 0 [SOURCE:POLICY]
      cap: int == 25 [SOURCE:POLICY]
    }
  }
  TRIGGER {
    grant()
    revoke()
  }
  GUARANTEE {
    ENSURE grant() WHEN Request.ready == true [SOURCE:must_be_ready]
  }
  PROHIBITION {
    FORBID grant WHEN Request.count > cap [SOURCE:over_cap]
  }
}

rejected semantic[ungoverned-action] at 15:3 every trigger action requires an executable clause

A single = is an assignment, not a comparisondiag-assignment
CONTRACT Claim {
  ENFORCEMENT_MODE: enforced
  CONTEXT {
    ENTITIES Request {
      count: int
      region: string
      ready: bool
      ratio: float
    }
    CONSTANTS {
      zero: int == 0 [SOURCE:POLICY]
      cap: int == 25 [SOURCE:POLICY]
    }
  }
  TRIGGER {
    grant()
  }
  GUARANTEE {
    ENSURE grant() WHEN Request.ready = true [SOURCE:must_be_ready]
  }
  PROHIBITION {
    FORBID grant WHEN Request.count > cap [SOURCE:over_cap]
  }
}

rejected unsupported[condition-form] at 19:25 only typed comparisons and AND/OR are supported

More, with machine-readable results, on Test vectors.