Commit graph

685 commits

Author SHA1 Message Date
55ab2c2e05 chore: merge executor worktree (worktree-agent-af09d50545da22d95) 2026-07-15 22:27:01 -04:00
eb660354ef docs(18-04): record self-check results in plan summary 2026-07-15 22:26:37 -04:00
21fde9eb3d docs(18-04): complete gap-closure plan summary
- CAMP-02 report_count double-increment fix and WR-02 limit-clamp fix, with
  a passing regression test and clean type check
2026-07-15 22:26:21 -04:00
abe3d4b900 fix(18-04): clamp campaigns list limit param to [0, 200] (WR-02)
- Parse limit once with Number.isFinite instead of `|| 50`, so an explicit
  limit=0 is honored instead of silently replaced by the default
- Math.max/min clamps to [0, 200], preventing a negative limit from reaching
  the SQL LIMIT clause and raising an unhandled 500
2026-07-15 22:25:29 -04:00
9ca2ccf1c7 fix(18-04): short-circuit own-campaign re-match to stop report_count double-increment
- OwnReportRow now selects campaign_id::text; match branch no-ops (created:
  false, no UPDATE) when the tiered match resolves to the report's own
  current campaign_id via a sibling row
- Closes CAMP-02 gap / CR-01: /analyze can be re-run indefinitely without
  inflating campaigns.report_count
2026-07-15 22:25:04 -04:00
afd7af70c8 test(18-04): add failing regression test for campaign report_count double-increment
- Sibling report already in campaign-1 re-matches via Tier 3; asserts zero
  UPDATE campaigns / INSERT campaigns / UPDATE reports calls (CAMP-02/CR-01)
2026-07-15 22:24:17 -04:00
20f3e1bbb5 docs(18): create gap-closure plan 18-04 2026-07-15 22:22:17 -04:00
ea71579fb0 fix(phase-18): revert premature roadmap completion marker after gaps_found verification 2026-07-15 19:49:37 -04:00
7f51d49839 docs(phase-18): add verification report (gaps found) 2026-07-15 19:49:01 -04:00
bd90cdcb30 docs(18): add code review report 2026-07-15 19:43:12 -04:00
95859660d6 docs(phase-18): update tracking after wave 2 2026-07-15 19:34:50 -04:00
701fea04cc chore: merge executor worktree (worktree-agent-a985b30606e37e1d0) 2026-07-15 19:33:32 -04:00
c0967b5a9c chore: merge executor worktree (worktree-agent-a9f362ad9e34b1a2a) 2026-07-15 19:33:32 -04:00
a544024e75 docs(18-02): complete on-demand analyze route + automatic grouping wiring plan
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-15 19:33:00 -04:00
86f5166a86 docs(18-03): complete campaign read API plan
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-15 19:32:31 -04:00
959907d63b feat(18-03): add GET /api/phishing/campaigns/[id] nested detail
- requirePermission('phishing','read') gate (ACCESS-01)
- UUID-validated id (400 on malformed), 404 when campaign absent
- bulk-fetch reports/messages/indicators via ANY($1::uuid[]) keyed by parent id array (device-link-conflicts pattern)
- requesterEmail derived via reports.requester_contact_id -> contacts join (campaigns has no recipients column)
- messages.subject pulled from headers->>'subject' JSONB (no subject column)
- classifications included in shape (Phase 19 stub, expected empty)
2026-07-15 19:31:35 -04:00
c852cfee13 feat(18-03): add GET /api/phishing/campaigns paginated list
- requirePermission('phishing','read') gate (ACCESS-01)
- limit/offset clamped, optional status filter via parameterized $n placeholder (never string-interpolated)
- camelCase response { items, total, limit, offset }
2026-07-15 19:31:19 -04:00
19b8b4b415 feat(18-02): wire groupReportIntoCampaign into webhook + cron sweep paths
- webhook-service.ts: triggerPhishingDetection calls groupReportIntoCampaign
  with skipIfAlreadyGrouped:true after a flagged detection (D-01, D-08)
