Reshape the sync-providers seam before Komoot (web-login) and Apple Health (device) adapters land. Captures the decisions in three ADRs, seeds CONTEXT.md with Connected Services vocabulary, and proposes the OpenSpec change covering schema rename + ConnectedServiceManager + capability seams (Importer / RoutePusher / WebhookReceiver). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1.2 KiB
Connected Services use a credential-kind discriminator + JSONB blob
trails.cool integrates with external services whose credentials have
fundamentally different shapes: OAuth2 (Wahoo, future Coros/Garmin/Strava),
web-login session (Komoot — no official API, we authenticate against the
provider's web login with email + password and reuse the cookie jar),
and device pairing (Apple Health, future Health Connect — no remote
credential at all). We considered an OAuth-only schema with nullable extras
and per-kind tables, and rejected both: nullable-OAuth would force Komoot
and Apple Health to leave most columns NULL and bake OAuth assumptions into
queries; per-kind tables would fragment connected_services and complicate
the user-facing "Connected Services" list.
We store all linked external accounts in one connected_services table
with a credential_kind ∈ {oauth, web-login, device} discriminator and a
credentials JSONB blob whose schema is determined by kind. OAuth-specific
granted_scopes stays a top-level column because feature gates query it
directly. Per-kind CredentialAdapter modules own the credential lifecycle
(refresh / relogin / noop); callers never read the JSONB directly.