docs: sync from docs/wiki/ at commit e45e4277
parent
10b14a2ffe
commit
a2d81d5716
126
AI-Providers.md
126
AI-Providers.md
@ -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
|
||||||
|
|||||||
45
Database.md
45
Database.md
@ -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
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 1–8 (Core application) | ✅ Complete |
|
| Phases 1–8 (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 |
|
||||||
|
|
||||||
|
|||||||
211
IPC-Commands.md
211
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 |
|
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>
|
||||||
|
```
|
||||||
|
|||||||
276
Integrations.md
276
Integrations.md
@ -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>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Configuration (Settings → Integrations → Confluence)
|
||||||
```
|
```
|
||||||
Base URL: https://yourorg.atlassian.net
|
Base URL: https://yourorg.atlassian.net
|
||||||
Email: user@example.com
|
Authentication: OAuth2 (bearer token, encrypted at rest)
|
||||||
API Token: (stored in Stronghold)
|
Default Space: PROJ
|
||||||
Space Key: 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>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Configuration (Settings → Integrations → ServiceNow)
|
||||||
```
|
```
|
||||||
Instance URL: https://yourorg.service-now.com
|
Instance URL: https://yourorg.service-now.com
|
||||||
Username: admin
|
Username: admin
|
||||||
Password: (stored in Stronghold)
|
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
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user