Skip to content

Backup, recovery, export, and import guide

This is the living implementation and operations guide for epic #372. Update it in the same change as every backup/recovery child issue. It records approved decisions, implementation details, validation evidence, and eventual operator instructions in one place.

Current status: Structured user export/replacement and platform recovery are implemented and exercised in myrecipes-test. Production promotion remains gated by #391 and is not operational.

Purpose

The project has two separate recovery systems:

  1. Platform disaster recovery protects the complete Firestore database and recipe-image objects from a platform incident. It is operated by an administrator and restores into an isolated environment first.
  2. User export and replacement import allows an authenticated user to download portable recipe content and later replace their own recipe content from that application-generated archive.

The readable cookbook PDF in #153 is not a structured backup and cannot be used for replacement import.

Release scope and issue map

Issue Responsibility Status in this guide
#374 Authoritative Firestore and Storage boundary Implemented locally; see Recipe backup scope
#375 Versioned structured archive contract Implemented locally; see Archive v1 specification
#376 Recipe-image disaster protection Implemented and exercised in myrecipes-test; production promotion pending
#377 Daily Firestore backups and monitoring Validated in development; production promotion remains gated by #391
#378 Private structured user export Implemented, deployed, and exercised in myrecipes-test; production promotion pending
#379 Upload, validation, and replacement preview Implemented and exercised in myrecipes-test
#381 Recoverable full-replacement import Implemented and exercised in myrecipes-test
#380 End-to-end user recovery exercise Complete in development
#382 Isolated platform recovery exercise and runbook Complete in development; see dated evidence

Recommended implementation order:

  1. 374

  2. 375, #376, and #377 as dependencies allow

  3. 378 and #379

  4. 381

  5. 380 and #382

Approved principles

  • Recovery is considered valid only after a restoration exercise succeeds.
  • Platform recovery-point objective is no more than 24 hours.
  • User export/import covers owned recipe content, not the entire account.
  • Import is full replacement, never merge.
  • The complete archive is validated before current content changes.
  • Destructive replacement requires a verified safety export and separate, explicit user confirmation.
  • Firestore and Storage cannot be committed atomically; replacement must use a recoverable, idempotent state machine.
  • Archives, temporary uploads, platform backups, and recovery copies remain private.
  • Backend code assigns ownership. Archive ownership fields and Storage paths are untrusted input.
  • Logs contain operation identifiers, status, counts, timing, and error codes, but never recipe content or image bytes.

Authoritative user-content boundary

The typed source of truth is functions/src/recipe_backup_scope.ts; its human-readable inventory is Recipe backup and replacement scope.

Summary:

  • Recipe-box ownership is established by owner_uid.
  • Recipes inherit ownership from recipe_box_id; creator_uid and shared access do not establish ownership.
  • Directions, ingredients, notes, private notes, and categories inherit from the owned recipe.
  • Personal metadata is included only when it belongs to the authenticated user and references that user's owned recipe.
  • User-created measures and ingredients use their existing user_id fields.
  • The archive never restores memberships. Under the approved #381 v1 policy, imported boxes are private and current collaborator access to replaced owned boxes is removed after explicit preview/confirmation disclosure.
  • Shopping lists, account/Auth data, subscriptions, purchases, invitations, usage controls, and administrative/audit data remain unchanged.

Recipe images are owned only when the canonical tenant path, owned Firestore recipe relationship, and exact image_url reference agree:

users/{uid}/recipes/{recipeId}/{objectId}

The shared placeholder, external/local references, legacy objects, missing objects, cross-tenant paths, and orphaned objects follow the classifications in the scope document.

User export

Status: Archive contract implemented in #375; the #378 workflow is deployed and successfully exercised only in myrecipes-test. Production promotion is separate release work.

Running a user structured export

In a development build connected to myrecipes-test:

  1. Sign in as a test user that owns recipe data and canonical recipe images.
  2. Open Account & Settings → Backup & export → Structured recipe backup.
  3. Select Create structured backup. The app polls the bounded status callable and does not depend on realtime synchronization.
  4. Wait for Complete and ready to download. Select Download structured backup and choose a local destination.
  5. Confirm the ZIP validates against the v1 contract and contains no other user's records or images.

On iOS and Android, the client streams the authorized archive into private temporary application storage and then supplies the completed bytes to the native save dialog, as required by the mobile file-picker API. The temporary copy is deleted after save, cancellation, or failure. Desktop platforms choose the destination first and stream directly to it.

The archive is private and retained for 24 hours. Download requires Firebase Authentication and App Check, a single-use 10-minute redemption token, and a bounded ranged-download session. A structurally valid incomplete result is labelled Incomplete export — cannot be restored, requires explicit acknowledgement, and is rejected by import validation.

The exporter includes only the authoritative #374 recipe boundary. Shopping lists, account/profile and Auth data, subscription/purchase state, sharing invitations and memberships, usage/rate-limit state, and administrative audit or entitlement records remain excluded.

