Skip to content

Platform Inventory

Phase 0, Deliverable 1 of platform-completion.md. The single authoritative answer to "what does this platform actually have."

Verification basis: code, migrations, Terraform and git only — never another doc's claim. Every line below was checked by reading or grepping the artefact it describes. Where the repo cannot settle a question (live AWS state, the applied DB schema, third-party account state), it is filed in §5 Verification notes as unverified rather than asserted.

Snapshot date: 2026-08-02. Working tree: branch staging at 0b5670c, with an in-flight docs-correction pass staged by parallel agents (63 modified/deleted paths, all under apps/docs/ and .claude/owner-briefs/). Statements about docs below describe the tree as found; statements about code, schema and infra describe committed state.

This document exists because the plan set asserted things that were not true — most starkly an appointments table that has never been created. Its whole value is that it can be trusted. If a claim here is wrong, correct it here first.

⚠️ Changed since the 2026-08-02 snapshot

Read this before trusting anything below. The body is a point-in-time record and has not been rewritten; these are the deltas.

Was recorded asNowWhere
F1 Specialists — no tables, no domain, no routesSHIPPED 2026-08-03. Migration 000040 creates specialties, specialists, specialist_specialties; both Go domains, routes, /v1/me/specialist-profile, S3 assets, OpenAPI + client, and the Clinic roster/detail/create UI all exist.§2.1 F1 row, §2.2 permissions line
Zero specialists.* / specialties.* permission rowsThree seeded in 000040: specialties.manage, specialists.view_org, specialists.manage.§2.2
F2.1 Offerings — "the word 'offering' appears exactly once in the whole migration set, in a prose comment"SHIPPED 2026-08-03. Migration 000041 creates offerings + offering_specialists with RLS, permission seeding, Go domain, routes, OpenAPI + client, and the Clinic catalog/detail/roster/cover UI. offering_forms still waits for F3.4 (needs form_templates as an FK target).§2.1 F2.1 row
Zero offerings.* permission rowsTwo seeded in 000041: offerings.view_org, offerings.manage.§2.2
Console sidebar /services — "a forbidden term… must become /offerings when F2.1 ships"Renamed 2026-08-03 to Offerings / /offerings in apps/console/components/app-sidebar.tsx. Still disabled: true — offerings are per-org clinic config and the Console surface is not built.§3 Console sidebar
cmd/expire-hard-cap-protocols scheduled by nothing — "hard-capped protocols never flip to ended… a live behavioural gap"CLOSED. Built into Dockerfile.api, scheduled in both envs, and applied to production 2026-08-03restartix-production-expire-hard-cap-protocols, ENABLED, cron(30 1 * * ? *). Verified the binary is present in the deployed image (7265d277).§1.1 binaries, §3.3, §4
Clinic sidebar: 10 of 20 slots disabled8 of 20 — /specialists and /locations are live and permission-gated.§3 leftovers
000039 application to production unverifiable from the repoVerified applied 2026-08-03 against the live DB (schema_migrations = 39, not dirty).§5
39 migrations41 — 000040_specialists, 000041_offerings. Production received 000040 via the 2026-08-03 deploy; 000041 is repo-only until the user promotes it.§1.1

New, not in the snapshot at all:

  • Unapplied Terraform drift on production: module.database.aws_db_parameter_group.this shows rds.force_ssl moving apply_method pending-rebootimmediate (value unchanged at 1, so TLS enforcement is unaffected — almost certainly a provider-version artefact). The 2026-08-03 cron apply was deliberately -targeted to avoid bundling it. It will appear in every future production plan until applied or reconciled.
  • Staging's applied schema version is still unverified. Only production was read.

1. HAVE — shipped and in the repo

1.1 Go API service (services/api/)

The platform's primary transactional service. Go 1.26, chi router, pgx, Postgres with RLS, Redis.

44 domain packages under services/api/internal/core/domain/ — verified by ls. Most follow the model.go / repository.go / service.go / handler.go (+ errors.go, routes.go) shape; the exceptions are noted inline:

GroupPackages
Identity & tenancyhuman, patients, patientprofiles, organization, orgsettings, orgbilling, orgentitlements, designations, invites, ownershiptransfer, sharelinks, portalonboarding, handoff
RBAC & platform opspermissions, roletemplates, platformmemberships, breakglass, impersonation, audit
Commercialtiers, subscriptions, patienttiers, patientsubscriptions, accessoffers, patientcontentgrants, catalog
Clinical contentexercises, exercises_taxonomy, programs, sessions, protocols, adherence (library only — no handler), stats
Session runtimesessionpairings, librarytoken (handler only — one endpoint, no DB writes)
Posepose_configs, poseengines
Integrations & platform infraintegrations, platformproviders, webhooks, aimodels, location, consents, legaldocument

39 migrations, 000001_init000039_restore_safe_principal_is_human, in services/api/migrations/core/. Up + down for each (78 files). They create 109 tables (excluding the 10 monthly partition children seeded in-migration) and 10 range-partitioned parents: audit_log, audit_ai_provenance, notifications, notification_deliveries, outbound_webhook_deliveries, inbound_webhook_dedup, usage_records, session_exercise_events, session_pain_events, session_tv_liveness. Verified by extracting every CREATE TABLE and every PARTITION BY RANGE from the *.up.sql set.

