FS FormScript

The specification

Every normative statement of the FormScript Language Specification 1.0.0, with the text that binds an implementation.

Chapter 1 — Purpose and scope

Spec-1.1

Within a chapter, statement numbers are assigned densely and in order of appearance: the first statement of chapter N is Spec-N.1, the second is Spec-N.2, and so on. A statement's number is therefore a stable handle for citation, not a mnemonic for its content, and a number is never reused after a statement is withdrawn. A statement's number does not change when it moves within its chapter; moving a statement between chapters renumbers it.

Spec-1.2

An implementation that fails any MUST is non-conforming. An implementation that does nothing more than parse documents and report errors is conformant at Level 0 (§15.1).

Chapter 2 — Design goals and the static-error commitment

Spec-2.1

A conforming implementation MUST implement all five stages. A Stage-3 (parse + semantic analysis) implementation with no renderer is Level 0 conformant.

Spec-2.2

Every error MUST carry a stable machine-readable code from the catalogue (§14), a human-readable message, and — when the error originates in source text — a location of line:column. Error code values are stable within a major version; message text is not stable and MUST NOT be matched on.

Spec-2.3G3

An implementation MUST detect and report, before any evaluation, all errors in the following list:

  1. Syntax errors
  2. Undefined identifier references
  3. Duplicate identifier declarations in the same scope
  4. Static type errors, where the type system is strong enough to prove them
  5. Dependency cycles among derived values
  6. Constraints that are statically contradictory (e.g. min = 10, max = 5)
  7. Unknown properties or block names
  8. Over-budget expressions (§6.6)
  9. Unbounded iteration (§6.6)
  10. Missing required properties (§14.3, E-PROP-07)
  11. Output paths that do not resolve to a declared field (§14.3, E-OUT-01)
  12. Comparison of a value with an absence literal using == or != (§5.5, E-TYPE-07)
  13. Presentation-plane values used in data positions (Spec-12.9, E-TYPE-08)
  14. Relations with no acyclic causality assignment, and malformed relations (§8.8, E-REL-01, E-REL-03); overridable without value (E-CONFLICT-04)
  15. Fields that provably have no satisfiable value under their own type and constraints (§10.6, analysis tier A1, E-SAT-01)
  16. An unknown or unsupported profile (§5.6, E-PROF-01)

This list is normative and exhaustive for the "must be caught at compile time" claim. A runtime that defers any of these to evaluation time is non-conforming.

Spec-2.4

References below the $ namespace ($env, $user, $form) are resolved by the runtime and MUST NOT be checked statically. Their contents are not known until the transaction runs, so a reference to one is never a compile-time error (Spec-6.23).

Spec-2.5

A document MAY produce more than one error. An implementation MUST report them in stage order — lexical, then syntactic, then semantic, then static-type, then constraint — and MUST report every error of the earliest stage that produced any. The stage of a diagnostic is the class of its code in the catalogue of §14, and the stage order is the order of that catalogue's sections: lexical (§14.1), syntactic (§14.2), semantic (§14.3), static-type (§14.4), constraint (§14.5). Within one stage, errors MUST be reported in document order (Spec-9.9). An error of a later stage MUST NOT be reported while an earlier stage produced at least one error; it is suppressed, not discarded, and MUST be reported once the earlier stage's errors are resolved. A warning (§14.7) is reported whenever it is produced: it neither suppresses an error nor is suppressed by one.

Spec-2.6

The diagnostic list for a document is complete for the earliest non-empty stage: it contains every error of that stage and no error of any later stage. No error of the earliest non-empty stage is ever suppressed. A document with a lexical error and a semantic error reports the lexical error and suppresses the semantic error; it does not report only the lexical error, and it does not report the semantic error alongside it.

Spec-2.7

A conformance-suite row that names a single expected code (§18.2) MUST produce that code as the first error it reports. A row MAY produce further errors of the same stage after it — the §18.2 rows are fragments, and the row that declares options and optionsFrom together also names a source the fragment never declares, so that reference is reported too — but the named code is the row's primary diagnostic (Spec-18.2).

Chapter 3 — Lexical structure

Spec-3.1

Source text MUST be UTF-8, with or without a byte-order mark. Line terminators are LF (U+000A) and CRLF (U+000D U+000A). A lone CR is an error.

Spec-3.2

A conforming implementation MUST NOT attach meaning to leading or trailing whitespace, and MUST NOT use indentation to delimit blocks.

Spec-3.3

A /* appearing inside a line comment is not a block comment. A # or // inside a block comment is not a line comment.

Spec-3.4

Identifiers are case-sensitive. Email and email are distinct identifiers and MAY both exist.

Spec-3.5

Identifiers MUST NOT begin with two underscores. The prefix __ is reserved for implementation-internal use.

Spec-3.6

Identifiers MUST NOT contain a hyphen.

Spec-3.7

Case sensitivity is retained despite the cost, because form definitions routinely mix conventions when merged, and a document that silently coalesces email and Email is worse than one that errors.

Spec-3.8

A hard keyword MUST NOT be used as the name of a field, group, repeat, section, choice, state, or page block, nor as an import alias.

Spec-3.9

A soft keyword MAY be used as a declaration name or an attribute name. A parser MUST resolve the soft keyword by context per §4.14, and MUST prefer the identifier interpretation where both are possible.

Spec-3.10

Single-quoted strings are NOT supported. One string syntax eliminates an entire class of quote-nesting bugs in the error-message and i18n grammars.

Spec-3.11

A date or datetime literal with no timezone offset MUST be interpreted as UTC. Local-time interpretation is a known source of off-by-one bugs across platforms and is prohibited.

Spec-3.12

A duration literal MUST be interpreted in UTC-relative calendar terms. 30d is 30 days; 6mo is 6 calendar months; 2y is 2 calendar years. A duration MUST NOT be interpreted as a fixed number of seconds, because month and year lengths vary.

Spec-3.13

Where a duration unit has a one-letter and a two-letter form (m minutes, mo months), the lexer MUST use the longest match. 6mo is six months; 6m is six minutes.

Spec-3.14

A lexer MUST reject a character that is not the start of a terminal in this list, a keyword, an identifier, or a literal.

Spec-3.15

The multi-character tokens ??, ->, ==, !=, <=, >=, //, /*, */ MUST be lexed as single tokens. The lexer MUST use longest-match. The two-character tokens && and || are not tokens and MUST be rejected (Spec-6.52).

Chapter 4 — Syntactic structure

Spec-4.1

A document MUST contain exactly one form definition. A document MAY contain any number of include, import, and namespace declarations before the form definition.

Spec-4.2

An include directive names a file to be merged into the current document at the point of inclusion. include "path" as alias prefixes every top-level name declared by the included file with alias.. include "path" imports every name without a prefix.

Spec-4.3

An import directive names an extension namespace — one declared in the same document or one registered with the runtime (Spec-16.8) — and MUST be followed by as <alias>. The alias is a prefix under which the namespace's declarations are visible.

Spec-4.4

A namespace block declares the contents of an extension namespace. A namespace block MAY appear only at the top level of a document (not nested inside a form block). Its contents are visible only to documents that import it.

Spec-4.5

A relation declares mutually dependent input fields together with one equation per member (§8.8). Inside a relation block the grammar is the one above, not the general block grammar: the first statement MUST be a hold clause, and every other statement is an equation whose left side is a field path. A relation is a declaration, not an entity (Spec-7.1).

Spec-4.6

A separator is optional. A trailing separator before } is permitted and is parsed as belonging to the preceding statement. Two consecutive separators are a syntax error (E-SYN-03).

Spec-4.7

Attributes MAY appear in any order within a block, with exactly one exception: type and options are typed attributes and are constrained to their own value grammar (§4.4). Every other attribute's value is an expression.

Spec-4.8

A page MUST be named with a string_literal, because a page's name is a route identifier and is referenced from navigation and from state declarations. Every other block kind MUST be named with an identifier. A string literal used as the name of any other block kind is E-SYN-01.

Spec-4.9

field, group, repeat, section, choice, state, and page are block-kind keywords. They are always recognised by position — a block is always kind name { } — and so they do not conflict with a declaration of the same name. Per Spec-3.9 they MAY name a declaration.

Spec-4.10

type and options MUST NOT be used as ordinary attribute names. type always introduces a type_ref; options always introduces an options_def. Both are soft keywords resolved by the attribute-name position (§4.14).

Spec-4.11

The match subject is evaluated once per transaction, before any arm is considered. Arms are tested in document order. The first arm whose pattern matches is executed; its body MUST be a single statement or a block. If no pattern matches, the default arm is executed if present, and otherwise the match statement has no effect.

Spec-4.12

A bare literal pattern matches when the subject is equal to that literal, using the equality rules of §6.2. A default arm matches when no other arm does, and MUST be the last arm if present.

Spec-4.13

A bound is always a range. string(2..10) is a lower and an upper limit; string(..10) is upper-limit only; number(0..) is lower-limit only. A bound with neither limit (..) is unbounded and triggers the cost rule of §6.6 when the type is used as an iterated collection.

Spec-4.14

An array type without an of clause is an array of any: its element type is unconstrained. Because an unbounded array is an unbounded-iteration risk (§6.6), an untyped array is only legal where it is never iterated.

Spec-4.15

The upper limit of an array type bounds the number of elements. The bound on an element type bounds the element. These are independent:

text
array(0..200) of string(1..200)
         ^^^^^^^^^^^   ^^^^^^^^^^^^^
         200 elements  each 1..200 characters
Spec-4.16

An object type MAY declare its members. An object type without members is opaque: expressions may read any property of it and receive null for an absent one.

Spec-4.17

A duration literal MUST use either the short form (30d, 6mo, 2y) or the ISO 8601 form (P30D, P1Y2M3D, PT2H30M). The short form and the ISO form MAY be mixed within a document, but a single literal MUST NOT mix them. In the ISO form the date components MUST appear in the order Y M W D and the time components in the order H M S, separated by T; each part MUST carry at least one component, so P alone and PT alone are not durations.

Spec-4.18

