Pin the form edition per row
Each row renders and validates against the form version its code was written for, while the role's form keeps evolving.
The situation
The inspection form gains a lotNumber field next week. Rows created today were written by code that expects the old shape, and they should keep rendering the old form until they close.
The recipe
const result = await conditional<{ pass: boolean; lotNumber: string }>(signalId, { role: 'inspection', description: 'Inspect the lot', metadata: { orderId }, schemaVersion: 3,});ltc roles save-schema inspection --file inspection.json --summary "Added lotNumber"ltc roles schema-versions inspectionltc roles schema inspection --version 3Every save that changes a role’s form_schema appends an immutable snapshot to lt_role_schemas and advances the current version. schemaVersion pins one snapshot to the row as metadata.schema_version.
The guarantee
A pinned row renders and validates against that snapshot regardless of later edits. A pin that names a missing version fails at creation with a 400 rather than falling through to another version. Unpinned rows use the latest form. The pin is a facet, so “rows still on v3” is a containment query. See Forms.
Related patterns
Resolving from anywhere
Parse the resolution at the boundary
One zod schema gives the workflow its type, its runtime check and its version pin, so resolver payloads enter code already parsed.
Resolving from anywhere
One contract, one 422
Every resolve surface rejects a bad payload with the same 422 body naming each field, so every client handles validation one way.
Forms and configuration
The form is data
A role's form is a versioned JSON Schema; each row pins the edition it renders, so editing the form never breaks work in flight.
Forms and configuration
Versioned lookups
Option lists live once in the knowledge store as immutable editions; rows carry a three-field ref and forms read the edition through it.
