Gap Analysis: Migrating Spec-Kitty to the Installation-Link-Mapping-Override Connector Model
Status: Draft
Date: 2026-03-10
Scope: spec-kitty-saas, spec-kitty CLI
Related ADR: Connector Installation, User Link, and Resource Mapping Separation
Related PRD: prd-connector-auth-first-team-binding-v1.md in the planning repo
Executive Summary
Spec-Kitty does not currently implement a connector model that can be cleanly re-skinned into "workspace installation first" with minor UI changes. The existing software is structurally centered on project-scoped bindings.
The current system has two partially overlapping connector architectures:
ConnectorBinding: legacy webhook/OAuth connector model with secrets and OAuth credentials stored directly on a project bindingIssueTrackerBinding+UserProviderConnection: newer tracker model that separates per-user auth somewhat, but still anchors auth and routing to a project binding
The largest migration gaps are:
- There is no workspace-level installation root object
- Webhook routing is addressed by
binding_uuid, not by installation plus resource mapping - Nango connection IDs and auth webhooks are keyed to project binding identity
- Tracker ingest and egress APIs auto-create or resolve
IssueTrackerBindingdirectly - The CLI already emits
repo_slug, but the SaaS does not materialize repo identity into build read models that can power Git repo auto-mapping
The good news is that some foundations are reusable:
Teamalready works as the v1 workspace boundary- Invite and membership flows are already usable for workspace onboarding
PreAuthSessionis conceptually reusable if retargeted from "binding creation" to "installation/link completion"- The CLI already emits the key Git correlation signals needed for repo auto-suggestion
Target Model Recap
The target connector model is:
TeamServiceInstallationWorkspace-level provider installation or admin-authorized connectionUserServiceLinkOptional member-level identity link to an installationServiceResourceMappingExplicit mapping from external resource to project or feedProjectServicePolicyOverrideOptional project-level doctrine and routing exceptions
GitHub and GitLab may auto-suggest mappings from Git signals, but the source of truth remains the explicit mapping record.
Current Software Snapshot
1. Tenancy and membership
Current state:
Teamis the only tenant container in the SaaS- users join via membership and invitation flows
- default-team creation already exists for first-time signup
Relevant code:
spec-kitty-saas/apps/teams/models.pyspec-kitty-saas/apps/teams/helpers.pyspec-kitty-saas/apps/teams/forms.py
Assessment:
- This is compatible with the proposed v1 model
- "workspace" can map to the existing
Teamobject without structural change - No squad/sub-team hierarchy exists yet, so installation and mapping must live on
Teamfor now
Gap severity: Low
2. Connector domain model
Current state:
ConnectorBindingis explicitly defined as a project-scoped external connector binding- it stores webhook secret, OAuth tokens, provider account metadata, lifecycle state, and health on the same project record
IssueTrackerBindingis also project-scopedUserProviderConnectionis per-user, but only in relation to one tracker bindingPreAuthSessionis user/team-scoped but still points toIssueTrackerBinding
Relevant code:
spec-kitty-saas/apps/connectors/models.py
Assessment:
- The current data model does not have a stable root object equivalent to
TeamServiceInstallation - auth state, routing state, and project policy are still collapsed into binding-centric records
- most downstream tracker records point at
IssueTrackerBinding, which means the binding abstraction is pervasive
Gap severity: Critical
3. URL and endpoint structure
Current state:
- connector URLs are binding-oriented:
/<connector_type>/create//<connector_type>/oauth/start//<connector_type>/<binding_uuid>/...
- tracker URLs are binding-oriented:
/trackers/create//trackers/<binding_id>/...
- GitHub webhook entrypoint is
/api/v1/webhooks/github/<binding_uuid>/
Relevant code:
spec-kitty-saas/apps/connectors/urls.py
Assessment:
- there is no installation detail page, resource mapping page, or override surface
- the external webhook contract itself assumes "webhook URL equals project binding"
Gap severity: High
4. OAuth onboarding flow
Current state:
- generic OAuth start requires
project - GitHub OAuth start also requires
repo_full_name - callback consumes state with
team_id,project_id, andrepo_full_name - callback creates or updates a
ConnectorBinding - OAuth credentials are persisted directly on that binding
Relevant code:
spec-kitty-saas/apps/connectors/views.py
Assessment:
- this is the opposite of installation-first onboarding
- the first act of consent creates project routing and auth state simultaneously
- reauth is also binding-specific instead of installation-specific
Gap severity: Critical
5. Tracker onboarding flow
Current state:
- issue tracker creation starts by choosing a project and provider
- OAuth providers require a
PreAuthSession - final submission creates
IssueTrackerBinding - if OAuth was used, it also creates
UserProviderConnection(binding, user) - wizard steps are
Select -> Authenticate -> Workspace -> Configure, but "Select" means project first
Relevant code:
spec-kitty-saas/apps/connectors/views.pyspec-kitty-saas/templates/web/connectors/tracker_binding_create.htmlspec-kitty-saas/templates/web/connectors/components/binding_step_project.html
Assessment:
- the tracker system has useful ideas, but the root is still wrong
- workspace discovery exists, but the discovered workspace is immediately bound to a project rather than to a workspace installation
- user auth is still modeled as a property of one binding, not of the installation
Gap severity: Critical
6. Nango control plane identity
Current state:
- Nango per-user connection IDs are built from:
team_idproject_uuidproviderbinding_iduser_id
- pre-auth IDs are generated per team/provider/user and later attached to a binding
- tracker OAuth session creation ensures a
UserProviderConnectionexists for a specific binding
Relevant code:
spec-kitty-saas/apps/connectors/nango.pyspec-kitty-saas/apps/connectors/views.py
Assessment:
- connection identity is binding-centric all the way down
- the current control-plane identifier scheme cannot express "this user linked to the workspace installation" without inventing a fake binding
- any migration will need a new Nango connection ID contract and compatibility plan for old connections
Gap severity: Critical
7. Webhook routing and event emission
Current state:
- GitHub webhook requests resolve exactly one
ConnectorBindingbybinding_uuid - the request is HMAC-validated with that binding's secret
- payload repository must exactly equal
binding.repo_full_name - handlers emit project events directly from
binding.project
Relevant code:
spec-kitty-saas/apps/connectors/views.pyspec-kitty-saas/apps/connectors/handlers.py
Assessment:
- webhook routing is currently "pre-routed" by URL plus one repo equality check
- there is no lookup path for "installation sees repo X, mapping routes repo X to project Y"
- GitHub/GitLab cannot move to installation-first without redesigning webhook endpoints, secrets, replay, idempotency, and event payload conventions
Gap severity: Critical
8. Tracker ingest and egress APIs
Current state:
- tracker snapshot ingest resolves a project first
- the payload must include
providerandworkspace - the API
update_or_creates anIssueTrackerBinding - tracker status egress also resolves by
(team, project, provider, workspace) - downstream records such as
WorkPackageTrackerLink,TrackerSyncRun,TrackerDrift,TrackerSnapshotReceipt,ConnectorOperationLog, andConnectorDeadLetterItemall refer to a binding
Relevant code:
spec-kitty-saas/apps/connectors/views.pyspec-kitty-saas/apps/connectors/models.py
Assessment:
- tracker synchronization is deeply bound to project binding identity
- the current ingest path treats tracker binding creation as a side effect of receiving snapshot data
- this is incompatible with explicit installation and mapping records being authoritative
Gap severity: Critical
9. Git signal persistence and repo auto-suggestion
Current state:
- the CLI resolves and emits:
git_branchhead_commit_sharepo_slug
- SaaS
Eventingestion stores the event envelope and payload Buildpersistsbranchandhead_commitBuildCommitSnapshotpersists commit graph snapshots- no build-level or commit-level read model stores
repo_slug, remote URL identity, or a provider repo identifier
Relevant code:
spec-kitty/src/specify_cli/sync/git_metadata.pyspec-kitty/src/specify_cli/sync/emitter.pyspec-kitty-saas/apps/sync/models.pyspec-kitty-saas/apps/sync/materialize.py
Assessment:
- the CLI contract is already good enough to support repo auto-suggestion
- the SaaS currently drops the most important provider identity field for that use case
- explicit repo mappings can be introduced without changing the CLI event shape, but only after the SaaS materializes and indexes repo identity
Gap severity: High
10. UI and information architecture
Current state:
- the Connectors page is a binding index
- generic connector create form starts with
Project - issue tracker wizard starts with
Project - details pages are centered on a single binding
- the list is split between "Webhook / OAuth Connectors" and "Issue Trackers"
Relevant code:
spec-kitty-saas/templates/web/connectors/binding_list.htmlspec-kitty-saas/templates/web/connectors/binding_create.htmlspec-kitty-saas/templates/web/connectors/tracker_binding_create.htmlspec-kitty-saas/templates/web/connectors/binding_detail.htmlspec-kitty-saas/templates/web/connectors/tracker_binding_detail.html
Assessment:
- the current information architecture teaches the wrong mental model
- the UI is not simply missing a page; it is shaped around the wrong primary object
Gap severity: High
11. Tests and operational assumptions
Current state:
- onboarding tests assume OAuth start requires
project - callback tests assert
ConnectorBindingcreation - webhook tests assume binding-specific webhook URLs and exact repo match
- provider parity tests treat GitHub, GitLab, Slack, Jira, and Linear as the same binding shape
Relevant code:
spec-kitty-saas/apps/connectors/tests/test_oauth_onboarding.pyspec-kitty-saas/apps/connectors/tests/test_views.py
Assessment:
- a large portion of the test suite encodes the current domain model
- migration success will require intentionally rewriting tests, not patching them
Gap severity: High
Gap Matrix
| Area | Current State | Target State | Gap Severity | Notes |
|---|---|---|---|---|
| Team/workspace boundary | Team exists, invites exist |
Team acts as workspace |
Low | Reusable as-is for v1 |
| Installation root | None | TeamServiceInstallation |
Critical | Foundational missing object |
| User link | Tied to IssueTrackerBinding |
UserServiceLink tied to installation |
Critical | Current auth graph is too narrow |
| Resource mapping | Implicit in bindings | Explicit ServiceResourceMapping |
Critical | Needed for all providers |
| Project override | Stored on tracker binding | Optional ProjectServicePolicyOverride |
High | Must split default vs exception policy |
| OAuth start/callback | Project-first, binding-creating | Installation-first, link later | Critical | Current flows create wrong object |
| Nango connection IDs | Binding/user/project keyed | Installation/user keyed | Critical | Requires compatibility strategy |
| GitHub webhooks | binding_uuid endpoint |
Installation endpoint + mapping lookup | Critical | Biggest external contract break |
| Tracker ingest/egress | Binding auto-create/lookup | Installation + mapping + override | Critical | Deep pipeline dependency |
| Git auto-suggestion | CLI emits signals, SaaS drops repo identity | Persist repo identity and confidence | High | Good opportunity with limited CLI churn |
| Connectors UI | Binding list and binding forms | Installation hub + mapping tables | High | Product and code change |
| Tests | Binding-first assertions | Installation-first assertions | High | Rewrite expected |
What Can Be Reused
Reusable with minor adaptation
Team, memberships, and invitationsPreAuthSessionas a temporary auth session primitive- parts of
UserProviderConnectionhealth fields and refresh telemetry - tracker operation logs, dead-letter handling, and sync-run telemetry patterns
- CLI git metadata extraction and event emission
Reusable only after retargeting foreign keys
WorkPackageTrackerLinkTrackerSyncRunTrackerDriftTrackerSnapshotReceiptConnectorOperationLogConnectorDeadLetterItem
These likely need to point to a mapping and installation combination rather than to IssueTrackerBinding.
Not reusable as the long-term root abstraction
ConnectorBindingIssueTrackerBinding- binding-specific webhook URLs
- binding-specific OAuth callback semantics
Recommended Migration Strategy
Wave 1: Add new canonical models without deleting old ones
Build and migrate:
TeamServiceInstallationUserServiceLinkServiceResourceMappingProjectServicePolicyOverride
Also add:
- installation-level lifecycle and health fields
- mapping-level routing metadata
- installation-level provider account identity and scopes
Goal:
- create the new canonical model graph before touching old flows
Wave 2: Persist Git provider identity in SaaS read models
Add new persisted fields or read models for:
repo_slug- observed provider host if needed
- maybe normalized
provider_resource_key
Populate from:
- incoming event envelope fields
- future build materialization
Goal:
- enable GitHub/GitLab mapping suggestions before webhook contract migration
Wave 3: Introduce installation-first UI and auth flows
Build:
- Connectors index by installation
- installation detail page
- explicit mapping table per installation
- optional "Link my account" actions per member
Change auth flows so that:
- install/create does not require project
- callback creates or updates
TeamServiceInstallation - user-link flows create
UserServiceLink
Goal:
- establish the correct mental model in product and code
Wave 4: Move tracker sync to installation + mapping resolution
Refactor tracker APIs to:
- resolve installation from provider + workspace/site identity
- resolve resource mapping from external resource to project
- apply optional project override
Stop:
- auto-creating
IssueTrackerBindingfrom ingest traffic
Goal:
- make the non-git provider pipeline authoritative on installations and mappings
Wave 5: Replace binding-specific webhook routing for Git providers
Introduce:
- installation-level Git webhook endpoint
- repo mapping lookup
- heuristic fallback using repo slug, commit graph, and branch signals
- unmatched event queue for low-confidence routing
Then deprecate:
github/<binding_uuid>/webhook URLsConnectorBindingas the webhook authority
Goal:
- move the external contract to the new model only after mapping infrastructure exists
Wave 6: Retire old binding-first surfaces
Remove or freeze:
- binding-first create flows
- binding-first detail pages
- binding-specific OAuth token storage
- obsolete tests
Goal:
- complete the domain migration cleanly rather than supporting two mental models indefinitely
Risks and Blocking Questions
1. GitHub provider mode
Open question:
- Is the intended long-term GitHub integration OAuth app, GitHub App, or both?
Why it matters:
- installation semantics, webhook ownership, and repository permissions differ materially
2. Multiple installations per provider per workspace
Open question:
- Should one team be allowed to connect multiple Jira sites, multiple GitHub orgs, or both GitHub.com and GitHub Enterprise?
Why it matters:
- it changes uniqueness constraints for
TeamServiceInstallation
3. Mapping destination types
Open question:
- Besides project, which first-class destinations should be supported in v1 for Slack and future connectors?
Why it matters:
- mapping schema should not have to be redesigned once notifications or incident streams are introduced
4. Backward compatibility for existing webhook URLs
Open question:
- Do existing GitHub bindings need a migration bridge or can they be invalidated during rollout?
Why it matters:
- this affects rollout safety, support load, and migration tooling
5. Ownership of tracker telemetry objects
Open question:
- Should tracker telemetry point at installation, mapping, override, or a combination?
Why it matters:
- analytics and debugging should answer both "which workspace installation failed?" and "which project mapping drifted?"
Fastest Practical Path
If the goal is to move quickly without destabilizing the current system, the best order is:
- add the new model graph
- persist Git repo identity in SaaS read models
- ship installation-first UI and auth flows
- migrate tracker APIs to the new model
- migrate Git webhook routing last
The wrong order would be:
- remove the project selector
- keep storing state on bindings
- defer webhook and tracker model changes
That would produce nicer screens while preserving the same architectural trap.
Bottom Line
The proposed model is achievable with the current software, but it is a real architecture migration, not a small refactor.
The most important implementation decision is to treat TeamServiceInstallation and ServiceResourceMapping as the new source of truth before rewriting webhook or tracker behavior. Once those exist, the rest of the migration becomes a sequence of retargeting flows. Without them, the product will continue to fight the provider-native mental model even if the UI language improves.