feat(calendar): per-provider credentials and settings namespace (#1394) - #1424
Draft
h4yfans wants to merge 1 commit into
Draft
Conversation
|
React Doctor found no new issues. 🎉 Reviewed by React Doctor for commit |
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
h4yfans
force-pushed
the
h4yfans/calendar-sync-engine-generalization
branch
from
August 13, 2026 16:17
302f054 to
ec745e7
Compare
h4yfans
force-pushed
the
h4yfans/calendar-provider-credentials-settings
branch
2 times, most recently
from
August 13, 2026 16:19
f7aeaa4 to
57ac489
Compare
Credentials and settings were both nailed to Google by a constant:
`SERVICE = 'com.memry.calendar.google'` and the literal settings group key
`'calendar.google'`.
**Keychain.** `calendar/provider/credentials.ts` derives the service name per
provider — `com.memry.calendar.<providerId>` — which for google produces the
exact string the constant held, so every credential already on disk still
resolves. The account-key scheme (`${kind}-${accountId}` plus the dev-profile
suffix) and the `LEGACY_DEFAULT_ACCOUNT_ID` path are preserved verbatim.
`GoogleTokenKind` becomes `ProviderSecretKind = 'access-token' |
'refresh-token' | 'password'`; CalDAV needs the password, ICS has no secret at
all.
**Settings.** `calendar.<providerId>` groups in a shared neutral shape
(`CalendarProviderSettings`). Google's group is frozen exactly as it is —
same key, same keys, `pushEventsToGoogle` included — and
`readCalendarProviderSettings` translates that one historic name to
`pushEventsToProvider` so the sync engine only ever sees the neutral form.
No migration; a new provider only ever writes a new key.
**Agent read consent — decision.** `agentReadEventsConsent` is promoted from a
Google special case to the base schema, with per-provider values. No provider's
external events are readable by the agent until that provider is explicitly
consented to; null (never asked) reads as no. Google Workspace Limited Use
forced the question first, and Google's behavior is now an instance of the rule
rather than an exception to it.
The renderer gate follows: `calendar.getRange` resolves `includeExternal` by
requiring consent for every registered provider, since the call carries one
flag rather than a per-provider list. One unconsented provider keeps every
external event out rather than risking one leaking through.
Documented under `apps/docs/src/user-guide/calendar.md` and
`apps/docs/src/user-guide/ai/agent-mcp.md`.
h4yfans
force-pushed
the
h4yfans/calendar-provider-credentials-settings
branch
from
August 13, 2026 16:45
57ac489 to
ccd6a4b
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 #1394. Phase 0 of the multi-provider calendar epic #1390.
What
Two constants were doing the nailing:
SERVICE = 'com.memry.calendar.google'ingoogle/keychain.ts, and the literal group key'calendar.google'.Keychain
calendar/provider/credentials.tsderives the service name per provider —com.memry.calendar.<providerId>. Forgooglethat produces the exact string the constant held, so every credential already on disk still resolves. The account-key scheme (${kind}-${accountId}plus theMEMRY_DEVICEdev-profile suffix) and theLEGACY_DEFAULT_ACCOUNT_IDpath are preserved verbatim.GoogleTokenKind→ProviderSecretKind = 'access-token' | 'refresh-token' | 'password'. CalDAV needspasswordfor basic auth; ICS has no secret at all — its URL is the credential and lives incalendar_sources.metadata.Settings
calendar.<providerId>groups in a shared neutral shape (CalendarProviderSettings). Google's group is frozen exactly as it is — same key, same keys,pushEventsToGoogleand all — andreadCalendarProviderSettingstranslates that one historic name topushEventsToProvideron the way out, so the sync engine only ever sees the neutral form. There is no migration and no new key is written into the Google row; a test asserts the stored JSON keeps precisely its original key set.Agent read consent — the decision
agentReadEventsConsentis promoted from a Google special case to the base schema, with per-provider values:Google Workspace Limited Use is what forced the question first; the answer became the house rule, and Google's current behavior is now an instance of it rather than an exception to it. This is the "privacy is the product" reading and still satisfies the Google obligation.
The renderer gate follows.
calendar.getRangeresolvesincludeExternalby requiring consent for every registered provider, because the call carries one boolean rather than a per-provider list — so one unconsented provider keeps every external event out rather than risking one leaking through. That trade-off is stated plainly in the docs; a per-provider filter is a natural follow-up once a second provider actually ships.Documented in
apps/docs/src/user-guide/calendar.md(new Connected Calendars and AI Features section, with the Google specifics kept underneath) andapps/docs/src/user-guide/ai/agent-mcp.md.Verification
pnpm typecheck— 16/16 greenpnpm lint— 0 errorspnpm ipc:check,pnpm check:contracts— greenpnpm docs:impact --base origin/main --strict— green (docs changed on this branch)pnpm docs:build— green@memry/contracts— 50 files, 1655 testsmain6378 passed / 1 expected fail / 4 skipped (519 files) ·renderer6965 passed / 6 skipped ·shared2455New tests:
provider/settings.test.ts— acalendar.googlefixture written by the Google-only build parses unchanged (including one written beforeagentReadEventsConsentexisted, which must land on "not asked" rather than fail); it reads back correctly through the neutral accessor; a write keeps the legacy key and adds none; a new provider's group never touches Google's; every provider starts at "agent may not read"; a corrupt group falls back to defaults rather than to permissive values.provider/credentials.test.ts— google's service name is byte-identical to the old constant; the account-key scheme and dev-profile suffix are unchanged; a credential written by a previous Google-only build resolves through both the neutral reader and the Google accessor; theLEGACY_DEFAULT_ACCOUNT_IDpath resolves; two providers sharing one account id are partitioned, and disconnecting CalDAV does not log the user out of Google.One existing renderer test file (
agent-mcp/desktop-api-handler.test.tsx) had its mocks repointed fromgetCalendarGoogleSettingstogetCalendarProviderSettings+listProviders, matching the gate's new source. Its assertions are unchanged, and the excluded-operations list gained the neutral twins alongside the Google-named ones.