docs(roadmap): add v1.5 Resilience & offline play; renumber later releases#171
Open
code418 wants to merge 1 commit into
Open
docs(roadmap): add v1.5 Resilience & offline play; renumber later releases#171code418 wants to merge 1 commit into
code418 wants to merge 1 commit into
Conversation
…eases
The claim flow is a chain of live callable round-trips with no offline story:
no connectivity detection, no timeouts, no retries, no local cache. Two
concrete failures today — a claim whose response is dropped is silently lost
(the write is idempotent, the response is not), and a player in a notspot
cannot play at all.
Adds v1.5 as a four-phase release: flaky-link resilience (attempts/{attemptId}
idempotency doc, timeouts + retry, preserved sheet state), a backend that can
accept a claim made in the past (eventTime + backfill, two-sided travel-speed
check, streakFromClaimDays), warm-path offline claiming, and blind capture +
offline discovery — the last marked explicitly conditional, since it carries
most of the attack surface and the least payoff.
Records the design constraints that are easy to miss: a naive offline claim
lets an attacker shift the outbox by 24h and re-farm it forever, so capture
times must be bounded by a server-issued token; and a backdated `today` passed
to updateUserLeaderboards would wipe the daily board for every player.
Pulls App Check server-side enforcement up from Reach into v1.5 — it is a
prerequisite for accepting client-supplied timestamps — leaving the iOS
AppleProvider wiring in Reach, which now makes it a launch blocker there.
Renumbers: Engagement & avatars -> v1.6, Collection & content -> v1.7,
Reach -> v1.8. Growth stays v2.0.
Also gitignores /.worktrees/ (git worktree add inside the repo would otherwise
show as untracked).
Co-Authored-By: Claude Opus 4.8 (1M context) <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.
Why
The claim flow is a chain of live callable round-trips with no offline story at all — no connectivity detection (the app infers "offline" from
FirebaseFunctionsException.code == 'unavailable'at three call sites), no timeouts, no retries, no local cache.Two concrete failures today:
claims/{uid}_{box}_{day}), but the response is not. If the response is dropped on a flaky link, the retry hits the "already claimed today" fast-path and the client renders an empty state — no points, no confetti. The claim landed; the player never sees it.What this adds
A new v1.5 — Resilience & offline play, in four phases:
attempts/{attemptId}idempotency doc, callable timeouts + jittered retry, stop tearing down the quiz sheet on network failure,getLastKnownPosition()fallback, offline banner. Ships alone and fixes the lost-claim bug; most of the user value is here._claimCore.tsextraction,eventTime+ backfill, two-sided travel-speed check,streakFromClaimDays.Three findings from tracing the flow shape the design, and are recorded so they don't have to be rediscovered:
startScoringderives which boxes you claimed from position alone, so a claim can be represented as "I stood here, then" and adjudicated later.Date.now(), so any two queued boxes >~32 m apart hard-reject the second.Two traps written down explicitly
issuedAt/serverNowbounds force the offline timeline to fit inside a wall-clock window the server actually observed.todaytoupdateUserLeaderboards. It would see aperiodKeymismatch, setexisting = [], andtx.set(..., { merge: false })— wiping the daily leaderboard for every player. Silent cross-user data loss.Schedule impact
App Check server-side enforcement moves up from Reach into v1.5 — it's a hard prerequisite for an endpoint that accepts client-supplied timestamps, and
enforceAppCheckcurrently appears nowhere infunctions/src. The iOSAppleProviderwiring stays in Reach, which makes it a launch blocker there (with enforcement already live, an iOS build without it fails every callable).Renumbering: Engagement & avatars → v1.6, Collection & content → v1.7, Reach → v1.8. Growth stays v2.0.
Also
/.worktrees/(git worktree addinside the repo otherwise shows as untracked).ClaimAction.kt(Android Auto) callsFirebaseFunctions.getInstance()with no region, targetingus-central1while every function deploys toeurope-west2. The CI region guard only scanslib/, so Kotlin call sites are invisible to it — the Auto claim button may simply not work.Docs only — no code changes, no CI risk.
🤖 Generated with Claude Code