Skip to content

ADR 0032: Structured fields, temporal values and relationship attributes

  • Status: Accepted architecture direction; contact fields, relationship attributes and base provisioning implemented locally; CRM functional scope locally accepted
  • Date: 2026-11-04
  • Refines: ADR 0007, ADR 0019 and ADR 0031

Implemented locally: structured fields with name_components, email_entries, phone_entries, address_entries, url_entries and partial_date; schema inventory through list_field_schemas; typed controls/summary rendering, normalization, byte/entry limits, safe conversion, whole-field rights and atomic field events with component-path/order context. Entry IDs accept normalized UUIDs; application-created entries use UUIDv7. There is no subcomponent ACL or structured Contact-reference codec yet.

Contacts activation provisions protected Contact/Distribution-list objects and semantic UUID bindings in sys_module_field_bindings and sys_module_relationship_bindings. Contact kinds cannot be added/removed/deactivated; their translated option labels remain editable. Salutation (salutation) and Academic title (academic_title) are configurable single choices, separate from structured personal-name parts. Title contributes its selected label in the workspace default language to the formatted name; Salutation is correspondence metadata and does not contribute. Keyword, formatted name and Organization name are virtual value_mode='derived' fields with no physical duplicate column. Current source/target field rights bound projections, search and relationship/history names. Membership enforces at most one Organization; Identities and distribution membership use ordinary edges. Projects and Contacts share the supplied Contact object; existing nonconforming Contacts fail activation atomically rather than being adopted or guessed.

Object definitions expose a UUID-bound contactProfile with field, kind and relationship-purpose bindings for conditional Contact requirements. Shared contracts drive both server validation and visible requiredness/save gating; hidden source fields are not inferred by the client. Derived fields have read-only value rules. Contacts use the fixed kind-specific core and designer extension boundary in ADR 0025; kind selection precedes creation and normal record updates cannot change it. Protected collection schemas remain fixed while their translated context options are configurable. Phone context options have an explicit usage or phone_type group; Messenger options use usage and messenger_type. A shared codec descriptor supplies these dimensions to configuration validation and both editors. The ordinary editor selects zero or one option per dimension. The interchange-capable UUID context array can retain several API-supplied labels; editing a dimension replaces its selection. Group identity is fixed once an option is created, and options cannot carry groups from a different codec. Existing options are retained/deactivated rather than removed. Entry storage remains one UUID context array; historical field snapshots retain the option group metadata. Module relationship bindings store their originating module explicitly; structural settings are protected and translated labels remain editable. Both record and instance journals reconstruct historical field contexts and use the same typed value formatting.

Pending architecture below: structured Contact-reference integrity, calendar-party codecs, recurrence/overrides, complete templates and exchange envelopes. Relationship-owned fields/attribute values, both-endpoint attribute authorization and a common object form are implemented locally. These base features do not establish complete Contacts delivery or RFC round-trip acceptance. The implementation follows the current-installation rebuild policy; no development-schema upgrade path was added.

The domain profiles require structured contact values, partial birthdays, domain time zones, recurrence exceptions and direct Contact professional roles. Current business fields already use validated JSONB for Money/User references, and now add nine finite structured contact codecs to the existing scalar/choice/reference kinds. sys_fields has exactly one object or relationship owner; relationships store typed attribute values and have an independent optimistic version. Record history already references sys_fields.id and reconstructs historical kind/configuration. Current object/field permissions are the authority; no separate groupware rights model is introduced.

This ADR defines the staged architecture. Contact codecs, derived names, relationship attributes and base modules are implemented locally; recurrence, calendar parties and exchange work remains open. V1 still excludes import/export, synchronization, calendar views, invitation delivery and notifications. Existing simple Date and Date/time fields retain their exact current contracts. The finite supported exchange profile remains a separate declaration under ADR 0031.

Add structured to the shared field-kind catalog. config.schema selects a finite, shipped codec; it is not arbitrary JSON Schema, executable code or an external schema URL. Each codec defines strict input/output shapes, normalization, validation, emptiness, component keys, reference extraction, display/edit composition and allowed filters. Client/server/API/MCP use the same contracts. Code owns type semantics as for existing scalar kinds; PostgreSQL field metadata owns labels, input aids, requiredness, form placement and declared configuration. Do not branch on object/field technical names.

