Skip to content

refactor(integrations): canonical provider key is the single integration identity - #719

Merged
kentcdodds merged 7 commits into
mainfrom
cursor/canonical-integration-names-aa86
Jul 10, 2026
Merged

kentcdodds merged 7 commits into
mainfrom
cursor/canonical-integration-names-aa86

Conversation

@kody-bot

@kody-bot kody-bot commented Jul 10, 2026 •

Copy link
Copy Markdown
Collaborator

Problem

Integration identity was handled two different ways: records were written under the raw provider string (_integration:GitHub if you connected with provider=GitHub), while the /connect/oauth wizard read by probing two candidate value names (raw, then normalized) via getIntegrationValueCandidates. Every other lookup — integration_get / integration_delete, the refreshAccessToken / createAuthenticatedFetch runtime helpers, and OpenAPI binding auth — was silently case-sensitive, so refreshAccessToken('github') would miss a record saved as GitHub. Follow-up to #716, where this probe was flagged as the remaining legacy-ish naming path.

The single way going forward

Integration identity is the canonical provider key: lowercase kebab via the existing normalizeProviderKey (letters, numbers, ., _, -). One function, applied at every boundary:

  • canonicalIntegrationName in integration-shared.ts; buildIntegrationValueName now canonicalizes, so every exact lookup (integration_get/integration_delete, connect wizard, OpenAPI bindings, runtime helpers via integration_get) tolerates caller casing while touching exactly one stored key.
  • normalizeIntegrationConfig canonicalizes the stored name, and both zod schemas reject names with no letters/numbers, so writes (integration_save, connect_oauth) can only produce canonical records.
  • parseIntegrationValueName only recognizes canonical keys — an integration exists iff its stored key is canonical.
  • The client's dual-candidate probe (getIntegrationValueCandidates) is deleted; the wizard does one deterministic lookup by provider key.
  • Docs: docs/guides/oauth.md naming section and the integration_save capability description state the rule.

No data migration needed: a live audit of the production account's stored _integration: values (17 records: dropbox, github, github-kent, google, google-business, google-youtube-brand, google-youtube-plus, groupme, linkedin, notion, slack, spotify, spotify-family, telegram, tesla, twitch, x) confirmed every name is already canonical. Per the pre-launch convention this is a direct breaking change with no compat aliasing: a hypothetical non-canonical record would simply stop being treated as an integration.

