Skip to content

ADR 0030: Field input rules and record numbers

Store input_rules once on sys_fields, next to the field’s type configuration. It contains translated description and helpText, a typed nullable defaultValue, and unique. Descriptions and hints follow the workspace’s existing definition-language policy and label fallback. They are plain text; record contents remain untranslated. The shared field editor separates identity, data type, input rules and explanations; detailed help is collapsible. Narrow layouts stack language selection above the text. Labels, descriptions, controls and hints form one semantic field, with descriptions/hints referenced by the actual controls. Type/Status/Category keep their existing dedicated dictionaries/defaults and save their explanations atomically with classification edits. Access principals and record numbers retain their protected system behavior.

Defaults use the same normalization, option/reference checks and precision as normal field writes. They apply only on creation and omission, including API/MCP and linked creation. Explicit null, false, zero or an empty supplied value does not reapply a default. Resolve unconditional defaults first, then conditional defaults against the complete controller values. Never write a default through a field the creator cannot create. A default does not backfill or change existing records. Updates never reapply it. Inactive options or references cannot become new assignments through defaults. Invalid defaults and unsafe conversions fail atomically.

The web form starts from the same omission/conditional resolver and uses normal typed/regional controls. Requiredness, applicability, field permissions and unsaved-edit guards remain unchanged. Clearing a default and uniqueness before a UI type change makes the configuration change explicit; backend conversions independently validate retained defaults and data.

A rule is scoped to one object type in its workspace. PostgreSQL enforces it with a metadata-identified unique index on the physical column, including inactive and recycled rows. Null/empty text values may repeat. Text, email, phone and URL comparisons ignore case and surrounding ASCII whitespace; stored values are unchanged. Other scalar values use their normalized PostgreSQL representation. Money compares amount and currency; a scalar reference compares kind and identity. Multiline text, lists and classification fields are not eligible. Unique text is limited to 2,000 UTF-8 bytes so its complete value can be indexed without hash collisions or oversized index keys.

Enabling uniqueness preflights existing normalized values and fails without changing definitions, data or history if duplicates exist. Safe storage conversions rebuild the index in the same transaction. Writes cannot bypass the index through concurrent API/MCP requests. Errors identify the field and distinguish record entry from definition configuration without exposing another record or its contents. API/MCP errors carry the field identity and context; web forms show a field-aware summary/inline message and focus the affected input or uniqueness rule. Duplication does not copy unique custom fields or an automatically assigned record number; creation defaults/requiredness still apply.

sys_object_types.numbering stores enabled, literal prefix/suffix, positive integer start and increment, minimum digit count (1–30), and restart (never, yearly, monthly). Integer inputs are decimal strings, with up to 30 digits; no floating-point arithmetic is used. Monthly/yearly numbers automatically include YYYY-MM-/YYYY- between prefix and counter. Period boundaries use the workspace’s named time zone and the creation transaction’s timestamp.

Enabling numbering installs one protected text field, record_number, on that object, labelled Number / Nummer in the interface. The documentation calls it a configurable record number / Datensatznummer, distinct from the technical UUID. It participates in normal forms, field visibility, tables and journals, but UI/API/MCP cannot assign, edit, rename or deactivate it manually. It is assigned only to newly created records while enabled. Existing records remain unchanged; disabling retains their numbers and the field. Counters never move backwards when a lower start is configured; a higher start advances the next allocation. Increment/padding/format changes affect future allocations only.

sys_number_counters serializes allocations per object/period. sys_number_allocations retains every issued number, including across format/restart changes, recycling and disabling/re-enabling. Runtime access to the allocation ledger is append-only. A collision with an already issued formatted number fails explicitly; configuration must be adjusted rather than reusing the number. Exhaustion also fails explicitly. Failed creation or journal writes roll back the counter, allocation, contents and journals together. Successful creation journals the number as an ordinary field with the same change ID as the other initial values. There is no promise of business-contiguous numbering across explicit configuration changes.

Object settings save labels, numbering and optional record-access changes under the same optimistic object version and transaction. Configuration changes are journaled per property; existing numbers and UUIDs never change. Numbering settings are available to workspace Designers and installation Administrators; normal users inspect them read-only. Field read restrictions also apply to number projections and search. No preview reserves a number. Object settings initially show an example/restart summary with expandable counter settings; invalid configurations suppress the example and expose associated field errors. Record numbers have the same standard-column placement after Keyword in object and workspace lists; permitted Command-menu results show the number alongside the object label.

Extend the one current unreleased schema; rebuild disposable development instances and the fictional seed. No historical runtime schema, compatibility DTO or development migration is introduced. Additional fields remain physically appended; display order stays in form metadata. Release upgrades, general expression-based defaults/number formats, manual number overrides and retroactive backfill are not introduced by this decision.

Validation covers typed defaults, explicit empties, conditions, current creation rights, metadata versions/languages, normalization, uniqueness races/retention, counter concurrency, restart periods/time zones, unchanged issued values and journal-failure rollback. See the development verification guide for reproducible checks and public evidence; this is not a software release or complete product acceptance.