Commit graph

964 commits

Author SHA1 Message Date
98c7153a10 docs(23): capture phase context 2026-07-16 18:37:04 -04:00
8a11a9f331 docs(23): add Classification Disposition + Per-Client Automation Gate phase
Scoping a dedicated "User Awareness" verdict for confirmed phishing-simulation
reports (KnowBe4/Breach Secure Now) plus a per-company automation gate for the
parse/classify/report-to-ticket pipeline stages. Surfaced live during Phase 22
review of a real Breach Secure Now report (ticket 699415).
2026-07-16 18:25:27 -04:00
1c9ce682e0 docs(260716-pgr): record quick task completion in STATE.md 2026-07-16 18:21:54 -04:00
3f16268152 fix(260716-pgr): confidence display shows percent, not raw 0-1 scale
classifications.confidence is 0.0-1.0 (1 = max confidence). ClassificationCard
appended "%" directly to the raw value, so confidence=1 rendered as "1%
confidence" -- read as near-zero, the opposite of its true meaning. Found live
while reviewing a Breach Secure Now simulated-phishing classification.
2026-07-16 18:21:40 -04:00
40dd1e10e9 docs(260716-n46): record quick task completion in STATE.md
Mimecast blast-radius fixes: future end-date no longer silently swallowed
into a false-clean result, and campaigns from companies with their own
registered mimecast_tenants row (e.g. Seubert & Associates) now resolve
against that tenant instead of always querying the global Wulf tenant.
2026-07-16 16:50:10 -04:00
50c55eec98 Merge branch 'worktree-agent-aa777fce7f2c01045' 2026-07-16 16:49:36 -04:00
4c0b1ff5aa docs(260716-n46): append self-check result to SUMMARY 2026-07-16 16:48:43 -04:00
1e384d5c9e docs(260716-n46): add plan SUMMARY and deferred-items log
Documents the getBlastRadius per-tenant injection/swallowed-error fixes,
the campaign detail route's date-window clamp + tenant resolution, the
in-scope test-hygiene deviation, and the pre-existing out-of-scope
itglue-search.test.ts failure.
2026-07-16 16:48:29 -04:00
9951e53832 fix(260716-n46): clamp blast-radius date window and resolve per-company Mimecast tenant
Bug 1: clamp dateWindow.end to Math.min(createdAt + 24h, Date.now()) so a
freshly-detected campaign (<24h old primary report) never sends Mimecast a
future end-date -- previously rejected as err_track_and_trace_invalid_end_date
and swallowed internally as a false-clean zero-count result.

Bug 2 (D-05): add company_id to the reports SELECT and, when the reporting
company has its own enabled mimecast_tenants row, resolve a tenant-scoped
client via getMimecastClientForTenant() and thread it into getBlastRadius as
{ client, cacheScope: companyId }. Falls back to the global env-configured
client when no company-specific tenant is registered.
2026-07-16 16:47:19 -04:00
12250c1e1d test(260716-n46): add coverage for getMimecastClientForTenant
Covers the already-implemented per-tenant factory: returns a MimecastClient
instance, builds a new independent instance per call (never the cached
global), doesn't affect getMimecastClient()'s singleton, and defaults
base_url when omitted. Uses fake credentials only.
2026-07-16 16:44:54 -04:00
7c724cc489 feat(260716-n46): support per-tenant client injection + surface swallowed delivered-search errors
- getBlastRadius(input, options?) accepts an optional injected MimecastClient
  and cacheScope; an injected client bypasses the global isMimecastConfigured()
  gate since it carries its own credentials
- cache key namespaced by cacheScope to prevent cross-tenant collisions
- deliveredResult.error (previously swallowed) now rethrown so the outer
  catch converts it to status: unavailable / reason: lookup_failed --
  defense-in-depth against Bug 1 (future end-date rejected by Mimecast)
