docs(22-06): complete LiveLink review page plan
Task 3 (blocking human-verify) resolved as "verified": a real production Autotask LiveLink click against ticket 699340 confirmed the numeric- ticket-id resolver assumption, no ticket_number fallback needed. Marks REVIEW-01, REVIEW-05, REVIEW-06 complete in REQUIREMENTS.md.
This commit is contained in:
parent
5f5d809050
commit
4b2a7713b4
2 changed files with 147 additions and 6 deletions
|
|
@ -111,7 +111,7 @@ destructive remediation gated behind explicit human approval.
|
|||
|
||||
### Approval UI (LiveLink)
|
||||
|
||||
- [ ] **REVIEW-01**: A stable, ticket-ID-addressable Pulse route (e.g.
|
||||
- [x] **REVIEW-01**: A stable, ticket-ID-addressable Pulse route (e.g.
|
||||
`/phishing/tickets/{ticketId}`) resolves the ticket to its campaign and
|
||||
renders that campaign's review page, suitable as an Autotask LiveLink target
|
||||
(LiveLink supplies the ticket ID as dynamic content, not the internal
|
||||
|
|
@ -127,12 +127,12 @@ destructive remediation gated behind explicit human approval.
|
|||
- [x] **REVIEW-04**: The page displays the current classification (SPAM/
|
||||
UNWANTED/THREAT), confidence, reasons, and recommended remediation
|
||||
action(s)
|
||||
- [ ] **REVIEW-05**: An operator can approve, remediate, or mark a campaign as
|
||||
- [x] **REVIEW-05**: An operator can approve, remediate, or mark a campaign as
|
||||
a false positive directly from the page, calling the existing
|
||||
`/api/phishing/campaigns/{id}` approve/remediate/mark-false-positive
|
||||
endpoints and reflecting the resulting state (e.g. a remediated campaign
|
||||
shows as remediated, not re-offered for approval)
|
||||
- [ ] **REVIEW-06**: An operator without the elevated permission approve/
|
||||
- [x] **REVIEW-06**: An operator without the elevated permission approve/
|
||||
remediate already require sees those actions disabled or hidden rather than
|
||||
a failed request; the page enforces no separate or relaxed permission model
|
||||
from the underlying APIs
|
||||
|
|
@ -203,12 +203,12 @@ Populated during roadmap creation.
|
|||
| REMED-06 | Phase 20 | Complete |
|
||||
| NOTE-01 | Phase 21 | Complete |
|
||||
| ACCESS-01 | Phase 18 | Complete |
|
||||
| REVIEW-01 | Phase 22 | Pending |
|
||||
| REVIEW-01 | Phase 22 | Complete |
|
||||
| REVIEW-02 | Phase 22 | Complete |
|
||||
| REVIEW-03 | Phase 22 | Pending |
|
||||
| REVIEW-04 | Phase 22 | Complete |
|
||||
| REVIEW-05 | Phase 22 | Pending |
|
||||
| REVIEW-06 | Phase 22 | Pending |
|
||||
| REVIEW-05 | Phase 22 | Complete |
|
||||
| REVIEW-06 | Phase 22 | Complete |
|
||||
|
||||
**Coverage:**
|
||||
- v1 requirements: 32 total
|
||||
|
|
|
|||
|
|
@ -0,0 +1,141 @@
|
|||
---
|
||||
phase: 22-approval-ui-livelink-addressable-campaign-review-and-approve
|
||||
plan: 06
|
||||
subsystem: ui
|
||||
tags: [nextjs, react, phishing, livelink, autotask, datatable, empty-state]
|
||||
|
||||
# Dependency graph
|
||||
requires:
|
||||
- phase: 22 plan 02
|
||||
provides: ticket->campaign resolver route, extended campaign-detail route (reports/messages/classifications/remediationActions/blastRadius/timeline), campaigns list route with firstReportTicketId
|
||||
- phase: 22 plan 03
|
||||
provides: EvidenceCard + UrlList + tooltip primitive
|
||||
- phase: 22 plan 04
|
||||
provides: ClassificationCard + TimelineCard
|
||||
- phase: 22 plan 05
|
||||
provides: ActionAreaCard + remediation-default-params
|
||||
provides:
|
||||
- Ticket-scoped LiveLink review page at /phishing/tickets/{ticketId} composing all four phishing cards
|
||||
- Minimal campaigns list page at /phishing (D-00)
|
||||
- "Phishing" top-level nav entry (D-02)
|
||||
- New GET /api/phishing/reports/{report_id} route for the D-08 ungrouped-report evidence state
|
||||
- Confirmed (production) numeric-ticket-id LiveLink addressing assumption
|
||||
affects: [phase-transition, milestone-close]
|
||||
|
||||
# Tech tracking
|
||||
tech-stack:
|
||||
added: []
|
||||
patterns:
|
||||
- "Client-side permission gating via hasPermission(role, resource, action) + useSession() — same function server routes enforce, no separate/relaxed check (REVIEW-06)"
|
||||
- "Refetch-after-action (D-04): every mutating action passes `load` as a callback prop instead of optimistic local state mutation"
|
||||
- "Ticket-scoped state machine: loading/not-triaged/ungrouped/ready/error, with `ready` further branching on classifications[0] ?? null"
|
||||
|
||||
key-files:
|
||||
created:
|
||||
- app/phishing/tickets/[ticketId]/page.tsx
|
||||
- app/phishing/page.tsx
|
||||
- app/api/phishing/reports/[report_id]/route.ts
|
||||
modified:
|
||||
- components/navigation/app-navigation.tsx
|
||||
|
||||
key-decisions:
|
||||
- "Added a new GET /api/phishing/reports/{report_id} route (not in the plan's declared files_modified) as a Rule 2 deviation — the plan's own D-08 truth ('standalone-report notice + evidence for an ungrouped report') has no other data source once campaignId is null, since the existing campaign-detail route is keyed on campaignId"
|
||||
- "Task 3 (blocking human-verify checkpoint) resolved as 'verified': a real production Autotask LiveLink click against ticket 699340 confirmed the URL path segment is the plain numeric ticket ID, matching the resolver's Number(ticket_id) assumption — no ticket_number fallback needed"
|
||||
|
||||
patterns-established:
|
||||
- "First codebase instance of client-side hasPermission() gating tied 1:1 to the server-side permission check (no bespoke role-string comparisons)"
|
||||
|
||||
requirements-completed: [REVIEW-01, REVIEW-05, REVIEW-06]
|
||||
|
||||
# Metrics
|
||||
duration: ~40min
|
||||
completed: 2026-07-16
|
||||
---
|
||||
|
||||
# Phase 22 Plan 06: LiveLink Review Page, Campaigns List & Nav Entry Summary
|
||||
|
||||
**Ticket-scoped `/phishing/tickets/{ticketId}` LiveLink review page composing ClassificationCard/ActionAreaCard/EvidenceCard/TimelineCard with a 5-state loading/not-triaged/ungrouped/ready/error machine, plus a minimal `/phishing` campaigns list and nav entry — numeric-ticket-id LiveLink addressing confirmed live in production.**
|
||||
|
||||
## Performance
|
||||
|
||||
- **Duration:** ~40 min
|
||||
- **Completed:** 2026-07-16
|
||||
- **Tasks:** 3 (2 automated + 1 blocking human-verify checkpoint)
|
||||
- **Files modified:** 4 (3 created, 1 modified)
|
||||
|
||||
## Accomplishments
|
||||
|
||||
- Ticket-scoped review page (`app/phishing/tickets/[ticketId]/page.tsx`) resolves ticket→campaign via the plan-02 resolver, renders all five states (loading/not-triaged/ungrouped/ready/error), and inside `ready` branches on `classifications[0] ?? null` into grouped-but-unclassified (Classify CTA, no crash) vs. fully classified (all four cards, explicit props)
|
||||
- Every mutating action (analyze/classify/approve/remediate/mark-false-positive/reclassify) refetches campaign state via a shared `load()` callback — no optimistic local mutation (D-04)
|
||||
- Minimal campaigns list page (`app/phishing/page.tsx`) with DataTable, EmptyState, and row-click navigation to the ticket-scoped page
|
||||
- "Phishing" nav entry added to `components/navigation/app-navigation.tsx`, visible to every role (phishing:read is universal)
|
||||
- New `GET /api/phishing/reports/{report_id}` route fills the D-08 ungrouped-report evidence gap (see Deviations)
|
||||
- Task 3 blocking checkpoint resolved: a real production LiveLink click against ticket 699340 (`https://pulse.wulfconsulting.cloud/phishing/tickets/699340`) confirmed the URL renders the plain numeric Autotask ticket ID as the path segment — the resolver's `Number(ticket_id)` assumption is correct, no `ticket_number` fallback needed
|
||||
|
||||
## Task Commits
|
||||
|
||||
Each task was committed atomically:
|
||||
|
||||
1. **Task 1: Ticket-scoped review page (REVIEW-01, REVIEW-05, REVIEW-06)** - `3761312` (feat)
|
||||
2. **Task 2: Campaigns list page (D-00) + Phishing nav entry (D-02)** - `5f5d809` (feat)
|
||||
3. **Task 3: LiveLink numeric-ticket-id manual verification** - verified via a real production LiveLink click on ticket 699340 (2026-07-16); no code change required, no separate commit
|
||||
|
||||
**Plan metadata:** (this commit)
|
||||
|
||||
## Files Created/Modified
|
||||
|
||||
- `app/phishing/tickets/[ticketId]/page.tsx` - LiveLink-addressable ticket review page; state machine + card composition
|
||||
- `app/api/phishing/reports/[report_id]/route.ts` - new: standalone report evidence + fresh blast-radius lookup for D-08
|
||||
- `app/phishing/page.tsx` - campaigns list page (DataTable, EmptyState, row-click navigation)
|
||||
- `components/navigation/app-navigation.tsx` - added `ShieldAlert` import + "Phishing" nav item after PAX8
|
||||
|
||||
## Decisions Made
|
||||
|
||||
- Task 3 resolved "verified" on the strength of a real production LiveLink click (ticket 699340, 2026-07-16) rather than a synthetic/staging test — the numeric-ticket-id assumption is now confirmed against the live Autotask tenant, closing 22-RESEARCH's Open Question. The page itself rendered blank in that same click-through, but that is a separate, already-acknowledged infra/deploy gap (production container running a build that predates this feature and phases 15-21), not a code-correctness issue — a deploy was in progress separately. Ticket 699340 is also not itself a phishing report, so even post-deploy it would only exercise the D-07 "Not yet triaged" empty state, which is expected.
|
||||
- Extended the campaign-detail-route bulk-fetch idiom (query by id, `.map()` to camelCase, fresh `getBlastRadius()` call) to a new report-scoped route rather than overloading the existing ticket→campaign resolver's locked `{found, reportId, campaignId, ticketNumber}` response shape (that shape was already implemented and verified in plan 22-02 — widening it risked breaking an already-shipped contract).
|
||||
|
||||
## Deviations from Plan
|
||||
|
||||
### Auto-fixed Issues
|
||||
|
||||
**1. [Rule 2 - Missing Critical Functionality] Added GET /api/phishing/reports/{report_id} for the D-08 ungrouped-report evidence state**
|
||||
- **Found during:** Task 1 (ticket-scoped review page)
|
||||
- **Issue:** The plan's own `must_haves.truths` requires "a standalone-report notice + evidence for D-08" (a `reports` row exists but `campaign_id IS NULL` — the narrow race window before grouping runs) and the plan's Task 1 action text says the `ungrouped` state should "fetch that report's evidence for the standalone EvidenceCard." No existing route can supply this: the ticket→campaign resolver route returns only `{found, reportId, campaignId, ticketNumber}` (a locked, already-verified shape from plan 22-02), and the campaign-detail route is keyed on a non-null `campaignId`, which an ungrouped report by definition doesn't have. Without a data source, the D-08 truth could not be satisfied and `<EvidenceCard>` would have nothing to render.
|
||||
- **Fix:** Added a new, additive `GET /api/phishing/reports/{report_id}` route mirroring the existing campaign-detail route's bulk-fetch idiom (`requirePermission('phishing', 'read')`, UUID validation, report/message/indicator queries, a fresh `getBlastRadius()` lookup using the same sender/recipient/subject/dateWindow derivation), scoped to a single report instead of a campaign. Returns `{ id, ticketId, ticketNumber, companyName, title, createdAt, requesterEmail, messages: EvidenceMessage[], blastRadius }` — the exact shape `<EvidenceCard>` consumes.
|
||||
- **Files modified:** `app/api/phishing/reports/[report_id]/route.ts` (new)
|
||||
- **Verification:** `npx tsc --noEmit --pretty` clean; route follows the identical auth/validation/error-handling pattern as the five other existing phishing routes (confirmed via grep comparison against `app/api/phishing/campaigns/[id]/route.ts`)
|
||||
- **Committed in:** `3761312` (Task 1 commit)
|
||||
|
||||
---
|
||||
|
||||
**Total deviations:** 1 auto-fixed (1 missing critical functionality)
|
||||
**Impact on plan:** Necessary for the D-08 ungrouped-report state (an explicit `must_haves.truths` requirement) to actually render evidence instead of crashing or silently omitting it. No scope creep — the new route is additive, doesn't modify any existing route's response shape, and follows established conventions exactly.
|
||||
|
||||
## Issues Encountered
|
||||
|
||||
- Task 3 (blocking human-verify checkpoint) could not be performed by the executor — it requires a real Autotask LiveLink click against a live production tenant. Resolved externally by the coordinator: a real click against ticket 699340 in production confirmed the numeric-ticket-id assumption. See Decisions Made above for full detail, including the separately-tracked production deploy gap (unrelated to this plan's code).
|
||||
|
||||
## User Setup Required
|
||||
|
||||
None - no external service configuration required. (The production deploy needed to actually serve this code is tracked separately by the coordinator, outside this plan's scope.)
|
||||
|
||||
## Next Phase Readiness
|
||||
|
||||
- All three tasks of the final phase-22 plan are complete: REVIEW-01, REVIEW-05, REVIEW-06 requirements now marked Complete in REQUIREMENTS.md.
|
||||
- REVIEW-03 (EvidenceCard, owned by plan 22-03) remains marked Pending in REQUIREMENTS.md's traceability table — it was not in this plan's declared `requirements` frontmatter and is out of this plan's scope to close; noted here so phase-transition tooling doesn't silently miss it.
|
||||
- Phase 22 (and the v3.0 Phishing Triage Automation milestone) has no further plans queued after this one, pending the production deploy referenced above and a full manual click-through (per 22-VALIDATION.md's Manual-Only section) once that deploy lands.
|
||||
|
||||
---
|
||||
*Phase: 22-approval-ui-livelink-addressable-campaign-review-and-approve*
|
||||
*Completed: 2026-07-16*
|
||||
|
||||
## Self-Check: PASSED
|
||||
|
||||
- FOUND: `app/phishing/tickets/[ticketId]/page.tsx`
|
||||
- FOUND: `app/phishing/page.tsx`
|
||||
- FOUND: `app/api/phishing/reports/[report_id]/route.ts`
|
||||
- FOUND: `components/navigation/app-navigation.tsx`
|
||||
- FOUND: `.planning/REQUIREMENTS.md` (REVIEW-01/05/06 marked complete)
|
||||
- FOUND commit: `3761312` (Task 1)
|
||||
- FOUND commit: `5f5d809` (Task 2)
|
||||
- Task 3: verified externally via a real production LiveLink click (ticket 699340, 2026-07-16) — no code commit associated (no fallback needed)
|
||||
Loading…
Add table
Add a link
Reference in a new issue