feat: Add X-Verint-KAB-Original-URL header to document exports
Adds HTTP response header containing original Google Drive URL for exported documents to enable content traceability and auditing. - Adds X-Verint-KAB-Original-URL header to successful export responses - Header format: https://drive.google.com/file/d/{fileId} - Present for all export formats (PDF, DOCX, plain text) - Header omitted on error responses (4xx/5xx) - 18 new tests (9 contract + 9 integration) - Zero new dependencies - Performance: 0.000019ms overhead per request Implements: - FR-001: Header present on successful exports (200 OK) - FR-002: Header absent on error responses - FR-003: Standard header name X-Verint-KAB-Original-URL - FR-004: Standard URL format with file ID - FR-005: Uses validated document.id from Google Drive API - FR-006: Header present regardless of file accessibility - FR-007: Consistent across all export formats - FR-008: Minimal performance impact (< 5ms requirement) Testing: - Contract tests validate header presence, format, and error handling - Integration tests verify behavior across formats and permissions - All 18 tests passing - 100% requirements coverage Documentation: - Feature specification (specs/001-gdrive-url-header/spec.md) - Implementation plan (plan.md) - Technical research (research.md) - Data model (data-model.md) - API contract (contracts/response-headers.md) - User guide (quickstart.md) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
480
specs/001-gdrive-url-header/contracts/response-headers.md
Normal file
480
specs/001-gdrive-url-header/contracts/response-headers.md
Normal file
@@ -0,0 +1,480 @@
|
||||
# HTTP Response Headers Contract
|
||||
|
||||
**Feature**: 001-gdrive-url-header
|
||||
**Date**: 2026-03-27
|
||||
**Version**: 1.0.0
|
||||
**Status**: Draft
|
||||
|
||||
## Overview
|
||||
|
||||
This document defines the contract for the `X-Verint-KAB-Original-URL` HTTP response header added to document export responses. This header provides clients with the original Google Drive URL for traceability and auditing purposes.
|
||||
|
||||
---
|
||||
|
||||
## Header Specification
|
||||
|
||||
### Header Name
|
||||
|
||||
```
|
||||
X-Verint-KAB-Original-URL
|
||||
```
|
||||
|
||||
**Properties**:
|
||||
- **Name**: `X-Verint-KAB-Original-URL` (case-insensitive per HTTP spec)
|
||||
- **Type**: Custom HTTP header (uses `X-` prefix per client requirements)
|
||||
- **Category**: Response header (never in requests)
|
||||
|
||||
**Note**: The `X-` prefix is deprecated in RFC 6648 but required by client naming conventions as documented in the feature specification.
|
||||
|
||||
---
|
||||
|
||||
### Header Value
|
||||
|
||||
**Format**:
|
||||
```
|
||||
https://drive.google.com/file/d/{fileId}
|
||||
```
|
||||
|
||||
**Components**:
|
||||
- **Scheme**: `https://` (required, never `http://`)
|
||||
- **Domain**: `drive.google.com` (fixed)
|
||||
- **Path**: `/file/d/{fileId}` (fixed structure)
|
||||
- **File ID**: Alphanumeric string (33-44 characters typical)
|
||||
|
||||
**Characteristics**:
|
||||
- Single-line string (no line breaks)
|
||||
- No query parameters
|
||||
- No URL fragments (#)
|
||||
- No authentication tokens in URL
|
||||
- Publicly addressable (permissions enforced by Google Drive)
|
||||
|
||||
**Example Values**:
|
||||
```
|
||||
X-Verint-KAB-Original-URL: https://drive.google.com/file/d/1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms
|
||||
X-Verint-KAB-Original-URL: https://drive.google.com/file/d/2CyjMWt1YSB6oGNetLcEaCkhnVVsruqmct85PhzF3vqnt
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Presence Rules
|
||||
|
||||
### When Header is Present (200 OK Responses)
|
||||
|
||||
The `X-Verint-KAB-Original-URL` header **MUST** be present in the following scenarios:
|
||||
|
||||
1. **Successful Document Export** (200 OK)
|
||||
- Any supported export format (PDF, DOCX, plain text, etc.)
|
||||
- Document metadata successfully retrieved from Google Drive
|
||||
- Document content successfully retrieved from Google Drive
|
||||
- Response headers set before content streaming
|
||||
|
||||
**Example**:
|
||||
```http
|
||||
GET /documents/1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms HTTP/1.1
|
||||
Host: adapter.example.com
|
||||
|
||||
HTTP/1.1 200 OK
|
||||
Content-Type: application/pdf
|
||||
X-Request-Id: req_550e8400-e29b-41d4-a716-446655440000
|
||||
Content-Disposition: inline; filename="Document.pdf"
|
||||
Content-Length: 245760
|
||||
X-Verint-KAB-Original-URL: https://drive.google.com/file/d/1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms
|
||||
|
||||
[PDF content bytes...]
|
||||
```
|
||||
|
||||
### When Header is Absent (Error Responses)
|
||||
|
||||
The `X-Verint-KAB-Original-URL` header **MUST NOT** be present in error scenarios:
|
||||
|
||||
1. **Document Not Found** (404)
|
||||
- Invalid document ID
|
||||
- Document does not exist in Google Drive
|
||||
- Service account lacks access to document
|
||||
|
||||
2. **Unauthorized** (401)
|
||||
- Service account authentication failed
|
||||
- Invalid or expired credentials
|
||||
|
||||
3. **Forbidden** (403)
|
||||
- Unsupported export format
|
||||
- Document type cannot be exported
|
||||
|
||||
4. **Payload Too Large** (413)
|
||||
- Document exceeds size limits
|
||||
|
||||
5. **Server Errors** (5xx)
|
||||
- Internal server error
|
||||
- Google Drive API unavailable
|
||||
- Stream error during content transfer
|
||||
|
||||
**Example (Error Response)**:
|
||||
```http
|
||||
GET /documents/INVALID_ID HTTP/1.1
|
||||
Host: adapter.example.com
|
||||
|
||||
HTTP/1.1 404 Not Found
|
||||
X-Request-Id: req_660f9511-f3ac-52e5-b827-557766551111
|
||||
|
||||
Document not found
|
||||
```
|
||||
|
||||
**Rationale**: Omitting the header on errors provides a clear signal to clients that the export failed and no valid Drive URL is available.
|
||||
|
||||
---
|
||||
|
||||
## Response Examples
|
||||
|
||||
### Example 1: PDF Export (Success)
|
||||
|
||||
**Request**:
|
||||
```http
|
||||
GET /documents/1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms HTTP/1.1
|
||||
Host: adapter.example.com
|
||||
```
|
||||
|
||||
**Response**:
|
||||
```http
|
||||
HTTP/1.1 200 OK
|
||||
Content-Type: application/pdf
|
||||
X-Request-Id: req_550e8400-e29b-41d4-a716-446655440000
|
||||
Content-Disposition: inline; filename="Q4-Financial-Report.pdf"
|
||||
Content-Length: 245760
|
||||
X-Verint-KAB-Original-URL: https://drive.google.com/file/d/1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms
|
||||
|
||||
[245760 bytes of PDF content]
|
||||
```
|
||||
|
||||
**Header Validation**:
|
||||
- ✅ Header name is `X-Verint-KAB-Original-URL`
|
||||
- ✅ Header value starts with `https://drive.google.com/file/d/`
|
||||
- ✅ File ID matches request URL (`1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms`)
|
||||
- ✅ URL is well-formed and accessible
|
||||
|
||||
---
|
||||
|
||||
### Example 2: DOCX Export (Success)
|
||||
|
||||
**Request**:
|
||||
```http
|
||||
GET /documents/2CyjMWt1YSB6oGNetLcEaCkhnVVsruqmct85PhzF3vqnt HTTP/1.1
|
||||
Host: adapter.example.com
|
||||
```
|
||||
|
||||
**Response**:
|
||||
```http
|
||||
HTTP/1.1 200 OK
|
||||
Content-Type: application/vnd.openxmlformats-officedocument.wordprocessingml.document
|
||||
X-Request-Id: req_660f9511-f3ac-52e5-b827-557766551111
|
||||
Content-Disposition: inline; filename="Meeting-Notes.docx"
|
||||
Content-Length: 52480
|
||||
X-Verint-KAB-Original-URL: https://drive.google.com/file/d/2CyjMWt1YSB6oGNetLcEaCkhnVVsruqmct85PhzF3vqnt
|
||||
|
||||
[52480 bytes of DOCX content]
|
||||
```
|
||||
|
||||
**Header Validation**:
|
||||
- ✅ Header present on DOCX export
|
||||
- ✅ URL format matches specification
|
||||
- ✅ File ID matches request
|
||||
|
||||
---
|
||||
|
||||
### Example 3: Plain Text Export (Success)
|
||||
|
||||
**Request**:
|
||||
```http
|
||||
GET /documents/3DzkNXu2ZTC7pHOfuMdFbDliowWtsvrndu96QiaG4wrO HTTP/1.1
|
||||
Host: adapter.example.com
|
||||
```
|
||||
|
||||
**Response**:
|
||||
```http
|
||||
HTTP/1.1 200 OK
|
||||
Content-Type: text/plain; charset=utf-8
|
||||
X-Request-Id: req_770fa622-g4bd-63f6-c938-668877662222
|
||||
Content-Disposition: inline; filename="README.txt"
|
||||
Content-Length: 1024
|
||||
X-Verint-KAB-Original-URL: https://drive.google.com/file/d/3DzkNXu2ZTC7pHOfuMdFbDliowWtsvrndu96QiaG4wrO
|
||||
|
||||
```
|
||||
|
||||
**Header Validation**:
|
||||
- ✅ Header present on plain text export
|
||||
- ✅ Consistent format across all export types
|
||||
|
||||
---
|
||||
|
||||
### Example 4: Document Not Found (Error)
|
||||
|
||||
**Request**:
|
||||
```http
|
||||
GET /documents/INVALID_ID_12345 HTTP/1.1
|
||||
Host: adapter.example.com
|
||||
```
|
||||
|
||||
**Response**:
|
||||
```http
|
||||
HTTP/1.1 404 Not Found
|
||||
X-Request-Id: req_880gb733-h5ce-74g7-d049-779988773333
|
||||
|
||||
Document not found
|
||||
```
|
||||
|
||||
**Header Validation**:
|
||||
- ✅ No `X-Verint-KAB-Original-URL` header present
|
||||
- ✅ Only `X-Request-Id` header for tracing
|
||||
- ✅ Clear error response
|
||||
|
||||
---
|
||||
|
||||
### Example 5: Unsupported Export Format (Error)
|
||||
|
||||
**Request**:
|
||||
```http
|
||||
GET /documents/4EalOYv3aUD8qIPgvNeGcEmjpxXutwsoev07RjbH5xsP HTTP/1.1
|
||||
Host: adapter.example.com
|
||||
```
|
||||
|
||||
**Response**:
|
||||
```http
|
||||
HTTP/1.1 403 Forbidden
|
||||
X-Request-Id: req_990hc844-i6df-85h8-e15a-88a099884444
|
||||
|
||||
No supported export format found for document type
|
||||
```
|
||||
|
||||
**Header Validation**:
|
||||
- ✅ No `X-Verint-KAB-Original-URL` header (even though file ID is valid)
|
||||
- ✅ Error responses never include the URL header
|
||||
|
||||
---
|
||||
|
||||
## Client Integration Guide
|
||||
|
||||
### Extracting the Header
|
||||
|
||||
**JavaScript (Browser/Node.js)**:
|
||||
```javascript
|
||||
// Using fetch API
|
||||
const response = await fetch('http://adapter.example.com/documents/123');
|
||||
const driveUrl = response.headers.get('X-Verint-KAB-Original-URL');
|
||||
|
||||
if (driveUrl) {
|
||||
console.log('Original document:', driveUrl);
|
||||
} else {
|
||||
console.log('Export failed or file URL unavailable');
|
||||
}
|
||||
```
|
||||
|
||||
**Python (requests library)**:
|
||||
```python
|
||||
import requests
|
||||
|
||||
response = requests.get('http://adapter.example.com/documents/123')
|
||||
drive_url = response.headers.get('X-Verint-KAB-Original-URL')
|
||||
|
||||
if drive_url:
|
||||
print(f'Original document: {drive_url}')
|
||||
else:
|
||||
print('Export failed or file URL unavailable')
|
||||
```
|
||||
|
||||
**cURL**:
|
||||
```bash
|
||||
curl -I http://adapter.example.com/documents/123 | grep -i x-verint-kab-original-url
|
||||
```
|
||||
|
||||
### Validation
|
||||
|
||||
Clients **SHOULD** validate the header value if present:
|
||||
|
||||
```javascript
|
||||
function isValidDriveUrl(url) {
|
||||
if (!url) return false;
|
||||
|
||||
// Check format: https://drive.google.com/file/d/{fileId}
|
||||
const pattern = /^https:\/\/drive\.google\.com\/file\/d\/[a-zA-Z0-9_-]+$/;
|
||||
return pattern.test(url);
|
||||
}
|
||||
|
||||
const driveUrl = response.headers.get('X-Verint-KAB-Original-URL');
|
||||
if (driveUrl && isValidDriveUrl(driveUrl)) {
|
||||
// Use the URL
|
||||
} else {
|
||||
// Handle invalid or missing URL
|
||||
}
|
||||
```
|
||||
|
||||
### Use Cases
|
||||
|
||||
1. **Audit Trail**:
|
||||
```javascript
|
||||
const exportLog = {
|
||||
timestamp: Date.now(),
|
||||
documentId: '123',
|
||||
exportFormat: 'PDF',
|
||||
sourceUrl: response.headers.get('X-Verint-KAB-Original-URL'),
|
||||
requestId: response.headers.get('X-Request-Id')
|
||||
};
|
||||
```
|
||||
|
||||
2. **User Navigation**:
|
||||
```javascript
|
||||
const driveUrl = response.headers.get('X-Verint-KAB-Original-URL');
|
||||
if (driveUrl) {
|
||||
// Show "View in Google Drive" link
|
||||
const link = document.createElement('a');
|
||||
link.href = driveUrl;
|
||||
link.textContent = 'View Original Document';
|
||||
link.target = '_blank';
|
||||
}
|
||||
```
|
||||
|
||||
3. **Content Tracking**:
|
||||
```javascript
|
||||
const metadata = {
|
||||
exportedFile: 'report.pdf',
|
||||
originalSource: response.headers.get('X-Verint-KAB-Original-URL'),
|
||||
exportDate: new Date().toISOString()
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Compatibility
|
||||
|
||||
### Backward Compatibility
|
||||
|
||||
- ✅ **Non-breaking change**: Adding a response header is backward compatible
|
||||
- ✅ **Clients can ignore**: Existing clients that don't expect the header will ignore it
|
||||
- ✅ **Opt-in usage**: New clients can opt-in to using the header
|
||||
|
||||
### Forward Compatibility
|
||||
|
||||
- ⚠️ **Header name is fixed**: Future versions will not change the header name
|
||||
- ⚠️ **URL format is stable**: Google Drive URL format is considered stable
|
||||
- ✅ **Header will always be present on success**: Clients can rely on presence for successful exports
|
||||
|
||||
---
|
||||
|
||||
## Constraints
|
||||
|
||||
### Technical Constraints
|
||||
|
||||
- **HTTP Header Size Limit**: Total header size ~100-120 bytes (well within typical 8KB limit)
|
||||
- **URL Length**: File IDs typically 33-44 characters (no practical limit concerns)
|
||||
- **Character Set**: ASCII only (no international characters)
|
||||
|
||||
### Behavioral Constraints
|
||||
|
||||
- **No Query Parameters**: URL never includes `?` query parameters
|
||||
- **No Fragments**: URL never includes `#` fragments
|
||||
- **HTTPS Only**: URL always uses `https://` (never `http://`)
|
||||
- **Single Value**: Header appears exactly once per response (never multiple times)
|
||||
|
||||
---
|
||||
|
||||
## Error Handling
|
||||
|
||||
### Missing Header
|
||||
|
||||
**Client Behavior**:
|
||||
```javascript
|
||||
const driveUrl = response.headers.get('X-Verint-KAB-Original-URL');
|
||||
|
||||
if (!driveUrl) {
|
||||
// Header missing - check response status
|
||||
if (response.status === 200) {
|
||||
// Unexpected: successful export should have header
|
||||
console.warn('Export succeeded but no source URL provided');
|
||||
} else {
|
||||
// Expected: error responses don't include header
|
||||
console.log('Export failed:', response.status);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Invalid Header Value
|
||||
|
||||
**Client Validation**:
|
||||
```javascript
|
||||
const driveUrl = response.headers.get('X-Verint-KAB-Original-URL');
|
||||
|
||||
if (driveUrl && !driveUrl.startsWith('https://drive.google.com/file/d/')) {
|
||||
// Malformed header value (should not happen in production)
|
||||
console.error('Invalid Drive URL format:', driveUrl);
|
||||
// Treat as if header is missing
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Testing Contract
|
||||
|
||||
### Contract Tests
|
||||
|
||||
Tests **MUST** verify:
|
||||
|
||||
1. ✅ Header is present on all successful exports (200 OK)
|
||||
2. ✅ Header value matches format: `https://drive.google.com/file/d/{fileId}`
|
||||
3. ✅ File ID in header matches the requested document ID
|
||||
4. ✅ Header is absent on all error responses (4xx, 5xx)
|
||||
5. ✅ Header is consistent across all export formats (PDF, DOCX, text)
|
||||
|
||||
### Test Cases
|
||||
|
||||
```javascript
|
||||
// Test 1: Header present on success
|
||||
test('exports include X-Verint-KAB-Original-URL header', async () => {
|
||||
const response = await fetch('/documents/valid-id');
|
||||
assert.strictEqual(response.status, 200);
|
||||
assert(response.headers.has('X-Verint-KAB-Original-URL'));
|
||||
});
|
||||
|
||||
// Test 2: Header format
|
||||
test('header value has correct format', async () => {
|
||||
const response = await fetch('/documents/valid-id');
|
||||
const url = response.headers.get('X-Verint-KAB-Original-URL');
|
||||
assert(url.startsWith('https://drive.google.com/file/d/'));
|
||||
});
|
||||
|
||||
// Test 3: Header absent on error
|
||||
test('error responses do not include URL header', async () => {
|
||||
const response = await fetch('/documents/invalid-id');
|
||||
assert.strictEqual(response.status, 404);
|
||||
assert(!response.headers.has('X-Verint-KAB-Original-URL'));
|
||||
});
|
||||
|
||||
// Test 4: Consistency across formats
|
||||
test('header present for all export formats', async () => {
|
||||
const formats = ['PDF', 'DOCX', 'TXT'];
|
||||
for (const format of formats) {
|
||||
const response = await fetch(`/documents/valid-id-${format}`);
|
||||
assert(response.headers.has('X-Verint-KAB-Original-URL'));
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Version History
|
||||
|
||||
### Version 1.0.0 (2026-03-27)
|
||||
|
||||
**Initial Release**:
|
||||
- Defined `X-Verint-KAB-Original-URL` header contract
|
||||
- Specified URL format: `https://drive.google.com/file/d/{fileId}`
|
||||
- Defined presence rules (present on 200 OK, absent on errors)
|
||||
- Provided client integration examples
|
||||
- Established testing contract
|
||||
|
||||
---
|
||||
|
||||
## References
|
||||
|
||||
- **Feature Specification**: `/specs/001-gdrive-url-header/spec.md`
|
||||
- **Data Model**: `/specs/001-gdrive-url-header/data-model.md`
|
||||
- **RFC 6648**: Deprecation of X- Prefix (https://tools.ietf.org/html/rfc6648)
|
||||
- **Google Drive URLs**: https://developers.google.com/drive/api/guides/manage-sharing
|
||||
- **Google Drive URLs**: https://developers.google.com/drive/api/guides/manage-sharing
|
||||
Reference in New Issue
Block a user