The escape sequences of a string literal are \" \\ \/ \n \t \r \b \f and \u{XXXX}, where XXXX is one to six hexadecimal digits. A backslash followed by anything else is E-LEX-02 (invalid escape sequence). A string literal is terminated by the first unescaped double quote; there is no way to embed a raw double quote.

Spec-4.19

Precedence, from loosest to tightest: ?: → ?? → or → and → equality → relational (including in, is between, and the absence tests) → additive → multiplicative → unary → postfix. Every production in the chain is left-associative except the ternary, which is right-associative.

Spec-4.20the `and` of `between`

between_expr is a production of its own. A parser that has read additive "is" "between" MUST consume the following additive "and" additive as part of the between form; a further and is the ordinary conjunction. Two tokens of lookahead are sufficient: the token after is distinguishes between from an absence test, and the token after that distinguishes the two. a and b is between 1 and 2 therefore parses as a and (b is between 1 and 2), and x is between 1 and 2 and y parses as (x is between 1 and 2) and y.

Spec-4.21

in is a hard keyword. A reference to a field is written identifier for a field in scope, or a.b.c for a path. The * selector (a.*) denotes "every member of the object or collection a" and is valid only as the right-hand side of a path.

Spec-4.22

FormScript has no lambdas. A function that takes a collection and a function argument does not exist. Comprehensions are the only higher-order construct. A map, filter, or reduce that appears in a builtin list with a function argument is not a FormScript builtin (§6.4).

Spec-4.23

A comprehension is distinguished from a call to a builtin of the same name by its first argument: if a comprehension head keyword is followed by ( identifier in, the construct is a comprehension, and identifier in is a binding, not a membership test. Because the binding is not an expression, the two forms are unambiguous with two tokens of lookahead:

text
count(x in items, x)   →  comprehension (binds x over items)
count(items)           →  builtin function
Spec-4.24

options constrains a field's value to one of the option keys. The declared type of the field MUST be string or enum.

Spec-4.25

An option written key = "label" has a static label. An option written key -> expression has a computed label. The two forms MAY be mixed in one options_def.

Spec-4.26

optionsFrom = <source name> binds a field's choice set to a source declared in the same document (§11.2). The source's declared type MUST be array whose element type is object { value: string, label: string }; a source of any other declared type is E-TYPE-02.

Spec-4.27

A field that declares optionsFrom MUST declare type as string or enum. A field MUST NOT declare both options and optionsFrom; doing so is E-CONFLICT-06. A field that declares neither has an empty choice set and accepts any value of its declared type.

Spec-4.28

When the field's declared type is enum, every value the source yields MUST be one of the enum's declared option keys; a value that is not is E-TYPE-04 at runtime. A source that yields no values leaves the field with an empty choice set; it is not an error.

Spec-4.29

A warn prefix declares the constraint's severity as warning rather than error (§10.5). A constraint with no warn prefix has severity error.

Spec-4.30

path_ref is used only in validation constraints and refers to a field path. It is not an expression form; in expression position the same syntax is built from selector (§4.6).

Spec-4.31

An action or source block is a named declaration, not an entity (§7). Its body is an ordinary block whose attributes are the properties listed in §8.7.

Spec-4.32

A source block MAY declare a type attribute describing the shape of the fetched value. A source without a declared type yields untyped data, and any expression reading a member of it has type any and cannot be statically verified against a field's declared type. A source whose result is iterated MUST declare a bounded type (§6.6).

Spec-4.33

The output block declares a projection of the form's data: which fields are submitted, and under what type. Every output_path MUST resolve to a declared field. A field not listed in output is not submitted.

Spec-4.34

The declared type of an output entry MUST match the declared type of the field it names (§5.2). An output entry whose path names a field inside a section or page MUST use the field's own path, because section and page do not introduce a data level (Spec-7.14).

Spec-4.35absence on the wire

The output document distinguishes the two absences. An applicable field whose content is empty is emitted as an explicit JSON null ("asked, not answered"). An inapplicable field is omitted: its key is absent ("not asked"). A consumer can therefore tell the two apart without any side channel. A field whose content is unparsed or failed blocks submission (§10.4) and never reaches an output document.

Spec-4.36numbers on the wire

A number or an integer value MUST be serialised as a JSON string carrying the canonical decimal form (Spec-5.32), and MUST NOT pass through binary floating point. The string MUST be the canonical decimal form and nothing else. There is no second wire form: a JSON number cannot carry a decimal128 value without loss, so a runtime that could emit one would emit a different output document from a runtime that could not, and Spec-15.6 would have nothing to say.

Spec-4.37

A text entry's value is either a single string (the default locale's text) or a map of locale tags to strings. In the map form, the reserved key "default" denotes the default-locale text. A key MUST NOT appear twice within one text block; a duplicate is E-SYN-08.

Spec-4.38

A text block MAY appear at the top level of a form block or inside any entity block. Text blocks at different levels are merged into one catalogue; a duplicate key across levels is E-SYN-08.

Spec-4.39

Where associativity matters, the production order in §4.6 defines it.

Spec-4.40

Trailing commas are permitted in options_def, primary's array form, and source's params block.

Spec-4.41

value and default are mutually exclusive on the same field (§7.1). Declaring both is E-CONFLICT-01.

Spec-4.42

A field with neither value nor default is an input field (§7.1). A field with value is a derived field.

Spec-4.43