Initial codecs:

Schema key Logical value Canonical shape
name_components One personal-name part Ordered {id,text} components. Each name part remains a separate field for normal field rights.
email_entries Email addresses Ordered entries {id,value,contexts,preference} with email validation.
phone_entries Telephone numbers Ordered entries {id,value,contexts,preference} with the common international phone normalizer.
address_entries Postal addresses Ordered entries {id,address,contexts,preference}; typed address components follow the domain appendix.
url_entries Websites Ordered entries {id,value,contexts,preference}; HTTP(S) values are stored without fetching them.
language_entries Communication languages Ordered {id,value,contexts,preference} entries; value is a canonical language tag.
messenger_entries Messenger addresses Ordered {id,value,contexts,preference} entries with a validated messaging URI, never dereferenced.
time_zone Contact time zone {timeZone} with a named IANA zone or UTC; independent of display preferences.
partial_date Birthday, Wedding anniversary, Date of death {month,day,year:null or integer}.
temporal Domain date/time Discriminated union in section 3.
recurrence Series rule Structured frequency/interval/end/weekday/addition/exclusion data in section 4.
calendar_party Organizer One {id,address,name,contact,role,response} entry, with nullable optional components.
calendar_parties Attendees Ordered calendar-party entries using the same schema.

id values are immutable UUIDv7 entry identities, generated before submission and validated for uniqueness/scope. Reordering retains IDs; replacing an entry cannot reuse another entry’s historical identity. Contexts reference stable per-field option UUIDs with translated labels; preference is derived from collection order as consecutive values 1–100. API/MCP callers may omit it; normalization overrides any submitted preference with the entry position. Reordering and deletion renumber atomically with the ordinary per-field journal. Calendar-address URIs are not restricted to local login emails; permitted schemes and lengths are explicitly allow-listed, never dereferenced. Contact references use {objectTypeId,recordId}, not display names. Role/response enum identities have translated labels and no permission meaning.

Collections use [] for no entries; optional single structured values use null. Omission on update preserves the complete stored field. Explicit empty replaces it only when permitted and not required. Required collection means at least one valid entry. Required partial date requires month/day, not an invented year; yearless February 29 is valid. The UI uses one regional text input with Calendar/clear actions. Input accepts the full date or the regional month/day form; invalid drafts block saving. Yearless calendar selections preserve null, and visible/accessible labels omit the internal leap-year anchor and its weekday. A known year can be explicitly cleared; no reference year is persisted. Empty entries are rejected rather than removed silently. Stable component keys are internal semantic identifiers; translated component labels/descriptions/help live in validated config.components, and configurable context options in config.options. The installed codec descriptor supplies their allowed keys, types and default translation proposals. Component presence/order cannot be redefined by arbitrary metadata.

Initial service limits: at most 100 collection entries, 20 components per name part and 256 KiB canonical UTF-8 JSON per field. Each string/URI also has a codec-specific bounded length before serialization. Reject excess input explicitly; never truncate a stored collection. These are resource guards, not measured production capacity. Incompatible codec changes are rejected; same-codec configuration changes preflight current/recycled values. New structured fields initially support typed literal defaults through the existing omission-only resolver, but no whole-value uniqueness setting. Semantic uniqueness such as Distribution-list membership continues to use relationship constraints.

Store each object-owned structured field in one JSONB column on its existing readable business table. Append it on installation as with other fields; do not create Contacts/Tasks shadow tables, an EAV content store or standalone public objects for addresses/participants. Ordered arrays carry their own order; JSON object-key ordering has no semantic meaning. PostgreSQL JSONB validates JSON representation, not our domain schema; strict codecs remain mandatory.

Generalize sys_fields ownership with nullable object_type_id and relationship_type_id, exactly one populated, both foreign-keyed to the owning definition. Preserve one field UUID namespace, existing definition lifecycle, translated metadata, journals and protected-structure rules. Partial unique indexes enforce technical-name uniqueness per owner. Structured schema keys and component configuration stay in config; no repeated codec definition is stored per record. Object APIs continue listing object-owned fields only; relationship definitions expose their own fields collection. Add protected value_mode metadata (stored or derived, default stored). Only shipped semantic bindings may declare a derived field: it has an output kind/labels but no business column or caller-writable value. Ordinary custom creation cannot enable arbitrary computed expressions. This provides the system-derived Contact Keyword/formatted-name projection; existing virtual Access principals can use the same descriptor mechanism without retaining parallel name-based detection. Immutable issued Record numbers remain stored values.