Testing

  • New unit coverage: save with name: 'GitHub' stores and returns github under _integration:github; buildIntegrationValueName casing/spacing normalization (client and server copies); strict parseIntegrationValueName; rejection of letterless names. Handler tests updated for canonical integrationName responses.
  • npm run validate fully green (format, lint, typecheck, unit tests, Playwright E2E, MCP E2E). One pre-push E2E run had a single og-images failure ("socket hang up" on the web server's first request) that passed on immediate re-run — flake, not related to this change.
  • Manual GUI test: opened /connect/oauth?provider=CANVA-Mock (uppercased) against the integration stored as _integration:canva-mock; the wizard resolved the stored record, displayed the canonical value name it loaded from, and completed the full OAuth flow — after which local D1 still contains exactly one canonical record and no _integration:CANVA-Mock duplicate. Screenshot and screen recording of this test are attached to the Cursor agent run.
System recap — extends existing primitives (medium risk)

Mode: recap · Base: main @ 646826e4 · Head: 00b124bb

Classification: extends — the integration naming contract becomes strictly canonical; no new primitives.

Primitives touched

Primitive Group Impact
capability-registry assistant extends — integrations domain canonicalizes names on save/get/delete; schemas reject letterless names
app-ui surfaces extends — connect wizard does a single canonical lookup instead of the dual-name probe
values assistant composes — _integration: value keys are always _integration:<canonical-name>

System map

Every integration read/write path now derives its value key through one canonicalization function.

Legend: green = composes (wiring only) · amber = extended by this PR · red = new primitive · gray = context (unchanged, included only when an edge crosses it).

flowchart LR
	appUi["app-ui<br/>Browser app (Remix 3)"]:::extended
	capabilityRegistry["capability-registry<br/>Capability registry"]:::extended
	values["values<br/>Values"]:::touched
	appUi -->|"single canonical _integration lookup"| values
	capabilityRegistry -->|"canonicalIntegrationName in buildIntegrationValueName"| values
	classDef touched fill:#1a7f37,color:#fff
	classDef extended fill:#9a6700,color:#fff
	classDef added fill:#cf222e,color:#fff
	classDef untouched fill:#57606a,color:#fff
Loading

Before / after

before: write key = raw name; connect wizard probes [raw, normalized]; other lookups case-sensitive
after:  key = _integration:<normalizeProviderKey(name)> everywhere; probe deleted; parse strict

Invariants

per-user-isolation untouched — all value reads/writes remain scoped by userId. Verified live data is already canonical, so the strictness introduces no orphaned records.

Summary by CodeRabbit

  • New Features
    • Integration names are now normalized to a canonical lowercase kebab-case provider key.
    • Stored integration identity is generated from this canonical key for consistent save/load behavior.
  • Bug Fixes
    • Retrieving existing integrations is more reliable across capitalization and formatting differences.
    • Non-canonical or malformed integration identifiers are rejected.
  • Documentation
    • Updated OAuth/integration guidance to document the canonical naming and storage behavior.
  • Tests
    • Expanded/adjusted coverage to verify canonical identity handling and validation rules.

…nect

Canva requires BOTH S256 PKCE and a client secret on token exchange, but
/connect/oauth treated flow as pkce XOR confidential. PKCE is now an
orthogonal usePkce switch (pkce=true|false query param, persisted in the
integration record when it differs from the flow default), and a new
basic-form token exchange style sends HTTP Basic client auth with an
urlencoded body. api.canva.com defaults to confidential flow + PKCE +
basic-form, mirroring the Notion basic-json host default.
Addresses CodeRabbit review on #716: percent-encode client_id and
client_secret before joining with ':' and base64-encoding so reserved
characters survive, and strengthen basic-form tests to cover body
credential stripping and each validation condition independently.
Bugbot flagged that requiring usePkce in the sessionStorage config guard
would reject configs persisted before the orthogonal-PKCE change, failing
in-flight connects at the callback leg. Backfill the flow/host default
instead of rejecting, and cover the legacy shapes with unit tests.
…ict validation

The sessionStorage snapshot lives for a single authorize round trip, so a
shape without usePkce can only exist for a flow in-flight across the one
deploy that ships this change; recovery is restarting the connect flow.
Keeping a permanent backfill for that transient window is not worth it.
Validation now requires usePkce and rejects stale shapes deliberately.
…tegration identity

Integration records were written under the raw provider string while the
connect wizard probed both the raw name and the normalized key, and every
other lookup (integration_get/delete, runtime helpers, OpenAPI bindings)
was silently case-sensitive. Now canonicalIntegrationName (lowercase kebab
via normalizeProviderKey) is applied by buildIntegrationValueName and
normalizeIntegrationConfig, so every read and write path shares one key
derivation; the client probe is deleted and parseIntegrationValueName only
recognizes canonical keys. Kent's 17 stored integrations are all already
canonical (verified via live audit), so no data migration is needed.
@coderabbitai

coderabbitai Bot commented Jul 10, 2026 •

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: fe680c96-2528-41d3-ac81-6e5b7492eb52

📥 Commits

Reviewing files that changed from the base of the PR and between 00b124b and 6008f78.

📒 Files selected for processing (2)
  • packages/worker/src/mcp/capabilities/integrations/integration-save.node.test.ts
  • packages/worker/src/mcp/capabilities/integrations/integration-shared.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/worker/src/mcp/capabilities/integrations/integration-save.node.test.ts
  • packages/worker/src/mcp/capabilities/integrations/integration-shared.ts

📝 Walkthrough

Walkthrough

Integration identities are normalized to canonical lowercase-kebab provider keys across validation, saved values, parsing, OAuth lookup, documentation, and related test expectations.

Changes

Canonical integration identity

Layer / File(s) Summary
Canonical storage contract
packages/worker/src/mcp/capabilities/integrations/integration-shared.ts, packages/worker/src/mcp/capabilities/integrations/integration-save.*, docs/guides/oauth.md
Integration names are validated and stored as canonical provider keys; value-name parsing accepts only canonical _integration: keys, with tests and documentation covering the behavior.
OAuth lookup migration
packages/worker/client/routes/connect-oauth.*, packages/worker/src/app/handlers/account-secrets.node.test.ts
OAuth configuration lookup derives one canonical value name instead of probing candidates, and expected integration metadata uses lowercase identifiers.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Possibly related PRs

  • kentcdodds/kody#87: Both changes touch the OAuth connect implementation and stored integration handling.
  • kentcdodds/kody#550: Both changes involve OAuth integration identifiers and stored _integration: values.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 20.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: canonical provider keys becoming the single integration identity.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch cursor/canonical-integration-names-aa86

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@kody-bot
kody-bot marked this pull request as ready for review July 10, 2026 22:37
@github-actions

github-actions Bot commented Jul 10, 2026 •

Copy link
Copy Markdown
Contributor

🔎 Preview deployed: https://kody-pr-719.kody-a99.workers.dev

Worker: kody-pr-719
D1: kody-pr-719-db
KV: kody-pr-719-oauth-kv

Mocks:

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/worker/src/mcp/capabilities/integrations/integration-shared.ts`:
- Around line 17-22: Update integrationNameSchema’s refine predicate to require
at least one ASCII letter or digit in the canonicalized name, rather than only
checking that canonicalIntegrationName(name) is non-empty. Preserve the existing
minimum-length validation and error message so names consisting solely of dots,
underscores, or hyphens are rejected.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: cf5117b9-2a4c-4c45-85fd-d8a5882aec1b

📥 Commits

Reviewing files that changed from the base of the PR and between 7bc4f08 and 00b124b.

📒 Files selected for processing (7)
  • docs/guides/oauth.md
  • packages/worker/client/routes/connect-oauth.node.test.ts
  • packages/worker/client/routes/connect-oauth.tsx
  • packages/worker/src/app/handlers/account-secrets.node.test.ts
  • packages/worker/src/mcp/capabilities/integrations/integration-save.node.test.ts
  • packages/worker/src/mcp/capabilities/integrations/integration-save.ts
  • packages/worker/src/mcp/capabilities/integrations/integration-shared.ts

…n names

Addresses CodeRabbit review on #719: names made only of dots, underscores,
or hyphens survive canonicalization non-empty, so the schema now requires
at least one alphanumeric character in the canonical form.
@kentcdodds
kentcdodds merged commit 497af02 into main Jul 10, 2026
4 checks passed
@kentcdodds
kentcdodds deleted the cursor/canonical-integration-names-aa86 branch July 10, 2026 22:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants