Skip to content

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_uid resolves according to an approved identity mapping;
  • share_scope uses a supported value and share_list and collaborator_list are arrays of UIDs, not strings or placeholders;
  • every owned recipe has a recipe_box_id resolving 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 matches image_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:

  1. 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.
  2. Create the production myrecipes-user-export runtime service account. Do not create a service-account key.
  3. Grant the project roles in Production Cloud Functions runtime identities: Datastore User, Cloud Tasks Enqueuer, Firebase App Check Token Verifier, and Firebase Authentication Viewer.
  4. 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.
  5. Grant Object Viewer on the application image bucket and Object User only on the dedicated export bucket. Grant no platform recovery-bucket access.
  6. Add reviewed production values for USER_EXPORT_BUCKET and FUNCTIONS_USER_EXPORT_SERVICE_ACCOUNT. Confirm no myrecipes-test value remains in the deployment diff. Do not add a manually maintained application version: archive manifests use the worker's platform-provided Cloud Run K_REVISION.
  7. Verify the generateUserBackupExportTask queue has the reviewed retry, concurrency, rate limits, dispatch identity, and worker invocation access.
  8. Verify the hourly cleanup_user_backup_exports scheduler 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:

  1. Capture the production configuration and IAM baseline.
  2. Complete the read-only data compatibility inventory.
  3. Provision and verify bucket, lifecycle, service account, IAM, and parameters.
  4. Run locked Functions, Firestore-rule, Storage-rule, and Flutter tests.
  5. Deploy required Firestore and Storage rules under their migration plan.
  6. Deploy only request_user_backup_export, get_user_backup_export, cancel_user_backup_export, create_user_backup_download, generateUserBackupExportTask, download_user_backup_export, and cleanup_user_backup_exports.
  7. Verify every revision uses the production export identity, then verify queue and scheduler targets.
  8. 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_queued appears. A successful request without worker activity requires immediate queue/IAM investigation.
  • Confirm user_backup_export_finished appears 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.