Files
cve-dashboard/.kiro/specs/archer-template-library/design.md
Jordan Ramos a61d254ff9 Sync .kiro/ from master — v2.2.0 release batch
New specs: archer-template-library, ccp-metrics-view-restructure,
compliance-list-stale-after-sidebar-edit, compliance-metric-estimated-resolution-date,
compliance-remediation-display-fix, flexible-jira-ticket-creation,
forecast-burndown-chart, granite-loader-export, ivanti-queue-clear-completed-fix,
multi-item-jira-ticket, queue-collapsible-sections, vendor-issue-type-dropdown

New steering: archer-template-gen.md

Updated: migration-registration-check hook, remediation-plan-history spec,
gitlab-workflow, tech, versioning steering files
2026-06-04 11:27:31 -06:00

519 lines
21 KiB
Markdown

# Design Document: Archer Template Library
## Overview
The Archer Template Library adds a template management system to the STEAM Security Dashboard, allowing editors to store, browse, and reuse pre-filled content for Archer Risk Acceptance forms. Templates are organized by a Vendor > Platform > Model hierarchy and contain the static and semi-static content sections that map directly to the external Archer eGRC application's form fields.
The feature integrates into the existing Ivanti Todo Queue page where Archer workflow items are worked. When an editor selects an Archer queue item, they can pick a template, view its sections, and copy content to their clipboard for pasting into the external Archer application.
### Design Decisions
1. **Separate route module** (`routes/archerTemplates.js`) following the project's factory pattern — keeps template CRUD isolated from existing `archerTickets.js` which handles EXC number tracking.
2. **PostgreSQL table** with a composite unique index on `(LOWER(TRIM(vendor)), LOWER(TRIM(platform)), LOWER(TRIM(model)))` to enforce case-insensitive uniqueness at the database level.
3. **Single page component** (`ArcherTemplatePage.js`) for the Template Manager, plus a **reusable TemplateSelector component** embedded in the existing `IvantiTodoQueuePage.js` for the queue workflow integration.
4. **No separate search endpoint** — the list endpoint handles search/filter via query parameters, keeping the API surface small.
5. **Clipboard API** (`navigator.clipboard.writeText`) for copy operations — the dashboard already runs on HTTPS in production.
---
## Architecture
```mermaid
graph TD
subgraph Frontend
TM[ArcherTemplatePage.js<br/>Template Manager]
TS[TemplateSelector.js<br/>Queue Integration]
IQ[IvantiTodoQueuePage.js]
end
subgraph Backend
R[routes/archerTemplates.js<br/>Express Router]
A[helpers/auditLog.js]
DB[(PostgreSQL<br/>archer_templates)]
end
TM -->|fetch /api/archer-templates| R
TS -->|fetch /api/archer-templates| R
IQ -->|embeds| TS
R -->|pool.query| DB
R -->|logAudit| A
A -->|fire-and-forget INSERT| DB
```
### Request Flow
1. Frontend component calls `/api/archer-templates/*` with session cookie
2. `requireAuth()` middleware validates session, attaches `req.user`
3. Write operations additionally pass through `requireGroup('Admin', 'Standard_User')`
4. Route handler validates input, executes PostgreSQL query via `pool`
5. On success of write operations, `logAudit()` is called fire-and-forget
6. Response returned to frontend
---
## Components and Interfaces
### Backend: `routes/archerTemplates.js`
Factory function signature:
```javascript
function createArcherTemplatesRouter() {
// Returns Express Router
// Imports pool from '../db', auth from '../middleware/auth', logAudit from '../helpers/auditLog'
}
```
#### API Endpoints
| Method | Path | Auth | Description |
|--------|------|------|-------------|
| `GET` | `/api/archer-templates` | requireAuth | List/search/filter templates |
| `GET` | `/api/archer-templates/:id` | requireAuth | Get single template by ID |
| `POST` | `/api/archer-templates` | requireAuth + requireGroup | Create template |
| `PUT` | `/api/archer-templates/:id` | requireAuth + requireGroup | Update template |
| `DELETE` | `/api/archer-templates/:id` | requireAuth + requireGroup | Delete template |
| `POST` | `/api/archer-templates/:id/clone` | requireAuth + requireGroup | Clone template |
| `GET` | `/api/archer-templates/hierarchy/vendors` | requireAuth | Distinct vendors list |
| `GET` | `/api/archer-templates/hierarchy/platforms` | requireAuth | Distinct platforms for vendor |
| `GET` | `/api/archer-templates/hierarchy/models` | requireAuth | Distinct models for vendor+platform |
#### `GET /api/archer-templates`
**Query Parameters:**
| Param | Type | Description |
|-------|------|-------------|
| `search` | string | Substring match across vendor, platform, model (case-insensitive). Ignored if empty/whitespace-only. |
| `vendor` | string | Exact match filter on vendor (case-insensitive) |
| `platform` | string | Exact match filter on platform (case-insensitive) |
| `model` | string | Exact match filter on model (case-insensitive) |
**Response:** `200 OK`
```json
[
{
"id": 1,
"vendor": "Harmonic",
"platform": "vCMTS",
"model": "3.29.1",
"environment_overview": "...",
"segmentation": "...",
"mitigating_controls": "...",
"additional_info": "...",
"charter_network_banner": "...",
"data_classification": "...",
"charter_network": "...",
"additional_access_list": "...",
"created_by": 3,
"created_at": "2025-01-15T10:30:00Z",
"updated_at": "2025-01-15T10:30:00Z"
}
]
```
**Filtering logic:**
- `search` applies as `ILIKE '%value%'` across vendor OR platform OR model
- Field-specific filters apply as `LOWER(TRIM(field)) = LOWER(TRIM(value))`
- When both search and filters are present, they combine with AND logic
- Results always sorted by `vendor ASC, platform ASC, model ASC`
#### `POST /api/archer-templates`
**Request Body:**
```json
{
"vendor": "Harmonic",
"platform": "vCMTS",
"model": "3.29.1",
"environment_overview": "content...",
"segmentation": "content...",
"mitigating_controls": "",
"additional_info": "",
"charter_network_banner": "",
"data_classification": "",
"charter_network": "",
"additional_access_list": ""
}
```
**Validation:**
- `vendor`, `platform`, `model`: required, 1-100 chars after trim, non-empty after trim
- Section fields: optional, max 10,000 chars each, default to empty string
- Uniqueness: `LOWER(TRIM(vendor)) + LOWER(TRIM(platform)) + LOWER(TRIM(model))` must be unique
**Response:** `201 Created`
```json
{
"id": 1,
"vendor": "Harmonic",
"platform": "vCMTS",
"model": "3.29.1",
"environment_overview": "content...",
"...": "...",
"created_by": 3,
"created_at": "2025-01-15T10:30:00Z",
"updated_at": "2025-01-15T10:30:00Z"
}
```
**Error Responses:**
- `400` — missing or invalid fields (includes field names in error message)
- `409` — duplicate vendor/platform/model combination
#### `PUT /api/archer-templates/:id`
**Request Body:** Partial — only provided fields are updated.
```json
{
"vendor": "Harmonic",
"mitigating_controls": "updated content..."
}
```
**Validation:** Same rules as create for any provided field. If vendor/platform/model change, uniqueness is re-checked against other templates (excluding self).
**Response:** `200 OK` — returns full updated template object.
**Error Responses:**
- `400` — invalid field values
- `404` — template ID not found
- `409` — new vendor/platform/model combination conflicts with another template
#### `DELETE /api/archer-templates/:id`
**Response:** `200 OK`
```json
{ "message": "Template deleted successfully" }
```
**Error Responses:**
- `404` — template ID not found
#### `POST /api/archer-templates/:id/clone`
**Request Body:**
```json
{
"vendor": "Harmonic",
"platform": "vCMTS",
"model": "3.30.0"
}
```
**Validation:**
- All three hierarchy fields required
- At least one must differ from source (enforced by uniqueness constraint)
- Same length/format validation as create
**Response:** `201 Created` — returns full new template object with copied section content.
**Error Responses:**
- `400` — missing/invalid hierarchy fields
- `404` — source template not found
- `409` — target combination already exists
#### `GET /api/archer-templates/hierarchy/vendors`
**Response:** `200 OK`
```json
["Adtran", "Harmonic", "Vecima"]
```
#### `GET /api/archer-templates/hierarchy/platforms?vendor=Harmonic`
**Query Parameters:** `vendor` (required)
**Response:** `200 OK`
```json
["RPD", "vCMTS"]
```
**Error:** `400` if `vendor` param missing.
#### `GET /api/archer-templates/hierarchy/models?vendor=Harmonic&platform=vCMTS`
**Query Parameters:** `vendor` (required), `platform` (required)
**Response:** `200 OK`
```json
["3.29.1", "3.30.0"]
```
**Error:** `400` if either param missing.
---
### Frontend: `ArcherTemplatePage.js`
Full-page Template Manager component at `frontend/src/components/pages/ArcherTemplatePage.js`.
**Component Structure:**
```
ArcherTemplatePage
├── Header (title + create button)
├── TemplateList (grouped by vendor > platform)
│ ├── VendorGroup (collapsible)
│ │ ├── PlatformSubgroup
│ │ │ └── TemplateRow (model, edit/clone/delete buttons)
│ │ └── ...
│ └── ...
├── TemplateFormModal (create/edit/clone)
│ ├── Hierarchy fields (vendor, platform, model)
│ └── Section textareas (8 sections with labels)
└── DeleteConfirmModal
```
**State Management:** Local `useState` — no global state needed. Template list fetched on mount and after mutations.
**Role-based rendering:** Uses `useAuth().canWrite()` to conditionally show action buttons. Viewers see the list but no create/edit/delete/clone controls.
---
### Frontend: `TemplateSelector.js`
Reusable component embedded in `IvantiTodoQueuePage.js` when viewing an Archer workflow item.
**Component Structure:**
```
TemplateSelector
├── SearchableDropdown
│ ├── Search input
│ └── Filtered template list
├── SectionPanel (shown after selection)
│ ├── StaticSections (Environment Overview, Segmentation, Mitigating Controls)
│ ├── SemiStaticSections (Additional Info, Charter Network Banner, etc.)
│ └── CopyAllButton
└── SectionBlock (repeated for each section)
├── Label
├── Content (or "No content stored" placeholder)
└── CopyButton (with confirmation state)
```
**Props:**
```javascript
// No props needed — fetches templates from API independently
// Appears as an expandable panel within the Archer queue item view
```
---
## Data Models
### Database Table: `archer_templates`
```sql
CREATE TABLE IF NOT EXISTS archer_templates (
id SERIAL PRIMARY KEY,
vendor VARCHAR(100) NOT NULL,
platform VARCHAR(100) NOT NULL,
model VARCHAR(100) NOT NULL,
environment_overview TEXT NOT NULL DEFAULT '',
segmentation TEXT NOT NULL DEFAULT '',
mitigating_controls TEXT NOT NULL DEFAULT '',
additional_info TEXT NOT NULL DEFAULT '',
charter_network_banner TEXT NOT NULL DEFAULT '',
data_classification TEXT NOT NULL DEFAULT '',
charter_network TEXT NOT NULL DEFAULT '',
additional_access_list TEXT NOT NULL DEFAULT '',
created_by INTEGER REFERENCES users(id) ON DELETE SET NULL,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
-- Case-insensitive uniqueness on trimmed vendor/platform/model
CREATE UNIQUE INDEX IF NOT EXISTS idx_archer_templates_unique_combo
ON archer_templates (LOWER(TRIM(vendor)), LOWER(TRIM(platform)), LOWER(TRIM(model)));
-- Index for list ordering and search
CREATE INDEX IF NOT EXISTS idx_archer_templates_vendor ON archer_templates(vendor);
CREATE INDEX IF NOT EXISTS idx_archer_templates_platform ON archer_templates(platform);
```
### Migration File: `backend/migrations/add_archer_templates_table.js`
Follows the project's migration pattern — idempotent, uses `IF NOT EXISTS`.
### Section Field Mapping
| DB Column | Archer Form Label | Type |
|-----------|------------------|------|
| `environment_overview` | Environment Overview | Static |
| `segmentation` | Segmentation | Static |
| `mitigating_controls` | Mitigating Controls | Static |
| `additional_info` | Additional Info/Background | Semi-Static |
| `charter_network_banner` | Charter Network Banner | Semi-Static |
| `data_classification` | Data Classification | Semi-Static |
| `charter_network` | Charter Network | Semi-Static |
| `additional_access_list` | Additional Access List | Semi-Static |
---
## Correctness Properties
*A property is a characteristic or behavior that should hold true across all valid executions of a system — essentially, a formal statement about what the system should do. Properties serve as the bridge between human-readable specifications and machine-verifiable correctness guarantees.*
### Property 1: Template data round-trip preservation
*For any* valid template with vendor (1-100 chars), platform (1-100 chars), model (1-100 chars), and section content (0-10,000 chars each), creating the template via the API and then retrieving it by ID SHALL return the exact same field values (after trimming vendor/platform/model whitespace).
**Validates: Requirements 1.1, 1.2, 1.5, 2.1**
### Property 2: Uniqueness enforcement across all write operations
*For any* two templates where `LOWER(TRIM(vendor))`, `LOWER(TRIM(platform))`, and `LOWER(TRIM(model))` are identical, the system SHALL reject the second operation (create, update, or clone) with a 409 status code, regardless of case or leading/trailing whitespace differences.
**Validates: Requirements 1.3, 2.2, 2.8, 3.2, 3.5**
### Property 3: Input validation rejects invalid hierarchy fields
*For any* template create or update request where vendor, platform, or model is empty after trimming, consists only of whitespace, or exceeds 100 characters, the API SHALL reject the request with a 400 status code and an error message identifying which field(s) failed validation.
**Validates: Requirements 1.6, 2.3**
### Property 4: List and search results are sorted
*For any* set of templates in the database and any combination of search/filter parameters, the API response SHALL return results ordered by vendor ascending, then platform ascending, then model ascending (lexicographic, case-insensitive).
**Validates: Requirements 2.4, 6.4**
### Property 5: Search and filter semantics with AND logic
*For any* search query (substring match on vendor/platform/model) combined with any field-specific exact-match filters (vendor, platform, model), the API SHALL return only templates that satisfy ALL conditions simultaneously: the search substring appears in at least one hierarchy field AND each provided filter exactly matches its respective field (case-insensitive).
**Validates: Requirements 2.5, 2.6, 6.1, 6.2, 6.3**
### Property 6: Partial update preserves unspecified fields
*For any* existing template and any subset of updatable fields provided in a PUT request, the API SHALL modify only the specified fields and leave all other fields unchanged, while setting `updated_at` to the current timestamp.
**Validates: Requirements 2.7**
### Property 7: Delete removes template permanently
*For any* existing template, after a successful DELETE request, a subsequent GET request for that template's ID SHALL return 404.
**Validates: Requirements 2.9**
### Property 8: Write operations require editor or admin role
*For any* user with viewer role (or no authenticated session), all write operations (POST create, PUT update, DELETE, POST clone) SHALL be rejected with 401 (unauthenticated) or 403 (insufficient permissions).
**Validates: Requirements 2.10, 4.13**
### Property 9: Non-existent template ID returns 404
*For any* template ID that does not exist in the database, GET, PUT, DELETE, and clone requests targeting that ID SHALL return a 404 status code.
**Validates: Requirements 2.12, 3.4**
### Property 10: Clone preserves all section content
*For any* existing template, cloning it with new vendor/platform/model values SHALL produce a new template whose 8 section content fields are byte-for-byte identical to the source template, with a different ID, new `created_at` timestamp, and `created_by` set to the requesting user.
**Validates: Requirements 3.1, 3.3**
### Property 11: Hierarchy endpoints return distinct sorted values
*For any* set of templates, the vendors endpoint SHALL return a deduplicated, alphabetically sorted array of vendor names; the platforms endpoint (given a vendor) SHALL return only distinct platforms from templates matching that vendor; and the models endpoint (given vendor+platform) SHALL return only distinct models from matching templates — all sorted alphabetically ascending.
**Validates: Requirements 7.1, 7.2, 7.3**
### Property 12: Copy All concatenation format
*For any* template with a mix of populated and empty sections, the "Copy All" operation SHALL produce a string that concatenates only non-empty sections, each preceded by its human-readable section header, in the order: static sections first (Environment Overview, Segmentation, Mitigating Controls) then semi-static sections (Additional Info/Background, Charter Network Banner, Data Classification, Charter Network, Additional Access List).
**Validates: Requirements 5.9**
---
## Error Handling
### Backend Error Strategy
| Scenario | HTTP Status | Error Response Format |
|----------|-------------|----------------------|
| Missing required field | 400 | `{ "error": "Vendor is required" }` or `{ "error": "Missing fields: vendor, model" }` |
| Field too long | 400 | `{ "error": "Vendor must be 100 characters or fewer" }` |
| Section too long | 400 | `{ "error": "environment_overview must be 10,000 characters or fewer" }` |
| Duplicate combination | 409 | `{ "error": "A template with this vendor/platform/model combination already exists" }` |
| Template not found | 404 | `{ "error": "Template not found" }` |
| Not authenticated | 401 | `{ "error": "Authentication required" }` |
| Insufficient permissions | 403 | `{ "error": "Insufficient permissions", "required": [...], "current": "..." }` |
| Database error | 500 | `{ "error": "Internal server error" }` |
### Frontend Error Handling
- **Network errors:** Caught in try/catch around `fetch()`, displayed as a banner message within the component.
- **Validation errors (400):** Displayed inline next to the relevant form field or as a general form error.
- **Conflict errors (409):** Displayed as a warning banner at the top of the form indicating the duplicate.
- **Auth errors (401/403):** Handled by AuthContext — 401 triggers redirect to login, 403 shows permission denied message.
- **Clipboard failures:** If `navigator.clipboard.writeText` rejects (e.g., permissions denied), display a fallback message suggesting manual copy.
### Audit Log Resilience
Per requirement 8.5, `logAudit()` is called fire-and-forget. If the audit insert fails, the error is logged to `console.error` but the main operation's response is unaffected. This matches the existing pattern in `helpers/auditLog.js`.
---
## Testing Strategy
### Property-Based Tests (fast-check)
The project uses `fast-check` for property-based testing (visible in existing `__tests__/*.property.test.js` files). Each correctness property above maps to one property-based test with a minimum of 100 iterations.
**Test file:** `backend/__tests__/archer-template-library.property.test.js`
**Properties to implement:**
| Property | Test Description |
|----------|-----------------|
| 1 | Template data round-trip — create + GET preserves all fields |
| 2 | Uniqueness enforcement — duplicate combinations rejected across create/update/clone |
| 3 | Input validation — invalid hierarchy fields rejected with 400 |
| 4 | Sorted results — list/search always returns sorted by vendor/platform/model |
| 5 | Search + filter AND logic — combined criteria narrow results correctly |
| 6 | Partial update semantics — unspecified fields preserved |
| 7 | Delete permanence — deleted templates return 404 |
| 8 | Access control — viewer role cannot perform write operations |
| 9 | Non-existent ID — 404 for all operations on missing templates |
| 10 | Clone content preservation — cloned sections match source |
| 11 | Hierarchy distinct values — deduplicated and sorted |
| 12 | Copy All format — concatenation includes only non-empty sections in order |
**Tag format:** Each test tagged with `// Feature: archer-template-library, Property N: <property text>`
**Configuration:** 100 iterations minimum per property.
### Unit / Example-Based Tests
**Test file:** `backend/__tests__/archer-template-library.test.js`
- Template creation with all sections populated
- Template creation with no sections (defaults to empty strings)
- Timestamp metadata correctness (created_at, updated_at, created_by)
- Clone metadata (new created_at, new created_by)
- Hierarchy endpoint without required params returns 400
- Empty search results return 200 with empty array
- Whitespace-only search param is ignored
- Authentication required for read endpoints (401 without session)
### Integration Tests
**Test file:** `backend/__tests__/archer-template-library.integration.test.js`
- Audit log entries created for create/update/delete/clone
- Audit log failure does not block template operation
- Failed operations do not produce audit entries
- Full workflow: create → list → update → clone → delete
### Frontend Tests
- TemplateSelector copy-to-clipboard behavior
- TemplateFormModal validation prevents submission with empty required fields
- Viewer role sees no action buttons
- Template list grouped by vendor/platform renders correctly