Skip to content

0017 — Small Groups & Ministries management ​

Status: Accepted (2026-06-18 — owner accepted during ADR review)

Date: 2026-06-17

ADO work item: AB#3092

Deciders: Kristopher Turner (platform owner)


Context ​

Heritage Virginia organizes members around two overlapping structures: ministries (standing, leader-driven programs such as worship, youth, or outreach) and small groups (recurring fellowship circles, often home-based or class-based). Both structures share a common shape: a named group, a defined membership roster, and one or more designated leaders whose authority is explicitly bounded to that group.

ADR 0006 (two-plane RBAC) established five canonical platform roles, two of which — Ministry Leader and Small Group Leader — exist precisely to represent this kind of scoped authority. That ADR names the roles but intentionally defers the definition of what a "group" or "ministry" is as a data entity to a feature ADR. This is that ADR.

ADR 0008 (platform composition) establishes that every feature is a slice of the API-first platform core. Group and ministry data must therefore live in Azure SQL (ADR 0004) behind the apps/api backend, accessible to clients only through the typed api-client SDK — the same rule that applies to every other domain.

The urgency of this decision is not incidental: groups and ministries are a platform-level scoping anchor, not a self-contained feature. The Calendar feature (ADR 0011), Announcements feature (ADR 0012), and the Messaging feature all need a shared, authoritative group-membership relationship to scope visibility, targeting, and delivery. If groups are not modeled as first-class entities before those features are built, each feature will invent its own group concept, producing type drift, divergent rosters, and inconsistent RBAC enforcement. The platform strategy (Epic AB#3076, Feature AB#3092) calls this out explicitly as a prerequisite for those downstream features.

This is a planning/architecture decision. It introduces no code; it defines the data model and authority boundaries that Phase 1 implementation and every dependent feature must honor.

Decision ​

We will model small groups and ministries as first-class platform entities — stored in Azure SQL behind the apps/api backend, each with a stable identity, a membership roster, and one or more assigned leaders — so that the Ministry Leader and Small Group Leader roles from ADR 0006 resolve to concrete group-scoped authority, and so that Calendar, Announcements, and Messaging can scope their behavior against a single, authoritative group-membership relationship rather than maintaining their own.

Data shape ​

The model introduces three tables in the existing Azure SQL database:

text
Groups
  id            (PK)
  type          ENUM('ministry', 'small_group')
  name
  description
  is_active
  created_at
  updated_at

GroupMemberships
  id            (PK)
  group_id      FK → Groups.id
  user_id       FK → Users.id
  role          ENUM('leader', 'member')
  joined_at

GroupInvitations          (optional Phase 2 extension)
  id, group_id, invited_by, email_or_user_id, status, expires_at

A single Groups table with a type discriminator unifies ministries and small groups. The shared shape is intentional: the platform treats both as "a named group of members with designated leaders." Feature-specific metadata (e.g., meeting location, ministry category) is an extension for Phase 2.

GroupMemberships.role carries the leader / member distinction; the platform RBAC layer maps a leader membership to the Ministry Leader or Small Group Leader role claim (per ADR 0006) scoped to that group_id. A user may hold leader status in one group and member status in another simultaneously.

API surface ​

The feature slice lives at apps/api/src/features/groups/. It exposes:

  • GET /groups — paginated list; scoped by caller's memberships unless the caller holds an admin or minister role.
  • GET /groups/:id — group detail including roster (leader-or-admin only for full roster; members see summary).
  • POST /groups — create a group (admin or minister only).
  • PATCH /groups/:id — update name, description, active status (admin, minister, or the group's own leader).
  • GET /groups/:id/members — roster (leader-or-admin only).
  • POST /groups/:id/members — add a member (leader-or-admin).
  • DELETE /groups/:id/members/:userId — remove a member (leader-or-admin).
  • PATCH /groups/:id/members/:userId — promote/demote leader flag (admin or minister only, to prevent self-elevation).

All endpoints enforce server-side RBAC per ADR 0006. No client makes authorization decisions of record.

RBAC scoping contract ​

This feature establishes the group-scoped authority contract other features rely on:

  • A user with GroupMemberships.role = 'leader' for a given group_id is authorized by the API to manage that group's roster, post group-scoped announcements (ADR 0012), and create group-scoped calendar events (ADR 0011).
  • The Calendar and Announcements features query GroupMemberships to resolve audience membership at render/delivery time — they do not maintain their own group lists.
  • Messaging targeting (a later feature) follows the same pattern: group_id as a targeting selector resolved server-side against GroupMemberships.

This contract is the primary reason groups must be first-class entities rather than tags or per-feature lists.

Alternatives considered ​

OptionProsConsWhy not chosen
First-class Groups entity, shared roster (chosen)Single source of truth for membership; RBAC scoping is clean and consistent; Calendar/Announcements/Messaging all resolve against one table; aligns with ADR 0006 rolesRequires the Groups feature to ship before dependent features can scope against it— chosen
Ad-hoc tags only (no roster, no leader authority)Zero schema work; ultra-flexible groupingNo clean leader authority boundary; RBAC cannot scope to a tag; no canonical membership for Calendar/Announcements to target; rejected at ADR 0006 when the Leader roles were definedRejected — fundamentally incompatible with server-side RBAC and the scoping requirements of downstream features
Third-party church-management SaaS for groups (e.g. Church Community Builder, Planning Center)Mature group/roster UX out of the boxBreaks the single-platform model; member data leaves the platform; RBAC cannot enforce group scope across an external boundary; adds cost; violates the "closed community, one platform" principle from the platform strategyRejected — incompatible with ADR 0008 (API-first, data in Azure SQL) and the cost/control constraints
Per-feature duplicate group lists (each feature maintains its own concept of "group")Features ship independently without a shared dependencyRosters drift; RBAC must be re-implemented per feature; no canonical truth for who belongs to a group; produces exactly the fragmentation this decision is meant to preventRejected — contradicts the platform composition rules in ADR 0008 and the scoping anchor requirement

Consequences ​

Positive ​

  • The Ministry Leader and Small Group Leader roles from ADR 0006 resolve to a concrete, enforceable boundary: authority is scoped to the specific group_id for which the user holds a leader membership.
  • Calendar, Announcements, and Messaging can scope visibility and targeting against GroupMemberships without maintaining their own group concept — one table is the answer for all of them.
  • A minister or admin can see the full cross-group membership picture; leaders see only their own roster. The server enforces this without client cooperation.
  • The type discriminator keeps the schema minimal now while leaving room to diverge ministries and small groups in Phase 2 if their metadata requirements genuinely differ.

Negative / trade-offs ​

  • The Groups feature becomes a build prerequisite for Calendar, Announcements, and Messaging. Any delay in delivering the Groups API delays those features' audience-scoping capability. Mitigation: keep the initial Groups API minimal (the eight endpoints above); defer group invitations and rich metadata to Phase 2.
  • The leader / member role enum in GroupMemberships is deliberately narrow. If a ministry later needs sub-roles (e.g., "section leader" within a worship ministry), the schema must be extended. Mitigation: document the extension point now; do not add sub-roles speculatively.
  • Leaders cannot promote other members to leader status without admin or minister involvement (by design, to prevent self-elevation). This adds friction for large groups. Mitigation: the minister or admin delegation path is straightforward; the security benefit outweighs the friction.

Risks ​

  • Orphaned memberships — if a Users record is deactivated or a family group is dissolved (ADR 0007), GroupMemberships rows may reference inactive users. Mitigation: the is_active flag on Groups and a soft-delete pattern on GroupMemberships (with a left_at timestamp) allow the API to filter without data loss; enforce via foreign-key constraints and API-layer validation.
  • Group proliferation — without governance, the group list becomes unwieldy. Mitigation: only admins and ministers can create groups; the is_active flag allows archiving without deletion; an admin roster view is part of the initial API surface.
  • Dependency inversion — Calendar or Announcements might ship before Groups and work around the missing dependency. Mitigation: the per-feature ADRs for Calendar (0011) and Announcements (0012) explicitly reference this ADR as a prerequisite; the platform strategy build order enforces Groups before those features.

References ​

Heritage Community Hub — Internal. Access restricted via Cloudflare Access + Entra ID.