Files
kandev/docs/specs/azure-devops-integration/spec.md
T
2026-07-21 08:40:26 +01:00

19 KiB

status, created, owner
status created owner
building 2026-07-17 tbd

Azure DevOps Integration

Decision: ADR-2026-07-20-provider-neutral-remote-repositories

Why

Teams whose source code and planning work live in Azure DevOps cannot use Kandev's GitHub or GitLab browsing surfaces to find work items, inspect pull requests, or associate a pull request with a task. Azure users must be able to connect their workspace and read Azure Boards and Azure Repos data without installing or authenticating the GitHub CLI.

What

  • An Azure DevOps connection is configured independently for each Kandev workspace.
  • The first release supports Azure DevOps Services organizations hosted at https://dev.azure.com/<organization> and authenticates with a personal access token stored in Kandev's encrypted secret store.
  • Azure DevOps reads use the Azure DevOps REST API directly. Neither gh nor az is required for connection checks, work-item reads, pull-request reads, or pull-request synchronization.
  • Users can test, replace, copy to another workspace, and delete an Azure DevOps connection from Settings > Integrations > Azure DevOps.
  • Users can browse work items returned by WIQL, inspect their core fields, and launch the existing task-creation flow with the work-item title, description, URL, project, type, state, and identifier available to the launcher.
  • The work-item and pull-request browse surface leads with named, provider-aware presets and supports workspace-scoped saved views. Raw WIQL remains available in an Advanced section instead of occupying the primary filter surface.
  • Users can browse active pull requests by project and repository, including pull requests authored by them and pull requests where they are a reviewer.
  • Pull-request detail includes branches, author, reviewers and votes, comment threads, linked work items, and branch-policy evaluation status.
  • A pull request can be associated with a Kandev task. The association survives backend restarts and refreshes in the background without requiring the task's agent environment to contain Azure or GitHub tooling.
  • Azure DevOps failures are isolated from GitHub, GitLab, Jira, and other integrations. An absent or invalid Azure connection does not prevent Kandev from starting.
  • Saving, copying, replacing, or deleting any integration configuration updates integration navigation immediately. The 90-second health poll remains a recovery mechanism, not the expected propagation path after a local mutation.
  • Configured integrations show an Enabled status in the expanded workspace settings navigation. Azure DevOps uses the official product mark consistently in settings, browse, and task-creation surfaces.
  • The task-creation repository picker combines repositories from every configured source-control provider: GitHub, GitLab, and Azure DevOps. Users can still paste a supported HTTPS or SSH repository URL manually. When more than one repository provider is available, bottom tabs switch the visible provider results; no provider tab bar is shown for a single provider. When all three providers are available, compact icon tabs retain accessible names and expose provider names on hover.
  • Azure DevOps private repositories can be materialized with the workspace PAT by the Kandev backend. The PAT is never added to task metadata, clone URLs, agent environment variables, logs, or persisted repository rows. Push access remains the responsibility of the selected executor's Git credentials.
  • The Azure DevOps browse and settings surfaces provide equivalent desktop and mobile workflows.
  • Organization URL inputs accept an optional trailing slash and persist the canonical URL without it.
  • PAT setup instructions and the organization-specific token-settings link are available from an info control beside the PAT field on hover, focus, or tap.
  • Opening the Azure DevOps browser runs the default work-item query as soon as the connected project's filters are ready; users do not need to submit the initial search manually.

Data Model

azure_devops_configs

One row per workspace:

Field Type Constraint
workspace_id text primary key
organization_url text required, canonical https://dev.azure.com/<organization> URL
default_project_id text optional project GUID
default_project_name text optional display name
auth_method text pat in the first release
last_checked_at datetime nullable
last_ok boolean required, default false
last_error text required, default empty
created_at datetime required
updated_at datetime required

The PAT is never stored in SQLite. It is stored under the encrypted secret key azure_devops:<workspace_id>:pat.

azure_devops_task_prs

One row per task, repository, and Azure pull request:

