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.
| Kind | Meaning |
|---|---|
syntax | the text is not v1 contract syntax |
limit | a resource bound was exceeded: size, nesting, tokens, literal range, condition depth |
unsupported | the construct parses, but is outside the executable subset |
semantic | the contract is well formed but inconsistent: types, names, clause ids, actions, mode |
2. Order of checks
- Bounds scan (linear, before parsing): source size;
NOT; arithmetic characters;AND/ORcount; nesting and word count. See Limits. - Syntax: the parser stops at the first error.
- 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;
ENSUREforms and their conditions;FORBIDforms and their conditions. - Structure: no contract has an empty
GUARANTEEblock. - 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
| Kind | Code | Raised when |
|---|---|---|
syntax | syntax | the text does not match the grammar, including an unterminated string |
limit | source-size | the source is empty or larger than maxSourceBytes |
limit | nesting-or-tokens | { ( [ nest deeper than maxNesting, or there are more than maxTokens words |
limit | boolean-tokens | more than maxBooleanOperators occurrences of AND/OR |
limit | integer-range | an integer literal outside ±(2^53-1) |
limit | float-range | a float literal that is not finite or exceeds ±(2^53-1) |
limit | condition-depth | a condition tree deeper than maxConditionDepth |
unsupported | not-operator | NOT appears outside strings, comments and source tags |
unsupported | arithmetic | + * / ^, or - not followed by a digit |
unsupported | type-unsupported | a field or constant type that is not int, float, bool or string |
unsupported | constants-named | CONSTANTS name { … } |
unsupported | constant-form | a constant without [SOURCE:id] |
unsupported | constant-type | a non-primitive constant type, if it reached validation |
unsupported | constant-invalid | a soft (=), duplicate or badly named constant, or a value of the wrong kind or out of range |
unsupported | trigger-form | a trigger without parentheses, or with a WHEN guard |
unsupported | trigger-params | a trigger with parameters |
unsupported | ensure-form | an ENSURE without (), with arguments, without WHEN, or without a source tag |
unsupported | forbid-form | a FORBID with arguments, without WHEN, or without a source tag |
unsupported | call-or-arithmetic | a call or a subtraction in a condition or constant |
unsupported | condition-form | a condition that is a bare operand or an assignment |
unsupported | literal-comparison | constant op literal |
unsupported | unknown-field | Entity.field where the field is not declared |
unsupported | unknown-name | a name that is not a declared constant |
unsupported | foreign-reference | a path on another entity, or a parenthesised value |
unsupported | kind-mismatch | comparing different kinds, or ordering strings or booleans |
unsupported | nonnumeric-comparison | a string or boolean comparison not shaped Entity.field op literal |
semantic | empty-guarantee | GUARANTEE { } |
semantic | enforcement-mode | the mode is not enforced (when required) |
semantic | context-required | no CONTEXT |
semantic | entity | not exactly one named ENTITIES block, or not the expected entity |
semantic | fields | duplicate fields, or fields that differ from the expected schema |
semantic | constant-collides | a constant named like a field |
semantic | trigger-required | no TRIGGER |
semantic | trigger-entry | a duplicate, guarded, bare or unexpected trigger action |
semantic | trigger-incomplete | TRIGGER does not list exactly the expected actions |
semantic | clause-source | an executable clause without a source tag |
semantic | clause-id | an invalid or duplicate clause id, [ SOURCE: with a space, or an action not in TRIGGER |
semantic | clause-predicate | a clause without WHEN |
semantic | ungoverned-action | a TRIGGER action with no clause |
semantic | duplicate-contract | two 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.
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'
=) is refuseddiag-soft-constantCONTRACT 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
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
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
= is an assignment, not a comparisondiag-assignmentCONTRACT 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.