Maintainer verification:

cd functions
npm test
npm run test:user-backup-export

The integration suite creates reciprocal two-user sharing fixtures, builds a real streaming ZIP with an owned Storage image, validates it against #375, and tests recovery-allowance idempotency plus owner-bound, single-use download authorization.

Development archives are stored in the uniform-access, public-access-prevented myrecipes-test-user-exports bucket. The export runtime is read-only on the application image bucket and has object management access only to this dedicated archive bucket. The hourly cleanup Function enforces the application expiry; the bucket's one-day lifecycle is a deletion backstop.

Production promotion is controlled by #391 and the structured user export production migration guide. It captures the production data inventory, private bucket, least-privilege IAM (including Service Account User on the task identity itself), App Check, targeted deployment, canary, evidence, stop conditions, and rollback. Never copy development accounts, entitlements, debug tokens, fixtures, or resource identifiers into production.

The intended server-side flow is:

  1. Authenticate and authorize the user under the approved recovery/export entitlement policy.
  2. Create a unique, user-bound export operation.
  3. Read owned recipe boxes, then recipes and their dependent records, using the authoritative scope.
  4. Verify every Firestore relationship and exact owned image object.
  5. Build the versioned archive with archive-local IDs and actual image bytes.
  6. Calculate manifest counts and SHA-256 checksums.
  7. Store the archive at a private, operation-scoped temporary path.
  8. Provide bounded, short-lived authorized download access.
  9. Expire the archive and operation metadata according to policy.

The archive format, filenames, schemas, limits, completeness marker, timestamp normalization, and compatibility rules are defined in the v1 archive specification.

An archive with a missing or unreadable required image may be offered only if the approved format clearly marks it incomplete. An incomplete archive is not a valid import or pre-replacement safety backup.

User export instructions

Not available yet. Add the final Settings navigation, progress behavior, download handling, cancellation rules, expiry, and troubleshooting steps when

378 ships.

User replacement import

**Status: Upload, validation, preview, confirmation, recoverable replacement, and the repeatable recovery exercise are implemented for development by

379–#381. Production promotion remains gated by #391.**

The intended flow is:

  1. Require an authentication time no more than 15 minutes old before enabling ZIP selection. Reopening the app or refreshing an ID token does not count as signing in again. A stale session must be stopped before upload or validation resources are allocated. The development project intentionally uses a two-hour window to support repeated recovery exercises; production remains 15 minutes.
  2. Create a user-bound import operation and private upload destination.
  3. Upload without modifying active recipe content.
  4. Validate the complete archive: structure, normalized paths, schema/version, checksums, relationships, required media, MIME types, counts, and size/ expansion limits.
  5. Show backup date/version, counts, warnings, and the full-replacement warning.
  6. Recheck the 15-minute authentication window and require a separate explicit confirmation action. The backend independently enforces the same window.
  7. Create and verify a complete private safety export of current content.
  8. Lock or detect concurrent recipe writes through a trusted backend mechanism.
  9. Stage documents and images with trusted ownership and controlled paths.
  10. Verify staged records, relationships, counts, and image bytes.
  11. Activate the new authoritative dataset using the approved #381 commit strategy.
  12. Verify active content before reporting success.
  13. Retain the safety export and clean up uploaded, staged, superseded, and operation artifacts according to policy.

The approved import state machine, lock/lease behavior, activation strategy, rollback, cleanup retention, failure boundaries, and development exercise are defined in the replacement commit strategy. The strategy is a design contract until #381 implementation and acceptance evidence are complete.

Replacement failure-injection verification

Maintainers do not manually terminate the mobile app, edit operation documents, or stop Cloud Tasks to prove interruption safety. Run the deterministic emulator matrix from the repository instead:

cd functions
npm run test:user-backup-replacement-failures

The command starts isolated Firestore and Storage emulators and runs serially. It injects a failure immediately before and after each named forward and rollback boundary. Every case must prove one of these outcomes:

  • a pre-mutation failure leaves the original logical dataset available and the other owner unchanged;
  • a post-mutation failure remains blocked and redelivery completes exact rollback;
  • an uncertain response after atomic publication preserves the terminal ledger and redelivery returns the same terminal result.

Expected successful output ends with tests 38, pass 38, and fail 0. Any failure is release-blocking. Do not substitute a simulator-only happy-path test for this command, and never run these injected failures against production.

User import and recovery instructions

The operator-facing deployed exercise, simulator flow, validation expectations, temporary edit lock, completion criteria, rollback evidence, cleanup policy, and troubleshooting stop conditions are maintained in the user backup recovery exercise.

Platform disaster recovery

Status: Image protection, hybrid Firestore backup, independent monitoring, and the combined isolated recovery exercise are complete in development. Production promotion remains gated by #391. See Firestore disaster backups.