Add values jsonb NOT NULL DEFAULT '{}', positive version, updated_at and updated_by to sys_relationships. Attribute keys are stable relationship-owned field UUIDs. Missing optional attributes are null semantically; JSON cannot contain arbitrary unregistered keys. Requiredness, option identities and field applicability are checked using the same field engine, constrained to that relationship owner. No attribute columns are added to Contact tables. Existing endpoint constraints, one-pair definitions, edge orientation and history stay authoritative. General/Primary have no editable attributes in the initial implementation.

Add sys_structured_record_references as a derived integrity index for Contact references inside structured values: workspace, source object/record, field, entry ID/component path and target object/record. Composite registry foreign keys enforce workspace scope; field-owner checks reject references attributed to another source. Its unique key includes source, field, entry and component path. The JSONB field is authoritative. The common transaction regenerates affected rows, and a deferred constraint checker verifies exact correspondence in both directions at commit. Reference rows cannot be written through API/MCP and never grant rights. Runtime SQL maintenance is unsupported; integrity mismatch fails closed. Restricted targets remain retained in JSON but are redacted on reads and cannot be newly assigned without current eligibility; recycling does not silently remove them.

Add sys_module_field_bindings(workspace_id,object_key,field_key,field_id) and sys_module_relationship_bindings(workspace_id,module_key,relationship_key,relationship_type_id) for protected semantic purposes. Composite uniqueness and owner/scope foreign keys tie them to the existing module object/relationship definitions. One shared object/field purpose has one binding identity even when several modules contribute fields; module memberships/contributions remain separate metadata. A field-purpose binding is valid only for the object referenced by its object key. Derived-name and role services resolve these bindings, never technical names or current translations.

Fields containing references or recurrence control data use targeted expression indexes only for declared query paths. No blanket GIN index or searchable raw-JSON endpoint is assumed. Search/filter/count/sort use permission-filtered typed projections before pagination. Unsupported structured sorts are rejected; generic record lists can show a compact summary without implying a sort. The initial global-search scope remains Keyword/record number; contact-email or temporal-range search must be added as an explicit shared operation/filter with permission tests.

3. Temporal contracts and time-zone ownership

Section titled “3. Temporal contracts and time-zone ownership”

Proposed temporal input values:

{"kind":"date","date":"2026-11-04"}
{"kind":"instant","instant":"2026-11-04T09:00:00Z"}
{"kind":"zoned","local":"2026-11-04T10:00:00","timeZone":"Europe/Berlin","occurrence":null}

occurrence is null for unambiguous local times, otherwise earlier or later must explicitly select the overlap. Reject gaps. Zoned values store local wall time, named zone and overlap choice as the source of intent; resolved UTC is a derived response/query value, never a second client-writable timestamp. Precision is milliseconds. Instants normalize to UTC; dates never gain midnight/offsets. A cache/index may contain derived instants but must declare its time-zone-data version and be rebuildable; stale caches cannot drive a write. Existing datetime continues accepting offset timestamps without this wrapper.

Appointments/Vacations validate their start/end as one semantic interval; both boundaries use the same temporal form and, when zoned, the same named zone. Their end is exclusive. The UI’s inclusive final-day choice converts once at the boundary. Task start/due rules follow the appendix, not the Appointment end label. An explicit instant-only Task remains valid. Recurring zoned starts retain local time through DST rather than adding multiples of 24 hours. Floating times are outside the first editable codec; future exchange must preserve/report or reject them explicitly, not assume a viewer zone. Date-only series stay date-only.

Personal/workspace zones affect display and initial proposals only. Saving a different viewer preference never changes domain zones. Future time-zone-data updates require an explicit recalculation/review policy for future zoned occurrences; they must not silently rewrite fixed instants or historical journals. Custom non-IANA time-zone definitions and leap-second exchange remain declared unsupported cases until a profile designs them.