- test mock hygiene: getMimecastClientMock now cleared in beforeEach
2026-07-16 16:44:23 -04:00
4d54abacae test(260716-n46): add failing tests for tenant client injection + swallowed-error surfacing
- Fake tenant client via options.client bypasses getMimecastClient
- Injected tenant client runs fan-out even when global env unconfigured
- searchDeliveredMessages error field now expected to degrade to unavailable/lookup_failed
2026-07-16 16:43:37 -04:00
ad84885daa docs(260716-n46): pre-dispatch plan for Mimecast blast-radius fixes 2026-07-16 16:42:23 -04:00
ced258bbda docs(22): mark phase 22 complete — approval UI (LiveLink) shipped
All 6 plans merged: pure-logic extraction (resolver/default-params/timeline),
API read surface, evidence/classification/timeline cards, action-area card,
and the ticket-scoped review page + campaigns list + nav entry. Blocking
LiveLink checkpoint verified against a real Autotask ticket (numeric ticket
ID confirmed, no ticket_number fallback needed).
2026-07-16 15:40:30 -04:00
897bc67b9e Merge branch 'worktree-agent-a2447e1cd43bb4c28' 2026-07-16 15:39:28 -04:00
4b2a7713b4 docs(22-06): complete LiveLink review page plan
Task 3 (blocking human-verify) resolved as "verified": a real production
Autotask LiveLink click against ticket 699340 confirmed the numeric-
ticket-id resolver assumption, no ticket_number fallback needed. Marks
REVIEW-01, REVIEW-05, REVIEW-06 complete in REQUIREMENTS.md.
2026-07-16 15:39:12 -04:00
5f5d809050 feat(22-06): campaigns list page + Phishing nav entry (D-00, D-02)
- app/phishing/page.tsx: minimal DataTable-backed campaigns list, row click
  navigates to /phishing/tickets/{firstReportTicketId}, EmptyState when no
  campaigns exist yet
- components/navigation/app-navigation.tsx: add flat "Phishing" nav item
  (ShieldAlert icon) immediately after PAX8, visible to all roles (every
  role has phishing:read)
2026-07-16 14:58:52 -04:00
3761312f93 feat(22-06): ticket-scoped LiveLink review page (REVIEW-01,05,06)
- app/phishing/tickets/[ticketId]/page.tsx: resolves ticket->campaign via
  the plan-02 resolver route, drives a loading/not-triaged/ungrouped/ready/
  error state machine, branches ready into grouped-but-unclassified
  (Classify CTA, no ClassificationCard/ActionAreaCard) vs. classified (all
  four cards with explicit props), refetches after every action (D-04),
  session-only auth (no token/query-param scheme)
- app/api/phishing/reports/[report_id]/route.ts (new, additive): thin
  report-scoped evidence + fresh blast-radius lookup for the D-08
  ungrouped-report state, which has no campaignId to key the existing
  campaign-detail route on — added as a Rule 2 dependency since the plan's
  own D-08 truth ("standalone-report notice + evidence") has no other data
  source
2026-07-16 14:58:39 -04:00
bced734717 Merge branch 'worktree-agent-a68fa4bcada53d003' 2026-07-16 14:45:47 -04:00
81734820d5 docs(22-02): append self-check result to SUMMARY
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W6RuWdiUiXrPK6FLBHjtpY
2026-07-16 14:45:25 -04:00
de13cd73b3 docs(22-02): complete review page read surface plan
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W6RuWdiUiXrPK6FLBHjtpY
2026-07-16 14:45:03 -04:00
0b5fc36cd5 Merge branch 'worktree-agent-ac9c5343cdf410faf' 2026-07-16 14:44:19 -04:00
a8bbd05e6b docs(22-05): append self-check results to SUMMARY
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-16 14:43:52 -04:00
c70b30a000 feat(22-02): add firstReportTicketId to campaigns list route
- Alias campaigns table as c, add correlated subquery for the earliest
  linked report's ticket_id so the list page can navigate a row click
  straight to /phishing/tickets/{firstReportTicketId}
- Additive only: count query, limit/offset, requirePermission gate, and
  the { items, total, limit, offset } envelope all unchanged
2026-07-16 14:43:43 -04:00
e78f1b0cdf docs(22-05): complete Action Area Card plan
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-16 14:43:35 -04:00
9e83ec09ee feat(22-02): extend campaign detail route with evidence, timeline, blast radius
- Widen messages query to include headers/urls/attachments/body_preview
- Widen classifications query to include reasons/recommendedActions/
  requiresApproval
