feat: implement finding archive tracking system
- Add migration script for ivanti_finding_archives and ivanti_archive_transitions tables - Add archive detection logic (detectArchiveChanges, detectClosedFindings) in sync pipeline - Add archive API router with list, stats, and history endpoints at /api/ivanti/archive - Add ArchiveSummaryBar UI component with four state cards (ACTIVE, ARCHIVED, RETURNED, CLOSED) - Integrate ArchiveSummaryBar into Ivanti findings page in App.js - Register archive router in server.js
This commit is contained in:
@@ -122,7 +122,7 @@ function ArchiveSummaryBar({ onStateClick, activeFilter }) { ... }
|
||||
| Endpoint | Method | Auth | Query Params | Response |
|
||||
|----------|--------|------|-------------|----------|
|
||||
| `/api/ivanti/archive` | GET | Required | `state` (optional: ACTIVE, ARCHIVED, RETURNED, CLOSED) | `{ archives: [...], total: N }` |
|
||||
| `/api/ivanti/archive/stats` | GET | Required | None | `{ ACTIVE: N, ARCHIVED: N, RETURNED: N, CLOSED: N, total: N }` |
|
||||
| `/api/ivanti/archive/stats` | GET | Required | None | `{ ARCHIVED: N, RETURNED: N, CLOSED: N, total: N }` |
|
||||
| `/api/ivanti/archive/:findingId/history` | GET | Required | None | `{ finding_id: "...", transitions: [...] }` |
|
||||
|
||||
## Data Models
|
||||
@@ -136,7 +136,7 @@ function ArchiveSummaryBar({ onStateClick, activeFilter }) { ... }
|
||||
| `finding_title` | TEXT | NOT NULL DEFAULT '' | Finding title at time of archival |
|
||||
| `host_name` | TEXT | NOT NULL DEFAULT '' | Host name at time of archival |
|
||||
| `ip_address` | TEXT | NOT NULL DEFAULT '' | IP address at time of archival |
|
||||
| `current_state` | TEXT | NOT NULL CHECK(IN ('ACTIVE','ARCHIVED','RETURNED','CLOSED')) | Current lifecycle state |
|
||||
| `current_state` | TEXT | NOT NULL CHECK(IN ('ARCHIVED','RETURNED','CLOSED')) | Current lifecycle state |
|
||||
| `last_severity` | REAL | NOT NULL DEFAULT 0 | Last known severity score |
|
||||
| `first_archived_at` | DATETIME | NOT NULL DEFAULT CURRENT_TIMESTAMP | When first archived |
|
||||
| `last_transition_at` | DATETIME | NOT NULL DEFAULT CURRENT_TIMESTAMP | When last state change occurred |
|
||||
@@ -163,10 +163,11 @@ function ArchiveSummaryBar({ onStateClick, activeFilter }) { ... }
|
||||
|
||||
### State Transition Diagram
|
||||
|
||||
Archive records are only created when a finding first disappears from sync results. Findings that remain present in sync results do not get archive records — they are simply "active" in the findings cache. The three database states are ARCHIVED, RETURNED, and CLOSED.
|
||||
|
||||
```mermaid
|
||||
stateDiagram-v2
|
||||
[*] --> ACTIVE : Finding present in sync
|
||||
ACTIVE --> ARCHIVED : Disappeared from sync (score drift)
|
||||
[*] --> ARCHIVED : Finding disappears from sync (score drift)
|
||||
ARCHIVED --> RETURNED : Reappeared in sync
|
||||
ARCHIVED --> CLOSED : Confirmed remediated in Ivanti
|
||||
RETURNED --> ARCHIVED : Disappeared again
|
||||
@@ -177,8 +178,7 @@ stateDiagram-v2
|
||||
|
||||
| From State | To State | Reason |
|
||||
|-----------|----------|--------|
|
||||
| NONE | ACTIVE | `initial_sync` |
|
||||
| ACTIVE → | ARCHIVED | `severity_score_drift` |
|
||||
| NONE → | ARCHIVED | `severity_score_drift` (first disappearance) |
|
||||
| ARCHIVED → | RETURNED | `reappeared_in_sync` |
|
||||
| ARCHIVED → | CLOSED | `remediated_in_ivanti` |
|
||||
| RETURNED → | ARCHIVED | `severity_score_drift` |
|
||||
@@ -252,7 +252,7 @@ stateDiagram-v2
|
||||
| Database error during transition insert | Log the error. The archive record state may have been updated but the transition history may be incomplete. This is acceptable as the current state is the source of truth. |
|
||||
| Invalid state transition attempted | The detection logic only performs valid transitions per the state diagram. Invalid transitions (e.g., CLOSED → ARCHIVED) are not possible by design since closed findings are excluded from the sync pipeline. |
|
||||
| Missing finding metadata | Use empty string defaults for finding_title, host_name, ip_address if the finding object lacks these fields. Severity defaults to 0. |
|
||||
| Archive API query with invalid state parameter | Return all records (ignore the filter) rather than returning an error, for resilience. |
|
||||
| Archive API query with invalid state parameter | Return a 400 status code with message "Invalid state parameter. Valid values: ACTIVE, ARCHIVED, RETURNED, CLOSED". Explicit errors surface frontend bugs faster than silent fallbacks. |
|
||||
| History query for non-existent finding | Return 200 with empty transitions array (not 404), per requirement 4.5. |
|
||||
|
||||
## Testing Strategy
|
||||
|
||||
Reference in New Issue
Block a user