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

21 KiB

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

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:

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

[
  {
    "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:

{
  "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

{
  "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.

{
  "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

{ "message": "Template deleted successfully" }

Error Responses:

  • 404 — template ID not found

POST /api/archer-templates/:id/clone

Request Body:

{
  "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

["Adtran", "Harmonic", "Vecima"]

GET /api/archer-templates/hierarchy/platforms?vendor=Harmonic

Query Parameters: vendor (required)

Response: 200 OK

["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

["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:

// 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

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