- phishing-sweep-service.ts: per-ticket sweep loop calls the same, inside the
  existing try/catch so a grouping failure counts against result.errors
  without aborting the sweep
2026-07-15 19:31:18 -04:00
de013e6ec1 feat(18-02): add POST /api/phishing/tickets/[ticket_id]/analyze route
- Orchestrates detectPhishingTicket -> parseAndStoreMessage -> groupReportIntoCampaign
- requirePermission('phishing','analyze') gate first-line (D-06, 401/403)
- Validates ticket_id numeric (400), missing ticket (404), non-phishing ticket (400)
- No skipIfAlreadyGrouped (D-08) — always re-runs grouping on demand
2026-07-15 19:30:46 -04:00
c77b7edae7 docs(phase-18): update tracking after wave 1 2026-07-15 19:27:40 -04:00
19f05dd564 chore: merge executor worktree (worktree-agent-a7caddbc3f0fc1d30) 2026-07-15 19:25:52 -04:00
e76ab1bf43 docs(18-01): complete campaign grouping core + phishing permission resource plan
Summary for Phase 18 Plan 01 — groupReportIntoCampaign tiered matching,
self-exclusion safeguard, and the phishing permission resource. Requirements
CAMP-01, CAMP-02, ACCESS-01 complete.
2026-07-15 19:25:18 -04:00
04ec51f370 feat(18-01): add phishing permission resource + role grants
- statement gets the full D-05 vocabulary now: read/analyze/approve/remediate
- superAdminRole and adminRole grant read+analyze
- userRole grants read only (cannot trigger /analyze)
- approve/remediate declared but ungranted to any role until Phase 20
2026-07-15 19:23:09 -04:00
da926bbe97 feat(18-01): implement groupReportIntoCampaign tiered matching
- Tiered find-or-create inside postgresClient.transaction (Pitfall 2 —
  campaigns.campaign_key has no UNIQUE constraint): Tier 1 Message-ID,
  Tier 2 attachment-hash/URL-domain + subject + sender + 24h, Tier 3
  sender + normalized subject + client + 24h (CAMP-01)
- Match path bumps report_count/last_seen_at and links reports.campaign_id
  without creating a second campaign; no-match path inserts a new
  campaigns row keyed by the strongest available tier signal (CAMP-02)
- skipIfAlreadyGrouped short-circuits before the transaction (D-08); the
  /analyze route path always re-runs full tiered matching
- Every tier query excludes the report's own id (r.id != $n) so a
  self-match against a report's own messages/indicators can never
  double-increment its already-linked campaign on re-run
- D-07 doc comment states the Tier-3-only automatic-path limitation:
  parseAndStoreMessage is not wired into the webhook/cron path this phase
