docs: sync from docs/wiki/ at commit e45e4277

Gitea Actions 2026-04-03 23:09:07 +00:00
parent 10b14a2ffe
commit a2d81d5716
5 changed files with 609 additions and 71 deletions

@ -1,6 +1,6 @@
# AI Providers # 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 ## 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)<br>`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<String>` | Override endpoint path | `/chat/completions` |
| `custom_auth_header` | `Option<String>` | Custom auth header name | `Authorization` |
| `custom_auth_prefix` | `Option<String>` | Prefix before API key | `Bearer ` |
| `api_format` | `Option<String>` | API format (`openai` or `msi_genai`) | `openai` |
| `session_id` | `Option<String>` | Session ID for stateful APIs | None |
| `user_id` | `Option<String>` | 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 ## Adding a New Provider
1. Create `src-tauri/src/ai/{name}.rs` implementing the `Provider` trait 1. Create `src-tauri/src/ai/{name}.rs` implementing the `Provider` trait

@ -2,7 +2,7 @@
## Overview ## 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` **DB file location:** `{app_data_dir}/tftsr.db`
@ -38,7 +38,7 @@ pub fn init_db(data_dir: &Path) -> anyhow::Result<Connection> {
--- ---
## Schema (10 Migrations) ## Schema (11 Migrations)
### 001 — issues ### 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 ## Key Design Notes

16
Home.md

@ -24,8 +24,10 @@
- **5-Whys AI Triage** — Interactive guided root cause analysis via multi-turn AI chat - **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 - **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) - **Multi-Provider AI** — OpenAI, Anthropic Claude, Google Gemini, Mistral, AWS Bedrock (via LiteLLM), MSI GenAI (Motorola internal), local Ollama (fully offline)
- **SQLCipher AES-256** — All issue history encrypted at rest - **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 - **RCA + Post-Mortem Generation** — Auto-populated Markdown templates, exportable as MD/PDF
- **Ollama Management** — Hardware detection, model recommendations, in-app model management - **Ollama Management** — Hardware detection, model recommendations, in-app model management
- **Audit Trail** — Every external data send logged with SHA-256 hash - **Audit Trail** — Every external data send logged with SHA-256 hash
@ -33,9 +35,13 @@
## Releases ## 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). 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 18 (Core application) | ✅ Complete | | Phases 18 (Core application) | ✅ Complete |
| Phase 9 (History/Search) | 🔲 Pending | | 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 11 (CI/CD) | ✅ Complete — Gitea Actions fully operational |
| Phase 12 (Release packaging) | ✅ linux/amd64 · linux/arm64 (native) · windows/amd64 | | Phase 12 (Release packaging) | ✅ linux/amd64 · linux/arm64 (native) · windows/amd64 |

