Appearance
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 typedapi-clientSDK 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 ​
- 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 asapps/api/src/features/<feature>/slices (decision 8) — they extend the platform; they do not bypass it. - Identity & access — Clerk verifies the Apple/Google social identity (ADR 0003); the API maps the provider
subclaim to a platformUsersrecord; 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. - Member & family domain — the
Userstable (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. - Shared platform services — the notification transport (email/SMS/in-app/push channels; the provider is decided in research, S4), audit logging (the
AuditLogtable 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. - Shared package layer (
packages/*) —shared-typesis the single definition of every API contract;api-clientis the only sanctioned way a surface calls the platform;shared-utils(validation, formatting, RBAC helpers),shared-config, andui(the design system; Claude Design is the sanctioned toolchain per ADR 0027) complete the layer. - 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 ofheritageva.app;window.location.hostnamedetermines which portal section mounts inside the singleapps/webbundle. See ADR 0028 for the subdomain map and TLS strategy. - 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) ​
- API-first, no exceptions. A surface or feature reaches data and rules only through
api-clientover 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 ​
| Option | Pros | Cons | Why 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 contract | Requires discipline to never bypass the API; platform must ship before features | — chosen |
| Feature-siloed (each feature owns its own backend + storage + auth) | Teams move independently | Re-implements auth/RBAC/notifications N times; type drift; breaks the closed-community approval guarantee | Rejected — duplicates the exact cross-cutting concerns ADRs 0003/0005/0006/0007 centralize |
| Backend-for-frontend per surface (separate API per client) | Tailored payloads per surface | Triples API surface area; logic leaks into per-surface backends | Rejected at this scale; a single API + typed SDK is sufficient |
| Thin-foundation platform (just auth + DB; features self-serve the rest) | Smaller initial platform | Features re-invent notifications, audit, media, web shell; inconsistent UX and security | Rejected — contradicts the strategy's "platform is not a thin foundation" |
Consequences ​
Positive ​
- 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 ​
- 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 ​
- 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 ​
- Platform strategy — Platform scope
- Platform strategy — Architecture (API-first)
- ADR 0001 — Monorepo + three-layer structure
- ADR 0003 — Authentication (Clerk, social login)
- ADR 0004 — Cloud/hosting stack + CI/CD
- ADR 0005 — Observability model
- ADR 0006 — Two-plane RBAC
- ADR 0007 — Account & Family-Group identity
- ADO: Epic AB#3154 (detailed ADRs), Story AB#3155 (this ADR), AB#3156 (Sermons & Music Hub ADR), Epic AB#3074 (Build Platform).