Field Type Constraint
id text primary key UUID
task_id text required
repository_id text Kandev repository ID, required
organization_url text required
project_id text required
azure_repository_id text Azure repository GUID, required
pull_request_id integer required
pull_request_url text required
title text required
source_branch text required, normalized without refs/heads/ for display
target_branch text required, normalized without refs/heads/ for display
author_id text required
author_name text required
status text active, completed, or abandoned
review_state text normalized summary: approved, waiting, rejected, or empty
policy_state text normalized summary: success, pending, failure, or empty
is_draft boolean required
last_synced_at datetime nullable
created_at datetime required
updated_at datetime required

The tuple (task_id, repository_id, azure_repository_id, pull_request_id) is unique. Provider-native reviewer votes, threads, and policy records are fetched on demand and are not flattened into GitHub review/check records.

Repository provider fields

Azure repositories use the existing repository fields with provider = "azure_devops", the Azure repository GUID in provider_repo_id, the project ID in provider_owner, and the repository name in provider_name. Provider-backed repositories also persist the provider-returned canonical HTTPS clone URL in remote_url. This avoids reconstructing URLs from GitHub-specific owner/name assumptions and allows remote executors to address Azure organizations and GitLab self-managed hosts correctly. Credentials are never embedded in this field.

Saved Azure views

Saved Azure views are workspace-scoped JSON records containing an ID, label, kind (work_item or pull_request), provider-native query/filter values, and a creation timestamp. Invalid entries are ignored when read. Saving a view never persists result data or credentials.

API Surface

Every route requires workspace_id as a query parameter unless the workspace is present in the path.

Method Path Behavior
GET /api/v1/azure-devops/config Return redacted workspace configuration or 204
POST /api/v1/azure-devops/config Validate and save organization, project, and optional replacement PAT
DELETE /api/v1/azure-devops/config Delete configuration and PAT
POST /api/v1/azure-devops/config/test Test submitted or stored credentials without persisting submitted values
POST /api/v1/azure-devops/config/copy Copy configuration and credential to another workspace
GET /api/v1/azure-devops/projects List accessible projects
GET /api/v1/azure-devops/repositories List repositories, optionally filtered by project
GET /api/v1/azure-devops/repositories/:projectId/:repositoryId/branches List repository branches for task creation
GET /api/v1/azure-devops/views Return workspace-scoped saved Azure views
PUT /api/v1/azure-devops/views Replace workspace-scoped saved Azure views
POST /api/v1/azure-devops/work-items/search Execute WIQL and return hydrated work items
GET /api/v1/azure-devops/work-items/:id Return one hydrated work item
GET /api/v1/azure-devops/pull-requests List PRs by project, repository, status, author, or reviewer
GET /api/v1/azure-devops/pull-requests/:projectId/:repositoryId/:pullRequestId Return PR detail
GET /api/v1/azure-devops/pull-requests/:projectId/:repositoryId/:pullRequestId/feedback Return reviewers, threads, linked work items, and policies
GET /api/v1/azure-devops/workspaces/:workspaceId/task-prs Return task PR associations grouped by task
POST /api/v1/azure-devops/tasks/:taskId/pull-requests Validate and associate an Azure PR with a task repository
POST /api/v1/azure-devops/tasks/:taskId/pull-requests/sync Refresh persisted state for one association

Search requests contain project, wiql, and an optional top value. The service hydrates WIQL references in batches no larger than 200. Descriptions returned as HTML are sanitized before display.

Task repository inputs use the provider-neutral remote_url field. The legacy github_url field remains accepted during migration and is normalized to the same internal input. Provider metadata supplied by the browser is treated as a hint and revalidated from the configured provider before persistence or clone.

Permissions

  • Any user who can configure a Kandev workspace can manage that workspace's Azure DevOps connection under the same authorization model as Jira and Linear configuration.
  • The initial PAT requires read access to Work Items and Code. Kandev does not request or exercise work-item write, thread write, or code write permissions in this release.
  • The backend may use the Code Read permission for a one-time authenticated Git clone. It supplies the PAT through an ephemeral Git credential mechanism and clears that mechanism when the child process exits.
  • Credentials from one workspace must never be used to answer a request for a different workspace.

