chore: archive v3.0 milestone files
This commit is contained in:
parent
8b04be160f
commit
e9101b0d44
6 changed files with 1130 additions and 63 deletions
18
.planning/MILESTONES.md
Normal file
18
.planning/MILESTONES.md
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
# Milestones
|
||||
|
||||
## v3.0 Phishing Triage Automation (Shipped: 2026-07-17)
|
||||
|
||||
**Phases completed:** 9 phases, 30 plans, 69 tasks
|
||||
|
||||
**Key accomplishments:**
|
||||
|
||||
- Full phishing-triage data pipeline: durable schema (7 tables, migrations 097-100), idempotent Autotask ticket detection (webhook + daily cron sweep), and a pure RFC822/MIME `.eml` parser producing normalized headers, SPF/DKIM/DMARC verdicts, deduped URLs, and a sanitized body preview — no network calls, no execution of anything found in a message
|
||||
- Mimecast blast-radius lookup abstraction and a deterministic, zero-LLM SPAM/UNWANTED/THREAT classifier — including a KnowBe4/Breach-Secure-Now simulation allowlist so routine security-awareness tests never cry wolf as THREAT
|
||||
- Full remediation safety layer: proposed-only actions, permission-gated approve/remediate/mark-false-positive APIs, idempotent re-run, and a single append-only audit trail for every state change — nothing destructive ever auto-executes
|
||||
- Ticket-ID-addressable Approval UI (`/phishing/tickets/{ticketId}`), a real Autotask LiveLink target confirmed live in production, showing timeline/evidence/classification with approve/remediate/mark-false-positive wired directly to the API layer
|
||||
- Classification Disposition + Per-Client Automation Gate (Phase 23): a dedicated `USER_AWARENESS` verdict + non-destructive customer-visible acknowledgment note, plus a per-company opt-in automation gate letting an admin choose automatic vs. manual pipeline execution — including a post-ship idempotency fix (closing a duplicate-note bug caught by code review) before milestone close
|
||||
- Two real code-review-caught defects fixed pre-close during this milestone: a duplicate-campaign bug in Phase 18's grouping service, and the Phase 23 duplicate-acknowledgment-note bug — both proven via regression tests and live-database re-verification, not just a fix commit
|
||||
|
||||
**Process note:** Phase 22 (Approval UI) shipped all 6 plans without ever running through formal verification — caught only at this milestone's close. A retroactive verification pass confirmed the code is correct (6/6 requirements), but 5 manual browser click-through checks remain outstanding; see `22-VERIFICATION.md`.
|
||||
|
||||
---
|
||||
|
|
@ -17,6 +17,8 @@ desktop for read-only awareness.
|
|||
|
||||
## Current Milestone: v3.0 Phishing Triage Automation
|
||||
|
||||
**Status:** ✅ Shipped 2026-07-17 (9/9 phases, 30/30 plans, all 38 v3.0 requirements Complete)
|
||||
|
||||
**Goal:** Detect candidate phishing/spam report tickets in Autotask, extract
|
||||
and parse original-message evidence, classify each as `SPAM` / `UNWANTED` /
|
||||
`THREAT`, group duplicate reports into campaigns, and prepare (never
|
||||
|
|
@ -233,20 +235,88 @@ Exchange purge may be preferable later).
|
|||
reconciliation sweep is disabled by default pending an admin opt-in, same
|
||||
convention as `pax8-daily`. Validated in Phase 15: Data Model, Detection &
|
||||
Ticket Evidence (DETECT-01, DETECT-02, EVID-01)
|
||||
- ✓ EML/MIME evidence parser (NEW) — pure, I/O-free `lib/services/eml-parser.ts`
|
||||
(`mailparser` + `linkify-it`): three-tier `.eml` attachment selection,
|
||||
RFC822/MIME header normalization with hand-rolled SPF/DKIM/DMARC verdicts,
|
||||
deduped URL extraction, sanitized/truncated body preview — test-enforced to
|
||||
never trigger a network call. `AutotaskClient.getAttachmentContent()`
|
||||
fetches the raw bytes; `parseAndStoreMessage()` orchestrates
|
||||
list→select→fetch→size-guard→optional-B2-store→parse→persist (one
|
||||
`messages` row + per-indicator `indicators` rows). Migration 099
|
||||
(`indicators.metadata` JSONB). Validated in Phase 16: EML/MIME Evidence
|
||||
Parser (EVID-02, EVID-03, EVID-04)
|
||||
- ✓ Mimecast blast-radius lookup (NEW) — `getBlastRadius()` composes
|
||||
`searchDeliveredMessages`/`getHeldMessages`/`getThreatEvents` into normalized
|
||||
matched/delivered/held/rejected/clicked counts + per-recipient status,
|
||||
gated by `isMimecastConfigured()`, Redis-cached 5 min, never throws —
|
||||
degrades to `status: 'unavailable'` on missing config or lookup failure.
|
||||
Validated in Phase 17: Mimecast Blast Radius Lookup (BLAST-01, BLAST-02)
|
||||
- ✓ Campaign grouping & phishing analysis API (NEW) — `groupReportIntoCampaign`
|
||||
called automatically from both the webhook `ticket.created` path and the
|
||||
cron sweep so campaigns accumulate without any API call; `POST
|
||||
/api/phishing/tickets/{ticket_id}/analyze` for on-demand
|
||||
detect→parse→group; `GET /api/phishing/campaigns` (paginated list) and
|
||||
`GET /api/phishing/campaigns/{id}` (nested detail), both
|
||||
`requirePermission('phishing','read')`-gated. Two report_count/duplicate-
|
||||
campaign bugs (CR-02, CR-03) caught by code review and fixed pre-ship,
|
||||
proven by regression tests + a live-database re-verification. Validated in
|
||||
Phase 18: Campaign Grouping & Phishing Analysis API (CAMP-01, CAMP-02,
|
||||
CAMP-03, DETECT-03, ACCESS-01)
|
||||
- ✓ Classification engine (NEW) — deterministic, zero-LLM
|
||||
`lib/services/campaign-classifier.ts`: SPAM/UNWANTED/THREAT verdict,
|
||||
KnowBe4/Breach-Secure-Now simulation sender-domain allowlist (exact-or-
|
||||
subdomain match only, no substring spoofing), THREAT gate requiring
|
||||
delivery + malicious signal, confidence scoring, append-only
|
||||
`classifications` insert. On-demand reclassify via `POST
|
||||
/api/phishing/campaigns/{id}/classify`. Validated in Phase 19:
|
||||
Classification Engine (CLASSIFY-01 through CLASSIFY-06)
|
||||
- ✓ Remediation, approval & audit safety (NEW) — transactional
|
||||
approve/remediate/mark-false-positive service layer, idempotent re-run
|
||||
(REMED-04), single append-only audit writer for every state change
|
||||
(REMED-06); all 7 action types (quarantine/block/purge/warn/reset/isolate/
|
||||
disable-forwarding) remain simulated status-only transitions this
|
||||
milestone — no real destructive execution. Validated in Phase 20:
|
||||
Remediation, Approval & Audit Safety (REMED-01 through REMED-06)
|
||||
- ✓ Autotask triage note (NEW) — sanitized formatter (URL query/fragment
|
||||
stripping, credential redaction) + `generateAndPostTriageNote(campaignId)`
|
||||
posting one internal Autotask note per linked ticket, independent
|
||||
per-ticket failure isolation. Validated in Phase 21: Autotask Triage Note
|
||||
(NOTE-01)
|
||||
- ✓ Approval UI / LiveLink (NEW) — ticket-ID-addressable
|
||||
`/phishing/tickets/{ticketId}` review page (numeric Autotask ticket id,
|
||||
not internal campaign UUID — confirmed live in production as a real
|
||||
LiveLink target), composing ClassificationCard/ActionAreaCard/
|
||||
EvidenceCard/TimelineCard behind the existing Better Auth session only.
|
||||
Evidence card renders parsed EML headers/URLs/attachments/body preview and
|
||||
Mimecast blast-radius with zero clickable-link surface and zero raw-HTML
|
||||
rendering of attacker-controlled content. Client-side permission gating
|
||||
verified to match server-side 1:1. Validated in Phase 22: Approval UI
|
||||
(LiveLink) (REVIEW-01 through REVIEW-06) — first formal verification pass
|
||||
for this phase was run retroactively at v3.0 close (2026-07-17); 5 manual
|
||||
browser click-through checks remain outstanding, see
|
||||
`22-VERIFICATION.md`'s `human_verification` list before treating the UI as
|
||||
fully signed off in a fresh deployment
|
||||
- ✓ Classification disposition + per-client automation gate (NEW) — a 4th
|
||||
`USER_AWARENESS` verdict for confirmed phishing-simulation-vendor reports
|
||||
(previously forced into generic UNWANTED) mapping to a non-destructive
|
||||
`acknowledge_user` action that posts a customer-visible thank-you note
|
||||
(Autotask `noteType: 18`); a per-Autotask-company opt-in automation gate
|
||||
(`auto_parse`/`auto_classify`/`auto_report`, all-off default) with an admin
|
||||
UI (`/admin/phishing-automation`) gating the webhook's auto pipeline —
|
||||
every other verdict/action still requires manual approval regardless of
|
||||
gate state. A post-ship code-review pass caught a duplicate-note bug
|
||||
(repeat webhooks re-posting the same acknowledgment as a campaign
|
||||
accumulated more reports) and a stale-`completedAt` bug, both fixed via an
|
||||
idempotent, audit-persisting `autoPostAcknowledgment()` wrapper before
|
||||
milestone close. Validated in Phase 23: Classification Disposition +
|
||||
Per-Client Automation Gate (CLASSDISP-01 through CLASSDISP-03,
|
||||
AUTOGATE-01 through AUTOGATE-03)
|
||||
|
||||
### Active
|
||||
|
||||
<!-- Hypotheses for this milestone — see .planning/REQUIREMENTS.md for the full v3.0 requirement list -->
|
||||
<!-- Hypotheses for v3.0 all validated above — nothing active pending next milestone -->
|
||||
|
||||
- `.eml` evidence (when present) can be parsed into normalized, actionable
|
||||
indicators without executing/fetching anything suspicious
|
||||
- Duplicate reports of the same campaign can be grouped via a small set of
|
||||
stable keys (Message-ID → indicator/subject/sender/window → sender/subject/
|
||||
client/window)
|
||||
- A rule-based classifier (with a clean LLM plug-point) can produce a useful
|
||||
SPAM/UNWANTED/THREAT verdict from structured evidence alone
|
||||
- Remediation can be modeled and gated behind approval without ever executing
|
||||
destructively in this milestone
|
||||
None yet — run `/gsd:new-milestone` to define the next milestone's requirements.
|
||||
|
||||
### Out of Scope
|
||||
|
||||
|
|
@ -284,6 +354,23 @@ Exchange purge may be preferable later).
|
|||
- **Engagement and Analyzer pages on desktop are large** (~1300 + ~650 lines
|
||||
for engagement; analyzer pipeline already has a desktop UI). Mobile
|
||||
surfaces reuse the data sources but build phone-first layouts from scratch.
|
||||
- **v3.0 codebase footprint (shipped 2026-07-17):** new phishing-triage schema
|
||||
spanning migrations 097-100 (`campaigns`, `reports`, `messages`,
|
||||
`indicators`, `classifications`, `remediation_actions`, `audit_events`,
|
||||
`phishing_automation_gate`); new `/api/phishing/*` and
|
||||
`/api/admin/phishing-automation*` route surface; new `/phishing` and
|
||||
`/admin/phishing-automation` UI surfaces; new services under
|
||||
`lib/services/` (`phishing-detector`, `eml-parser`, `phishing-eml-service`,
|
||||
`mimecast-blast-radius`, `campaign-grouping-service`, `campaign-classifier`,
|
||||
`remediation-service`, `triage-note-service`/`triage-note-format`,
|
||||
`phishing-audit`, `phishing-timeline`, `phishing-ticket-resolver`,
|
||||
`phishing-automation-gate`). ~33K LOC inserted, 189 files touched, 30 plans
|
||||
across 9 phases, 2026-07-15 → 2026-07-17.
|
||||
- **Known open item carried into next milestone:** Phase 22's UI was never
|
||||
through a human browser click-through pass (only a retroactive code-level
|
||||
verification at v3.0 close) — see `22-VERIFICATION.md`'s
|
||||
`human_verification` list. Not blocking, but worth closing before this UI
|
||||
is treated as fully hardened.
|
||||
|
||||
## Constraints
|
||||
|
||||
|
|
@ -315,6 +402,11 @@ Exchange purge may be preferable later).
|
|||
| No service worker in this iteration | Spec §4 — defer until a clear offline use-case lands | — Pending |
|
||||
| Engagement mobile is a real refactor, not a thin adaptation | Spec §6.5 — desktop's wide tables and modals don't translate; build phone-first from same data sources | — Pending |
|
||||
| Mobile user-detail is a page, not a modal | Spec §6.5 — back gesture needs real navigation history | — Pending |
|
||||
| Zero-LLM deterministic classifier (rule-based, not an LLM abstraction) | Structured, size-bounded evidence in → deterministic verdict out; avoids prompt-injection surface from attacker-controlled email content reaching an LLM | ✓ Good — shipped in Phase 19, no LLM call anywhere in the classification path |
|
||||
| `acknowledge_user` is the one narrow carve-out from the "all destructive actions require approval" rule | It's a non-destructive thank-you note, not a security action; the automation-gate feature (Phase 23) has nothing to automate without this carve-out | ✓ Good — scoped exclusively to `USER_AWARENESS` verdicts; every other action (7 types) stays manual-approval-gated |
|
||||
| Per-company automation gate is 3 independent opt-in booleans (parse/classify/report), not one master switch, defaulting all-off | Mirrors Phase 20's proposed-only-by-default safety posture — a client only gets automatic pipeline execution once an admin deliberately opts them in | ✓ Good — shipped in Phase 23, admin UI at `/admin/phishing-automation` |
|
||||
| KnowBe4/Breach-Secure-Now simulation allowlist is a TypeScript constant, not a DB table | Every rule including the allowlist should be unit-tested pure code, not a runtime-editable table that could silently drift | ✓ Good — `KNOWN_SIMULATION_SENDERS` in `campaign-classifier.ts`, exact-domain-or-subdomain match only (no substring spoofing) |
|
||||
| Phase 22 shipped without ever running `/gsd:verify-work` | Process gap discovered only at v3.0 milestone close, not during Phase 22 itself | ⚠️ Revisit — retroactive verification found the code correct (6/6 requirements), but 5 human browser click-through checks are still outstanding; run them before treating this UI as fully hardened |
|
||||
|
||||
## Evolution
|
||||
|
||||
|
|
@ -334,4 +426,4 @@ This document evolves at phase transitions and milestone boundaries.
|
|||
4. Update Context with current state
|
||||
|
||||
---
|
||||
*Last updated: 2026-07-15 — Phase 15: Data Model, Detection & Ticket Evidence complete*
|
||||
*Last updated: 2026-07-17 — v3.0 Phishing Triage Automation milestone shipped (Phases 15-23)*
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
- ✅ **v1.0 Mobile Shell Redesign** — Phases 1-9.1 (shipped 2026-07-10)
|
||||
- ✅ **v2.0 PAX8 Integration** — Phases 10-14 (shipped 2026-07-12)
|
||||
- 🚧 **v3.0 Phishing Triage Automation** — Phases 15-21 (in progress)
|
||||
- ✅ **v3.0 Phishing Triage Automation** — Phases 15-23 (shipped 2026-07-17)
|
||||
|
||||
## Phases
|
||||
|
||||
|
|
@ -410,14 +410,15 @@ render, including the manual-resolution workflow for flagged companies.
|
|||
|
||||
</details>
|
||||
|
||||
### 🚧 v3.0 Phishing Triage Automation (In Progress)
|
||||
<details>
|
||||
<summary>✅ v3.0 Phishing Triage Automation (Phases 15-23) - SHIPPED 2026-07-17</summary>
|
||||
|
||||
**Milestone Goal:** Detect candidate phishing/spam report tickets in Autotask, extract
|
||||
and parse original-message evidence, classify each as `SPAM` / `UNWANTED` / `THREAT`,
|
||||
group duplicate reports into campaigns, and prepare (never auto-execute) remediation
|
||||
actions behind an explicit human-approval gate.
|
||||
|
||||
Eight phases follow the domain's natural dependency chain rather than a generic
|
||||
Nine phases follow the domain's natural dependency chain rather than a generic
|
||||
foundation→features→polish template. Phase 15 lands the durable data model
|
||||
(campaigns/reports/messages/indicators/classifications/remediation_actions/
|
||||
audit_events, migration 097+) together with ticket detection and basic ticket-level
|
||||
|
|
@ -441,7 +442,10 @@ depends on the same Phase 19/20 outputs as Phase 21 but is otherwise independent
|
|||
it — a LiveLink button in Autotask is a separate configuration surface from the
|
||||
triage note's content, so Phase 22 does not need Phase 21 to land first; it is
|
||||
sequenced last only because it is the newest addition to this milestone, not because
|
||||
of a functional dependency on Phase 21.
|
||||
of a functional dependency on Phase 21. Phase 23 (Classification Disposition +
|
||||
Per-Client Automation Gate) was added after live review of a real Breach Secure Now
|
||||
report surfaced a gap — it depends on Phases 17-22 since it extends the classifier,
|
||||
the review UI, and the webhook automation path all at once.
|
||||
|
||||
- [x] **Phase 15: Data Model, Detection & Ticket Evidence** — New phishing schema (migration 097) + idempotent Autotask ticket scanner + base ticket evidence capture (completed 2026-07-15)
|
||||
- [x] **Phase 16: EML/MIME Evidence Parser** — Pure RFC822/MIME parser: `.eml` selection (`rfc.eml` over `OriginatingEmail.eml`), normalized headers/URLs/attachments, sanitized body preview, synthetic-fixture tests (completed 2026-07-15)
|
||||
|
|
@ -453,8 +457,6 @@ of a functional dependency on Phase 21.
|
|||
- [x] **Phase 22: Approval UI (LiveLink)** — Ticket-ID-addressable Pulse page (Autotask LiveLink target) showing campaign timeline, evidence, and classification, with approve/remediate/mark-false-positive wired to the Phase 20 APIs (completed 2026-07-16)
|
||||
- [x] **Phase 23: Classification Disposition + Per-Client Automation Gate** — Dedicated "User Awareness" verdict for confirmed phishing-simulation-vendor reports (currently forced into generic UNWANTED), plus an admin UI gate controlling per-company whether the phishing pipeline (parse/classify/report-to-ticket) runs automatically or requires manual trigger (completed 2026-07-17)
|
||||
|
||||
## Phase Details
|
||||
|
||||
### Phase 15: Data Model, Detection & Ticket Evidence
|
||||
|
||||
**Goal**: The durable phishing-triage schema exists in Postgres, and Pulse can scan Autotask/Pulse tickets for known phishing/spam-report patterns idempotently, capturing base ticket-level evidence for each candidate.
|
||||
|
|
@ -592,38 +594,6 @@ of a functional dependency on Phase 21.
|
|||
|
||||
**UI hint**: no
|
||||
|
||||
## Progress
|
||||
|
||||
**Execution Order:**
|
||||
Phases execute in numeric order. v1.0 (Phases 1-9.1) shipped 2026-07-10. v2.0 (Phases 10-14) shipped 2026-07-12. v3.0 phases run 15 → 16 → 17 → 18 → 19 → 20 → 21 → 22 in strict sequence — Phase 17 has no functional dependency on Phase 16 and could run in parallel with it if split across two workstreams, but both must complete before Phase 19; Phase 22 depends only on Phase 19 and Phase 20 and could equally run in parallel with Phase 21.
|
||||
|
||||
| Phase | Milestone | Plans Complete | Status | Completed |
|
||||
|-------|-----------|----------------|--------|-----------|
|
||||
| 1. PWA Scaffolding | v1.0 | 2/2 | Complete | 2026-07-10 |
|
||||
| 2. Mobile Shell + More Drawer | v1.0 | 2/2 | Complete | 2026-07-10 |
|
||||
| 3. Dashboard Restyle | v1.0 | 2/2 | Complete | 2026-07-10 |
|
||||
| 4. Tickets Restyle | v1.0 | 3/3 | Complete | 2026-07-10 |
|
||||
| 5. Finance Restyle | v1.0 | 2/2 | Complete | 2026-05-03 |
|
||||
| 6. Analyzer Feed | v1.0 | 3/3 | Complete | 2026-07-10 |
|
||||
| 7. Engagement Overview | v1.0 | 3/3 | Complete | 2026-07-10 |
|
||||
| 7.1. User Timezone Fix | v1.0 | 5/5 | Complete | 2026-07-10 |
|
||||
| 8. Engagement User Profile | v1.0 | 2/2 | Complete | 2026-07-10 |
|
||||
| 9. User Profile & Preferences | v1.0 | 6/6 | Complete | 2026-07-10 |
|
||||
| 9.1. ntfy Backend Fix | v1.0 | 1/1 | Complete | 2026-07-10 |
|
||||
| 10. PAX8 Client & Auth Foundation | v2.0 | 3/3 | Complete | 2026-07-10 |
|
||||
| 11. Company, Catalog & Subscription Sync | v2.0 | 3/3 | Complete | 2026-07-11 |
|
||||
| 12. Orders/Invoices & Company Matching | v2.0 | 5/5 | Complete | 2026-07-11 |
|
||||
| 13. Scheduler & Admin Toggle | v2.0 | 3/3 | Complete | 2026-07-11 |
|
||||
| 14. /pax8 UI Surface | v2.0 | 6/6 | Complete | 2026-07-12 |
|
||||
| 15. Data Model, Detection & Ticket Evidence | v3.0 | 3/3 | Complete | 2026-07-15 |
|
||||
| 16. EML/MIME Evidence Parser | v3.0 | 3/3 | Complete | 2026-07-15 |
|
||||
| 17. Mimecast Blast Radius Lookup | v3.0 | 1/1 | Complete | 2026-07-15 |
|
||||
| 18. Campaign Grouping & Phishing Analysis API | v3.0 | 5/5 | Complete | 2026-07-16 |
|
||||
| 19. Classification Engine | v3.0 | 2/2 | Complete | 2026-07-16 |
|
||||
| 20. Remediation, Approval & Audit Safety | v3.0 | 2/2 | Complete | 2026-07-16 |
|
||||
| 21. Autotask Triage Note | v3.0 | 2/2 | Complete | 2026-07-16 |
|
||||
| 22. Approval UI (LiveLink) | v3.0 | 6/6 | Complete | 2026-07-16 |
|
||||
|
||||
### Phase 22: Approval UI (LiveLink)
|
||||
|
||||
**Goal**: A security operator opens an Autotask ticket, clicks a LiveLink button, and lands on a Pulse page scoped to that ticket showing the campaign's timeline, evidence, and classification — with approve/remediate/mark-false-positive actions right there, so no one is calling the Phase 20 APIs by hand.
|
||||
|
|
@ -672,11 +642,46 @@ Plans:
|
|||
|
||||
- [x] 23-06-PLAN.md — Idempotent + audited auto-post: autoPostAcknowledgment prevents duplicate customer-visible notes on repeat campaign webhooks (AUTOGATE-03)
|
||||
|
||||
</details>
|
||||
|
||||
## Progress
|
||||
|
||||
**Execution Order:**
|
||||
Phases execute in numeric order. v1.0 (Phases 1-9.1) shipped 2026-07-10. v2.0 (Phases 10-14) shipped 2026-07-12. v3.0 (Phases 15-23) shipped 2026-07-17 — phases ran 15 → 16 → 17 → 18 → 19 → 20 → 21 → 22 → 23 in strict sequence; Phase 17 had no functional dependency on Phase 16 and could have run in parallel with it if split across two workstreams, but both had to complete before Phase 19; Phase 22 depended only on Phase 19 and Phase 20 and could equally have run in parallel with Phase 21; Phase 23 was a late addition depending on Phases 17-22.
|
||||
|
||||
| Phase | Milestone | Plans Complete | Status | Completed |
|
||||
|-------|-----------|----------------|--------|-----------|
|
||||
| 1. PWA Scaffolding | v1.0 | 2/2 | Complete | 2026-07-10 |
|
||||
| 2. Mobile Shell + More Drawer | v1.0 | 2/2 | Complete | 2026-07-10 |
|
||||
| 3. Dashboard Restyle | v1.0 | 2/2 | Complete | 2026-07-10 |
|
||||
| 4. Tickets Restyle | v1.0 | 3/3 | Complete | 2026-07-10 |
|
||||
| 5. Finance Restyle | v1.0 | 2/2 | Complete | 2026-05-03 |
|
||||
| 6. Analyzer Feed | v1.0 | 3/3 | Complete | 2026-07-10 |
|
||||
| 7. Engagement Overview | v1.0 | 3/3 | Complete | 2026-07-10 |
|
||||
| 7.1. User Timezone Fix | v1.0 | 5/5 | Complete | 2026-07-10 |
|
||||
| 8. Engagement User Profile | v1.0 | 2/2 | Complete | 2026-07-10 |
|
||||
| 9. User Profile & Preferences | v1.0 | 6/6 | Complete | 2026-07-10 |
|
||||
| 9.1. ntfy Backend Fix | v1.0 | 1/1 | Complete | 2026-07-10 |
|
||||
| 10. PAX8 Client & Auth Foundation | v2.0 | 3/3 | Complete | 2026-07-10 |
|
||||
| 11. Company, Catalog & Subscription Sync | v2.0 | 3/3 | Complete | 2026-07-11 |
|
||||
| 12. Orders/Invoices & Company Matching | v2.0 | 5/5 | Complete | 2026-07-11 |
|
||||
| 13. Scheduler & Admin Toggle | v2.0 | 3/3 | Complete | 2026-07-11 |
|
||||
| 14. /pax8 UI Surface | v2.0 | 6/6 | Complete | 2026-07-12 |
|
||||
| 15. Data Model, Detection & Ticket Evidence | v3.0 | 3/3 | Complete | 2026-07-15 |
|
||||
| 16. EML/MIME Evidence Parser | v3.0 | 3/3 | Complete | 2026-07-15 |
|
||||
| 17. Mimecast Blast Radius Lookup | v3.0 | 1/1 | Complete | 2026-07-15 |
|
||||
| 18. Campaign Grouping & Phishing Analysis API | v3.0 | 5/5 | Complete | 2026-07-16 |
|
||||
| 19. Classification Engine | v3.0 | 2/2 | Complete | 2026-07-16 |
|
||||
| 20. Remediation, Approval & Audit Safety | v3.0 | 2/2 | Complete | 2026-07-16 |
|
||||
| 21. Autotask Triage Note | v3.0 | 2/2 | Complete | 2026-07-16 |
|
||||
| 22. Approval UI (LiveLink) | v3.0 | 6/6 | Complete | 2026-07-16 |
|
||||
| 23. Classification Disposition + Per-Client Automation Gate | v3.0 | 6/6 | Complete | 2026-07-17 |
|
||||
|
||||
---
|
||||
*Roadmap created: 2026-05-03*
|
||||
*v2.0 phases added: 2026-07-10*
|
||||
*v3.0 phases added: 2026-07-14 (Phases 15-21), 2026-07-16 (Phase 22)*
|
||||
*v3.0 phases added: 2026-07-14 (Phases 15-21), 2026-07-16 (Phase 22, Phase 23), shipped 2026-07-17*
|
||||
*Source spec (v1.0): `docs/superpowers/specs/2026-05-03-mobile-shell-design.md`*
|
||||
*Source seed (v2.0): `.planning/seeds/SEED-002-pax8-integration.md`*
|
||||
*Source requirements (v3.0): `.planning/REQUIREMENTS.md`*
|
||||
*Source requirements (v3.0, archived): `.planning/milestones/v3.0-REQUIREMENTS.md`*
|
||||
</content>
|
||||
|
|
|
|||
|
|
@ -2,16 +2,16 @@
|
|||
gsd_state_version: 1.0
|
||||
milestone: v3.0
|
||||
milestone_name: Phishing Triage Automation
|
||||
status: milestone_complete
|
||||
stopped_at: Milestone complete (Phase 23 was final phase)
|
||||
last_updated: 2026-07-17T03:33:49.428Z
|
||||
last_activity: 2026-07-17 -- Phase 23 execution started
|
||||
status: Awaiting next milestone
|
||||
stopped_at: Phase 23 context gathered
|
||||
last_updated: "2026-07-17T10:55:33.806Z"
|
||||
last_activity: 2026-07-17 — Milestone v3.0 completed and archived
|
||||
progress:
|
||||
total_phases: 9
|
||||
completed_phases: 8
|
||||
completed_phases: 9
|
||||
total_plans: 30
|
||||
completed_plans: 30
|
||||
percent: 89
|
||||
percent: 100
|
||||
---
|
||||
|
||||
# Project State
|
||||
|
|
@ -25,12 +25,10 @@ See: .planning/PROJECT.md (updated 2026-07-14)
|
|||
|
||||
## Current Position
|
||||
|
||||
Phase: 23
|
||||
Plan: Not started
|
||||
Status: Milestone complete
|
||||
Last activity: 2026-07-17
|
||||
|
||||
Progress: [░░░░░░░░░░] 0%
|
||||
Phase: Milestone v3.0 complete
|
||||
Plan: —
|
||||
Status: Awaiting next milestone
|
||||
Last activity: 2026-07-17 — Milestone v3.0 completed and archived
|
||||
|
||||
## Performance Metrics
|
||||
|
||||
|
|
@ -162,3 +160,7 @@ Last session: 2026-07-16T22:37:09.979Z
|
|||
Stopped at: Phase 23 context gathered
|
||||
Resume file: .planning/phases/23-classification-disposition-per-client-automation-gate/23-CONTEXT.md
|
||||
</content>
|
||||
|
||||
## Operator Next Steps
|
||||
|
||||
- Start the next milestone with /gsd-new-milestone
|
||||
|
|
|
|||
263
.planning/milestones/v3.0-REQUIREMENTS.md
Normal file
263
.planning/milestones/v3.0-REQUIREMENTS.md
Normal file
|
|
@ -0,0 +1,263 @@
|
|||
# Requirements Archive: v3.0 Phishing Triage Automation
|
||||
|
||||
**Archived:** 2026-07-17
|
||||
**Status:** SHIPPED
|
||||
|
||||
For current requirements, see `.planning/REQUIREMENTS.md`.
|
||||
|
||||
---
|
||||
|
||||
# Requirements: Pulse — v3.0 Phishing Triage Automation
|
||||
|
||||
**Defined:** 2026-07-14
|
||||
**Core Value:** A manager/security operator can see every phishing/spam report ticket
|
||||
automatically triaged, deduplicated into campaigns, and classified — with any
|
||||
destructive remediation gated behind explicit human approval.
|
||||
|
||||
## v1 Requirements
|
||||
|
||||
### Detection
|
||||
|
||||
- [x] **DETECT-01**: System scans recent Autotask/Pulse tickets and flags candidates
|
||||
matching known phishing/spam-report patterns (title/body: "Phishing Report",
|
||||
"Spam Alert", "Phishing Alert - Email Security Report", "KnowBe4 Phish Alert
|
||||
Report", "Source: KnowBe4 Phish Alert Button", "userSubmissionsReportMessage",
|
||||
"reported message destinations", "Microsoft directly")
|
||||
- [x] **DETECT-02**: Re-scanning does not reprocess a ticket already ingested unless
|
||||
its source ticket data has changed since last processed (idempotent)
|
||||
- [x] **DETECT-03**: An operator can trigger analysis of one specific ticket by ID
|
||||
on demand (`POST /api/phishing/tickets/{ticket_id}/analyze`) instead of waiting
|
||||
for the scheduled scan
|
||||
|
||||
### Evidence Extraction
|
||||
|
||||
- [x] **EVID-01**: For each candidate ticket, the system extracts ticket ID/number,
|
||||
company, requester/reporter, title, description, notes, relevant time entries,
|
||||
and attachment metadata
|
||||
- [x] **EVID-02**: When multiple `.eml` attachments exist, the system prefers
|
||||
`rfc.eml` as the original reported message over `OriginatingEmail.eml`
|
||||
(wrapper/context), matching case-insensitively and by `message/rfc822`
|
||||
content-type, not filename alone
|
||||
- [x] **EVID-03**: The system parses the selected original email's RFC822/MIME
|
||||
structure into normalized headers (From, display name, sender email/domain,
|
||||
Reply-To, Return-Path, To, Cc, Subject, Date, Message-ID, Received chain,
|
||||
SPF/DKIM/DMARC authentication results), extracted URLs, and attachment
|
||||
metadata (name, content-type, size, hash)
|
||||
- [x] **EVID-04**: The system stores a sanitized/truncated body preview alongside
|
||||
raw evidence, and never executes or fetches any URL found in a message
|
||||
|
||||
### Campaign Grouping
|
||||
|
||||
- [x] **CAMP-01**: Reports are grouped into a campaign using original Message-ID
|
||||
first, then attachment-hash/URL-domain + subject + sender + time-window, then
|
||||
sender + normalized subject + client + time-window as fallback keys
|
||||
- [x] **CAMP-02**: A campaign can accumulate many linked ticket reports and
|
||||
recipients over time as duplicates are detected
|
||||
- [x] **CAMP-03**: An operator can list campaigns and view a single campaign's
|
||||
full detail (linked reports, messages, indicators, classification history)
|
||||
via API (`GET /api/phishing/campaigns`, `GET /api/phishing/campaigns/{id}`)
|
||||
|
||||
### Blast Radius (Mimecast)
|
||||
|
||||
- [x] **BLAST-01**: The system can query a Mimecast blast-radius abstraction for
|
||||
message delivery data (matched/delivered/held/rejected/clicked counts,
|
||||
per-recipient status) when Mimecast is configured, keyed on message ID,
|
||||
sender, recipient/reporter, subject, and date window
|
||||
- [x] **BLAST-02**: When Mimecast is not configured, the system records
|
||||
`status: unavailable` for that lookup and classification proceeds using
|
||||
ticket/email evidence alone — it never blocks on missing Mimecast config
|
||||
|
||||
### Classification
|
||||
|
||||
- [x] **CLASSIFY-01**: The system classifies a campaign as exactly one of
|
||||
`SPAM` / `UNWANTED` / `THREAT`, with confidence, a short summary,
|
||||
evidence-backed reasons, recommended actions, and a `requires_approval` flag
|
||||
- [x] **CLASSIFY-02**: Any classification recommending a destructive action
|
||||
(purge, block, delete, reset, etc.) always sets `requires_approval: true`
|
||||
- [x] **CLASSIFY-03**: When evidence is incomplete (no Mimecast data, no `.eml`,
|
||||
etc.), confidence is lowered and the missing evidence is named in the reasons
|
||||
- [x] **CLASSIFY-04**: Known/expected KnowBe4 security-awareness simulations are
|
||||
not classified as `THREAT` absent contrary evidence
|
||||
- [x] **CLASSIFY-05**: An operator can (re-)trigger classification of a campaign
|
||||
via API (`POST /api/phishing/campaigns/{id}/classify`)
|
||||
- [x] **CLASSIFY-06**: The classifier accepts structured, size-bounded evidence
|
||||
(not raw unbounded email bodies) — long bodies are redacted/truncated before
|
||||
reaching any AI layer, and IT Glue-sourced evidence goes through the existing
|
||||
redacted search path if referenced
|
||||
|
||||
### Remediation & Approval Safety
|
||||
|
||||
- [x] **REMED-01**: Recommended remediation actions are recorded as `proposed`
|
||||
but never executed automatically in this milestone
|
||||
- [x] **REMED-02**: An authorized operator can approve a campaign's remediation
|
||||
via API (`POST /api/phishing/campaigns/{id}/approve`), recording approver,
|
||||
timestamp, and the exact approved action parameters
|
||||
- [x] **REMED-03**: `POST /api/phishing/campaigns/{id}/remediate` proceeds only
|
||||
for approved actions against a configured, non-destructive-by-default
|
||||
provider path; otherwise it returns `not_implemented`/an explicit failure —
|
||||
it never silently succeeds without taking or logging an action
|
||||
- [x] **REMED-04**: Re-running remediation against an already-completed action
|
||||
does not duplicate the destructive effect (idempotent)
|
||||
- [x] **REMED-05**: An operator can mark a campaign as a false positive via API
|
||||
(`POST /api/phishing/campaigns/{id}/mark-false-positive`)
|
||||
- [x] **REMED-06**: Every state-changing action (classify, approve, remediate,
|
||||
mark-false-positive) is recorded as an audit event with actor, event type,
|
||||
and payload
|
||||
|
||||
### Autotask Integration
|
||||
|
||||
- [x] **NOTE-01**: If Pulse already has a safe Autotask note-writing method, the
|
||||
system can post an internal triage note summarizing classification, evidence,
|
||||
blast radius, and recommended actions (sanitized — no raw secrets/tokens/full
|
||||
malicious URL query strings); otherwise the note text is returned via API
|
||||
without writing anything to Autotask
|
||||
|
||||
### Access Control
|
||||
|
||||
- [x] **ACCESS-01**: All `/api/phishing/*` endpoints enforce existing Pulse auth
|
||||
conventions (`requireAuth`/`requirePermission`), with approve/remediate
|
||||
requiring elevated permission beyond plain read access
|
||||
|
||||
### Approval UI (LiveLink)
|
||||
|
||||
- [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
|
||||
campaign UUID), authenticated via the existing Better Auth session only —
|
||||
no separate token or query-param auth scheme
|
||||
- [x] **REVIEW-02**: The page displays the campaign's timeline — linked
|
||||
reports, classification history, and audit events (classify/approve/
|
||||
remediate/mark-false-positive) — in chronological order
|
||||
- [x] **REVIEW-03**: The page displays the gathered evidence — parsed EML
|
||||
headers/URLs/attachments, sanitized body preview, and Mimecast blast-radius
|
||||
data (including an explicit `unavailable` state when Mimecast isn't
|
||||
configured) — never rendering a raw/unsanitized body or unredacted secrets
|
||||
- [x] **REVIEW-04**: The page displays the current classification (SPAM/
|
||||
UNWANTED/THREAT), confidence, reasons, and recommended remediation
|
||||
action(s)
|
||||
- [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)
|
||||
- [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
|
||||
|
||||
### Classification Disposition + Automation Gate
|
||||
|
||||
- [x] **CLASSDISP-01**: The classifier assigns a dedicated `USER_AWARENESS`
|
||||
verdict to campaigns confirmed as phishing-simulation-vendor (KnowBe4/Breach
|
||||
Secure Now) reports, replacing the previous forced-`UNWANTED` disposition
|
||||
for these confirmed-simulation cases
|
||||
- [x] **CLASSDISP-02**: `USER_AWARENESS` maps to a new non-destructive
|
||||
`acknowledge_user` action that posts a customer-visible thank-you note
|
||||
(Autotask `noteType: 18`, "Client Portal Note") to the reporting employee
|
||||
- [x] **CLASSDISP-03**: The campaign review UI surfaces `USER_AWARENESS` and
|
||||
`acknowledge_user` distinctly from the existing SPAM/UNWANTED/THREAT
|
||||
verdicts and their actions
|
||||
- [x] **AUTOGATE-01**: A per-company `phishing_automation_gate` table and
|
||||
admin-gated `GET`/`PATCH`/`DELETE` API let an operator read and set three
|
||||
independent opt-in automation flags (`auto_parse`, `auto_classify`,
|
||||
`auto_report`) per Autotask company, defaulting to all-OFF when no row
|
||||
exists
|
||||
- [x] **AUTOGATE-02**: An `/admin/phishing-automation` page lists companies
|
||||
with three independent per-company `Switch` toggles (one per automation
|
||||
stage), backed by the `AUTOGATE-01` API
|
||||
- [x] **AUTOGATE-03**: When a company's automation gate stages are enabled,
|
||||
the Autotask webhook automatically runs the gated parse→classify→acknowledge
|
||||
chain for that company's phishing reports, with the `acknowledge_user`
|
||||
auto-post carve-out narrowly scoped to `USER_AWARENESS` verdicts only — all
|
||||
other verdicts/actions still require manual approval regardless of gate
|
||||
state
|
||||
|
||||
## v2 Requirements
|
||||
|
||||
Deferred to future release. Tracked but not in current roadmap.
|
||||
|
||||
### Remediation Execution
|
||||
|
||||
- **REMEDEXEC-01**: Actually execute Mimecast sender/domain/URL block (behind
|
||||
approval, once REMED-01..06 land and are trusted)
|
||||
- **REMEDEXEC-02**: Microsoft Graph mailbox search/delete/move for delivered
|
||||
copies
|
||||
- **REMEDEXEC-03**: Microsoft Defender/Exchange purge (may be preferable to
|
||||
Graph — needs its own evaluation)
|
||||
- **REMEDEXEC-04**: Auto-create/associate an Autotask parent incident ticket
|
||||
- **REMEDEXEC-05**: Auto-close duplicate reports once a campaign is resolved
|
||||
|
||||
### Enrichment
|
||||
|
||||
- **ENRICH-01**: URL reputation lookup / safe expansion of shortened links
|
||||
(without detonation)
|
||||
- **ENRICH-02**: LLM-backed classification layer wired into the
|
||||
`CLASSIFY-*` rule engine (rule layer ships first; this plugs in behind the
|
||||
same interface)
|
||||
|
||||
## Out of Scope
|
||||
|
||||
| Feature | Reason |
|
||||
|---------|--------|
|
||||
| Automatic tenant-wide mailbox purge | Destructive; requires proven accuracy and explicit approval infrastructure first (REMED-01..04) |
|
||||
| Automatic password reset / session revocation | Too high-blast-radius for an MVP triage tool; human-in-the-loop only |
|
||||
| URL detonation / sandbox execution | Security risk of interacting with malicious infra; reputation-only enrichment deferred to v2 |
|
||||
| Fully automated ticket closure | Accuracy not yet proven; operator closes tickets manually in this milestone |
|
||||
| Assuming Microsoft Graph is the eventual purge mechanism | Defender/Exchange purge may be preferable; decision deferred to whichever v2 remediation-execution phase picks it up |
|
||||
| Real customer `.eml` fixtures in tests | Privacy/security — all test fixtures are synthetic |
|
||||
|
||||
## Traceability
|
||||
|
||||
Populated during roadmap creation.
|
||||
|
||||
| Requirement | Phase | Status |
|
||||
|-------------|-------|--------|
|
||||
| DETECT-01 | Phase 15 | Complete |
|
||||
| DETECT-02 | Phase 15 | Complete |
|
||||
| DETECT-03 | Phase 18 | Complete |
|
||||
| EVID-01 | Phase 15 | Complete |
|
||||
| EVID-02 | Phase 16 | Complete |
|
||||
| EVID-03 | Phase 16 | Complete |
|
||||
| EVID-04 | Phase 16 | Complete |
|
||||
| CAMP-01 | Phase 18 | Complete |
|
||||
| CAMP-02 | Phase 18 | Complete |
|
||||
| CAMP-03 | Phase 18 | Complete |
|
||||
| BLAST-01 | Phase 17 | Complete |
|
||||
| BLAST-02 | Phase 17 | Complete |
|
||||
| CLASSIFY-01 | Phase 19 | Complete |
|
||||
| CLASSIFY-02 | Phase 19 | Complete |
|
||||
| CLASSIFY-03 | Phase 19 | Complete |
|
||||
| CLASSIFY-04 | Phase 19 | Complete |
|
||||
| CLASSIFY-05 | Phase 19 | Complete |
|
||||
| CLASSIFY-06 | Phase 19 | Complete |
|
||||
| REMED-01 | Phase 20 | Complete |
|
||||
| REMED-02 | Phase 20 | Complete |
|
||||
| REMED-03 | Phase 20 | Complete |
|
||||
| REMED-04 | Phase 20 | Complete |
|
||||
| REMED-05 | Phase 20 | Complete |
|
||||
| REMED-06 | Phase 20 | Complete |
|
||||
| NOTE-01 | Phase 21 | Complete |
|
||||
| ACCESS-01 | Phase 18 | Complete |
|
||||
| REVIEW-01 | Phase 22 | Complete |
|
||||
| REVIEW-02 | Phase 22 | Complete |
|
||||
| REVIEW-03 | Phase 22 | Complete |
|
||||
| REVIEW-04 | Phase 22 | Complete |
|
||||
| REVIEW-05 | Phase 22 | Complete |
|
||||
| REVIEW-06 | Phase 22 | Complete |
|
||||
| CLASSDISP-01 | Phase 23 | Complete |
|
||||
| CLASSDISP-02 | Phase 23 | Complete |
|
||||
| CLASSDISP-03 | Phase 23 | Complete |
|
||||
| AUTOGATE-01 | Phase 23 | Complete |
|
||||
| AUTOGATE-02 | Phase 23 | Complete |
|
||||
| AUTOGATE-03 | Phase 23 | Complete |
|
||||
|
||||
**Coverage:**
|
||||
- v1 requirements: 38 total
|
||||
- Mapped to phases: 38 (Phases 15-23)
|
||||
- Unmapped: 0 ✓
|
||||
|
||||
---
|
||||
*Requirements defined: 2026-07-14*
|
||||
*Traceability populated: 2026-07-14 — ROADMAP.md Phases 15-21*
|
||||
*Last updated: 2026-07-16 — backfilled Phase 23 CLASSDISP-*/AUTOGATE-* entries (23-03)*
|
||||
687
.planning/milestones/v3.0-ROADMAP.md
Normal file
687
.planning/milestones/v3.0-ROADMAP.md
Normal file
|
|
@ -0,0 +1,687 @@
|
|||
# Roadmap: Pulse
|
||||
|
||||
## Milestones
|
||||
|
||||
- ✅ **v1.0 Mobile Shell Redesign** — Phases 1-9.1 (shipped 2026-07-10)
|
||||
- ✅ **v2.0 PAX8 Integration** — Phases 10-14 (shipped 2026-07-12)
|
||||
- ✅ **v3.0 Phishing Triage Automation** — Phases 15-23 (shipped 2026-07-17)
|
||||
|
||||
## Phases
|
||||
|
||||
**Phase Numbering:**
|
||||
|
||||
- Integer phases (1, 2, 3): Planned milestone work
|
||||
- Decimal phases (2.1, 2.2): Urgent insertions (marked with INSERTED)
|
||||
|
||||
Decimal phases appear between their surrounding integers in numeric order.
|
||||
|
||||
<details>
|
||||
<summary>✅ v1.0 Mobile Shell Redesign (Phases 1-9.1) - SHIPPED 2026-07-10</summary>
|
||||
|
||||
Eight phases mirror the deliberate build order in the source spec
|
||||
(`docs/superpowers/specs/2026-05-03-mobile-shell-design.md` §8). Each phase
|
||||
shipped independently to `master` — no big-bang merge. Phase 1 laid PWA
|
||||
metadata and safe-area utilities. Phase 2 rebuilt `app/mobile/layout.tsx`
|
||||
with the new header, 5-cell bottom nav, and More drawer (deleting
|
||||
`/mobile/nav` in the same change). Once the shell landed, Phases 3–7 were
|
||||
independent restyles/new pages; Phase 8 followed Phase 7 because the user
|
||||
profile is reached from the Engagement overview. All work happened in place
|
||||
under `/mobile/*` — no `/mobile-v2`, no parallel routes.
|
||||
|
||||
- [x] **Phase 1: PWA Scaffolding** — Manifest, viewport meta, and safe-area utilities so the shell installs and paints under the home indicator
|
||||
- [x] **Phase 2: Mobile Shell + More Drawer** — New `app/mobile/layout.tsx` (header + 5-cell bottom nav) and Sheet drawer that replaces `/mobile/nav`
|
||||
- [x] **Phase 3: Dashboard Restyle** — 2×2 KPI grid, Needs Attention strip, worker/backup status row (no charts)
|
||||
- [x] **Phase 4: Tickets Restyle** — Collapsible URL-synced filters, priority-bar rows, cursor-based infinite scroll, detail header reskin
|
||||
- [x] **Phase 5: Finance Restyle** — Adopt new Card + typography scale, swap wide tables for stacked lists (completed 2026-05-03)
|
||||
- [x] **Phase 6: Analyzer Feed (NEW)** — `/mobile/analyzer` read-only stream + `/api/mobile/analyzer/feed`
|
||||
- [x] **Phase 7: Engagement Overview (NEW)** — `/mobile/engagement` phone-first overview reachable from the More drawer
|
||||
- [x] **Phase 7.1: User Timezone Fix (INSERTED — urgent)** — Per-user IANA timezone column + viewer-tz date math so dashboards and filters render the right "today"
|
||||
- [x] **Phase 8: Engagement User Profile (NEW)** — `/mobile/engagement/[userId]` real-page profile that replaces the desktop modal pattern
|
||||
- [x] **Phase 9: User Profile & Preferences (NEW)** — `/mobile/profile` settings page (timezone chooser, theme, mobile push, Teams + ntfy channels)
|
||||
- [x] **Phase 9.1: ntfy Backend Fix (INSERTED — urgent)** — Personal ntfy channels target the company ntfy server with bearer auth + `pulse-me-` prefix (UAT gap closure)
|
||||
|
||||
### Phase 1: PWA Scaffolding
|
||||
|
||||
**Goal**: A manager who taps "Add to Home Screen" gets a standalone Pulse icon that opens to the mobile shell with content respecting the device safe areas.
|
||||
**Depends on**: Nothing (first phase)
|
||||
**Requirements**: PWA-01, PWA-02, PWA-03, PWA-04
|
||||
**Success Criteria** (what must be TRUE):
|
||||
|
||||
1. Visiting `/manifest.json` returns valid JSON with `name: "Pulse"`, `display: "standalone"`, `start_url: "/mobile"`, and theme/background colors matching the app shells
|
||||
2. The root `app/layout.tsx` references the manifest via `<link rel="manifest">` and the viewport meta includes `viewport-fit=cover`
|
||||
3. A safe-area utility (Tailwind arbitrary values or shared class) is available so any sticky top/bottom bar can opt into `env(safe-area-inset-top)` / `env(safe-area-inset-bottom)` padding
|
||||
4. Installing Pulse to a phone home screen launches a chromeless app pointed at `/mobile` (no service worker, no offline)
|
||||
|
||||
**Plans**: 2 plans
|
||||
|
||||
- [x] 01-01-PLAN.md — Web App Manifest + viewport-fit=cover (PWA-01, PWA-02, PWA-03)
|
||||
- [x] 01-02-PLAN.md — Safe-area `pt-safe` / `pb-safe` @utility blocks in brand.css (PWA-04, gap closure)
|
||||
|
||||
**UI hint**: no
|
||||
|
||||
### Phase 2: Mobile Shell + More Drawer
|
||||
|
||||
**Goal**: Every `/mobile/*` page renders inside a new layout — sticky header (Wulf mark + Bell placeholder + avatar), scrollable content, and a 5-cell bottom nav whose fifth control opens a Sheet drawer that fully replaces `/mobile/nav`.
|
||||
**Depends on**: Phase 1
|
||||
**Requirements**: SHELL-01, SHELL-02, SHELL-03, SHELL-04, SHELL-05, SHELL-06, NAV-01, NAV-02, NAV-03, DRAWER-01, DRAWER-02, DRAWER-03, DRAWER-04, DRAWER-05, DRAWER-06
|
||||
**Success Criteria** (what must be TRUE):
|
||||
|
||||
1. On any `/mobile/*` route the user sees a sticky header with the Wulf wordmark linking to `/mobile/dashboard`, a Bell icon button (keyboard-focusable, no menu), and a compact avatar — no page title in the header
|
||||
2. A fixed bottom bar exposes four primary tabs (Dashboard, Tickets, Finance, Analyzer) plus a More cell; tapping a tab routes to its page and the active tab uses `text-primary` based on `pathname.startsWith(href)`
|
||||
3. Tapping More (or the header avatar) opens a single Sheet drawer with three sections — Mobile sections (Engagement), Full site (Quotes, Configuration Items, Backup Status, Ticket Digest, Admin/Sync — each with an `ExternalLink` hint), and Account (current user read-only + Sign out)
|
||||
4. Tapping Sign out in the drawer signs the user out and lands them on `/auth/sign-in`
|
||||
5. `app/mobile/nav/page.tsx` no longer exists; visiting `/mobile/nav` does not render the old standalone nav page
|
||||
6. Page content scrolls under the sticky header and is not hidden behind the bottom nav (bottom padding accounts for nav height + safe-area inset)
|
||||
|
||||
**Plans**: 2 plans
|
||||
|
||||
- [x] 02-01-PLAN.md — Build mobile shell components (HeaderBar, BottomNav, MoreDrawer) + analyzer placeholder (SHELL-02..04, SHELL-06, NAV-01..03, DRAWER-01..05)
|
||||
- [x] 02-02-PLAN.md — Wire new components into app/mobile/layout.tsx, delete app/mobile/nav/page.tsx (SHELL-01, SHELL-05, DRAWER-06)
|
||||
|
||||
**UI hint**: yes
|
||||
|
||||
### Phase 3: Dashboard Restyle
|
||||
|
||||
**Goal**: A manager opening `/mobile/dashboard` sees the state of the business at a glance — four KPIs, items needing attention, and a worker/backup status row — with no charts.
|
||||
**Depends on**: Phase 2
|
||||
**Requirements**: DASH-01, DASH-02, DASH-03, DASH-04
|
||||
**Success Criteria** (what must be TRUE):
|
||||
|
||||
1. Dashboard renders a 2×2 grid of four primary KPI cards drawn from desktop hero stats (no 1×4 row, no charts)
|
||||
2. Below the grid, a "Needs Attention" horizontally-scrollable strip surfaces overdue tickets, failed backups, and stalled workflows; tapping a card opens its detail view
|
||||
3. A compact status row shows analyzer worker, RMM worker, and backup-success-rate; tapping any element opens the corresponding desktop admin page
|
||||
4. The page contains no recharts/chart components on phone widths
|
||||
|
||||
**Plans**: 2 plans
|
||||
|
||||
- [x] 03-01-PLAN.md — /api/mobile/dashboard reshape + KpiCardMobile/NeedsAttentionStrip/WorkerStatusRow components (DASH-01, DASH-02, DASH-03)
|
||||
- [x] 03-02-PLAN.md — Replace /mobile/dashboard page body with 3-section layout, no charts (DASH-01, DASH-02, DASH-03, DASH-04)
|
||||
|
||||
**UI hint**: yes
|
||||
|
||||
### Phase 4: Tickets Restyle
|
||||
|
||||
**Goal**: A manager triages tickets on a phone with a collapsible filter bar that deep-links via URL, priority-coloured rows, and infinite scroll — and the detail page header matches the new shell.
|
||||
**Depends on**: Phase 2
|
||||
**Requirements**: TICK-01, TICK-02, TICK-03, TICK-04, TICK-05, TICK-06, TICK-07
|
||||
**Success Criteria** (what must be TRUE):
|
||||
|
||||
1. The Tickets page opens with the filter strip collapsed; expanding it reveals status, priority, queue, and an assigned-to-me toggle, and changing any filter updates the URL query string (deep link works on reload)
|
||||
2. Each list row has a left-edge stripe matching priority (Critical/High/Medium/Low → red/orange/amber/slate) and shows ticket #, title, company, age, and assignee
|
||||
3. Single-tapping a row navigates to `/mobile/tickets/[id]`
|
||||
4. Scrolling to the bottom of the list automatically loads the next ~25 rows (no Next button); a "Load more" fallback button is also visible/focusable for accessibility
|
||||
5. The detail page header uses the new shell styling (Wulf mark, breadcrumb back) while the body remains largely unchanged
|
||||
|
||||
**Plans**: 3 plans
|
||||
|
||||
- [x] 04-01-PLAN.md — /api/mobile/tickets cursor rewrite + TicketFilterStrip + TicketRowSkeleton components (TICK-01, TICK-02, TICK-05)
|
||||
- [x] 04-02-PLAN.md — Replace app/mobile/tickets/page.tsx with URL-synced filters, priority-stripe rows, IntersectionObserver infinite scroll (TICK-01..TICK-06)
|
||||
- [x] 04-03-PLAN.md — Reskin in-page header of app/mobile/tickets/[id]/page.tsx (back chevron + breadcrumb + ExternalLink) (TICK-07)
|
||||
|
||||
**UI hint**: yes
|
||||
|
||||
### Phase 5: Finance Restyle
|
||||
|
||||
**Goal**: A manager reading AR / invoice / payment status on a phone sees properly spaced cards and stacked lists instead of squished wide tables — same data, new shell.
|
||||
**Depends on**: Phase 2
|
||||
**Requirements**: FIN-01, FIN-02
|
||||
**Success Criteria** (what must be TRUE):
|
||||
|
||||
1. `/mobile/finance` adopts the new Card and typography scale — no horizontal overflow, spacing legible on small phones
|
||||
2. Sections that previously rendered wide tables on phone widths now render as stacked lists (no new sections, no new data sources)
|
||||
|
||||
**Plans**: 2 plans
|
||||
|
||||
- [x] 05-01-PLAN.md — FinanceRow + FinanceSkeleton helper components (FIN-01, FIN-02)
|
||||
- [x] 05-02-PLAN.md — Rewrite app/mobile/finance/page.tsx to KPI grid + stacked lists + shadcn Collapsibles (FIN-01, FIN-02)
|
||||
|
||||
**UI hint**: yes
|
||||
|
||||
### Phase 6: Analyzer Feed (NEW)
|
||||
|
||||
**Goal**: A manager taps the Analyzer tab and skims a most-recent-first stream of AI ticket analyses, opening any one to a phone-friendly summary view that links out to desktop for full details.
|
||||
**Depends on**: Phase 2
|
||||
**Requirements**: ANL-01, ANL-02, ANL-03, ANL-04, ANL-05, ANL-06
|
||||
**Success Criteria** (what must be TRUE):
|
||||
|
||||
1. Tapping the Analyzer tab in the bottom nav lands on `/mobile/analyzer` and shows a most-recent-first list of AI ticket analyses
|
||||
2. Each row shows ticket #, title, the analyzer's one-line summary, a confidence badge, and a stage indicator (Triage → Analyze → Deep Review)
|
||||
3. Tapping a row opens a mobile summary view rendering Summary, Next Step, and Next Step Rationale, with a "View full analysis" link out to the desktop analyzer page
|
||||
4. The mobile feed never exposes editing, re-run, or prompt-tuning controls (read-only by design)
|
||||
5. The list reads from `analyzer_analyses` via `/api/mobile/analyzer/feed` (or a reused list endpoint that already returns the right shape)
|
||||
|
||||
**Plans**: 3 plans
|
||||
|
||||
- [x] 06-01-PLAN.md — /api/mobile/analyzer/feed endpoint with cursor pagination + kiosk_settings scoping (ANL-01, ANL-02, ANL-06)
|
||||
- [x] 06-02-PLAN.md — AnalyzerFeedRow/StagePips/ConfidenceBadge/RowSkeleton components + replace /mobile/analyzer placeholder with feed list page (ANL-01, ANL-02, ANL-05, ANL-06)
|
||||
- [x] 06-03-PLAN.md — /mobile/analyzer/[id] detail page reading existing /api/analyzer/analyses/[id] (ANL-03, ANL-04, ANL-05)
|
||||
|
||||
**UI hint**: yes
|
||||
|
||||
### Phase 7: Engagement Overview (NEW)
|
||||
|
||||
**Goal**: A manager reaches Engagement from the More drawer and sees a phone-first overview — period chips, stacked summary cards, a sortable per-employee list, and one compact sparkline.
|
||||
**Depends on**: Phase 2
|
||||
**Requirements**: ENG-01, ENG-02, ENG-03, ENG-04, ENG-05, ENG-09
|
||||
**Success Criteria** (what must be TRUE):
|
||||
|
||||
1. The Mobile sections row in the More drawer links to `/mobile/engagement`; the Analyzer is on the bottom bar but Engagement is not
|
||||
2. The overview page shows a period selector (today / 7d / 30d) sticky just below the H1, with active period clearly indicated
|
||||
3. Summary cards (active users, total Graph hours, total Autotask hours, hours-per-active-user) render single-column stacked — no 4-up grid on phone widths
|
||||
4. The per-employee list renders as stacked rows (avatar/initials, name, role, hours bar) with a search input and a sort control above (sort by hours, name, utilization)
|
||||
5. A single compact "hours trend" sparkline renders at the top of the list, scoped to the selected period — no multi-series chart
|
||||
|
||||
**Plans**: 3 plans
|
||||
|
||||
- [x] 07-01-PLAN.md — /api/mobile/engagement/summary + /api/mobile/engagement/trend endpoints with period whitelist + requireAuth (ENG-03, ENG-05)
|
||||
- [x] 07-02-PLAN.md — Engagement* mobile components (PeriodChips, SummaryCard, HoursSparkline, SortChips, SearchInput, UserRow, UserRowSkeleton + getInitials utility) (ENG-02, ENG-03, ENG-04, ENG-05)
|
||||
- [x] 07-03-PLAN.md — app/mobile/engagement/page.tsx orchestration (period/sort state, IntersectionObserver, empty/error/not-configured states) (ENG-01, ENG-02, ENG-03, ENG-04, ENG-05, ENG-09)
|
||||
|
||||
**UI hint**: yes
|
||||
|
||||
### Phase 7.1: User Timezone Fix (INSERTED — urgent)
|
||||
|
||||
**Goal**: A user opening Pulse sees dashboards, filters, and "today/this week" date math computed in their own IANA timezone — not server UTC — so reports stop showing yesterday's data as today (and vice versa). Persistence layer remains UTC; only the read/display path changes.
|
||||
**Depends on**: Nothing structural (Better Auth users table extension + read-path changes)
|
||||
**Requirements**: TZ-01, TZ-02, TZ-03, TZ-04
|
||||
**Success Criteria** (what must be TRUE):
|
||||
|
||||
1. Each user has an IANA timezone (e.g. `America/New_York`) persisted server-side; default = `process.env.DEFAULT_TIMEZONE || 'UTC'` for users with no value yet
|
||||
2. Mobile and desktop dashboards, ticket filters, finance views, and engagement period selectors compute day/week boundaries against the viewer's timezone — not UTC and not the browser's local zone (browser zone may differ from the user's chosen zone, e.g. travel)
|
||||
3. Authenticated `GET /api/me/timezone` returns the user's tz; `PUT /api/me/timezone` accepts an IANA string and rejects anything not in `Intl.supportedValuesOf('timeZone')`
|
||||
4. A shared client hook (`useUserTimezone()`) reads the value from `useSession()` so all components use a single source of truth — no per-page `Intl` calls scattered around
|
||||
5. Existing UTC-stored data stays untouched (no destructive migration); only formatting and range-bucketing change
|
||||
|
||||
**Plans**: 6 plans
|
||||
|
||||
- [x] 07.1-01-PLAN.md — Add timezone column to user table + Better Auth additionalField (TZ-01)
|
||||
- [x] 07.1-02-PLAN.md — /api/me/timezone GET + PUT with IANA validation (TZ-03)
|
||||
- [x] 07.1-03-PLAN.md — Server-side read paths use user.timezone for day/week/month boundaries; auth-gates /api/mobile/finance; migrates /api/dashboard/trends (TZ-02)
|
||||
- [x] 07.1-04-PLAN.md — useUserTimezone() client hook + reported-bug-surface mobile page migration + codebase-wide audit (TZ-04, TZ-02 client portion)
|
||||
- [x] 07.1-05-PLAN.md — Codebase-wide useUserTimezone() adoption per the Plan 04 audit (TZ-04 SC#4 single-source-of-truth at codebase scale)
|
||||
|
||||
**UI hint**: no (this is a data/plumbing phase; the picker UI is part of Phase 9)
|
||||
|
||||
### Phase 8: Engagement User Profile (NEW)
|
||||
|
||||
**Goal**: From the Engagement overview, a manager taps an employee row and arrives at a real, shareable profile page — single-column phone-first — and the device back gesture returns them to the overview.
|
||||
**Depends on**: Phase 7
|
||||
**Requirements**: ENG-06, ENG-07, ENG-08
|
||||
**Success Criteria** (what must be TRUE):
|
||||
|
||||
1. Tapping a row in the per-employee list navigates to `/mobile/engagement/[userId]` (segment form, shareable URL)
|
||||
2. The profile is a real page (not a modal) — the device/browser back gesture returns to the overview at the same scroll position
|
||||
3. The profile renders single-column: identity header → period selector → key metrics (compact) → activity breakdown list → recent items, sourced from the existing engagement profile data endpoints (no new data)
|
||||
|
||||
**Plans**: 2 plans
|
||||
|
||||
- [x] 08-01-PLAN.md — MS Graph user-photo proxy at /api/mobile/engagement/user/[userId]/photo (ENG-06; D-25, D-26)
|
||||
- [x] 08-02-PLAN.md — Mobile profile page at /mobile/engagement/[userId] + 6 EngagementProfile* components (ENG-06, ENG-07, ENG-08)
|
||||
|
||||
**UI hint**: yes
|
||||
|
||||
### Phase 9: User Profile & Preferences (NEW)
|
||||
|
||||
**Goal**: A logged-in user reaches a profile/settings page from the More drawer and can configure timezone (chooser UI for TZ-01), theme (light/dark/system, persisted server-side for cross-device consistency), mobile push notifications (per-event toggles, delivered via the ntfy phone app per the no-SW constraint), and personal notification channels (Teams webhook URL, Pulse-minted ntfy topic). Changes persist per-user and the existing notify pipeline routes through these per-user channels for events the user is subscribed to.
|
||||
**Depends on**: Phase 7.1 (timezone schema), Phase 2 (More drawer)
|
||||
**Requirements**: PROF-01, PROF-02, PROF-03, PROF-04, TZ-CHOOSER-01, TZ-CHOOSER-02, THEME-01, THEME-02, THEME-03, THEME-04, THEME-05, CHAN-01, CHAN-02, CHAN-03, CHAN-04, CHAN-05, CHAN-06, CHAN-07, SUB-01, SUB-02, SUB-03, SUB-04, ROUTE-01, ROUTE-02, ROUTE-03, ROUTE-04, ROUTE-05, ROUTE-06, ROUTE-07
|
||||
**Canonical refs:**
|
||||
|
||||
- `docs/superpowers/specs/2026-05-03-mobile-shell-design.md` §4, §5.1, §7 (no-SW constraint, drawer Account section, deferred items)
|
||||
- `lib/services/pipeline-steps/notify.ts` (existing channel-only notify; Phase 9 adds `route_to_user`)
|
||||
- `lib/auth.ts` `additionalFields` (Phase 7.1 precedent for `theme` column exposure)
|
||||
- `migrations/033_create_pipeline_engine_tables.sql` (`notification_channels`, `pipeline_steps` shapes)
|
||||
- `migrations/083_add_user_timezone.sql` (column-on-user precedent from Phase 7.1)
|
||||
- `app/api/me/timezone/route.ts` (per-user API conventions to mirror for `/api/me/theme`, `/api/me/channels`, `/api/me/notification-subscriptions`)
|
||||
- `components/mobile/MoreDrawer.tsx` (Account section gains "Profile & preferences" link)
|
||||
- `components/theme-toggle.tsx` + `components/theme-provider.tsx` (next-themes write-through path)
|
||||
|
||||
**Success Criteria** (what must be TRUE):
|
||||
|
||||
1. Tapping Profile/Account in the More drawer routes to `/mobile/profile` (real page, not modal); the page renders four sections in order — Timezone, Theme, Notifications, Channels — each gated by `requireAuth()` and saving per-user (PROF-01..04)
|
||||
2. Theme persists server-side and applies on sign-in across devices via the `theme` column on `user` (Better Auth additionalFields), with next-themes still handling FOUC and the desktop `ThemeToggle` writing through to the server (THEME-01..05)
|
||||
3. Each user can configure one Teams webhook URL and one Pulse-minted ntfy topic; both are test-sent on save and admins have full read+edit access via `/admin/workflow/channels` (CHAN-01..07)
|
||||
4. The Notifications section renders a per-event × per-channel matrix sourced from `notify_event_keys`; defaults to enabled (opt-out model); writes via `/api/me/notification-subscriptions` (SUB-01..04)
|
||||
5. `lib/services/pipeline-steps/notify.ts` honors an optional `route_to_user` block on each notify step — resolving the user via a registered resolver, checking the subscription matrix, sending via the personal channel, and falling back to the step's `channel_id` on no-channel/send-failure (recorded as `user_route_fallback`) but skipping silently when the user has the toggle muted (ROUTE-01..07)
|
||||
|
||||
**Plans**: 6 plans
|
||||
|
||||
- [x] 09-01-PLAN.md — Schema foundation: theme column, owner_user_id, notify_event_keys, user_event_subscriptions (THEME-01, THEME-05, CHAN-01, SUB-01, SUB-02)
|
||||
- [x] 09-02-PLAN.md — /api/me/* endpoints: theme, channels (Teams + ntfy), notification-subscriptions matrix (THEME-02, CHAN-02..05, CHAN-07, SUB-04)
|
||||
- [x] 09-03-PLAN.md — notify.ts route_to_user branch + resolver registry + fallback semantics (ROUTE-01..06)
|
||||
- [x] 09-04-PLAN.md — /mobile/profile UI part 1: page shell + drawer link + Timezone/Theme/Notifications Cards + Channels placeholder (PROF-01..04, TZ-CHOOSER-01..02, THEME-03, SUB-03)
|
||||
- [x] 09-05-PLAN.md — /mobile/profile UI part 2: real Channels Card (Teams + ntfy + QR) + ThemeSessionBridge + ThemeToggle write-through (CHAN-02..05, CHAN-07, THEME-04)
|
||||
- [x] 09-06-PLAN.md — Admin surfaces: channels Owner column + filter, event-keys CRUD page, NEW /admin/workflow/executions with fallback filter (CHAN-06, SUB-01, ROUTE-07)
|
||||
|
||||
**UI hint**: yes
|
||||
|
||||
### Phase 9.1: ntfy Backend Fix (INSERTED — urgent)
|
||||
|
||||
**Goal**: A logged-in user enabling mobile push from `/mobile/profile` gets a topic published to `https://ntfy.wulfconsulting.cloud` (not the public `ntfy.sh`) with bearer auth via `NTFY_PULSE_TOKEN`, using the `pulse-me-` reserved prefix so personal channels never collide with the `noc-*` / `soc-*` namespaces reserved for NOC/SOC operations.
|
||||
**Depends on**: Phase 9 (personal channels feature must exist)
|
||||
**Requirements**: CHAN-03, CHAN-05, CHAN-07, ROUTE-04 (gap closure — re-targeting the existing implementation)
|
||||
**Source**: `.planning/phases/09-user-profile-preferences-new/09-HUMAN-UAT.md` Test 1 — diagnosed gap
|
||||
**Success Criteria** (what must be TRUE):
|
||||
|
||||
1. `mintNtfyTopic()` returns `pulse-me-XXXXXXXX`; `NTFY_TOPIC_RE` enforces `^pulse-me-[A-Za-z0-9-]{6,64}$`; custom topics matching `pulse-`, `noc-`, `soc-`, or arbitrary names are rejected
|
||||
2. All four ntfy publish paths used for personal channels (`sendChannelTest`, `pipeline-steps/notify.ts sendNtfy`, `pipeline-steps/approval.ts` ntfy branch, `ticket-digest-service.ts deliver()` ntfy branch) target `${NTFY_BASE_URL || 'https://ntfy.wulfconsulting.cloud'}` and send `Authorization: Bearer ${NTFY_PULSE_TOKEN}` when `channel.owner_user_id` is set
|
||||
3. Global / admin ntfy channels (`owner_user_id IS NULL`) preserve their existing `channel.config.server_url` / `channel.config.auth_token` behavior — out-of-scope per gap diagnosis
|
||||
4. `/mobile/profile` QR code and subscribe link target `process.env.NEXT_PUBLIC_NTFY_BASE_URL || 'https://ntfy.wulfconsulting.cloud'`; help line under custom-topic Input reads "Topic must start with `pulse-me-`"
|
||||
5. `npx tsc --noEmit --pretty` and `npx vitest run lib/services/pipeline-steps/notify.test.ts` both pass (mute semantics intact)
|
||||
|
||||
**Plans**: 1 plan
|
||||
|
||||
- [x] 09.1-01-PLAN.md — Personal-channels regex/prefix/bearer + propagate to notify/approval/digest send paths + ProfileChannelsSection QR & copy
|
||||
|
||||
**UI hint**: no (backend-heavy; one component edit for QR/link target)
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>✅ v2.0 PAX8 Integration (Phases 10-14) - SHIPPED 2026-07-12</summary>
|
||||
|
||||
**Milestone Goal:** Sync PAX8 licensing/subscription data into Pulse, read-only,
|
||||
mapped to Autotask companies, so managers can see subscription costs and seat
|
||||
counts alongside existing company data.
|
||||
|
||||
This milestone follows the codebase's existing external-integration pattern
|
||||
(`<name>-client.ts` + `<name>-factory.ts` + numbered migration + sync service +
|
||||
scheduler entry + admin toggle). Phase 10 stands up auth + schema in isolation
|
||||
so the OAuth2 client-credentials flow is proven before anything is built on
|
||||
top of it. Phase 11 syncs the "current state" entities (companies, catalog,
|
||||
subscriptions). Phase 12 adds historical cost data (orders/invoices) and the
|
||||
fuzzy-name company-matching pass, since matching needs companies to already
|
||||
exist. Phase 13 wires the combined sync into the daily scheduler and the
|
||||
`/admin/integrations` toggle — deliberately last among the backend phases so
|
||||
it schedules the *complete* sync, not a partial one. Phase 14 ships the
|
||||
`/pax8` page, which needs Phase 12's data and match state to have something to
|
||||
render, including the manual-resolution workflow for flagged companies.
|
||||
|
||||
- [x] **Phase 10: PAX8 Client & Auth Foundation** — OAuth2 client-credentials auth, `isPax8Configured()`, and the PAX8 schema migration (completed 2026-07-10)
|
||||
- [x] **Phase 11: Company, Catalog & Subscription Sync** — Read-only sync of current-state companies, product catalog, and subscriptions into Postgres (completed 2026-07-11)
|
||||
- [x] **Phase 12: Orders/Invoices & Company Matching** — Historical cost sync plus fuzzy-name auto-matching (with flagging) of PAX8 companies to Autotask companies (completed 2026-07-11)
|
||||
- [x] **Phase 13: Scheduler & Admin Toggle** — Daily `pax8-daily` cron entry and an on/off switch in `/admin/integrations` (completed 2026-07-11)
|
||||
- [x] **Phase 14: /pax8 UI Surface** — New page listing companies/subscriptions/cost breakdown, plus manual resolution of flagged company matches (completed 2026-07-12)
|
||||
|
||||
### Phase 10: PAX8 Client & Auth Foundation
|
||||
|
||||
**Goal**: Pulse can authenticate to the PAX8 API via OAuth2 client-credentials, and the Postgres schema for all four PAX8 entities exists — proving the integration pattern before any sync logic is built on top of it.
|
||||
**Depends on**: Nothing (first phase of v2.0)
|
||||
**Requirements**: PAX8-01, PAX8-02
|
||||
**Success Criteria** (what must be TRUE):
|
||||
|
||||
1. `lib/services/pax8-factory.ts` exports `isPax8Configured()`, returning `true` only when the PAX8 client ID and secret env vars are both set, `false` otherwise
|
||||
2. `getPax8Client()` performs an OAuth2 client-credentials token exchange against `api.pax8.com/v1` and successfully calls a read-only endpoint (e.g., list companies) using the resulting bearer token
|
||||
3. Calling the client with missing/invalid credentials throws a clear, typed error rather than failing silently or crashing the process — matching the existing `is<Name>Configured()` + throw-if-missing pattern used by other integrations
|
||||
4. A new numbered migration creates the PAX8 tables (companies, subscriptions, products/catalog, orders, and a company-match/review table) using `IF NOT EXISTS`, ready for Phase 11+ to populate
|
||||
|
||||
**Plans**: 3 plans
|
||||
|
||||
- [x] 10-01-PLAN.md — PAX8 types + OAuth2 client (token exchange, audience, cache) + factory (isPax8Configured/getPax8Client) + mocked tests (PAX8-01, PAX8-02)
|
||||
- [x] 10-02-PLAN.md — migrations/091_pax8_tables.sql (6 PAX8 tables, IF NOT EXISTS) + apply to dev DB (PAX8-01, PAX8-02)
|
||||
- [x] 10-03-PLAN.md — verify-pax8-auth.ts live auth-proof (SC#2) + CLAUDE.md/INTEGRATIONS.md docs (PAX8-01, PAX8-02)
|
||||
|
||||
**UI hint**: no
|
||||
|
||||
### Phase 11: Company, Catalog & Subscription Sync
|
||||
|
||||
**Goal**: PAX8 companies, the product catalog, and current subscriptions are synced into Postgres and are human-readable (not raw SKU IDs) — the "current state" half of the integration.
|
||||
**Depends on**: Phase 10
|
||||
**Requirements**: PAX8-03, PAX8-04, PAX8-05, PAX8-08
|
||||
**Success Criteria** (what must be TRUE):
|
||||
|
||||
1. Running the sync populates a companies table with every PAX8 company (PAX8 ID, name, and other identifying fields)
|
||||
2. Running the sync populates a product/catalog table (SKUs, categories) and a subscriptions table (product, seat count, billing term) per company
|
||||
3. A synced subscription row displays a readable product name and category by joining to the catalog table — not a bare SKU/product ID
|
||||
4. No code path in the PAX8 client or this sync service issues a write (POST/PUT/PATCH/DELETE) to the PAX8 API — every call is a read, verified by inspection of the client's exposed methods
|
||||
|
||||
**Plans**: 3 plans
|
||||
|
||||
- [x] 11-01-PLAN.md — Migration 092 subscription cost columns + extend pax8 types + read-only client pagination helpers (PAX8-04, PAX8-05, PAX8-08)
|
||||
- [x] 11-02-PLAN.md — pax8-sync-service.ts (companies + subscriptions + referenced-only catalog + soft-delete reconciliation) + /api/pax8/sync fire-and-forget route (PAX8-03, PAX8-04, PAX8-05, PAX8-08)
|
||||
- [x] 11-03-PLAN.md — Read-only invariant proof + live sync run DB verification checkpoint (PAX8-03, PAX8-04, PAX8-05, PAX8-08)
|
||||
|
||||
**UI hint**: no
|
||||
|
||||
### Phase 12: Orders/Invoices & Company Matching
|
||||
|
||||
**Goal**: Pulse has historical PAX8 cost data for reconciliation over time, and every PAX8 company is automatically linked to its Autotask counterpart or explicitly flagged for review — never silently guessed.
|
||||
**Depends on**: Phase 11
|
||||
**Requirements**: PAX8-06, PAX8-10, PAX8-11
|
||||
**Success Criteria** (what must be TRUE):
|
||||
|
||||
1. Running the sync populates an orders/invoices table with historical line items (not just current-state seat counts), enabling cost-over-time comparisons
|
||||
2. At sync time, each PAX8 company is automatically matched to an Autotask company by fuzzy name similarity when a sufficiently confident match exists, and the match is persisted
|
||||
3. A PAX8 company with no match, or with multiple similarly-scored Autotask candidates, is persisted with a flagged/needs-review status instead of being auto-assigned
|
||||
4. Re-running the sync does not overwrite a match that has already been manually confirmed/resolved (idempotent with respect to human decisions)
|
||||
|
||||
**Plans**: 5 plans
|
||||
|
||||
- [x] 12-01-PLAN.md — Migration 093 (pg_trgm + pax8_order_items/pax8_companies columns) + Pax8Invoice/Pax8InvoiceItem types (PAX8-06, PAX8-10, PAX8-11)
|
||||
- [x] 12-02-PLAN.md — pax8-client listAllInvoices/listAllInvoiceItems + tests + live field-mapping spot-check (PAX8-06)
|
||||
- [x] 12-03-PLAN.md — pax8-company-matcher.ts (pg_trgm similarity, 0.90 threshold, tie/empty/idempotency policy) + tests (PAX8-10, PAX8-11)
|
||||
- [x] 12-04-PLAN.md — syncOrders + syncCompanyMatches wired into Pax8SyncService.fullSync + sync-service tests (PAX8-06, PAX8-10, PAX8-11)
|
||||
- [x] 12-05-PLAN.md — Live full-sync verification of all 4 success criteria + human-verify checkpoint (PAX8-06, PAX8-10, PAX8-11)
|
||||
|
||||
**UI hint**: no
|
||||
|
||||
### Phase 13: Scheduler & Admin Toggle
|
||||
|
||||
**Goal**: PAX8 sync runs automatically once a day like every other Pulse integration, and can be turned on or off from `/admin/integrations` without a container restart.
|
||||
**Depends on**: Phase 12
|
||||
**Requirements**: PAX8-07, PAX8-09
|
||||
**Success Criteria** (what must be TRUE):
|
||||
|
||||
1. A `pax8-daily` (or equivalently named) entry exists in the sync scheduler and fires once per day, running the full companies + catalog + subscriptions + orders sync in sequence
|
||||
2. PAX8 appears as a toggleable row on `/admin/integrations`, backed by the `integration_settings` table like every other integration
|
||||
3. Disabling PAX8 from that UI stops future scheduled sync runs (respecting the existing health-cache window, or immediately per the PATCH-clears-cache convention) and records `disabled_by`, `disabled_at`, and an optional `disabled_reason`
|
||||
4. Re-enabling PAX8 resumes scheduled sync at the next cron tick with no code deploy or container restart required
|
||||
|
||||
**Plans**: 3 plans
|
||||
|
||||
- [x] 13-01-PLAN.md — Migration 096 pax8-daily seed + dual-guarded scheduler branch + CLAUDE.md precedent note (PAX8-07, PAX8-09)
|
||||
- [x] 13-02-PLAN.md — checkConfigOnly('pax8') admin-integrations row + POST /api/pax8/sync 403 disabled-gate (PAX8-09)
|
||||
- [x] 13-03-PLAN.md — Live verification checkpoint of Phase 13 SC#1-4 (PAX8-07, PAX8-09)
|
||||
|
||||
**UI hint**: no
|
||||
|
||||
### Phase 14: /pax8 UI Surface
|
||||
|
||||
**Goal**: A manager can open `/pax8` and see PAX8 companies with their subscriptions and a cost breakdown, and an admin can resolve any flagged/ambiguous company match directly from that page — no psql required.
|
||||
**Depends on**: Phase 12
|
||||
**Requirements**: PAX8-12, PAX8-13, PAX8-14
|
||||
**Success Criteria** (what must be TRUE):
|
||||
|
||||
1. `/pax8` lists PAX8 companies together with their current subscriptions
|
||||
2. Each company shows a cost breakdown (e.g., by subscription/product) built from the synced subscription and order/invoice data
|
||||
3. Flagged/ambiguous company matches appear in a distinct, clearly-labeled review section on `/pax8` rather than being mixed silently into the main list
|
||||
4. From that review section, an admin can pick the correct Autotask company for a flagged PAX8 company; the resolution persists and is respected (not overwritten) by future syncs
|
||||
|
||||
**Plans**: 6 plans
|
||||
|
||||
- [x] 14-01-PLAN.md — GET /api/pax8/companies list + /api/pax8/companies/[id] cost-breakdown (requireAuth) (PAX8-13)
|
||||
- [x] 14-02-PLAN.md — /api/pax8/company-matches queue + admin-gated resolve route + extracted resolver service & test (PAX8-12, PAX8-14)
|
||||
- [x] 14-03-PLAN.md — DetailModal additive extension: kind prop + PAX8_COMPANY_GROUPS + subscriptions cost-breakdown section (PAX8-13)
|
||||
- [x] 14-04-PLAN.md — /pax8 page shell + Companies tab (DataTable + DetailModal drill-down) + top-level nav entry (PAX8-13)
|
||||
- [x] 14-05-PLAN.md — Needs Review tab (review cards, candidate + manual-search resolve, count badge) + companies-list auth hardening (PAX8-14, PAX8-12)
|
||||
- [x] 14-06-PLAN.md — Automated gates + human verification of all 4 SCs and the view/resolve permission split (PAX8-12, PAX8-13, PAX8-14)
|
||||
|
||||
**UI hint**: yes
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>✅ v3.0 Phishing Triage Automation (Phases 15-23) - SHIPPED 2026-07-17</summary>
|
||||
|
||||
**Milestone Goal:** Detect candidate phishing/spam report tickets in Autotask, extract
|
||||
and parse original-message evidence, classify each as `SPAM` / `UNWANTED` / `THREAT`,
|
||||
group duplicate reports into campaigns, and prepare (never auto-execute) remediation
|
||||
actions behind an explicit human-approval gate.
|
||||
|
||||
Nine phases follow the domain's natural dependency chain rather than a generic
|
||||
foundation→features→polish template. Phase 15 lands the durable data model
|
||||
(campaigns/reports/messages/indicators/classifications/remediation_actions/
|
||||
audit_events, migration 097+) together with ticket detection and basic ticket-level
|
||||
evidence, since every later service writes to that schema. Phase 16 is the pure,
|
||||
testable RFC822/MIME `.eml` parser — it has no dependency on detection beyond the
|
||||
schema, but campaign grouping depends on its output (Message-ID, indicators), so it
|
||||
must land before Phase 18. Phase 17 (Mimecast blast-radius) has no dependency on the
|
||||
parser or on campaigns — it only needs the Phase 15 schema — so it's sequenced here
|
||||
as an independent unit that could equally have been built in parallel with Phase 16
|
||||
by a second workstream. Phase 18 is the first phase to expose `/api/phishing/*`
|
||||
routes (campaign list/get, on-demand ticket analysis) and is where ACCESS-01's
|
||||
auth convention is established for every phishing endpoint that follows. Phase 19
|
||||
(classification) depends on both Phase 17's blast-radius output and Phase 18's
|
||||
campaign data as inputs — it cannot run before either. Phase 20 (remediation/
|
||||
approval/audit) depends on campaigns existing (Phase 18) and classifications
|
||||
existing (Phase 19), since you can't approve or gate an action that doesn't
|
||||
reference either. Phase 21 (Autotask triage note) is last because its note content
|
||||
summarizes classification, blast radius, and recommended/approved remediation state
|
||||
— it has nothing to summarize until Phases 19 and 20 exist. Phase 22 (Approval UI)
|
||||
depends on the same Phase 19/20 outputs as Phase 21 but is otherwise independent of
|
||||
it — a LiveLink button in Autotask is a separate configuration surface from the
|
||||
triage note's content, so Phase 22 does not need Phase 21 to land first; it is
|
||||
sequenced last only because it is the newest addition to this milestone, not because
|
||||
of a functional dependency on Phase 21. Phase 23 (Classification Disposition +
|
||||
Per-Client Automation Gate) was added after live review of a real Breach Secure Now
|
||||
report surfaced a gap — it depends on Phases 17-22 since it extends the classifier,
|
||||
the review UI, and the webhook automation path all at once.
|
||||
|
||||
- [x] **Phase 15: Data Model, Detection & Ticket Evidence** — New phishing schema (migration 097) + idempotent Autotask ticket scanner + base ticket evidence capture (completed 2026-07-15)
|
||||
- [x] **Phase 16: EML/MIME Evidence Parser** — Pure RFC822/MIME parser: `.eml` selection (`rfc.eml` over `OriginatingEmail.eml`), normalized headers/URLs/attachments, sanitized body preview, synthetic-fixture tests (completed 2026-07-15)
|
||||
- [x] **Phase 17: Mimecast Blast Radius Lookup** — Blast-radius abstraction with graceful `unavailable` degradation when Mimecast isn't configured (completed 2026-07-15)
|
||||
- [x] **Phase 18: Campaign Grouping & Phishing Analysis API** — Message-ID-first dedupe/grouping, on-demand single-ticket analysis, and the first `/api/phishing/*` routes with the ACCESS-01 auth convention (blocking gap CR-03 found via live verification 2026-07-16 — duplicate campaign on single-report re-analyze — see 18-VERIFICATION.md) (completed 2026-07-16)
|
||||
- [x] **Phase 19: Classification Engine** — Deterministic SPAM/UNWANTED/THREAT rule classifier over bounded structured evidence, KnowBe4-simulation guard, (re-)trigger API (completed 2026-07-16)
|
||||
- [x] **Phase 20: Remediation, Approval & Audit Safety** — Proposed-only remediation actions, approve/remediate/mark-false-positive APIs, idempotent re-run, full audit trail (completed 2026-07-16)
|
||||
- [x] **Phase 21: Autotask Triage Note** — Sanitized internal triage note posted via existing safe note-write path, or returned via API if no such path exists (completed 2026-07-16)
|
||||
- [x] **Phase 22: Approval UI (LiveLink)** — Ticket-ID-addressable Pulse page (Autotask LiveLink target) showing campaign timeline, evidence, and classification, with approve/remediate/mark-false-positive wired to the Phase 20 APIs (completed 2026-07-16)
|
||||
- [x] **Phase 23: Classification Disposition + Per-Client Automation Gate** — Dedicated "User Awareness" verdict for confirmed phishing-simulation-vendor reports (currently forced into generic UNWANTED), plus an admin UI gate controlling per-company whether the phishing pipeline (parse/classify/report-to-ticket) runs automatically or requires manual trigger (completed 2026-07-17)
|
||||
|
||||
### Phase 15: Data Model, Detection & Ticket Evidence
|
||||
|
||||
**Goal**: The durable phishing-triage schema exists in Postgres, and Pulse can scan Autotask/Pulse tickets for known phishing/spam-report patterns idempotently, capturing base ticket-level evidence for each candidate.
|
||||
**Depends on**: Nothing (first phase of v3.0)
|
||||
**Requirements**: DETECT-01, DETECT-02, EVID-01
|
||||
**Success Criteria** (what must be TRUE):
|
||||
|
||||
1. A new migration (`migrations/097_*.sql` or next available number) creates `campaigns`, `reports`, `messages`, `indicators`, `classifications`, `remediation_actions`, and `audit_events` tables with `IF NOT EXISTS`, ready for every later phase to read/write
|
||||
2. Running the ticket scanner against Autotask/Pulse tickets flags candidates matching the known title/body patterns ("Phishing Report", "Spam Alert", "Phishing Alert - Email Security Report", "KnowBe4 Phish Alert Report", "Source: KnowBe4 Phish Alert Button", "userSubmissionsReportMessage", "reported message destinations", "Microsoft directly") and persists a `reports` row per candidate
|
||||
3. Re-scanning tickets that haven't changed since last processed does not reprocess or duplicate their `reports` rows; a ticket whose Autotask data changed since last processed IS reprocessed (idempotent on ticket state, not just ticket ID)
|
||||
4. Each flagged ticket's stored evidence includes ticket ID/number, company, requester/reporter, title, description, notes, relevant time entries, and attachment metadata (EVID-01)
|
||||
|
||||
**Plans**: 3 plans
|
||||
|
||||
- [x] 15-01-PLAN.md — Migration 097: 7-table phishing-triage schema (reports fully designed, others stubbed) (DETECT-01, DETECT-02, EVID-01)
|
||||
- [x] 15-02-PLAN.md — phishing-detector.ts core: pattern matcher + content-hash idempotency + EVID-01 evidence capture + reports upsert (DETECT-01, DETECT-02, EVID-01)
|
||||
- [x] 15-03-PLAN.md — Wiring: webhook fire-and-forget hook + bounded cron sweep service + scheduler branch + migration 098 seed (DETECT-01, DETECT-02)
|
||||
|
||||
**UI hint**: no
|
||||
|
||||
### Phase 16: EML/MIME Evidence Parser
|
||||
|
||||
**Goal**: Given a ticket's attachments, Pulse selects the correct original reported message and parses its RFC822/MIME structure into normalized, actionable evidence — without ever executing or fetching anything from the message.
|
||||
**Depends on**: Phase 15 (messages/indicators tables to persist output into)
|
||||
**Requirements**: EVID-02, EVID-03, EVID-04
|
||||
**Success Criteria** (what must be TRUE):
|
||||
|
||||
1. Given synthetic fixtures with both `rfc.eml` and `OriginatingEmail.eml` present, the selection logic picks `rfc.eml` as the original reported message, matching case-insensitively and by `message/rfc822` content-type — not filename alone
|
||||
2. Parsing a synthetic `.eml` fixture produces normalized headers (From, display name, sender email/domain, Reply-To, Return-Path, To, Cc, Subject, Date, Message-ID, Received chain, SPF/DKIM/DMARC results), a list of extracted URLs, and attachment metadata (name, content-type, size, hash)
|
||||
3. The parser never executes or fetches any URL found in a message — verified by tests asserting no outbound network calls happen during parsing
|
||||
4. Parsed output includes a sanitized/truncated body preview stored alongside the raw evidence, distinct from the full raw body
|
||||
5. `npx vitest run` for the new parser test file passes using synthetic fixtures only (no real customer email)
|
||||
|
||||
**Plans**: 3 plans
|
||||
|
||||
- [x] 16-01-PLAN.md — Deps (mailparser + linkify-it) + pure EML parser: 3-tier selection, RFC822/MIME normalization, structured SPF/DKIM/DMARC verdicts, sanitized preview, no-network + size-guard tests (EVID-02, EVID-03, EVID-04)
|
||||
- [x] 16-02-PLAN.md — Supporting infra: AutotaskClient.getAttachmentContent (items[0]), b2 EML_OBJECT_KEY_REGEX + parameterized key validation, migration 099 indicators.metadata JSONB (EVID-03, EVID-04; D-05, D-07)
|
||||
- [x] 16-03-PLAN.md — phishing-eml-service orchestration: list→select→fetch→B2 (gated)→parse→persist messages/indicators, end-to-end no-network + graceful-degrade tests (EVID-03, EVID-04; D-05, D-06, D-07)
|
||||
|
||||
**UI hint**: no
|
||||
|
||||
### Phase 17: Mimecast Blast Radius Lookup
|
||||
|
||||
**Goal**: Pulse can ask "how far did this message spread" via a Mimecast blast-radius abstraction when Mimecast is configured, and gets a clean `unavailable` signal — never a crash or a block — when it isn't.
|
||||
**Depends on**: Phase 15 (schema to store lookup results against)
|
||||
**Requirements**: BLAST-01, BLAST-02
|
||||
**Success Criteria** (what must be TRUE):
|
||||
|
||||
1. When Mimecast is configured, querying the blast-radius abstraction for a message (keyed on message ID, sender, recipient/reporter, subject, and date window) returns normalized delivery data — matched/delivered/held/rejected/clicked counts and per-recipient status
|
||||
2. When Mimecast is not configured, the same lookup call returns `status: unavailable` synchronously rather than throwing, timing out, or blocking the caller
|
||||
3. The lookup follows the existing `lib/services/` factory convention (`getMimecastClient()` + `isMimecastConfigured()`-equivalent) so Phase 19's classifier can call it without knowing whether Mimecast is present
|
||||
|
||||
**Plans**: 1 plan
|
||||
|
||||
- [x] 17-01-PLAN.md — isMimecastConfigured() gate + mimecast-blast-radius.ts fan-out/merge/cache orchestration + tests (BLAST-01, BLAST-02)
|
||||
|
||||
**UI hint**: no
|
||||
|
||||
### Phase 18: Campaign Grouping & Phishing Analysis API
|
||||
|
||||
**Goal**: Duplicate reports of the same phishing/spam campaign are automatically grouped and accumulate over time, and an operator can trigger analysis of a specific ticket or browse campaigns through a properly access-controlled `/api/phishing/*` surface.
|
||||
**Depends on**: Phase 16 (parsed Message-ID/indicators to key grouping on)
|
||||
**Requirements**: CAMP-01, CAMP-02, CAMP-03, DETECT-03, ACCESS-01
|
||||
**Success Criteria** (what must be TRUE):
|
||||
|
||||
1. Two reports sharing the same original Message-ID are grouped into the same campaign; absent a shared Message-ID, reports sharing attachment-hash/URL-domain + subject + sender within a time window are grouped instead; absent that too, sender + normalized subject + client + time-window groups them as the final fallback
|
||||
2. A campaign accumulates additional linked ticket reports and recipients as new duplicate reports arrive over time, without ever creating a second campaign for the same underlying report
|
||||
3. `POST /api/phishing/tickets/{ticket_id}/analyze` runs detection + evidence extraction + campaign grouping for one specific ticket on demand and returns the resulting campaign linkage, instead of waiting for the next scheduled scan
|
||||
4. `GET /api/phishing/campaigns` lists campaigns and `GET /api/phishing/campaigns/{id}` returns full detail (linked reports, messages, indicators, classification history)
|
||||
5. Every `/api/phishing/*` route introduced in this phase calls `requireAuth()` (or `requirePermission()`) and rejects an unauthenticated/unauthorized request with 401/403 — establishing the auth convention every later phishing endpoint (Phases 19-21) must also follow
|
||||
|
||||
**Plans**: 3 plans (2 waves)
|
||||
|
||||
- [x] 18-01-PLAN.md — Campaign grouping service (tiered match + transactional find-or-create) + tests + phishing permission resource (CAMP-01, CAMP-02, ACCESS-01)
|
||||
- [x] 18-02-PLAN.md — POST /api/phishing/tickets/{id}/analyze + wire groupReportIntoCampaign into webhook + cron sweep automatic paths (DETECT-03, CAMP-01, CAMP-02, ACCESS-01)
|
||||
- [x] 18-03-PLAN.md — GET /api/phishing/campaigns list + GET /api/phishing/campaigns/{id} nested detail (CAMP-03, ACCESS-01)
|
||||
|
||||
**UI hint**: no
|
||||
|
||||
### Phase 19: Classification Engine
|
||||
|
||||
**Goal**: Every campaign gets a deterministic SPAM/UNWANTED/THREAT verdict, built from bounded structured evidence (never raw unbounded email), that correctly flags destructive-action recommendations for approval and doesn't cry wolf on routine KnowBe4 simulations.
|
||||
**Depends on**: Phase 17 (blast-radius input), Phase 18 (campaign data input + auth convention)
|
||||
**Requirements**: CLASSIFY-01, CLASSIFY-02, CLASSIFY-03, CLASSIFY-04, CLASSIFY-05, CLASSIFY-06
|
||||
**Success Criteria** (what must be TRUE):
|
||||
|
||||
1. Classifying a campaign returns exactly one of `SPAM` / `UNWANTED` / `THREAT` with confidence, a short summary, evidence-backed reasons, recommended actions, and a `requires_approval` flag
|
||||
2. A classification whose recommended actions include any destructive action (purge/block/delete/reset) always has `requires_approval: true` — proven by a test asserting the invariant can't be produced any other way
|
||||
3. Classifying a campaign with incomplete evidence (no Mimecast data, no `.eml`) lowers confidence and names the specific missing evidence in the reasons
|
||||
4. A synthetic KnowBe4 security-awareness-simulation fixture is not classified as `THREAT` absent contrary evidence
|
||||
5. `POST /api/phishing/campaigns/{id}/classify` (re-)triggers classification, enforces the Phase 18 auth convention, and the classifier only ever receives structured, size-bounded evidence — long bodies are redacted/truncated before reaching any AI layer, and IT Glue-sourced evidence (if referenced) goes through the existing redacted `lib/services/analyzer/itglue-search.ts` path
|
||||
|
||||
**Plans**: 2 plans (2 waves)
|
||||
|
||||
- [x] 19-01-PLAN.md — campaign-classifier.ts deterministic rule engine (evidence gather + D-03/D-04/D-06 rules + D-05 confidence + D-08 actions + append-only INSERT) + vitest suite + synthetic KnowBe4/BSN fixtures (CLASSIFY-01, CLASSIFY-02, CLASSIFY-03, CLASSIFY-04, CLASSIFY-06)
|
||||
- [x] 19-02-PLAN.md — POST /api/phishing/campaigns/[id]/classify route (requirePermission analyze + UUID guard + classifyCampaign delegation) (CLASSIFY-05)
|
||||
|
||||
**UI hint**: no
|
||||
|
||||
### Phase 20: Remediation, Approval & Audit Safety
|
||||
|
||||
**Goal**: Remediation actions are proposed, never auto-executed, and every approve/remediate/mark-false-positive action is gated by elevated permission, idempotent on re-run, and fully audited.
|
||||
**Depends on**: Phase 18 (campaigns to act against), Phase 19 (classifications to approve/act on)
|
||||
**Requirements**: REMED-01, REMED-02, REMED-03, REMED-04, REMED-05, REMED-06
|
||||
**Success Criteria** (what must be TRUE):
|
||||
|
||||
1. Recommended remediation actions are persisted with status `proposed`, and no code path in this milestone executes one automatically
|
||||
2. `POST /api/phishing/campaigns/{id}/approve` records approver, timestamp, and the exact approved action parameters, and is gated behind a permission level above plain read access (beyond the Phase 18 baseline)
|
||||
3. `POST /api/phishing/campaigns/{id}/remediate` proceeds only for already-approved actions against a configured, non-destructive-by-default provider path; otherwise it returns `not_implemented`/an explicit failure and never silently succeeds without taking or logging an action
|
||||
4. Re-running remediation against an already-completed action does not duplicate the destructive effect — proven by a test that calls remediate twice and asserts a single effect/log entry
|
||||
5. `POST /api/phishing/campaigns/{id}/mark-false-positive` exists, and every state-changing action (classify, approve, remediate, mark-false-positive) writes an `audit_events` row recording actor, event type, and payload
|
||||
|
||||
**Plans**: 2 plans (2 waves)
|
||||
|
||||
- [x] 20-01-PLAN.md — phishing-audit.ts writeAuditEvent + remediation-service.ts approve/remediate/mark-false-positive orchestrators (idempotent, audited, D-04 guard) + vitest suite (REMED-01..06)
|
||||
- [x] 20-02-PLAN.md — lib/permissions.ts approve/remediate grant (D-02) + approve/remediate/mark-false-positive routes + classify audit wiring (REMED-02, REMED-03, REMED-04, REMED-05, REMED-06)
|
||||
|
||||
**UI hint**: no
|
||||
|
||||
### Phase 21: Autotask Triage Note
|
||||
|
||||
**Goal**: Once a campaign is classified, a human-readable, sanitized internal triage note either gets posted to the Autotask ticket (if a safe write path already exists) or is returned via API for manual use — never a raw/unsanitized dump, never a silent no-op.
|
||||
**Depends on**: Phase 19 (classification content to summarize), Phase 20 (recommended/approved remediation state to include)
|
||||
**Requirements**: NOTE-01
|
||||
**Success Criteria** (what must be TRUE):
|
||||
|
||||
1. If Pulse has a safe existing Autotask note-writing method, triggering note generation for a classified campaign posts an internal triage note summarizing classification, evidence, blast radius, and recommended actions to the originating ticket
|
||||
2. The posted (or returned) note text is sanitized — no raw secrets/tokens/full malicious URL query strings appear in it
|
||||
3. If no safe note-writing path exists, the same note content is returned via the API response instead of attempting any Autotask write, and no partial/unsanitized write is ever attempted as a fallback
|
||||
|
||||
**Plans**: 2 plans
|
||||
|
||||
- [x] 21-01-PLAN.md — Pure text layer: triage-note-sanitize (URL query/secret stripping) + triage-note-format (TriageNoteEvidence + formatTriageNote) with Vitest coverage (NOTE-01)
|
||||
- [x] 21-02-PLAN.md — triage-note-service (evidence gather + per-ticket TicketNotes post loop + partial-failure result) + POST /api/phishing/campaigns/[id]/triage-note route (NOTE-01)
|
||||
|
||||
**UI hint**: no
|
||||
|
||||
### Phase 22: Approval UI (LiveLink)
|
||||
|
||||
**Goal**: A security operator opens an Autotask ticket, clicks a LiveLink button, and lands on a Pulse page scoped to that ticket showing the campaign's timeline, evidence, and classification — with approve/remediate/mark-false-positive actions right there, so no one is calling the Phase 20 APIs by hand.
|
||||
**Depends on**: Phase 19 (classification + recommended action to display), Phase 20 (approve/remediate/mark-false-positive APIs the page calls)
|
||||
**Requirements**: REVIEW-01, REVIEW-02, REVIEW-03, REVIEW-04, REVIEW-05, REVIEW-06
|
||||
**Success Criteria** (what must be TRUE):
|
||||
|
||||
1. 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; it does not know the internal campaign UUID), using the existing Better Auth session with no separate token/query-param auth
|
||||
2. The page shows the campaign's timeline — linked reports, classification history, and audit events (classify/approve/remediate/mark-false-positive) — in chronological order
|
||||
3. The page shows the gathered evidence — parsed EML headers/URLs/attachments (Phase 16), sanitized body preview, and Mimecast blast-radius data (Phase 17, including an explicit `unavailable` state when Mimecast isn't configured) — never rendering a raw/unsanitized body or unredacted secrets
|
||||
4. The page shows the current classification (SPAM/UNWANTED/THREAT), confidence, reasons, and recommended remediation action(s) from Phase 19
|
||||
5. Approve, remediate, and mark-false-positive buttons call the Phase 20 APIs directly from the page and reflect the resulting state (e.g. a remediated campaign shows as remediated, not re-offered for approval)
|
||||
6. An operator without the elevated permission REMED-02/ACCESS-01 already require sees the approve/remediate actions disabled or hidden rather than a failed request; the page never uses a relaxed or separate permission check from the underlying APIs
|
||||
|
||||
**Plans**: 6 plans
|
||||
|
||||
- [x] 22-01-PLAN.md — Pure testable logic: ticket->campaign resolver, 7-action default-params, timeline merge (REVIEW-01, REVIEW-02, REVIEW-04)
|
||||
- [x] 22-02-PLAN.md — Backend routes: new ticket->campaign resolver + extend campaign-detail (evidence/timeline/classification/blast radius) + list firstReportTicketId (REVIEW-01..04)
|
||||
- [x] 22-03-PLAN.md — Evidence display: shadcn tooltip + inert UrlList (D-09) + tabbed EvidenceCard (REVIEW-03)
|
||||
- [x] 22-04-PLAN.md — ClassificationCard + TimelineCard (REVIEW-02, REVIEW-04)
|
||||
- [x] 22-05-PLAN.md — ActionAreaCard: approve/remediate/mark-false-positive with server-identical permission gating (REVIEW-05, REVIEW-06)
|
||||
- [x] 22-06-PLAN.md — Review page + campaigns list page + nav entry (REVIEW-01, REVIEW-05, REVIEW-06)
|
||||
|
||||
**UI hint**: yes
|
||||
|
||||
### Phase 23: Classification Disposition + Per-Client Automation Gate
|
||||
|
||||
**Goal:** Add a dedicated "User Awareness" verdict for confirmed phishing-simulation-vendor (KnowBe4/Breach Secure Now) reports — today forced into the generic UNWANTED bucket despite the classifier already detecting the simulation vendor and explicitly skipping the THREAT tier — and add an admin UI gate page letting an admin choose, per Autotask company, whether the phishing pipeline's parse/classify/report-to-ticket stages run automatically (now that the previously-dead Autotask webhook is fixed) or require the existing manual Analyze/Classify/triage-note triggers.
|
||||
**Requirements**: CLASSDISP-01, CLASSDISP-02, CLASSDISP-03, AUTOGATE-01, AUTOGATE-02, AUTOGATE-03
|
||||
**Depends on:** Phase 17, Phase 18, Phase 19, Phase 20, Phase 21, Phase 22
|
||||
**Plans:** 6/6 plans complete
|
||||
|
||||
Plans:
|
||||
**Wave 1**
|
||||
|
||||
- [x] 23-01-PLAN.md — USER_AWARENESS verdict + acknowledge_user action + customer-visible note writer (noteType 18) (CLASSDISP-01, CLASSDISP-02)
|
||||
- [x] 23-02-PLAN.md — Review UI: USER_AWARENESS badge + acknowledge_user manual action (CLASSDISP-03)
|
||||
- [x] 23-03-PLAN.md — Migration 100 phishing_automation_gate + admin GET/PATCH/DELETE API (AUTOGATE-01)
|
||||
|
||||
**Wave 2** *(blocked on Wave 1 completion)*
|
||||
|
||||
- [x] 23-04-PLAN.md — /admin/phishing-automation page (3-toggle company table) + admin index tile (AUTOGATE-02)
|
||||
- [x] 23-05-PLAN.md — Gate reader + gated parse->classify->acknowledge webhook chain (D-04 carve-out) (AUTOGATE-03)
|
||||
|
||||
**Gap closure** *(from 23-VERIFICATION.md, Truth #18 / CR-01)*
|
||||
|
||||
- [x] 23-06-PLAN.md — Idempotent + audited auto-post: autoPostAcknowledgment prevents duplicate customer-visible notes on repeat campaign webhooks (AUTOGATE-03)
|
||||
|
||||
</details>
|
||||
|
||||
## Progress
|
||||
|
||||
**Execution Order:**
|
||||
Phases execute in numeric order. v1.0 (Phases 1-9.1) shipped 2026-07-10. v2.0 (Phases 10-14) shipped 2026-07-12. v3.0 (Phases 15-23) shipped 2026-07-17 — phases ran 15 → 16 → 17 → 18 → 19 → 20 → 21 → 22 → 23 in strict sequence; Phase 17 had no functional dependency on Phase 16 and could have run in parallel with it if split across two workstreams, but both had to complete before Phase 19; Phase 22 depended only on Phase 19 and Phase 20 and could equally have run in parallel with Phase 21; Phase 23 was a late addition depending on Phases 17-22.
|
||||
|
||||
| Phase | Milestone | Plans Complete | Status | Completed |
|
||||
|-------|-----------|----------------|--------|-----------|
|
||||
| 1. PWA Scaffolding | v1.0 | 2/2 | Complete | 2026-07-10 |
|
||||
| 2. Mobile Shell + More Drawer | v1.0 | 2/2 | Complete | 2026-07-10 |
|
||||
| 3. Dashboard Restyle | v1.0 | 2/2 | Complete | 2026-07-10 |
|
||||
| 4. Tickets Restyle | v1.0 | 3/3 | Complete | 2026-07-10 |
|
||||
| 5. Finance Restyle | v1.0 | 2/2 | Complete | 2026-05-03 |
|
||||
| 6. Analyzer Feed | v1.0 | 3/3 | Complete | 2026-07-10 |
|
||||
| 7. Engagement Overview | v1.0 | 3/3 | Complete | 2026-07-10 |
|
||||
| 7.1. User Timezone Fix | v1.0 | 5/5 | Complete | 2026-07-10 |
|
||||
| 8. Engagement User Profile | v1.0 | 2/2 | Complete | 2026-07-10 |
|
||||
| 9. User Profile & Preferences | v1.0 | 6/6 | Complete | 2026-07-10 |
|
||||
| 9.1. ntfy Backend Fix | v1.0 | 1/1 | Complete | 2026-07-10 |
|
||||
| 10. PAX8 Client & Auth Foundation | v2.0 | 3/3 | Complete | 2026-07-10 |
|
||||
| 11. Company, Catalog & Subscription Sync | v2.0 | 3/3 | Complete | 2026-07-11 |
|
||||
| 12. Orders/Invoices & Company Matching | v2.0 | 5/5 | Complete | 2026-07-11 |
|
||||
| 13. Scheduler & Admin Toggle | v2.0 | 3/3 | Complete | 2026-07-11 |
|
||||
| 14. /pax8 UI Surface | v2.0 | 6/6 | Complete | 2026-07-12 |
|
||||
| 15. Data Model, Detection & Ticket Evidence | v3.0 | 3/3 | Complete | 2026-07-15 |
|
||||
| 16. EML/MIME Evidence Parser | v3.0 | 3/3 | Complete | 2026-07-15 |
|
||||
| 17. Mimecast Blast Radius Lookup | v3.0 | 1/1 | Complete | 2026-07-15 |
|
||||
| 18. Campaign Grouping & Phishing Analysis API | v3.0 | 5/5 | Complete | 2026-07-16 |
|
||||
| 19. Classification Engine | v3.0 | 2/2 | Complete | 2026-07-16 |
|
||||
| 20. Remediation, Approval & Audit Safety | v3.0 | 2/2 | Complete | 2026-07-16 |
|
||||
| 21. Autotask Triage Note | v3.0 | 2/2 | Complete | 2026-07-16 |
|
||||
| 22. Approval UI (LiveLink) | v3.0 | 6/6 | Complete | 2026-07-16 |
|
||||
| 23. Classification Disposition + Per-Client Automation Gate | v3.0 | 6/6 | Complete | 2026-07-17 |
|
||||
|
||||
---
|
||||
*Roadmap created: 2026-05-03*
|
||||
*v2.0 phases added: 2026-07-10*
|
||||
*v3.0 phases added: 2026-07-14 (Phases 15-21), 2026-07-16 (Phase 22, Phase 23), shipped 2026-07-17*
|
||||
*Source spec (v1.0): `docs/superpowers/specs/2026-05-03-mobile-shell-design.md`*
|
||||
*Source seed (v2.0): `.planning/seeds/SEED-002-pax8-integration.md`*
|
||||
*Source requirements (v3.0, archived): `.planning/milestones/v3.0-REQUIREMENTS.md`*
|
||||
</content>
|
||||
Loading…
Add table
Add a link
Reference in a new issue