docs(22-01): complete Wave 0 pure-logic extraction plan

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W6RuWdiUiXrPK6FLBHjtpY
This commit is contained in:
lorentz 2026-07-16 14:30:18 -04:00
parent d30a49f644
commit 96507dc2c6
2 changed files with 136 additions and 0 deletions

View file

@ -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*

View file

@ -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.