2026-07-15 19:22:31 -04:00
ea677b7aba test(18-01): add campaign-grouping tier-key helpers + test scaffold
- normalizeSubject strips repeated Re:/Fwd:/Fw: prefixes case-insensitively, lowercases, trims (D-03)
- extractUrlDomain returns hostname or null (never throws) for malformed URLs (Pitfall 4)
- test file mocks ./postgres-client before import, mirroring phishing-eml-service.test.ts's discipline
2026-07-15 19:19:44 -04:00
78c984dceb docs(state): record phase 18 planning session 2026-07-15 16:51:02 -04:00
a494cd0c0b docs(18): fix plan-checker warnings (tier self-exclusion + resolved open questions) 2026-07-15 16:50:46 -04:00
808604e861 docs(18): create phase plan (3 plans, 2 waves) 2026-07-15 16:45:29 -04:00
ffdd86a0ed docs(18): add pattern map 2026-07-15 16:37:37 -04:00
1c6d2fdde5 docs(phase-18): add validation strategy 2026-07-15 16:34:20 -04:00
e05d34640c docs(18): add research findings + EML-parsing scope decision 2026-07-15 16:33:49 -04:00
4ccd379dc3 docs(18): research phase domain 2026-07-15 16:22:25 -04:00
0ec690b261 docs(state): record phase 18 context session 2026-07-15 15:39:03 -04:00
a37dcebd2d docs(18): capture phase context 2026-07-15 15:39:03 -04:00
4decefb940 docs(requirements): mark EVID-02/03/04 complete (phase 16 gap) 2026-07-15 15:19:59 -04:00
3fe1c02570 docs(phase-17): mark phase complete 2026-07-15 14:34:03 -04:00
fc7e47532b docs(phase-17): update tracking after wave 1 2026-07-15 14:31:59 -04:00
30bfa127bb chore: merge executor worktree (worktree-agent-a5e56aaa233a4df21) 2026-07-15 14:31:40 -04:00
eaee6292dc docs(17-01): record self-check result in SUMMARY.md
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-15 14:31:22 -04:00
2bb1817e8d docs(17-01): complete Mimecast blast-radius lookup plan
Mark BLAST-01/BLAST-02 complete in REQUIREMENTS.md and add the plan's
SUMMARY.md documenting getBlastRadius() delivery.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-15 14:31:10 -04:00
efbc437e2e feat(17-01): build mimecast-blast-radius.ts fan-out orchestration
- Add getBlastRadius(): never-throwing orchestration that fans out to
  searchDeliveredMessages + getHeldMessages + getThreatEvents (D-01,
  unconditional fan-out) and merges into normalized matched/delivered/
  held/rejected/clicked counts + perRecipient status array
- Config gate (BLAST-02): returns status:'unavailable' reason:'not_configured'
  synchronously when Mimecast is unconfigured, never constructs the client
- Redis-backed 5-min cache (D-04) via redis-client.ts, short-circuits before
  any MimecastClient call on hit
- clicked derived best-effort from getThreatEvents() analysis[] (D-02);
  documents the /api/ttp/url/get-logs limitation in code
- Documents D-05 known limitation: single global getMimecastClient() only,
  not per-company mimecast_tenants
- Unrecognized delivered-message status strings treated conservatively as
  non-rejected (A3 unconfirmed enum), raw values logged at debug level
- Add lib/services/mimecast-blast-radius.test.ts covering config gate,
  cache-hit short-circuit, fan-out merge, never-throw-on-error, and
  unknown-recipient classification
- Log pre-existing unrelated itglue-search.test.ts failures to
  deferred-items.md (out of scope for this plan)
2026-07-15 14:30:00 -04:00
8b032c3890 feat(17-01): add isMimecastConfigured() config gate + test seam
- Add isMimecastConfigured() to lib/services/mimecast-client.ts mirroring
  the pax8-factory.ts is<Name>Configured() convention
- Add _resetMimecastClient() test seam so tests can isolate env-var state
- Add lib/services/mimecast-client.test.ts covering config gate + throw/cache
  behavior of getMimecastClient()
2026-07-15 14:27:47 -04:00
683d90edd0 docs(state): record phase 17 planning session 2026-07-15 13:48:04 -04:00
3424812d60 docs(17): fix plan-checker warnings (merge formula clarity + resolved open questions) 2026-07-15 13:47:48 -04:00
7303b16cbb docs(17): create phase plan (1 plan, 1 wave) 2026-07-15 13:44:14 -04:00
78ff39fa48 docs(17): add pattern map 2026-07-15 13:39:21 -04:00
c702cb9b03 docs(phase-17): add validation strategy 2026-07-15 13:35:57 -04:00
9dbc1e7510 docs(17): add research findings + tenant-scope decision 2026-07-15 13:35:21 -04:00
cacf41a493 docs(17): research phase domain 2026-07-15 13:28:58 -04:00
e30d9af46d docs(state): record phase 17 context session 2026-07-15 13:13:37 -04:00