Repository navigation
Production schema fallbacks: messaging, AI usage, provider sync, calendar feeds and bills work before their migrations - #982
Conversation
…ging are checked, with the feed claim held as reserved 0490 Messaging (#834, #853, #854, composed onto main): - The canonical family chat is created new and adopts no legacy group (#854's decision); #834's tip (a stopped chat turn writes nothing more) is included; #853's later re-adoption merges are not. - 0475_messaging_conversation_privacy_and_delivery and 0476_messaging_notifications_preferences keep #834's reserved numbers and names under the owner's preserved map; next free is 0477. - 0293 stays main's byte for byte; 0476 checks notifications.related_id itself and its notice policies read the key through a lookup, so 0293 still re-applies after it. - On production's schema without 0475/0476 the messages page and service fall back to main's behaviour, recognising only the exact missing object (lib/messages/schema-compat.ts, legacy-schema.ts). - Messaging probes clear the JWT claims they set. Calendar (#923, #908, plus the date and DST fixes): - Planner, reminders, free slots, readiness and the assistants read every week of a series through readCalendarOccurrences, paged against an exact count; a page that repeats a row is refused. - Feeds keep the TZID zone, attach RECURRENCE-ID exceptions to their series and drop cancellations; one sync runs per feed under a claim. calendar_feed_apply_sync is reserved as 0490 and held in supabase/reserved/; without it the sync takes its check-then-write path. - DST nights follow RFC 5545 3.3.5 in the ICS readers and the expander. - An all-day event is drawn on its own date on every clock; reminder keys carry the family's day. No migration is applied by this change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KP9rt5rVQ9jDMpZp2xBy3K
Making family-media private is 0459's decision, which the owner has deferred in production (SEC-001, kept public on 2026-09-30). 0475 also set public = false, so applying messaging would have applied that deferred decision. It now only adds the voice-note audio types, and only where the bucket already has an allow-list (0418 sets one); a NULL list allows every type and appending to it would narrow uploads to audio. A new guard allows only 0459 to write the bucket's public flag; it fails on the previous 0475. The messaging database verifier passes on a disposable local cluster, including 0459's family-media probe. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KP9rt5rVQ9jDMpZp2xBy3K
An all-day row is stored at its date's Greenwich midnight (lib/calendar/day.ts allDayDate). Two labels still read that instant on the family's clock, so west of Greenwich it fell on the previous day: - the reminder body (lib/server/notifications.ts timeLabel). In New York at 08:00 on 21 September, the all-day 22nd read "today" and the 23rd "tomorrow"; - the "Get ready" moment label (lib/moments/prep.ts momentWhen). In Los Angeles, 5 July read "Today", while the moment's own dedupe key already said the 5th. Both now use the stored date. Timed labels are unchanged, and a regression asserts one beside the all-day cases. The birthday-label test that pinned the instant reading now expects the stored date. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KP9rt5rVQ9jDMpZp2xBy3K
…wered Production serves main 82f2db1 (#970), and the smoke routes return 200. The checkpoint states what #970 and #971 carry, the held 0488/0490, and the owner-only items still open: the rejected service-role key and migrations from 0177, allowance_rules, the Family Chat choice, and re-running 0475/0476 on development databases. No audit rows are closed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KP9rt5rVQ9jDMpZp2xBy3K
Storage checks a bucket's allowed_mime_types only when the list has entries, so an empty list, like NULL, allows every type. 0475 appended the voice-note audio types to any non-NULL list, which would have narrowed an empty one to audio only. It now updates only a list with entries (cardinality > 0). verify-messaging-database.mjs now runs the real 0475 against four bucket states, each in a transaction it rolls back: - public with a NULL list, and public with an empty list: both left as found; - private with a list: stays private, and gains the six audio types once; - no bucket at all: none is created. The check fails against the previous condition (empty list) and against the original 0475 (public flag). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KP9rt5rVQ9jDMpZp2xBy3K
The calendar module and the recurrence expander now import lib/calendar/day.ts (familyFetchRange, allDayDate, compareOccurrences). The browser fixture in calendar-navigation.spec.ts loads only the modules it lists and stubs every other import with functions that return null, so familyFetchRange returned null and the module threw "Cannot read properties of null (reading 'from')" in all ten cases. The fixture now loads day.ts too. All ten pass locally in Chromium, and the unfixed spec fails locally with CI's error. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KP9rt5rVQ9jDMpZp2xBy3K
…osed, carried by #969) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KP9rt5rVQ9jDMpZp2xBy3K
Production has not applied 0475_messaging_conversation_privacy_and_delivery.sql or 0476. Until it does, each messaging path now does what the previous production build (82f2db1) did, and takes the new path unchanged once the object exists: - The inbox lists every conversation the read returns instead of blanking and toasting when rows carry no is_family_chat; the first group that is not a DM opens as the family chat. - Family Chat: without ensure_family_conversation, the existing group chat is reused (or one is created with every member), with no error toast. - New conversations insert directly without create_family_conversation, retrying without participant_ids on a pre-0017 schema. - Previews and unread badges come from the bounded 400-message scan without family_conversation_overview. - Read receipts use 0163's mark_conversation_read (then a per-row append) without mark_conversation_read_through. - Reactions update the message without toggle_family_message_reaction. - Presence joins the old public topic; typing stays off, as before. - The assistant/approval send (sendFamilyMessage) opens the oldest un-archived group, probes duplicates by the same words within ten minutes and inserts directly when ensure_family_conversation, find_family_message or send_family_message are missing, and accepts any family conversation when is_family_chat is missing. A fallback runs only on PostgREST's exact missing-object answer naming that object (code plus name in message, details or hint); permission, network and constraint errors still surface. Each missing object warns once, naming the migration file. Tests that pinned the old refusal now pin this decision. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KP9rt5rVQ9jDMpZp2xBy3K
Follow-up to the 0475 fallback, closer to the previous production build (82f2db1) on a database without 0475: - Starting a 1:1 chat with someone you already have one with opens that chat again instead of inserting a duplicate (matched on participant_ids, or member_ids for older rows); a failed lookup is shown as an error and inserts nothing. - An assistant/approval send retried without find_family_message returns the row the same words already wrote, as before, even when its kind or reply differs; with find_family_message the drift refusal is unchanged. - The archive action is hidden before 0475, as the previous build had none; archiving the unmarked family group would have split the family chat. - Typing and presence wait for the first Family Chat ensure, so the old schema never attempts the private topics only 0475 authorizes. The acting-member gate in sendFamilyMessage stays on every schema: it is a deliberate hardening independent of 0475, so a scope without an active member is refused on the old schema too (82f2db1 sent as Bubaly). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KP9rt5rVQ9jDMpZp2xBy3K
…0493 Production does not yet have count_family_ai_requests_month (held supabase/reserved/0493). Every Free-plan AI request on web and mobile (both go through assertAIAccess) was refused with 'Bubaly could not check this month's usage'. When the RPC answers with the exact missing-function error naming it (PGRST202 or 42883), assertAIAccess now counts the month the way the previous release did: a direct exact, head-only ai_requests count from the start of the UTC month under the caller's RLS. It warns once per process naming the migration file. Any other RPC error (permission, network, a different missing function) still fails closed, and once 0493 is applied the RPC path is used unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KP9rt5rVQ9jDMpZp2xBy3K
The missing-RPC warning for count_family_ai_requests_month now tells operators to reload PostgREST's schema cache (NOTIFY pgrst, 'reload schema') if 0493 has been applied. A stale cache still answers PGRST202 after 0493, and the direct ai_requests count would then miss private rows under 0493's RLS. A new test also checks that a rejected direct count query fails closed with 'could not check this month's usage'. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KP9rt5rVQ9jDMpZp2xBy3K
Production does not have supabase/reserved/0494_sync_atomic_pull.sql yet, so
ensure_sync_pull_container / create_sync_pull_item do not exist and every
provider pull ('Sync now', export and the provider cron) failed at container
admission. When PostgREST answers that exactly one of those RPCs is missing
(PGRST202/42883 naming it in message, details or hint), the pull now takes the
previous production path from 82f2db1: the sync_calendars / sync_reminder_lists
lookup-or-insert, the separate sync_calendar_events / sync_reminders insert
followed by its sync_external_mappings insert, and the calendar cursor written
as soon as the calendar pull succeeds, keyed by the mirror id. A warning names
the migration once per process per RPC.
Any other RPC failure (permission, lock, a missing helper inside the RPC, an
unnamed PGRST202, network) still fails the run with the cursor kept, and the
path with 0494 applied is unchanged.
Tests: the two atomic-pull tests that asserted the old refusal without 0494
now assert the owner's fallback decision; new cases cover non-missing
failures, legacy cursor order and the once-per-process warning.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KP9rt5rVQ9jDMpZp2xBy3K
…sking it
Without supabase/reserved/0494_sync_atomic_pull.sql, provider pulls ('Sync
now', export, the provider cron) take the previous direct writes. Two
refinements:
- The RPC counts as absent only when the PostgREST/Postgres message itself
says that public.<rpc>( cannot be found / does not exist. A name in details
or hint alone (a hint can suggest a different, existing function) no longer
counts. PostgREST gives the same answer for a signature mismatch, so a
mismatched 0494 is still treated as absent; this is documented in the code.
- After that answer the RPC is not called again for 60 seconds, so the old
schema no longer pays one failed round trip per new pulled item. After the
window the RPC is tried again, so an applied 0494 takes over. Other failures
are never remembered and keep failing the run.
The path with 0494 applied is unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KP9rt5rVQ9jDMpZp2xBy3K
…n 0490 Production has not applied held migration 0490, so calendar_feed_apply_sync does not exist there. Every feed sync answered "Calendar sync is unavailable until ... (supabase/reserved/0490_...)" - a repository path shown to the family - subscriptions stopped refreshing, and "Add & Sync Now" rolled the new subscription back. Now, when the database answers that exactly calendar_feed_apply_sync is missing (PGRST202 or 42883 naming it in message, details or hint), the sync writes events the way production did before 0490: one upsert per chunk on (feed_id, external_uid), no removals, so events the source cancelled stay as they were (a cancelled event is still never written as a live one). The claim taken on calendar_feeds.last_status/updated_at is kept and checked by reading the feed row before each chunk, so a sync that has visibly lost its claim writes and stamps nothing. A warning naming the migration file is logged once per process; absence is probed again on every sync, so the atomic function is used unchanged as soon as 0490 is applied. Any other error (permission, network, constraint, a missing helper inside the function, another function's name) still fails the sync and an add still rolls back. The archive path's missing-function message no longer carries a repository path either. Tests: the fail-closed block and three source assertions that pinned "no direct upsert" are changed to the owner's decision; new cases cover both missing-function answers, cancellations, a lost claim, non-missing errors, a refused fallback upsert, and add-feed keeping its subscription. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KP9rt5rVQ9jDMpZp2xBy3K
…e them Follow-up to the missing-0490 fallback. When the database does not have calendar_feed_apply_sync, a feed sync now writes a cancelled event that carries a start as an ordinary event, the way production did before 0490 (its buildFeedRows never read STATUS). The event is updated in place or inserted on (feed_id, external_uid) and counted in the feed's event count. Nothing is removed, as before. Cancellations with no start are not written, as before. A cancelled event is written only if the feed would admit it as a live event, so a cancelled timed series is not written as a live weekly series and the recurrence admission rules are unchanged. When 0490 exists the sync is unchanged: it calls the RPC with the same arguments and makes the same writes and removals. The check for a missing function now reads only the message and details. PostgREST's hint is a "Perhaps you meant ..." suggestion, so a 0490 applied with a different signature now fails instead of quietly using the fallback. Tests: the cancelled-event cases are changed to the 82f2db behaviour. New cases cover an all-day cancelled master that is written, a timed cancelled master that is not, a bare cancellation, a snapshot holding only cancellations the feed never stored (no RPC call and no write, the same as with 0490), a refused upsert of a cancelled event, and a PGRST202 that names the function only in its hint, which must still fail. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KP9rt5rVQ9jDMpZp2xBy3K
Production has not applied the held 0488 (bills.due_day). Until it has, and only on the exact missing-column answer naming bills.due_day (PGRST204 / 42703), bills fall back to the previous production behaviour (82f2db1): - Mark paid on a monthly bill due on the 28th-30th, or a recurring bill without a cadence, rolls it by its due date's own day (monthly when no cadence is named) instead of opening the schedule dialog. A row read without the column is checked with a side-effect-free probe first. - A roll that must clamp to a shorter month (31st -> Feb 28, 30th -> Feb 28) asks the person whether to move the bill there; yes writes it, no leaves it as it was with the "needs a database update" notice. - Recurring bills due on the 28th-30th can be created and their schedule edited; the write is repeated without due_day when the date carries it. - The 12-week forecast and Family CFO dashboard load for families with such bills or a legacy cadence-less recurring bill. Every retry goes through the same compare-and-swap write. Any other error (permission, network, another column or table) is surfaced as before. One warning per process names supabase/reserved/0488_a_month_end_bill_keeps_its_day.sql. With the column present the anchored behaviour is unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KP9rt5rVQ9jDMpZp2xBy3K
… month lacks
Follow-ups for recurring bills while bills.due_day (held 0488) is missing:
- Mark paid on an older schema no longer respells the stored cadence
("Monthly", "annually", "fortnightly" stay as stored); it writes only
status and due date, as the previous production did. With the column
present the normalized cadence is still written.
- Adding a recurring bill whose chosen day the first month lacks (day 31
starting Apr 30) now asks whether to add it due on that date, and adds
it without due_day on yes, instead of refusing with "schedule
unavailable". No keeps the form open. Any other refusal is surfaced.
- Once the side-effect-free probe has answered that bills.due_day is
missing, Mark paid writes without the column at once (one guarded
PATCH instead of a refused one first), still asking before a clamp.
The compare-and-swap guard is unchanged on every write.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KP9rt5rVQ9jDMpZp2xBy3K
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KP9rt5rVQ9jDMpZp2xBy3K
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
|
Audit blockers at 4300411. The claimed owner reversal is not linked to verified decision evidence; existing audience, admission and held-migration decisions remain unresolved. Passing fallback expectations cannot by themselves close those holds. Current PR owner, bounded next source/test tasks (keep existing ownership, no duplicate workers):
Report exact repair commits and targeted negative controls before broad acceptance claims. Keep one finalaudit writer and all history. These are candidate source findings, not verified production incidents. No merge, deploy, production schema/configuration/credential changes or policy reversal requested. |
|
New exact-head CI blocker: 4300411 / run38003497146 / E2E job114066850170 failed smoke step20. Core, DB and Mobile passed. The failing test/cause is not established by step metadata. Current PR owner: after your active slice, diagnose this exact smoke failure through your authorized CI access, reproduce the failing case, and report the test name, source repair commit and exact-head rerun. Preserve negative safety assertions and the open source findings in6090921876; do not resolve this by weakening guards or assuming the pending fallback-policy decision. Keep source-only audit scope and the single ledger writer. No merge, deployment or production changes requested. |
…absent A single missing or stale 0475 RPC (reaction toggle, read-through, family chat ensure) set the module-wide legacy0475 flag, which switched presence to the old public topic and published identity and timestamps there. Presence now follows only the inbox read lacking is_family_chat; with the column present it stays on the private topic (which fails closed if its policies are missing), and nothing is joined until an inbox read has answered. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KP9rt5rVQ9jDMpZp2xBy3K
Before 0475 the read-through wrapper ran 0163's mark_conversation_read or the per-row append and returned success whatever happened, so the module marked every row read locally even when the append was refused (0463 rejects one built from a stale read_by), wrote no row, or the security-invoker RPC touched nothing. 82f2db1 never updated local state from this path. legacyMarkConversationRead now reports an error and exactly which rows landed: per-row appends stop at the first refusal or zero-row write, and an RPC success is confirmed by reading those rows back. The module shows only landed receipts, toasts the failure, and leaves the rest unread to retry. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KP9rt5rVQ9jDMpZp2xBy3K
On a database without count_family_ai_requests_month (held 0493), the allowance falls back to a direct head count of ai_requests. That fallback read `legacy.count ?? 0`, so a response with no error and no count (supabase-js leaves count null when Content-Range does not come back) was granted as an empty month: Free families got their allowance back. The RPC path already refuses a null receipt; the fallback now does too. Replaces the test that pinned null-as-zero with negative controls: a null count under both missing-function codes, invalid counts (NaN, -1, 0.5), and an error that comes with a count, plus a positive control for a real zero. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KP9rt5rVQ9jDMpZp2xBy3K
…nthly Without bills.due_day, the pre-0488 fallback read a recurring bill with no or an unrecognised cadence as monthly: Mark paid probed the column and rolled it a month, and the forecast loader stepped it monthly. That invents a payment schedule nobody chose. Now, as with the column: - cadenceBefore0488 returns only a named cadence; billBefore0488 leaves an unnamed one as read, so buildCashflowTimeline raises BillScheduleConfirmationRequired. - billPaidPatchBefore0488 returns null for a flagged bill without a named cadence (no write), and saveBillPaymentBefore0488 returns null before any probe or await, so bills-view / billing-module open the schedule chooser and no new late-dispatch path exists. - The Family CFO page catches BillScheduleConfirmationRequired and shows the same confirm-schedules notice the Money Timeline page already shows. Tests: negative controls in the pre-0488 payment, markPaid/markBillPaid callback, timeline-load and Family CFO suites fail before this change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KP9rt5rVQ9jDMpZp2xBy3K
Without held 0494 the pull looked up its calendar/task-list mirror by household, provider and remote id only, as the previous production build did. Google's '@default' task list has the same id for every account, so a second Google account in a household adopted the first account's list: its pulled tasks landed there and two-way sync exported the first account's local reminders to the second account's Google Tasks. The fallback now matches by account, as 0494's ensure_sync_pull_container does, and fails closed on a mirror naming another household or owner, or on ambiguity. The fallback's separate item and mapping inserts are also retry safe: a failed mapping write removes the item it just inserted, and a retry adopts an unmapped item for the same remote id in the account's mirror instead of inserting a duplicate. An item another mapping claims is never adopted. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KP9rt5rVQ9jDMpZp2xBy3K
supabase-js can report an error for a mapping INSERT that committed. The legacy pull then deleted the just-inserted item, leaving a mapping whose local_id names a missing row, and every later run failed its local lookup. The item is now kept: a committed mapping is found by the next run, and an uncommitted one leaves an unmapped item that the retry adopts. Adoption also clears deleted_at, as the RPC inserts a live row, and refuses an item that names another owner. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KP9rt5rVQ9jDMpZp2xBy3K
Owner decision 2026-10-09 (PR #982): while held 0488 (bills.due_day) is absent, recurring bills fall back to previous production behaviour. recurring-bill-payment.spec.ts: for missing-column / missing-cache the payment modal sends the anchored PATCH once, gets the missing-column answer, warns once, and - with no confirmClampedDay for day 31 -> Apr 30 - refuses before a second write: one PATCH, "not available yet" alert, row unchanged. finish() now allows exactly that one 0488 warning for those two outcomes only; every other outcome must stay diagnostic-free. recurring-bill-authenticated.spec.ts (phase 'old'): Mark paid on a 31st bill gets PGRST204 for the anchored patch and asks "Mark paid and move this bill to Feb N?". Declining writes nothing and shows the "left as it was" notice; accepting sends the same CAS with only status and due_date (no due_day, stored cadence kept) and moves the row. Editing a 28th bill to day 31 runs server-side with no one to ask and is still refused with "not available yet"; the row and dialog stay. The 'modern' phase is unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KP9rt5rVQ9jDMpZp2xBy3K
|
Replying to 6090921876 and 6091337155. Head is now Decision evidence. The fallback policy is the repository owner's decision, made in the session that authored this PR on 2026-10-09. After the owner saw the evidence that #969 left production's Messages inbox empty, stopped provider sync and calendar feeds, blocked Free-plan AI and blocked 28th–30th bills on production's schema, they chose: "Roll back, then fix: Vercel Instant Rollback to 82f2db1, then a PR that makes each area fall back to the old behaviour until its migration is applied." This PR is that fix. Findings in 6090921876Each finding was verified against the source, and every fix has a negative control that fails without it.
CI blocker in 6091337155E2E job 114066850170 failed these tests, which still asserted the reversed pre-0488 refusal:
Fixed in
Local validation on this head
No merge, deployment or production change. Generated by Claude Code |
|
Source review of 1d1b90c confirms the static account-scoped mirror repair and sequential rejected/ambiguous mapping recovery. Presence partial-schema selection, receipt error propagation, invalid AI counts and unknown bill cadence are also repaired in source; this is not a full acceptance receipt. Next bounded provider task: FALLBACK-ORPHAN-ADOPTION-FENCE. findUnmappedLegacyItem reads ownership/no mapping, then a separate PATCH refreshes content and clears deleted_at before claiming the mapping. Pull A can pause after that read, pull B claim the item with a newer snapshot, then A overwrite B before A's mapping insert fails. The PATCH also does not recheck user_id. Preserve the new sequential/ambiguous-commit behavior while preventing a retired/unowned adoption from mutating a now-claimed item. Add an interleaving test between lookup and PATCH, not merely an already-claimed fixture. Historical behavior at82f2db1fc is acknowledged, but does not establish equivalence to the current main safety baseline: a pre-write fence read cannot prevent takeover between that read and the upsert, and a cancelled occurrence replanned as live still needs an explicit unresolved qualification. Keep the existing feed, installed-0493 quota, messaging atomic revocation/idempotency and legacy-anchor policy holds until independently addressed. No production or policy-change authority is added by this review. |
…d_at findUnmappedLegacyItem only reads that an orphan is unmapped and owned by the account; the fallback then refreshed the item before inserting its mapping. A pull paused after that reading could overwrite the newer snapshot a concurrent pull had already adopted, and the refresh did not recheck user_id. Adoption now inserts this account's mapping first, with a client-chosen id. 0018's unique (provider, item_type, external_id, account_id) lets only one pull claim the remote id (and unique (provider, item_type, local_id, account_id) refuses a second mapping of the same item); the loser changes nothing and fails. Only the winner refreshes the item, filtered on id, container, family, provider, remote id, user_id = account owner and the updated_at seen at lookup. A claim whose answer is lost after commit is recognised by its id and completed; a refresh that does not apply releases the claim (the mapping row only, never the item) and fails the run. The fresh-insert path and its ambiguous-commit handling are unchanged. The SDK fixture now enforces the two mapping unique constraints, emulates set_updated_at on item writes and can pause a request. New tests pause a pull between lookup and write while another pull adopts a newer snapshot, and change the owner or edit the item at write time. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KP9rt5rVQ9jDMpZp2xBy3K
|
Re 6091454529: FALLBACK-ORPHAN-ADOPTION-FENCE is fixed in How adoption works now:
Tests:
Residual window: if the process dies between the claim and the patch, the mapping points at the unrefreshed item. It does not wedge, and the next remote change surfaces it as a conflict in two-way sync. Qualifications I accept:
The feed, installed-0493 quota, messaging atomic revocation/idempotency and legacy-anchor holds remain open. Generated by Claude Code |
|
d7d2da0 source review confirms the pre-claim race is repaired. The remaining adoption hold now concerns the published-claim window, beyond the existing test paused before INSERT. Current provider owner, bounded continuation of FALLBACK-ORPHAN-ADOPTION-FENCE:
Unfinished adoption must not be consumed as a completed synchronization receipt. Preserve the account isolation, owner/version checks and ambiguous-commit protections already added. Report exact repair and complementary interleaving/restart tests; keep the acknowledged feed, quota, messaging and anchor-policy holds. Source/test work only; no production or migration changes requested. |
|
New current-head gate evidence for the existing CI repair task6091337155: d7d2da0 / run38009059888 / Core job114084761853 failed first Unit tests step11. Exact failing test/cause is unknown from metadata. DB/Mobile passed; E2E is still running, so this is distinct from the earlier430 smoke failure. Current owner: include this exact unit failure in the existing bounded diagnosis after the active adoption repair. Preserve the new interleaving controls and unresolved safety holds; report failing test, repair commit and exact-head results rather than weakening the assertions. No additional worker or production action requested. |
…tionally
adoptLegacyItem now inserts its mapping claim with sync_status 'pending',
metadata {adoption: 'pending'} and no lastHash, so it is never read as a
sync receipt. After the fenced item refresh it completes the mapping (real
lastHash, sync_status 'synced') only while the row is still its own pending
claim, and on a refused refresh releases it only while still pending; the
release is now confirmed (.select) and a failed release is reported.
The ordinary mapped pull path (google and generic engines, events and
reminders) treats a pending/syncing mapping as needing a refresh: it takes
the claim over (conditional update to 'syncing'), skips conflict detection
and the local-only/unchanged skips, refreshes the item from the snapshot
fenced on owner, provider and remote id (clearing deleted_at, as adoption
does) and completes the mapping. A failure throws, so the cursor does not
advance past an unfinished adoption.
Tests: a pull paused after its claim commits no longer removes the
association a concurrent pull completed; an adoption interrupted after its
claim is refreshed and completed by a same-snapshot two-way retry with no
provider writes and a consistent cursor.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KP9rt5rVQ9jDMpZp2xBy3K
|
Re 6091623122 and 6091710565. Head is now Unit failure on
|
|
6166881 source review confirms the two previously reported live-item cases are corrected. Keep the acknowledged concurrent recovery hold: adopting refresh additionally clears deleted_at, so a stale syncing recovery can revive an item cancelled by a newer pull; this is more than ordinary live last-writer ordering. Bounded continuation for the current owner: include cancellation/deletion in pending-adoption coordination. Those branches currently execute before takeover. Test A publishing pending then pausing; B processes cancellation/deletion and succeeds while the mapping remains pending; A's item CAS fails and pending-only cleanup deletes B's association. Preserve the cancellation receipt and prevent stale recovery from resurrecting the item. Add this after-claim cancellation variant and a syncing-recovery/newer-cancellation interleaving across both engines. A reusable syncing label does not establish a unique recovery generation. Keep that residual explicit unless independently fenced or the fallback refuses safely. Preserve the live-path and ambiguous-response repairs already added; no production changes requested. |
An adoption claim now carries a unique generation, metadata.adoption =
{ state, token } with a fresh random token per claim or takeover
(sync_status keeps the state for coarse filtering). Takeover, completion
and release are all fenced on the exact token
(metadata->adoption->>token), so a reused 'syncing' label never stands in
for ownership and two recoveries that read one generation cannot both
take it over.
A takeover touches the item and fences its own item writes on the
updated_at that touch returned; every earlier generation's item write is
fenced on an older updated_at, so once a newer generation holds the claim
a stale adoption or recovery can neither overwrite it nor clear a
deleted_at a newer pull set.
A remote cancellation/deletion of an item whose mapping is unfinished is
routed through the same takeover (cancelPendingAdoption): it takes a
fresh token, marks the item deleted under that fence and completes the
mapping as its receipt, so the paused claimant's token-fenced release
cannot delete that association.
Tests (google + generic, event + reminder): a cancellation after the
claim, a stale 'syncing' recovery against a newer cancellation, and two
recoveries with distinct tokens. The synthetic SDK fixture now evaluates
JSON-path eq filters.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KP9rt5rVQ9jDMpZp2xBy3K
|
Re 6091778694. Head is now The fixEach adoption now has its own generation token. When a claim is published or taken over, it writes
How the item is fenced
Cancellation and deletionAll four cancellation and deletion branches (google and generic, events and reminders) now handle an unfinished claim the same way:
As a result:
TestsEach test runs on google and generic, for event and reminder.
All 12 new cases fail on Residual windows
Generated by Claude Code |
|
AUDIT-HOLD / main integration recorded: #982 merged externally at 2026-10-10 12:17:01 UTC as0039e3d23ce0c73dce2338f1b2157ce39862bb44. Its tree2f59398adc37a941f00d457786053cc924fdf0ad exactly matches8b78130d7464db55c4580b41eba26f1a46336113; no merge-only repair. Integration does not close the prior source/evidence/policy holds or establish deployment. In particular, the acknowledged mapping-CAS→item-touch gap from6091778694/6091898381 remains in main: a superseded claim can still touch the item after a newer generation completes or cancels; rejection of its later completion does not undo that item write. Existing messaging/feed/AI/billing fallback qualifications also remain. Green candidate CI is not approval of those contract changes. Next repair, when this lane is actively resumed, should use a separate audit branch/draft PR based on the verified current main, with a deterministic pause between mapping CAS and item touch proving the stale owner cannot overwrite/revive the newer result. Check active ownership before assigning or editing; do not duplicate an in-flight repair. Keep broader policy decisions separate. No rollback, merge, deploy, production schema/config/credentials or data actions are requested. Preserve all task IDs and the sole finalaudit writer; no claim that posting wakes a private session. |
Summary
Main (
6d9d9207fand later, with #969) is live in production. Its code depends on database objects that production does not have:0475and0476, which were never applied;0488,0490,0493and0494, which are held insupabase/reserved/.A read-only, adversarially verified review found that on production's schema:
Per the owner's decision, each path now falls back to previous production behaviour (
82f2db1fc) while its migration is absent. Once the object exists, the new path is used unchanged.Absence is detected only from the exact missing-object answer that names the object (PGRST202/42883, PGRST204/42703, PGRST205/42P01). Every other error, such as permission, network or constraint, still surfaces as before. Each fallback warns once per process and names its migration.
is_family_chatmark_conversation_read, 0163), reactions and presence use the previous paths.count_family_ai_requests_monthai_requestscount, as before, so Free keeps 10 requests a month.ensure_sync_pull_container/create_sync_pull_itemcalendar_feed_apply_syncbills.due_daySome tests asserted the reversed "refuse until the migration lands" decision. Those assertions now expect the fallback, with a comment; non-missing errors are still asserted to fail. Nothing under
supabase/migrationsorsupabase/reservedchanged, and no migration was applied.Not changed (owner decision pending): #969's stricter ICS parsing and recurrence admission still reject many feeds that previous production imported, such as VTIMEZONE feeds, Windows zone names and school or sports series.
Verification
tsc --noEmit: clean.audit-migration-versions: next 0477.node-version-is-pinned,stream-cancellation-runtime); CI uses the pinned Node 24.21. Theordering-guardsfailure found in that run is fixed in 4300411.family-messages-stateandmessaging-preserve-access: 79 passed.🤖 Generated with Claude Code
https://claude.ai/code/session_01KP9rt5rVQ9jDMpZp2xBy3K
Generated by Claude Code