ADR 0027: Ownership, graded membership and foreign access
- Status: Accepted; implemented in the local prototype
- Date: 2026-11-04
- Complements: ADR 0026
Decision
Section titled “Decision”Implement PRD section 4 in the shared contracts/application layer. Keep object/field administrative ceilings, direct-user overrides and the global Administrator exception. Add graded direct/nested memberships, object creation capability, ownership, public fallback, privacy/foreign maxima, record field restrictions and explicit requester-to-owner foreign grants.
Store owners, ownership levels and the complete record policy once in sys_record_security, referencing the record registry. The protected virtual access_principals field is derived from that policy; generic references remain separate. Memberships retain direct user identities with grades; a separate workspace-scoped edge table stores nested groups. Foreign rules and per-user inheritance/delegation settings are versioned per workspace. Changes and property journals share the existing transaction barriers.
The pure resolver uses finite monotone relaxation over principal/rank/confidentiality states. Membership paths and foreign paths are bounded by their weakest edge. Upward membership resolves requester groups; downward expansion happens only for an explicitly targeted owner group. This prevents automatic coworker access. Direct ownership precedes group ownership and public fallback; any ownership precedes foreign access. Intermediate direct ownership limits forwarded paths. Personal blocks foreign access; Confidential requires permission on the complete path. Group cycles are rejected; foreign cycles terminate without manufacturing grants.
Transaction-local policies load security metadata, compute authorized record identities, and apply SQL predicates before search/count/paging. Per-record field restrictions apply in projections, search, sorting, conditions and historical views. Record security edits use the record version and normal Full/delegation checks; no separate unjournaled mutation route exists. Effective-right explanations use the same resolver.
The current installation creates the model directly under ADR 0028. Development instances are rebuilt; no transfer from an earlier rights model or duplicate ownership representation is supported. Enabling sensitivity is a current business operation: unrestricted rows receive explicit public policies, while re-enabling retains existing policies. Missing sensitive policies fail closed and violate deferred integrity.
Scope and verification
Section titled “Scope and verification”Calendar views, busy-time projection/time-only writes, mail delivery, creation templates and per-member task completion remain separate capabilities. Their reserved rights never authorize ordinary content reads below Read. The verification guide identifies integration/web tests for shared API/MCP enforcement, current-session revocation, policy-journal rollback and current initialization, together with browser acceptance procedures and public CI evidence. This is not release or production acceptance.
Administration and limits
Section titled “Administration and limits”Administrators configure object/field ceilings and a separate Create flag. Missing user Create inherits group Create grants; an explicit setting wins. Create is explicit and never inferred from Edit. Duplication requires Duplicate plus Create, copies only readable initially writable values and runs ordinary validation. The web flow prepares an unsaved creation draft, then rechecks the source version and both rights when saving; preparation consumes no identity, number or journal event. Ownership cannot create unrelated records. Availability is never ordinary Read; scheduling does not authorize general record updates.
Group editors configure users and child groups once, with each membership grade. Foreign settings expose requester and owner directions over one rule store, per-object overrides and inheritance controls. Optional Administrator delegation exposes only rules targeting the user’s own identity and bounds all levels by that user’s object ceiling; filtered responses also apply on no-ops. Record dialogs expose graded owners, current-member expansion, public confirmation, privacy/maxima, field restrictions and winning paths. All editors use shared Select/Field/Dialog/table primitives with guarded drafts and save/reset actions. create_record.security can initialize a policy for a Full-authorized creator; the record form uses update_record.security for an atomic content/relationship/security edit. configure_record_security remains the dedicated API/MCP operation; both use the same policy writer, validation and journal path. Clearing owners into public mode requires explicit confirmation.
Security editors retain the version on which a draft was started; background refetches never refresh that version under unsaved changes. Reset explicitly adopts the latest fetched snapshot. Record security returns current per-record field capabilities and readable principal identities, including retained inactive entries; selectors cannot offer explicit field grants above those capabilities. Unchanged inactive owners/field exceptions remain retainable and removable. New inactive group memberships and foreign rules are rejected. Deleted-object foreign overrides may be retained or removed without blocking unrelated settings. Workspace-member removal journals the separate Create grant as well as object/field and membership changes.
Foreign administration works independently of object selection, including workspaces without object types. Both directions share a searchable, paged rule table; object reset removes only that override, while removal in the default scope deletes the complete rule. User inheritance/delegation settings are a separate section. Delegated users see only incoming own-record rules. Group membership has one graded editor for direct users and nested groups; new UI-created groups start empty, and read-only viewers see their actual membership list. Record dialogs separate current effective access, owners/public mode, privacy/foreign limits and per-principal field restrictions. Public widening alone requires the explicit public confirmation. All dialogs use shared controls, associated labels, common save shortcuts, pending/dirty close guards and responsive tables.
The Form designer controls the group, order and half/full width of the existing virtual Access principals field without changing its security behavior. Record Details displays Access principals as a protected system field with a searchable multi-selection and a Details action. Existing ownership grades are retained; new principals start at Full, still bounded by object/field ceilings. Removing the last owner never silently enables public access. Workspace-public mode and widening require explicit selection/confirmation. The detail dialog has Owners, Foreign access and Field rights tabs. Apply transfers its local draft into the record form; closing/discarding the dialog retains any earlier form draft. Only the record Save persists these edits together. Reset restores contents, relationships and security. New sensitive records show the same field; changing their initial policy requires Full object access. A combined save increments the source record once and journals scalar, edge and security changes with one change ID; failures roll back the entire transaction, and loss of Read returns a redacted result and exits the form.
sys_record_security is separate policy metadata, not a duplicate content table. Dynamic records contain business columns; policy identities are not duplicated there. Object records and security metadata are currently resolved transaction-locally in memory, with authorized IDs/field CASE projections supplied to SQL before filtering/count/paging. Identical record-level explanations are memoized within an object/transaction while each row keeps its field policy. The local quality profile measures 10,250 records and 20 nested groups against explicit p95 targets. A SQL/materialized policy strategy remains an option for larger production sizing. No global policy cache outlives a transaction, and relationships never confer rights.
Security history uses translated property names, rights and privacy values in both record and instance Journal tables/details. Current names supplement only principals referenced in already-authorized events; they are explicitly current context, not historical identity snapshots. Original old/new values remain immutable and inspectable in technical details. The virtual Access principals field is displayable but cannot be sorted as a business column.
Activities are always user-sensitive. Current activity identities are the module-bound Project, Case and Task objects; future activity profiles must join the same classification. Provisioning creates their Access principals field and sensitivity policy. The shared permissions writer rejects disabling activity sensitivity, including confirmed widening and combined object-settings updates; the UI displays the setting as fixed. Classification uses module bindings, never labels or technical names.
Activity assignment uses Access principals as its single source: the listed users/groups participate in performing the activity and their ownership controls access. Supplied Tasks have no separate assignment field, column, binding or writer. Responsible remains a coordinating reference. Foreign access never changes the assignee list. Notifications remain future work and will use these participants as their default audience.