401 lines
22 KiB
Markdown
401 lines
22 KiB
Markdown
---
|
|
spec: docs/specs/integrations/requirements/azure-devops-integration.md
|
|
created: 2026-07-17
|
|
updated: 2026-07-31
|
|
status: building
|
|
---
|
|
|
|
# Azure DevOps Integration Plan
|
|
|
|
## Scope
|
|
|
|
Implement the Azure DevOps Services integration defined in
|
|
[`../../specs/integrations/requirements/azure-devops-integration.md`](../../specs/integrations/requirements/azure-devops-integration.md):
|
|
workspace-scoped PAT configuration, direct REST work-item and pull-request
|
|
reads, persistent task PR links, responsive settings/browse surfaces, immediate
|
|
integration availability updates, provider-neutral remote repository selection,
|
|
server-side authenticated Azure clones, an Azure Boards view, remembered browse
|
|
preferences, rich work-item detail, provider-specific task actions, and
|
|
work-item/PR watchers. No Azure runtime path may require `gh` or `az`.
|
|
|
|
## Current State
|
|
|
|
Tasks 01-29 are implemented in this workspace. Tasks 19 and 23 were reopened
|
|
and completed for a refresh-hydration regression and settings-parity gap. The backend persists workspace
|
|
query/action overrides at `/api/v1/azure-devops/workspace-settings`, exposes
|
|
generation-safe work-item and pull-request watchers, dispatches deduplicated
|
|
tasks through the orchestrator, and performs immediate auth-health probes after
|
|
credential changes. The browse surface restores portable per-user filters,
|
|
opens a responsive read-only detail dialog/drawer, supports assignment and
|
|
board-column changes, and launches associated Kandev tasks from provider
|
|
quick-action presets. Settings exposes quick-action configuration plus watcher
|
|
create/edit/enable/disable/run/reset/delete controls.
|
|
|
|
The repair adds boot-state mapping coverage, makes the browse surface consume
|
|
workspace-resolved query presets, and aligns Azure's watcher/action/query
|
|
section order and editor semantics with GitHub. Focused Go, Vitest, desktop
|
|
Playwright, and mobile Playwright commands are recorded in Tasks 19 and 23.
|
|
|
|
## Architecture
|
|
|
|
- Add an independent `internal/azuredevops` package. Do not add Azure methods to
|
|
`github.Client` or translate Azure records into GitHub API structs.
|
|
- Reuse Jira's workspace-scoped config/secret/health patterns and GitLab's
|
|
source-host REST/task-review patterns.
|
|
- Persist provider-native Azure identifiers. Normalize only the summary fields
|
|
required by shared task UI.
|
|
- Persist canonical credential-free remote URLs for provider repositories and
|
|
normalize new `remote_url` task inputs alongside the legacy `github_url`
|
|
compatibility field.
|
|
- Resolve Azure clone credentials by workspace inside the backend clone path.
|
|
Never expose the PAT to task metadata, an agent environment, a persisted URL,
|
|
or command output.
|
|
- Use Azure DevOps REST API 7.1, an injected HTTP client for deterministic
|
|
tests, bounded response bodies, context-aware requests, and typed API errors.
|
|
- Discover board context through project teams and team backlog levels, then
|
|
combine the selected backlog's work-item references with its board column
|
|
metadata. Hydrate work items through the existing bounded 200-item batches.
|
|
- Keep provider writes behind a fixed server-side field allowlist. Resolve the
|
|
selected board's column/done reference names on the backend and use an Azure
|
|
JSON Patch `/rev` test before assignment or board-position operations. Resolve
|
|
Assign to me from the stored PAT identity; never accept an arbitrary identity
|
|
supplied by the browser.
|
|
- Store Azure browse preferences in backend-owned portable user settings, keyed
|
|
by workspace. Do not introduce localStorage/sessionStorage fallback reads or
|
|
dual writes.
|
|
- Fetch work-item detail and discussion on demand. Normalize a small allowlist
|
|
of planning fields, sanitize provider HTML, and page comments with Azure's
|
|
opaque continuation token.
|
|
- Add a direct revision-safe work-item assignment endpoint for detail opened
|
|
outside a board. Keep status/column changes on the existing board endpoint,
|
|
because only board context can validate a provider column.
|
|
- Reuse GitHub's action/default-query preset shapes and GitLab's generation-safe
|
|
issue/review watcher lifecycle without translating Azure records into either
|
|
provider's models.
|
|
- Probe saved credentials immediately and return the resulting health in the
|
|
config mutation response. Keep the 90-second health poll as recovery.
|
|
- Register the service as non-fatal during backend boot and expose mock routes
|
|
only under `KANDEV_MOCK_AZURE_DEVOPS=true`.
|
|
|
|
## Backend Touch Points
|
|
|
|
- New package: `apps/backend/internal/azuredevops/`.
|
|
- Service wiring: `apps/backend/internal/backendapp/services.go`,
|
|
`helpers.go`, and `main.go` where pollers are started.
|
|
- Repository provider parsing/discovery where provider enums are currently
|
|
restricted to GitHub/GitLab.
|
|
- Runtime defaults: `profiles.yaml` for the E2E mock selector only.
|
|
- Workspace cleanup and task/repository validation through existing service
|
|
interfaces rather than integration-specific SQL outside the new package.
|
|
- User settings models/DTO/store patches for
|
|
`azure_devops_browse_preferences`.
|
|
- Work-item detail/comments/current-identity REST methods, normalized planning
|
|
fields, and constrained assignment/column patch generation.
|
|
- Persistent task/work-item links, workspace query/action preset overrides, and
|
|
Azure watcher/reservation stores.
|
|
- Azure watcher polling plus provider events, orchestrator watcher sources,
|
|
self-heal, in-flight throttling, reset, and cleanup wiring.
|
|
|
|
## Frontend Touch Points
|
|
|
|
- Typed API and types under `apps/web/lib/api/domains/azure-devops-api.ts` and
|
|
`apps/web/lib/types/azure-devops.ts`.
|
|
- Domain hooks under `apps/web/hooks/domains/azure-devops/`.
|
|
- Settings route and integration menu entry.
|
|
- `/azure-devops` browse page with a compact work-item/PR segmented view,
|
|
desktop filter rail, and mobile filter sheet.
|
|
- `/azure-devops` Board mode becomes the default connected view. Its project,
|
|
team, and board selectors share one board view model with a multi-column
|
|
desktop DnD composition and a focused single-column mobile composition.
|
|
- Desktop card moves are optimistic and roll back on failure. Card editing uses
|
|
a dialog on wider layouts and a full-height, safe-area-aware mobile surface;
|
|
the mobile editor includes an explicit column picker instead of requiring
|
|
touch drag.
|
|
- Task PR summary integration through a provider-tagged view model; Azure
|
|
detail remains in Azure-specific components.
|
|
- A shared integration availability invalidation channel updates every consumer
|
|
after configuration mutations while retaining periodic health polling.
|
|
- A shared source-control repository picker merges GitHub, GitLab, and Azure
|
|
discovery and dispatches branch reads to the selected provider.
|
|
- Azure browse presets and saved views reuse the interaction model of GitHub and
|
|
GitLab, with raw WIQL contained in an Advanced disclosure.
|
|
- The page hydrates portable Azure preferences before selecting defaults,
|
|
persists only valid user changes, and falls back deterministically when a
|
|
remembered provider ID is stale.
|
|
- Board cards and work-item rows open a desktop `Dialog`. Phone uses a dedicated
|
|
full-height `Drawer` with a fixed header/action area and one internally
|
|
scrolling detail/discussion body. Shared hooks own detail, assignment, move,
|
|
quick-action, and error state.
|
|
- Work-item detail is read-only except for Assign to me, Unassign, and board
|
|
column/split-state controls. A visible Task menu exposes workspace-configured
|
|
actions and existing linked Kandev tasks.
|
|
- Azure settings follows GitHub's analogous order: connection, pull-request
|
|
watches, work-item watches, quick actions, and default queries. Action and
|
|
query editors use tabbed responsive cards, Reset, dirty highlighting, and the
|
|
shared floating Save changes control.
|
|
- No required action may be hover-only or desktop-only.
|
|
|
|
## Implemented Design
|
|
|
|
### Watch persistence and lifecycle
|
|
|
|
- Mirror GitLab's two-kind watcher layout with Azure-native names:
|
|
`watch_models.go`, `store_watches.go`, `service_watch_reset.go`,
|
|
`controller_watches.go`, and `controller_watch_reset.go`.
|
|
- Persist separate work-item and PR watch tables plus separate reservation
|
|
tables. Reservation uniqueness always includes `generation`; work-item
|
|
identity is project/work-item ID and PR identity is
|
|
project/Azure-repository/PR ID.
|
|
- Keep the Kandev task repository (`repository_id`) distinct from the optional
|
|
Azure PR filter (`azure_repository_id`). Normalize poll interval to 300
|
|
seconds when omitted and clamp values below 60. For `max_inflight_tasks`,
|
|
omitted preserves the value, zero clears it, and a positive value sets it.
|
|
- ID-addressed controller operations first load the record, authorize its
|
|
workspace, and return the same not-found response for absent and unauthorized
|
|
watches. Reset routes use `/:id/reset/preview` and `/:id/reset`.
|
|
- Reset increments the generation before shared cleanup and then removes prior
|
|
reservations. Delete marks the watch deleting and disabled before cleanup.
|
|
Every reservation attach and release is conditioned on watch ID plus
|
|
generation.
|
|
|
|
### Polling and dispatch
|
|
|
|
- One Azure poller schedules both watch kinds, selects enabled due watches, and
|
|
uses one bounded check path for create, Run now, and periodic polling. Limit
|
|
each provider check to 100 ordered matches and hydrate work items in Azure's
|
|
200-ID maximum batches.
|
|
- Publish an event only after a current-generation reservation succeeds.
|
|
Authentication, query, and provider failures update `last_error` and
|
|
`last_error_at`, publish nothing, and retain the next polling opportunity.
|
|
- Reconcile terminal state only for reservations that own an auto-created task.
|
|
Apply shared `auto`, `always`, and `never` cleanup without touching manual
|
|
tasks or another generation's task.
|
|
- Add work-item and PR Azure `WatcherSource` implementations. Their
|
|
`WatchMetadataKey` values are respectively
|
|
`azure_devops_work_item_watch_id` and `azure_devops_pr_watch_id`; the task
|
|
metadata must write those exact keys plus the provider identity keys defined
|
|
in the spec.
|
|
- Wire event handlers, shared throttling, reservation release/attach,
|
|
dependency self-heal, poller start/stop, and workspace/task cleanup into
|
|
backend startup. Missing workflow, step, task repository, agent profile, or
|
|
executor profile disables the watch with a user-visible error.
|
|
|
|
### Work-item detail
|
|
|
|
- Add `PATCH /api/v1/azure-devops/work-items/:id` for Assign to me/Unassign
|
|
from search results. Reuse the existing PAT-identity lookup, allowlisted JSON
|
|
Patch builder, revision conflict mapping, and normalized work-item response.
|
|
Continue using the board endpoint for column and split-state changes.
|
|
- A shared detail hook loads core item and comments independently, owns comment
|
|
continuation tokens, derives linked tasks from the workspace association
|
|
cache, and exposes rollback-safe assignment/move mutations.
|
|
- Render Azure description HTML through the existing
|
|
`rehype-raw`/`rehype-sanitize` approach used by provider content. Never mount
|
|
provider HTML directly. Load comments newest-first; append older pages
|
|
without duplicates and retry only the failed page.
|
|
- Board cards and work-item rows open the same logical detail. Desktop uses a
|
|
modal `Dialog` over retained context. Phone uses a `100dvh` full-height
|
|
`Drawer` with fixed header/action regions, one `min-h-0 overflow-y-auto`
|
|
body, safe-area padding, focus return, and 44px targets.
|
|
- All contexts show assignment, Azure link, linked Kandev tasks, and the
|
|
workspace quick-action Task menu. Only board-origin detail shows
|
|
column/split controls. Successful quick-action creation persists the
|
|
association before invalidating detail/task-link queries.
|
|
|
|
### Automation settings and E2E
|
|
|
|
- Add pull-request and work-item watcher sections before Quick actions and
|
|
Default queries, matching GitHub's analogous settings order. Desktop watcher
|
|
lists use tables; phone uses stacked cards.
|
|
Create/edit is a desktop dialog and full-height phone drawer backed by the
|
|
same form normalization and domain hook.
|
|
- Extend the Azure mock client/controller and E2E API helper with deterministic
|
|
detail pages, comment continuation/failure, current identity, watcher
|
|
matches, reservations, reset previews, and terminal-state transitions.
|
|
- Add focused desktop scenarios to the existing Azure integration spec and
|
|
mobile scenarios to `mobile-azure-devops.spec.ts`. Implementation follows
|
|
RED-GREEN: write the failing Playwright assertion before completing each
|
|
corresponding UI behavior.
|
|
|
|
## Tests
|
|
|
|
- Go table tests for URL validation, PAT headers, API errors, WIQL batching,
|
|
PR conversion, workspace isolation, persistence, and route status codes.
|
|
- Go service tests for repository/task association validation and restart
|
|
persistence.
|
|
- TypeScript unit tests for API request/response normalization and pure filter
|
|
or status helpers.
|
|
- Playwright desktop and `mobile-chrome` flows using the Azure mock controller:
|
|
connect, browse work items, browse PRs, and open PR feedback.
|
|
- Go tests for provider-neutral task inputs, canonical remote URLs, and
|
|
credential cleanup around Azure clone processes.
|
|
- Component and Playwright coverage for immediate availability, Enabled chips,
|
|
provider grouping, preset/saved-view behavior, and mobile parity.
|
|
- Go REST/service/controller tests for team and board discovery, backlog-order
|
|
hydration, dynamic column/done fields, allowlisted JSON Patch generation,
|
|
revision conflicts, and mock mutation.
|
|
- TypeScript API/hook/view-model tests for dependent selector resets, board
|
|
grouping, optimistic move rollback, conflict refresh, and normalized card
|
|
updates.
|
|
- Playwright desktop and `mobile-chrome` flows for initial board load,
|
|
assignment and column changes, reload persistence, mobile focused-column
|
|
navigation, detail containment, and absence of document horizontal overflow.
|
|
- Go tests for immediate save-time auth health, work-item detail/comment
|
|
pagination, PAT identity resolution, planning-field normalization, constrained
|
|
patch operations, task/work-item associations, preset normalization, watcher
|
|
generation/reservation safety, cleanup, and poll errors.
|
|
- User settings and frontend hook tests for per-user/per-workspace preference
|
|
hydration, queued optimistic writes, stale provider fallback, and no browser
|
|
storage dependency.
|
|
- Component tests for read-only detail, section retries, Assign to me/Unassign,
|
|
quick-action payloads, linked-task indicators, and responsive watcher
|
|
controls.
|
|
- Playwright desktop and `mobile-chrome` flows for restored filters, detail and
|
|
discussion, assignment/column changes, quick task creation, immediate
|
|
availability, and watcher create/run/reset.
|
|
- Watcher persistence tests must cover cross-workspace ID access, restart
|
|
persistence, generation-aware reserve/attach/release, deleting state,
|
|
reset preview cleanup sets, and workspace deletion.
|
|
- Poller/dispatch tests must cover bounded initial checks, duplicate matches
|
|
after restart, provider failures, terminal cleanup policies, exact metadata
|
|
keys, in-flight throttling, ownership loss, and dependency self-heal.
|
|
- Detail tests must cover independent core/comment errors, continuation-token
|
|
append/dedup, unsafe HTML removal, search-result assignment, conflict
|
|
rollback, board-only column controls, linked-task cache invalidation, and
|
|
association failure.
|
|
|
|
## Verification
|
|
|
|
- `make fmt` — passed on 2026-07-31.
|
|
- `make typecheck` — passed on 2026-07-31.
|
|
- `make test` — passed on 2026-07-31.
|
|
- `make lint` — passed on 2026-07-31 (Go, web, and harness checks).
|
|
- Task files define the exact focused Go and Vitest commands for each new
|
|
behavior; run those during TDD before the corresponding task is marked done.
|
|
- `pnpm e2e:run --host --no-build tests/integrations/azure-devops.spec.ts -- --project=chromium` from `apps/web` — passed.
|
|
- `pnpm e2e:run --host --no-build tests/integrations/mobile-azure-devops.spec.ts -- --project=mobile-chrome` from `apps/web` — passed.
|
|
- Managed desktop and mobile `pnpm e2e:run` equivalents — passed after the
|
|
production build.
|
|
- `node --test scripts/validate-public-docs.test.mjs` and
|
|
`node scripts/validate-public-docs.mjs` — passed on 2026-07-31.
|
|
|
|
## Risks
|
|
|
|
- Azure organization URLs are an outbound-request boundary. V1 accepts only
|
|
canonical HTTPS `dev.azure.com/<organization>` URLs to avoid an SSRF-capable
|
|
arbitrary host setting.
|
|
- WIQL returns references rather than hydrated work items and Azure caps batch
|
|
retrieval at 200 IDs; ordering and partial omissions require explicit tests.
|
|
- Azure reviewer votes and branch policies do not map one-to-one to GitHub
|
|
reviews and checks. Only summary states are shared.
|
|
- Existing task PR UI is GitHub-heavy. The implementation must extract only the
|
|
smallest provider-tagged presentation contract required for Azure, not begin
|
|
a broad GitHub/GitLab refactor.
|
|
- The task creation API and branch loader are GitHub-named today. Compatibility
|
|
fields must remain accepted while internal contracts become provider-neutral.
|
|
- Azure PAT clone auth must not leak through process arguments, persisted remote
|
|
URLs, executor metadata, structured logs, or agent-visible environment state.
|
|
- Remote executors receive credential-free clone URLs. A private Azure repo is
|
|
guaranteed to clone through the backend materialization path; remote executor
|
|
push/clone credentials remain separately configured.
|
|
- Existing workspace PATs may have only Work Items Read. Board reads continue
|
|
to work, while mutations can return 403 until the user replaces the PAT with
|
|
Work Items Read & write; the UI must preserve readable board data and show
|
|
reconnect guidance.
|
|
- Board column and done field reference names are provider data and can differ
|
|
by team/process. The browser sends column IDs only; the backend must derive
|
|
and validate all provider-native patch paths and values.
|
|
- Azure board snapshots can exceed one work-item batch and can contain deleted
|
|
references. Preserve backlog order, omit missing items, and keep the
|
|
remaining board usable.
|
|
- Azure work-item types expose different estimate fields. Normalize only the
|
|
documented planning-field allowlist and omit unavailable values instead of
|
|
guessing a universal Effort field.
|
|
- Discussion uses a preview-version Azure endpoint and opaque continuation
|
|
tokens. Keep paging isolated behind the Azure client contract.
|
|
- The PAT represents one Azure identity for the whole workspace; Assign to me
|
|
means that identity, which may differ from the signed-in Kandev user's email.
|
|
- Portable preference writes can race rapid filter changes. Use one queued
|
|
patch stream and ignore stale loads so an older response cannot overwrite the
|
|
most recent in-memory selection.
|
|
- Watchers can fan out task creation. Reuse generation reservations, profile
|
|
dependency checks, 60-second minimum polling, and `max_inflight_tasks`
|
|
throttling before enabling them.
|
|
|
|
## Task Waves
|
|
|
|
Wave 1: backend foundation
|
|
|
|
- [x] [Task 01: Workspace configuration](task-01-workspace-configuration.md)
|
|
- [x] [Task 02: REST client](task-02-rest-client.md)
|
|
|
|
Wave 2: backend product reads
|
|
|
|
- [x] [Task 03: Work-item and PR services](task-03-read-services.md)
|
|
- [x] [Task 04: Task PR persistence and backend wiring](task-04-task-pr-wiring.md)
|
|
|
|
Wave 3: frontend
|
|
|
|
- [x] [Task 05: Frontend data and settings](task-05-frontend-settings.md)
|
|
- [x] [Task 06: Responsive browse and task PR UI](task-06-frontend-browse.md)
|
|
|
|
Wave 4: integrated validation
|
|
|
|
- [x] [Task 07: E2E, security review, and documentation](task-07-e2e-security-docs.md)
|
|
|
|
Wave 5: integration navigation and Azure browse UX
|
|
|
|
- [x] [Task 08: Immediate availability and integration identity](task-08-availability-and-identity.md)
|
|
- [x] [Task 09: Azure presets and saved views](task-09-azure-presets.md)
|
|
|
|
Wave 6: provider-neutral repository selection
|
|
|
|
- [x] [Task 10: Remote repository contracts and discovery](task-10-remote-repository-contracts.md)
|
|
- [x] [Task 11: Secure Azure repository materialization](task-11-secure-azure-clone.md)
|
|
- [x] [Task 12: Unified task repository picker](task-12-unified-repository-picker.md)
|
|
|
|
Wave 7: integrated validation
|
|
|
|
- [x] [Task 13: Cross-provider E2E, security review, and documentation](task-13-enhancement-validation.md)
|
|
|
|
Wave 8: editable Azure board backend
|
|
|
|
- [x] [Task 14: Board discovery and snapshots](task-14-board-discovery.md)
|
|
- [x] [Task 15: Revision-safe work-item mutations](task-15-board-mutations.md)
|
|
|
|
Wave 9: responsive board UI
|
|
|
|
- [x] [Task 16: Editable desktop and mobile board](task-16-board-ui.md)
|
|
|
|
Wave 10: board validation and documentation
|
|
|
|
- [x] [Task 17: Board E2E, docs, and verification](task-17-board-validation.md)
|
|
|
|
Wave 11: connection, preference, and work-item contracts
|
|
|
|
- [x] [Task 18: Immediate connection activation](task-18-immediate-activation.md)
|
|
- [x] [Task 19: Portable Azure browse preferences](task-19-browse-preferences.md)
|
|
- [x] [Task 20: Work-item detail contracts](task-20-work-item-detail.md)
|
|
- [x] [Task 21: Constrained work-item mutations](task-21-work-item-mutations.md)
|
|
- [x] [Task 22: Task work-item associations](task-22-task-work-item-links.md)
|
|
- [x] [Task 23: Azure provider presets](task-23-provider-presets.md)
|
|
|
|
Wave 12: watcher backend
|
|
|
|
- [x] [Task 24: Azure watcher persistence](task-24-watcher-persistence.md)
|
|
- [x] [Task 25: Azure watcher polling](task-25-watcher-polling.md)
|
|
- [x] [Task 26: Azure watcher dispatch](task-26-watcher-dispatch.md)
|
|
|
|
Wave 13: responsive frontend
|
|
|
|
- [x] [Task 27: Responsive work-item detail](task-27-work-item-detail-ui.md)
|
|
- [x] [Task 28: Azure watcher settings](task-28-automation-settings.md)
|
|
|
|
Wave 14: integrated validation
|
|
|
|
- [x] [Task 29: Azure enhancement validation](task-29-enhancement-validation.md)
|
|
|
|
Tasks within a wave are listed separately for ownership clarity but should run
|
|
sequentially in the current workspace when they touch the same package or state
|
|
composition files.
|