← Back to Plugin Architecture

5.1 Officers Plugin

The Officers plugin owns tenant-local office configuration and time-bounded officer assignments. It connects organizational reporting, granted roles, and warrant requirements without embedding officer rules in core members or branches.

Data model

Model Responsibility
Department Groups related offices
Office Defines a position, applicable branch types, reporting/deputy relationships, granted role, and warrant requirement
Officer Assigns one member to one office in one branch for an active window and stores the resolved reporting relationship

OfficesTable models ReportsTo, DeputyTo, DirectReports, and Deputies. Setting an office as a deputy also makes it report to that office; setting an independent report clears the deputy relationship. OfficersTable keeps current/upcoming/previous assignments, branch and member associations, effective reporting data, and warrant associations.

Hire, update, and release lifecycle

Hiring is workflow-driven. officers-hire.json responds to Officers.HireRequested; registered workflow actions calculate reporting fields, resolve overlapping assignments, create the officer record, assign roles, request a warrant roster when required, and prepare notifications.

Authorized assignment edits dispatch Officers.AssignmentUpdateRequested to the active, ephemeral officer-assignment-update workflow. Every assignment the user may edit is available, including assignments without an office contact email. The workflow can change that contact email, the deputy description, and the term start/end dates. A non-blank note is mandatory whenever either term date changes, and existing term notes are shown in the edit surface.

Officers.UpdateOfficerAssignment applies the assignment, linked role window, required term note, and any warrant shortening or past-end warrant termination atomically. Moving a non-past term end earlier also shortens the linked role and any longer active current warrant to that end. When that issued warrant now covers the complete revised term, the core manager withdraws obsolete pending coverage and cancels an emptied roster’s approval workflow. The edit path never lengthens an issued warrant. If a corrected end date is already in the past, the assignment keeps that historical end date while its role permission and current or pending warrants are terminated when the correction is recorded; the audit note explains that access and warrant authority remained effective through the recording date.

Warrant extension and member notification are separate workflow nodes so a kingdom can customize those policies without replacing the atomic mutation. When a current warrant ends before the new term and a later warrant period is available, Officers.RequestWarrantExtension submits a request from the following day through the earlier of the revised term end or that period end, without cancelling the current warrant. If the period ends first, the saved update reports the remaining uncovered term as a warning. The core warrant manager reuses an exact pending extension and replaces a pending request for the same warrant identity when its name or dates no longer match; replacement always receives a fresh roster and approvals. The final email uses the officer’s member-account address and the tenant-editable officer-assignment-updated-notification template.

Ephemeral execution is not graph-wide transactional. After Officers.UpdateOfficerAssignment commits, an expected warrant-extension, notification-preparation, or email-queue failure completes as success: true, updated: true, with warning details. The edit controller reports that the assignment was saved, refreshes the grid row, and directs the editor to complete the failed follow-up separately rather than submitting the assignment update again. The default email includes the prepared term dates and omits empty Term note and Warrant status sections.

The controller release path dispatches Officers.Released into officers-release.json. OfficerWorkflowActions::releaseOfficer() mirrors the lifecycle steps directly through the ActiveWindow and warrant managers; the workflow then prepares and sends the release notification. OfficerManagerInterface::release() remains a separate legacy/shared entry point that performs similar lifecycle work, queues mail, and dispatches the trigger. The workflow action does not call that manager method.

Do not add direct “hire” persistence to DefaultOfficerManager or bypass the assignment-update workflow from an edit controller. These versioned definitions are the orchestration source. Because assignment updates are ephemeral, they do not create workflow instances or execution logs; the assignment, term note, role, warrant roster, and email queue records are the durable audit trail. Actions used on an expected error-port path must return a normalized success: false result; thrown exceptions fail the ephemeral run and cannot be routed through that port.

Reporting and recalculation

The office definition describes the intended hierarchy; each officer assignment stores the resolved office/branch reporting coordinates. OfficersTable::findEffectiveReportsTo() can walk both office and branch hierarchies and honor skippable reporting positions. When an office’s report, deputy, or granted-role configuration changes, call recalculateOfficersForOffice() rather than patching assignments individually.

See Office reporting structure for the data semantics.

Integration points

The plugin registers API v1 routes for departments, offices, and rosters. These endpoints still require policy/scoped query handling and operate only within the host tenant.

Primary code and tests

For the service contract, see Officers services.