# MediaRouter production deployment gate ## Deployment contract The only backend production target is a Hugging Face Docker Space. The root README declares `sdk: docker` and `app_port: 7860`; the single production `Dockerfile` uses `python:3.10-slim`, installs the pinned `requirements.txt`, runs as UID 1000, exposes port 7860, and starts one Uvicorn process on `0.0.0.0:7860`. Android/Termux Python is not part of this contract. `APP_ENVIRONMENT=production` is set by the image. Production configuration is validated when FastAPI is imported, before a database engine starts serving requests. Local development retains the existing SQLite defaults because it uses `APP_ENVIRONMENT=development`. ## Required Hugging Face configuration Configure secrets in the Space settings, never in source or Docker layers. Values below are illustrative and are not usable credentials. ```env DATABASE_URL=postgresql+asyncpg://:@/ SECURITY_DATABASE_ROLE=mediarouter_security_service SECURITY_ENFORCE_RLS=true SECURITY_AUTO_MIGRATE=false AUTH_ENABLED=true CORS_ALLOWED_ORIGINS=https://.vercel.app BASE_URL=https://-.hf.space SOCIAL_AUTO_MIGRATE=false ``` `DATABASE_URL` must be external PostgreSQL/Supabase and must authenticate as the configured backend-only security role. SQLite and localhost database URLs are rejected in production. The security role must have the role attributes required by the existing forced-RLS execution-boundary check. When `SOCIAL_ENABLED=true`, also configure the existing separated RLS roles: ```env SOCIAL_DATABASE_URL=postgresql+asyncpg://:@/ SOCIAL_TENANT_DATABASE_ROLE=mediarouter_tenant SOCIAL_WORKER_DATABASE_URL=postgresql+asyncpg://:@/ SOCIAL_WORKER_DATABASE_ROLE=mediarouter_social_worker SOCIAL_ENFORCE_RLS=true ``` The tenant connection must be non-owner/non-superuser/non-`BYPASSRLS`; the worker connection must be the explicitly named backend-only `BYPASSRLS` role. Set `SOCIAL_ENABLED=false` when deploying only core/project APIs and those social database boundaries have not been provisioned. Missing YouTube, Meta, TikTok, X, LinkedIn, Telegram, or WhatsApp provider credentials do not prevent startup. Their adapters report controlled unavailability when invoked. WAN and FLUX are also optional. Leaving their URL/token pairs empty keeps them unavailable. Invalid optional worker identity/readiness never makes a model available and does not stop FastAPI. `CORS_ALLOWED_ORIGINS` is a comma-separated list of exact origins. Production requires HTTPS and rejects `*`, credentials embedded in URLs, paths, query strings, and fragments. Add every Vercel origin that is allowed to call the backend directly. The normal frontend path remains its same-origin server-side BFF, so backend keys are never exposed to browsers. ## Explicit migration order Normal application startup never applies PostgreSQL migrations. Use a controlled administrative migration connection and apply the additive files in this dependency order before starting the Space: 1. `app/security/migrations/0001_api_key_security.sql` 2. `app/security/migrations/0002_authoritative_tenancy_postgres.sql` 3. `app/projects/migrations/0001_projects_foundation.sql` 4. `app/security/migrations/0003_generation_domain_postgres.sql` 5. `app/security/migrations/0004_generation_provider_runtime_postgres.sql` 6. `app/projects/migrations/0002_project_resources.sql` 7. `app/projects/migrations/0003_editor_persistence_rendering.sql` 8. `app/projects/migrations/0004_ai_studio.sql` 9. `app/projects/migrations/0005_ai_copilot.sql` 10. `app/projects/migrations/0006_template_marketplace.sql` 11. `app/social/migrations/0001_social_foundation_postgres.sql` 12. `app/social/migrations/0002_social_rls.sql` 13. `app/social/migrations/0003_social_integrity_postgres.sql` 14. `app/social/migrations/0004_youtube_media_assets.sql` 15. `app/social/migrations/0005_x_text_posts.sql` 16. `app/social/migrations/0006_linkedin_oauth_account_type.sql` 17. `app/social/migrations/0007_tenant_asset_and_rls_hardening.sql` Keep `SECURITY_AUTO_MIGRATE=false` and `SOCIAL_AUTO_MIGRATE=false`. The security startup gate checks tables and safety indexes. It then verifies the configured PostgreSQL execution role before generation, projects, workers, or social scheduling can serve traffic. ## Health, readiness, and startup failure behavior `GET /health` is public and process-level. It reports only safe dependency availability and version metadata; external AI credentials are not required. The Docker health check calls it over `127.0.0.1:7860` from inside the container. The authoritative security database and complete migrated security/project schema are required for startup. A missing connection, missing schema object, or invalid RLS role produces a clear startup failure. Social database/provider failures are contained by the existing social readiness boundary; social APIs then return controlled unavailability. Generation providers are initialized lazily by the bounded background dispatcher. Cleanup, generation, and social workers respect cancellation, catch iteration failures, and cannot enter tight retry loops. ## Filesystem lifecycle `TEMP_DIR` and `OUTPUT_DIR` contain bounded working files and expiring media outputs. They are not durable merely because their metadata is in PostgreSQL. The cleanup service removes them after `CLEANUP_MINUTES`, and container rebuilds may remove them sooner. If an installation needs media bytes beyond that lifecycle, attach Hugging Face persistent storage and point the directories at its mounted path, or implement/configure an authoritative object-storage adapter in a separate storage phase. The current deployment gate does not claim durable object storage. ## Docker smoke gate Create a non-committed production env file whose database has already received the migrations, then run: ```bash scripts/docker_deployment_gate.sh /absolute/path/production.env ``` Optionally pass a disposable API key as the second argument to validate an authenticated `GET /v1/auth/context`. Without a key, the smoke test requires that endpoint to fail closed with HTTP 401. The gate: 1. builds the actual root Dockerfile; 2. starts it with the supplied production environment; 3. waits for Docker health; 4. checks `/health` and `/openapi.json`; 5. verifies every project and project-resource operation in runtime OpenAPI; 6. verifies social and generation route families; 7. verifies authentication middleware; and 8. proves that the container remains alive. The HTTP portion can also target a deployed Space: ```bash python3 scripts/deployment_smoke.py \ --base-url https://-.hf.space \ --api-key '' ``` The script never prints the key. Do not use a production administrator key in shared CI logs or shell history. ## Vercel frontend Set the Vercel server-only `MEDIAROUTER_API_URL` to the Space HTTPS origin. `MEDIAROUTER_MCP_URL` may use the same origin. In Vercel production, `backendBaseUrl()` rejects HTTP and loopback/localhost upstreams. Browser calls continue to use `/api/backend/*`, where the authenticated BFF attaches only a server-side role credential. OpenAPI project capability discovery therefore uses the real runtime backend and requires no frontend override. ## Operator acceptance gate Do not mark the Space ready from source inspection alone. Acceptance requires an actual Docker build, live container startup, `0.0.0.0:7860` binding, health response, and runtime OpenAPI response using production Python 3.10/Linux. A host without Docker and an accessible migrated PostgreSQL database must report these checks as blocked, not passed.