Where a soft keyword could begin either an attribute or a block, the parser MUST look at the token after the keyword to choose. A = means attribute; { or an identifier or a string literal means block. A conforming parser MUST NOT require the author to disambiguate by other means.

Spec-4.44

Inside a validate block, the constraint keywords required, accepted, min, max, minLength, maxLength, pattern, email, phone, url, unique, notIn, in, multipleOf, equal, notEqual, cross, custom, and the severity keyword warn are recognised as keywords. when is recognised as a guard only immediately after a complete constraint.

Chapter 5 — Types, coercion and the profile

Spec-5.1

integer is a subtype of number, and string is a subtype of display (Spec-12.9). No other type is a subtype of any other type, and display is not a subtype of string.

Spec-5.2

Every field's type is static and MUST be known at compile time. A runtime MUST reject a document that computes a type dynamically.

Spec-5.3

The distinguished error value (§6.1) has no static type. It is assignable to any type, propagates through every operator (§6.2), and gives any field that receives it the content failed (§5.2).

Spec-5.4

empty means: null, or a string of length 0, or an empty array, or an empty choice. A number equal to 0 and a boolean equal to false are NOT empty.

Spec-5.5

required is violated if and only if the field is applicable and its content is empty. A field holding 0 or false satisfies required; a form that treats a legitimate 0 as a missing answer is a defect. A boolean toggle that has never been assigned has content empty and reads null (Spec-5.8); a toggle that must read false when unticked MUST declare default = false.

Spec-5.6

unparsed content is retained, not discarded. The runtime MUST keep the raw input so the user does not lose typing on a validation failure. An unparsed field reads as null (unknown) to other expressions (Spec-5.8) and is itself a type finding (E-EVAL-05), so a typing error in one field never poisons its dependants with the error value.

Spec-5.7Touched

Touched becomes true on the first assignment by a person or by an action's input mapping. Defaults, value expressions, and relation equations never set it. It stays true until the form is reset, and it is retained with the field's content in the shadow store (§8.2). Touched exists to answer one question — may a violation be shown yet? (Spec-10.4) — and affects no data-plane result.

Spec-5.8Read rule

What an expression sees when it reads a field is a function of two axes:

Applicability Content An expression reads
inapplicable any inapplicable
applicable empty, scalar-typed, input field null (unknown)
applicable empty, produced by a derived field's value expression the absence the expression produced (null or inapplicable)
applicable empty, array/repeat-typed []
applicable value the value
applicable unparsed null (unknown)
applicable failed the error value
Spec-5.9

Validity is derived, not stored (§5.2.1). Two consequences follow. First, "required but empty" and "untouched" are different facts, and a renderer can ask each independently. Second, a derived value is never "invalid" in the sense of a stored flag; it is failed (a content fact) and judged invalid (a derived fact).

Spec-5.10

A constraint binds when its guard — absent guard meaning the literal true — evaluates to exactly true (Spec-8.2). A constraint whose guard evaluates to false does not bind and is not evaluated. A constraint whose guard is unknown or inapplicable is deferred: it does not bind, it is not evaluated, and it is not reported as either passing or failing.

Spec-5.11

The derivation is:

text
validity(F) =
  not_applicable  if F is inapplicable (§8.2)
  invalid         if any binding constraint of F fails
  pending         if F is applicable, no binding constraint fails, and at
                  least one of F's constraints is deferred
  valid           otherwise
Spec-5.12

A deferred constraint MUST NOT be reported as a violation and MUST NOT be reported as satisfied. A runtime MUST report deferred constraints in the transaction's diagnostic output under the code E-VAL-05.

Spec-5.13

A pending field does not block submission (Spec-10.13). A deferred constraint becomes binding when its guard becomes exactly true, at which point the field's validity is recomputed in the same transaction (Spec-9.6). A runtime MUST NOT treat pending as valid in any data-plane result: $form.valid is true only if no field is invalid (§10.4).

Spec-5.14

Touched does not affect validity. It affects only whether a violation may be shown (Spec-10.4). Validity is never stored and is never pending for a field that is not applicable.

Spec-5.15derived content

A derived field's content is set from the result of its value expression:

  • the error value → content failed (§5.6);
  • null → content empty, and the field reads as null;
  • inapplicable → content empty, and the field reads as inapplicable;
  • any other value → content value.

The distinction between the two absences is preserved by the read rule of Spec-5.8, which distinguishes a derived field's empty content from an input field's. This is what lets a derived boolean say "not yet known" instead of guessing false.

Spec-5.16

Coercion is permitted in exactly four cases and forbidden everywhere else:

From To Rule
integer number Widening; always exact
string number Only if the string fully matches -?\d+(\.\d+)?
number integer Only if exactly representable and finite
string date/datetime Only if it matches the declared format exactly
Spec-5.17

All other conversions are compile-time errors. E-TYPE-01 is the code when an operator's operands or a builtin's argument types are not acceptable; E-TYPE-02 is the code when a field's value or default expression does not match the field's declared type; and E-TYPE-03 is the code when a builtin's argument has no permitted conversion to the parameter's type. In particular there is no "5" + 1 == "51"; concatenating a number to a string requires explicit toString().

Spec-5.18

A coercion that fails produces the error value, not an exception. A runtime MUST NOT throw on user input.

Spec-5.19

Implementations MUST implement at minimum: type of every literal, type of every field, arithmetic operand types, comparison operand compatibility, and the arity and argument types of every built-in function.

Spec-5.20

Implementations MAY implement full bidirectional inference over comprehensions. Implementations that do not MUST defer those errors to evaluation time, and MUST report them as runtime errors, not silently.

Spec-5.21

A value read from a source without a declared type has type any. A value expression of type any assigned to a field of a declared type MUST be checked at runtime (E-TYPE-02).

Spec-5.22

null and inapplicable are distinct first-class values. isUnknown(inapplicable) is false. Neither equals any value under ==, including itself (Spec-6.8).

Spec-5.23propagation and dominance

A strict operator or builtin (Spec-6.8) applied to an absent operand yields an absent result. When operands of both kinds are present, inapplicable dominates null. The reason is monotonicity: learning an unknown can change a result, but nothing the user types can make an inapplicable question apply, so the more final fact wins. The error value dominates both (Spec-6.9).

Spec-5.24testing for absence

Absence is tested only with the predicates isUnknown, isInapplicable, isAbsent, and isKnown (§6.4). Because == is strict, x == null is itself null whenever x is, so it can never answer the question it appears to ask. A comparison of any operand with an absence literal by == or != is therefore a compile-time error (E-TYPE-07) whose message suggests the predicate. The literals remain legal as values (if (c) null else x).

Spec-5.25

Every type admits both absences and the error value. The type system does not track absence statically (there is no T?); the analysis of §10.6 reasons about it.

Spec-5.26

A document MAY declare profile = "<id>" in its form header (§4.1). A document without one is read under the default profile of this specification, core-2026.1. A runtime MUST support every profile it claims, MUST publish the profiles it supports, and MUST reject a document naming any other with E-PROF-01.

Spec-5.27G1/G2 restated

For a fixed document, profile, sequence of user actions, environment values (Spec-5.41), and transaction timestamps, every conforming runtime produces identical data-plane results: field content, touched flags, applicability, validity and its codes, relation and override state, and output documents. Text shown to a person is the presentation plane (Spec-12.9) and is outside this guarantee.

Spec-5.28

Under core-2026.1, integer is a signed 64-bit integer. Every integer operation is exact; a result outside the range yields the error value (E-EVAL-06). number is IEEE 754-2008 decimal128: 34 significant decimal digits, and an adjusted exponent — the exponent of the leading significant digit — in the range −6143…+6144 inclusive. Addition, subtraction, multiplication, and division are correctly rounded under round-half-even to 34 digits; a result whose adjusted exponent falls outside that range yields the error value (E-EVAL-06). Thus 0.1 + 0.2 == 0.3 is true, and binary floating point appears nowhere in the data plane.

Spec-5.29subnormals are not produced

The range of Spec-5.28 is the normal range of decimal128. IEEE 754-2008 also permits subnormal results whose adjusted exponent reaches down to −6176; this profile does not produce them. A result that would require a subnormal is an underflow and yields the error value (E-EVAL-06), exactly as a result beyond the exponent range does. An implementation MUST NOT silently round such a result to zero.

Spec-5.30

Arithmetic with a number operand yields number. / always yields number (7 / 2 is 3.5; use floor(a / b) for integer division). % is the truncated remainder, a − b × trunc(a / b), computed exactly. == and the ordering operators compare numeric value, so 2.0 == 2.

A numeric literal denotes an exact decimal value, and its kind fixes its type and its domain. An int_literal (§4.5) has type integer and MUST denote a value in that type's domain — a signed 64-bit integer (Spec-5.28). A dec_literal has type number and MUST denote a value exactly representable in that type's domain (Spec-5.28). A literal outside the domain of its own kind is a lexical error (E-LEX-06); a literal whose kind's domain admits it is never a lexical error, whatever its value.

Spec-5.31

round(n [, places [, mode]]): places defaults to 0; mode is "half_up" (the default; ties away from zero) or "half_even". round(2.5) is 3, round(-2.5) is -3, round(2.5, 0, "half_even") is 2, and round(1.005, 2) is 1.01. ceil and floor are exact. avg(arr) is sum(arr) / count(arr) in number arithmetic; avg([]) is the error value (division by zero).

Spec-5.32canonical decimal form

The canonical decimal form of a number or an integer is the single text by which that number is written, and is defined as follows. Let d be the number's significant digits with trailing zeros removed, and let a be its adjusted exponent — the exponent of its leading digit.

  1. Zero is written 0. The sign of zero is not distinguished.
  2. If −6 ≤ a ≤ 20 the form is positional: an optional leading -, then the digits of d with the zeros that d dropped restored on the right, a . placed so that the units digit falls last, and no trailing zeros in the fractional part.
  3. Otherwise the form is scientific: an optional leading -, then the first digit of d, then a . and the remaining digits of d when d has more than one digit, then E, then + or -, then the digits of a with no leading zeros.

The form carries no + sign on a non-negative number, no leading zero before the point, and no trailing point. Examples: 0, 1, -1, 0.1, 123.45, 1E+21, 1.5E+3, 1E-7.

The zeros that d dropped are restored by rule 2 and absent from rule 3, so a value has exactly one text: 100 is written 100 and not 1E+2, and 5.0 is written 5. An integer is always written without a fractional part, because its normalised exponent is never negative.

Spec-5.33where the canonical form is used

A number or an integer MUST be written in canonical decimal form wherever it is serialised: in an output document (Spec-4.36), in a state snapshot (Spec-8.26), and as the result of the toString builtin (§6.4). The toString builtin applied to a number or an integer yields the canonical decimal form and nothing else. Two conforming runtimes MUST produce byte-identical text for the same number.

Spec-5.34

A string is a sequence of Unicode scalar values (never a lone surrogate). Every string that enters the data plane — literals, user input, source results, $ values — is normalised to NFC on entry. Equality is scalar-by-scalar equality of NFC forms, so "é" (U+00E9) equals "e\u{0301}".

Spec-5.35

Length is the number of Unicode scalar values of the NFC form. len, minLength, maxLength, substring, indexOf, and the bounds of string(a..b) all count scalar values. graphemeLen(s) counts extended grapheme clusters (UAX #29) for authors who want user-perceived length. The default is the scalar count because grapheme segmentation depends on larger, more frequently revised tables.

Spec-5.36

Ordering of strings (<, sort, minOf, maxOf) is by scalar value of the NFC form. Locale-aware collation is a presentation concern and is not available to data-plane expressions.

Spec-5.37

lower and upper use the Unicode default full case mappings with no locale tailoring (the Turkish dotless i rules are not applied). trim removes characters with the Unicode White_Space property at both ends. slugify(s) is: NFKD, remove combining marks, lower, replace each maximal run of characters outside [a-z0-9] with a single -, strip leading and trailing -.

Spec-5.38

The calendar is proleptic Gregorian. Every datetime is an instant normalised to UTC; a date is a calendar date. In dateAdd(d, n, unit), unit is one of "s", "m", "h", "d", "w", "mo", "y" (the duration-literal suffixes of §3.6); months and years are added first and the day is clamped to the last valid day, so dateAdd(2026-01-31, 1, "mo") is 2026-02-28. dateDiff(a, b, unit) counts whole units from a to b, truncated toward zero. A 29 February anniversary is reached on 1 March in non-leap years — a convention, chosen and stated so that two runtimes cannot differ.

Spec-5.39

core-2026.1 pins Unicode 16.0 (normalisation, case mapping, grapheme and regular-expression property data) for the data plane, and CLDR 46 for the presentation plane (Spec-12.9). A runtime whose platform libraries implement other versions MUST ship the pinned tables or declare that it cannot support the profile.

Spec-5.40

A new profile MAY be published in a MINOR release (§16.1). A profile is immutable once published; behaviour that must change ships as a new profile identifier.

Spec-5.41

$env, $user, and other $ values are typed inputs of the determinism contract, as is the transaction timestamp $now. They are the only way a data-plane result can depend on anything outside the document and the user's actions. A conformance harness supplies them explicitly.

Spec-5.42

A profile pins the static-analysis thresholds as well as the primitives. MAX_EXPR_COST and MAX_DOC_COST (§6.6) are properties of the profile, not of the runtime. A conforming runtime MUST apply the profile's values and MUST NOT raise them: the set of documents a runtime accepts is part of its observable behaviour, and two runtimes that accept different documents are not interchangeable even when they agree on every document they both accept. A runtime that wishes to accept documents beyond a profile's thresholds MUST publish a new profile (Spec-5.40).

Spec-5.43

The profile also pins the diagnostic order of §2.5: the order in which a document's errors are reported is part of the profile, so that two runtimes produce the same diagnostic list for the same document, in the same order, with the same codes.

Spec-5.44profile identifiers are immutable

A published profile identifier MUST NOT be reused for different content. Before a profile's first publication its identifier and its content may be re-cut freely; after publication, any change to its content — including its cost thresholds and its diagnostic order — MUST ship under a new identifier (Spec-5.40).

Spec-5.45the profile of this specification

The conformance suite of §18 is bound to the profile core-2026.1 as published with this specification: MAX_EXPR_COST = 100,000, MAX_DOC_COST = 1,000,000, Unicode 16.0, CLDR 46, decimal128 with round-half-even, and the diagnostic order of §2.5. A row of §18 whose expected result depends on the profile states so.

Chapter 6 — Expressions

Spec-6.1G4

Expressions are total and pure.

  • Total — every expression terminates and yields a value. There are no loops, no recursion, and no unbounded iteration.
  • Pure — expressions have no side effects. Evaluating an expression cannot modify form state, perform I/O, or read a clock other than now (§6.4).
Spec-6.2

An operation that cannot produce a meaningful result — division by zero, a regular expression that fails to compile at runtime, an out-of-range conversion — yields the error value rather than raising. The error value propagates through every operator.

Spec-6.3

When the error value reaches a field's value, that field's content becomes failed (§5.2) and the runtime MUST report E-EVAL-03 (or the matching E-EVAL-* code) against that field. When the error value reaches the top level of a transaction without materialising into a field — an applicability (when) expression, for example — the transaction MUST report the error and MUST NOT commit (E-EVAL-07).

Spec-6.4

The ONLY permitted non-determinism is now (§6.6), which reads the evaluation timestamp fixed for the current transaction (§9.3). now MUST be stable within a transaction.

Spec-6.5

== on array and object is structural and order-sensitive for arrays: it is the conjunction (Spec-6.12) of the length test and the element-wise ==. A comparison containing an absent element is absent unless some pair is definitely unequal.

Spec-6.6

+ on a string and a number is a compile-time error, not a runtime error. This is the most common bug in loosely-typed form languages and FormScript refuses to permit it.

Spec-6.7G1

and and or are symmetric: a and b and b and a always produce the same value (Spec-6.12), so evaluation order is unobservable. An implementation MAY evaluate either operand first, and MAY skip an operand that cannot change the result; it MUST NOT let that choice change any result. The static cost counts both operands (§6.6). x != 0 and 10 / x > 1 is safe at x = 0 because F absorbs the error from the right operand.

Spec-6.8absence propagation

An operator or builtin is strict unless this specification says otherwise. For a strict operation:

  1. If any operand is E, the result is E (but see absorption in Spec-6.12 and Spec-6.10).
  2. Otherwise, if any operand is absent, the result is absent: I if any operand is I, else N (Spec-5.23).
  3. Otherwise the operation is evaluated normally.

Arithmetic, comparison, ==/!=, in, and every builtin not listed in Spec-6.28 are strict. and, or, !, ? :, ??, and the absence-aware builtins are not.

Spec-6.9

The error value takes precedence over absence in every strict operation (E + N is E). In and/or it is absorbed by F/T respectively and otherwise dominates absence (Spec-6.12).

Spec-6.10G1

c ? a : b — and its identical statement form if (c) a else b — is: a if c is T; b if c is F; the absence c itself if c is N or I (the result is not a branch); E if c is E. Only the selected branch is evaluated, and MUST NOT be evaluated otherwise, even when the other branch would produce E.

Spec-6.11`??`

a ?? b is a, unless a is absent (N or I), an empty string, or an empty array, in which case it is b; if a is E, the result is E. ?? is the one operator that turns absence into a value silently, which is its purpose; use it deliberately. 0 ?? "d" is 0 and false ?? "d" is false.

Spec-6.12three-valued logic

and, or, and ! are Kleene strong three-valued logic, extended so that the two absences are the "undetermined" value and E is absorbed by the dominant value. Stated as rules:

  • a and b is F if either operand is F; otherwise E if either is E; otherwise I if either is I; otherwise N if either is N; otherwise T.
  • a or b is T if either operand is T; otherwise E if either is E; otherwise I if either is I; otherwise N if either is N; otherwise F.
  • !a is F for T, T for F, and a itself for N, I, and E.

As tables (row is the left operand, column the right):

and T F N I E
T T F N I E
F F F F F F
N N F N I E
I I F I I E
E E F E E E
or T F N I E
T T T T T T
F T F N I E
N T N N I E
I T I I I E
E T E E E E

Both operators are commutative and associative; T is the identity of and and F of or; De Morgan's laws hold. The law of the excluded middle holds for known operands only: x or !x is N when x is N. That is the correct law for an unknown, and the reason an absent condition can no longer satisfy both a rule and its negation.

Spec-6.13`is between`

x is between a and b is exactly x >= a and x <= b under Spec-6.12. With x unknown the result is N; with x = 10, a = 20, b = null it is F, because one comparison is definitely false.

Spec-6.14`in`

x in [e1, …, en] is (x == e1) or … or (x == en) under Spec-6.12, and F for an empty list. If the right-hand side is itself absent, the result is that absence.

Spec-6.15the absence tests

These are the operator forms of the predicates of Spec-6.28 and have exactly the same semantics:

  • x is known is true iff x is a value — neither absent nor the error value. It is false for null and for inapplicable, and it is the error value for the error value.
  • x is unknown is true iff x is null; it is false for inapplicable and for a value.
  • x is absent is true iff x is null or inapplicable; it is false for a value.
  • x is inapplicable is true iff x is inapplicable; it is false for null and for a value.
  • x is empty is true iff x is absent, or is a string of length 0, or is an empty array — the semantics of isEmpty (Spec-6.28).

Each propagates the error value, exactly like the builtin it mirrors.

Spec-6.16three different questions

"Known", "absent" and "empty" are three different questions, and the language answers all three:

Question Operator null inapplicable "" [] 0 false
Do I have a value? x is known no no yes yes yes yes
Is my value missing? x is absent yes yes no no no no
Is my value nothing? x is empty yes yes yes yes no no

0 and false are values, never empty (Spec-5.4). An empty string is known and empty but not absent: the question was asked and answered with nothing.

Spec-6.17`??` is empty-aware; the absence tests are not

?? treats an empty string and an empty array as grounds for falling through to its right operand (Spec-6.11). is known does not: an empty string is a known value. The two are answering different questions, and an author who wants "fall through if empty" writes ??; an author who wants "has this been answered at all" writes is known.

Spec-6.18masked errors

When the error value is absorbed by a dominant operand in and or or — F and E is F, T or E is T — the value of the expression is unchanged, and the runtime MUST NOT materialise the absorbed error into a field as failed content, because it was not the result of the expression. The runtime MUST, however, record the absorption and report it as warning W-EVAL-01 against the location of the expression.

Spec-6.19

Absorption affects diagnostics only. It MUST NOT affect the value of any expression, and therefore MUST NOT affect the determinism contract (Spec-9.1) or the cross-platform conformance test (Spec-15.6).

Spec-6.20

Reference resolution walks the lexical scope chain: current block, then enclosing blocks, then form root, then the environment ($env).

Spec-6.21

An unqualified reference that resolves to nothing is a compile-time error E-REF-01. FormScript does not support dynamic name lookup; it is exactly the kind of magic that G3 forbids.

Spec-6.22

$now and all $-prefixed builtins are read-only.

Spec-6.23

A $-path that does not resolve yields null (§6.3). A runtime MUST report every unresolved $-path it evaluated as the warning W-REF-01, naming the path, so that a typo is visible rather than silent. The warning MUST NOT change the data-plane value, which stays absent. The runtime MUST provide $env.locale; MAY provide $env.api, $env.user.*, and other environment-specific values.

Spec-6.24

self denotes the subject value of the context in which the expression appears:

Context self is
inside a validate block the current value of the field being validated
inside a field's value expression the field's own current value
inside a field's default expression the field's own current value (usually null at first evaluation)
inside a repeat element's expression the value of the enclosing repeat element
anywhere else the error value
Spec-6.25

self is a hard keyword and MUST NOT be used as a declaration name.

Spec-6.26

Within a repeat, self refers to the current element of that repeat. Inside a comprehension over that repeat, the bound identifier (or $this) refers to the current element of the comprehension's own iteration.

Spec-6.27

Builtins MUST be side-effect free and total. A builtin that would otherwise fail (e.g. substring with a negative index) MUST return the error value or null as documented for that builtin, and MUST NOT raise.

Spec-6.28builtins and absence

Builtins are strict (Spec-6.8) except the following, which are absence-aware: isEmpty, isUnknown, isInapplicable, isAbsent, isKnown, typeOf, coalesce, ifNull, iif, orDefault, filledFields, isActive, shadow, isOverridden, count (one-argument form), and the presentation builtins, which format an absent argument as the empty string. isEmpty(x) is true for an absent value, "", and []. isUnknown(x) is true only for null; isInapplicable(x) only for inapplicable; isAbsent(x) for either; isKnown(x) is true for a value that is neither absent nor the error value. The predicates and isEmpty propagate the error value (isKnown(E) is E, not false), so a failure is never mistaken for a missing answer.

Spec-6.29

Implementations MUST support every builtin listed here to reach Level 2 conformance (§15.1). Additional builtins MAY be added; a document using a non-standard builtin MUST be rejected by a conforming implementation unless it declares an extension (§16.2).

Spec-6.30no lambdas

No builtin takes a function argument. map, filter, reduce, count, sum, any, and all are available in comprehension form only, except for count(arr) and sum(arr), which also have a one-argument builtin form (Spec-4.23).

Spec-6.31deprecated builtins

ifNull(a, b) and iif(c, a, b) are deprecated: ifNull is exactly a ?? b (Spec-6.11) and iif is exactly c ? a : b (Spec-6.10). A runtime MUST implement them, MUST NOT remove them within a major version, and MUST emit warning W-BUILTIN-01 when a document uses them. A runtime MAY implement coalesce as the variadic form of ?? and orDefault as the ?? that also catches the error value; these two are recommended over ifNull and iif and MUST be used in the specification's own examples.

Spec-6.32

pmt(rate, periods, principal) returns the level payment for a fully amortising loan. With r = rate, n = periods, and P = principal:

text
r == 0  →  P / n
r != 0  →  P * r / (1 - (1 + r)^(-n))
Spec-6.33

The power in pmt is defined for integer exponents only, by left-to-right binary square-and-multiply in number arithmetic with each product correctly rounded (Spec-5.28); x^(-n) is 1 / x^n. Fixing the sequence of roundings is what lets two runtimes agree on every digit of a payment.

Spec-6.34

age(d) returns the number of whole calendar years between the date d and the current transaction date, in UTC. A d in the future yields a negative value. A 29 February birth date attains its anniversary on 1 March in non-leap years (Spec-5.38).

Spec-6.35

now() returns the transaction timestamp and is identical to $now. today() returns the UTC date of the transaction timestamp.

Spec-6.36

t(key, args…) resolves a text key against the current locale (§12.2) and formats it with the supplied arguments. It is the only function that produces user-facing text from a catalogue key. It returns a value of type display (Spec-12.9) and is, with formatDate and formatMoney, one of the three presentation builtins.

Spec-6.37

filledFields() returns an array of the paths of all active fields whose content is not empty (§5.2), in document order.

Spec-6.38

isActive(path) returns whether the field at path is currently active (§8.2). shadow(path) returns the retained value of a field that is currently inactive, or null if it has none (§8.2).

Spec-6.39

The matches(s, regex) builtin and the pattern constraint use the same regular-expression engine, which MUST have linear-time matching semantics (no backtracking, no backreferences, no lookaround). A pattern that the engine cannot compile is a compile-time error (E-BOUND-04).

Spec-6.40regular-expression dialect

The shared engine implements the syntax of RE2 (linear-time, no backreferences, no lookaround) restricted to: literals, ., character classes, the Perl classes \d \w \s (ASCII only), \p{…} for Unicode General_Category and Script properties of the pinned Unicode version, anchors ^ $, alternation, groups, and the quantifiers * + ? {m,n} with their lazy forms. Matching runs over the NFC scalar values of the subject (Spec-5.34). matches(s, re) is true if re matches anywhere in s (anchor it to require a full match), so "abc" matches "^a" is true.

Spec-6.41

$this inside a comprehension refers to the bound element. The bound identifier is scoped to the comprehension body only. reduce takes three arguments: the bound identifier, the iterated collection, the initial value, and the body.

Spec-6.42

A collection-typed field (array, repeat) that is applicable and empty reads as the empty collection [] (Spec-5.8), so a comprehension over it yields the identity of its aggregate: sum is 0, count is 0, any is false, all is true, and map and filter are []. This is honest — an empty list really has zero elements — and is what keeps conditional logic free of null guards.

Spec-6.43

A comprehension over an inapplicable collection yields inapplicable, and over an unknown collection (for example a source that has not resolved) yields null. An excluded list is not an empty list, and an unloaded list is not an empty list; neither silently becomes [].

Spec-6.44comprehension bodies

Each element's body is evaluated under §6.2. map collects the bodies, absent ones included. filter and count(x in xs, p) keep or count an element only when p is exactly true. any and all fold the bodies with three-valued or and and. sum and reduce are strict: one absent body makes the result absent (Spec-6.8), so a total is unknown while any row it covers is unanswered. Write ifNull(item.balance, 0) to say "count an unanswered row as zero" explicitly.

Spec-6.45G8

Every expression has a statically computable cost, in abstract cost units:

Construct Cost
Literal 0
Field reference 1
Binary operator 1 + cost(left) + cost(right)
Function call 1 + Σ cost(args)
Array literal length
Comprehension cost(body) × maxIterations
Every other node 1 + Σ cost(children)
Spec-6.46

maxIterations for a comprehension is the declared maximum size of the iterated collection:

Iterated expression maxIterations
a repeat its max attribute (§8.6)
a field of type array(bound) the bound's upper limit (§4.4)
a source the upper limit of its declared type (§4.9)
a field of type object the bound of the member's type
anything else unbounded
Spec-6.47

A comprehension whose iterated collection is unbounded MUST be rejected at compile time with E-COST-01. A repeat used as an iterated collection MUST therefore declare max (§8.6), and an iterated array or source MUST declare a bounded type (§4.4, §4.9).

Spec-6.48

An expression whose static cost exceeds MAX_EXPR_COST MUST be rejected at compile time with E-COST-01. MAX_EXPR_COST is pinned by the profile (Spec-5.42); the default value of core-2026.1 is 100,000.

Spec-6.49

(document budget). The sum of the static costs of all distinct expressions in a document MUST NOT exceed MAX_DOC_COST. MAX_DOC_COST is pinned by the profile (Spec-5.42); the default value of core-2026.1 is 1,000,000. A document exceeding it MUST be rejected at compile time with E-COST-01.

Spec-6.50the multiplication cliff

Because the cost of a comprehension is cost(body) × maxIterations, the per-expression ceiling imposes an implicit ceiling on the size of a repeat: a repeat with max = 1000 and a body of cost 12 costs 12,000, while the same repeat with max = 2000 costs 24,000. Authors of large field-collection forms MUST be able to express a thousand-row repeat. A profile MUST set MAX_EXPR_COST high enough that the multiplication never becomes an accidental limit on the domain, and a spec revision that lowers it below the level at which a documented form shape becomes illegal is a MAJOR change.

Spec-6.51runtime budget

A runtime MUST enforce a runtime cost budget per evaluation transaction, defaulting to max(100,000, document_total_cost) units, halting with E-COST-02 if exceeded. The static limit exists to catch mistakes at authoring time; the runtime limit exists to bound adversarial documents that a checker was bypassed on.

Spec-6.52G4

A conforming implementation MUST reject the symbolic forms && and || with E-LEX-03. They are not part of the language; accepting them as aliases would make two spellings of every condition, and the second spelling is the one that drifts out of style guides.

Spec-6.53

The static cost of a relation (§8.8) is the maximum, over its members, of the cost of the equations retained when that member is held, because only one holder is active in any transaction. Applicability (when) expressions are ordinary expressions and are costed as such.

Chapter 7 — Forms, groups, repeats and choices

Spec-7.1

state and relation are not entities. A state is a named state declaration (§8.3) and a relation a declaration tying existing fields together (§8.8); neither contributes data of its own. text, output, action, source, validate, and namespace are likewise not entities; they are block forms (§4.2) that may appear within a document.

Spec-7.2

The block-kind keywords field, group, repeat, section, choice, state, and page name these seven block forms. Per Spec-3.9, they may also be used as declaration names of other entities.

Spec-7.3

A field MAY declare value (computed) or default (initial, user-editable), never both. A derived field MAY additionally declare overridable = true (§8.9).

Spec-7.4

A field with value = expr is a derived field. Its value is recomputed whenever any referenced value changes. User input to a derived field MUST be rejected by the runtime (§9.4) unless the field is overridable (§8.9).

Spec-7.5

A field with neither value nor default is an input field. Its state is owned by the user.

Spec-7.6

A field's type is required. A field without a type is E-PROP-07 (missing required property).

Spec-7.7

A group MAY be when-gated. A hidden group's fields are inapplicable: they read as inapplicable, contribute nothing to output, and none of their constraints run (§8.2). This is the correct behaviour: a form must not require a home address from a user who has no fixed home.

Spec-7.8

min and max bounds are normative. A runtime MUST enforce max and MUST NOT permit a user to add beyond it.

Spec-7.9

A repeat MUST declare max. A repeat without max is E-PROP-07. This is required because the repeat's max is the iteration bound used by the cost model (§6.6).

Spec-7.10

Every repeat MUST have a stable identity strategy. The default is index-based with splice-aware reindexing. Implementations MAY offer key = <path> for UUID-based identity, but MUST document the difference, since index identity and key identity differ observably when rows are reordered or deleted from the middle.

Spec-7.11

Deleting element i reindexes all subsequent elements in the same transaction. Computed values and validation state for affected elements MUST be recomputed within that transaction, not deferred to the next user action. Getting this wrong is a visible bug: delete row 1 of 3 and row 2's computed total silently becomes row 1's.

Spec-7.12

A choice renders as a radio group by default and as a select element when the enum has more than 6 members — a renderer MAY deviate, and MUST document the threshold.

Spec-7.13

A choice is semantically a field whose type is enum. The two forms exist because a choice is the one field type whose rendering is negotiated, and a separate entity lets a renderer and a validator treat it without a special case on type. A choice MUST declare type, and that type MUST be an enum type; the enum's members are the choice's options and their labels. A choice MUST NOT declare options or optionsFrom: the option set of a choice is the enum's member list, which is closed and written down in the document. A form that needs a remote option set declares a field with optionsFrom (§4.7), and one that must render as a radio group adds render = "radio".

Spec-7.14

section and page are presentation-only. They MUST NOT appear in output data and MUST NOT introduce a data level. Neither is a path prefix: a field inside section address is addressed by its own name, relative to the nearest data-bearing ancestor (Spec-6.20).

Spec-7.15

A page MAY declare when, making it skippable. A runtime MUST skip an unsatisfied page without blocking navigation past it, and MUST NOT count its fields toward the form's required-field set.

Chapter 8 — The property table

Spec-8.1G5

FormScript has exactly one visibility gate: when. Dynamic requiredness is required = <expr>, dynamic readonlyness is readonly = <expr>. All three are ordinary boolean-valued attributes evaluated by the same engine, with the same dependency tracking, in the same order.

Spec-8.2definite activation

An entity is applicable if and only if its own when evaluates to exactly true and every enclosing entity is applicable. In every other case — when is false, unknown, or inapplicable — it is inapplicable. (A when that yields the error value is not a quiet case: the transaction fails, Spec-6.3.) This is the definite-activation rule: a thing switches on only when it is definitely on. The same rule governs dynamic required and readonly (they bind only when exactly true) and constraint guards (Spec-10.9).

Spec-8.3

An inapplicable entity:

  • is not rendered
  • has no constraints evaluated; its validity is not_applicable (§10.4)
  • is omitted from output (Spec-4.35)
  • reads as inapplicable in every expression (Spec-5.8)
  • retains its content and Touched flag in a shadow store, for restoration if it becomes applicable again
Spec-8.4

The shadow store is reachable through the shadow(path) builtin (Spec-6.38) and through no other means; shadow returns the retained value if the retained content was a value, and null otherwise. The shadow-store rule is deliberate, and the read rule matters as much as the retention rule: preserving a hidden field's value means a user who toggles a condition back and forth does not lose their input, which matches how users understand "hide" (not "delete"). But a hidden field that still drives derived values makes those values depend on data the user can no longer see or change, which is worse than losing the input. Hence an inapplicable field reads as inapplicable, and its retained value is reachable only through an explicit shadow() call.

Spec-8.5

state declares a named snapshot of runtime state. It is the supported way to implement save-and-resume without a runtime-specific API. The body of a state block contains attributes whose values are expressions; a runtime MUST expose the resulting map under the state's name (e.g. draft).

Spec-8.6

A state block's members MAY reference the form's fields and the builtins of §6.4. A state block does not declare a data model and MUST NOT appear in output.

Spec-8.7scope

A state name is a declaration in the form root scope and is visible to every expression in the form, exactly as a field name is (Spec-6.20). Referencing a state name evaluates its members in the current transaction and yields an object. A state name MUST NOT shadow a field name; a collision is E-REF-02.

Spec-8.8

An effect attribute MAY appear only on a form block or as the sole statement of a match arm. An effect MUST take effect after the transaction commits.

Spec-8.9

A renderer that ignores layout hints is still conformant. A renderer that honours them MUST honour span consistently with columns.

Spec-8.10

min and max on a repeat are normative and are enforced by the runtime. max is required (Spec-7.9) because it is the iteration bound used by the cost model.

Spec-8.11

A relation names its members — input fields — and gives one equation per member, saying how to compute that member from the others and from any outside fields. Every member MUST be an input field (no value, not overridable), a field MUST belong to at most one relation, every member MUST have exactly one equation, each equation's type MUST match its member's type, and hold MUST name a member. Any violation is E-REL-03.

Spec-8.12held member

Each relation has a runtime state, its held member, initially the hold member. The held member is treated as an input: its content is whatever the person (or its default) put there. Every other member's content is computed from its equation. A transaction that assigns a member by input first sets the held member to it. Assigning two or more members of one relation in a single transaction is rejected with E-REL-02 and the transaction does not commit.

Spec-8.13causality, checked statically

For every member h of a relation, consider the dependency graph in which h's equation is removed (h is an input), the equations of the other members are present, and every other relation is fully cut (all of its equations removed, its members treated as inputs). That graph MUST be acyclic. In addition, the quotient graph obtained by contracting each relation to a single node MUST be acyclic. A violation is E-REL-01, reported with the member and the cycle. The check is polynomial — one graph per member — and is what makes the otherwise-cyclic declaration safe: in any one transaction exactly one holder is active, and the graph for that holder is a DAG.

Spec-8.14

Computed members are never Touched (Spec-5.7) and are always editable. Editing a computed member makes it the held member (Spec-8.12), which is how a person takes over from the computation.

Spec-8.15

Constraints apply to every member whether held or computed. A computed gross above its type bound is a violation like any other.

Spec-8.16

The held member is part of the state snapshot (Spec-9.1).

Spec-8.17

The language does not require the equations to be inverses of one another; if they are not, values will differ depending on the holder.

Spec-8.18inconsistency is reported

A runtime claiming Level 1 or above MUST report W-REL-01 when it can prove that two equations of one relation are not mutually consistent. "Can prove" means one of:

  1. the tier-A2 narrowing of §10.6 shows that, for some value in a member's declared domain, the two equations disagree; or
  2. both equations are affine in the two members — each of the form c₀ + c₁·a + c₂·b over the profile's decimal arithmetic — and substituting one into the other does not yield the identity on the members' declared domains.

A runtime that cannot decide MUST NOT report W-REL-01. The obligation is absolute for a runtime that implements tier A2 and conditional only on tier A2 being implemented at all, which a runtime declares under Spec-15.1.

Spec-8.19the warning is not an error

W-REL-01 does not prevent compilation and does not block submission. A relation whose equations are not inverses is legal: the form is then deterministic — the same input sequence always yields the same result — but not arithmetically coherent, and the warning is how the author learns that.

Spec-8.20

overridable = true on a field that declares value = expr gives it a source: computed or manual. The source is initially computed. An input assignment sets it to manual, stores the input as the content, and sets Touched. While manual, the result of expr is ignored. The effect reset = <path> (or the runtime API) returns the field to computed.

Spec-8.21

overridable takes a boolean literal. It without value is E-CONFLICT-04.

Spec-8.22

isOverridden(path) is true iff the field's source is manual. The source is part of the state snapshot, and the output carries the effective content.

Spec-8.23

An overridable field keeps its expression edges in the dependency graph even while manual. This is conservative: cycle detection (Spec-9.4) is unaffected by the source.

Spec-8.24

A runtime MUST be able to export a form's live state as a snapshot, and MUST be able to import a snapshot and restore the form to the state it describes. Save-and-resume is the reason state exists (Spec-8.5), and an export format that is not specified is not save-and-resume.

Spec-8.25

A snapshot MUST serialise, for every field: its path, its content, its Touched flag, its applicability, its override source (Spec-8.20), and — for every relation — its held member (Spec-8.16). The snapshot MUST also record the current page, the document's version (§16.2), and the profile identifier (§5.6) under which it was taken.

Spec-8.26

A snapshot MUST be encoded as a single JSON object whose serialisation is byte-stable across conforming runtimes, so that a snapshot taken on one platform restores identically on another. The encoding of a number is the canonical decimal form of Spec-4.36; the encoding of a date is ISO 8601; the encoding of a duration is the ISO 8601 form of Spec-4.17.

Spec-8.27

A runtime MUST reject a snapshot whose document version or profile it does not implement, with E-VER-01, rather than restoring it partially.

Spec-8.28

Restoring a snapshot is a transaction. The restored state MUST satisfy the same invariants as any other transaction's result, and the runtime MUST recompute validity in the restored transaction.

Spec-8.29

The required property is sugar, not a second mechanism:

  • required = true is exactly validate { required };
  • required = false is exactly validate { required when false } — a constraint whose guard is never satisfied, and which therefore never binds;
  • required = <expression> is exactly validate { required when <expression> }.

A runtime MUST treat the two spellings identically. The property exists because static requiredness is common and reads better on one line; it is not a different rule. In particular required = false never makes a field required, and it is the spelling to use when the requirement is carried by accepted instead (Spec-8.31).

Spec-8.30

A field MUST NOT declare both the required property and a required constraint. Doing so is E-CONFLICT-05: the two would say the same thing twice, and two rules that must agree are a place where they will not.

Spec-8.31

The accepted constraint (§10.2.1) is unaffected by this rule. A field MAY declare required = false together with accepted, and required = false with accepted is the normal spelling for an affirmation that is required to be true rather than merely answered.

Chapter 9 — The engine

Spec-9.1G1

For a fixed document D, profile P (§5.6), environment E (Spec-5.41), and a fixed sequence of user actions A, the resulting sequence of state snapshots is uniquely determined. A snapshot contains, for every field, its content, Touched flag, and applicability; the held member of every relation; the source of every overridable field; the status of every action; and the current page.

Spec-9.2

D, P, E, and A fully determine every data-plane value. There is no implementation-defined ordering, no dependence on hash-map iteration order, no dependence on wall-clock time except through $now, and no dependence on locale in the data plane (Spec-12.9).

Spec-9.3

The runtime maintains a directed graph whose nodes are:

  • Value nodes — the content of each input field
  • Applicability nodes — the result of each entity's when
  • Derived nodes — the result of each value, required, readonly, options, constraint-guard, and validate expression, and each relation equation
  • Collection nodes — the element set of each repeat

An edge X → Y exists iff expression Y reads node X. A read of a path P is a read of P's value node and also of the applicability nodes of P and of every entity enclosing P, because what an expression sees depends on both (Spec-5.8).

Spec-9.4

The graph MUST be acyclic, subject to the relation rule of Spec-8.13. A cycle is a compile-time error E-CYCLE-01, reported with the full cycle path:

text
E-CYCLE-01: computed cycle
  a.value → b.value → c.value → a.value
Spec-9.5static graph

The graph is static up to one finite choice: which member of each relation is held. Spec-8.13 requires every such choice to yield an acyclic graph, and that is checked at compile time. Applicability never adds or removes an edge: an inapplicable field changes the value a reader sees (inapplicable), not the set of things it depends on. Within a transaction the order is therefore fixed, and a single topological pass reaches the fixed point (Spec-9.7).

Spec-9.6

Evaluation happens in transactions. A transaction is created by any of: user input, action completion, repeat mutation, or explicit runtime API call.

Spec-9.7

Because the graph is static and acyclic (Spec-9.5), step 5 computes a fixed point in a single pass. A runtime MUST NOT iterate step 5 to convergence.

Spec-9.8

$now MUST return the same value for every evaluation within a transaction. Reading the clock per-expression would make created_at differ from updated_at for reasons the user cannot see.

Spec-9.9

Tie-breaking in step 4 MUST be by document order — the order in which nodes appear in the source file. Two implementations that break ties differently would violate G1.

Spec-9.10

A field with value = expr that is not overridable MUST ignore user input; relation members (§8.8) and overridable fields (§8.9) accept input by definition. A runtime MUST either reject the write and report a non-blocking notice, or (in a UI shell) present the field as read-only. Silently accepting input that is immediately overwritten is non-conforming.

Spec-9.11

Derived fields MUST NOT be used as the target of action input mapping. This prevents an action result from fighting the expression.

Spec-9.12

Changes to the shape of a repeat (add, remove, reorder) MUST recompute, in a single transaction: the repeat's element count, every derived value whose expression iterates that repeat, and the validation state of every element. This is the Spec-7.11 requirement stated as an engine rule.

Spec-9.13

A runtime MUST NOT evaluate a comprehension over a repeat whose element set is mid-mutation. Structural changes are atomic.

Chapter 10 — Validation

Spec-10.1

blur (default for most fields) — validate when the field loses focus. Minimises interruption; the user has finished with the field.

Spec-10.2

change — validate on every keystroke. Correct for fields where immediate feedback is cheap and useful: checkbox, radio, select, number with a live range indicator.

Spec-10.3

submit — validate only on submit. Correct for expensive or password fields.

Spec-10.4

A field MUST NOT display a validation error before the user has interacted with it, unless the value came from a default that the runtime can prove invalid. Showing "this field is required" on a pristine form is a defect.

Spec-10.5

Constraints accumulate. Multiple failing constraints on one field MUST all be reported, not just the first, so the user can fix everything in one pass.

Spec-10.6

custom = expr is the escape hatch. The expression MUST return true (valid) or a string (error message) or null (valid). Returning a string is what makes custom errors i18n-friendly (§12.3).

Spec-10.7

A custom constraint is evaluated in the field's own context, so self denotes the field's value (§6.3.1).

Spec-10.8

The required constraint on a boolean field is satisfied by false. It means "must have been assigned", not "must be true". A form whose consent must be affirmative MUST use the accepted constraint.

Spec-10.9

The accepted constraint binds when its guard is exactly true and fails unless the field's content is exactly the boolean true. An unassigned boolean, an empty boolean, and a false boolean all fail it. Unlike required, accepted is not satisfiable by un-ticking the control.

Spec-10.10

A failure of accepted is reported as E-VAL-04. The message MUST be localisable (Spec-12.7) and MUST distinguish "not agreed" from "not answered".

Spec-10.11

A required constraint on a boolean field MUST be reported at compile time as warning W-VAL-01, whose message suggests accepted. A runtime MUST NOT report W-VAL-01 for a boolean field that also declares accepted.

Spec-10.12

Cross-field constraints participate in the same transaction as the fields they read, so a form cannot be submitted in an inconsistent state between two field updates.

Spec-10.13

A form is submittable iff every active, required field is valid and every active constraint passes.

Spec-10.14strict forms

A form that declares strict = true is submittable only if every active field is valid. A pending field (Spec-5.13) blocks submission under strict, because its deferred constraint may yet fail. A form without strict — the default — is submittable when no field is invalid; a pending field does not block, and its deferred constraint is reported as E-VAL-05 so that the deferral is visible rather than silent.

Spec-10.15

strict is a property of the form, not of a field, and it affects only the submittability judgement of Spec-10.13. It MUST NOT affect any data-plane result, the content of any field, or the value of any constraint.

Spec-10.16

A runtime MUST report the first failing field in document order, not in click order or dependency order. Users read top to bottom.

Spec-10.17

The expression $form.valid evaluates to the submittability judgement of Spec-10.13 computed without the strict adjustment of Spec-10.14. strict governs whether the form MAY be submitted; it MUST NOT change the value of $form.valid, which is a data-plane result (Spec-10.15).

Spec-10.18

Every validation message MUST carry a severity. Default is error for all constraints in §4.8. Warnings are declared with a warn prefix: warn max = 400 (credit-bureau guidance style, e.g. "this is above typical").

Spec-10.19

An implementation MUST implement tier A1 and SHOULD implement tier A2. Tier A1 is part of Level 0 conformance (§15.1). An implementation that does not implement tier A2 MUST declare that fact (Spec-15.1) and MUST NOT report tier-A2 diagnostics.

Spec-10.20

No tier may use a general constraint solver. No tier may search, backtrack, or enumerate values. Every conclusion MUST be derivable from the rules of this section, so that two conforming implementations reach the same conclusion on the same document — a compile-time behaviour that differs between runtimes is a conformance failure, exactly as a data-plane behaviour that differs is (Spec-15.6).

Spec-10.21

Tier A1 reasons about one field's declared type and its unguarded constraints, and MUST report E-SAT-01 when the intersection of their domains is provably empty.

The domain of each construct is:

Construct Domain
type = string(lo..hi) strings of length lo..hi (scalars, NFC)
type = number(lo..hi) the closed decimal interval [lo, hi] of the profile (Spec-5.28)
type = integer(lo..hi) the closed integer interval [lo, hi]
type = enum(…) the finite set of declared option keys
type = array(lo..hi) of T arrays of length lo..hi with elements in T
type = object { … } objects whose members are in the member domains
type = boolean {true, false}
type = date / datetime / duration / file / signature / location / geo unconstrained within the type's domain
required the domain minus the empty content (Spec-5.4)
min / max the numeric or date interval intersected with the domain
minLength / maxLength the length interval intersected with the domain
multipleOf = k the domain, with a divisibility constraint on numbers
pattern = re the domain intersected with the language of re
options the domain intersected with the option key set
in / notIn the domain intersected with (resp. reduced by) the listed set
Spec-10.22

Tier A1 MUST report the more specific code where one applies: E-BOUND-01 when the declared min exceeds the declared max; E-BOUND-02 when the declared max exceeds the type's upper bound; E-BOUND-03 when the declared min exceeds the type's upper bound; E-BOUND-05 when a repeat's min exceeds its max. Only when none of those applies does tier A1 report E-SAT-01.

Spec-10.23

The emptiness tests tier A1 MUST perform are:

  1. Interval intersection — for number, integer, date, and length bounds. Exact over the profile's decimal domain (Spec-5.28).
  2. Finite-set intersection — for enum, options, in, notIn.
  3. Regular-language intersection with a length range — for pattern against a length-bounded string domain. The length range of a regular language is computable for the RE2 dialect (Spec-6.40); the intersection of the language with the length range is decidable. Where the string domain is length-unbounded, a pattern constraint can only make the field unsatisfiable if the language itself is empty, which is decidable.
Spec-10.24

Tier A1 MUST NOT report a field unsatisfiable unless the intersection is provably empty. A construct whose domain tier A1 cannot express — a pattern over an unbounded string domain whose emptiness is not decidable, a custom constraint, a constraint whose guard is not true — is undetermined at tier A1 and MUST NOT be reported.

Spec-10.25

A field whose constraints are all guarded is satisfiable at tier A1: a guard that is not exactly true means the constraint does not bind (Spec-5.10), and a constraint that does not bind cannot make a field unsatisfiable.

Spec-10.26

Tier A2 reasons about constraints whose guard or target depends on another field, and MUST consider, for each field F:

  1. the guards of F's own constraints, narrowed by the tier-A1 domains of the fields the guards read;
  2. F's cross-field constraints (equal, notEqual, cross);
  3. the relations F participates in (§8.8), whose equations relate F's members.
Spec-10.27

Tier A2 MUST NOT use the $ namespace, a source whose type is undeclared or any (Spec-5.21), or any expression whose domain tier A2 cannot bound. A field whose satisfiability depends on such an expression is undetermined and MUST NOT be reported.

Spec-10.28

Tier A2 MUST report:

  • E-SAT-02 — two constraints on the same field have provably disjoint domains. This is the cross-constraint form of E-SAT-01 and is reported in preference to it when both apply.
  • E-SAT-03 — a contradiction that spans two fields: a field's constraint and another field's constraint together have no joint solution (for example equal = b on a and notEqual = a on b).
  • W-SAT-01 — a field is provably dead: its when is the literal false, so the field can never be applicable and its constraints can never run. This is a warning, not an error, because an extension or a later revision may reinstate the field.
Spec-10.29

Tier A2 MUST terminate and MUST be bounded by the size of the dependency graph (§9.2). Narrowing MUST proceed in document order and MUST visit each node at most once per field; a cycle in the graph is already E-CYCLE-01 and MUST NOT be entered by tier A2.

Chapter 11 — Sources, actions and offline behaviour

Spec-11.1

An action declaration describes a request. It is data, not code. The runtime owns transport, auth, and serialisation. The legacy editor's onSuccess(showSuccessSnackbar) form — a directive naming an unregistered function — is removed; effects are handled by match over action status, which is typed and analysable.

Spec-11.2

An action has a status: idle | pending | success | error. Status is a normal derived value, so a form can branch on it:

forms
match $submit_application.status {
  "success" -> { navigate = "/receipt" }
  "error"   -> { notify = t("form.submit.failed") }
  default   -> { }
}
Spec-11.3

retry MUST use exponential backoff with jitter and MUST cap at the declared value. An action MUST NOT be retried on a 4xx response.

Spec-11.4

The body of an action is $form.output — the projected output document (§4.10) — unless the action declares its own body.

Spec-11.5

A source is a lazily-evaluated, cached remote value. It MUST NOT be fetched until something references it.

Spec-11.6

cache values: "none", "5m", "1h", "24h", "7d", or an explicit duration.

Spec-11.7

Source resolution is asynchronous. A field whose options come from a source — that is, a field that declares optionsFrom (§4.7) — MUST render in a pending state until resolution, and MUST handle failure without trapping the user. An implementation that cannot render a pending state is non-conforming at Level 3 (§15.1).

Spec-11.8

A source that is iterated MUST declare a bounded type (Spec-6.47).

Spec-11.9

A runtime operating offline MUST preserve unsent submissions in a durable queue and MUST replay them on reconnect, in submission order.

Spec-11.10

An action declares a conflict policy for replay via the conflict attribute: retry, skip, or fail. When the attribute is absent the policy is skip, and the action is unmarked: a runtime MUST record that the policy was not chosen explicitly, so that it can be reported. The default MUST NOT be retry. A replayed request that carries a payment instruction would send it twice, so re-sending MUST be an explicit, chosen behaviour.

Spec-11.11

A source that cannot be reached MUST NOT be queued for later replay. The offline queue of Spec-11.9 preserves submissions, which are writes; a source is a read. A source that fails to resolve leaves the fields that depend on it without a choice set, and the failure is reported as E-IO-01. A field whose source failed is not in the state of a field whose source yielded no values (§4.7): the first carries the failure, the second does not.

Chapter 12 — Internationalization

Spec-12.1G7

User-facing text MUST be externalizable: a conforming runtime MUST be able to resolve every user-facing string in a document to a locale-specific message, and the text block is the mechanism by which translations are supplied.

Spec-12.2

Authors SHOULD place user-facing strings in text blocks and reference them with t("key"). A string literal used directly as a user-facing value is permitted — the language does not force indirection on every label — but a Level 0 linter MUST report it as W-TEXT-01.

Spec-12.3

The text block's entries form a single catalogue. Entries across all text blocks in a document are merged; a key MUST NOT appear twice in the merged catalogue (E-SYN-08).

Spec-12.4

t("key") resolves against the current locale with this fallback chain:

text
exact locale  →  language-only (e.g. "pt" for "pt-BR")  →  "default"  →  key itself
Spec-12.5G7

Any user-facing text MAY contain ICU MessageFormat placeholders. The plural and select forms are REQUIRED at Level 2:

text
"files.selected": "{count, plural,
    =0 {No files selected}
    one {One file selected}
    other {# files selected}}"

"greeting": "{gender, select, female {She} male {He} other {They}} applied."
Spec-12.6

Runtime implementations MUST use the host platform's CLDR plural rules, not a hardcoded two-form one/other table. Languages with three or six plural categories — Arabic, Welsh, Russian, Polish — are the reason this is normative.

Spec-12.7

Error messages MUST be localisable, including validation errors produced by built-in constraints. A runtime MUST be able to render "Field must contain at least {min} characters" in any supported locale.

Spec-12.8

Dates, times, numbers, and currency MUST be formatted through the host locale's CLDR data. A hardcoded MM/DD/YYYY is a conformance violation on submit.

Spec-12.9G11

A data-plane result MUST NOT depend on the presentation plane, and the presentation plane MUST NOT feed the data plane. The display type marks a presentation-plane value. In particular:

  1. The three presentation builtins — t, formatDate and formatMoney — are the only functions that produce a display value (Spec-6.36).
  2. A display-typed expression MUST NOT be used in a data position. The data positions are: a field's value or default; an output entry's value; the operand of an ordering comparison; the operand of -, *, / or %; and one side of an == or != whose other side is not itself display. Such a use is E-TYPE-08, and it is reported in preference to the more general E-TYPE-02.
  3. A data-plane result MUST NOT depend on the locale. Text shown to a person MAY differ between locales; the data MUST NOT.

Chapter 13 — Accessibility

Spec-13.1G6

A Level 2 implementation MUST satisfy:

Requirement Source Rationale
Every input has a programmatically associated label WCAG 3.3.2 (A) Screen readers announce unlabelled inputs as "edit text"
Errors identified in text, not by colour alone WCAG 1.4.1 (A) ~8% of men have colour vision deficiency
Errors described with a suggestion for correction WCAG 3.3.3 (AA) "Invalid" does not help; "Use YYYY-MM-DD" does
An error summary on submit, focus moved to it WCAG 3.3.1 (A) The established accessible pattern
Inline errors linked via aria-describedby WCAG 1.3.1 (A) Associates message with control
Focus visible at 2px minimum contrast WCAG 2.4.11 (AA) Keyboard and low-vision users
Target size ≥ 24×24 CSS px WCAG 2.5.8 (AA) Motor impairment
placeholder never substitutes for label WCAG 3.3.2 (A) Placeholder text vanishes on input
Change-of-context announced WCAG 3.2.2 (A) Page auto-navigation on select is disorienting
Spec-13.2

A placeholder MUST NOT be the only user-facing text for a field. It disappears the moment the user types, taking the instruction with it.

Spec-13.3

A runtime MUST render label and MUST render placeholder only as supplementary information.

Spec-13.4

On a failed submit, a conforming runtime MUST:

  1. Render an error summary at the top of the form listing every failing field
  2. Move keyboard focus to that summary
  3. Announce it to assistive technology (e.g. role="alert", or a live region)
  4. Render each summary entry as a link to its field
  5. Render the same message inline at the field
Spec-13.5

Re-failing submission MUST return focus to the summary, not scroll silently. This is the single most commonly-missed accessibility requirement in form implementations.

Spec-13.6

group and repeat MUST be exposed to assistive technology as labelled regions (<fieldset>/<legend> or equivalent). A repeated block MUST expose its item count to the user.

Chapter 14 — The error catalogue

Spec-14.1

code and location are stable and MUST be emitted. message is for humans and MUST NOT be parsed by tooling.

Spec-14.2

Implementations MUST emit context.suggestions for E-REF-01 using edit distance, and context.near_misses for E-PROP-01. A raw "unknown identifier" with no suggestion is a diagnosability failure under G5.

Spec-14.3

A missing required property MUST be reported as E-PROP-07, not as E-PROP-01. E-PROP-01 means "this property does not exist"; E-PROP-07 means "this property is required and is absent". The two are different diagnoses and a linter that reports them identically is not useful.

Chapter 15 — Rendering and conformance levels

Spec-15.1

An implementation MUST declare its level. It MUST NOT claim a level whose requirements it does not meet.

Spec-15.2

A renderer MUST map every intent it receives. An unmapped intent is a conformance failure, and the runtime MUST surface it as a warning rather than silently dropping the field.

Spec-15.3

A renderer MAY map an intent to a different widget, provided the mapping preserves the intent's accessibility, validation, and keyboard semantics. A renderer MUST NOT map RenderChoice to a control that cannot be operated by keyboard alone.

Spec-15.4

A Level 2+ renderer MUST:

  • Maintain a labelled control for every active field
  • Expose field state to assistive technology per §13
  • Never render a value in a way that omits its label
  • Handle the pending source state without trapping focus
  • Preserve field identity across re-renders (so focus is not lost mid-typing)
Spec-15.5

A renderer MUST preserve input focus and cursor position across a state propagation. A runtime that re-renders the whole form on every keystroke and loses focus is non-conforming.

Spec-15.6

For a fixed document and a fixed script of transactions, two conforming runtimes MUST produce identical: field values, validity states, validation error codes, and output documents.

Chapter 16 — Versioning and extensions

Spec-16.1

The language is versioned MAJOR.MINOR.PATCH with SemVer semantics.

Spec-16.2

A document declares its target with form name version = "1.0". A runtime MUST reject a document whose MAJOR version it does not implement, with E-VER-01.

Spec-16.3

Within a major version, MINOR releases MAY add:

  • new builtins (§6.4)
  • new field properties
  • new constraint kinds
  • new render intents
Spec-16.4

A document using a feature newer than the runtime's MINOR version MUST be rejected, not ignored. Ignoring an unknown validate constraint means a form silently stops enforcing a rule the author believed was active.

Spec-16.5

Extension blocks MUST be namespaced. Unnamespaced unknown syntax MUST be an error.

Spec-16.6

An implementation MUST ignore unknown properties inside a known extension namespace, and MUST report unknown blocks in that namespace. This split is what allows a newer extension document to degrade usefully on an older runtime instead of failing wholesale.

Spec-16.7

A namespace block's contents are visible only to documents that import that namespace. A runtime that does not implement a namespace MUST reject a document that imports it, with E-EXT-01.

Spec-16.8resolution

import "id" as alias resolves id against, in order: (1) the namespace blocks declared in the same document, then (2) the runtime's extension registry. The first match wins. If neither resolves, the import is E-EXT-01. A document MUST NOT import two namespaces under the same alias; that is E-EXT-02.

Spec-16.9contribution

An imported namespace contributes to the form exactly as core vocabulary does:

  • a field declared in the namespace is a field of the form, addressable as alias.name, and participates in the data model, in validity, and in output if it is listed in the output block;
  • a constraint declared in the namespace binds under the rules of Spec-5.10 and contributes to validity like any other constraint; a failure is reported as E-VAL-02, with the constraint kind and the extension identifier in the error's context;
  • a property declared in the namespace is available on the entities the extension declares it for.
Spec-16.10versioning

A namespace block MAY declare version = "<string>". A registry namespace carries the version the runtime implements. A document that requires a version the runtime does not implement is rejected with E-EXT-03; it MUST NOT be silently downgraded, because a downgraded extension is a rule that does not mean what the author wrote.

Spec-16.11extensions are part of the form's identity

An imported namespace participates in the document's identity for the purposes of §16.2 versioning and Spec-8.27 snapshots: a snapshot taken under one set of imported namespaces MUST NOT be restored under a different set.

Chapter 17 — A worked example

Spec-17.1

A page with when = false MUST be skipped without blocking navigation, and its fields MUST NOT contribute to form validity (Spec-7.15). The verify page is skipped for technical support, and the user is never blocked by a code they were never sent.

Chapter 18 — The conformance suite

Spec-18.1

The complete §17.2 document MUST parse, pass semantic analysis, and produce a single well-defined output document for a given input sequence. A conforming implementation MUST ship this document as a test fixture.

Spec-18.2

A conforming implementation MUST pass §18.1–§18.4 against the same test vectors. The rows in §18.1 and §18.4 are complete documents and MUST be parsed as written; the rows in §18.2 are single-statement fragments and MUST be rejected with the stated code. The stated code MUST be the first error reported for the row. An implementation MAY report further errors of the same stage after it, but the stated code is the row's primary diagnostic.

Spec-18.3

An implementation MUST treat the §18.2 rows as errors of the stated kind, not merely as rejections. A parser that rejects type = strng with a generic "parse error" has not implemented §14; the code is the interface. The stated code has to be the first error, so that a caller can act on it without having to know which of the reported errors matters.

Spec-18.4

The reference parser for §3 and §4 is a grammar-level artefact: it implements §3 and §4 only, and rejects the syntactic and lexical rows of §18.2. The semantic rows of §18.2 — undefined references, type errors, cycles, cost overruns, missing required properties, unresolvable output paths — require the static analyser of §2.3 and are not claimed by the reference parser. A conforming implementation MUST implement both halves.

Chapter 19 — Migration from the legacy form format

Spec-19.1

A migration tool MUST be able to detect the legacy computed(from …) pattern and MUST emit a diagnostic rather than a guess, because the original expression is not recoverable from the dependency list.

Spec-19.2

A migration tool MUST emit a diagnostic when a legacy identifier contains a hyphen, because FormScript 1.0 identifiers may not (§3.6). The tool MUST suggest an underscore-separated equivalent and MUST check the result for collisions.

This is the honest answer: The legacy editor's computed(from price, quantity) recorded that total depends on price and quantity, but there was no syntax anywhere in the nine parts for what total equals. The language's central feature was unexpressible. Any automated migration must therefore surface these as manual work.

Chapter 20 — Reserved words

Spec-20.1

The hard keyword not is the word form of the ! operator: not x is exactly !x, with the same three-valued semantics (Spec-6.12). A document MAY use either spelling. not is reserved because and and or are reserved and it would be incoherent to reserve two of the three words.

Spec-20.2

This section is the single normative list of reserved words. §3.5 shows a summary for the reader and MUST agree with this section.

Spec-20.3

A hard keyword MUST NOT be used as a declaration name — that is, as the identifier of a field, group, repeat, section, page, choice, or state block, nor as an import alias.

Spec-20.4

A soft keyword MAY be used as a declaration name, an attribute name, and a field reference. The parser resolves the keyword interpretation by context, per §4.14.

Spec-20.5

The following names are reserved for future use in version 2.x, so that 3.0 can adopt them without a break: break, case, catch, for, then, while, with.