Files
cve-dashboard/.kiro/specs/ticket-linking/tasks.md

10 KiB
Raw Blame History

Implementation Plan: Ticket Linking

Overview

Add a generic bidirectional many-to-many linking system between Archer tickets, Jira tickets, and CVEs. Implementation spans three layers: a ticket_links PostgreSQL table with CHECK constraints and bidirectional indexes, a new Express router at backend/routes/links.js with POST/GET/DELETE endpoints behind requireAuth(), and a reusable LinkedItems React component mounted on Archer and Jira detail views. Entity type is auto-detected from key format. Audit logging on create/delete.

Tasks

  • 1. Database schema and migration

    • 1.1 Create migration file backend/migrations/add_ticket_links_table.js

      • Create ticket_links table with columns: id (SERIAL PK), source_type (TEXT NOT NULL), source_id (TEXT NOT NULL), target_type (TEXT NOT NULL), target_id (TEXT NOT NULL), relationship (TEXT NOT NULL DEFAULT 'related'), created_by (INTEGER REFERENCES users(id)), created_at (TIMESTAMPTZ DEFAULT NOW())
      • Add CHECK constraints: source_type IN ('archer', 'jira', 'cve'), target_type IN ('archer', 'jira', 'cve'), relationship IN ('related', 'spawned_by', 'blocks')
      • Add UNIQUE constraint on (source_type, source_id, target_type, target_id)
      • Add indexes: idx_ticket_links_source ON (source_type, source_id), idx_ticket_links_target ON (target_type, target_id)
      • Use CREATE TABLE IF NOT EXISTS and CREATE INDEX IF NOT EXISTS for idempotence
      • Use the PostgreSQL pool from backend/db.js (not SQLite)
      • Requirements: 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7
    • 1.2 Add ticket_links DDL to backend/db-schema.sql

      • Append the CREATE TABLE and CREATE INDEX statements to the existing schema file for documentation and fresh-install setup
      • Requirements: 1.1, 1.2, 1.3, 1.7
    • 1.3 Run migration against development database

      • Execute the migration script to create the table on the running Postgres instance
      • Verify table exists with correct constraints via a quick SELECT
      • Requirements: 1.1
  • 2. Checkpoint — Verify database schema

    • Ensure the ticket_links table exists with correct columns, constraints, and indexes. Ask the user if questions arise.
  • 3. Backend API route

    • 3.1 Create backend/routes/links.js with validation helpers

      • Define ENTITY_PATTERNS regex map: archer /^EXC-\d{4,}$/, jira /^[A-Z][A-Z0-9_]+-\d+$/, cve /^CVE-\d{4}-\d{4,}$/
      • Implement detectEntityType(key) function that returns 'cve', 'archer', 'jira', or null based on pattern matching
      • Implement validateEntityId(type, id) that checks the ID matches the pattern for the given type
      • Define VALID_RELATIONSHIPS array: ['related', 'spawned_by', 'blocks']
      • Requirements: 2.4, 2.5, 6.4
    • * 3.2 Write property test for entity type detection (Property 1)

      • Property 1: Entity Type Detection from Key Format
      • Generate random strings matching EXC-XXXX, PROJECT-XXXX, CVE-YYYY-NNNNN patterns and verify detectEntityType returns correct type; generate non-matching strings and verify null return
      • Validates: Requirements 6.4, 1.3, 2.4
    • * 3.3 Write property test for entity ID format validation (Property 2)

      • Property 2: Entity ID Format Validation
      • Generate (entity_type, entity_id) pairs with valid and invalid formats, verify validation accepts/rejects correctly
      • Validates: Requirements 2.5
    • 3.4 Implement POST /api/links endpoint

      • Require authentication via requireAuth()
      • Validate required fields: source_type, source_id, target_type, target_id
      • Validate entity types are in ['archer', 'jira', 'cve']
      • Validate entity ID formats match expected patterns for their types
      • Reject self-links (same type AND same ID) with 400
      • Check for duplicate links in both directions (A→B and B→A) with SELECT query, return 409 if exists
      • Insert new row into ticket_links with created_by from req.user.id
      • Log audit entry via logAudit() with action 'link_create'
      • Return 201 with created link record
      • Requirements: 2.1, 2.2, 2.3, 2.4, 2.5, 2.6, 2.7, 8.3
    • * 3.5 Write property test for self-link rejection (Property 3)

      • Property 3: Self-Link Rejection
      • Generate valid entities, attempt self-link creation, verify rejection with 400 status
      • Validates: Requirements 2.2
    • * 3.6 Write property test for bidirectional duplicate prevention (Property 4)

      • Property 4: Bidirectional Duplicate Prevention
      • Generate entity pairs, create link A→B, attempt A→B again and B→A, verify both return 409
      • Validates: Requirements 2.3, 8.3
    • 3.7 Implement GET /api/links endpoint

      • Require authentication via requireAuth()
      • Accept query params: type (entity type) and id (entity ID)
      • Validate type and id are provided
      • Execute bidirectional query: SELECT where (source_type=$1 AND source_id=$2) OR (target_type=$1 AND target_id=$2)
      • Transform results to return linked_type, linked_id (the "other" side from the queried entity), relationship, created_by_username (JOIN with users table), created_at
      • Return empty array when no links exist
      • Requirements: 3.1, 3.2, 3.3, 3.4, 8.1, 8.2
    • * 3.8 Write property test for bidirectional query completeness (Property 5)

      • Property 5: Bidirectional Query Completeness
      • Generate a set of links, query for a specific entity, verify result contains exactly the links where that entity appears on either side
      • Validates: Requirements 3.1, 8.1, 8.2
    • * 3.9 Write property test for link creation round-trip (Property 6)

      • Property 6: Link Creation Round-Trip
      • Generate valid link inputs (source ≠ target, no duplicate), create link, query for it, verify returned record matches input values
      • Validates: Requirements 2.1, 3.2
    • 3.10 Implement DELETE /api/links/:id endpoint

      • Require authentication via requireAuth()
      • Query the link by ID to verify it exists, return 404 if not found
      • Delete the row from ticket_links
      • Log audit entry via logAudit() with action 'link_delete', including source/target details
      • Return 200 with success message
      • Requirements: 4.1, 4.2, 4.3, 4.4
    • * 3.11 Write property test for delete removes link from queries (Property 7)

      • Property 7: Delete Removes Link from Queries
      • Generate links, delete one by ID, verify querying from either side no longer includes the deleted link
      • Validates: Requirements 4.1
    • 3.12 Mount links router in backend/server.js

      • Import createLinksRouter from ./routes/links
      • Add app.use('/api/links', createLinksRouter()) alongside existing route mounts
      • Requirements: 2.6, 3.4, 4.3
  • 4. Checkpoint — Verify backend API

    • Ensure POST/GET/DELETE endpoints work correctly, validation rejects bad input, duplicates return 409, bidirectional queries return links from both sides, and audit logs are created. Ask the user if questions arise.
  • 5. Frontend LinkedItems component

    • 5.1 Create frontend/src/components/LinkedItems.js

      • Accept props: entityType ('archer' | 'jira' | 'cve') and entityId (string)
      • Manage state: links array, loading boolean, showAddForm boolean, error string
      • On mount, fetch links from GET /api/links?type={entityType}&id={entityId}
      • Render "Linked Items" section header
      • Display empty state message when no links exist
      • Render each linked item with: entity type badge, entity ID as navigable link, relationship label
      • Display remove button (×) on each linked item
      • Requirements: 5.1, 5.2, 5.3, 5.4, 5.5, 7.1
    • 5.2 Implement "Add Link" form in LinkedItems component

      • "Add Link" button toggles form visibility
      • Form contains: text input for target entity key, dropdown for relationship type (related, spawned_by, blocks)
      • Auto-detect entity type from entered key using same regex patterns as backend
      • On submit, call POST /api/links with source (current entity) and target (entered key)
      • On success, refresh the links list
      • On error, display error message inline without clearing the form
      • Requirements: 6.1, 6.2, 6.3, 6.4
    • 5.3 Implement link removal in LinkedItems component

      • Remove button triggers confirmation prompt
      • On confirm, call DELETE /api/links/:id
      • On success, refresh the links list
      • On error, display error message to user
      • Requirements: 7.1, 7.2, 7.3, 7.4
  • 6. Integrate into existing views

    • 6.1 Mount LinkedItems on Archer ticket detail view

      • Import LinkedItems component
      • Render with entityType="archer" and entityId={excNumber} (the EXC-XXXX identifier)
      • Place in the detail panel below existing ticket information
      • Requirements: 5.1, 8.1, 8.2, 9.1
    • 6.2 Mount LinkedItems on Jira ticket detail/page view

      • Import LinkedItems component
      • Render with entityType="jira" and entityId={ticketKey} (the PROJECT-XXXX identifier)
      • Place in the detail view below existing ticket information
      • Requirements: 5.2, 8.1, 8.2, 9.1
  • 7. Checkpoint — Verify end-to-end flow

    • Ensure LinkedItems renders on both Archer and Jira detail views, links can be created/viewed/deleted from the UI, bidirectional visibility works (link created from Archer shows on Jira side), and entities with no links show empty state without errors. Ask the user if questions arise.
  • 8. Final checkpoint — Ensure all tests pass

    • Ensure all tests pass, audit log entries are created on link create/delete, and the feature is fully functional. Ask the user if questions arise.

Notes

  • Tasks marked with * are optional and can be skipped for faster MVP
  • Each task references specific requirements for traceability
  • Checkpoints ensure incremental validation between major phases
  • Property tests validate universal correctness properties from the design document
  • The design uses JavaScript — all code examples and implementations use Node.js/Express/React
  • Entity IDs are text-based (EXC-6056, STEAM-1234, CVE-2025-0905) — not integer PKs
  • The existing cve_id column on archer/jira tickets remains unchanged (backward compatible)
  • Links are purely additive — they supplement the primary CVE relationship, not replace it
  • Auto-detection of entity type from ID format reduces user friction
  • Duplicate prevention checks both directions (A→B and B→A) since links are logically undirected
  • The UNIQUE constraint on the table acts as a final safety net for concurrent duplicate inserts