A series remains one ordinary record with a recurrence field. Its identity is that record UUID plus the recurrence field UUID, not an exchange UID or a new public object type. Store the structured rule/additions/exclusions on that field, with the selected finite subset from the appendix. Reject contradictory end modes, unanchored rules and unsupported rule parts. A proven recurrence implementation must pass the RFC-based fixture set before selection; this ADR does not invent a parser or add a package.

Store edited occurrences in sys_record_occurrence_overrides: UUID, workspace, series object/record, recurrence field ID, immutable original occurrence key, typed field-ID override map, canceled flag and optimistic version. A unique composite constraint covers series, field and original key; registry/field foreign keys and ownership checks constrain scope. Original keys carry the original date/local-time/zone/overlap identity, even after an occurrence is moved. No materialized row per unedited occurrence and no detached duplicate Task/Appointment is created.

Each override is a partial record-field overlay: omitted fields inherit the series, explicit null clears an optional value. Validate the resulting occurrence against current domain fields and series rights. The first implementation permits content/time/status overrides but no per-occurrence relationship topology, ownership, independent security policy or independent recycle lifecycle. Privacy is inherited from the series; field caps still apply. Canceling an occurrence retains its key and history. Series edits preflight retained overrides; invalid overrides block the edit rather than being dropped or reanchored. Recycled/inactive series produce no ordinary occurrence results; restore validates their overrides too.

Recurrence expansion is a bounded, read-only application service, not a scheduler. Require a requested window, cap it initially at 366 days and 1,000 inspected candidates per call, with explicit resumable cursor and incomplete-result flag when the budget is hit. Invalid rule instances are skipped according to the selected standard semantics; do not clamp dates silently. Return effective occurrences ordered by time/key, with original identity and override version. Rights apply before results/counts; never return an unauthorized series count or hidden temporal sort key. Series-record edits and override writes use one series lock and expected series version; an existing override additionally requires its own version. A save increments the series once and each affected override once, atomically with history.

Use ordinary field kinds for attributes; a custom relationship can define a multiple_choice attribute with initially empty configurable option UUIDs/labels. The supplied Contact professional fields are object-owned, as specified below. Store its selections on the actual edge. Reassigning an Organization removes one edge and creates another; old roles remain only in the old edge’s history and are never silently carried over. Reusing a technical name never reuses an edge/field identity.

Reuse administrative object grants and their fields UUID maps for relationship-owned field restrictions: an object’s permission editor exposes fields of incident relationship definitions in a clearly separate section. A relationship field is valid for a grant only when that object is in its declared pair. Record-level field exception maps may likewise restrict incident relationship fields. Field-owner/scope checks reject forged unrelated IDs. Existing group combination, explicit user overrides, privacy and Administrator exception remain unchanged; no separate relationship ACL store or foreign-access graph is added.

Reading an attribute requires both endpoints readable plus Read for that field in both endpoint contexts. Editing attributes initially requires both endpoint records editable plus Edit for the field in both contexts. This is deliberately stricter than changing an attribute-free link from one editable endpoint with a readable target. Attribute editing from either side resolves the same conjunction; a user cannot evade a denial by using the other record’s URL. Administrator content access still does not bypass field structure/validation. To create an edge carrying attributes, creation/link rights and attribute checks must all pass; omit optional attributes if the caller cannot write them, or reject when required input cannot be completed.

Changing an edge’s attributes requires expected edge/definition versions and the source record version, increments the edge once and both endpoint record versions once, and shares the record transaction/change ID. Per-record duplicate references to the same edge are rejected or canonicalized before mutation, never applied twice. If a record save changes its scalars, links and edge attributes, increment that focus record only once. Final-state graph preflight, locks, stale-write guards and journal rollback retain ADR 0019 behavior. Existing General/Primary remain attribute-free and do not gain special permission routes.

6. Field rights, derived labels and journal contract

Section titled “6. Field rights, derived labels and journal contract”

