fix(migrations): resolve the #548/#549 collision that made the digest timezone fix inert - #563
Merged
Merged
Conversation
… timezone fix inert #548 (keyset pagination for get_daily_digest_candidates) and #549 (the tenant-local streak-nudge comparison) were developed in parallel and landed within hours of each other. Each merged cleanly on its own; together they left master in a state neither PR was reviewed in. Two problems. 1. Duplicate migration version. Both `daily_digest_candidates_pagination` and `league_rollover_catchup_and_bands` were stamped 20260726140000, so a fresh `db reset` would try to record two rows under one primary key. The league migration is independent of everything around it, so it moves to 20260726140500 rather than reordering anything. 2. The #549 digest fix was inert. #548 replaced the zero-argument function with a three-argument keyset-paginated one, and explicitly DROPped the zero-arg version because an overload makes a zero-arg call ambiguous (PostgREST 300). #549's migration then re-created that zero-arg function. Because it sorts after #548's, a fresh reset ended up with both: the cron passes three named arguments so it kept resolving to the paginated definition, which still carried the `= CURRENT_DATE - 1` UTC comparison #549 exists to remove — while #549's own function sat unused. The bug was live and the fix looked applied. The #549 migration now replaces the paginated signature, carrying #548's cursor and ORDER BY over verbatim, so one function has both fixes. Verified by applying both migrations in file order inside a rolled-back transaction: exactly one `get_daily_digest_candidates(uuid,uuid,integer)` survives, it contains `>= CURRENT_DATE - 2`, and it retains `_after_tenant_id`. 502/502 unit tests and typecheck green. Cloud was already patched directly against the live paginated definition, so it has been correct throughout; this brings the repo in line with it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RGCEptT7pgWwRiMdB3TUCM
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.
What
Fixes two problems that only exist in the combination of #548 (keyset pagination for
get_daily_digest_candidates) and #549 (the tenant-local streak-nudge comparison). Both PRs merged cleanly on their own within hours of each other; neither was reviewed against the other's result.Follow-up to #559. Relates to #548, #549.
Why
1. Duplicate migration version.
20260726140000_daily_digest_candidates_pagination.sqland20260726140000_league_rollover_catchup_and_bands.sqlshare a version, so a freshdb resetwould try to record two ledger rows under one primary key. The league migration is independent of everything around it, so it moves to20260726140500rather than reordering anything.2. The #549 digest fix was inert — the bug was live while the fix looked applied. #548 replaced the zero-argument function with a three-argument keyset-paginated one, and deliberately
DROPped the zero-arg version, with a comment explaining that an overload makes a zero-arg call ambiguous (PostgREST answers 300).#549's migration then re-created exactly that zero-arg function. It sorts after #548's, so a fresh reset ended up with both. The cron passes three named arguments, so it kept resolving to the paginated definition — which still carried the
= CURRENT_DATE - 1UTC comparison that #549 exists to remove. Meanwhile #549's own function sat unused.Net effect on a fresh database: the LATAM streak-nudge bug is still live, the unit tests still pass (they cover
isStreakAtRisk, which is correct), and the migration that was supposed to fix it is present and applied. Nothing surfaces the problem.The #549 migration now replaces the paginated signature, carrying #548's cursor predicate and
ORDER BYover verbatim, so a single function has both fixes.How to QA
The two migrations are applied in file order inside a rolled-back transaction, then asserted on —
BEGIN;+ both migration files + the assertion below +ROLLBACK;:Result:
Also checked:
ls supabase/migrations/*.sql | xargs -n1 basename | cut -d_ -f1 | sort | uniq -dreturns empty.npm run test:unitpasses 502/502.npm run typecheckclean.A full
db resetwas deliberately not run: four other worktrees are active against the shared local stack and a reset would wipe it out from under them.Screenshots / GIF
N/A - two migration files.
Checklist
npm run typecheckandnpm run test:unitpassnpm run buildpasses (unchanged from fix(learning-engine): league rollover catch-up, local-day streak nudge, FSRS lapsed backfill, interleaving gate, store races (#549) #559 - no application code in this diff)tenant_id(or the table genuinely has no such column)get_daily_digest_candidatesisservice_role-only; the grants are restated in the migration and verified revoked fromanon/authenticatedlib/database.types.tswas regenerated - no type change: the generated signature already reflects Finish the #533 pagination sweep — eight unbounded reads remain #548's three-argument formNote on cloud
Cloud never had this problem. When applying #549 I checked the live definition first, found the paginated signature, and patched that rather than applying the repo's zero-arg file - so production has carried both fixes on a single function since then. This PR brings the repo in line with what cloud already runs.