23 cmd/ binaries (services/api/cmd/):

  • Runtime / scheduled: api, api-partition-roll, backup-runner, expired-sessions-sweep, session-runs-auto-close, patient-subscriptions-expiry-sweep, usage-quota-reset, usage-summary-rollup, check-providers, expire-hard-cap-protocols (wired + applied to production 2026-08-03), appointment-noshow-sweep (F5.1; built into the image and scheduled every 15 min in both envs' Terraform — NOT applied to either environment), restore-drill, devhandoff
  • CI invariant checks wired into make check: check-classification, check-soup, check-migrations, check-events-registry, check-softdelete, check-capabilities, check-cata-resolution, check-inbound-webhooks, check-ai-models, check-audit-coverage, check-response-contract
  • Tooling: dump-events-registry

Cross-cutting packagesinternal/core/ (36 dirs incl. domain): access, activity, ai, audit, auth, billing, cache, capabilities, config, crypto, database, displaytoken, events, gdpr, handofftoken, idempotency, inboundwebhooks, integrations, internalapi, locks, metering, middleware, notify, onboarding, partitionroll, partitions, phonetoken, principal, providers, ratelimit, server, session, sse, telemetrytoken, webhooks. internal/shared/ (9): apiquery, classification, clientgeo, clientip, httputil, pseudonym, redact, requestctx, softdelete. internal/integration/ (10): awsmetrics, bunnystorage, bunnystream, cloudflare-saas, media, merchantpro, s3, shoporder, telemetry, woocommerce.

302 route mounts (method + path pairs) across internal/core/server/routes.go (97) and the 14 per-domain routes.go files (205). Top-level groups: /internal/v1, /webhooks, /v1/public, /v1, plus three anonymous CORS-scoped mounts for the TV companion (session-pairings create, tv-liveness display-token, TV/phone SSE channels).

Enforced invariants that already exist and that F1–F6 inherit:

  • RLS on every tenant table; ConnFromContext in every repo; current_app_has_permission(resource, action) in policies.
  • P47middleware.RequireURLOrgMatchesScope("id") mounted once at the /v1/organizations/{id} group (routes.go:752), covering every nested per-org route.
  • Data classification — exactly 9 classes (public, org_internal, pii_basic, pii_regulated, clinical, clinical_sensitive, auth_secret, audit_only, system_metadata) and 7 egress targets (bulk_export, analytics_internal, webhook_egress, marketing_email, support_export, ai_clinical_drafting, ai_admin_summarization) in internal/shared/classification/types.go:17-40; ~1,229 column rows in the data-classification.md registry. cmd/check-classification passes as of this snapshot and fails the build on an unregistered column.
  • SOUP — ~87 dependency rows in soup.md; cmd/check-soup passes as of this snapshot and fails the build on an unlisted direct dependency. There is no PDF-engine row — F6 will add the first one.
  • Column encryptioninternal/core/crypto (AES-256-GCM, versioned wire format). Exactly three encrypted column families exist today, all auth_secret: platform_service_providers.credentials_encrypted, outbound_webhook_subscriptions.signing_secret{,_previous}_encrypted, organization_integrations.credentials_encrypted + inbound_signing_secret_encrypted. Exactly one pii_regulated column exists: organization_billing.tax_id_encrypted.
  • Events registry (Cat E) — 11 registered events, verified by running go run ./cmd/dump-events-registry: commerce.order_paid, commerce.order_refunded, organization.created, organization.updated, organization.member_added, organization.member_removed, organization.member_role_changed, organization_domain.added, organization_domain.verified, organization_domain.removed, patient.onboarded.
  • Capability frameworkinternal/core/capabilities with WrapInternal for Internal Library capabilities.
  • Curated-provider catalog (Cat A)platform_service_providers with chk_psp_capability_provider whitelisting exactly email→ses, storage→aws_s3, auth→clerk (000015:119-124).
  • Connected-account catalog (Cat B) — 2 seeded rows, woocommerce and merchantpro, both auth_type='api_key', status='available' (000036).
  • Email — live SES EmailChannel (not FakeChannel) constructed in cmd/api/main.go initNotify, registered as notifyemail.NewScopedChannel. 5 notification templates × 2 locales in internal/core/notify/templates/.
  • S3 surfaces — registry in internal/integration/s3/surfaces.go: signatures, documents, forms-upload, logos, appointment-files, exercise-assets. Only SurfaceLogos has non-test call sites (4, all org branding). SurfaceExerciseAssets is referenced by the media service by bucket-name convention rather than through this registry. SurfaceSignatures, SurfaceDocuments, SurfaceFormsUpload and SurfaceAppointmentFiles have zero consumers — registered and reserved for F1/F3/F5/F6, which is exactly the shape those features need.
  • Redis primitivesinternal/core/locks (Lua-atomic), internal/core/sse (pub/sub fan-out across ECS tasks), internal/core/ratelimit (IPKey / PrincipalKey / URLParamKey), internal/core/cache (P45 cache-aside).

53 seeded permission codes, extracted from every INSERT INTO permissions block across the 22 migrations that carry one:

audit_log.view_org · catalog.manage · catalog.read · catalog.pose_configs.{manage,invalidate} · catalog.tags.{view,manage_org,manage_platform} · clinical.pose_overrides.{create,view} · consents.{manage,view_org} · content.{grant,platform_write,publish,read,write} · data.view_deleted · exercises.{manage,read} · locations.manage · offers.manage · organizations.{manage_billing,manage_designations,manage_domains,manage_integrations,manage_members,manage_privacy_notice,manage_share_links,manage_webhooks,transfer_ownership,update,update_settings,view_directory} · patient_subscriptions.{manage,view_org} · patient_tiers.manage · patients.{impersonate,manage,offboard,view} · protocols.{approve,enroll,pause,prescribe,read} · session_runs.view · sessions.{manage,read} · stats.read · subscriptions.{manage,view_org} · usage.view_org

(content.premium and content.prescription_play look like permission codes but are entitlements on the content.* patient-access family, not permissions rows — see glossary.md.)

3 system role templates seeded in 000002:1706: admin, specialist, customer_support.

Content seeds: 16 platform exercises, all status='draft', in 000031_exercises_seed.up.sql, plus the taxonomy vocabulary in 000030. No catalog_sections, catalog_entries, programs or sessions rows are seeded by any migration — that content is authored in the live DB through the UI.

Legal / consent seeds: 5 consent purposes (platform_privacy_notice, platform_terms, org_privacy_notice, org_terms, org_service_disclaimer) in 000008profile_sharing was seeded here until it was removed 2026-08-20; 3 legal document types (terms, privacy_notice, service_disclaimer) in 000009, whose bodies are v1 placeholders — the string placeholder appears 30 times in that migration.

1.2 Telemetry service (services/telemetry/)

Separate Go module, separate database. 2 migrations creating 3 tables: media_session_metrics (flat — a per-(run, media) rollup, i.e. state), media_buffering_events and media_library_views (both PARTITION BY RANGE (started_at)). 000002_video_errors adds playback-error columns (error_count, first_error_code, first_error_src, first_error_detail, client_user_agent) to the rollup. Packages: auth, core, coreapi, internalapi, library, media. Binaries: telemetry, telemetry-partition-roll.

1.3 Media service (services/media/)

Separate Go module, no database. Packages: bunny (storage + stream), composer, core, storage. Binaries: media, compose-local. Called by the API over Service Connect; pushes to Bunny.

1.4 Frontends

AppSurfacesNotes
apps/clinic/30 pages, 16 route handlers, 3 layoutsDashboard, patients (+ detail tabs: overview, protocols, stats, consents, activity, subscription, forms — placeholder), library (exercises / sessions / programs / catalog), patient-tiers, integrations (offers / campaigns / orders / fulfillments / sku-bindings), legal-documents, analytics
apps/portal/40 pages, 18 route handlers, 8 layoutsPatient group (dashboard, my-programs, sessions, library, progress, consents), kiosk group (session start wizard + run player + companion + feedback + done), /enter /reentry /logout /demo handoff bridge, /join/[code], /onboard, /orders/[ref], /campaigns, /unsupported, plus 8 diagnostic routes (§3)
apps/console/35 pages, 2 route handlers, 3 layoutsClinics (+ detail: profile, members, patients, settings, tier, audit), exercises (+ pose-config), vocabulary (7 sub-pages), audit-logs, permissions, role-templates, legal-document-templates, platform-legal-documents, platform-stats, render-pipeline, system-health (+ errors, media-failures), palette
apps/tv/Static vanilla JS/HTMLNo build step. index.html (pairing) + play.html (player). Own Sentry: self-hosted @sentry/browser 7.120.4 ES5 bundle, pinned deliberately (v8+ fails to parse on Chrome 56 / Tizen 4)
apps/landing/Static index.html + CSSApex marketing page. Production only — no staging bucket, no staging deploy job
apps/docs/VitePressarchitecture/, features/ (26 dirs), implementation-plan/, reference/, legal/, ro/, product/, telemetry/, security/, overview/, commercial/, dev/

1.5 Packages

  • packages/ui/38 components including the ones F1–F6 will lean on: async-multi-select-filter, data-table, sortable-list, calendar, edit-lock-banner, require-permission, permissions-provider, date-range-filter, qr-code, route-preloader, debug-panel. 5 hooks: use-api-resource, use-edit-lock, use-has-permission, use-mobile, use-server-synced-state.
  • packages/api-client/ — 18 modules, 24,491 lines. generated.ts (15,225 lines) is openapi-typescript output from apps/docs/openapi.yaml; catalog.ts, programs.ts, protocols.ts, pose.ts, taxonomy.ts, sessions.ts, content-grants.ts are hand-written because those domains are not in the spec (see §3). Also cache-tags.ts (P42 taxonomy), dispatcher.ts (P43), proxy.ts, permissions.ts.
  • packages/i18n/ — next-intl catalogs, en.json + ro.json.
  • packages/eslint-config/, packages/typescript-config/.

1.6 Infrastructure (infra/)

3 Terraform compositions: bootstrap (tfstate backend + deploy IAM/OIDC), staging, production. 14 modules: cache-redis, database-aurora-serverless, database-rds, deploy-iam, ecs-cluster, ecs-service, edge-cloudflare, email-ses (scaffold only, used by nothing), network, observability, scheduled-tasks, storage-backups, storage-s3, tfstate-backend.

7 ECS services per environment: api, pgbouncer, media, telemetry, clinic, portal, console. ECR repos are shared between environments (staging's state owns them; production reads them via a data source).

21 operational scripts in infra/scripts/ including production-apply.sh, production-tunnel.sh, production-reset-db.sh, production-media-toggle.sh, restore-drill.sh, bootstrap-db-roles.sh, 000023-skip-note-prod.sql (the canonical catch-up-DDL pattern for prod-applied migrations).

3 GitHub Actions workflows: ci.yml (on PR), deploy-staging.yml (workflow_dispatch only — push to staging deliberately no longer auto-deploys), deploy-production.yml (workflow_dispatch from master only, environment: production approval gate on every AWS-touching job). Both authenticate via OIDC, no long-lived credentials. Neither applies migrations (§2.3).

Backups (production only)storage-backups module: daily 02:00 UTC backup-runner Fargate task dumping both databases (core + telemetry) to restartix-backups-primary-production, envelope-encrypted, with a backup-missing CloudWatch alarm gated on var.enable_backup_cron. enable_backup_cron = true in production/terraform.tfvars as of commit 51a0492. A real restore drill ran green on 2026-08-02 (0b5670c) after the first attempt failed and produced migration 000039.

1.7 Repo tooling

Root Makefile (check, check-all, test-integration, setup-hooks); .githooks/pre-commit; .claude/ with 23 slash commands (including /new-domain, /new-migration, /new-owner, 8 audit-* sweeps, /review, /verify-foundation), 8 owner briefs + README, 8 agent definitions, 1 skill (translate-ro).


2. DON'T HAVE — specced somewhere, absent in code

Everything in this section was verified by direct grep across migrations/core/*.up.sql, internal/, and the app trees. "Zero" means zero.

2.1 The F1–F6 clinical-operations stack

No table, no domain package, no route, and no permission row exists for any of these. Verified three ways: absent from the extracted CREATE TABLE set; absent from ls internal/core/domain/; absent from the seeded permission-code set.

FeatureTables the specs call forActual stateWhich doc implies otherwise
F1 Specialists SHIPPED 2026-08-03 (000040)specialties, specialists, specialist_specialtiesspecialist_locations deferred to F4 with the rest of the availability substrate(finding as of the snapshot:) grep -i "specialt" across all 39 *.up.sql returned literally zero hits — the word "specialty" has never been written into the schema. specialists appears in 10 files, all of them role descriptions, permission-grant WHERE r.code IN (...) clauses, or the seeded max_specialists limit (000004:568, free 2 / pro 20 / dedicated unlimited). No table.features/specialists/, features/specialties/, data-model.md Area 2
F2.1 Offerings SHIPPED 2026-08-03 (000041)offerings, offering_specialistsoffering_forms deferred to F3.4, which brings its form_templates FK target(finding as of the snapshot:) the word "offering" appeared exactly once in the whole migration set, in a prose comment at 000022:14 describing the exercise catalog. No table.features/services/ (still under the forbidden services name), data-model.md Area 3 "Service Catalog"
F3 Formscustom_fields, custom_field_versions, custom_field_values, form_templates, form_template_versions, formsform_templates, custom_fields, custom_field_values appear in zero migrations. consents.source_form_id UUID exists in 000008:151 with a CHECK ((source='form') = (source_form_id IS NOT NULL)) but no FK — reserved, not lit.features/forms/ (7 files), features/custom-fields/
F4 Schedulingspecialist_weekly_hours, specialist_schedule_overrides, calendars, calendar_specialists, calendar_formsspecialist_weekly_hours appears in zero migrations. calendars appears in 2, both comments. btree_gist is not enabled000001 enables uuid-ossp, pgcrypto, unaccent, pg_trgm, vector, pg_stat_statements only.features/scheduling/ (4 docs + a 6-file Go reference engine), data-model.md Area 4
F5 Appointmentsappointments, appointment_files, appointment_reviewsThe appointments table does not exist. All 7 migration files that mention it do so in comments — most explicitly forward-looking, e.g. 000023:901 "the appointments table is the source of truth for…" and 000014:12 "(specialists, calendars, appointments) needs location_id from day one."june-launch.md claims F5's substrate columns "already landed in the cadence redesign" — false; appointments-substrate.md reads as a shipped spec
F6 Documentspdf_templates, pdf_template_versions, pdf_template_components, appointment_documentspdf_templates appears in zero migrations. No internal/core/pdf/.features/documents/ (6 files), features/pdf-templates/

Consequential absences that follow from the above and are easy to miss:

  • Zero forms.* / appointments.* / specialists.* / specialties.* / documents.* / calendars.* / offerings.* permission rows in any migration. They exist only as prose in rbac-permissions.md and as a forward-looking note at 000002:1730 ("add the relevant verbs (forms.*, appointments.*, etc.) as those…").
  • humans has no name column. 000002:292-322 defines provider_subject_id, provider_org_id, email, confirmed, blocked, portal_credential_generation, last_activity, preferred_language, timezone. Admin and customer_support staff have nowhere to store a display name today.
  • adherence.AppointmentCounter is never wired. The interface is defined at cadence.go:116 and the only construction site passes nilstats/service.go:248: "AppointmentCounter is nil until the appointments substrate…". Supervised protocols return ErrSupervisedNotImplemented.
  • video / daily is not in the curated-provider whitelist. chk_psp_capability_provider permits only email, storage, auth. F5's Daily.co integration needs an ALTER on a prod-applied constraint.
  • No SurfaceAvatars in the S3 registry; SurfaceLogos is org branding and must not be overloaded.
  • No ResolveSchedulingTimezone — grep across internal/ returns nothing. P23's fallback chain has no implementation.
  • No GET /v1/reference/timezones — no reference route group exists at all.
  • No pdf.Renderer capability and no PDF engine dependency in go.mod.

2.2 F11 / compliance

GapVerified stateDoc that implies otherwise
DSAR export routeZero routes. grep -i "gdpr|dsar|export" across routes.go returns only two comment lines about DSAR routing (GET /v1/me/clinics exists so a patient can find which clinic to file with).production-launch-readiness.md F11 line; compliance.md
Erasure end-to-endinternal/core/gdpr/anonymize.go is a helper only — grep for callers outside the package and its own test returns zero. No route, no service, no request workflow.same
Retention automationNo archive job, no purge job, no cron. The audit_archive S3 bucket is provisioned in both environments and has zero code consumersgrep -rn "AUDIT_ARCHIVE|audit_archive" services/ --include="*.go" is empty. organization_settings.audit_retention_months is stored and never read by an enforcement path.compliance.md "Hot / Warm / Purge"
Break-glass review controlbreak_glass_sessions (000011:62-72) has principal_id, organization_id, reason_text, reason_ref, opened_at, expires_at, closed_at, closed_by_principal_id. No reviewed_at, no review_notes, no opened_from_ip.compliance.md:47 asserts "review within 24 hours, two approvers" — an asserted control with no mechanism. 000011 is prod-applied, so this needs the catch-up-DDL pattern
SES bounce/complaint handlingNo notification_suppression table (zero migrations). No SNS webhook receiver — grep -i "suppress|bounce|complaint" across internal/core/notify/ and routes.go returns only two comment lines. infra/modules/email-ses is a 27-line scaffold used by neither environment; both envs declare a bare aws_sesv2_configuration_set inline instead.production-launch-readiness.md line 123 (correctly flags it)
SentryWired in apps/portal only (@sentry/nextjs ^10.56.0, 5 config/instrumentation files, lib/sentry-scrub.ts) and apps/tv (self-hosted ES5 v7 bundle). Zero in apps/clinic, apps/console, services/api, services/telemetry, services/media.production-launch-readiness.md External services
iOS playback fixNo Bunny token-auth / signed-URL code exists anywhere. Grep for token_auth, tokenAuth, SignURL, BunnySign across services/, apps/, packages/ returns only two comments acknowledging hotlink protection (bunnystorage/client.go:40, portal/lib/session/companion-conductor.ts:361).production-launch-readiness.md:52 (correctly flags it as a live prod incident)
WAF for custom hostnamesNo WAF resource in any Terraform. Grep across infra/envs/**/*.tf and infra/modules/**/*.tf finds only four prose mentions. Whatever WAF exists is Cloudflare-dashboard-managed and not IaC.decisions.md "Cloudflare-only WAF"; readiness line 168 (open)
Cross-region backupsstorage-backups provisions one primary bucket in eu-central-1. No replication rule.readiness line 139

2.3 Migration and launch tooling

  • services/migration-tools/ does not exist. ls services/ returns api, media, telemetry. It is referenced as a real path by deployment.md:525-528 (go run ./services/migration-tools/legacy-import) and production-launch-readiness.md:207.
  • No automated migration application. Grep for migrate across .github/workflows/*.yml returns nothing — neither deploy workflow applies migrations. Both environments ship a migrations_runner ECS task definition (aws_ecs_task_definition.migrations_runner + .telemetry_migrations_runner) that must be invoked by hand via aws ecs run-task with a containerOverrides.command, per deployment.md:145. Schema advancement in every environment is a manual step.
  • No restore-drill cron. /bin/restore-drill exists and infra/scripts/restore-drill.sh drives it, but it is wired to no schedule. restore-drill.sh accepts a staging argument even though staging has no storage-backups module — that invocation cannot work.

2.4 Other asserted-but-absent

  • Per-org audit-log read route. audit_log.view_org is a seeded permission with RLS behind it, but the only mounted route is superadmin-only GET /v1/audit-logs (routes.go:393), whose own comment says the per-org read "live[s] alongside the future per-org route, not here." The clinic sidebar's /audit-log entry is disabled: true.
  • Pose-frame ingest. No pose_frames / session_pose_frames table in any migration. The 8 shipped pose tables are configuration and vocabulary only — exercise_pose_configs, exercise_pose_config_history, exercise_pose_landmarks, exercise_pose_metrics, exercise_pose_feedback_rules, pose_engines, pose_landmarks, pose_data_quality_overrides. This matches the deliberate scope cut (client-side preview, no MDR scope); it is recorded here so nobody re-reads the pose tables as an ingest pipeline.
  • Real (counsel-reviewed) legal text. All three seeded legal-document templates carry v1 placeholder bodies — e.g. 000009:677: "v1 placeholder — replaced by lawyer-reviewed text before public launch."
  • Seeded patient-facing catalog. No migration inserts a catalog_sections or catalog_entries row.
  • CSV/bulk export surface. A bulk_export egress target and a bulk_export entitlement both exist in the schema; no export route existsgrep -n "export" apps/docs/openapi.yaml returns nothing.

3. LEFTOVERS

Half-built surfaces, placeholder pages, deferred-then-forgotten items, and provisioned-but-unused infrastructure. All paths verified.

3.1 Placeholder UI

  • apps/clinic/app/(dashboard)/patients/[id]/forms/page.tsx — 20 lines. A Card with two i18n strings and the comment "Forms tab — placeholder shell… when the forms domain ships." Reachable from the patient detail tab bar today.
  • Clinic sidebar: 10 of 20 nav slots are disabled: true (apps/clinic/components/app-sidebar.tsx) — /appointments, /calendar, /subscriptions, /specialists, /forms, /organizations, /locations, /users, /audit-log, /settings. They render greyed and non-navigable (nav-main.tsx:35 branches on disabled), so they are honest placeholders rather than 404s — but half the clinic app's information architecture is a promise. Note /subscriptions and /locations are disabled even though the backend routes exist (GET /v1/organizations/{id}/subscriptions, .../locations).
  • Console sidebar: 19 of 31 nav slots are disabled: true (apps/console/components/app-sidebar.tsx) — including /onboarding, /subscriptions, /users, /agents, /service-accounts, /plans, /specialties, /services, /forms, /notifications, /suppression, /announcements, /webhooks, /connectors, /break-glass, /feature-flags, /locales, /settings. /services (label "Services") is a forbidden term per the glossary — it must become /offerings when F2.1 ships, and the slot should be renamed in the same PR rather than carried forward. /suppression is a nav slot for a table that does not exist (§2.2).

3.2 Diagnostic and demo-era code still in the production apps

  • /demo is live production code, not a leftover route — but it carries a leftover name. apps/portal/app/demo/ is 1,451 lines across 10 files and is the post-sign-in landing for every legacy-handoff patient: app/enter/route.ts redirects to /demo?ticket=… at lines 64 and 161, and proxy.ts:47 lists "/demo(.*)" as a public route. DEMO_NAME_COOKIE = "demo-name" is set by /enter and /reentry, cleared by /logout. Renaming this is a real, load-bearing refactor on the path the ~20k migration will use — not a find-and-replace.
  • 8 diagnostic pages in the portal, all reachable behind auth: (diag-bare)/bare-a, (diag-bare)/bare-b, (diag-lite)/lite-a, (diag-lite)/lite-b, (patient)/diag-content, (patient)/diag-empty, (patient)/diag-me-server, (patient)/diag-me-swr. Their own comments say "Remove with the nav-probe harness." Residue of the nav-perf workstream.
  • 4 diagnostic route handlers: app/api/diag/ping, app/api/diag/upstream ("DIAGNOSTIC ONLY — paired with the /dev/perf page; remove before production"), app/api/debug/me (returns the full getMe() payload), app/dev/tv ("Delete this route once kiosk compat lands").
  • /dev/* bypasses the portal proxy entirelyproxy.ts:90: if (request.nextUrl.pathname.startsWith("/dev/")) return NextResponse.next();. That makes /dev/empty, /dev/perf, /dev/telemetry-poc and /dev/tv unauthenticated and publicly reachable on the production portal. /dev/telemetry-poc's own header says "Throwaway / exploratory… Remove the /dev tree before production" and hardcodes a Bunny library ID. None of these pages read patient data, so this is hygiene rather than a leak — but it is a deliberate auth bypass that outlived its stated lifetime.

3.3 Wired-nowhere code and infrastructure

  • cmd/expire-hard-cap-protocols is scheduled by nothing. CLOSED 2026-08-03 — built into Dockerfile.api and scheduled in both envs; the production schedule is ENABLED and verified. Original finding preserved below. (was) Its own package comment says "Driven by EventBridge Scheduler → ECS RunTask once per day (staging + production)" — but grep "/bin/expire-hard-cap-protocols" infra/envs/ returns zero hits in both environments. Hard-capped protocols never flip to ended. This is a live behavioural gap, not just untidy config.
  • infra/modules/email-ses/ is a 27-line scaffold with variables, outputs and a # SCAFFOLD ONLY banner listing the resources to build. No environment references it.
  • audit_archive S3 bucket is created in both environments and consumed by nothing (§2.2).
  • apps/docs/features/scheduling/go/ — 6 .go files (availability.go, assignment.go, types.go, holds.go, ip.go, ratelimit_ip.go) sitting inside the docs tree, in no Go module (go.mod exists only at services/{api,telemetry,media}). They are therefore never compiled, never vetted, never tested by make check-all, and carry zero _test.go files. Note: these files are being edited by the concurrent docs pass.
  • experiments/bunny-telemetry-poc, exercise-composer. Outside every workspace and every build.

3.4 API contract drift

apps/docs/openapi.yaml documents 169 paths. The Go server mounts 302 method+path pairs. Entire shipped domains have zero OpenAPI coverage — verified by grepping the spec for each:

DomainRoutes registeredIn openapi.yaml
programs180
pose_configs200
exercises_taxonomy490
patientcontentgrants30
catalog110 (the word appears 65× in descriptions, never as a path)
protocols110 — the spec mentions the surface only in a comment that says "hand-rolled TS client in packages/api-client/src/protocols.ts" (openapi.yaml:5388)
Handoff /v1/public/handoff/{session,reentry}20
TV tv-liveness / tv-state0

This is why packages/api-client/src/ carries seven hand-written modules (catalog, programs, protocols, pose, taxonomy, sessions, content-grants) alongside the generated one. The drift is self-perpetuating: each uncovered domain adds another hand-written client, and the spec's own comments now document the workaround rather than the contract.

3.5 Plan-document debt (counts as found, 2026-08-02)

DocOpen checkboxesDone
foundation.md98 — 65 of them 1D (1D.1 Console 21, 1D.2 Clinic 18, 1D.5 cross-org 11, 1D.3 Portal 10, 1D.0 3, 1D.4 2); 1B.14 Locations 10; the rest ≤4 each410
features.md25941
june-launch.md5130
patient-catalog-and-access.md2918
tv-baseline-mp4.md21 open, 0 done — a plan that was never started or never ticked0
june-demo.md041

The three open 1D.0 boxes are explicit deferrals, not gaps — the four backend gap-fillers named in the plan (GET /v1/admin/permissions, GET /v1/admin/role-templates, platform-memberships CRUD, cross-org subscriptions aggregator) are all shipped and mounted at routes.go:507-513.

3.6 Small stuff worth knowing

  • Only 3 TODO/FIXME comments in all of services/ (excluding tests): principal/subject.go:248 (layer-2-plus entitlement resolution), principal/subject.go:267 (layer-5-plus usage counters), exercises/service.go:23 (multi-language — locale hardcoded to "ro"). Zero in apps/{clinic,portal,console} source and packages/ (the ~40 hits under apps/portal/public/mediapipe/ are vendored Emscripten output).
  • organization_settings.feature_flags is a JSONB column that is written, read and echoed back by the API but gates nothing — no code reads a specific key.
  • 8 owner briefs exist, not 9 as platform-completion.md:130 states (README.md + consents, infra, media, notify, programs, telemetry, translations, tv).
  • All 16 seeded platform exercises are status='draft' — nothing in the migration set is publishable content out of the box.
  • The concurrent docs pass has already staged the deletion of apps/docs/gaps/ (27 files) and apps/docs/features/treatment-plans/ (5 files), and updated glossary.md (the enrollments reservation is retired; services → offerings is marked in flight with offering_packages as the new reserved name for the deferred F2.2 concept).

4. PROD vs STAGING drift

Derived from infra/envs/production/ and infra/envs/staging/ plus git history. What Terraform declares is verifiable here; what is actually applied is not — see §5.

4.1 Branch and deploy state

Value
origin/staging HEAD0b5670c — 2026-08-02 10:47
origin/master HEAD0328038 "Merge pull request #44 from RestartiX/staging" — 2026-08-02 09:38
staging ahead of master3 commitsafc9cde, c6e5e57, 0b5670c (all restore-drill fixes + the doc closing the backup blocker)
master ahead of staging10 commits, all merge commitsgit rev-list --count origin/staging..origin/master = 10. They are PRs #35#44, every one merged from staging; there is no other source branch, so master carries no unique file content. origin/master is therefore not a git-ancestor of staging, but nothing on it is missing from staging
Local master8d5eba4 (2026-05-15) — 695 commits behind origin/master (688 behind origin/staging); do not read it as production's code
Staging deployworkflow_dispatch only — a push to staging no longer auto-deploys. Targets all|api|pgbouncer|media|telemetry|clinic|portal|console|tv (no landing). OIDC role restartix-deploy-staging, trust policy pinned to refs/heads/staging
Production deployworkflow_dispatch from master only, approval-gated, targets all|api|pgbouncer|media|telemetry|clinic|portal|console|tv|landing. Never dispatched by an agent — the user's exclusively.

4.2 Substrate differences

DimensionStagingProduction
VPC CIDR10.10.0.0/1610.20.0.0/16
DatabaseAurora Serverless v2, min_capacity = 0, max_capacity = 2RDS db.t4g.medium, multi_az = true, 50 GB → 200 GB autoscale, 7-day PITR
Rediscache.t4g.microcache.t4g.small
Terraform modules7 distinct9 distinct — adds observability and storage-backups
AlarmsHand-rolled monitoring.tf, 3 alarms (api-no-healthy-targets, alb-5xx-spike, aurora-connections-high)observability module — ALB 5xx, RDS CPU / connections / free-storage, per-service CPU + memory × 6 services, NAT port-alloc + packet-drop × 2 AZs, plus the backup-missing alarm
Budgetnoneaws_budgets_budget at $900/mo, alerts at 50/80/100% actual + 100% forecast → SNS → webmaster@restartix.com
Layer-2 backupsnonedaily 02:00 UTC, core + telemetry, envelope-encrypted, absence-alarmed
Landing site (apps/landing/)not deployed — no bucket, no deploy jobapex restartix.pro, S3 website behind Cloudflare flexible-SSL
TV sitestaging.restartix.tv bucketapex restartix.tv bucket
Cloudflare zone singletonsstaging's state owns them for both envscloudflare_zone_settings_override, cloudflare_managed_headers, and the restartix_pro_flexible_ssl / restartix_tv_flexible_ssl / restartix_tv_www_redirect rulesetsproduction declares none of them, by design
ECR reposstaging's state owns them, force_delete = true, lifecycle "keep last 20 tagged" counting both envsproduction reads them via data.aws_ecr_repository

Two known hazards of the shared-ECR arrangement are flagged in production/compute.tf:29-36 and remain open: staging churn can expire a prod :<sha> still referenced by a running task, and a staging terraform destroy would delete production's images.

4.3 Declared desired counts

ServiceStagingProduction
api1var.app_replicas = 2
portal12
telemetry12
pgbouncer12
console11
clinic11 — comment still reads "desired_count = 1 (needed to author the demo)"
media10, min_capacity = 0, max_capacity = 4, target_cpu_pct = 70

The media-at-zero problem is structural, not a setting. A CPU target-tracking policy has no CPU metric to track at zero tasks, so production media cannot scale up on its own. New bakes require a manual bump (infra/scripts/production-media-toggle.sh). Serving already-baked Bunny content is unaffected. Production media is also sized cpu = 4096 / memory = 8192 with FFMPEG_THREADS = "4" (the cgroup-vs-host-cores fix); staging is not.

4.4 Crons

Both environments declare the same 8 base crons through the scheduled-tasks module: api-partition-roll and telemetry-partition-roll (daily 02:00 UTC, -ahead=3), usage-quota-reset (monthly, day 1 00:05), usage-summary-rollup (monthly, day 1 03:00), check-providers, expired-sessions-sweep (rate(15 minutes)), session-runs-auto-close (rate(5 minutes)), patient-subscriptions-expiry-sweep (daily 01:00 UTC). Differences:

  • check-providers — staging rate(30 minutes), production rate(1 minute).
  • backup-runner — production only, cron(0 2 * * ? *), behind var.enable_backup_cron which is true in terraform.tfvars as of 51a0492.
  • Production crons carry heartbeat alarm blocks; staging's do not.
  • expire-hard-cap-protocols is in neither (§3.3). CLOSED 2026-08-03 — in both.

4.5 Demo-posture residue in production config

ItemState
DEBUG_DEFAULT_ON (portal kiosk debug overlay)"0" — already flipped for launch
app_replicas = 2Comment reads "flipped to 2 for the demo launch" — the steady-state decision is still open
clinic desired_count = 1Comment reads "needed to author the demo"
media desired_count = 0Comment reads "media can sit at 0 during the demo"
observability.ecs_service_namesExcludes media "add it when it goes live at the real launch" — so a media task that IS running has no CPU/memory alarm
Section headers in compute.tfStill literally say DEMO: desired_count = 0 / DEMO: desired_count = 1

4.6 What is pending an apply

Terraform state is not in the repo, so "pending" here means committed config whose apply cannot be confirmed from the repo. The highest-signal candidates, newest first:

  1. enable_backup_cron = true + the whole storage-backups module (8899911, 51a0492, 9f5f037, 67d2640). The 2026-08-02 drill (0b5670c) proves the pipeline ran against production at least once, so this one is almost certainly applied.
  2. Migration 000039 (166f86d). RESOLVED 2026-08-03 — applied. Checked against the live production database: schema_migrations reads 39, not dirty; principal_is_human carries public.principals; and the function resolves under SET search_path = '' (the restore condition) rather than erroring. The circumstantial reasoning below was correct, but it took a live read to settle — which is the point of this section.
  3. HA NAT + interface-endpoint removal (8660908, documented as applied in 20f3321 / d9f2a92).
  4. ECS Environment tag propagation (07dc5a5, documented as applied to both envs in f93e646) — the $600→$900 budget raise depends on it.
  5. The EventBridge scheduler ecs_parameters.tags fix (3e50ed3) — until this was applied, no cron in either environment had ever run.

5. VERIFICATION NOTES

Everything below is outside the repo's reach. It needs live AWS, DB, Cloudflare or vendor-console access to settle. Treat any doc that asserts one of these without an artefact as unverified.

Database

  • The applied schema version in production. RESOLVED 2026-08-03 — production reads schema_migrations = 39, not dirty. platform-completion.md had said 000038 and the task brief said 000039; the repo could not arbitrate, and a live read settled it. Staging is still unverified.
  • Whether 000039's CREATE OR REPLACE FUNCTION principal_is_human reached production. RESOLVED — it did, confirmed by both the function body and an empty-search_path call returning a value instead of erroring.
  • Whether infra/scripts/000023-skip-note-prod.sql (and any other catch-up DDL) was executed per environment.
  • Row counts and table shape. The only measurement in the repo is the 2026-08-02 restore-drill result recorded in commit 0b5670c: core = 180 tables / 731 indexes / 2,285 constraints / 246 functions / 109 RLS-enabled tables / 292 RLS policies / 371 partitions / 119,865 rows across 62 tables; telemetry = 50,817 rows / 70 partitions. That is a snapshot of one artefact on one day, quoted from a commit message, not a live reading.

Deployed code

  • Which image SHA each production ECS service is running. Services pin immutable :<sha> task-def revisions, so a service can be arbitrarily far behind origin/master. origin/master being 3 commits behind staging says nothing about what is deployed.
  • Whether the 3 staging-only commits, or any of §4.6's items, have been promoted.

Terraform

  • Whether any of infra/envs/{staging,production}/ is applied. State lives in S3; the repo has only the config. Every §4 statement is "declared," not "running."
  • Actual running desired_count values (autoscaling and manual toggles move them independently of terraform.tfvars).
  • Whether the Environment cost-allocation tag was activated in the Billing console — without it the $900 budget tracks $0.

Cloudflare

  • All WAF rules (none are in Terraform).
  • The dashboard-managed rulesets the config explicitly defers to: the www.restartix.pro → apex 301 and the platforma.restartix.ro legacy redirects, both in the http_request_dynamic_redirect phase.
  • Which custom hostnames are registered via Cloudflare for SaaS.

Third-party account state

  • SES: sandbox status, suppression-list state, whether a configuration set has event destinations attached.
  • Clerk: production keys, plan tier, BAA.
  • Bunny: library contents, hotlink-protection settings, whether token auth is even enabled on the pull zone.
  • Daily.co, Sentry, Anthropic: whether accounts/projects exist at all.

Live behaviour

  • Whether the iOS CoreMedia 403 is still occurring. The fix is unbuilt (§2.2); whether the symptom persists needs current video_error telemetry, which is in the telemetry DB, not the repo.
  • Whether the backup-missing alarm has ever fired.
  • Whether any EventBridge cron is currently succeeding (the heartbeat alarms exist in production config; their state is not in the repo).
  • Whether production media has been manually scaled since the last bake.