- Add remediationActions (with completedAt derived from audit_events
  payload.actionId, no completion-timestamp column exists) and
  auditEvents to the response
- Add fresh per-request blastRadius via getBlastRadius(), sender/
  recipient/subject derivation copied from campaign-classifier.ts's
  gatherCampaignEvidence (not triage-note-service's empty-string call)
- Add mergeTimeline()-derived chronological timeline
- All additive — existing fields, UUID_RE guard, and auth gate unchanged
2026-07-16 14:42:56 -04:00
1a7a5922fe feat(22-05): remediate + mark-false-positive + permission/resolved gating (REVIEW-06, D-05, D-06)
- Add GatedButton: buttons stay in the DOM (D-05) always, wrapped in a
  Tooltip explanation when disabled
- Gate all three actions with hasPermission(role, 'phishing', 'approve'|
  'remediate') from lib/permissions.ts — the identical check the server
  routes enforce, never a bespoke role === 'admin' string check (REVIEW-06)
- Derive resolved = campaignStatus === 'false_positive' OR any completed
  remediation_actions row; resolved-state tooltip mirrors the completed
  action's approver/date or the campaign's updated_at
- Remediate: AlertDialog confirmation listing approved action count/types,
  then POST /remediate (no body), refetch on success
- Mark as false positive: AlertDialog with optional reason Textarea, then
  POST /mark-false-positive { reason? }; disabled reason mirrors the
  server's 409 guard exactly (approved/completed remediation blocks it)
2026-07-16 14:42:05 -04:00
ca63910562 feat(22-02): add ticket->campaign resolver route
- New GET /api/phishing/tickets/{ticket_id}/campaign wraps
  resolveTicketToCampaign() from plan 22-01
- requirePermission('phishing','read') gate, Number.isFinite param
  validation, D-07: found:false at 200 (not 404) for untriaged tickets
2026-07-16 14:41:06 -04:00
11190abafd feat(22-05): checkbox list + editable params + Approve selected (D-03)
- Create ActionAreaCard with a null-guard on classification (renders an
  informational note, never dereferences recommendedActions, for the
  default grouped-but-unclassified state)
- Render one checkbox row per recommended action with an always-visible
  params form pre-filled via deriveDefaultParams(actionType, evidence)
- Submit exact ApproveActionInput[] to POST /approve; purge_message
  mailboxes is edited as a comma-separated string and normalized to
  string[] at submit time
- Refetch via onActionComplete() on success (D-04, no optimistic mutation)
2026-07-16 14:40:00 -04:00
75cd454ff8 Merge branch 'worktree-agent-a432f929ffab2e5b8'
# Conflicts:
#	.planning/phases/22-approval-ui-livelink-addressable-campaign-review-and-approve/deferred-items.md
2026-07-16 14:33:16 -04:00
4f976b741f chore: merge executor worktree (worktree-agent-a972575d901be6c4f)
# Conflicts:
#	.planning/phases/22-approval-ui-livelink-addressable-campaign-review-and-approve/deferred-items.md
2026-07-16 14:32:29 -04:00
2c70747822 chore: merge executor worktree (worktree-agent-a5b1c5010c37561a1) 2026-07-16 14:32:03 -04:00
d81fc46dc0 docs(22-03): record self-check result in summary 2026-07-16 14:31:17 -04:00
f7516c04c7 docs(22-03): complete evidence display plan 2026-07-16 14:30:59 -04:00
fdc6a52dde docs(22-04): complete ClassificationCard + TimelineCard plan
- Add 22-04-SUMMARY.md documenting the two components delivered
- Log pre-existing unrelated itglue-search.test.ts failures to
  deferred-items.md (out of scope for this plan)
- Mark REVIEW-02, REVIEW-04 complete in REQUIREMENTS.md
2026-07-16 14:30:52 -04:00
9702847131 docs(22-01): append self-check results to SUMMARY.md
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W6RuWdiUiXrPK6FLBHjtpY
2026-07-16 14:30:38 -04:00
96507dc2c6 docs(22-01): complete Wave 0 pure-logic extraction plan
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W6RuWdiUiXrPK6FLBHjtpY
2026-07-16 14:30:18 -04:00
2d5761bf6f docs(22-03): log pre-existing itglue-search test failures as deferred
Out-of-scope failures observed during npm test for plan 22-03; unrelated
to this plan's files.
2026-07-16 14:29:40 -04:00
4ec5ba4979 feat(22-03): add EvidenceCard tabbed EML evidence display (REVIEW-03)
- components/phishing/evidence-card.tsx renders Headers, URLs,
  Attachments, Body preview, and Blast Radius tabs for a selected
  message
- Body preview renders inside a <pre className="whitespace-pre-wrap">
  as plain JSX text only, never via a raw-HTML injection prop
- URLs tab delegates to UrlList (D-09 inert copy-only)
- Blast radius renders explicit unavailable-state copy or a
  matched/delivered/held/rejected/clicked stat row + per-recipient
  table when ok
- CardTitle explicitly overridden with font-bold per UI-SPEC typography
2026-07-16 14:29:34 -04:00
e990a320b2 feat(22-04): add TimelineCard chronological event renderer
- Merges reports/classifications/audit-events into one ascending list
  (relies on server ordering, no client-side sort)
- Per-kind icon/label/tint: FileText for reports, Sparkles tinted by
  verdict for classifications, event_type table for audit rows
  (remediation_approved/completed, campaign_marked_false_positive,
  campaign_classified, humanized fallback for anything else)
- 8px rail dot + border-l connector per UI-SPEC Timeline Spec
2026-07-16 14:29:15 -04:00
d30a49f644 feat(22-01): implement mergeTimeline for reports/classifications/audit merge
- Discriminated union TimelineEntry with report/classification/audit variants
- Ascending sort by createdAt with stable report<classification<audit tie-break
2026-07-16 14:28:27 -04:00
37f6657839 test(22-01): add failing test for mergeTimeline
- Covers ascending chronological sort across reports/classifications/audit
- Asserts discriminant kind + source fields per variant, plus stable tie-break order
2026-07-16 14:27:59 -04:00
86cffc45b8 feat(22-01): implement deriveDefaultParams for the 7 remediation action types
- Pure switch over no_action/warn_user/block_sender/purge_message/
  reset_password/isolate_endpoint/disable_forwarding_rule
- Unknown/future action types fall through to {} rather than throwing
2026-07-16 14:27:33 -04:00
14adddfdf0 feat(22-04): add ClassificationCard read-only verdict display
- Renders latest classification verdict/confidence/summary/reasons
- Recommended-action chips (informational, no checkboxes)
- Requires-approval warning Alert when requiresApproval is true
- Reclassify button gated on hasPermission(role, 'phishing', 'analyze')
- Returns null when classification is missing (empty-state handled by plan 06)
2026-07-16 14:27:29 -04:00
c64a90572d test(22-01): add failing test for deriveDefaultParams
- Covers all 7 known action types plus unknown-type fallback
- Asserts null-evidence empty-string fallback behavior
2026-07-16 14:27:13 -04:00
87008a5da6 feat(22-03): add tooltip primitive + inert UrlList component (D-09)
- npx shadcn add tooltip generates components/ui/tooltip.tsx (official
  registry, no npm dependency added)
- components/phishing/url-list.tsx renders extracted URLs as inert
  <code> text with copy-to-clipboard only — no <a>/href, no <Link>,
  no navigating onClick per D-09
2026-07-16 14:27:11 -04:00
e619321b4b feat(22-01): implement resolveTicketToCampaign resolver service
- Pure lookup: reports row for a ticket id -> found/reportId/campaignId/ticketNumber
- Parameterized query only (WHERE ticket_id = $1), no requirePermission/NextResponse
2026-07-16 14:26:32 -04:00
20b1e1bb6f test(22-01): add failing test for resolveTicketToCampaign
- Covers no-report, ungrouped, and grouped resolution states
- Asserts parameterized ticket_id lookup query shape
2026-07-16 14:26:28 -04:00
4b5e31c068 docs(22): create phase plan 2026-07-16 14:10:33 -04:00
476eb017eb fix(22): resolve checker blockers (unclassified default state, LiveLink id gate) 2026-07-16 14:00:52 -04:00