Identity and fields

Every entity needs an identity and a set of fields. Identity is a field carrying the pk flag; fields are a name, a type, and a small closed set of modifiers. The modifiers are declarative — they describe what a field is, and the platform turns that into column types, constraints, indexes, and validation.

Identity

The near-universal identity is a UUID primary key:

id uuid pk filterable[eq]

pk marks the primary key; filterable[eq] says the field can be filtered by equality in a query. You never author proto field numbers or column order — identity is just a field with a flag.

Natural keys — a business identifier like a SKU or an order code — are modeled with unique, not pk. Chowk's product keeps a UUID id and a unique sku:

sub-domains/catalog/entities/product.vishwakarma
entity Product aggregate_root global {
  doc     "A sellable product in Chowk's catalog."
  systems [CatalogSystem]
  audit   timestamps

  indexes [
    { name "uk_product_id"  kind unique fields [id] }
    { name "uk_product_sku" kind unique fields [sku] }
  ]

  id     uuid   pk filterable[eq]
  sku    string required immutable filterable[eq] specs [Sku]
  name   string required
  price  Money  required
  status ProductStatus required
}

The surrogate id is the stable internal identity; the sku is the human-facing natural key, kept unique by an index and shaped by a spec.

The field vocabulary

A field is <name> <type> [modifiers…]. The type is a built-in scalar (uuid, string, text, bool, int32, int64, timestamp, date, decimal, …), or the name of an enum or a value object (like Money). Append ? to make it optional.

The modifiers are a closed set — a fixed vocabulary, not free-form annotations:

ModifierMeaning
pkprimary key
requiredmust be present
immutablewrite-once — set at creation, never updated
uniquea single-column uniqueness constraint (a natural key)
filterable[eq, …]queryable, with the allowed operators in brackets
sortableorderable in list queries
specs [Name, …]attach reusable field-shape rules (see Field specs)
= <default>a default value (a literal, or a bare enum member)
relates <Entity> …a foreign-key relationship (see Aggregates and entities)

Reading a real spread, every modifier is a small declarative fact about the column:

id          uuid        pk filterable[eq]
customer_id uuid        required immutable filterable[eq]
status      OrderStatus required
sku         string      required immutable filterable[eq] specs [Sku]

customer_id is required, set once, and filterable — a caller can list orders by customer but never move an order to a different one. sku adds specs [Sku], attaching a shape rule. Because the vocabulary is closed, a field declaration is fully checkable: the compiler knows exactly what each word means and generates the column, constraint, and index to match. There is no annotation whose effect you have to guess.

Fields flow outward automatically

You declare a field once, on the entity, and it propagates: the table column, the create/update command inputs that carry it, the query filters (filterable) and sort keys (sortable), the wire types, and the client-side validators are all derived from it. Change required to optional, or add a spec, and every layer re-derives. The field declaration is the single source of truth for that piece of data everywhere it appears.