Skip to content

0008 — Platform composition ​

Status: Accepted — amended by ADR 0027 (design-system toolchain) and ADR 0028 (hostname-routed web shell)

Date: 2026-06-17

ADO work item: AB#3155

Deciders: Kristopher Turner (platform owner)


Context ​

ADRs 0001–0007 each lock a single cross-cutting decision: the monorepo structure (0001), mobile tech (0002), authentication (0003), the cloud/hosting stack and CI/CD (0004), observability (0005), RBAC (0006), and the account/family-group identity model (0007). What none of them states is how these pieces fit together to form "the platform" — the boundary every surface (web, iOS, Android) and every feature (Sermons & Music Hub, Calendar, Announcements, Messaging, Homeschool, Marketplace, …) depends on.

The platform strategy is explicit that the platform is the most important deliverable and is not a thin foundation — it owns identity, the member/family domain, RBAC, the notification transport, audit logging, the shared SDK, the design system, the web shell, and the operational/security baseline. It is built first (Phase 1, Epic AB#3074); every other Epic is a client of it.

Without a composition ADR, the risk is that feature Epics reach for infrastructure ad hoc — a feature talks to the database directly, re-implements authorization on the client, or invents its own notification path — eroding the API-first guarantee and the "decide before building" gate. This ADR records the architecture-level contract between the platform core and everything that consumes it.

This is a planning/architecture decision. It introduces no code; it defines the boundaries that Phase 1 implementation (AB#3074) and every later feature Epic must honor.

Decision ​

We will compose the platform as a single API-first core with seven bounded responsibilities — (1) the backend API framework, (2) identity & access (Clerk auth + signup/approval + server-side RBAC), (3) the member & family domain, (4) shared services (notifications transport, audit log, media storage), (5) the shared package layer (shared-types, api-client, shared-utils, shared-config, ui), (6) the core web shell, and (7) the cross-cutting operational/security baseline. Every surface and every feature consumes the platform only through the typed api-client SDK over the backend API; no client holds business logic, talks to the database directly, or self-authorizes.

Composition map ​

text
        ┌──────────────── Surfaces (clients) ─────────────────┐
        │  apps/web (React)   apps/mobile (RN+Expo)           │
        └───────────────┬─────────────────┬───────────────────┘
                        │  packages/api-client (typed SDK)     │  ← only entry point
                        │  packages/shared-types (contracts)   │
        ┌───────────────▼─────────────────▼───────────────────┐
        │              Platform core — apps/api                │
        │  1 API framework (validation, errors, rate limit)    │
        │  2 Identity & access — Clerk verify → Users, RBAC    │  (ADR 0003, 0006)
        │  3 Member & family domain — Users, FamilyGroups,     │  (ADR 0007)
        │     approval workflow, profiles, child sub-accounts  │
        │  4 Shared services — notifications transport,        │
        │     audit log, Blob media storage                    │  (ADR 0005)
        └───────────────┬──────────────────────────────────────┘

        ┌───────────────▼──────────────────────────────────────┐
        │  Cloud baseline — Azure Functions + SQL Serverless +  │  (ADR 0004)
        │  Blob + Key Vault; Bicep IaC; GitHub Actions CI/CD;   │
        │  four-layer observability; cost + security guardrails │  (ADR 0005)
        └───────────────────────────────────────────────────────┘

The seven platform responsibilities ​

  1. Backend API framework (apps/api) — the single source of truth for all business logic, data access, and rules. Provides request validation, error handling, the OWASP API-security baseline (server-side validation, rate limiting, CORS, output encoding), and the convention every feature endpoint follows. Features are added as apps/api/src/features/<feature>/ slices (decision 8) — they extend the platform; they do not bypass it.
  2. Identity & access — Clerk verifies the Apple/Google social identity (ADR 0003); the API maps the provider sub claim to a platform Users record; RBAC (five canonical roles) is enforced server-side (ADR 0006). Signups run through the minister approval workflow (ADR 0007). Clients receive only short-lived tokens and never make authorization decisions of record.
  3. Member & family domain — the Users table (single table, nullable email), FamilyGroups, the family portal, member profiles, parent-managed child sub-accounts, and the member directory (ADR 0007). This domain is platform-owned because nearly every feature reads from it.
  4. Shared platform services — the notification transport (email/SMS/in-app/push channels; the provider is decided in research, S4), audit logging (the AuditLog table feeding observability layer 1, ADR 0005), and the media-storage primitives (Blob) the Sermons & Music Hub builds on. Features call these services; they do not re-implement them.
  5. Shared package layer (packages/*) — shared-types is the single definition of every API contract; api-client is the only sanctioned way a surface calls the platform; shared-utils (validation, formatting, RBAC helpers), shared-config, and ui (the design system; Claude Design is the sanctioned toolchain per ADR 0027) complete the layer.
  6. Core web shell (apps/web) — navigation, auth-gated routing, layout, and account/profile screens. Feature web UIs mount inside this shell. The shell is hostname-routed: each major portal is served on its own subdomain of heritageva.app; window.location.hostname determines which portal section mounts inside the single apps/web bundle. See ADR 0028 for the subdomain map and TLS strategy.
  7. Cross-cutting operational & security baseline — Bicep IaC for dev/test/prod (ADR 0004), GitHub Actions CI/CD, Key Vault for all secrets, the four-layer observability baseline (ADR 0005), cost governance (budget alerts + documented free-tier ceilings), and the data-protection/COPPA foundations (security audit S1–S8).

Composition rules (the contract features must honor) &ZeroWidthSpace;

  • API-first, no exceptions. A surface or feature reaches data and rules only through api-client over the backend API. No direct database access from any client; no business logic in web/mobile.
  • Contracts live in shared-types. Any new endpoint defines its request/response types there first; web, mobile, and API all import the same definition.
  • Authorization is server-side. A client may hint the UI from a role claim, but the API is the authority on every protected action (ADR 0006).
  • Features extend, never fork, the platform. New capability = a feature slice that adds endpoints and uses the platform's auth, RBAC, notifications, audit, and storage services. Promotion to packages/features/* happens only when a slice is genuinely shared web↔mobile (decision 8).
  • Each major feature gets its own ADR (the per-feature ADR plan, AB#3154) describing how it sits on this composition — starting with the Sermons & Music Hub (AB#3156).

Alternatives considered &ZeroWidthSpace;

OptionProsConsWhy not chosen
API-first platform core, single SDK entry point (chosen)One place for logic/auth/rules; surfaces stay thin; ~1.3× effort for web + 2 apps; clean feature contractRequires discipline to never bypass the API; platform must ship before features— chosen
Feature-siloed (each feature owns its own backend + storage + auth)Teams move independentlyRe-implements auth/RBAC/notifications N times; type drift; breaks the closed-community approval guaranteeRejected — duplicates the exact cross-cutting concerns ADRs 0003/0005/0006/0007 centralize
Backend-for-frontend per surface (separate API per client)Tailored payloads per surfaceTriples API surface area; logic leaks into per-surface backendsRejected at this scale; a single API + typed SDK is sufficient
Thin-foundation platform (just auth + DB; features self-serve the rest)Smaller initial platformFeatures re-invent notifications, audit, media, web shell; inconsistent UX and securityRejected — contradicts the strategy's "platform is not a thin foundation"

Consequences &ZeroWidthSpace;

Positive &ZeroWidthSpace;

  • A feature built once (API endpoint + shared type) appears on web and both apps with no duplicated logic — the API-first payoff.
  • Security and the closed-community guarantees (approval workflow, server-side RBAC, audit, COPPA) are enforced in one place, not re-litigated per feature.
  • Clear build order: the platform (AB#3074) ships first; feature Epics plug into a known contract.
  • Per-feature ADRs have a stable frame to reference ("how this feature composes on the platform").

Negative / trade-offs &ZeroWidthSpace;

  • The platform must reach a usable baseline before the first feature delivers value — front-loaded effort (mitigated by the phased roadmap: platform → iOS → Sermons/Music → …).
  • Discipline cost: the "never bypass the API / never self-authorize" rules need lint/review enforcement (e.g. import/no-restricted-paths, code review) or they erode.

Risks &ZeroWidthSpace;

  • Platform-as-bottleneck — every feature waits on platform capabilities. Mitigation: keep the core services (auth, RBAC, notifications, audit, storage) deliberately small and stable; add feature- specific logic in feature slices, not the core.
  • Hidden coupling — a feature quietly depends on another feature's internals. Mitigation: features communicate only through platform services and shared-types; no cross-feature imports.
  • Scope creep into the platform — "make it platform" for things that are really one feature's concern. Mitigation: the seven responsibilities above are the boundary; anything outside them is a feature and gets a feature ADR.

References &ZeroWidthSpace;

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