135 lines
8.3 KiB
Markdown
135 lines
8.3 KiB
Markdown
# Implementation Plan: Finding Archive Tracking
|
|
|
|
## Overview
|
|
|
|
Implement the Finding Archive Tracking system by creating the database migration, archive detection logic within the existing sync pipeline, three API endpoints via a new route module, and an Archive Summary Bar UI component. Each task builds incrementally — schema first, then detection logic, then API, then frontend.
|
|
|
|
## Tasks
|
|
|
|
- [ ] 1. Create database migration and archive tables
|
|
- [ ] 1.1 Create `backend/migrations/add_finding_archive_tables.js` migration script
|
|
- Create `ivanti_finding_archives` table with columns: id, finding_id (UNIQUE), finding_title, host_name, ip_address, current_state (CHECK constraint for ACTIVE/ARCHIVED/RETURNED/CLOSED), last_severity, first_archived_at, last_transition_at, created_at
|
|
- Create `ivanti_archive_transitions` table with columns: id, archive_id (FK), from_state, to_state, severity_at_transition, reason, transitioned_at
|
|
- Create indexes: idx_archive_finding_id, idx_archive_current_state, idx_transition_archive_id
|
|
- Use `CREATE TABLE IF NOT EXISTS` and `CREATE INDEX IF NOT EXISTS` for idempotency
|
|
- Follow existing migration pattern: open db, `db.serialize()`, log progress, close db
|
|
- _Requirements: 3.1, 3.2, 3.3, 3.4, 6.1, 6.2, 6.3_
|
|
|
|
- [ ]* 1.2 Write property test for migration idempotency
|
|
- **Property 9: Migration idempotency**
|
|
- Run migration logic multiple times against in-memory SQLite, verify no errors and schema is consistent
|
|
- **Validates: Requirements 6.2**
|
|
|
|
- [ ] 2. Implement archive detection logic in sync pipeline
|
|
- [ ] 2.1 Add `initArchiveTables(db)` function to `backend/routes/ivantiFindings.js`
|
|
- Create both archive tables inline (same pattern as existing `initTables`) so they exist on startup
|
|
- Call from `createIvantiFindingsRouter` during init alongside existing `initTables`
|
|
- _Requirements: 3.1, 3.2, 3.3, 3.4_
|
|
|
|
- [ ] 2.2 Implement `detectArchiveChanges(db, previousFindings, currentFindings)` function
|
|
- Build ID sets from previous and current findings
|
|
- For disappeared findings (in previous, not in current): upsert archive record with state ARCHIVED, insert transition history
|
|
- For returned findings (in current, has ARCHIVED record): update to RETURNED, insert transition history
|
|
- For re-disappeared findings (has RETURNED record, not in current): update to ARCHIVED, insert transition history
|
|
- Use `db.run` with callbacks wrapped in promises (matching existing `dbRun` helper pattern)
|
|
- _Requirements: 1.1, 1.2, 1.3, 1.4, 2.1, 2.2_
|
|
|
|
- [ ] 2.3 Implement `detectClosedFindings(db, closedFindingIds)` function
|
|
- Query archive records with state ARCHIVED or RETURNED
|
|
- For any that appear in the closed findings set, update to CLOSED with reason "remediated_in_ivanti"
|
|
- Insert transition history for each state change
|
|
- _Requirements: 2.3_
|
|
|
|
- [ ] 2.4 Integrate archive detection into `syncFindings()` flow
|
|
- Before updating the cache, read the current findings from `ivanti_findings_cache` as `previousFindings`
|
|
- After successful cache update, call `detectArchiveChanges(db, previousFindings, currentFindings)`
|
|
- Skip archive detection if sync encountered an error (requirement 1.5)
|
|
- Call `detectClosedFindings` during `syncClosedCount` with closed finding IDs
|
|
- _Requirements: 1.1, 1.5, 2.3_
|
|
|
|
- [ ]* 2.5 Write property test for archive detection — disappeared findings
|
|
- **Property 1: Disappeared findings are archived with complete metadata**
|
|
- Generate random previous/current finding sets using fast-check, run detectArchiveChanges against in-memory SQLite, verify all disappeared findings have ARCHIVED records with correct metadata
|
|
- **Validates: Requirements 1.1, 1.2, 2.2**
|
|
|
|
- [ ]* 2.6 Write property test for archive detection — returned findings
|
|
- **Property 2: Returned findings transition from ARCHIVED to RETURNED**
|
|
- Generate archived findings, add some back to current set, verify RETURNED state and updated severity
|
|
- **Validates: Requirements 1.3**
|
|
|
|
- [ ]* 2.7 Write property test for archive detection — re-disappeared findings
|
|
- **Property 3: Re-disappeared findings transition from RETURNED to ARCHIVED**
|
|
- Generate returned findings, remove some from current set, verify ARCHIVED state
|
|
- **Validates: Requirements 1.4**
|
|
|
|
- [ ]* 2.8 Write property test for transition history completeness
|
|
- **Property 4: Every state transition produces a history record with all required fields**
|
|
- Generate random state transitions, verify each produces a complete history row with archive_id, from_state, to_state, severity_at_transition, reason, transitioned_at
|
|
- **Validates: Requirements 2.1**
|
|
|
|
- [ ]* 2.9 Write property test for closed finding detection
|
|
- **Property 5: Closed findings transition to CLOSED state**
|
|
- Generate archived/returned findings, mark some as closed, verify CLOSED state and reason "remediated_in_ivanti"
|
|
- **Validates: Requirements 2.3**
|
|
|
|
- [ ] 3. Checkpoint — Verify archive detection logic
|
|
- Ensure all tests pass, ask the user if questions arise.
|
|
|
|
- [ ] 4. Implement Archive API endpoints
|
|
- [ ] 4.1 Create `backend/routes/ivantiArchive.js` route module
|
|
- Export factory function `createIvantiArchiveRouter(db, requireAuth)` returning Express Router
|
|
- Apply `requireAuth(db)` middleware to all routes
|
|
- Implement GET `/` — list archive records with optional `?state=` filter, return `{ archives: [...], total: N }`
|
|
- Implement GET `/stats` — return `{ ACTIVE: N, ARCHIVED: N, RETURNED: N, CLOSED: N, total: N }`
|
|
- Implement GET `/:findingId/history` — return `{ finding_id, transitions: [...] }` ordered by transitioned_at DESC, return empty array for unknown finding_id
|
|
- _Requirements: 4.1, 4.2, 4.3, 4.4, 4.5_
|
|
|
|
- [ ] 4.2 Register archive router in `backend/server.js`
|
|
- Import `createIvantiArchiveRouter` from `./routes/ivantiArchive`
|
|
- Mount at `/api/ivanti/archive` with `requireAuth` middleware
|
|
- _Requirements: 4.1_
|
|
|
|
- [ ]* 4.3 Write property test for state filtering
|
|
- **Property 6: State filter returns only matching records**
|
|
- Generate archive records with random states, query with filter, verify only matching records returned
|
|
- **Validates: Requirements 4.1**
|
|
|
|
- [ ]* 4.4 Write property test for history ordering
|
|
- **Property 7: Transition history is ordered by timestamp descending**
|
|
- Generate multiple transitions for a finding, query history, verify descending timestamp order
|
|
- **Validates: Requirements 4.2**
|
|
|
|
- [ ]* 4.5 Write property test for stats accuracy
|
|
- **Property 8: Stats counts match actual record distribution**
|
|
- Generate archive records with random states, query stats, verify counts match actual distribution
|
|
- **Validates: Requirements 4.3**
|
|
|
|
- [ ] 5. Checkpoint — Verify API endpoints
|
|
- Ensure all tests pass, ask the user if questions arise.
|
|
|
|
- [ ] 6. Implement Archive Summary Bar UI component
|
|
- [ ] 6.1 Create `frontend/src/components/pages/ArchiveSummaryBar.js`
|
|
- Fetch stats from `/api/ivanti/archive/stats` on mount
|
|
- Render four stat cards: ACTIVE (sky blue #0EA5E9), ARCHIVED (amber #F59E0B), RETURNED (emerald #10B981), CLOSED (red #EF4444)
|
|
- Each card shows the count and state label with Lucide icons and monospace typography
|
|
- Accept `onStateClick` callback prop and `activeFilter` prop for highlighting the selected state
|
|
- Use inline style objects matching the existing design system (dark gradients, glows, hover effects)
|
|
- _Requirements: 5.1, 5.2, 5.3, 5.4, 5.5_
|
|
|
|
- [ ] 6.2 Integrate Archive Summary Bar into the Ivanti findings page
|
|
- Import and render `ArchiveSummaryBar` in the Ivanti findings section of `App.js` (or the relevant page component)
|
|
- Wire `onStateClick` to manage a state filter for the archive list display
|
|
- _Requirements: 5.3_
|
|
|
|
- [ ] 7. Final checkpoint — Verify full integration
|
|
- Ensure all tests pass, 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
|
|
- Property tests use `fast-check` library with minimum 100 iterations per test
|
|
- All backend code uses callback-based SQLite API wrapped in promises (matching existing patterns)
|
|
- All frontend code uses plain JavaScript (no TypeScript)
|