Dependency Map & Build Order
Which features depend on which entities and patterns, and what implementation order falls out of those dependencies. This replaces "phases of features" with "layers of dependencies": each layer can only land after the layers below it are real.
Why this exists. A phased plan (Phase 3, Phase 4, ...) sequences features by feature name, not by dependency. That hides cross-cutting infrastructure that has to land before any feature uses it, and creates schema rewrites when late features surface patterns the foundation should have included. This doc fixes that — see patterns.md for the patterns, data-model.md for the entities, this doc for the order.
The Layered Model
Twelve layers. Each depends only on the layers below. Within a layer, items can be built in parallel.
Layer 12 ─ Compliance hardening, DSAR, monitoring ─ Phase 10/11 territory
Layer 11 ─ (formerly Telemetry; reassigned — Telemetry is now Layer 2 alongside features)
Layer 10 ─ Telerehab (exercises, treatment plans, sessions, logs)
Layer 9 ─ Segments
Layer 8 ─ Automations + Webhooks
Layer 7 ─ Documents (PDF templates, generated PDFs)
Layer 6 ─ Appointments
Layer 5 ─ Scheduling (calendars, hours, holds)
Layer 4 ─ Custom Fields + Forms
Layer 3 ─ Service Catalog (services, plans, products)
Layer 2 ─ People (patient_profiles, patient_caregivers, patients, specialists, specialties)
Layer 1 ─ Cross-cutting infrastructure (foundation patterns)
Layer 0 ─ Org + User + RBAC ─ DONELayer 0 is what shipped in commit 3ade31c. Layer 1 (cross-cutting infrastructure) corresponds 1:1 to implementation-plan sections 1.1–1.18. Layers 2–10 correspond to implementation-plan sections 2–10 — the same numbering used in implementation-plan.md.
Layer 0 — Foundation (already shipped)
These exist. Don't re-implement.
organizations,organization_domains, domain routing, slug+domain resolveprincipals(root identity),humans(Clerk-bound human profile, PKprincipal_id),organization_memberships(membership), Clerk integration. See decisions.md → Why principals as the root identity.- RBAC:
permissions,roles,role_permissions,platform_memberships - Two-pool DB architecture, RLS helpers, base RLS template
audit_logtable (no writers yet — Layer 1 owns the writers)
Layer 1 — Cross-Cutting Infrastructure
Everything in this layer must land before Layer 2 starts. Why: every entity from Layer 2 onward will use these patterns, and retrofitting them is much more expensive than building them once.
Two views of the same content. This section is ordered by dependency —
1.1is what everything else builds on,1.27lands last. The canonical foundation doc is foundation.md, which is grouped by sub-phase (1A Cross-Cutting Runtime, 1B Identity & Tenancy, 1C Admin Surfaces, 1D Foundation Gate). The mapping:1.1–1.12→1A.1–1A.12;1.13/1.14→1Cadmin surfaces;1.16→1A.13(rate limiting + SOUP);1.17→1D.3(staging deploy);1.18→1D.1(gate documentation);1.19→1B.2(org settings);1.20→1B.3(tiers / subscriptions);1.21→1B.4(patient tiers);1.22→1B.5(tier-entitlement / org-entitlement / limit middleware);1.23→1D.2(setup-a-clinic test);1.24→1B.1(principal model);1.25→1A.14(data classification);1.26→1A.15(audit log partitioning);1.27→1A.16(postgres extensions). Use this map for dependency reasoning; use foundation.md to track what's shipped vs. what's open.
1.1 Audit logging writes (Pattern P10, P11)
Every Layer ≥ 2 mutation must log. Build the centralized audit middleware now and wire it into the existing organization/member/domain handlers as the proof.
Blocks: Every layer above.
1.2 RLS integration test harness (validates P1, P3, P4)
testcontainers harness, two seeded orgs, two seeded humans with different roles. Tests for cross-org isolation, mutation gating, public-resolve boundary, blocked-human 403, one-hat invariant.
Blocks: Anything that adds tables — tests are the safety net.
1.3 Encryption helper (P12)
internal/core/crypto/ with AES-256-GCM, KMS-backed in prod, local key in dev. Encrypt/decrypt at repo layer.
Blocks: organization_billing.tax_id_encrypted (CUI — pii_regulated), and (forward references) platform_service_providers.credentials_encrypted once Foundation 1C.2 (Curated Providers — Cat A) lands the table, organization_integrations.credentials_encrypted once Foundation 1C.5 (Connected Accounts — Cat B) lands the table, plus webhook signing secrets (auth_secret) at Foundation 1C.4 (Outbound Webhook Subscriptions — Cat C). Column-level encryption is reserved for pii_regulated and auth_secret per decisions.md → Why most PII is plaintext; ordinary PII (phones, emails, contact info) stays plaintext + layered defense.
1.4 Soft-delete pattern (P13)
deleted_at convention, repository helper, migration template update, RLS template update.
Blocks: patients, appointments, specialists, exercises, treatment_plans.
1.5 PII redaction in logs (P11)
Slog handler redacts the documented patterns. Test coverage. Audit existing log call sites.
Blocks: Any production deploy.
1.6 Error-response audit (P34 — in part)
Walk handlers, confirm *AppError everywhere, no err.Error() to client. Define the error envelope shape used by every Layer ≥ 2 handler.
Blocks: Establishing the envelope before adding many endpoints.
1.7 API contract conventions (P34)
Pagination, filtering/sorting query-param shape, error envelope. Decide once, reference from every Layer ≥ 2 handler. Decide whether to generate OpenAPI now or later.
Blocks: Adding many handlers without a shared shape.
1.8 File storage (P27)
S3 client with org-scoped key prefixes, signed URLs (15-min read, 5-min write), MIME validation, bucket policy. Single internal/integration/s3/ package.
Where it's needed first: Form file uploads (Layer 4) and exercise videos / signature images / logo uploads (anywhere in Layer 2+ that stores user-uploaded content).
Pre-Phase-3 status: Borderline. Strict reading: needed before any feature uploads files. Pragmatic reading: doesn't strictly block Layer 2 (people don't upload anything yet — except specialist signatures). Recommend landing it in Layer 1 anyway, because doing it twice costs more than doing it once.
1.9 Internal event bus (P28)
In-process pub/sub. Standard event payload envelope. Empty initially — events get published as features ship.
Where it's needed first: Automations (Layer 8) and webhooks (Layer 8). But every Layer ≥ 2 feature will publish events.
Convention. Even if no consumer exists yet, every feature publishes its events from day one. That way when the consumer (automations/webhooks) arrives in Layer 8, every event is already firing — no retroactive instrumentation.
1.10 Translation infrastructure (P21)
UI: next-intl scaffold on the three frontends. Content: convention for translations JSONB documented (no actual content tables exist until Layer 10).
Blocks: Strict reading — every new user-facing string needs to flow through i18n. Pragmatic reading — UI scaffold can be added when the Clinic Admin UI lands. Content translations only matter for Layer 10. Recommend landing UI scaffold in Layer 1 to avoid retrofitting.
1.11 Activity tracking (P35)
humans.last_activity write path. organization_memberships.last_used_at column + write path.
Blocks: Console "inactive humans" view in Clinic admin / Console UI (Layer 1.13).
1.12 Reserved column inventory (P36)
Verify audit_log.impersonation_id, break_glass_id, action_context, request_id all exist. Add organization_memberships.invited_at, invited_by (FK to principals(id)), accepted_at.
Blocks: Layer 3+ impersonation, break-glass, email invitations would each need a migration if not done now.
1.13 Clinic Admin UI + Console UI completion (P3 layer 3-4 management)
Org-self-service: org profile edit, members CRUD with inline role change, domains CRUD, custom roles editor, audit log viewer. Console: humans page, platform roles, cross-tenant audit, system role template editor, permission catalog viewer.
Blocks: "An org admin can administer their org" requires this; not strictly blocking Layer 2 entities, but blocking the platform from being "Layer 1 done" in any honest sense.
1.14 Shared UI patterns (P34 — frontend side)
Empty/loading/error states, toasts, server-validation rendering, permission-aware UI helper. In packages/ui/.
Blocks: Layer 2+ frontend features without consistent UX.
1.15 (vacated — superseded by Foundation 1B.9)
This slot is left vacant for grep navigation. The numbered "Layer 1 / Layer 2 / Layer 4.5" build order in this map predates the Foundation / Features split documented in implementation-plan.md. The substantive consent design now ships in Foundation 1B.9 (Consents Ledger), with form-driven (Tier B) medical consents layering on top in F3.5. See decisions.md → Why clinic is controller, platform is processor for the controller/processor architecture that drove the redesign.
1.16 Sensitive-endpoint rate limiting + SOUP list (P31-related; gap docs 09, 10.0)
Auth endpoints + public resolve endpoint rate-limited (Redis-based). SOUP list at apps/docs/reference/soup.md started.
Blocks: Any production deploy. SOUP list is regulatory prep — small but cheaper to start than backfill.
1.17 Foundation deployment (staging) (P1, P2, P12 in real env, custom domains real test)
Single-AZ AWS staging. RDS Postgres 17, ElastiCache Redis, Fargate Core API, ALB+ACM, KMS for 1.3, Route53 for *.clinic.restartix.pro etc. Run Layer 0.14 E2E against staging including real-DNS custom domain test.
Blocks: Any honest claim that Layer 1 works. Custom domains can't be validated locally.
1.18 Phase discipline documentation (no entities, just rules)
STOP gate at top of implementation-plan, Foundation Discipline section in CLAUDE.md, gate referenced from skill descriptions, summary table reflecting "in progress."
1.18a Frontend performance foundation (Patterns P42, P43, P44, P45, P46) + URL≡scope guard
Five composable performance patterns plus a security middleware that closes a latent URL/header gap surfaced by the cache work. P42 (server-side unstable_cache + tag taxonomy in api-client), P43 (tuned undici dispatcher for keep-alive), P44 (pgbouncer transaction pooling, local docker-compose mirroring AWS ECS), P45 (Redis cache-aside helper at the repo layer, sharing the P42 scope namespace), P46 (Portal hybrid render decision matrix; docs-only at this phase). The RequireURLOrgMatchesScope middleware mounts on every per-org route group as a P42/P45 prerequisite — without it, caching by URL {id} would serve org A's cached response to a member of org B.
Depends on: 1.16 (Redis already wired for rate-limit and idempotency; cache-aside reuses the same client).
Blocks: Nothing for Foundation, but every Layer 2+ feature that exposes a per-org GET endpoint inherits both the cache pattern and the URL-guard requirement. Detail: foundation 1A.17, decisions.md → Why unstable_cache, Why URL≡scope, Why pgbouncer-on-ECS.
1.19 Org-level settings, billing, entitlements (P38)
organization_settings, organization_billing, organization_entitlements — three typed companion tables to organizations, each owning one sensitivity class. Triggers auto-create on org provisioning. current_app_has_org_entitlement(entitlement_code TEXT) SQL function. Subject.OrgEntitlements populated by middleware. New permissions: organizations.update_settings, organizations.manage_billing. See org-settings.md.
Depends on: 1.1 (audit), 1.3 (encryption — tax_id_encrypted).
Blocks: 1.20 (subscriptions write to organization_billing.current_tier_id), 1.22 (org-entitlement middleware reads from organization_entitlements), every regulated feature in Layer 7+ that calls current_app_has_org_entitlement.
1.20 Tiers, subscriptions, sales overrides (P37, P14, P18)
Catalog tables (tiers, tier_versions, entitlements, limit_definitions, tier_entitlements, tier_limits) + per-org subscription tables (organization_subscriptions, organization_subscription_entitlements snapshot, organization_subscription_limits snapshot, organization_subscription_overrides). Snapshot-on-subscribe (P37) is the load-bearing pattern. Catalog seed migration ships Free / Pro / Dedicated. Org-creation hook auto-creates a Free subscription. Entitlement projection service ships as a no-op until the first regulated entitlement appears in a tier. See tiers-and-subscriptions.md.
Depends on: 1.19 (writes to organization_billing.current_tier_id).
Blocks: 1.22 (RequireTierEntitlement / EnforceLimit read subscription state), 1.23 (acceptance test).
1.21 Patient tiers catalog
patient_tiers + patient_tier_versions + patient_tier_entitlements + patient_tier_limits (mirror of the tiers engine; share the entitlements / limit_definitions catalogs from 1.20). patient_subscriptions defers to 2.5 (FKs to patients); patient_tier_inclusions defers to 3.2 (FKs to service_plans). Org-creation hook auto-creates one default tier per org. No tier→role binding — patients are not memberships post-1.26, so subscribing to a tier never mutates RBAC; perks ride entirely on the patient_tier_entitlements / patient_tier_limits catalogs. New permission: patient_tiers.manage.
Depends on: 1.20 (shares entitlements / limit_definitions catalogs).
Blocks: 2.5 (patient_subscriptions FKs to patient_tiers); portal sign-up (every patient gets a default tier).
1.22 Tier-entitlement / org-entitlement / limit middleware (P38, P3 composition)
RequireTierEntitlement(code), RequireOrgEntitlement(code), EnforceLimit(code, delta) middlewares + httputil error constructors (NewTierEntitlementUnavailableError 402, NewOrgEntitlementDisabledError 403, NewLimitExceededError 402). Subject extensions (OrgEntitlements, TierEntitlements, Limits). Layer 1 stub semantics: HasTierEntitlement returns TRUE and Limit returns unlimited until consumers exist; activates when the first entitlement-gated route ships. Test routes verify each path end-to-end. See middleware-composition.md.
Depends on: 1.19 (org-entitlement surface), 1.20 (subscription resolver), 1.6 (error envelope).
Blocks: Every Layer 2+ route gated by tier-entitlement / org-entitlement / limit.
1.23 Setup-a-clinic acceptance test
The Layer 1 → Layer 2 gate. End-to-end scenarios: org provisioning auto-creates settings/billing/entitlements/Free-subscription/default-tier; superadmin can change tier, attach add-ons, grant overrides, flip org entitlements; clinic admin can define patient tiers; patient signup respects the default tier; middleware paths return correct status codes and discriminators; audit-log carries action_context = 'org_entitlement_change' on org-entitlement writes; audit rows from these scenarios carry the correct actor_type (verifies 1.24's principal-model wiring). Test suite passes via make test-integration.
Depends on: 1.19, 1.20, 1.21, 1.22, 1.24, plus the related UI work in 1.13.
Blocks: Layer 2. Foundation discipline rule: Layer 2 does not start until 1.23 passes (and 1.24 is closed).
1.24 Principal model as root identity
Refactor the actor model from "users + parallel principals mirror" to principals as the root identity. users is renamed to humans with principal_id PK + FK to principals(id). Future actor types (agents, service_accounts) are sibling tables sharing the same PK pattern. Audit, RLS, RBAC, and every domain reference principals.id. New audit columns on audit_log (actor_id, actor_type); AI-provenance metadata moves to a sibling audit_ai_provenance table (per Layer 1.26) so AI-feature schema churn doesn't pollute the core audit table. RLS helpers renamed current_app_user_id() → current_app_principal_id(); session vars app.current_user_id → app.current_principal_id. Concrete non-human actor types ship per-feature; this layer ships the abstraction, the humans profile table, the singleton 'system' principal, and the parent_principal_id delegation column. Closes Hooks 1 + 4 from the AI-first ADR. See decisions.md → Why principals as the root identity.
Depends on: 1.1 (audit_log shape), 1.2 (RLS test harness), and the existing tenancy/RBAC schema in 0.x.
Blocks: 1.23 (acceptance test verifies actor_type wiring), Layer 2 (every domain after this assumes the principal model; baking user_id into Layer 2+ schemas would re-open the gap).
1.25 Column-level data classification
Markdown registry at apps/docs/architecture/data-classification.md mapping every column to a class (public, org_internal, pii_basic, pii_regulated, clinical, clinical_sensitive, auth_secret, audit_only, system_metadata) and a list of allowed egress targets (bulk_export, analytics_internal, webhook_egress, marketing_email, support_export, plus AI placeholders). CI check enforces no schema column escapes classification AND that column-level encryption is reserved for pii_regulated + auth_secret (the encryption invariants — see decisions.md → Why most PII is plaintext). Go runtime helper at internal/shared/classification/ lets egress paths filter records to allowed columns only. Closes Hook 3 from the AI-first ADR. See decisions.md → Why a column-level data classification.
Depends on: every existing migration (000001 through 000005+) — the backfill classifies all current columns.
Blocks: Layer 2 (every domain that adds a column without a classification entry creates backfill debt; CI rejects unclassified columns), Layer 8 (webhooks + marketing email are the first runtime consumers of the helper).
1.26 Audit log partitioning (P41)
audit_log and audit_ai_provenance range-partitioned monthly on created_at / audit_log_created_at. Composite PK (id, created_at) because Postgres requires the partition key in every unique index; composite FK from audit_ai_provenance keeps parent/child handoff in lockstep so a single DROP PARTITION retires both tables' month together. Migration seeds only the current month — cmd/audit-partition-roll (default -ahead=3) provisions forward partitions, intended to run on a monthly scheduler. No DEFAULT partition; missed rollover fails INSERT loud rather than piling rows into a default that later blocks attaching new ranges. audit_log gaps would themselves be a compliance finding, so partitioning has to land before launch — retrofitting a billion-row partition migration on a live audit-grade table with no allowable write gap is multi-day work; pre-prod cost is a 60-line migration edit. New cross-cutting pattern P41 documents the events-partitioned-state-not heuristic so future event tables (webhook delivery log, notification send log, AI agent action log, patient measurement timeseries) follow the same shape without rediscovering it.
Depends on: 1.1 (audit_log shape), 1.24 (principal model determines the actor columns that participate).
Blocks: Any future event-shaped table at Layer 2+ that should follow the same partitioning convention; the warm/cold tier S3 archival flow (CLAUDE.md → Data Retention).
1.27 Postgres extension preinstall
Enable unaccent + pg_trgm (diacritic-folded fuzzy search for picker UIs — Romanian without unaccent breaks UX), vector (pgvector — embedding columns for AI features ready when needed), pg_stat_statements (top-N slow-query observability). Dev image switched from postgres:17-alpine to pgvector/pgvector:pg17 and shared_preload_libraries=pg_stat_statements configured in docker-compose; testcontainers harness updated to match with the same customizer. Each of these costs more to retrofit than to enable preemptively — unaccent + pg_trgm need to be present before the first picker migration adds a GIN trigram index; vector needs the right Postgres image at the infrastructure layer, not a per-feature decision; pg_stat_statements needs shared_preload_libraries configured at server start, which means a restart in production. AWS RDS parameter group still pending — closed by 1D.
Depends on: Migration framework (000001_init); none of the previous 1.X items.
Blocks: Any Layer 2+ migration that wants to use these extensions without coordinating an extension-enable + image-swap. Specifically: every picker-queryable text column convention (USING GIN (col gin_trgm_ops) + unaccent(col) ILIKE …); the first AI feature that adds an embedding vector(N) column; production observability work in 1D.
Layer 2 — People
After Layer 1 lands, build the human-identity model.
2.1 Patient profiles + caregivers (P6, P7)
patient_profiles (plaintext phone / emergency_contact_phone per pii_basic rule, human_id FK to humans(principal_id), RLS via 1.2's helper), patient_caregivers (with caregiver_human_id), current_human_patient_profile_ids() helper.
Depends on: 1.4 (soft-delete? actually patient_profiles isn't soft-deleted — only patients is), 1.2 (RLS test pattern). 1.3 (encryption) is a dependency for pii_regulated/auth_secret columns; patient_profiles no longer carries any of those.
Blocks: Everything patient-facing.
2.2 Patients (per-org link) (P8)
patients table with organization_id, patient_profile_id, profile_shared, consumer_id, deleted_at. RLS uses current_human_patient_profile_ids() for patient access.
Depends on: 2.1, 1.4 (soft-delete). The profile_shared = TRUE flip is wired by the profile-sharing consent form (Foundation 1B.9 ledger plumbing + F3.5 form-driven Tier B) — forward-reference; the column itself ships in 2.2 with a default of FALSE and lights up once F3.5 lands.
2.3 Specialists + specialties (P9)
specialties, specialists (with human_id UNIQUE NULL FK to humans(principal_id), scheduling_timezone, scheduling_active, signature_url, deleted_at), specialist_specialties junction.
Depends on: 1.4 (soft-delete), 1.8 (S3 for signature_url).
Note. Scheduling profile fields (scheduling_timezone, scheduling_active) are on the specialists table from day one — don't add via ALTER later. The spec's ALTER TABLE specialists ADD COLUMN scheduling_* pattern is just because the spec was written sequentially; the migration should include these from the start.
2.4 Patient onboarding flow
Wire up: public booking → patient_profiles row → caregiver if needed → patients row → automation rule fires → consent forms required → patient signs → profile_shared = TRUE. The row-creation parts ship in Layer 2; the consent-form requirement is a forward-reference to F3.5 (which ships alongside F3 forms; the underlying ledger ships in Foundation 1B.9).
Depends on: 2.1, 2.2, 1.9 (event bus — onboarding emits patient.onboarded), 1.13 (admin UI to manage patients). Forward-reference: F3.5 (consent forms gate the booking flow; 2.4 ships rows in Layer 2 and the gate lights up when F3.5 lands).
2.5 Patient subscriptions
patient_subscriptions table — per-org link between patients and patient_tiers, with snapshot tables (patient_subscription_entitlements, patient_subscription_limits, patient_subscription_overrides) mirroring the org-side billing engine. No tier→role binding hook — patients have no role at the org post-1.26; portal access comes from the patients row, perks come from the snapshot tables. Portal onboarding (POST /v1/portal/onboard) creates patient_profiles + patients + patient_subscriptions (snapshotted from the default tier) in one transaction.
Depends on: 2.2 (patients), 1.21 (patient_tiers + patient_tier_entitlements + patient_tier_limits).
Blocks: 3.2 (patient_tier_inclusions provisioning hook reads patient_subscriptions state).
Note. patient_tier_inclusions defers to 3.2 — its service_plan_id FK can't exist until service_plans does.
Layer 3 — Service Catalog
Pure catalog entities. No scheduling logic.
3.1 Services + service_specialists + service_attachments
Depends on: 2.3 (specialists), 1.8 (S3 for attachments).
3.2 Service plans + patient_service_plans + patient tier inclusions
service_plans with three plan types + treatment_plan_assignments_total counter extension for tier inclusions. patient_service_plans with source_tier_subscription_id FK column for tier-auto-granted enrollments. Access grants (telerehab_access, library_access). patient_tier_inclusions (deferred from 1.21 because service_plan_id FKs here). Provisioning hook on patient_subscriptions state change projects inclusions onto patient_service_plans (provision / revoke / rollover semantics per tiers-and-subscriptions.md § Tier → entitlement provisioning).
Depends on: 3.1, 2.2 (patients), 2.5 (patient_subscriptions), 1.21 (patient_tiers).
3.3 Products + service_plan_products
Reference catalog only, no fulfillment.
Depends on: 3.1.
3.4 Service forms
service_forms junction. Note: this depends on form_templates from Layer 4, but the junction itself is in Layer 3. Either:
- (a) define the junction in Layer 4 alongside form_templates, or
- (b) define
form_templatesplaceholder before, then complete forms in Layer 4.
Recommend (a): defer service_forms until Layer 4.
Layer 4 — Custom Fields + Forms
Most features depend on this layer because forms are everywhere.
4.1 Custom fields + versioning + values (P19, P18)
custom_fields, custom_field_versions, custom_field_values. Polymorphic entity_type, entity_id.
Depends on: Layer 0 (orgs).
4.2 Form templates + versioning
form_templates, form_template_versions. pdf_template_id FK is added in Layer 7 (reserved column).
Depends on: 4.1 (forms reference custom_field_id).
4.3 Forms (instances)
forms table with snapshot pattern, status state machine, immutability after sign. File upload integration. Signing a consent-purpose form is the canonical content source for clinical-grade consent — the form-signing hook lives in F3.5 (Tier B medical consents — ships alongside F3, not after; the underlying ledger is Foundation 1B.9).
Depends on: 4.2, 1.8 (S3 for file uploads), 1.9 (publishes form.created, form.completed, form.signed).
4.4 Service forms + calendar forms junctions
Now that form_templates exist, wire the junction tables.
Depends on: 4.2, 3.1, 5.1.
4.5 (superseded — see Foundation 1B.9 + F3.5)
This subsection predates the Foundation / Features split. The consents ledger ships in Foundation 1B.9 — a single append-on-grant table spanning platform-scope (platform_terms, platform_privacy_notice) and org-scope (org_terms, org_privacy_notice, marketing_email, marketing_sms, analytics, ai_processing) purposes, with a legal_basis discriminator and a withdrawable derived flag. Form-driven (Tier B) medical consents — telemedicine, video recording, biometric capture, treatment-specific — layer on top in F3.5, using the same table with source = 'form' rows. See decisions.md → Why clinic is controller, platform is processor for the redesign rationale.
Layer 5 — Scheduling
5.1 Specialist hours + overrides
specialist_weekly_hours, specialist_schedule_overrides.
Depends on: 2.3.
5.2 Calendars + calendar_specialists + calendar_forms
calendars (with override hours JSONB, modality, assignment_strategy, etc.), calendar_specialists (with priority + per-calendar override hours), calendar_forms.
Depends on: 3.1 (services — required FK), 5.1 (default hours), 4.2 (form_templates).
5.3 Specialist assignment tracking
specialist_assignment_tracking for round-robin counters.
Depends on: 5.2.
5.4 Slot hold system (Redis) (P30)
Redis hold keys, TTL, SSE streaming for live availability.
Depends on: 5.2 (calendars define availability).
5.5 Availability engine
Compute slots from weekly hours + date overrides + calendar overrides + held slots + booked appointments.
Depends on: 5.1, 5.2, 5.3, 5.4, Layer 6 (appointments — to know which slots are taken).
Iteration. This may be built incrementally — the engine has multiple variants (single specialist, round-robin, priority-based, with-overrides).
Layer 6 — Appointments
6.1 Appointments table
With state machine (P33), patient_profile_id FK, calendar_id, service_id, contact fields, soft delete.
Depends on: 2.1 (patient_profiles), 2.3 (specialists), 3.1 (services), 5.2 (calendars).
6.2 Appointment files
S3-backed.
Depends on: 6.1, 1.8.
6.3 Appointment reviews
Rating + low-rating alerts (publish appointment.review_low event).
Depends on: 6.1, 1.9.
6.4 Public booking API
GET /v1/public/availability, POST /v1/public/bookings (no auth). Two-phase booking flow: guest registration → CS confirmation → account creation + appointment. Slot hold during form fill.
Depends on: 5.5 (availability engine), 5.4 (slot hold), 6.1.
6.5 Daily.co video integration
Auto-create video room when status → inprogress, auto-delete on done. Cat A consumer — Daily.co is a platform-curated provider (the platform owns the Daily.co account and credentials), so it resolves through platform_service_providers, NOT organization_integrations. Per-org rows in platform_service_providers are only used for per-tenant brand-isolation overrides (e.g., a clinic with its own Daily.co subdomain — available on either tenancy mode); the default platform-managed Daily.co account serves every clinic by default. Video minutes are metered.
Depends on: 6.1, Foundation 1C.2 (Curated Providers — Cat A) for the platform_service_providers resolution table that holds the platform-default Daily.co credentials (and optional per-org overrides for per-tenant brand isolation), Foundation 1C.7 (Metering & Quotas) since video minutes are metered, 1.3 (encryption helper that secures credentials_encrypted), 2.3 (specialists' video links).
Historical note. Daily.co was previously framed as the first consumer of
organization_integrations. That framing conflated Cat A (platform-curated, platform-owned credentials) with Cat B (clinic-connected, clinic-owned credentials) and was the load-bearing confusion that drove the new § 1C. Capabilities, Integrations & Metering sub-phase. Daily.co is Cat A and consumes 1C.2;organization_integrationsis the Cat B table and ships under 1C.5 for clinic-connected services like Google Calendar. See glossary.md → Integration categories.
6.6 Consent gating in appointment flow
Check global org consents (Terms, GDPR) before allowing booking proceed. Aggregate forms from service + calendar. Block flow until required forms signed.
Depends on: 6.1, 4.3 (forms), Foundation 1B.9 (consents ledger) + F3.5 (Tier B medical consents), 1.9 (event bus to fire appointment.first_booked).
Layer 7 — Documents
7.1 PDF templates + components + versions
pdf_templates, pdf_template_components, pdf_template_versions.
Depends on: Layer 0.
7.2 Document generation pipeline
PDF rendering (ChromeDP — decision pending; alternatives wkhtmltopdf, Gotenberg). Template + form values → PDF → S3.
Depends on: 7.1, 4.3 (forms), 6.1 (appointments).
7.3 Appointment documents
appointment_documents (reports + prescriptions, unified by type). appointment_document_files.
Depends on: 7.2.
7.4 form_templates.pdf_template_id FK
Forms can specify which PDF template to use for rendering.
Depends on: 7.1, 4.2.
Layer 8 — Automations + Webhooks
8.1 Event bus consumers (P28, P29)
Two consumers for the event bus from 1.9: automation engine + webhook dispatcher.
Depends on: 1.9 (the event bus itself), and the events needing to be firing across all layers above.
8.2 Automation rules + executions
automation_rules, automation_executions. Action handlers: require_form, suggest_form, send_email, send_sms, send_whatsapp, show_notification, send_push, block_booking, grant_access, require_consent, update_segment, schedule_action, send_session_reminder, book_followup_appointment.
Depends on: 8.1, every entity any action touches (forms, segments, appointments, etc.). Practically: don't ship 8.2 until forms (4) and appointments (6) are real.
Note. Email/SMS/push transport providers are decisions: AWS SES, SendGrid, Postmark; Twilio, AWS SNS. Decision pending — block before action handlers ship.
8.3 Webhook subscriptions + delivery
webhook_subscriptions, webhook_events, HMAC signing, retry with exponential backoff, idempotency.
Depends on: 8.1.
Layer 9 — Segments
9.1 Segments + members + versions
segments with rules JSONB, segment_members (materialized cache), segment_versions (history).
Depends on: 4.3 (forms — rules query forms.values), 4.1 (custom_field_values for profile-rule source), 6.1 (appointments for activity rules), 2.2 (patients).
9.2 Rule engine + auto-update
Evaluate segments against patient data on demand and on data changes (event bus consumer for form.signed, appointment.completed, patient.profile_completed, etc.).
Depends on: 9.1, 1.9 (event bus).
Layer 10 — Telerehab
10.1 Exercise library (P20 dual-scope, P21 translations)
exercises (UUID PK, dual-scope), taxonomy tables (categories, body_regions, equipment), tags (polymorphic), instructions, contraindications. translations JSONB on global rows.
Depends on: 1.4 (soft-delete), 1.8 (S3 for video_url and image_url; or Bunny Stream — decision pending), 1.10 (translation convention).
10.2 Treatment plans (P20 three-scope)
treatment_plans, treatment_plan_versions, treatment_plan_sessions, treatment_plan_session_exercises. Three scopes (global / org / custom-per-patient).
Depends on: 10.1 (exercises), 2.3 (specialists), 3.2 (service_plans for treatment_plans.service_plan_id), 4.2 (form_templates for post_session_form_template_id).
10.3 Patient enrollment + execution
patient_treatment_plans, patient_session_completions, patient_exercise_logs (with pose tracking columns).
Depends on: 10.2, 2.2 (patients), 4.3 (forms for post-session questionnaire), 6.1 (appointments for in_clinic plan session linkage).
10.4 Telerehab automations
Automation actions specific to treatment plans: send_session_reminder, book_followup_appointment. Triggers: treatment_plan.assigned, treatment_plan.session_completed, treatment_plan.completed, treatment_plan.expired, low-adherence detection.
Depends on: 10.3, 8.2.
Layer 11 — (reassigned)
The original "Telemetry Service" Layer 11 has been reassigned. Telemetry is now a Layer 2 feature alongside other Layer 2 features (F1–F11), not an end-of-the-build separate concern. The reasons are in decisions.md → Why telemetry is PG + S3, not ClickHouse — the workload it actually serves is patient-engagement + pose-tracking ingest for Treatment Plans (F9), not "consume audit events from everyone." Earlier framings of telemetry as "downstream of all features" assumed the rejected forwarder design.
When Layer 2 telemetry work begins it depends on Layer 1 (audit, RLS, classification, events.Bus, signed-token helper) and runs alongside F9 (Telerehab). It does NOT block other Layer 2 features and CAN run in parallel with them.
See /telemetry/index.md for the full design.
Layer 12 — Compliance Hardening
DSAR + erasure endpoints (consume data from all layers). Rate limiting beyond the auth-and-public minimum from 1.16. Monitoring, alerting, performance benchmarks. IEC 62304 traceability for clinical features (Layer 10).
This is what the old plan called Phase 10/11.
Reading the Map
When picking the next thing to build:
- Find your target in the layer table. What layer is it?
- Check every dependency listed. Are all of them in place?
- If yes — build it. If no — go fix the unmet dependency first. That's the foundation discipline rule from CLAUDE.md.
- Follow patterns.md for any cross-cutting concern your target touches. Audit log writes, RLS template, soft-delete pattern, encryption, etc.
What This Replaces
The implementation plan in apps/docs/implementation-plan.md had Phases 1–13 sequenced by feature name. Reading this dependency map, the actual order should be (numbered to match this doc's layers):
| Old plan name | This doc's layer | Notes |
|---|---|---|
| Phase 1 (Foundation) | Layer 0 | Done |
| Phase 2 (Auth & Multi-Tenancy) | Layer 0 + Layer 1 | First wave is Layer 0; 2.10–2.27 close out Layer 1 |
| Phase 3 (Core Domain Models) | Layer 2 + Layer 3 | The "specialties, specialists, patients, services" set spans two layers |
| Phase 4 (Scheduling & Appointments) | Layer 5 + Layer 6 | |
| Phase 5 (Forms & Custom Fields) | Layer 4 — earlier than old plan suggested | Forms must come before Phase 4 because services/calendars reference form_templates |
| Phase 6 (Documents & PDF Generation) | Layer 7 | |
| Phase 7 (Treatment Plans & Exercises) | Layer 10 | |
| Phase 8 (Automations & Webhooks) | Layer 8 | |
| Phase 9 (Telemetry Service) | Layer 2 (F10) | Reassigned from Layer 11 — see decisions.md → Why telemetry is PG + S3. Runs in parallel with F9 (Telerehab). |
| Phase 10 (Compliance & Security) | Mostly Layer 1 (encryption, audit) + Layer 12 (DSAR, monitoring) | Big chunks of Phase 10 already pulled into Layer 1 |
| Phase 11 (Dashboard & Stats) | Cross-layer feature on top of Layer 6, 9, 10 data | |
| Phase 12 (Testing) | Continuous, every layer | |
| Phase 13 (Frontend Integration Contract) | Layer 1 (P34 conventions) plus Layer-specific work |
The biggest reordering: Forms (old Phase 5) becomes Layer 4 — before Scheduling (old Phase 4 = Layer 5 here). Why: services and calendars both reference form_templates. Trying to build calendars before forms means defining form_templates as a stub or coming back to add the FK later. Build forms first.
Second reordering: chunks of Phase 10 (encryption, audit, security headers) move into Layer 1. Why: every Layer 2 entity needs them — patient phone is encrypted, every mutation logs, every API response uses the same envelope.
Foundation Discipline Reminder
Layer N+1 does not start until Layer N is complete. This is the same rule as in CLAUDE.md, applied to layers instead of phases.
If you're tempted to start Layer 5 because Layer 4 has one missing checkbox — stop. Close the checkbox. The foundation is paid for once; working around it is paid every time.
When updating the implementation plan, the order of 1.x subsections should reflect Layer 1 completing in dependency order:
- Audit (1.1) before everything because everything writes to it
- RLS tests (1.2) right after, because they validate the model new layers stack on
- Encryption (1.3) and soft-delete (1.4) before patients (Layer 2)
- PII redaction (1.5) and error envelope (1.6, 1.7) before many endpoints exist
- File storage (1.8) and event bus (1.9) before features that publish events
- Translation, activity, reserved columns (1.10–1.12) — column-shape decisions before tables land
- UI work (1.13, 1.14) — required for Layer 2 frontend (consent foundation now ships in Foundation 1B.9 ledger; form-driven Tier B medical consents in F3.5; the 2.4 onboarding flow holds a forward-reference until F3.5 ships)
- Rate limiting + SOUP (1.16) and deployment (1.17) — production readiness
- Documentation (1.18) — the gate that prevents a future chat from skipping Layer 1
Within each numbered Layer 1 item, the work is: design → migration → repo + service + handlers → tests → wire into existing code as proof.