feat: add return classification for archive chart, CARD API integration, compliance charts, systemd services

This commit is contained in:
root
2026-05-01 17:15:41 +00:00
parent 8df961cce8
commit 15abf8bae4
21 changed files with 3639 additions and 210 deletions

View File

@@ -0,0 +1,339 @@
# Design Document: CARD API Integration
## Overview
This design integrates the CARD Asset Ownership API into the STEAM Security Dashboard, enabling users to confirm, decline, and redirect CARD assets directly from the Ivanti Queue. The integration follows the existing architectural patterns established by the Atlas API integration (`atlasApi.js` / `atlas.js` route), adding OAuth Bearer token management with automatic caching and refresh.
The implementation is split into three layers:
1. **Helper module** (`backend/helpers/cardApi.js`) — already built and UAT-tested. Handles HTTP transport, OAuth token lifecycle, and high-level CARD API wrappers.
2. **Route module** (`backend/routes/cardApi.js`) — new Express router that proxies CARD operations, validates queue items, orchestrates the two-step update_token flow, and logs audit entries.
3. **Frontend UI** — CARD action buttons (Confirm, Decline, Redirect) on queue items, team selection dropdowns, and an asset search panel.
### Key Findings from UAT Testing
- Token endpoint is `POST /api/v1/auth/get_token` (not GET)
- Team name field in API responses is `card_team_name` or `_id`
- The `update_token` is nested at `owner.update_token` in the owner record
- The assets endpoint **requires** a `disposition` query parameter (returns 500 without it)
- The helper module and UAT test script are already built and validated
## Architecture
```mermaid
graph TD
subgraph Frontend
QP[Ivanti Queue Panel] --> AB[CARD Action Buttons]
AB --> CF[Confirm Form]
AB --> DF[Decline Form]
AB --> RF[Redirect Form]
QP --> AS[Asset Search Panel]
end
subgraph Backend
CR[cardApi Route<br>/api/card/*] --> CM[cardApi Helper]
CR --> DB[(SQLite DB<br>ivanti_todo_queue)]
CR --> AL[Audit Logger]
CM --> TM[Token Manager]
end
subgraph External
CARD[CARD API<br>card.charter.com]
end
CF --> CR
DF --> CR
RF --> CR
AS --> CR
CM --> CARD
TM --> CARD
```
### Request Flow for Mutations (Confirm/Decline/Redirect)
```mermaid
sequenceDiagram
participant UI as Frontend
participant Route as cardApi Route
participant DB as SQLite
participant Helper as cardApi Helper
participant CARD as CARD API
UI->>Route: POST /api/card/queue/:id/confirm
Route->>DB: Validate queue item (exists, user, CARD, pending)
DB-->>Route: Queue item record
Route->>Helper: getOwner(assetId)
Helper->>CARD: GET /api/v1/owner/{assetId}
CARD-->>Helper: Owner record with update_token
Helper-->>Route: { owner: { update_token: "..." } }
Route->>Helper: confirmAsset(assetId, team, token, comment)
Helper->>CARD: POST /api/v2/owner/{assetId}/confirm?update_token=...
CARD-->>Helper: Success response
Helper-->>Route: { status: 200, body: "..." }
Route->>DB: UPDATE status = 'complete'
Route->>AL: logAudit(card_confirm, ...)
Route-->>UI: { success: true, cardResponse: ... }
```
## Components and Interfaces
### 1. CARD API Helper (`backend/helpers/cardApi.js`) — Already Built
The helper module is complete and UAT-tested. It exports:
| Export | Type | Description |
|--------|------|-------------|
| `isConfigured` | `boolean` | `true` when `CARD_API_URL`, `CARD_API_USER`, `CARD_API_PASS` are all set |
| `missingVars` | `string[]` | List of missing env var names |
| `cardGet(urlPath, options)` | `function` | GET request with Bearer auth, returns `{ status, body }` |
| `cardPost(urlPath, body, options)` | `function` | POST request with Bearer auth, returns `{ status, body }` |
| `getTeams()` | `function` | `GET /api/v1/teams` — returns `{ status, body, ok }` |
| `getTeamAssets(teamName, opts)` | `function` | `GET /api/v1/team/{name}/assets` with disposition, page, pageSize |
| `getOwner(assetId)` | `function` | `GET /api/v1/owner/{assetId}` — returns owner record with `update_token` |
| `confirmAsset(assetId, team, token, comment)` | `function` | `POST /api/v2/owner/{id}/confirm` |
| `declineAsset(assetId, team, token, comment)` | `function` | `POST /api/v2/owner/{id}/decline` |
| `redirectAsset(assetId, from, to, token)` | `function` | `POST /api/v2/owner/{id}/{from}/redirect` |
| `invalidateToken()` | `function` | Clears cached Bearer token |
| `testConnection()` | `function` | Acquires token and returns `{ ok, token }` or `{ ok, error }` |
**Token Manager** (internal to helper):
- Acquires tokens via `POST /api/v1/auth/get_token` with Basic Auth
- Caches in memory with 1-hour TTL, refreshes when within 60s of expiry
- Automatically retries once on HTTP 401 (invalidate → re-acquire → retry)
### 2. CARD API Route (`backend/routes/cardApi.js`) — New
Factory function: `createCardApiRouter(db, requireAuth)` → Express Router
**Endpoints:**
| Method | Path | Description |
|--------|------|-------------|
| GET | `/api/card/status` | Returns `{ configured: boolean }` |
| GET | `/api/card/teams` | Proxies CARD teams list |
| GET | `/api/card/teams/:teamName/assets` | Proxies team assets with `disposition` (required), `page`, `page_size` (default 50) |
| GET | `/api/card/owner/:assetId` | Proxies owner record lookup |
| POST | `/api/card/queue/:queueItemId/confirm` | Confirm asset — body: `{ teamName, assetId, comment? }` |
| POST | `/api/card/queue/:queueItemId/decline` | Decline asset — body: `{ teamName, assetId, comment? }` |
| POST | `/api/card/queue/:queueItemId/redirect` | Redirect asset — body: `{ fromTeam, toTeam, assetId }` |
**Middleware:** All endpoints use `requireAuth(db)` + `requireGroup('Admin', 'Standard_User')`.
**Mutation flow** (confirm/decline/redirect):
1. Validate queue item: exists, belongs to `req.user.id`, `workflow_type = 'CARD'`, `status = 'pending'`
2. Fetch owner record via `getOwner(assetId)` to get fresh `update_token`
3. Extract `update_token` from `owner.update_token` (nested path)
4. Execute CARD mutation with the `update_token`
5. On success: update queue item `status = 'complete'`, log audit, return response
6. On failure: leave queue item as `pending`, log audit failure, return error
### 3. Frontend Components
**Modified:** Ivanti Queue panel in the existing queue UI
**New UI elements:**
- **CARD Action Buttons**: Confirm, Decline, Redirect buttons rendered on pending CARD queue items
- **Confirm/Decline Form**: Team dropdown (from `/api/card/teams`) + optional comment field
- **Redirect Form**: From Team dropdown + To Team dropdown
- **Asset Search Panel**: Team dropdown + disposition filter + paginated results table
- **Loading/Error States**: Inline loading indicators and error messages per queue item
**Session-level caching:** Teams list fetched once per browser session and reused across all forms.
### 4. Server Integration (`backend/server.js`)
Mount the new route:
```javascript
const createCardApiRouter = require('./routes/cardApi');
// ...
app.use('/api/card', createCardApiRouter(db, requireAuth));
```
## Data Models
### Existing: `ivanti_todo_queue` Table
No schema changes required. CARD items use `workflow_type = 'CARD'`.
| Column | Type | Notes |
|--------|------|-------|
| `id` | INTEGER | Primary key |
| `user_id` | INTEGER | FK to users |
| `finding_id` | TEXT | Ivanti finding identifier |
| `finding_title` | TEXT | Finding description |
| `cves_json` | TEXT | JSON array of CVE IDs |
| `ip_address` | TEXT | Asset IP address (used as CARD Asset_ID with suffix) |
| `hostname` | TEXT | Asset hostname |
| `vendor` | TEXT | Empty string for CARD items |
| `workflow_type` | TEXT | `'CARD'` for this integration |
| `status` | TEXT | `'pending'` or `'complete'` |
| `created_at` | DATETIME | Auto-set |
| `updated_at` | DATETIME | Auto-updated |
### CARD API Response Shapes (from UAT testing)
**Teams response** (`GET /api/v1/teams`):
```json
[
{ "_id": "NTS-AEO-STEAM", "card_team_name": "NTS-AEO-STEAM", ... },
{ "_id": "NTS-ACCESS-ENG", "card_team_name": "NTS-ACCESS-ENG", ... }
]
```
Team name extraction: `t.card_team_name || t._id`
**Owner record** (`GET /api/v1/owner/{assetId}`):
```json
{
"owner": {
"update_token": "abc123...",
"dispositions": [
{ "team": "NTS-AEO-STEAM", "disposition": "confirmed", ... }
],
...
}
}
```
Update token path: `response.owner.update_token`
**Team assets** (`GET /api/v1/team/{name}/assets?disposition=confirmed&page_size=50`):
```json
{
"assets": [ { "asset_id": "98.8.142.56-NATL", ... } ],
"total": 150,
"page": 1,
"page_size": 50
}
```
### Audit Log Entries
| Action | entityType | entityId | Details |
|--------|-----------|----------|---------|
| `card_confirm` | `ivanti_todo_queue` | queue item ID | `{ assetId, teamName, comment, cardStatus }` |
| `card_decline` | `ivanti_todo_queue` | queue item ID | `{ assetId, teamName, comment, cardStatus }` |
| `card_redirect` | `ivanti_todo_queue` | queue item ID | `{ assetId, fromTeam, toTeam, cardStatus }` |
| `card_search` | `card_asset` | team name | `{ disposition, resultCount }` |
| `card_action_failed` | `ivanti_todo_queue` | queue item ID | `{ actionType, assetId, error, cardStatus }` |
## 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: isConfigured reflects environment variable presence
*For any* combination of the three required environment variables (`CARD_API_URL`, `CARD_API_USER`, `CARD_API_PASS`) being present/non-empty or absent/empty, `isConfigured` SHALL be `true` if and only if all three are present and non-empty.
**Validates: Requirements 1.1, 3.3**
### Property 2: All CARD API responses have consistent shape
*For any* successful CARD API call (any HTTP method and URL path), the resolved Promise SHALL contain an object with a numeric `status` field and a string `body` field.
**Validates: Requirements 1.7**
### Property 3: Token acquisition errors include status and body
*For any* non-success HTTP status code and response body returned by the token acquisition endpoint, the rejected Promise error message SHALL include both the HTTP status code and the response body text.
**Validates: Requirements 2.7**
### Property 4: CARD API error status codes are forwarded through proxy
*For any* HTTP error status code (4xx or 5xx) returned by the CARD API on a proxied request, the route SHALL return that same status code to the client along with a JSON error body containing the upstream error message.
**Validates: Requirements 4.9**
### Property 5: Queue item validation rejects invalid states for mutations
*For any* CARD mutation request (confirm, decline, or redirect), if the referenced queue item does not exist, does not belong to the requesting user, has `workflow_type` other than `'CARD'`, or has `status` other than `'pending'`, the endpoint SHALL reject the request with the appropriate HTTP error code (404 or 400) without calling the CARD API.
**Validates: Requirements 5.4, 5.5, 5.6**
### Property 6: Mutation input validation enforces required fields
*For any* CARD mutation request, the endpoint SHALL reject with HTTP 400 if any required field is missing or empty: `teamName` and `assetId` for confirm/decline; `fromTeam`, `toTeam`, and `assetId` for redirect. Optional fields (e.g., `comment`) SHALL be accepted when absent.
**Validates: Requirements 5.11, 5.12, 5.13**
### Property 7: CARD mutation audit entries contain required fields
*For any* CARD mutation action (confirm, decline, or redirect) executed through the dashboard, the audit log entry SHALL contain the correct `action` name (`card_confirm`, `card_decline`, or `card_redirect`), `entityType` of `'ivanti_todo_queue'`, the queue item ID as `entityId`, the requesting user's `userId`, `username`, and `ipAddress`, and a `details` object containing the `assetId` and CARD API response status.
**Validates: Requirements 9.1, 9.2, 9.3, 9.6**
## Error Handling
### CARD Helper Error Handling
| Scenario | Behavior |
|----------|----------|
| CARD API unreachable / timeout | Reject with `[card-api] {METHOD} {path} failed: {reason}` |
| Token endpoint returns non-2xx | Reject with `[card-api] Token acquisition failed with HTTP {status}: {body}` |
| Token endpoint returns unparseable JSON | Fall back to raw body as token string; reject if empty |
| Token endpoint returns empty token | Reject with `[card-api] Token parse failure: empty token in response body.` |
| Non-auth request returns 401 | Invalidate token, re-acquire, retry once. If retry also 401, return the 401 |
### Route Error Handling
| CARD API Status | Route Response | Error Message |
|----------------|----------------|---------------|
| 401 (token endpoint) | 401 | `CARD authorization failed. Check service account credentials.` |
| 403 (token endpoint) | 403 | `CARD access denied. The service account may not be onboarded with the CARD team.` |
| 525 (token endpoint) | 502 | `CARD LDAP error. The service account may not be provisioned correctly.` |
| 401 (API call, after retry) | 401 | `CARD token expired or invalid. The request has been retried once automatically.` |
| 403 (API call) | 403 | `Insufficient CARD permissions for this operation.` |
| Any unhandled error | 502 | `CARD API request failed.` + details |
| Not configured | 503 | `CARD API is not configured.` + missing vars |
All errors are logged to console with `[card-api]` prefix for consistent log filtering.
### Frontend Error Handling
- Inline error messages on the affected queue item (no modal popups)
- Loading state disables action buttons to prevent double-submission
- Network errors display a generic "Unable to reach server" message
- CARD-specific errors display the backend error message verbatim
## Testing Strategy
### Property-Based Tests (fast-check)
The project uses Jest as the test runner. Property-based tests will use [fast-check](https://github.com/dubzzz/fast-check) with a minimum of 100 iterations per property.
Each property test references its design document property:
| Property | Test File | What It Validates |
|----------|-----------|-------------------|
| Property 1: isConfigured | `backend/__tests__/card-isConfigured.property.test.js` | Env var combinations → isConfigured correctness |
| Property 2: Response shape | `backend/__tests__/card-response-shape.property.test.js` | All API responses have { status, body } |
| Property 3: Token error messages | `backend/__tests__/card-token-errors.property.test.js` | Error messages include status + body |
| Property 4: Error forwarding | `backend/__tests__/card-error-forwarding.property.test.js` | Proxy forwards CARD error status codes |
| Property 5: Queue validation | `backend/__tests__/card-queue-validation.property.test.js` | Invalid queue states rejected correctly |
| Property 6: Input validation | `backend/__tests__/card-input-validation.property.test.js` | Required fields enforced on mutations |
| Property 7: Audit entries | `backend/__tests__/card-audit-entries.property.test.js` | Mutation audit logs have correct shape |
Tag format: `Feature: card-api-integration, Property {N}: {title}`
### Unit Tests (example-based)
- Token acquisition flow (mock HTTP, verify Basic Auth header)
- Token caching and refresh timing
- 401 retry logic (mock 401 → 200 sequence)
- Two-step update_token flow (getOwner → mutation)
- Specific CARD API endpoint URL construction
- Default page_size=50 on assets endpoint
- TLS skip configuration
### Integration Tests
- Route mounting at `/api/card` prefix
- Auth middleware enforcement (401 without session)
- End-to-end confirm/decline/redirect with mocked CARD API
- Asset search with pagination
### Frontend Tests
- CARD action buttons render only on pending CARD items
- Form submission sends correct request body
- Loading state disables buttons
- Error messages display inline
- Teams list caching (single fetch per session)

View File

@@ -0,0 +1,165 @@
# Implementation Plan: CARD API Integration
## Overview
This plan covers the remaining implementation work for the CARD API integration into the STEAM Security Dashboard. The CARD API helper module (`backend/helpers/cardApi.js`), environment variable configuration, and UAT test script are already built and validated. The remaining work focuses on the backend route module, server mounting, frontend CARD action UI, asset search panel, and property-based tests.
## Tasks
- [x] 1. CARD API Helper Module (Already Complete)
- `backend/helpers/cardApi.js` is built and UAT-tested with all exports: `isConfigured`, `cardGet`, `cardPost`, `getTeams`, `getTeamAssets`, `getOwner`, `confirmAsset`, `declineAsset`, `redirectAsset`, `invalidateToken`, `testConnection`
- Token Manager handles OAuth Bearer token acquisition, 1-hour TTL caching, 60s refresh window, and automatic 401 retry
- _Requirements: 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 2.1, 2.2, 2.3, 2.4, 2.5, 2.6, 2.7_
- [x] 2. Environment Variable Configuration (Already Complete)
- `backend/.env` and `backend/.env.example` have `CARD_API_URL`, `CARD_API_USER`, `CARD_API_PASS`, `CARD_SKIP_TLS` configured with descriptive comments
- _Requirements: 3.1, 3.2, 3.3, 3.4_
- [x] 3. UAT Test Script (Already Complete)
- `backend/scripts/card-uat-test.js` exercises all 9 CARD API use cases and passes
- _Requirements: 1.1, 1.7, 2.1, 2.3, 2.6_
- [x] 4. Backend CARD API Route Module
- [x] 4.1 Create `backend/routes/cardApi.js` with factory function `createCardApiRouter(db, requireAuth)`
- Follow the existing `atlas.js` route pattern: import `requireGroup` from middleware, import `logAudit` from helpers, import CARD helper functions from `helpers/cardApi.js`
- Add promise-based DB helpers (`dbRun`, `dbGet`) matching the atlas.js pattern
- Protect all endpoints with `requireAuth(db)` and `requireGroup('Admin', 'Standard_User')`
- _Requirements: 4.1, 4.2_
- [x] 4.2 Implement read-only proxy endpoints
- `GET /status` — return `{ configured: isConfigured }`; if not configured, return 503 with missing vars
- `GET /teams` — proxy `getTeams()`, parse JSON response, forward to client
- `GET /teams/:teamName/assets` — proxy `getTeamAssets()` with `disposition` (required), `page`, `page_size` (default 50) query params
- `GET /owner/:assetId` — proxy `getOwner()`, return owner record
- All proxy endpoints: return 503 if not configured, forward CARD API error status codes with JSON error body
- _Requirements: 4.3, 4.4, 4.5, 4.6, 4.7, 4.8, 4.9_
- [x] 4.3 Implement mutation endpoints (confirm, decline, redirect) with two-step update_token flow
- `POST /queue/:queueItemId/confirm` — body: `{ teamName, assetId, comment? }`
- `POST /queue/:queueItemId/decline` — body: `{ teamName, assetId, comment? }`
- `POST /queue/:queueItemId/redirect` — body: `{ fromTeam, toTeam, assetId }`
- Validate queue item: exists, belongs to `req.user.id`, `workflow_type = 'CARD'`, `status = 'pending'`; return 404 if not found/wrong user/wrong type, 400 if not pending
- Validate required fields: `teamName` + `assetId` for confirm/decline; `fromTeam` + `toTeam` + `assetId` for redirect; return 400 if missing
- Two-step flow: call `getOwner(assetId)` → extract `update_token` from `owner.update_token` → call mutation with token
- On success: update queue item `status = 'complete'`, return `{ success: true, cardResponse }`
- On failure: leave queue item as `pending`, return error
- _Requirements: 5.1, 5.2, 5.3, 5.4, 5.5, 5.6, 5.7, 5.8, 5.9, 5.10, 5.11, 5.12, 5.13, 5.14, 5.15_
- [x] 4.4 Implement error handling for CARD API responses
- Map token endpoint errors: 401 → 401 auth failed, 403 → 403 access denied, 525 → 502 LDAP error
- Map API call errors: 401 after retry → 401 token expired, 403 → 403 insufficient permissions
- Catch unhandled errors → 502 with `{ error: 'CARD API request failed.', details }`
- Log all errors with `[card-api]` prefix via `console.error`
- _Requirements: 8.1, 8.2, 8.3, 8.4, 8.5, 8.6, 8.7, 8.8, 8.9, 8.10_
- [x] 4.5 Implement audit logging for all CARD actions
- `card_confirm`: entityType `ivanti_todo_queue`, entityId = queue item ID, details = `{ assetId, teamName, comment, cardStatus }`
- `card_decline`: same pattern with decline details
- `card_redirect`: details = `{ assetId, fromTeam, toTeam, cardStatus }`
- `card_search`: entityType `card_asset`, entityId = team name, details = `{ disposition, resultCount }`
- `card_action_failed`: details = `{ actionType, assetId, error, cardStatus }`
- All entries include `userId`, `username`, `ipAddress`; use fire-and-forget semantics
- _Requirements: 9.1, 9.2, 9.3, 9.4, 9.5, 9.6, 9.7_
- [x] 5. Mount CARD route in server.js
- [x] 5.1 Add `const createCardApiRouter = require('./routes/cardApi');` import to server.js alongside existing route imports
- Mount with `app.use('/api/card', createCardApiRouter(db, requireAuth));` after the Atlas route mount
- _Requirements: 4.10_
- [x] 6. Checkpoint — Backend route verification
- Ensure all tests pass, ask the user if questions arise.
- [x] 7. Frontend CARD Action UI
- [x] 7.1 Add CARD teams fetch and session-level caching to ReportingPage
- Fetch `/api/card/status` on mount to check if CARD is configured
- Fetch `/api/card/teams` once per session and cache in component state
- Pass `cardConfigured`, `cardTeams` props down to `QueuePanel`
- _Requirements: 6.8, 6.9_
- [x] 7.2 Add CARD action buttons (Confirm, Decline, Redirect) to queue items in QueuePanel
- Render three action buttons on pending CARD queue items (`workflow_type === 'CARD'` and `status === 'pending'`)
- Disable buttons when CARD is not configured; show tooltip "CARD integration not configured"
- Style buttons to match existing queue item action patterns (compact, inline)
- _Requirements: 6.1, 6.8_
- [x] 7.3 Implement Confirm and Decline action forms
- On Confirm/Decline button click: show inline form with team selection dropdown (populated from cached teams list) and optional comment text field
- On form submit: POST to `/api/card/queue/:queueItemId/confirm` or `/decline` with `{ teamName, assetId: item.ip_address, comment }`
- While request is in flight: disable action buttons, show loading indicator on the queue item
- On success: update queue item status to `complete` in local state without full refresh
- On failure: display backend error message inline on the affected queue item
- _Requirements: 6.2, 6.3, 6.5, 6.6, 6.7_
- [x] 7.4 Implement Redirect action form
- On Redirect button click: show inline form with "From Team" dropdown and "To Team" dropdown (both from cached teams list)
- On form submit: POST to `/api/card/queue/:queueItemId/redirect` with `{ fromTeam, toTeam, assetId: item.ip_address }`
- Same loading/success/error handling as confirm/decline
- _Requirements: 6.4, 6.5, 6.6, 6.7_
- [x] 8. Frontend Asset Search Panel
- [x] 8.1 Create asset search interface accessible from the Ivanti Queue page
- Add a "CARD Asset Search" button/section in the queue panel or as a collapsible panel
- Include team selection dropdown (from cached teams) and disposition filter dropdown (`confirmed`, `unconfirmed`, `declined`, `candidate`)
- On search: GET `/api/card/teams/:teamName/assets?disposition=X&page_size=50`
- Display total asset count and results table with Asset_ID and identifying fields
- Add pagination controls when total exceeds page size (increment `page` param)
- Display error messages inline in the search results area on failure
- _Requirements: 7.1, 7.2, 7.3, 7.4, 7.5, 7.6, 7.7_
- [x] 9. Checkpoint — Full integration verification
- Ensure all tests pass, ask the user if questions arise.
- [ ] 10. Property-based tests for CARD API correctness properties
- [ ]* 10.1 Write property test for isConfigured environment variable logic
- **Property 1: isConfigured reflects environment variable presence**
- For any combination of the three required env vars being present/non-empty or absent/empty, `isConfigured` is `true` iff all three are present and non-empty
- Create `backend/__tests__/card-isConfigured.property.test.js` using Jest + fast-check with 100+ iterations
- **Validates: Requirements 1.1, 3.3**
- [ ]* 10.2 Write property test for CARD API response shape consistency
- **Property 2: All CARD API responses have consistent shape**
- For any successful CARD API call, the resolved Promise contains `{ status: number, body: string }`
- Create `backend/__tests__/card-response-shape.property.test.js`
- **Validates: Requirements 1.7**
- [ ]* 10.3 Write property test for token acquisition error messages
- **Property 3: Token acquisition errors include status and body**
- For any non-success HTTP status and response body from the token endpoint, the rejected error message includes both the status code and body text
- Create `backend/__tests__/card-token-errors.property.test.js`
- **Validates: Requirements 2.7**
- [ ]* 10.4 Write property test for CARD API error status code forwarding
- **Property 4: CARD API error status codes are forwarded through proxy**
- For any 4xx/5xx status from CARD API on a proxied request, the route returns that same status code with a JSON error body
- Create `backend/__tests__/card-error-forwarding.property.test.js`
- **Validates: Requirements 4.9**
- [ ]* 10.5 Write property test for queue item validation on mutations
- **Property 5: Queue item validation rejects invalid states for mutations**
- For any mutation request where the queue item doesn't exist, wrong user, wrong workflow_type, or wrong status, the endpoint rejects with 404 or 400 without calling CARD API
- Create `backend/__tests__/card-queue-validation.property.test.js`
- **Validates: Requirements 5.4, 5.5, 5.6**
- [ ]* 10.6 Write property test for mutation input validation
- **Property 6: Mutation input validation enforces required fields**
- For any mutation request missing required fields (teamName/assetId for confirm/decline; fromTeam/toTeam/assetId for redirect), the endpoint rejects with 400
- Create `backend/__tests__/card-input-validation.property.test.js`
- **Validates: Requirements 5.11, 5.12, 5.13**
- [ ]* 10.7 Write property test for CARD mutation audit log entries
- **Property 7: CARD mutation audit entries contain required fields**
- For any mutation action, the audit log entry contains correct `action` name, `entityType`, `entityId`, `userId`, `username`, `ipAddress`, and `details` with `assetId` and CARD response status
- Create `backend/__tests__/card-audit-entries.property.test.js`
- **Validates: Requirements 9.1, 9.2, 9.3, 9.6**
- [x] 11. Final checkpoint — Ensure all tests pass
- Ensure all tests pass, ask the user if questions arise.
## Notes
- Tasks 13 are marked complete because `backend/helpers/cardApi.js`, `.env`/`.env.example`, and `backend/scripts/card-uat-test.js` are already built and UAT-tested
- Tasks marked with `*` are optional property-based tests and can be skipped for faster MVP
- Each task references specific requirements for traceability
- The backend route module (Task 4) follows the existing `atlas.js` route pattern exactly
- The frontend UI (Tasks 78) extends the existing `QueuePanel` in `ReportingPage.js`
- Property tests use Jest + fast-check matching the existing test pattern in `backend/__tests__/`