21 KiB
spec, created, updated, status
| spec | created | updated | status |
|---|---|---|---|
| docs/specs/azure-devops-integration/spec.md | 2026-07-17 | 2026-07-31 | building |
Azure DevOps Integration Plan
Scope
Implement the Azure DevOps Services integration defined in
../../specs/azure-devops-integration/spec.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/azuredevopspackage. Do not add Azure methods togithub.Clientor 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_urltask inputs alongside the legacygithub_urlcompatibility 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
/revtest 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, andmain.gowhere pollers are started. - Repository provider parsing/discovery where provider enums are currently restricted to GitHub/GitLab.
- Runtime defaults:
profiles.yamlfor 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.tsandapps/web/lib/types/azure-devops.ts. - Domain hooks under
apps/web/hooks/domains/azure-devops/. - Settings route and integration menu entry.
/azure-devopsbrowse page with a compact work-item/PR segmented view, desktop filter rail, and mobile filter sheet./azure-devopsBoard 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-heightDrawerwith 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, andcontroller_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. Formax_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/previewand/: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_errorandlast_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, andnevercleanup without touching manual tasks or another generation's task. - Add work-item and PR Azure
WatcherSourceimplementations. TheirWatchMetadataKeyvalues are respectivelyazure_devops_work_item_watch_idandazure_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/:idfor 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-sanitizeapproach 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
Dialogover retained context. Phone uses a100dvhfull-heightDrawerwith fixed header/action regions, onemin-h-0 overflow-y-autobody, 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-chromeflows 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-chromeflows 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-chromeflows 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=chromiumfromapps/web— passed.pnpm e2e:run --host --no-build tests/integrations/mobile-azure-devops.spec.ts -- --project=mobile-chromefromapps/web— passed.- Managed desktop and mobile
pnpm e2e:runequivalents — passed after the production build. node --test scripts/validate-public-docs.test.mjsandnode 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_tasksthrottling before enabling them.
Task Waves
Wave 1: backend foundation
Wave 2: backend product reads
Wave 3: frontend
Wave 4: integrated validation
Wave 5: integration navigation and Azure browse UX
Wave 6: provider-neutral repository selection
- Task 10: Remote repository contracts and discovery
- Task 11: Secure Azure repository materialization
- Task 12: Unified task repository picker
Wave 7: integrated validation
Wave 8: editable Azure board backend
Wave 9: responsive board UI
Wave 10: board validation and documentation
Wave 11: connection, preference, and work-item contracts
- Task 18: Immediate connection activation
- Task 19: Portable Azure browse preferences
- Task 20: Work-item detail contracts
- Task 21: Constrained work-item mutations
- Task 22: Task work-item associations
- Task 23: Azure provider presets
Wave 12: watcher backend
Wave 13: responsive frontend
Wave 14: integrated validation
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.