A structured field is one permission unit. Its existing None/Read/Edit ceiling covers every nested entry/component. There is no implicit per-entry or per-component ACL in this slice; if separate rights are necessary, define separate fields (as for the personal-name parts). Readable whole fields may expose only authorized referenced Contact context; retain hidden reference slots as unchanged opaque entries, without target IDs/names. Changing another entry must preserve those slots server-side, not require resubmitting secret values. Reject an attempted whole replacement that would implicitly remove hidden retained references. No rights are inferred from addresses, calendar parties, roles or Distribution-list membership.

Derived Contact Keyword/formatted-name projections use stable field-purpose UUID bindings and source/target authorization. Evaluate visibility before search/count/sort and project the record ID when required name/Organization dependencies are hidden. Do not persist an unrestricted duplicate label as a searchable bypass. Organization renames therefore change derived current projections, not child business rows/history. Technical design must measure these joins before production scaling; no generic expression engine or unbounded fan-out update is introduced.

Keep one journal entry per changed field, old/new canonical values and one common save change ID. Add nullable detail_context on sys_journal for immutable structured schema identity, changed component paths, stable entry IDs and optional occurrence locator; it contains no alternate live value. Keep the existing response-level value_context for reconstructed before/after field formatting; supplemental detail_context must not overwrite it. Paths describe semantic changes, not numeric array indexes. Reordering writes one field event with order change; it does not report every unchanged entry as a value edit. Current rights filter complete values/paths before journal search, counts and pagination. Hidden reference context stays redacted, including technical details.

Relationship attributes use entity_kind=relationship, the edge identity and the actual relationship-owned field_id. Project the same central event to both endpoints through the existing endpoint table, subject to the same attribute Read conjunction in either Journal. Old edge snapshots and field tombstones remain readable only under current rights. The structural link event and each attribute event are separate rows under one change ID. Occurrence changes use the series as the record entity, actual affected field IDs and an immutable occurrence locator in context; recurrence-control edits use the recurrence field ID. Definition events remain separate. No no-op creates a history event or increments a version; any late journal failure rolls back the entire write.

Extend existing create_field/configure_field, get_object_type, create_record/update_record, get_record, definition/history and relationship operations with typed structured fields, owner scope and edge attribute/version metadata. Field creation requires exactly one owner identifier: objectTypeId or relationshipTypeId. Editing uses the immutable field ID; no owner move or duplicate request alias is accepted. Object creation still installs only object-owned fields. No untyped JSON write endpoint, direct-SQL content route or UI-only capability is added. Uniqueness/conversion/default errors retain field identity and context.

Object field writes remain replacements under expected record version. Hidden retained structured-reference entries use server-validated keep markers scoped to the record/field/entry and current version, not exposed target IDs. A keep marker cannot create/move an entry, survive a version change or be used in another field; decode it only inside the authorized writer. Read projections explicitly distinguish empty from retained-unavailable data. Entry IDs shown with keep markers are local identities only; tokens contain no readable target identifiers. A stale or forged marker fails the save atomically.

Add shared list_field_schemas, list_record_occurrences, get_record_occurrence and update_record_occurrence operations when implementing these capabilities. The schema inventory exposes installed codec descriptors/localized metadata, not arbitrary executable definitions. Occurrence operations use explicit series/recurrence identities, bounded windows, allow-listed field updates and expected versions. New actions appear in API and MCP together. Saved profiles and the Form designer reference field UUIDs; field renderers come from the codec registry and standard shadcn composition. Collection rows support Add/Remove/Reorder and normal keyboard/dirty/error guards; partial birthdays use an optional year rather than a false date. Calendar-party controls clearly distinguish external participation from Access principals.

No separate Save per entry or automatic network request to an entered address is needed. Full record Save commits fields/relationships/security together as today; exception editing uses its explicit occurrence operation. A generic read-only summary replaces unsupported table sorting. Permissions shape controls and metadata, but only the common application writer authorizes writes.

8. Implementation sequence and acceptance gates

Section titled “8. Implementation sequence and acceptance gates”

