Field specs

A spec is a named, reusable validation rule for a single field or value — a shape a value must satisfy. You write the rule once, give it a typed error, and attach it wherever that shape is needed with specs [Name]. It is how a plain string becomes a SKU or a postal code without repeating the rule at every call site.

Declaring a spec

Chowk's SKU rule constrains a product code to uppercase letters, digits, and dashes:

sub-domains/catalog/specs/sku.vishwakarma
spec Sku "A stock-keeping unit: 3–32 uppercase letters, digits, or dashes." {
  expr    "this.matches('^[A-Z0-9][A-Z0-9-]{2,31}
)" message "SKU must be 3–32 uppercase letters, digits, or dashes." error InvalidSku target application scope field }

Four clauses do the work:

  • expr — a condition over this (the field value). It evaluates to true or false; it does not run a procedure.
  • message — the human-readable text shown when the rule fails.
  • error — the typed error the failure resolves to, so a generated client gets a stable, catchable error code rather than a bare string.
  • target and scopewhere and over what the rule runs. target application runs it in the application layer (where a typed error can be returned); scope field binds this to the field value.

Attaching a spec

A spec attaches to a field with the specs [...] modifier — the field stays a plain string, and the rule rides along:

sku string required immutable filterable[eq] specs [Sku]

The same spec can guard a value object's field just as easily. Chowk's Address shapes its postal code with a spec declared right beside it:

sub-domains/ordering/specs/postal_code.vishwakarma
spec PostalCode "A postal code: 3–10 letters, digits, spaces, or dashes." {
  expr    "this.matches('^[A-Za-z0-9 -]{3,10}
)" message "Postal code must be 3–10 letters, digits, spaces, or dashes." error InvalidPostalCode target application scope field }

Because Address.postal_code carries specs [PostalCode], the rule enforces itself everywhere an Address is used — placing an order with a malformed postal code is rejected with the typed InvalidPostalCode error, no matter which command carried the address.

Specs resolve to a typed error catalog

A spec's error points at an error definition — a small entry that gives the failure a stable code and HTTP status:

sub-domains/catalog/errors/validation.vishwakarma
error InvalidSku "Value failed the SKU shape rule (Sku spec)." {
  code        "INVALID_SKU"
  severity    validation
  retryable
  http_status 400
}

This is what turns validation into a first-class contract. A generated client does not parse an error string — it receives INVALID_SKU with a 400, typed and catchable. The spec, its message, and its error travel together from the model to the wire to the client, so a shape rule you write once is enforced consistently and reported the same way everywhere it applies.