The completed system must provide:

  • A complete Firestore backup at least once every 24 hours.
  • Recipe-image deletion and overwrite protection aligned with Firestore retention.
  • Private, least-privilege backup infrastructure.
  • Alerts when no successful Firestore backup or recoverable image generation is available within 30 hours, or when the destination becomes inaccessible.
  • Restoration of Firestore and images into an isolated environment.
  • Deployment and validation of Firestore rules/indexes, Storage rules, functions/configuration, and required secrets references.
  • Recorded document/object counts, ownership checks, image relationships, measured RPO, measured RTO, cost, failures, and manual steps.

Disaster-recovery runbook

Use the tested, copy-paste-safe development recovery runbook for myrecipes-test. It covers authority, artifact selection, isolated restore, image recovery, rules/indexes, Auth dependency, validation, RPO/RTO, cost, evidence, failure handling, cleanup, and communication.

Use the production operations runbook for release gates. #391 must supply production-specific resources and cutover authorization. Production must never be the first restore destination.

Security and privacy checklist

  • [ ] Backup/export/import operations use least-privilege service identities.
  • [ ] Application users cannot read platform backup artifacts or administrative operation data.
  • [ ] A user cannot read another user's temporary archive or upload.
  • [ ] Archive ownership fields and object paths are ignored as authority.
  • [ ] ZIP traversal, duplicate/colliding paths, links, malformed names, archive bombs, excessive counts, and unsupported executable/media content are rejected.
  • [ ] Temporary artifacts have enforced expiration and cleanup retry behavior.
  • [ ] Rate, concurrency, archive-size, expansion, record, image, and temporary storage limits are enforced server-side.
  • [ ] Logs and alerts exclude recipe content, archive bytes, tokens, and signed download URLs.
  • [ ] Cross-user Firestore and Storage isolation tests pass.

Verification record

Add dated evidence as each issue completes. Do not check an exercise merely because backup creation succeeded.

Date Issue Environment Evidence/result
2026-08-19 #374 Local Functions test suite Typed scope and two-owner reciprocal-sharing fixtures pass; no new composite index or ownership/image metadata migration required
2026-08-19 #375 Local Functions test suite V1 schema, Unicode/image/empty/boundary fixtures, integrity, relationship, ownership-injection, path, and completeness validation pass
2026-08-22 #380 Local isolated emulators + myrecipes-test read-only evidence Repeatable exercise passed 56 unit, 10 end-to-end replacement, 38 interruption, 18 Firestore-rule, 8 Storage-rule, and 9 lock/publication tests. Previously authenticated deployed operation is completed with 3 boxes, 317 recipes, 183 images, verified safety/journal/staging state, clear projections, released lease, and publication ledger. See docs/release-evidence/user-backup-recovery-20260822.md.
2026-08-28 #382 myrecipes-test isolated database and image prefix Post-remediation combined exercise restored 5,768 documents, 323 recipes, seven boxes, 185/185 images, all fixtures and 11/11 authenticated reads; zero dangling relationships or image mismatches; native restore 14m 4.113s. See the dated evidence report.
2026-08-20 Development environment prerequisite myrecipes-test / recovery-test Managed export/import restored and verified 5,752 documents; controlled image generation restored; Apple V2 sandbox test delivered successfully. This is development infrastructure evidence, not completion of #376/#377/#382.
2026-08-21 #378 development data prerequisite myrecipes-test Migrated and checksum-verified 183 referenced legacy images to canonical tenant paths after a successful 5,781-document safety export. A post-export audit detected legacy Content-Type: image; byte signatures proved all 183 were JPEGs and canonical metadata was repaired to image/jpeg. Final audit: 185 canonical paths, 134 placeholders, zero remaining migrations/metadata repairs/conflicts. Image protection copied 183 new generations with zero missing paths. Production remains gated by #391.
2026-08-21 #378 myrecipes-test / iOS simulator Authenticated export and mobile save completed with 317 recipes and 183 images. The saved ZIP passed compressed-data validation; its v1.0 manifest reports status: complete, matching counts, and no issues. Replacement import remains untested and belongs to #379/#381/#380.
2026-08-22 #381 Local isolated Firestore/Storage emulators Replacement failure-injection matrix passed all 38 before/after checkpoints across 19 forward and rollback boundaries; unit tests passed 56/56 and the existing user-backup integration suite passed 10/10.

Maintenance rules

When a child issue changes behavior, update this guide in the same pull request:

  • 375: archive format, compatibility, limits, timestamps, and completeness.

  • 376/#377: infrastructure, retention, IAM, schedules, monitoring, cost, and

    alert ownership.
  • 378: user export flow, authorization, download, expiry, and troubleshooting.

  • 391: production promotion manifest and structured-export migration gates.

  • 379: upload, validation, preview, confirmation, and error behavior.

  • 381: state machine, activation, lock, safety export, rollback, and cleanup.

  • 380/#382: tested commands, manual steps, results, RPO/RTO, and exercise

    evidence.

Keep planned behavior visibly marked until tests demonstrate that it works.