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:
- 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.
- 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:
-
374
-
375, #376, and #377 as dependencies allow
-
378 and #379
-
381
-
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_uidand 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_idfields. - 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:
- Sign in as a test user that owns recipe data and canonical recipe images.
- Open Account & Settings → Backup & export → Structured recipe backup.
- Select Create structured backup. The app polls the bounded status callable and does not depend on realtime synchronization.
- Wait for Complete and ready to download. Select Download structured backup and choose a local destination.
- 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:
- Authenticate and authorize the user under the approved recovery/export entitlement policy.
- Create a unique, user-bound export operation.
- Read owned recipe boxes, then recipes and their dependent records, using the authoritative scope.
- Verify every Firestore relationship and exact owned image object.
- Build the versioned archive with archive-local IDs and actual image bytes.
- Calculate manifest counts and SHA-256 checksums.
- Store the archive at a private, operation-scoped temporary path.
- Provide bounded, short-lived authorized download access.
- 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:
- 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.
- Create a user-bound import operation and private upload destination.
- Upload without modifying active recipe content.
- Validate the complete archive: structure, normalized paths, schema/version, checksums, relationships, required media, MIME types, counts, and size/ expansion limits.
- Show backup date/version, counts, warnings, and the full-replacement warning.
- Recheck the 15-minute authentication window and require a separate explicit confirmation action. The backend independently enforces the same window.
- Create and verify a complete private safety export of current content.
- Lock or detect concurrent recipe writes through a trusted backend mechanism.
- Stage documents and images with trusted ownership and controlled paths.
- Verify staged records, relationships, counts, and image bytes.
- Activate the new authoritative dataset using the approved #381 commit strategy.
- Verify active content before reporting success.
- 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.