These are architectural dependencies, not a duplicate operational task tracker; OpenProject owns execution/priorities.

  1. Extend typed field ownership/registry, schema constraints, component metadata and field-level journal contexts. Implement contact collections/name parts/partial dates, permissions and retained-reference integrity first. Recreate the disposable local database from the current baseline under ADR 0028 when actual schema work occurs; preserve unrelated/private artifacts.
  2. Locally accepted: Contacts/Distribution-list templates, protected UUID-purpose bindings, derived names, real membership edges, direct Contact professional fields and shared Contacts identity. Generic relationship attributes and both-endpoint field permissions are implemented independently.
  3. The temporal codec, interval validation and lean Task template are implemented locally. Calendar parties and the later Appointment/Vacation templates remain to implement and accept across UI/API/MCP before recurrence editing.
  4. Implement bounded recurrence expansion and versioned occurrence overrides with history, then complete the selected profile’s semantic fixture set. Calendar UI, DAV transport and synchronization jobs remain later work, independent of these gates. User-bound application API/MCP credentials are implemented separately under ADR 0034.

Required focused tests include strict schema rejection; stable entry IDs/order; partial birthdays; scope/hidden retained references; current/recycled data preflight; byte/entry limits; atomic nested-field journals/no-ops; current-rights history redaction; both-side role grants and version races; unlink/reassign history; derived-name leaks; zoned/date-only round trips; gaps/overlaps and DST series; moved/canceled occurrence identity; invalidated overrides; expansion budgets/cursors; stale exception writes and late-journal rollback. Browser checks cover keyboard entry/reorder, error focus, read-only/hidden values, locale input and narrow forms. The contact-field/base-provisioning slice has focused API/MCP tests for schemas, rights, derived-name redaction, cardinality, versions and late-journal rollback. Attribute rights, versions, shared endpoint history, reassignment, recycle/restore and late-journal rollback are now verified locally. Task temporal values have targeted local acceptance; recurrence and later calendar profiles remain pending. Current execution evidence is recorded in the development guide.

  • Separate public Address/Participant objects or a content EAV table would complicate identities, forms and rights; bounded typed JSONB fields preserve the current per-object architecture.
  • Arbitrary JSON blobs would lose contracts and field semantics; the finite codec registry is mandatory.
  • One field per phone slot would impose artificial limits; one unrestricted shared JSON document would prevent useful field rights. Separate typed collections and name-part fields balance these concerns.
  • Separate role lists on Contacts or duplicate role stores for each endpoint would lose edge meaning; one attribute value on the actual edge avoids that divergence.
  • Persisting every recurrence occurrence or replacing a moved occurrence with a new record would lose original identity and create unnecessary content; sparse versioned overrides retain it.

Still to select during the relevant slice: pinned recurrence dependency against fixtures, precise codec string/URI limits, tested query indexes and production-volume targets, full released-upgrade tooling and the complete future exchange envelope/unknown-property retention. Task completion automation and business-specific absence approvals remain domain decisions, not hidden storage behavior. No compatibility layer for previous unreleased schemas is authorized.

The installed codec catalog now contains nine schemas. language_entries accepts the language-tag subset supported by Intl.getCanonicalLocales and normalizes equivalent casing; unsupported tags are rejected, never silently replaced. This is communication metadata, independent of installed interface languages. messenger_entries permits the explicit URI schemes xmpp, sip, sips, im, irc, ircs, matrix, skype, http and https; whitespace, missing addresses, passwords and other schemes are rejected. No resolver, endpoint request or message sending is implemented. Existing stable IDs, context choices, 100-entry bounds, order-derived preferences, whole-field rights and typed history apply to both collections.

The first editable time_zone codec deliberately accepts only named IANA zones/UTC through the existing shared zone validator. vCard text/offset representations require a later declared exchange policy and are not coerced to a named zone. This field never inherits or modifies the viewer/workspace display zone. partial_date is reused for all three personal dates, retaining an optional year with no inferred time or life-state workflow.

Nickname is a lean optional plain text field. Photo/Logo is a fixed-core presentation placeholder with disabled upload controls and no stored media descriptors/URLs or network fetching. Its storage, media-field rights and transfer contract remain future work. Position, Function and Department are now installed on Organization-membership edges for Contact persons. Individual contacts have separate own-business-context fields, restricted to their Contact kind.

Messenger entries compose the same Usage/Type/value row as Telephone entries, with URI as their value label. Both dimensions have optional single selection and configurable translated option identities; API-supplied multi-label values are retained across unrelated edits. Configured Type is a label, not a URI rewrite rule or a connection action. The current catalog supplies Work/Home usage and XMPP, Matrix, Signal, WhatsApp, Microsoft Teams, Telegram, SIP and Other types as editable initial suggestions. Group identity is immutable and inactive selections can be retained but not newly assigned. Sorting still determines preference.

