Skip to content

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 ─ DONE

Layer 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 resolve
  • principals (root identity), humans (Clerk-bound human profile, PK principal_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_log table (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 dependency1.1 is what everything else builds on, 1.27 lands 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.11.121A.11A.12; 1.13 / 1.141C admin surfaces; 1.161A.13 (rate limiting + SOUP); 1.171D.3 (staging deploy); 1.181D.1 (gate documentation); 1.191B.2 (org settings); 1.201B.3 (tiers / subscriptions); 1.211B.4 (patient tiers); 1.221B.5 (tier-entitlement / org-entitlement / limit middleware); 1.231D.2 (setup-a-clinic test); 1.241B.1 (principal model); 1.251A.14 (data classification); 1.261A.15 (audit log partitioning); 1.271A.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.

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_idapp.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.

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_templates placeholder 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_integrations is the Cat B table and ships under 1C.5 for clinic-connected services like Google Calendar. See glossary.md → Integration categories.

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:

  1. Find your target in the layer table. What layer is it?
  2. Check every dependency listed. Are all of them in place?
  3. If yes — build it. If no — go fix the unmet dependency first. That's the foundation discipline rule from CLAUDE.md.
  4. 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 nameThis doc's layerNotes
Phase 1 (Foundation)Layer 0Done
Phase 2 (Auth & Multi-Tenancy)Layer 0 + Layer 1First wave is Layer 0; 2.10–2.27 close out Layer 1
Phase 3 (Core Domain Models)Layer 2 + Layer 3The "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 suggestedForms 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:

  1. Audit (1.1) before everything because everything writes to it
  2. RLS tests (1.2) right after, because they validate the model new layers stack on
  3. Encryption (1.3) and soft-delete (1.4) before patients (Layer 2)
  4. PII redaction (1.5) and error envelope (1.6, 1.7) before many endpoints exist
  5. File storage (1.8) and event bus (1.9) before features that publish events
  6. Translation, activity, reserved columns (1.10–1.12) — column-shape decisions before tables land
  7. 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)
  8. Rate limiting + SOUP (1.16) and deployment (1.17) — production readiness
  9. 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.