Failure Modes

  • Missing workspace configuration returns a typed not-configured response and a connection CTA; it does not invoke gh or az.
  • A 401 or 403 marks the connection unhealthy and surfaces an authentication or permission error without deleting the stored PAT.
  • Rate limiting, timeouts, and Azure 5xx responses preserve the last known health and PR association data while surfacing staleness and the current error.
  • Invalid organization URLs, unsupported hosts, missing workspace IDs, and malformed WIQL are rejected without persistence.
  • A WIQL result larger than one batch is hydrated in deterministic batches; one omitted/deleted work item does not corrupt the rest of the page.
  • PR association fails closed when the repository is not attached to the task or is not an azure_devops repository.
  • A repository selected from an integration is rejected if its canonical URL or provider identity no longer matches data returned for the active workspace.
  • Failure to resolve or use server-side Azure clone credentials fails the clone without falling back to an unauthenticated URL containing a secret.
  • Integration initialization errors are logged as non-fatal and the rest of the backend remains available.

Persistence Guarantees

  • Configuration, connection health, encrypted PATs, and task PR associations survive backend restarts.
  • Browse results, PR feedback, and REST response caches are transient.
  • Deleting a workspace follows the existing integration cleanup behavior and removes its Azure configuration, PAT, and task PR associations.

Scenarios

  • GIVEN a workspace without GitHub CLI installed, WHEN a user saves a valid Azure organization and PAT, THEN the connection succeeds and Azure projects can be listed without executing gh or az.
  • GIVEN two workspaces configured for different Azure organizations, WHEN each workspace searches work items, THEN each response contains only data accessible through that workspace's credential.
  • GIVEN a valid WIQL query returning more than 200 references, WHEN a user runs the query, THEN Kandev hydrates the requested page in batches and returns normalized work items in query order.
  • GIVEN a displayed Azure work item, WHEN a user launches a task from it, THEN the task-creation flow is populated with the work-item context and source URL.
  • GIVEN a user is a reviewer on an active Azure PR, WHEN they select the reviewer preset, THEN the PR appears with its repository, branches, draft state, and current vote summary.
  • GIVEN an Azure PR linked to a Kandev task, WHEN reviewer votes, threads, or policy evaluations change upstream, THEN a refresh updates the displayed summary while retaining Azure-native detail.
  • GIVEN an expired PAT, WHEN the health poller checks the connection, THEN settings shows the connection as unhealthy with a reconnect action and existing PR associations remain stored.
  • GIVEN a user saves or deletes an integration configuration, WHEN the request succeeds, THEN settings status and home integration navigation update without waiting for the periodic health poll.
  • GIVEN multiple configured source-control providers, WHEN a user opens the Remote repository picker, THEN a bottom tab is shown for each available repository provider, only the active provider's matching results are visible, and selections retain the correct provider icon and branch source on desktop and mobile. The tab footer does not scroll vertically, and three-provider tabs compact to icons with accessible provider names.
  • GIVEN only one configured source-control provider, WHEN a user opens the Remote repository picker, THEN its repositories are shown without a provider tab bar.
  • GIVEN a private Azure repository selected for a task, WHEN Kandev materializes it, THEN the backend uses the workspace PAT for the clone and no task or agent-visible value contains the PAT.
  • GIVEN a user chooses an Azure preset or saved view, WHEN they search, THEN Kandev applies the preset's provider-native query while Advanced WIQL remains available for custom work-item searches.
  • GIVEN a narrow mobile viewport, WHEN a user configures Azure DevOps or browses work items and PRs, THEN all filters and primary actions remain reachable without horizontal page scrolling.

Out Of Scope

  • Azure DevOps Server or Team Foundation Server installations.
  • Microsoft Entra OAuth, service principals, and managed identities.
  • Creating, updating, or transitioning work items.
  • Creating, approving, commenting on, abandoning, or completing pull requests.
  • Automatic CI repair, auto-merge, and Azure Pipelines log streaming.
  • Service-hook/webhook ingestion; reads and refreshes use requests, local invalidation after configuration mutations, and polling for recovery.
  • Using the Azure PAT for agent-authored pushes, pull-request creation, or any other write operation.
  • Requiring Azure CLI or the Azure DevOps CLI extension. The existing optional agentctl PR-create fallback remains separate until write support is added.

Implementation Plan

See the active implementation plan.