docs(21-02): add plan summary for triage-note service and endpoint
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W6RuWdiUiXrPK6FLBHjtpY
This commit is contained in:
parent
e3a9cb5191
commit
950227ea4e
1 changed files with 103 additions and 0 deletions
103
.planning/phases/21-autotask-triage-note/21-02-SUMMARY.md
Normal file
103
.planning/phases/21-autotask-triage-note/21-02-SUMMARY.md
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
---
|
||||
phase: 21-autotask-triage-note
|
||||
plan: 02
|
||||
subsystem: api
|
||||
tags: [phishing-triage, autotask, triage-note, vitest, api-route]
|
||||
|
||||
# Dependency graph
|
||||
requires:
|
||||
- phase: 21-autotask-triage-note
|
||||
plan: 01
|
||||
provides: "formatTriageNote(evidence) + TriageNoteEvidence contract, sanitizeUrl/sanitizeNoteText"
|
||||
- phase: 17-mimecast-blast-radius-lookup
|
||||
provides: getBlastRadius(input) -> BlastRadiusResult
|
||||
- phase: 19-classification-engine
|
||||
provides: classifications table (verdict/confidence/summary/reasons/recommended_actions)
|
||||
- phase: 20-remediation-approval-audit-safety
|
||||
provides: remediation_actions status lifecycle rows
|
||||
provides:
|
||||
- "generateAndPostTriageNote(campaignId) orchestrator (lib/services/triage-note-service.ts)"
|
||||
- "POST /api/phishing/campaigns/[id]/triage-note endpoint"
|
||||
affects: [22-approval-ui-livelink-addressable-campaign-review-and-approve]
|
||||
|
||||
# Tech tracking
|
||||
tech-stack:
|
||||
added: []
|
||||
patterns:
|
||||
- "Per-ticket try/catch INSIDE the write loop (not around it) so one Autotask write failure never aborts remaining writes (D-05)"
|
||||
- "NUMERIC confidence column defensively coerced with both a SQL ::float8 cast and a runtime Number() wrap, so the number|null contract holds even against a string-returning mock/driver"
|
||||
- "Service always returns generated note text regardless of write outcome (D-06) — never a silent partial rollback"
|
||||
|
||||
key-files:
|
||||
created:
|
||||
- lib/services/triage-note-service.ts
|
||||
- lib/services/triage-note-service.test.ts
|
||||
- app/api/phishing/campaigns/[id]/triage-note/route.ts
|
||||
modified: []
|
||||
|
||||
key-decisions:
|
||||
- "Blast-radius sender/recipient inputs use best-available data from the bounded reports query (title only, sender/recipient left empty) rather than adding extra contact/message-header queries — getBlastRadius degrades gracefully (never throws) on sparse input, and the plan's evidence-gathering SQL is limited to the four queries it specifies (reports, classifications, remediation_actions, indicators)"
|
||||
- "Verdict is cast from the classifications.verdict TEXT column to TriageNoteEvidence['verdict'] rather than validated against the union at runtime — consistent with how campaign-classifier.ts/classify route already trust this column"
|
||||
|
||||
requirements-completed: [NOTE-01]
|
||||
|
||||
# Metrics
|
||||
duration: ~11min
|
||||
completed: 2026-07-16
|
||||
---
|
||||
|
||||
# Phase 21 Plan 02: Triage-Note Service + Endpoint Summary
|
||||
|
||||
**`generateAndPostTriageNote(campaignId)` gathers current campaign evidence (linked reports, real url indicators via the reports→messages→indicators join, most-recent classification with a numeric-coerced confidence, current remediation state, fresh blast radius), renders it via Plan 01's sanitized formatter, and posts one internal Autotask TicketNote per linked ticket with independent per-ticket failure capture — exposed via `POST /api/phishing/campaigns/{id}/triage-note`.**
|
||||
|
||||
## Performance
|
||||
|
||||
- **Duration:** ~11 min
|
||||
- **Started:** 2026-07-16T16:03:00Z (approx.)
|
||||
- **Completed:** 2026-07-16T16:14:33Z
|
||||
- **Tasks:** 2 completed
|
||||
- **Files modified:** 3 (all newly created)
|
||||
|
||||
## Accomplishments
|
||||
- `generateAndPostTriageNote(campaignId)` orchestrates: linked-reports read, most-recent classification read (with `confidence::float8` SQL cast + defensive `Number()` runtime coercion), current `remediation_actions` state, and a real `reports → messages → indicators` join filtered to `indicator_type = 'url'` for extracted indicator URLs
|
||||
- Fresh `getBlastRadius()` lookup per call (D-04 — always current, not frozen at classify-time)
|
||||
- Builds a `TriageNoteEvidence` object and calls Plan 01's `formatTriageNote()` to get the sanitized note text
|
||||
- Posts one `createEntity('TicketNotes', { ticketID, title, description, noteType: 1, publish: 1 })` write per linked ticket, each in its OWN try/catch (D-05) — a single ticket's failure is captured as `{ ticketId, posted: false, error }` without aborting the remaining writes
|
||||
- `noteText` is always returned regardless of write outcome (D-06) — verified by a test where the only linked ticket's write fails and `noteText` is still non-empty
|
||||
- `POST /api/phishing/campaigns/[id]/triage-note` — structural twin of `classify/route.ts`: `requirePermission('phishing', 'analyze')` gate, UUID guard (400), campaign-exists check (404), delegates to the service, returns its result verbatim, no audit-event write (deferred per CONTEXT.md)
|
||||
|
||||
## Task Commits
|
||||
|
||||
Task 1 followed RED → GREEN (TDD):
|
||||
1. **Task 1: triage-note-service** - `34a0269` (test: RED, verified failing — module did not exist) → `2d410f8` (feat: GREEN, all 8 tests pass)
|
||||
2. **Task 2: POST route** - `e3a9cb5` (feat, no TDD gate — `type="auto"` without `tdd="true"`)
|
||||
|
||||
_TDD gate compliance: Task 1 has a `test(...)` commit followed by a `feat(...)` commit; RED was verified by temporarily removing the implementation file and confirming the suite failed with "Cannot find module" before restoring it and re-running to GREEN. No refactor step was needed._
|
||||
|
||||
## Files Created/Modified
|
||||
- `lib/services/triage-note-service.ts` - `TriageNotePostResult`/`TriageNoteResult` interfaces + `generateAndPostTriageNote(campaignId)` orchestrator
|
||||
- `lib/services/triage-note-service.test.ts` - 8 Vitest cases: all-succeed write loop, one-of-three-fails isolation (D-05), note-text-always-returned (D-06), sanitized-URL-flows-into-note, zero-url-indicators, string-confidence coercion, zero-linked-reports, no-classification-row
|
||||
- `app/api/phishing/campaigns/[id]/triage-note/route.ts` - `POST` handler: auth gate, UUID guard, 404, delegate, verbatim return, 500 catch
|
||||
|
||||
## Decisions Made
|
||||
- Blast-radius sender/recipient use best-available data (title only) from the bounded reports query rather than adding extra contact/message-header lookups — matches the plan's literal four-query evidence-gathering scope and `getBlastRadius()`'s documented graceful-degradation behavior on sparse input (never throws).
|
||||
- `classifications.verdict` (a TEXT column) is cast to `TriageNoteEvidence['verdict']` without an additional runtime validation step, consistent with how the existing classify route and campaign-classifier.ts already trust this column's values.
|
||||
- Route uses `phishing:analyze` (not `phishing:approve`) per CONTEXT.md's explicit lean — note generation is informational, not a state-changing security decision.
|
||||
|
||||
## Deviations from Plan
|
||||
|
||||
None - plan executed exactly as written. Both tasks' `<behavior>`, `<action>`, and `<acceptance_criteria>` blocks were implemented as specified; no architectural changes, no missing critical functionality found, no blocking issues encountered.
|
||||
|
||||
## Issues Encountered
|
||||
None.
|
||||
|
||||
## User Setup Required
|
||||
None - no external service configuration required beyond what Phases 17-20 already established (Autotask/Mimecast env vars, if configured).
|
||||
|
||||
## Next Phase Readiness
|
||||
- `generateAndPostTriageNote()` and the new `POST /api/phishing/campaigns/{id}/triage-note` endpoint are ready for Phase 22's LiveLink approval UI to call as its "send triage note" action.
|
||||
- No blockers.
|
||||
|
||||
---
|
||||
*Phase: 21-autotask-triage-note*
|
||||
*Completed: 2026-07-16*
|
||||
Loading…
Add table
Add a link
Reference in a new issue