Structured user export production migration guide
This guide is the production-promotion checklist for the private structured user export implemented by issue #378. It supplements the controlling production gate in issue #391 and the shared backup/recovery guide.
Status: Not authorized or deployed in production. Complete this guide only from an approved, reviewed commit after the remaining release gates are satisfied. Development validation does not authorize a change to
myrecipes-e8640.
What must and must not be promoted
Promote the reviewed application and Functions implementation, rules, private archive infrastructure, least-privilege identities, monitoring, and operational procedures. Replace all development resource names and App Check configuration with production-specific values.
Do not copy development users, email-verification overrides, complimentary
entitlements, administrator grants, debug App Check tokens, fixture data,
notification destinations, service-account identities, bucket names, or
myrecipes-test configuration into production.
Production data compatibility gate
Development testing exposed legacy relationship shapes that can prevent an otherwise valid owner from exporting recipes. Before deployment, run a read-only production inventory and retain aggregate results. Confirm:
- account documents use the canonical UID-keyed path where the app requires it;
- each recipe box document ID is the string form of its business
id, because authorization performs a direct document lookup; owner_uidresolves according to an approved identity mapping;share_scopeuses a supported value andshare_listandcollaborator_listare arrays of UIDs, not strings or placeholders;- every owned recipe has a
recipe_box_idresolving to an existing canonical recipe-box document; - dependent recipe records reference an existing owned recipe; and
- each included image uses
users/{uid}/recipes/{recipeId}/{objectId}and exactly matchesimage_url.
Record inspected, conforming, nonconforming, and dangling counts. Do not rewrite
production creator_uid, infer ownership from it, or apply development fixture
mappings. Per #374, recipe ownership comes from the owned recipe-box
relationship, not creator_uid.
If incompatible production data exists, stop and use a separately reviewed migration under the Firestore rules and data migration runbook. Require an approved identity mapping, dry-run output, a recent recoverable backup, preserved counts, an audit record, post-write validation, and rollback. Issue #392 controls dangling-relationship remediation. Recipe images remain separately protected under #376.
Infrastructure and IAM gate
Create production-specific resources before deploying an export endpoint:
- Create a dedicated private production export bucket with uniform bucket-level access, public-access prevention, and a one-day object lifecycle as a backstop to application cleanup.
- Create the production
myrecipes-user-exportruntime service account. Do not create a service-account key. - Grant the project roles in Production Cloud Functions runtime identities: Datastore User, Cloud Tasks Enqueuer, Firebase App Check Token Verifier, and Firebase Authentication Viewer.
- Grant the runtime identity Service Account User on itself. Task dispatch uses that identity; without this binding the request callable fails before the worker starts.
- Grant Object Viewer on the application image bucket and Object User only on the dedicated export bucket. Grant no platform recovery-bucket access.
- Add reviewed production values for
USER_EXPORT_BUCKETandFUNCTIONS_USER_EXPORT_SERVICE_ACCOUNT. Confirm nomyrecipes-testvalue remains in the deployment diff. Do not add a manually maintained application version: archive manifests use the worker's platform-provided Cloud RunK_REVISION. - Verify the
generateUserBackupExportTaskqueue has the reviewed retry, concurrency, rate limits, dispatch identity, and worker invocation access. - Verify the hourly
cleanup_user_backup_exportsscheduler exists and the bucket lifecycle remains an independent deletion backstop.
Retain resource names, IAM policies, lifecycle and queue configuration, and the approver in the #391 promotion manifest.
App Check and client gate
- Register and validate production App Check providers for every released client platform. Never reuse a development debug token.
- Complete the App Check installation verification from the reviewed production build before exercising export.
- Confirm Authentication and App Check protect request, status, cancellation, download authorization, and the HTTP download session.
- Confirm invalid, missing, replayed, expired, and cross-user credentials are denied without logging tokens or signed URLs.
- Ship the structured-backup screen's shared persistent error presentation. Broader error-message standardization remains tracked by issue #323.
Ordered deployment
Follow the targeted practices in the shared Firebase Functions reference and production operations runbook. From a clean worktree at the approved commit:
- Capture the production configuration and IAM baseline.
- Complete the read-only data compatibility inventory.
- Provision and verify bucket, lifecycle, service account, IAM, and parameters.
- Run locked Functions, Firestore-rule, Storage-rule, and Flutter tests.
- Deploy required Firestore and Storage rules under their migration plan.
- Deploy only
request_user_backup_export,get_user_backup_export,cancel_user_backup_export,create_user_backup_download,generateUserBackupExportTask,download_user_backup_export, andcleanup_user_backup_exports. - Verify every revision uses the production export identity, then verify queue and scheduler targets.
- Release the reviewed client only after the backend canary passes.
Do not combine this with unrelated subscription, Apple, account-deletion, or disaster-recovery changes unless the approved #391 manifest includes them.
Production canary and evidence
Use an approved non-destructive canary account with representative owned data. Do not alter ownership or sharing merely to make the canary pass.
- Request one export and record its operation ID, timestamps, revisions, and runtime identity without recording recipe content.
- Confirm
user_backup_export_queuedappears. A successful request without worker activity requires immediate queue/IAM investigation. - Confirm
user_backup_export_finishedappears with expected aggregate counts and no enqueue or worker failure event. - Download once, validate against the v1 contract, and confirm replay and cross-user attempts are rejected.
- Compare aggregate box, recipe, child-record, and image counts and confirm only owned #374 content and canonical images are included.
- Confirm expiry within 24 hours and that cleanup plus the bucket lifecycle leave no stale public or user-readable artifact.
- Exercise App Check-invalid and unauthenticated denials.
- Review logs for IAM, permission, queue, retry, timeout, memory, and cleanup failures through the approved observation window.
Attach sanitized output, configuration diffs, counts, log-query links, archive validation, cost observations, and approver sign-off to #391.
Stop conditions and rollback
Stop without broadening IAM if the inventory is incomplete, an unexpected relationship shape exists, App Check is invalid, a revision uses the wrong identity, a request is accepted but its worker does not start, an archive crosses an ownership boundary, cleanup fails, or counts differ.
Rollback by preventing new requests through the approved operational control, redeploying last-known-good reviewed revisions and rules, and removing only new temporary artifacts after verifying their exact paths and retention needs. Do not delete or rewrite customer recipe data as an export rollback. Preserve logs and operation metadata and record the rollback in #391.