ADR 0024: Primary levels and relationship endpoint conditions
- Status: Accepted for the local implementation
- Date: 2026-11-03
- Extends: ADR 0019, ADR 0021 and ADR 0023
Decision
Section titled “Decision”The Projects & Tasks module supplies fixed Contact → Project → Case levels. Activation creates protected base Contacts, Projects, Cases and Tasks with standard fields and binds their UUIDs atomically. The levels are not user-configurable; there is no configure_primary_chain API/MCP operation. Module activation uses the schema barrier, common Administrator/Designer authorization, optimistic module version, graph preflight and atomic per-field journals. sys_module_object_bindings stores the supplied definition UUIDs and their fixed role keys independently of display or technical names; Primary context uses these same bindings without a second role-mapping table. A matching custom name causes activation to fail without adoption or partial changes. Lean V1 Projects/Cases/Tasks profiles are implemented; additional profiles and calendar/recurrence extensions remain later work.
Primary is available for all child object types while Projects & Tasks is enabled. Contact is a root, a Project may belong to a Contact, a Case may belong to a Contact or Project, and any other object may belong to one of these levels. Levels in individual record paths may be absent unless required by the source object. Direct-parent uniqueness and cycle checks remain authoritative. Candidate filtering applies fixed role rules before counts/pagination; all writes use the same rule. Disabling the module requires removing existing Primary links and clearing object-level requiredness, retains definitions/records/bindings and removes Primary fields/context. Re-enabling reuses the same UUIDs. Bound objects remain protected against deactivation/deletion.
Record Details shows the available levels as a single chain of single-value InputGroups. Earlier levels are derived from the deepest selected parent; all available levels expose Choose/Change, Open and Clear controls with normal read-only/unavailable disabling. Changing a level replaces the draft parent and clears later selections. Clearing a level falls back to its nearest visible earlier level, or removes the association when none exists. Only the direct relationship of the current record is persisted; no parent record is changed or deleted. Selection is initially scoped to the displayed value of that level rather than the deepest parent. The picker uses the nearest selected earlier level as its primaryAncestor context: Projects under Contact, Cases under Project, or Cases under Contact when Project is absent. search_relationship_candidates validates this optional filter against the active Primary relationship, fixed level order, workspace and current ancestor access. It follows only readable active paths and applies the restriction before counts/pagination. The filter is available through the common API/MCP contract; it does not create independently stored ancestor values. get_primary_chain_context resolves persisted or proposed direct parents through the shared content authorization and real graph. It does not write journals or copy ancestor values. Explicit custom relationships use one labeled field per maximum-one side and one table per multi-target side under ADR 0019. General links are edited from the record-header action; General links and subordinate Primary records are listed in the Dossier.
Requiredness is stored once on each workspace-scoped object definition as primary_requirements (associationRequired, requiredLevels). Fixed role identities/order are unchanged. The shared configure_object_settings operation checks the object version, validates permitted earlier roles, preflights all retained records (including recycled records) under the existing model barrier and journals each changed property atomically. Every relationship writer validates the final graph against active requirements, including descendants of a changed ancestor; no direct edge, copied ancestor fields or per-record exceptions are added. Creation/duplication/linked creation and restoration use these same rules. A failed check rolls back all associated contents, versions and journals. Requiredness traverses retained composite object/record identities rather than actor-visible context; it neither opens hidden ancestors nor changes lifecycle behavior. Recycling retains edges and thus the structural path. Configuration errors disclose no hidden record identities/counts. The form relationship metadata marks a required Primary element so the designer/effective form cannot hide it. All requirements initially remain optional.
Custom relationship definitions may constrain each endpoint using the same unconditional single-choice controller/option identities as field applicability. Conditions belong to the corresponding object pair; symmetric endpoints require equivalent conditions. The common API/MCP validates definitions, preflights existing edges and filters candidates before pagination. Final record writes validate retained edges even when only a discriminator field changes. Incompatible links must be explicitly removed; values and connections are never silently cleared. Controller fields/options cannot be removed or converted incompatibly while referenced.
The definition editor reuses one applicability component for fields and relationship endpoints, with unique control IDs. The Contact fixture configures Organization → Contact person with a maximum of one Organization per Contact person. Identities is a symmetric multiple relationship among Contact persons and Individual contacts. It is a direct list on Contacts, with no Identity object type and no inferred transitive grouping. These are regular configured rules, not runtime branches based on object names. Contacts provisioning, supplied-definition ownership, derived Keywords and kind-dependent names are implemented under ADRs 0025/0032.
Installation and identity handling
Section titled “Installation and identity handling”Supplied object UUIDs are stored in sys_module_object_bindings. Primary context reads its fixed Contact/Project/Case keys from that same table; no second role mapping is maintained. Fresh installation is defined in ADR 0028.
Verification
Section titled “Verification”Integration tests cover requiredness/preflight and concurrent writes, ancestor impacts, creation/duplication/linked creation, recycle/restore, per-property history and journal rollback, ordinary User/Designer boundaries, API/MCP context parity, inherited and skipped levels, identity spelling, candidate restrictions, stale module versions, fixed level identities, activation conflicts, preflight rollback, object lifecycle and content access. Endpoint tests cover conditional candidates, incompatible target rejection, scalar-change rollback, controller protection and symmetric Identities links. Browser acceptance covers derived drafts, Reset, correct Contact-kind visibility, selection restrictions, configuration guards, keyboard interaction and narrow layouts. The current unreleased installation follows ADR 0028; disposable instances are rebuilt without earlier-development-schema adoption. Ordinary relationship changes preserve unrelated records, edges and original content journals. No publication or release is implied.