Appearance
0023 — Communications authoring & approval workflow ​
Status: Accepted (2026-06-18 — owner approved the sixth comms_author role; ADR 0006 role table updated accordingly)
Date: 2026-06-18
ADO work item: AB#3089
Deciders: Kristopher Turner (platform owner)
Context ​
One-way broadcast — governing constraint ​
The Messages feature (and the overlapping Announcements feature) is a one-way broadcast system, not a two-way chat or messaging product. There is no member-to-member reply, thread, or direct message capability — this is an explicit, locked platform constraint (see ADR 0013 Alternatives, "Two-way chat / direct messages between members": rejected). Broadcast communications flow in one direction: from an authorized author, through a minister's approval queue, out to members via SMS, email, in-app notification, and push notification.
Authoring rights are held by a defined set of roles:
- Small group ministers / group leaders — post updates, schedules, and information to their group's members
- Homeschool teachers and co-op coordinators — post to homeschool families enrolled in their program
- Ministers and ministry leaders — post to their ministry audience or the whole community
- Event organizers (
comms_authorrole delegated for an event scope) — post to registered event participants
Members receive messages. Members do not reply, react, or initiate broadcasts.
Note — potential future convergence: The platform currently carries two related broadcast surfaces: Announcements (ADR 0012, church-wide from leadership) and Messages (targeted broadcasts from small group leaders, teachers, and event organizers). These may converge into a single unified broadcast system in a future revision. The authoring and approval workflow defined in this ADR is designed to be content-type-agnostic and would apply without change to a merged system.
This ADR covers the authoring and approval workflow layer of the three-layer communications model:
| Layer | ADR | Concern |
|---|---|---|
| Content artifact | ADR 0012 | Announcements data model — fields, audience scope, priority, status values, receipts |
| Authoring & approval workflow | 0023 (this ADR) | Who drafts vs who approves; the comms_author role; queue lifecycle; self-approve prohibition |
| Delivery transport | ADR 0013 | Channel adapters (SMS, email, in-app, push), fan-out, retry, member preferences |
ADR 0012 defines the Announcements table schema, the draft → pending_approval → approved | rejected status lifecycle, and the handoff to the platform notifications transport (ADR 0013). What ADR 0012 leaves underspecified is who drafts versus who approves, and whether those can be the same person for all use cases the ministry requires.
Heritage Virginia delegates writing tasks. A minister may ask a volunteer or a ministry coordinator to draft announcements for the whole congregation, for a specific small group, or for a specific ministry — without granting that person the authority to publish unilaterally. The real-world workflow has two distinct actors:
- An author — tasked to draft; has no right to approve their own content.
- An approver — a minister or community leader who reviews the queue and approves or rejects before anything reaches members.
The five canonical RBAC roles in ADR 0006 (admin, ministry_leader, group_leader, member, visitor) do not accommodate this separation cleanly. Every role that can currently write announcements (admin, ministry_leader, group_leader) also carries implicit approval authority under the ADR 0012 model. There is no way, within the current five-role set, to grant someone authoring access for community-wide or cross-ministry communications without also granting them approval authority. This creates two problems:
- Self-approval risk. A small group leader who is tasked to draft a community-wide announcement can submit and approve it themselves — removing the oversight the minister intends.
- Delegation without escalation. There is no clean way to say "this person can draft for any audience but cannot publish without a minister's sign-off."
ADR 0008 establishes that every surface hits the same API endpoints; there is no separate authoring system. Drafting on a phone and approving on the web must follow the same state machine, enforced server-side. ADR 0005 requires that every author/approve/publish action is logged in the AuditLog table.
This ADR proposes the workflow model that satisfies real-world ministry delegation, closes the self-approval gap in ADR 0012, and records a proposed extension to ADR 0006.
Decision ​
We will introduce a sixth application RBAC role, Communications Author (slug:
comms_author), whose scope is strictly write-without-approve: a Communications Author may create and editAnnouncementsdrafts for any audience they are explicitly assigned (whole community, a specific ministry, a specific small group), may submit drafts to the approval queue, but cannot approve, reject, or publish any announcement — including their own. Approval authority remains exclusively withministry_leaderandadminroles, enforced server-side per ADR 0006. Every state transition (draft → pending_approval → approved | rejected → published) is logged in the platform audit log per ADR 0005 and is reachable from any platform surface (mobile or web) through the typedapi-clientSDK per ADR 0008.
The two-actor separation mapped onto the ADR 0012 lifecycle ​
text
AUTHOR (comms_author role) APPROVER (ministry_leader | admin role)
───────────────────────── ───────────────────────────────────────
Creates draft ← queue visible in Admin / Ministry portal
Sets title, body, audience, Reviews content + audience scope
priority, scheduled_at Approves → status: approved
Submits for approval → notifications transport fans out
→ status: pending_approval → AnnouncementReceipts written
Rejects → status: rejected
→ author notified
Revises and resubmits if rejected
→ status: draft → pending_approvalAudience scope columns (audience_role, audience_group_id) on the Announcements table (ADR 0012) determine who can receive a given announcement. The API additionally gates what audiences a comms_author may write for:
| comms_author assignment | May author announcements targeting |
|---|---|
audience = community | All members (audience_role = NULL, audience_group_id = NULL) |
audience = ministry:<id> | That ministry's role scope |
audience = group:<id> | That specific FamilyGroups entry |
An admin sets these assignments on the comms_author row; the API rejects draft creation that exceeds the author's assigned audience scope. This assignment is stored in a lightweight UserCommunicationsScope join table (one row per author per permitted audience).
State machine (extends ADR 0012) ​
text
draft ──[author submits]──► pending_approval
▲ │
│ [approver rejects] │ [approver approves]
└───────────────────────── ▼
approved ──[scheduled_at or immediate]──► published
│
▼
notifications transport
(ADR 0013)expired is a terminal state reachable from approved/published when expires_at passes (unchanged from ADR 0012).
API-first: cross-surface by design ​
Because every surface calls the same API endpoints (ADR 0008), the authoring and approval workflow is surface-agnostic:
- A
comms_authordrafts on their phone viaapps/mobile→api-client→POST /announcements(status:draft). - A minister opens
apps/web(or the admin portal) →api-client→GET /announcements?status=pending_approval→ reviews →PATCH /announcements/:id/approve. - The API validates the role on every call; the surface receives only the response.
No announcement logic lives in the client. The queue is the same data regardless of which surface the approver uses.
Audit ​
Every state transition writes an AuditLog row per ADR 0005:
| Event | actor_user_id | target_type | detail |
|---|---|---|---|
announcement.draft_created | author | Announcement | audience scope |
announcement.submitted | author | Announcement | from draft |
announcement.approved | approver | Announcement | approved_by_id set |
announcement.rejected | approver | Announcement | rejection reason |
announcement.published | system/approver | Announcement | fan-out initiated |
author_user_id and approved_by_id on the Announcements row (defined in ADR 0012) are always distinct when a comms_author is the drafter — enforced by the API: the endpoint that transitions pending_approval → approved rejects the request if the caller's user_id matches author_user_id.
Relationship to ADR 0006 ​
This ADR proposes an extension to ADR 0006. The comms_author slug is a sixth Plane-2 role. On acceptance of this ADR, ADR 0006 should be updated to add the row to the canonical role table, or a superseding ADR should be issued. The existing five roles and their enforcement rules are unchanged; comms_author adds a write-without-approve permission tier that did not previously exist.
Alternatives considered ​
| Option | Pros | Cons | Why not chosen |
|---|---|---|---|
Sixth role: comms_author — write without approve (chosen) | Clean author ≠ approver separation; explicit in the role model; audience scope is assignable per author; no existing role semantics changed | Extends ADR 0006 (requires update or supersession); adds a UserCommunicationsScope join table; adds one role to every authorization check path | — chosen |
Reuse existing roles — group_leader authors within their group; ministry_leader authors for their ministry; both self-approve | No schema or role model change | Loses the author ≠ approver guarantee entirely; a group leader or ministry leader can publish without review; minister loses oversight of community-wide communications authored by delegated writers; does not model the "write for whole community, cannot approve" use case | Rejected — self-approval is the specific failure mode this ADR exists to prevent |
| Separate CMS / communications tool (e.g. Mailchimp, a headless CMS) | Off-the-shelf authoring UX; no in-house queue to build | Member data leaves the platform; RBAC audience scoping cannot be enforced externally; no in-app delivery or receipt tracking; recurring cost; contradicts the one-platform principle; approval workflow would need to be rebuilt or approximated in a third-party tool | Rejected — cost, data residency, and platform composition rules (ADR 0008) all prohibit it |
Consequences ​
Positive ​
- The minister retains full oversight: no announcement reaches members without an
adminorministry_leaderexplicitly approving it, regardless of who drafted it. - Authors can work from any surface at any time; the queue appears to approvers on any surface — the API-first design (ADR 0008) makes this free.
- The
comms_authorrole is additive. No existing role permissions change; existing users are unaffected. - Delegating writing without delegating publishing authority is a real need for growing ministries; this model supports it without privileged workarounds.
- Every action is auditable (ADR 0005);
author_user_id≠approved_by_idis enforced at the API layer, not just by convention. - Moderation collapses into the approval workflow (unchanged from ADR 0012): only approved content ever reaches members; no separate moderation pipeline is needed.
Negative / trade-offs ​
- ADR 0006 must be updated (or a superseding ADR issued) to record the sixth role. This is coordination overhead, not implementation complexity.
- A new join table (
UserCommunicationsScope) is required to store per-author audience assignments. It is a small table (one row per author per permitted scope) but it is an additional schema object to migrate. - Admin UI must expose controls for assigning audience scope to
comms_authorusers. This is in scope for the Admin & Ministry Portal (ADR 0021) but adds a configuration screen that would not be needed if the simpler "reuse existing roles" approach were taken. - The
approved_by_id ≠ author_user_idserver-side check must be added to the approval endpoint; without it the constraint is advisory only.
Risks ​
- Scope misconfiguration — an admin assigns a
comms_authora broader audience scope than intended (e.g. community-wide when only a group was meant). Mitigation: the admin UI displays the effective audience at assignment time; the approval step provides a second review of the targeted audience before publish. - Approval queue neglect —
pending_approvalannouncements age without review, causing time-sensitive content to miss its window. Mitigation: the platform notifications transport (ADR 0013) notifies approvers when a new submission enters the queue; ascheduled_atthat lapses while still inpending_approvalis flagged as overdue in the admin portal. - Role proliferation — adding a sixth role increases the surface area of every role-check in the API. Mitigation: the
comms_authorrole is narrowly scoped to the Announcements feature only; it carries no permissions in any other feature area. - ADR 0006 drift — if ADR 0006 is not updated promptly after this ADR is accepted, the canonical role table becomes stale. Mitigation: acceptance of this ADR creates an ADO task to update ADR 0006 before any implementation begins.
References ​
- ADR 0005 — Observability model — audit log (Layer 1); every state transition must produce an
AuditLogrow. - ADR 0006 — Two-plane RBAC — this ADR proposes adding a sixth Plane-2 role (
comms_author); ADR 0006 must be updated on acceptance. - ADR 0008 — Platform composition — API-first;
api-clientis the only entry point; no surface holds authoring or approval logic. - ADR 0012 — Announcements (one-way broadcast) — the content artifact layer: defines the
Announcementstable schema,AnnouncementReceipts, valid status values, and the one-way constraint. This ADR (0023) provides the workflow and roles that govern theauthor_user_id/approved_by_idcolumns and status transitions defined there. - ADR 0013 — Messaging & Notifications (transport) — the delivery layer: channel adapters, fan-out, retry, and member preferences. This ADR (0023) hands approved content to 0013; it does not own any channel adapter.
- ADR 0021 — Admin & Ministry Portal — the surface where approvers manage the queue and where admins assign
comms_authoraudience scopes. - Platform strategy — Messaging: one-way broadcast — governing constraint that all community messaging is authored, approved, and one-way.
- ADO: Feature AB#3089 (Communications authoring & approval workflow).