Postal-address controls and server validation share one component schema and its limits. UI errors identify only the offending input, including additional street lines, and invalid drafts cannot be saved. Display and historical formatting include all populated components without changing their order or source values. The generic readable projection does not claim country-specific delivery formatting. A retained formatted label is a display fallback only when components are absent; ordinary editing never computes or writes that label.

The postal Country control offers a searchable localized country catalog. New choices store an English country name as the existing vCard-compatible text component; catalog codes identify choices and search terms, not a new stored address field. Existing text remains unchanged until explicitly replaced or cleared, including values outside the catalog. Interface-language changes and abandoned searches do not write record values.

Relationship-attribute slice — 2026-11-05

Section titled “Relationship-attribute slice — 2026-11-05”

Ordinary sys_fields metadata now has exactly one owner, enforced by a database check and foreign keys. An edge stores UUID-keyed typed values in sys_relationships.values, its own positive version and changed metadata; a database trigger rejects unrelated field keys. General and Primary stay attribute-free. Shared creation/configuration operations accept relationship owners and expose fields through relationship definitions; the standard field dialog is reused in Relationship types. Requiredness, translated options/hints, literal defaults, safe conversions, inactive choices and protected structures use the ordinary field engine. Conditional relationship attributes and uniqueness are not exposed in this first slice. Used custom fields can be deactivated; only unused fields can be deleted.

create_record/update_record.relationshipAttributes carries the relationship definition/version, opposite target, field-ID values and existing edge ID/version. New edges omit the old identity/version. Omitted fields retain values. Typed defaults apply only when both endpoints permit writing; missing required values fail. Supplied unauthorized fields fail even if their submitted value happens to equal the stored value. A save increments each changed endpoint once and each existing changed edge once, with per-field immutable events projected to both endpoint journals. The same endpoint/field conjunction filters history before counts and paging, including recycled read views. Reassignment creates a new edge with fresh values; removed-edge history stays retained. Structured attribute history also carries codec/component details.

Contact forms show Job title, Department and configurable multiple Function choices in Professional details beneath Photo for Contact persons and Individual contacts. They are object-owned fields, not supplied membership attributes. Organization changes/removal preserve them. No second Organization or transitive Identity grouping is introduced. The fictional fixture demonstrates bilingual role choices and different organizational roles. Validation evidence belongs to the development guide.

Professional-details ownership refinement — 2026-11-05

Section titled “Professional-details ownership refinement — 2026-11-05”

Each Contact person has at most one Organization. Job title, Department and Function therefore belong directly to that Contact record; Individual contacts always expose the same fields, independently of a company name. Ordinary Contact field permissions, versions and per-field record history apply. The supplied Organization-membership relationship has no attribute fields. Changing its target leaves professional values unchanged. Generic relationship attributes remain available for actual relationship-specific data with their separate both-endpoint rights and history. Professional details is a fixed Contact-core section beneath Photo, with the same stacked composition in loading and narrow layouts.

Projects, Cases and Tasks now use supplied protected field UUID bindings and editable common starter forms. The installed registry has ten codecs: nine Contact codecs plus temporal. Its strict JSONB union implements the date/instant/zoned forms in section 3, millisecond precision, explicit named zones and DST overlap choice, with gap rejection. The shared Temporal implementation is a declared contracts dependency and drives API/MCP validation and regional UI. Existing Date/Date-time kinds retain their contracts. Optional Task boundaries must have the same form and Due strictly after Start; Project date ranges allow equality.

Task classification Status options carry an optional explicit meaning (open/in_progress/done/canceled), independent of UUID/labels/Type scope. Done requires 100% and a completion instant; other/empty statuses disallow both. The web draft proposes completion/reopening values visibly, keeps the timestamp editable and saves ordinary authorized field changes atomically. API/MCP submit complete values. Classification changes preflight retained records, including recycled Tasks. The profile remains lean: calendar parties, recurrence, Appointments and Vacations are later scope. See PRD section 6.2 for the canonical completion policy.