test(calendar): prove non-Google sources are safe on an old client (#1396) - #1426
Draft
h4yfans wants to merge 1 commit into
Draft
test(calendar): prove non-Google sources are safe on an old client (#1396)#1426h4yfans wants to merge 1 commit into
h4yfans wants to merge 1 commit into
Conversation
|
React Doctor found no new issues. 🎉 Reviewed by React Doctor for commit |
h4yfans
force-pushed
the
h4yfans/calendar-provider-ui-shell
branch
from
August 13, 2026 16:17
da8a305 to
2c46af1
Compare
h4yfans
force-pushed
the
h4yfans/calendar-cross-version-compat
branch
from
August 13, 2026 16:17
cf46ffa to
af7f23b
Compare
h4yfans
force-pushed
the
h4yfans/calendar-provider-ui-shell
branch
from
August 13, 2026 16:19
2c46af1 to
b03870c
Compare
h4yfans
force-pushed
the
h4yfans/calendar-cross-version-compat
branch
from
August 13, 2026 16:19
af7f23b to
1de5e16
Compare
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
…1396) Device A on a newer build connects CalDAV and syncs `provider='caldav'` rows. Device B is still on a Google-only build and pulls them down. The item handlers were already defensive in the other direction — an absent `provider` falls back to `'google'` — but this direction had never been verified, and it is the one that can lose data: if the old client decided an unrecognised row was corrupt and cleaned it up, that delete would sync back and remotely destroy the connection the user just made. Finding: it does not. `main/sync/calendar-cross-version-compat.test.ts` runs the handlers — which is exactly what a Google-only build does — against `ics`, `caldav` and `microsoft` rows and asserts they are: - stored verbatim, never rewritten to `google` - not archived, not deleted, not "repaired" on a second pull - pushed back out with the provider intact - accepted as parents for external events (the missing-parent guard keys on the row existing, not on the provider being syncable) - subject to the same delete-clock rules as google — a stale tombstone is skipped, a causally-newer one applied, identically The payload round trip is checked too: `provider` is `z.string().optional()` rather than an enum, so an old client's zod does not strip a value it has never seen — a stripped field would come back as the `?? 'google'` fallback and silently re-home the calendar. Consequence: no sync type negotiation gate is needed. The PR #754 pattern was the fallback if the old client had misbehaved; it does not, so new-provider rows fan out unconditionally and an old device carries them inert until it updates. Findings written up under `apps/docs/src/architecture/sync-handlers.md`.
h4yfans
force-pushed
the
h4yfans/calendar-provider-ui-shell
branch
from
August 13, 2026 16:45
b03870c to
fc57c6f
Compare
h4yfans
force-pushed
the
h4yfans/calendar-cross-version-compat
branch
from
August 13, 2026 16:45
1de5e16 to
08afc49
Compare
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.
Closes #1396. Phase 0 of the multi-provider calendar epic #1390. Top of the stack — the gate for every provider phase.
The question
Device A runs a build that can connect CalDAV and creates
provider='caldav'sources. Device B is still on a Google-only build and pulls them down.The item handlers were already defensive in one direction —
provider: data.provider ?? 'google'covers rows written before the column carried meaning. The other direction had never been verified, and it is the one that can lose user data: if the old client decided an unrecognised row was corrupt and cleaned it up, that delete would sync back and remotely destroy the connection the user had just made on A.The answer: it does not misbehave
main/sync/calendar-cross-version-compat.test.ts(26 tests) drives the handlers directly — which is exactly what a Google-only build does with these rows — againstics,caldavandmicrosoft:applyUpsertwrites the provider through and never rewrites one it does not recognise;?? 'google'only fires when the key is genuinely absent.archivedAt IS NULL). I checked for a sweep too: the only two deletes oncalendar_sourcesare an explicit user-initiated disconnect — scoped byprovider— and applying a remote tombstone. There is no orphan sweep or credential-reconcile pass over these tables.buildPushPayloadround-tripsproviderunchanged, so an old client re-pushing a row it cannot use does not re-home the calendar.Payload round trip.
providerisz.string().optional()rather than an enum, so an old client's zod does not strip a value it has never seen. That matters more than it looks: a stripped field comes back as the?? 'google'fallback, which would silently re-home the calendar. Asserted for sources and bindings, plus a full local →buildPushPayload→ schema parse → apply-on-a-second-db loop that checksprovider,metadataandsyncCursorall survive.Consequence: no fan-out gate needed
The PR #754 sync-type-negotiation pattern was the fallback if the old client had misbehaved. It does not, so new-provider rows fan out to every device unconditionally and an old device simply carries them inert — nothing polls them,
hasLocalAuthreads false, and they resolve themselves the moment that device updates.Written up under
apps/docs/src/architecture/sync-handlers.md→ Unknown Calendar Providers Are Not Corruption.Verification
pnpm --filter @memry/desktop typecheck— greenpnpm test:sync-server— 62 files, 941 tests passedmain6404 passed / 1 expected fail / 4 skipped (520 files) ·shared2455 ·main-integration9 · (renderer6973 green as of feat(calendar): provider-aware settings and connect UI shell (#1395) #1425)pnpm docs:impact --base origin/main --strict— green ·pnpm docs:build— greenOwed before merge
The two-profile manual run is not done. The acceptance criteria call for
dev:aon the new build anddev:bon the pre-change build, and I cannot drive two Electron profiles from here. What the tests establish is that the old client's code paths are safe with these rows; what the manual run would add is the end-to-end confirmation over a real sync round trip. Given this issue gates every provider phase, that run should happen before merge rather than after.