@ -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 | All integration commands are production-ready with complete OAuth2/authentication flows.
|---------|---------|
| `test_confluence_connection` | Verify Confluence credentials | ### OAuth2 Commands
| `publish_to_confluence` | Publish RCA/postmortem to Confluence space |
| `test_servicenow_connection` | Verify ServiceNow credentials | ### `initiate_oauth`
| `create_servicenow_incident` | Create incident from issue | ```typescript
| `test_azuredevops_connection` | Verify Azure DevOps credentials | initiateOauthCmd(service: "confluence" | "servicenow" | "azuredevops") → OAuthInitResponse
| `create_azuredevops_workitem` | Create work item from issue | ```
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<string, unknown>) → 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<string, unknown>) → 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<string, any>) → TicketResult
```
Updates incident fields. Uses JSON-PATCH format.
### Azure DevOps Commands
### `test_azuredevops_connection`
```typescript
testAzureDevOpsConnectionCmd(orgUrl: string, credentials: Record<string, unknown>) → 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<string, any>) → 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<String, String>
```

@ -1,97 +1,273 @@
# Integrations # 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 ## Confluence
**Purpose:** Publish RCA and post-mortem documents to a Confluence space. **Purpose:** Publish RCA and post-mortem documents to Confluence spaces.
**Commands:** **Status:** ✅ **Implemented** (v0.2.3)
- `test_confluence_connection(base_url, credentials)` — Verify credentials
- `publish_to_confluence(doc_id, space_key, parent_page_id?)` — Create/update page
**Planned implementation:** ### Features
- Confluence REST API v2: `POST /wiki/rest/api/content` - OAuth2 authentication with PKCE flow
- Auth: Basic auth (email + API token) or OAuth2 - List accessible spaces
- Page format: Convert Markdown → Confluence storage format (XHTML-like) - 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<ConnectionResult, String>
list_spaces(config: &ConfluenceConfig) -> Result<Vec<Space>, String>
search_pages(config: &ConfluenceConfig, query: &str, space_key: Option<&str>) -> Result<Vec<Page>, String>
publish_page(config: &ConfluenceConfig, space_key: &str, title: &str, content_html: &str, parent_page_id: Option<&str>) -> Result<PublishResult, String>
update_page(config: &ConfluenceConfig, page_id: &str, title: &str, content_html: &str, version: i32) -> Result<PublishResult, String>
``` ```
Base URL: https://yourorg.atlassian.net
Email: user@example.com ### Configuration (Settings → Integrations → Confluence)
API Token: (stored in Stronghold)
Space Key: PROJ
``` ```
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 ## ServiceNow
**Purpose:** Create incident records in ServiceNow from TFTSR issues. **Purpose:** Create and manage incident records in ServiceNow.
**Commands:** **Status:** ✅ **Implemented** (v0.2.3)
- `test_servicenow_connection(instance_url, credentials)` — Verify credentials
- `create_servicenow_incident(issue_id, config)` — Create incident
**Planned implementation:** ### Features
- ServiceNow Table API: `POST /api/now/table/incident` - Basic authentication (username/password)
- Auth: Basic auth or OAuth2 bearer token - Search incidents by description
- Field mapping: TFTSR severity → ServiceNow priority (P1=Critical, P2=High, etc.) - 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<ConnectionResult, String>
search_incidents(config: &ServiceNowConfig, query: &str) -> Result<Vec<Incident>, String>
create_incident(config: &ServiceNowConfig, short_description: &str, description: &str, urgency: &str, impact: &str) -> Result<TicketResult, String>
get_incident(config: &ServiceNowConfig, incident_id: &str) -> Result<Incident, String>
update_incident(config: &ServiceNowConfig, sys_id: &str, updates: serde_json::Value) -> Result<TicketResult, String>
``` ```
Instance URL: https://yourorg.service-now.com
Username: admin ### Configuration (Settings → Integrations → ServiceNow)
Password: (stored in Stronghold)
``` ```
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 ## 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:** **Status:** ✅ **Implemented** (v0.2.3)
- `test_azuredevops_connection(org_url, credentials)` — Verify credentials
- `create_azuredevops_workitem(issue_id, project, config)` — Create work item
**Planned implementation:** ### Features
- Azure DevOps REST API: `POST /{organization}/{project}/_apis/wit/workitems/${type}` - OAuth2 authentication with PKCE flow
- Auth: Personal Access Token (PAT) via Basic auth header - Search work items via WIQL queries
- Work item type: Bug or Incident - 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<ConnectionResult, String>
search_work_items(config: &AzureDevOpsConfig, query: &str) -> Result<Vec<WorkItem>, String>
create_work_item(config: &AzureDevOpsConfig, title: &str, description: &str, work_item_type: &str, severity: &str) -> Result<TicketResult, String>
get_work_item(config: &AzureDevOpsConfig, work_item_id: i64) -> Result<WorkItem, String>
update_work_item(config: &AzureDevOpsConfig, work_item_id: i64, updates: serde_json::Value) -> Result<TicketResult, String>
```
### Configuration (Settings → Integrations → Azure DevOps)
``` ```
Organization URL: https://dev.azure.com/yourorg Organization URL: https://dev.azure.com/yourorg
Personal Access Token: (stored in Stronghold) Authentication: OAuth2 (bearer token, encrypted at rest)
Project: MyProject 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 ```bash
2. **Azure DevOps** — Clean REST API, straightforward PAT auth # Run all integration tests
3. **ServiceNow** — More complex field mapping; may require customer-specific configuration cargo test --manifest-path src-tauri/Cargo.toml --lib integrations
Each integration will also require: # Run specific integration tests
- Audit log entry on every publish action cargo test --manifest-path src-tauri/Cargo.toml confluence
- PII check on document content before external publish cargo test --manifest-path src-tauri/Cargo.toml servicenow
- Connection test UI in Settings → Integrations 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` All integration domains are whitelisted in `src-tauri/tauri.conf.json`:
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` ```json
4. Add Stronghold secret key for the API credentials "connect-src": "... https://auth.atlassian.com https://*.atlassian.net https://login.microsoftonline.com https://dev.azure.com"
5. Wire up the Settings UI in `src/pages/Settings/Integrations.tsx` ```
6. Add audit log call before the external API request
---
## 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