chore: pin Cloud Functions + Flutter client to europe-west2 (EU migration)#159
Merged
Conversation
Roadmap v1.3 step 2 of the us-central1 -> europe-west2 migration: a code-only region pin, no deploy yet (deploying before the Firestore nam5 -> eur3 cut-over would add a cross-Atlantic hop and worsen latency). - New _region.ts owns FUNCTION_REGION and calls setGlobalOptions, covering all 9 v2 functions (callables + scheduler) in one place. Imported first in index.ts so it runs before any function module is evaluated. - The two v1 triggers (onUserCreated, onFriendAdded) reference the same constant via .region(). - Test guard iterates every export and asserts __endpoint.region includes europe-west2, so a future function that forgets the region fails CI. Verified: 11/11 endpoints report ["europe-west2"]; npm test 372 passing; lint clean. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Roadmap v1.3 step 5: pin the Flutter client to the europe-west2 Cloud Functions so it stays in-region once the functions are deployed there. - New lib/firebase_functions_eu.dart exposes `appFunctions` (FirebaseFunctions.instanceFor(region: 'europe-west2')) — the single region-pinned seam. The default FirebaseFunctions.instance targets us-central1. - All 11 callable call sites switched from FirebaseFunctions.instance to appFunctions (nearby, claim_quiz_sheet, wear x2, route x2, reports, admin, user_repository, claim_history_screen, notification_service). cloud_functions imports kept only where a cloud_functions type is still used. - New test/firebase_functions_region_test.dart guards the helper region and scans lib/ so a future call site using the US-default instance fails CI. Inert until the functions exist in europe-west2 (instanceFor only changes the target URL), so this ships with/after the functions deploy, not before. Verified: flutter analyze clean; region guard 2/2; flutter test 390 passing. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Step G of the EU migration (ROADMAP v1.3): confirm the eur3 import is complete before reopening traffic. - scripts/verify_migration.ts: read-only CLI. `snapshot` counts every root collection (via listCollections + count() aggregations — never reads bodies, never writes) plus the nested groups entries/countyStats/counties/periods, writing a JSON snapshot. `compare` diffs two snapshots and exits non-zero on ANY mismatch. Run before (nam5) and after (eur3) on frozen data. - _migrationVerify.ts: pure diffSnapshots() extracted so the diff verdict is unit-testable — a verify tool that falsely reports "match" is the worst failure mode. 4 new tests (match / mismatch+delta / one-sided / groups). - npm run verify-migration script (mirrors plan-route). Verified: build + lint clean; TS suite 376 passing; compare smoke-tested both ways (matching -> exit 0, mismatch -> exit 1). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The v1.3 migration plan said to export to a europe-west2 bucket, but a nam5 DB can only export to a US bucket (and eur3 can only import from an EU one). Correct to the two-bucket US->EU copy flow, and fix related inaccuracies found while executing: - two buckets (US for export, EU for import) + the cp -r hop between them - verify step now references the read-only verify-migration CLI + full collection list (incl. reports/reportQuotas) - deploy step: keep the 8 us-central1 callables but delete the 3 event-driven functions to avoid double-firing; verify 11 (not 8) healthy - client-pin step marked done (11 call sites, not 8; claim.dart was wrong) - storage step notes the default bucket holds report_photos/ + osm_changesets/ - rollback imports the pre-migration backup from the US bucket Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The v1 (Gen1) Firestore trigger onFriendAdded can't deploy to europe-west2 against an eur3 database: Gen1 Firestore triggers must run in the DB's region, and eur3's Gen1 trigger region is europe-west1, not europe-west2. The first EU deploy failed with "...is in region eur3-europe-west1 which is not supported" (the other 10 functions deployed fine). - New FIRESTORE_TRIGGER_REGION = "europe-west1" in _region.ts; onFriendAdded uses it instead of FUNCTION_REGION. - onUserCreated stays on europe-west2 (Auth triggers are global, not DB-bound). - Region guard updated with a per-function override map. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
eur3 has no Gen1 Firestore triggers — a v1 trigger deploy fails in every
region ("...is in region eur3-europe-west1 which is not supported"), so
europe-west1 didn't help either. Convert onFriendAdded to a 2nd-gen
onDocumentUpdated trigger; Eventarc maps eur3 -> europe-west4, so it runs
there.
- FIRESTORE_TRIGGER_REGION = europe-west4; onFriendAdded uses v2
onDocumentUpdated (firebase-functions/v2/firestore), dropping functionsV1.
- Region guard now also asserts onFriendAdded is gcfv2, to catch a silent
regression back to a v1 trigger (which eur3 can't run).
Verified: build + lint clean; suite 377 passing; runtime endpoint reports
region europe-west4, platform gcfv2, eventTrigger true.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
First v1.3 "Platform foundations" release: Cloud Functions + Firestore now in europe-west2 / eur3, client pinned to europe-west2 via appFunctions. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Roadmap v1.3 — the reversible, code-only groundwork for the
us-central1 → europe-west2migration, plus the read-only tool to verify the data move.All of this is code-only and inert until the operator-run migration happens, and the order is load-bearing:
nam5 → eur3cut-over — export to a US bucket, copy US→EU, import to the recreatedeur3DB (destructive gcloud work, not in this PR)firebase deploy --only functions→ creates theeurope-west2copiesDeploying functions before the Firestore move adds a cross-Atlantic hop; shipping the client before the functions exist in
europe-west2points the app at nothing.Phase 1 — backend region pin (
e821b87)functions/src/_region.tsownsFUNCTION_REGIONand callssetGlobalOptions({ region }), pinning all 9 v2 functions (8 callables +newDayScoreboard) in one place; imported first inindex.tsso it runs before any function is defined.onUserCreated,onFriendAdded) reference the same constant via.region().__endpoint.regionincludeseurope-west2.Phase 4 — client region pin (
f208ccc)lib/firebase_functions_eu.dartexposesappFunctions→instanceFor(region: 'europe-west2'), the single region-pinned seam.appFunctions.test/firebase_functions_region_test.dartscanslib/so any future call site on the US-default instance fails CI.Migration verification CLI (
39cb264)functions/src/scripts/verify_migration.ts(npm run verify-migration): read-only —snapshotcounts every root collection (listCollections+count()aggregations) plus nested groups, never reading bodies or writing;comparediffs two snapshots and exits non-zero on any mismatch. Run before (nam5) and after (eur3) on frozen data — the step-G gate before reopening traffic.diffSnapshots()extracted to_migrationVerify.tsand unit-tested (a verify tool that falsely reports "match" is the worst failure mode).Verification
cd functions && npm test→ 376 passing;npm run lintclean; 11/11 endpoints oneurope-west2.flutter analyzeclean;flutter test→ 390 passing.Still to do (operator/console, out of scope)
Firestore
nam5→eur3move (US→EU bucket hop), functions deploy, app release, then decommission us-central1 callables + the oldnewDayScoreboardscheduler after one zero-traffic release cycle.🤖 Generated with Claude Code