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
stagingat0b5670c, with an in-flight docs-correction pass staged by parallel agents (63 modified/deleted paths, all underapps/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
appointmentstable 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 as | Now | Where |
|---|---|---|
| F1 Specialists — no tables, no domain, no routes | SHIPPED 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 rows | Three 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 rows | Two 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-03 — restartix-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 disabled | 8 of 20 — /specialists and /locations are live and permission-gated. | §3 leftovers |
000039 application to production unverifiable from the repo | Verified applied 2026-08-03 against the live DB (schema_migrations = 39, not dirty). | §5 |
| 39 migrations | 41 — 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.thisshowsrds.force_sslmovingapply_methodpending-reboot→immediate(value unchanged at1, 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:
| Group | Packages |
|---|---|
| Identity & tenancy | human, patients, patientprofiles, organization, orgsettings, orgbilling, orgentitlements, designations, invites, ownershiptransfer, sharelinks, portalonboarding, handoff |
| RBAC & platform ops | permissions, roletemplates, platformmemberships, breakglass, impersonation, audit |
| Commercial | tiers, subscriptions, patienttiers, patientsubscriptions, accessoffers, patientcontentgrants, catalog |
| Clinical content | exercises, exercises_taxonomy, programs, sessions, protocols, adherence (library only — no handler), stats |
| Session runtime | sessionpairings, librarytoken (handler only — one endpoint, no DB writes) |
| Pose | pose_configs, poseengines |
| Integrations & platform infra | integrations, platformproviders, webhooks, aimodels, location, consents, legaldocument |
39 migrations, 000001_init → 000039_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 packages — internal/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;
ConnFromContextin every repo;current_app_has_permission(resource, action)in policies. - P47 —
middleware.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) ininternal/shared/classification/types.go:17-40; ~1,229 column rows in the data-classification.md registry.cmd/check-classificationpasses as of this snapshot and fails the build on an unregistered column. - SOUP — ~87 dependency rows in soup.md;
cmd/check-souppasses 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 encryption —
internal/core/crypto(AES-256-GCM, versioned wire format). Exactly three encrypted column families exist today, allauth_secret:platform_service_providers.credentials_encrypted,outbound_webhook_subscriptions.signing_secret{,_previous}_encrypted,organization_integrations.credentials_encrypted+inbound_signing_secret_encrypted. Exactly onepii_regulatedcolumn 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 framework —
internal/core/capabilitieswithWrapInternalfor Internal Library capabilities. - Curated-provider catalog (Cat A) —
platform_service_providerswithchk_psp_capability_providerwhitelisting exactlyemail→ses,storage→aws_s3,auth→clerk(000015:119-124). - Connected-account catalog (Cat B) — 2 seeded rows,
woocommerceandmerchantpro, bothauth_type='api_key',status='available'(000036). - Email — live SES
EmailChannel(notFakeChannel) constructed incmd/api/main.goinitNotify, registered asnotifyemail.NewScopedChannel. 5 notification templates × 2 locales ininternal/core/notify/templates/. - S3 surfaces — registry in
internal/integration/s3/surfaces.go:signatures,documents,forms-upload,logos,appointment-files,exercise-assets. OnlySurfaceLogoshas non-test call sites (4, all org branding).SurfaceExerciseAssetsis referenced by the media service by bucket-name convention rather than through this registry.SurfaceSignatures,SurfaceDocuments,SurfaceFormsUploadandSurfaceAppointmentFileshave zero consumers — registered and reserved for F1/F3/F5/F6, which is exactly the shape those features need. - Redis primitives —
internal/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 000008 — profile_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
| App | Surfaces | Notes |
|---|---|---|
apps/clinic/ | 30 pages, 16 route handlers, 3 layouts | Dashboard, 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 layouts | Patient 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 layouts | Clinics (+ 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/HTML | No 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 + CSS | Apex marketing page. Production only — no staging bucket, no staging deploy job |
apps/docs/ | VitePress | architecture/, 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) isopenapi-typescriptoutput fromapps/docs/openapi.yaml;catalog.ts,programs.ts,protocols.ts,pose.ts,taxonomy.ts,sessions.ts,content-grants.tsare hand-written because those domains are not in the spec (see §3). Alsocache-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.
| Feature | Tables the specs call for | Actual state | Which doc implies otherwise |
|---|---|---|---|
000040) | specialties, specialists, specialist_specialties — specialist_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 |
000041) | offerings, offering_specialists — offering_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 Forms | custom_fields, custom_field_versions, custom_field_values, form_templates, form_template_versions, forms | form_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 Scheduling | specialist_weekly_hours, specialist_schedule_overrides, calendars, calendar_specialists, calendar_forms | specialist_weekly_hours appears in zero migrations. calendars appears in 2, both comments. btree_gist is not enabled — 000001 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 Appointments | appointments, appointment_files, appointment_reviews | The 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 Documents | pdf_templates, pdf_template_versions, pdf_template_components, appointment_documents | pdf_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 at000002:1730("add the relevant verbs (forms.*, appointments.*, etc.) as those…"). humanshas no name column.000002:292-322definesprovider_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.AppointmentCounteris never wired. The interface is defined atcadence.go:116and the only construction site passesnil—stats/service.go:248: "AppointmentCounter is nil until the appointments substrate…". Supervised protocols returnErrSupervisedNotImplemented.video/dailyis not in the curated-provider whitelist.chk_psp_capability_providerpermits onlyemail,storage,auth. F5's Daily.co integration needs anALTERon a prod-applied constraint.- No
SurfaceAvatarsin the S3 registry;SurfaceLogosis org branding and must not be overloaded. - No
ResolveSchedulingTimezone— grep acrossinternal/returns nothing. P23's fallback chain has no implementation. - No
GET /v1/reference/timezones— noreferenceroute group exists at all. - No
pdf.Renderercapability and no PDF engine dependency ingo.mod.
2.2 F11 / compliance
| Gap | Verified state | Doc that implies otherwise |
|---|---|---|
| DSAR export route | Zero 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-end | internal/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 automation | No archive job, no purge job, no cron. The audit_archive S3 bucket is provisioned in both environments and has zero code consumers — grep -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 control | break_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 handling | No 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) |
| Sentry | Wired 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 fix | No 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 hostnames | No 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 backups | storage-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/returnsapi,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
migrateacross.github/workflows/*.ymlreturns nothing — neither deploy workflow applies migrations. Both environments ship amigrations_runnerECS task definition (aws_ecs_task_definition.migrations_runner+.telemetry_migrations_runner) that must be invoked by hand viaaws ecs run-taskwith acontainerOverrides.command, per deployment.md:145. Schema advancement in every environment is a manual step. - No
restore-drillcron./bin/restore-drillexists andinfra/scripts/restore-drill.shdrives it, but it is wired to no schedule.restore-drill.shaccepts astagingargument even though staging has nostorage-backupsmodule — that invocation cannot work.
2.4 Other asserted-but-absent
- Per-org audit-log read route.
audit_log.view_orgis a seeded permission with RLS behind it, but the only mounted route is superadmin-onlyGET /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-logentry isdisabled: true. - Pose-frame ingest. No
pose_frames/session_pose_framestable 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_sectionsorcatalog_entriesrow. - CSV/bulk export surface. A
bulk_exportegress target and abulk_exportentitlement both exist in the schema; no export route exists —grep -n "export" apps/docs/openapi.yamlreturns 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. ACardwith 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:35branches ondisabled), so they are honest placeholders rather than 404s — but half the clinic app's information architecture is a promise. Note/subscriptionsand/locationsare 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/offeringswhen F2.1 ships, and the slot should be renamed in the same PR rather than carried forward./suppressionis a nav slot for a table that does not exist (§2.2).
3.2 Diagnostic and demo-era code still in the production apps
/demois 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.tsredirects to/demo?ticket=…at lines 64 and 161, andproxy.ts:47lists"/demo(.*)"as a public route.DEMO_NAME_COOKIE = "demo-name"is set by/enterand/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 fullgetMe()payload),app/dev/tv("Delete this route once kiosk compat lands"). /dev/*bypasses the portal proxy entirely —proxy.ts:90:if (request.nextUrl.pathname.startsWith("/dev/")) return NextResponse.next();. That makes/dev/empty,/dev/perf,/dev/telemetry-pocand/dev/tvunauthenticated 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
CLOSED 2026-08-03 — built intocmd/expire-hard-cap-protocolsis scheduled by nothing.Dockerfile.apiand 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)" — butgrep "/bin/expire-hard-cap-protocols" infra/envs/returns zero hits in both environments. Hard-capped protocols never flip toended. 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 ONLYbanner listing the resources to build. No environment references it.audit_archiveS3 bucket is created in both environments and consumed by nothing (§2.2).apps/docs/features/scheduling/go/— 6.gofiles (availability.go,assignment.go,types.go,holds.go,ip.go,ratelimit_ip.go) sitting inside the docs tree, in no Go module (go.modexists only atservices/{api,telemetry,media}). They are therefore never compiled, never vetted, never tested bymake check-all, and carry zero_test.gofiles. 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:
| Domain | Routes registered | In openapi.yaml |
|---|---|---|
programs | 18 | 0 |
pose_configs | 20 | 0 |
exercises_taxonomy | 49 | 0 |
patientcontentgrants | 3 | 0 |
catalog | 11 | 0 (the word appears 65× in descriptions, never as a path) |
protocols | 11 | 0 — 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} | 2 | 0 |
TV tv-liveness / tv-state | — | 0 |
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)
| Doc | Open checkboxes | Done |
|---|---|---|
| foundation.md | 98 — 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 each | 410 |
| features.md | 259 | 41 |
| june-launch.md | 51 | 30 |
| patient-catalog-and-access.md | 29 | 18 |
| tv-baseline-mp4.md | 21 open, 0 done — a plan that was never started or never ticked | 0 |
| june-demo.md | 0 | 41 |
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/FIXMEcomments in all ofservices/(excluding tests):principal/subject.go:248(layer-2-plusentitlement resolution),principal/subject.go:267(layer-5-plususage counters),exercises/service.go:23(multi-language— locale hardcoded to"ro"). Zero inapps/{clinic,portal,console}source andpackages/(the ~40 hits underapps/portal/public/mediapipe/are vendored Emscripten output). organization_settings.feature_flagsis 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) andapps/docs/features/treatment-plans/(5 files), and updatedglossary.md(theenrollmentsreservation is retired;services → offeringsis marked in flight withoffering_packagesas 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 HEAD | 0b5670c — 2026-08-02 10:47 |
origin/master HEAD | 0328038 "Merge pull request #44 from RestartiX/staging" — 2026-08-02 09:38 |
| staging ahead of master | 3 commits — afc9cde, c6e5e57, 0b5670c (all restore-drill fixes + the doc closing the backup blocker) |
| master ahead of staging | 10 commits, all merge commits — git 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 master | 8d5eba4 (2026-05-15) — 695 commits behind origin/master (688 behind origin/staging); do not read it as production's code |
| Staging deploy | workflow_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 deploy | workflow_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
| Dimension | Staging | Production |
|---|---|---|
| VPC CIDR | 10.10.0.0/16 | 10.20.0.0/16 |
| Database | Aurora Serverless v2, min_capacity = 0, max_capacity = 2 | RDS db.t4g.medium, multi_az = true, 50 GB → 200 GB autoscale, 7-day PITR |
| Redis | cache.t4g.micro | cache.t4g.small |
| Terraform modules | 7 distinct | 9 distinct — adds observability and storage-backups |
| Alarms | Hand-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 |
| Budget | none | aws_budgets_budget at $900/mo, alerts at 50/80/100% actual + 100% forecast → SNS → webmaster@restartix.com |
| Layer-2 backups | none | daily 02:00 UTC, core + telemetry, envelope-encrypted, absence-alarmed |
Landing site (apps/landing/) | not deployed — no bucket, no deploy job | apex restartix.pro, S3 website behind Cloudflare flexible-SSL |
| TV site | staging.restartix.tv bucket | apex restartix.tv bucket |
| Cloudflare zone singletons | staging's state owns them for both envs — cloudflare_zone_settings_override, cloudflare_managed_headers, and the restartix_pro_flexible_ssl / restartix_tv_flexible_ssl / restartix_tv_www_redirect rulesets | production declares none of them, by design |
| ECR repos | staging's state owns them, force_delete = true, lifecycle "keep last 20 tagged" counting both envs | production 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
| Service | Staging | Production |
|---|---|---|
api | 1 | var.app_replicas = 2 |
portal | 1 | 2 |
telemetry | 1 | 2 |
pgbouncer | 1 | 2 |
console | 1 | 1 |
clinic | 1 | 1 — comment still reads "desired_count = 1 (needed to author the demo)" |
media | 1 | 0, 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— stagingrate(30 minutes), productionrate(1 minute).backup-runner— production only,cron(0 2 * * ? *), behindvar.enable_backup_cronwhich istrueinterraform.tfvarsas of51a0492.- Production crons carry
heartbeatalarm blocks; staging's do not. CLOSED 2026-08-03 — in both.expire-hard-cap-protocolsis in neither (§3.3).
4.5 Demo-posture residue in production config
| Item | State |
|---|---|
DEBUG_DEFAULT_ON (portal kiosk debug overlay) | "0" — already flipped for launch |
app_replicas = 2 | Comment reads "flipped to 2 for the demo launch" — the steady-state decision is still open |
clinic desired_count = 1 | Comment reads "needed to author the demo" |
media desired_count = 0 | Comment reads "media can sit at 0 during the demo" |
observability.ecs_service_names | Excludes 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.tf | Still 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:
enable_backup_cron = true+ the wholestorage-backupsmodule (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.MigrationRESOLVED 2026-08-03 — applied. Checked against the live production database:000039(166f86d).schema_migrationsreads 39, not dirty;principal_is_humancarriespublic.principals; and the function resolves underSET 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.- HA NAT + interface-endpoint removal (
8660908, documented as applied in20f3321/d9f2a92). - ECS
Environmenttag propagation (07dc5a5, documented as applied to both envs inf93e646) — the $600→$900 budget raise depends on it. - The EventBridge scheduler
ecs_parameters.tagsfix (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 readsschema_migrations= 39, not dirty.platform-completion.mdhad said000038and the task brief said000039; the repo could not arbitrate, and a live read settled it. Staging is still unverified.WhetherRESOLVED — it did, confirmed by both the function body and an empty-000039'sCREATE OR REPLACE FUNCTION principal_is_humanreached production.search_pathcall 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 behindorigin/master.origin/masterbeing 3 commits behindstagingsays 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_countvalues (autoscaling and manual toggles move them independently ofterraform.tfvars). - Whether the
Environmentcost-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 → apex301 and theplatforma.restartix.rolegacy redirects, both in thehttp_request_dynamic_redirectphase. - 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
403is still occurring. The fix is unbuilt (§2.2); whether the symptom persists needs currentvideo_errortelemetry, which is in the telemetry DB, not the repo. - Whether the
backup-missingalarm has ever fired. - Whether any EventBridge cron is currently succeeding (the
heartbeatalarms exist in production config; their state is not in the repo). - Whether production media has been manually scaled since the last bake.
Related documentation
- platform-completion.md — the active plan this inventory serves
- leo-port-map.md — the survey behind the F1–F6 port
- foundation.md — authoritative for 1A–1E checkbox status
- production-launch-readiness.md — the operational gate
- data-model.md · patterns.md · glossary.md