diff --git a/.planning/phases/22-approval-ui-livelink-addressable-campaign-review-and-approve/22-01-SUMMARY.md b/.planning/phases/22-approval-ui-livelink-addressable-campaign-review-and-approve/22-01-SUMMARY.md new file mode 100644 index 0000000..30b1ab7 --- /dev/null +++ b/.planning/phases/22-approval-ui-livelink-addressable-campaign-review-and-approve/22-01-SUMMARY.md @@ -0,0 +1,120 @@ +--- +phase: 22-approval-ui-livelink-addressable-campaign-review-and-approve +plan: 01 +subsystem: api +tags: [vitest, tdd, postgres, phishing-triage, pure-functions] + +# Dependency graph +requires: + - phase: 19-phishing-triage-classification-engine + provides: campaign-classifier.ts's mapVerdictToActions 7-action vocabulary and postgresClient/vi.mock testing idiom + - phase: 20-remediation-approval-audit-safety + provides: remediation-service.ts's ApproveActionInput shape and phishing-audit.ts's canonical event_type values +provides: + - resolveTicketToCampaign(ticketId) — pure ticket->campaign lookup service + - deriveDefaultParams(actionType, evidence) — 7-action-type default-param derivation + - mergeTimeline(reports, classifications, auditEvents) — chronological 3-source merge +affects: [22-02-ticket-campaign-route, 22-04-action-area-card, 22-05-timeline-card] + +# Tech tracking +tech-stack: + added: [] + patterns: + - "Pure lib/services/*.ts extraction for route/component logic that needs vitest coverage (test.include is lib/**/*.test.ts only)" + - "vi.mock('./postgres-client', () => ({ postgresClient: { query: ... } })) factory-mock idiom (mirrors campaign-classifier.test.ts)" + +key-files: + created: + - lib/services/phishing-ticket-resolver.ts + - lib/services/phishing-ticket-resolver.test.ts + - lib/services/remediation-default-params.ts + - lib/services/remediation-default-params.test.ts + - lib/services/phishing-timeline.ts + - lib/services/phishing-timeline.test.ts + modified: [] + +key-decisions: + - "Used named import `{ postgresClient }` from './postgres-client' (matches the sibling files' actual convention — campaign-classifier.ts, remediation-service.ts, phishing-audit.ts — and the PATTERNS.md code snippet), not the plan action text's parenthetical 'default import' gloss, since the vi.mock factory only provides a named `postgresClient` export and a default-import call site would not be mocked." + - "mergeTimeline's report/classification/audit source shapes use camelCase `createdAt` fields (not snake_case `created_at`) since the read_first note specifies these consume the already-transformed camelCase API response shapes from app/api/phishing/campaigns/[id]/route.ts." + +patterns-established: + - "Pattern: exhaustive switch with `default: return {}` for extensible action-type-keyed pure derivations (mirrors mapVerdictToActions)" + - "Pattern: discriminated union + fixed KIND_PRIORITY map for stable multi-source chronological merges" + +requirements-completed: [REVIEW-01, REVIEW-02, REVIEW-04] + +# Metrics +duration: 12min +completed: 2026-07-16 +--- + +# Phase 22 Plan 01: Wave 0 Pure-Logic Extraction Summary + +**Three pure `lib/services/*.ts` modules (ticket->campaign resolver, 7-action-type default-param derivation, 3-source timeline merge) extracted and unit-tested under vitest, unblocking the route/component work in plans 02/04/05.** + +## Performance + +- **Duration:** 12 min +- **Started:** 2026-07-16T18:17:00Z +- **Completed:** 2026-07-16T18:29:00Z +- **Tasks:** 3 completed +- **Files modified:** 6 (all new) + +## Accomplishments +- `resolveTicketToCampaign(ticketId)` covers all three resolution states (no report / ungrouped / grouped) via a single parameterized `reports` lookup +- `deriveDefaultParams(actionType, evidence)` implements the exact 7-action-type default-params table from the UI-SPEC, with a safe `{}` fallback for unknown/future action types +- `mergeTimeline(reports, classifications, auditEvents)` produces a single ascending-chronological array with a discriminated `TimelineEntry` union and a deterministic tie-break order +- All 3 modules are pure or DB-only (no `NextResponse`/`requirePermission` in any of them) — route-free per the plan's design intent + +## Task Commits + +Each task was committed via full TDD RED/GREEN pairs: + +1. **Task 1: phishing-ticket-resolver.ts + test** — `20b1e1b` (test), `e619321` (feat) +2. **Task 2: remediation-default-params.ts + test** — `c64a905` (test), `86cffc4` (feat) +3. **Task 3: phishing-timeline.ts + test (server-merge)** — `37f6657` (test), `d30a49f` (feat) + +**Plan metadata:** (this commit) + +## Files Created/Modified +- `lib/services/phishing-ticket-resolver.ts` - `resolveTicketToCampaign(ticketId)`, parameterized `reports` lookup +- `lib/services/phishing-ticket-resolver.test.ts` - 4 tests covering the 3 resolution states + query-shape assertion +- `lib/services/remediation-default-params.ts` - `deriveDefaultParams(actionType, evidence)`, exhaustive switch over 7 action types + fallback +- `lib/services/remediation-default-params.test.ts` - 9 tests covering all 7 known types, unknown fallback, and null-evidence defaults +- `lib/services/phishing-timeline.ts` - `mergeTimeline(reports, classifications, auditEvents)`, `TimelineEntry` discriminated union +- `lib/services/phishing-timeline.test.ts` - 4 tests covering sort order, length invariant, per-variant fields, and tie-break stability + +## Decisions Made +- Named import for `postgresClient` (not the plan text's literal "default import" parenthetical) — see `key-decisions` above; verified against all 3 sibling files (`campaign-classifier.ts`, `remediation-service.ts`, `phishing-audit.ts`) and the working `vi.mock` test pattern. +- `mergeTimeline` inputs are camelCase (`createdAt`, `reportId`, `ticketNumber`, `companyName`, `verdict`, `confidence`, `eventType`, `actor`, `payload`) since this function consumes the campaign-detail route's already-transformed response shape, not raw DB rows. + +## Deviations from Plan + +None - plan executed exactly as written, with one clarifying correction to an internal inconsistency in the plan text (see Decisions Made: named vs. default import) resolved in favor of the working, testable convention already established in this phase's sibling files. + +## Issues Encountered +None - all three RED phases confirmed failing before implementation (module-not-found), all three GREEN phases passed on first implementation attempt. + +## TDD Gate Compliance + +All three tasks followed the full RED -> GREEN cycle with separate commits: +- Task 1: `test(22-01)` at `20b1e1b` -> `feat(22-01)` at `e619321` +- Task 2: `test(22-01)` at `c64a905` -> `feat(22-01)` at `86cffc4` +- Task 3: `test(22-01)` at `37f6657` -> `feat(22-01)` at `d30a49f` + +No REFACTOR commits were needed — each GREEN implementation passed cleanly without follow-up cleanup. + +## User Setup Required + +None - no external service configuration required. + +## Next Phase Readiness + +- Plan 02 (ticket->campaign resolver route) can now call `resolveTicketToCampaign` directly — the route becomes a thin `requirePermission` + param-parse + call wrapper per PATTERNS.md. +- Plans 04/05 (Action Area Card, Timeline Card) have a tested `deriveDefaultParams`/`mergeTimeline` to call from their respective routes/components (or the extended `campaigns/[id]` route, per the server-merge decision). +- Full test suite: 411/413 passing. The 2 failures are pre-existing, unrelated `lib/services/analyzer/itglue-search.test.ts` failures (untouched by this plan) — logged in `.planning/phases/22-approval-ui-livelink-addressable-campaign-review-and-approve/deferred-items.md`, out of scope per the plan's file list. +- `npx tsc --noEmit --pretty` is clean. + +--- +*Phase: 22-approval-ui-livelink-addressable-campaign-review-and-approve* +*Completed: 2026-07-16* diff --git a/.planning/phases/22-approval-ui-livelink-addressable-campaign-review-and-approve/deferred-items.md b/.planning/phases/22-approval-ui-livelink-addressable-campaign-review-and-approve/deferred-items.md new file mode 100644 index 0000000..b8468e6 --- /dev/null +++ b/.planning/phases/22-approval-ui-livelink-addressable-campaign-review-and-approve/deferred-items.md @@ -0,0 +1,16 @@ +# Deferred Items — Phase 22 + +Pre-existing failures/warnings discovered during execution but out of scope +for the current plan (not touched by this plan's files, not caused by this +plan's changes). + +## Plan 01 + +- `lib/services/analyzer/itglue-search.test.ts` — 2 pre-existing failing + tests (`itglueSearch > tolerates per-call failures ...`, doc count + mismatches). Unrelated to phishing-ticket-resolver / + remediation-default-params / phishing-timeline (this plan's files). Not + modified by any commit in this plan. Confirmed present before this plan's + changes (file untouched in `git diff`). Full-suite run: `2 failed | 411 + passed (413)` with only these 2 pre-existing failures — all other tests, + including the 3 new files added by this plan, are green.