diff --git a/docs/wiki/AI-Providers.md b/docs/wiki/AI-Providers.md
index 462f57be..91880a0f 100644
--- a/docs/wiki/AI-Providers.md
+++ b/docs/wiki/AI-Providers.md
@@ -1,6 +1,6 @@
# AI Providers
-TFTSR supports 5 AI providers, selectable per-session. API keys are stored in the Stronghold encrypted vault.
+TFTSR supports 6+ AI providers, including custom providers with flexible authentication and API formats. API keys are stored encrypted with AES-256-GCM.
## Provider Factory
@@ -113,6 +113,130 @@ The domain prompt is injected as the first `system` role message in every new co
---
+## 6. Custom Provider (MSI GenAI & Others)
+
+**Status:** ✅ **Implemented** (v0.2.6)
+
+Custom providers allow integration with non-OpenAI-compatible APIs. The application supports two API formats:
+
+### Format: OpenAI Compatible (Default)
+
+Standard OpenAI `/chat/completions` endpoint with Bearer authentication.
+
+| Field | Default Value |
+|-------|--------------|
+| `api_format` | `"openai"` |
+| `custom_endpoint_path` | `/chat/completions` |
+| `custom_auth_header` | `Authorization` |
+| `custom_auth_prefix` | `Bearer ` |
+
+**Use cases:**
+- Self-hosted LLMs with OpenAI-compatible APIs
+- Custom proxy services
+- Enterprise gateways
+
+---
+
+### Format: MSI GenAI
+
+**Motorola Solutions Internal GenAI Service** — Enterprise AI platform with centralized cost tracking and model access.
+
+| Field | Value |
+|-------|-------|
+| `config.provider_type` | `"custom"` |
+| `config.api_format` | `"msi_genai"` |
+| API URL | `https://genai-service.commandcentral.com/app-gateway` (prod)
`https://genai-service.stage.commandcentral.com/app-gateway` (stage) |
+| Auth Header | `x-msi-genai-api-key` |
+| Auth Prefix | `` (empty - no Bearer prefix) |
+| Endpoint Path | `` (empty - URL includes full path `/api/v2/chat`) |
+
+**Available Models:**
+- `VertexGemini` — Gemini 2.0 Flash (Private/GCP)
+- `Claude-Sonnet-4` — Claude Sonnet 4 (Public/Anthropic)
+- `ChatGPT4o` — GPT-4o (Public/OpenAI)
+- `ChatGPT-5_2-Chat` — GPT-4.5 (Public/OpenAI)
+- See [GenAI API User Guide](../GenAI%20API%20User%20Guide.md) for full model list
+
+**Request Format:**
+```json
+{
+ "model": "VertexGemini",
+ "prompt": "User's latest message",
+ "system": "Optional system prompt",
+ "sessionId": "uuid-for-conversation-continuity",
+ "userId": "user.name@motorolasolutions.com"
+}
+```
+
+**Response Format:**
+```json
+{
+ "status": true,
+ "sessionId": "uuid",
+ "msg": "AI response text",
+ "initialPrompt": false
+}
+```
+
+**Key Differences from OpenAI:**
+- **Single prompt** instead of message array (server manages history via `sessionId`)
+- **Response in `msg` field** instead of `choices[0].message.content`
+- **Session-based** conversation continuity (no need to resend history)
+- **Cost tracking** via `userId` field (optional — defaults to API key owner if omitted)
+- **Custom client header**: `X-msi-genai-client: tftsr-devops-investigation`
+
+**Configuration (Settings → AI Providers → Add Provider):**
+```
+Name: MSI GenAI
+Type: Custom
+API Format: MSI GenAI
+API URL: https://genai-service.stage.commandcentral.com/app-gateway
+Model: VertexGemini
+API Key: (your MSI GenAI API key from portal)
+User ID: your.name@motorolasolutions.com (optional)
+Endpoint Path: (leave empty)
+Auth Header: x-msi-genai-api-key
+Auth Prefix: (leave empty)
+```
+
+**Rate Limits:**
+- $50/user/month (enforced server-side)
+- Per-API-key quotas available
+
+**Troubleshooting:**
+
+| Error | Cause | Solution |
+|-------|-------|----------|
+| 403 Forbidden | Invalid API key or insufficient permissions | Verify key in MSI GenAI portal, check model access |
+| Missing `userId` field | Configuration not saved | Ensure UI shows User ID field when `api_format=msi_genai` |
+| No conversation history | `sessionId` not persisted | Session ID stored in `ProviderConfig.session_id` — currently per-provider, not per-conversation |
+
+**Implementation Details:**
+- Backend: `src-tauri/src/ai/openai.rs::chat_msi_genai()`
+- Schema: `src-tauri/src/state.rs::ProviderConfig` (added `user_id`, `api_format`, custom auth fields)
+- Frontend: `src/pages/Settings/AIProviders.tsx` (conditional UI for MSI GenAI)
+- CSP whitelist: `https://genai-service.stage.commandcentral.com` and production domain
+
+---
+
+## Custom Provider Configuration Fields
+
+All providers support the following optional configuration fields (v0.2.6+):
+
+| Field | Type | Purpose | Default |
+|-------|------|---------|---------|
+| `custom_endpoint_path` | `Option` | Override endpoint path | `/chat/completions` |
+| `custom_auth_header` | `Option` | Custom auth header name | `Authorization` |
+| `custom_auth_prefix` | `Option` | Prefix before API key | `Bearer ` |
+| `api_format` | `Option` | API format (`openai` or `msi_genai`) | `openai` |
+| `session_id` | `Option` | Session ID for stateful APIs | None |
+| `user_id` | `Option` | User ID for cost tracking (MSI GenAI) | None |
+
+**Backward Compatibility:**
+All fields are optional and default to OpenAI-compatible behavior. Existing provider configurations are unaffected.
+
+---
+
## Adding a New Provider
1. Create `src-tauri/src/ai/{name}.rs` implementing the `Provider` trait
diff --git a/docs/wiki/Database.md b/docs/wiki/Database.md
index 67c9001f..68b51041 100644
--- a/docs/wiki/Database.md
+++ b/docs/wiki/Database.md
@@ -2,7 +2,7 @@
## Overview
-TFTSR uses **SQLite** via `rusqlite` with the `bundled-sqlcipher` feature for AES-256 encryption in production. 10 versioned migrations are tracked in the `_migrations` table.
+TFTSR uses **SQLite** via `rusqlite` with the `bundled-sqlcipher` feature for AES-256 encryption in production. 11 versioned migrations are tracked in the `_migrations` table.
**DB file location:** `{app_data_dir}/tftsr.db`
@@ -38,7 +38,7 @@ pub fn init_db(data_dir: &Path) -> anyhow::Result {
---
-## Schema (10 Migrations)
+## Schema (11 Migrations)
### 001 — issues
@@ -181,6 +181,47 @@ CREATE VIRTUAL TABLE issues_fts USING fts5(
);
```
+### 011 — credentials & integration_config (v0.2.3+)
+
+**Integration credentials table:**
+```sql
+CREATE TABLE credentials (
+ id TEXT PRIMARY KEY,
+ service TEXT NOT NULL CHECK(service IN ('confluence','servicenow','azuredevops')),
+ token_hash TEXT NOT NULL, -- SHA-256 hash for audit
+ encrypted_token TEXT NOT NULL, -- AES-256-GCM encrypted
+ created_at TEXT NOT NULL,
+ expires_at TEXT,
+ UNIQUE(service)
+);
+```
+
+**Integration configuration table:**
+```sql
+CREATE TABLE integration_config (
+ id TEXT PRIMARY KEY,
+ service TEXT NOT NULL CHECK(service IN ('confluence','servicenow','azuredevops')),
+ base_url TEXT NOT NULL,
+ username TEXT, -- ServiceNow only
+ project_name TEXT, -- Azure DevOps only
+ space_key TEXT, -- Confluence only
+ auto_create_enabled INTEGER NOT NULL DEFAULT 0,
+ updated_at TEXT NOT NULL,
+ UNIQUE(service)
+);
+```
+
+**Encryption:**
+- OAuth2 tokens encrypted with AES-256-GCM
+- Key derived from `TFTSR_DB_KEY` environment variable
+- Random 96-bit nonce per encryption
+- Format: `base64(nonce || ciphertext || tag)`
+
+**Usage:**
+- OAuth2 flows (Confluence, Azure DevOps): Store encrypted bearer token
+- Basic auth (ServiceNow): Store encrypted password
+- One credential per service (enforced by UNIQUE constraint)
+
---
## Key Design Notes
diff --git a/docs/wiki/Home.md b/docs/wiki/Home.md
index d5af4c36..ebbe7feb 100644
--- a/docs/wiki/Home.md
+++ b/docs/wiki/Home.md
@@ -24,8 +24,10 @@
- **5-Whys AI Triage** — Interactive guided root cause analysis via multi-turn AI chat
- **PII Auto-Redaction** — Detects and redacts sensitive data before any AI send
-- **Multi-Provider AI** — OpenAI, Anthropic Claude, Google Gemini, Mistral, AWS Bedrock (via LiteLLM), local Ollama (fully offline)
-- **SQLCipher AES-256** — All issue history encrypted at rest
+- **Multi-Provider AI** — OpenAI, Anthropic Claude, Google Gemini, Mistral, AWS Bedrock (via LiteLLM), MSI GenAI (Motorola internal), local Ollama (fully offline)
+- **Custom Provider Support** — Flexible authentication (Bearer, custom headers) and API formats (OpenAI-compatible, MSI GenAI)
+- **External Integrations** — Confluence, ServiceNow, Azure DevOps with OAuth2 PKCE flows
+- **SQLCipher AES-256** — All issue history and credentials encrypted at rest
- **RCA + Post-Mortem Generation** — Auto-populated Markdown templates, exportable as MD/PDF
- **Ollama Management** — Hardware detection, model recommendations, in-app model management
- **Audit Trail** — Every external data send logged with SHA-256 hash
@@ -33,9 +35,13 @@
## Releases
-| Version | Status | Platforms |
+| Version | Status | Highlights |
|---------|--------|-----------|
-| v0.1.1 | 🚀 Released | linux/amd64 · linux/arm64 · windows/amd64 (.deb, .rpm, .AppImage, .exe, .msi) |
+| v0.2.6 | 🚀 Latest | MSI GenAI support, OAuth2 shell permissions, user ID tracking |
+| v0.2.3 | Released | Confluence/ServiceNow/ADO REST API clients (19 TDD tests) |
+| v0.1.1 | Released | Core application with PII detection, RCA generation |
+
+**Platforms:** linux/amd64 · linux/arm64 · windows/amd64 (.deb, .rpm, .AppImage, .exe, .msi)
Download from [Releases](https://gogs.tftsr.com/sarman/tftsr-devops_investigation/releases). All builds are produced natively (no QEMU emulation).
@@ -45,7 +51,7 @@ Download from [Releases](https://gogs.tftsr.com/sarman/tftsr-devops_investigatio
|-------|--------|
| Phases 1–8 (Core application) | ✅ Complete |
| Phase 9 (History/Search) | 🔲 Pending |
-| Phase 10 (Integrations) | 🕐 v0.2 stubs only |
+| Phase 10 (Integrations) | ✅ Complete — Confluence, ServiceNow, Azure DevOps fully implemented with OAuth2 |
| Phase 11 (CI/CD) | ✅ Complete — Gitea Actions fully operational |
| Phase 12 (Release packaging) | ✅ linux/amd64 · linux/arm64 (native) · windows/amd64 |
diff --git a/docs/wiki/IPC-Commands.md b/docs/wiki/IPC-Commands.md
index 3d25f9b1..d0b66498 100644
--- a/docs/wiki/IPC-Commands.md
+++ b/docs/wiki/IPC-Commands.md
@@ -220,15 +220,206 @@ Returns audit log entries. Filter by action, entity_type, date range.
---
-## Integration Commands (v0.2 Stubs)
+## Integration Commands
-All 6 integration commands currently return `"not yet available"` errors.
+> **Status:** ✅ **Fully Implemented** (v0.2.3+)
-| Command | Purpose |
-|---------|---------|
-| `test_confluence_connection` | Verify Confluence credentials |
-| `publish_to_confluence` | Publish RCA/postmortem to Confluence space |
-| `test_servicenow_connection` | Verify ServiceNow credentials |
-| `create_servicenow_incident` | Create incident from issue |
-| `test_azuredevops_connection` | Verify Azure DevOps credentials |
-| `create_azuredevops_workitem` | Create work item from issue |
+All integration commands are production-ready with complete OAuth2/authentication flows.
+
+### OAuth2 Commands
+
+### `initiate_oauth`
+```typescript
+initiateOauthCmd(service: "confluence" | "servicenow" | "azuredevops") → OAuthInitResponse
+```
+Starts OAuth2 PKCE flow. Returns authorization URL and state key. Opens browser window for user authentication.
+
+```typescript
+interface OAuthInitResponse {
+ auth_url: string; // URL to open in browser
+ state: string; // State key for callback verification
+}
+```
+
+**Flow:**
+1. Generates PKCE challenge
+2. Starts local callback server on `http://localhost:8765`
+3. Opens authorization URL in browser
+4. User authenticates with service
+5. Service redirects to callback server
+6. Callback server triggers `handle_oauth_callback`
+
+### `handle_oauth_callback`
+```typescript
+handleOauthCallbackCmd(service: string, code: string, stateKey: string) → void
+```
+Exchanges authorization code for access token. Encrypts token with AES-256-GCM and stores in database.
+
+### Confluence Commands
+
+### `test_confluence_connection`
+```typescript
+testConfluenceConnectionCmd(baseUrl: string, credentials: Record) → ConnectionResult
+```
+Verifies Confluence connection by calling `/rest/api/user/current`.
+
+### `list_confluence_spaces`
+```typescript
+listConfluenceSpacesCmd(config: ConfluenceConfig) → Space[]
+```
+Lists all accessible Confluence spaces.
+
+### `search_confluence_pages`
+```typescript
+searchConfluencePagesCmd(config: ConfluenceConfig, query: string, spaceKey?: string) → Page[]
+```
+Searches pages using CQL (Confluence Query Language). Optional space filter.
+
+### `publish_to_confluence`
+```typescript
+publishToConfluenceCmd(config: ConfluenceConfig, spaceKey: string, title: string, contentHtml: string, parentPageId?: string) → PublishResult
+```
+Creates a new page in Confluence. Returns page ID and URL.
+
+### `update_confluence_page`
+```typescript
+updateConfluencePageCmd(config: ConfluenceConfig, pageId: string, title: string, contentHtml: string, version: number) → PublishResult
+```
+Updates an existing page. Requires current version number.
+
+### ServiceNow Commands
+
+### `test_servicenow_connection`
+```typescript
+testServiceNowConnectionCmd(instanceUrl: string, credentials: Record) → ConnectionResult
+```
+Verifies ServiceNow connection by querying incident table.
+
+### `search_servicenow_incidents`
+```typescript
+searchServiceNowIncidentsCmd(config: ServiceNowConfig, query: string) → Incident[]
+```
+Searches incidents by short description. Returns up to 10 results.
+
+### `create_servicenow_incident`
+```typescript
+createServiceNowIncidentCmd(config: ServiceNowConfig, shortDesc: string, description: string, urgency: string, impact: string) → TicketResult
+```
+Creates a new incident. Returns incident number and URL.
+
+```typescript
+interface TicketResult {
+ id: string; // sys_id (UUID)
+ ticket_number: string; // INC0010001
+ url: string; // Direct link to incident
+}
+```
+
+### `get_servicenow_incident`
+```typescript
+getServiceNowIncidentCmd(config: ServiceNowConfig, incidentId: string) → Incident
+```
+Retrieves incident by sys_id or incident number (e.g., `INC0010001`).
+
+### `update_servicenow_incident`
+```typescript
+updateServiceNowIncidentCmd(config: ServiceNowConfig, sysId: string, updates: Record) → TicketResult
+```
+Updates incident fields. Uses JSON-PATCH format.
+
+### Azure DevOps Commands
+
+### `test_azuredevops_connection`
+```typescript
+testAzureDevOpsConnectionCmd(orgUrl: string, credentials: Record) → ConnectionResult
+```
+Verifies Azure DevOps connection by querying project info.
+
+### `search_azuredevops_workitems`
+```typescript
+searchAzureDevOpsWorkItemsCmd(config: AzureDevOpsConfig, query: string) → WorkItem[]
+```
+Searches work items using WIQL (Work Item Query Language).
+
+### `create_azuredevops_workitem`
+```typescript
+createAzureDevOpsWorkItemCmd(config: AzureDevOpsConfig, title: string, description: string, workItemType: string, severity: string) → TicketResult
+```
+Creates a work item (Bug, Task, User Story). Returns work item ID and URL.
+
+**Work Item Types:**
+- `Bug` — Software defect
+- `Task` — Work assignment
+- `User Story` — Feature request
+- `Issue` — Problem or blocker
+- `Incident` — Production incident
+
+### `get_azuredevops_workitem`
+```typescript
+getAzureDevOpsWorkItemCmd(config: AzureDevOpsConfig, workItemId: number) → WorkItem
+```
+Retrieves work item by ID.
+
+### `update_azuredevops_workitem`
+```typescript
+updateAzureDevOpsWorkItemCmd(config: AzureDevOpsConfig, workItemId: number, updates: Record) → TicketResult
+```
+Updates work item fields. Uses JSON-PATCH format.
+
+---
+
+## Common Types
+
+### `ConnectionResult`
+```typescript
+interface ConnectionResult {
+ success: boolean;
+ message: string;
+}
+```
+
+### `PublishResult`
+```typescript
+interface PublishResult {
+ id: string; // Page ID or document ID
+ url: string; // Direct link to published content
+}
+```
+
+### `TicketResult`
+```typescript
+interface TicketResult {
+ id: string; // sys_id or work item ID
+ ticket_number: string; // Human-readable number
+ url: string; // Direct link
+}
+```
+
+---
+
+## Authentication Storage
+
+All integration credentials are stored in the `credentials` table:
+
+```sql
+CREATE TABLE credentials (
+ id TEXT PRIMARY KEY,
+ service TEXT NOT NULL CHECK(service IN ('confluence','servicenow','azuredevops')),
+ token_hash TEXT NOT NULL, -- SHA-256 for audit
+ encrypted_token TEXT NOT NULL, -- AES-256-GCM encrypted
+ created_at TEXT NOT NULL,
+ expires_at TEXT
+);
+```
+
+**Encryption:**
+- Algorithm: AES-256-GCM
+- Key derivation: From `TFTSR_DB_KEY` environment variable
+- Nonce: Random 96-bit per encryption
+- Format: `base64(nonce || ciphertext || tag)`
+
+**Token retrieval:**
+```rust
+// Backend: src-tauri/src/integrations/auth.rs
+pub fn decrypt_token(encrypted: &str) -> Result
+```
diff --git a/docs/wiki/Integrations.md b/docs/wiki/Integrations.md
index a74b2926..c7480c02 100644
--- a/docs/wiki/Integrations.md
+++ b/docs/wiki/Integrations.md
@@ -1,97 +1,273 @@
# Integrations
-> **Status: All integrations are v0.2 stubs.** They are implemented as placeholder commands that return `"not yet available"` errors. The authentication framework and command signatures are finalized, but the actual API calls are not yet implemented.
+> **Status: ✅ Fully Implemented (v0.2.6)** — All three integrations (Confluence, ServiceNow, Azure DevOps) are production-ready with complete OAuth2/authentication flows and REST API clients.
---
## Confluence
-**Purpose:** Publish RCA and post-mortem documents to a Confluence space.
+**Purpose:** Publish RCA and post-mortem documents to Confluence spaces.
-**Commands:**
-- `test_confluence_connection(base_url, credentials)` — Verify credentials
-- `publish_to_confluence(doc_id, space_key, parent_page_id?)` — Create/update page
+**Status:** ✅ **Implemented** (v0.2.3)
-**Planned implementation:**
-- Confluence REST API v2: `POST /wiki/rest/api/content`
-- Auth: Basic auth (email + API token) or OAuth2
-- Page format: Convert Markdown → Confluence storage format (XHTML-like)
+### Features
+- OAuth2 authentication with PKCE flow
+- List accessible spaces
+- Search pages by CQL query
+- Create new pages with optional parent
+- Update existing pages with version management
-**Configuration (Settings → Integrations → Confluence):**
+### API Client (`src-tauri/src/integrations/confluence.rs`)
+
+**Functions:**
+```rust
+test_connection(config: &ConfluenceConfig) -> Result
+list_spaces(config: &ConfluenceConfig) -> Result, String>
+search_pages(config: &ConfluenceConfig, query: &str, space_key: Option<&str>) -> Result, String>
+publish_page(config: &ConfluenceConfig, space_key: &str, title: &str, content_html: &str, parent_page_id: Option<&str>) -> Result
+update_page(config: &ConfluenceConfig, page_id: &str, title: &str, content_html: &str, version: i32) -> Result
```
-Base URL: https://yourorg.atlassian.net
-Email: user@example.com
-API Token: (stored in Stronghold)
-Space Key: PROJ
+
+### Configuration (Settings → Integrations → Confluence)
```
+Base URL: https://yourorg.atlassian.net
+Authentication: OAuth2 (bearer token, encrypted at rest)
+Default Space: PROJ
+```
+
+### Implementation Details
+- **API**: Confluence REST API v1 (`/rest/api/`)
+- **Auth**: OAuth2 bearer token (encrypted with AES-256-GCM)
+- **Endpoints**:
+ - `GET /rest/api/user/current` — Test connection
+ - `GET /rest/api/space` — List spaces
+ - `GET /rest/api/content/search` — Search with CQL
+ - `POST /rest/api/content` — Create page
+ - `PUT /rest/api/content/{id}` — Update page
+- **Page format**: Confluence Storage Format (XHTML)
+- **TDD Tests**: 6 tests with mockito HTTP mocking
---
## ServiceNow
-**Purpose:** Create incident records in ServiceNow from TFTSR issues.
+**Purpose:** Create and manage incident records in ServiceNow.
-**Commands:**
-- `test_servicenow_connection(instance_url, credentials)` — Verify credentials
-- `create_servicenow_incident(issue_id, config)` — Create incident
+**Status:** ✅ **Implemented** (v0.2.3)
-**Planned implementation:**
-- ServiceNow Table API: `POST /api/now/table/incident`
-- Auth: Basic auth or OAuth2 bearer token
-- Field mapping: TFTSR severity → ServiceNow priority (P1=Critical, P2=High, etc.)
+### Features
+- Basic authentication (username/password)
+- Search incidents by description
+- Create new incidents with urgency/impact
+- Get incident by sys_id or number
+- Update existing incidents
-**Configuration:**
+### API Client (`src-tauri/src/integrations/servicenow.rs`)
+
+**Functions:**
+```rust
+test_connection(config: &ServiceNowConfig) -> Result
+search_incidents(config: &ServiceNowConfig, query: &str) -> Result, String>
+create_incident(config: &ServiceNowConfig, short_description: &str, description: &str, urgency: &str, impact: &str) -> Result
+get_incident(config: &ServiceNowConfig, incident_id: &str) -> Result
+update_incident(config: &ServiceNowConfig, sys_id: &str, updates: serde_json::Value) -> Result
```
-Instance URL: https://yourorg.service-now.com
-Username: admin
-Password: (stored in Stronghold)
+
+### Configuration (Settings → Integrations → ServiceNow)
```
+Instance URL: https://yourorg.service-now.com
+Username: admin
+Password: (encrypted with AES-256-GCM)
+```
+
+### Implementation Details
+- **API**: ServiceNow Table API (`/api/now/table/incident`)
+- **Auth**: HTTP Basic authentication
+- **Severity mapping**: TFTSR P1-P4 → ServiceNow urgency/impact (1-3)
+- **Incident lookup**: Supports both sys_id (UUID) and incident number (INC0010001)
+- **TDD Tests**: 7 tests with mockito HTTP mocking
---
## Azure DevOps
-**Purpose:** Create work items (bugs/incidents) in Azure DevOps from TFTSR issues.
+**Purpose:** Create and manage work items (bugs/tasks) in Azure DevOps.
-**Commands:**
-- `test_azuredevops_connection(org_url, credentials)` — Verify credentials
-- `create_azuredevops_workitem(issue_id, project, config)` — Create work item
+**Status:** ✅ **Implemented** (v0.2.3)
-**Planned implementation:**
-- Azure DevOps REST API: `POST /{organization}/{project}/_apis/wit/workitems/${type}`
-- Auth: Personal Access Token (PAT) via Basic auth header
-- Work item type: Bug or Incident
+### Features
+- OAuth2 authentication with PKCE flow
+- Search work items via WIQL queries
+- Create work items (Bug, Task, User Story)
+- Get work item details by ID
+- Update work items with JSON-PATCH operations
-**Configuration:**
+### API Client (`src-tauri/src/integrations/azuredevops.rs`)
+
+**Functions:**
+```rust
+test_connection(config: &AzureDevOpsConfig) -> Result
+search_work_items(config: &AzureDevOpsConfig, query: &str) -> Result, String>
+create_work_item(config: &AzureDevOpsConfig, title: &str, description: &str, work_item_type: &str, severity: &str) -> Result
+get_work_item(config: &AzureDevOpsConfig, work_item_id: i64) -> Result
+update_work_item(config: &AzureDevOpsConfig, work_item_id: i64, updates: serde_json::Value) -> Result
+```
+
+### Configuration (Settings → Integrations → Azure DevOps)
```
Organization URL: https://dev.azure.com/yourorg
-Personal Access Token: (stored in Stronghold)
+Authentication: OAuth2 (bearer token, encrypted at rest)
Project: MyProject
-Work Item Type: Bug
+```
+
+### Implementation Details
+- **API**: Azure DevOps REST API v7.0
+- **Auth**: OAuth2 bearer token (encrypted with AES-256-GCM)
+- **WIQL**: Work Item Query Language for advanced search
+- **Work item types**: Bug, Task, User Story, Issue, Incident
+- **Severity mapping**: Bug-specific field `Microsoft.VSTS.Common.Severity`
+- **TDD Tests**: 6 tests with mockito HTTP mocking
+
+---
+
+## OAuth2 Authentication Flow
+
+All integrations using OAuth2 (Confluence, Azure DevOps) follow the same flow:
+
+1. **User clicks "Connect"** in Settings → Integrations
+2. **Backend generates PKCE challenge** and stores code verifier
+3. **Local callback server starts** on `http://localhost:8765`
+4. **Browser opens** with OAuth authorization URL
+5. **User authenticates** with service provider
+6. **Service redirects** to `http://localhost:8765/callback?code=...`
+7. **Callback server extracts code** and triggers token exchange
+8. **Backend exchanges code for token** using PKCE verifier
+9. **Token encrypted** with AES-256-GCM and stored in DB
+10. **UI shows "Connected"** status
+
+**Implementation:**
+- `src-tauri/src/integrations/auth.rs` — PKCE generation, token exchange, encryption
+- `src-tauri/src/integrations/callback_server.rs` — Local HTTP server (warp)
+- `src-tauri/src/commands/integrations.rs` — IPC command handlers
+
+**Security:**
+- Tokens encrypted at rest with AES-256-GCM (256-bit key)
+- Key derived from environment variable `TFTSR_DB_KEY`
+- PKCE prevents authorization code interception
+- Callback server only accepts from `localhost`
+
+---
+
+## Database Schema
+
+**Credentials Table (`migration 011`):**
+```sql
+CREATE TABLE credentials (
+ id TEXT PRIMARY KEY,
+ service TEXT NOT NULL CHECK(service IN ('confluence','servicenow','azuredevops')),
+ token_hash TEXT NOT NULL, -- SHA-256 hash for audit
+ encrypted_token TEXT NOT NULL, -- AES-256-GCM encrypted
+ created_at TEXT NOT NULL,
+ expires_at TEXT,
+ UNIQUE(service)
+);
+```
+
+**Integration Config Table:**
+```sql
+CREATE TABLE integration_config (
+ id TEXT PRIMARY KEY,
+ service TEXT NOT NULL CHECK(service IN ('confluence','servicenow','azuredevops')),
+ base_url TEXT NOT NULL,
+ username TEXT, -- ServiceNow only
+ project_name TEXT, -- Azure DevOps only
+ space_key TEXT, -- Confluence only
+ auto_create_enabled INTEGER NOT NULL DEFAULT 0,
+ updated_at TEXT NOT NULL,
+ UNIQUE(service)
+);
```
---
-## v0.2 Roadmap
+## Testing
-Integration implementation order (planned):
+All integrations have comprehensive test coverage:
-1. **Confluence** — Most commonly requested; Markdown-to-Confluence conversion library needed
-2. **Azure DevOps** — Clean REST API, straightforward PAT auth
-3. **ServiceNow** — More complex field mapping; may require customer-specific configuration
+```bash
+# Run all integration tests
+cargo test --manifest-path src-tauri/Cargo.toml --lib integrations
-Each integration will also require:
-- Audit log entry on every publish action
-- PII check on document content before external publish
-- Connection test UI in Settings → Integrations
+# Run specific integration tests
+cargo test --manifest-path src-tauri/Cargo.toml confluence
+cargo test --manifest-path src-tauri/Cargo.toml servicenow
+cargo test --manifest-path src-tauri/Cargo.toml azuredevops
+```
+
+**Test statistics:**
+- **Confluence**: 6 tests (connection, spaces, search, publish, update)
+- **ServiceNow**: 7 tests (connection, search, create, get by sys_id, get by number, update)
+- **Azure DevOps**: 6 tests (connection, WIQL search, create, get, update)
+- **Total**: 19 integration tests (all passing)
+
+**Test approach:**
+- TDD methodology (tests written first)
+- HTTP mocking with `mockito` crate
+- No external API calls in tests
+- All auth flows tested with mock responses
---
-## Adding an Integration
+## CSP Configuration
-1. Implement the logic in `src-tauri/src/integrations/{name}.rs`
-2. Remove the stub `Err("not yet available")` return in `commands/integrations.rs`
-3. Add the new API endpoint to the Tauri CSP `connect-src`
-4. Add Stronghold secret key for the API credentials
-5. Wire up the Settings UI in `src/pages/Settings/Integrations.tsx`
-6. Add audit log call before the external API request
+All integration domains are whitelisted in `src-tauri/tauri.conf.json`:
+
+```json
+"connect-src": "... https://auth.atlassian.com https://*.atlassian.net https://login.microsoftonline.com https://dev.azure.com"
+```
+
+---
+
+## Adding a New Integration
+
+1. **Create API client**: `src-tauri/src/integrations/{name}.rs`
+2. **Implement functions**: `test_connection()`, create/read/update operations
+3. **Add TDD tests**: Use `mockito` for HTTP mocking
+4. **Update migration**: Add service to `credentials` and `integration_config` CHECK constraints
+5. **Add IPC commands**: `src-tauri/src/commands/integrations.rs`
+6. **Update CSP**: Add API domains to `tauri.conf.json`
+7. **Wire up UI**: `src/pages/Settings/Integrations.tsx`
+8. **Update capabilities**: Add any required Tauri permissions
+9. **Document**: Update this wiki page
+
+---
+
+## Troubleshooting
+
+### OAuth "Command plugin:shell|open not allowed"
+**Fix**: Add `"shell:allow-open"` to `src-tauri/capabilities/default.json`
+
+### Token Exchange Fails
+**Check**:
+1. PKCE verifier matches challenge
+2. Redirect URI exactly matches registered callback
+3. Authorization code hasn't expired
+4. Client ID/secret are correct
+
+### ServiceNow 401 Unauthorized
+**Check**:
+1. Username/password are correct
+2. User has API access enabled
+3. Instance URL is correct (no trailing slash)
+
+### Confluence API 404
+**Check**:
+1. Base URL format: `https://yourorg.atlassian.net` (no `/wiki/`)
+2. Space key exists and user has access
+3. OAuth token has required scopes (`read:confluence-content.all`, `write:confluence-content`)
+
+### Azure DevOps 403 Forbidden
+**Check**:
+1. OAuth token has required scopes (`vso.work_write`)
+2. User has permissions in the project
+3. Project name is case-sensitive